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() 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, _): path = item.get("path") if not path: return try: subprocess.Popen( self.explorer.split() + [path.decode()], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, ) 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