diff --git a/beetsplug/fuzzy_search.py b/beetsplug/fuzzy_search.py new file mode 100644 index 000000000..4e93a4ee3 --- /dev/null +++ b/beetsplug/fuzzy_search.py @@ -0,0 +1,101 @@ +# This file is part of beets. +# Copyright 2011, Philippe Mongeau. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Like beet list, but with fuzzy matching +""" +from beets.plugins import BeetsPlugin +from beets.ui import Subcommand, decargs, print_ +from beets.util.functemplate import Template +import difflib + +# THRESHOLD = 0.7 + + +def fuzzy_score(query, item): + return difflib.SequenceMatcher(a=query, b=item).quick_ratio() + + +def is_match(query, item, album=False, verbose=False, threshold=0.7): + query = ' '.join(query) + + if album: + values = [item.albumartist, item.album] + else: + values = [item.artist, item.album, item.title] + + s = max(fuzzy_score(query.lower(), i.lower()) for i in values) + if s >= threshold: + return (True, s) if verbose else True + else: + return (False, s) if verbose else False + + +def fuzzy_list(lib, config, opts, args): + query = decargs(args) + path = opts.path + fmt = opts.format + verbose = opts.verbose + threshold = float(opts.threshold) + + if fmt is None: + # If no specific template is supplied, use a default + if opts.album: + fmt = u'$albumartist - $album' + else: + fmt = u'$artist - $album - $title' + template = Template(fmt) + + if opts.album: + objs = lib.albums() + else: + objs = lib.items() + + if opts.album: + for album in objs: + if is_match(query, album, album=True, threshold=threshold): + if path: + print_(album.item_dir()) + else: + print_(album.evaluate_template(template)) + if verbose: + print is_match(query, album, album=True, verbose=True)[1] + else: + for item in objs: + if is_match(query, item, threshold=threshold): + if path: + print_(item.path) + else: + print_(item.evaluate_template(template, lib)) + if verbose: + print is_match(query, item, verbose=True)[1] + +fuzzy_cmd = Subcommand('fuzzy', + help='list items using fuzzy matching') +fuzzy_cmd.parser.add_option('-a', '--album', action='store_true', + help='choose an album instead of track') +fuzzy_cmd.parser.add_option('-p', '--path', action='store_true', + help='print the path of the matched item') +fuzzy_cmd.parser.add_option('-f', '--format', action='store', + help='print with custom format', default=None) +fuzzy_cmd.parser.add_option('-v', '--verbose', action='store_true', + help='output scores for matches') +fuzzy_cmd.parser.add_option('-t', '--threshold', action='store', + help='return result with a fuzzy score above threshold. \ + (default is 0.7)', default=0.7) +fuzzy_cmd.func = fuzzy_list + + +class Fuzzy(BeetsPlugin): + def commands(self): + return [fuzzy_cmd] diff --git a/docs/plugins/fuzzy_search.rst b/docs/plugins/fuzzy_search.rst new file mode 100644 index 000000000..e0ecffc7f --- /dev/null +++ b/docs/plugins/fuzzy_search.rst @@ -0,0 +1,20 @@ +Fuzzy Search Plugin +============= + +The ``fuzzy_search`` plugin provides a command that search your library using +fuzzy pattern matching. This can be useful if you want to find a track with complicated characters in the title. + +First, enable the plugin named ``fuzzy_search`` (see :doc:`/plugins/index`). +You'll then be able to use the ``beet fuzzy`` command:: + + $ beet fuzzy Vareoldur + Sigur Rós - Valtari - Varðeldur + +The command has several options that resemble those for the ``beet list`` +command (see :doc:`/reference/cli`). To choose an album instead of a single +track, use ``-a``; to print paths to items instead of metadata, use ``-p``; and +to use a custom format for printing, use ``-f FORMAT``. + +The ``-t NUMBER`` option lets you specify how precise the fuzzy match has to be +(default is 0.7). To make a fuzzier search, try ``beet fuzzy -t 0.5 Varoeldur``. +A value of ``1`` will show only perfect matches and a value of ``0`` will match everything.