diff options
Diffstat (limited to '')
-rw-r--r-- | beetsplug/__init__.py | 2 | ||||
-rw-r--r-- | beetsplug/browse.py | 82 | ||||
-rw-r--r-- | beetsplug/extras.py | 44 |
3 files changed, 128 insertions, 0 deletions
diff --git a/beetsplug/__init__.py b/beetsplug/__init__.py new file mode 100644 index 0000000..3ad9513 --- /dev/null +++ b/beetsplug/__init__.py @@ -0,0 +1,2 @@ +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) diff --git a/beetsplug/browse.py b/beetsplug/browse.py new file mode 100644 index 0000000..72999b9 --- /dev/null +++ b/beetsplug/browse.py @@ -0,0 +1,82 @@ +import subprocess +import uuid +import webbrowser + +from beets.plugins import BeetsPlugin +from beets.ui import Subcommand, UserError + +MUSICBRAINZ_LOOKUP='https://musicbrainz.org/otherlookup/mbid?other-lookup.mbid=' +FIELD_NAMES = ['albumartist', 'album', 'artist', 'releasegroup', 'releasetrack', 'track', 'work'] + +class BrowsePlugin(BeetsPlugin): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def commands(self): + return [BrowseCommand(self.config)] + +class BrowseCommand(Subcommand): + + explorer = 'thunar' + + def __init__(self, config): + super().__init__('browse', parser=None, + help='browse items on MusicBrainz or the file system') + + self.parser.add_option('-f', '--field', type='string', + help='which field to look up on MusicBrainz, ' + 'e.g. album, artist, track, work, ...') + self.parser.add_option('-o', '--open', action='store_true', + help='open in the file browser instead of MusicBrainz') + self.parser.add_album_option() + + if 'explorer' in config: + self.explorer = config['explorer'].get() + + # pylint: disable=no-self-use + def browse_musicbrainz(self, item, field): + mbid = item.get(f'mb_{field}id') + if not mbid: + raise UserError(f'\'mb_{field}id\' not available for: {item}') + + try: + uuid.UUID(mbid) + except ValueError: + raise UserError(f'invalid UUID "{mbid}" for: {item}') from None + + webbrowser.open(MUSICBRAINZ_LOOKUP + mbid) + + def browse_filesystem(self, item, _): + try: + subprocess.Popen(self.explorer.split(' ') + [item.get('path')]) # pylint: disable=consider-using-with + except OSError as err: + raise UserError(err) from err + + def func(self, lib, opts, args): + queryfun = lib.albums if opts.album else lib.items + browsefun = self.browse_filesystem if opts.open else self.browse_musicbrainz + field = opts.field or ('album' if opts.album else 'track') + + if field not in FIELD_NAMES: + raise UserError(f'invalid field "{field}", try one of: {", ".join(FIELD_NAMES)}') + + if not args: + raise UserError('empty query, refusing') + + items = queryfun(args) + + if not items: + return + + if len(items) == 1: + browsefun(items[0], field) + return + + print('Query returned multiple matches, please disambiguate:') + for index, item in enumerate(items): + if index < 5: + print(item) + else: + print(f'[{len(items) - index} more]') + return diff --git a/beetsplug/extras.py b/beetsplug/extras.py new file mode 100644 index 0000000..32fe3f6 --- /dev/null +++ b/beetsplug/extras.py @@ -0,0 +1,44 @@ +import os +import shutil +import beets.plugins + +class ExtrasPlugin(beets.plugins.BeetsPlugin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.register_listener('item_moved', self.on_item_moved) + self.register_listener('item_copied', self.on_item_copied) + + def on_item_moved(self, _, source, destination): + for (sourcepath, destpath) in self.gather(source, destination): + os.rename(sourcepath, destpath) + + def on_item_copied(self, _, source, destination): + for (sourcepath, destpath) in self.gather(source, destination): + if os.path.isdir(sourcepath): + shutil.copytree(sourcepath, destpath) + else: + shutil.copy(sourcepath, destpath) + + def gather(self, source, destination): + candidates = [] + sourcedir = os.path.dirname(source) + destdir = os.path.dirname(destination) + + if sourcedir == destdir: + return [] + + paths = [beets.util.bytestring_path(p) for p in self.config['paths'].get()] + + for path in paths: + sourcepath = os.path.join(sourcedir, path) + if not os.path.exists(sourcepath): + continue + + destpath = os.path.join(destdir, path) + if os.path.exists(destpath): + continue + + candidates.append((sourcepath, destpath)) + + return candidates |