aboutsummaryrefslogblamecommitdiffstats
path: root/beetsplug/browse.py
blob: c1d37c72bd00d2b07417d2578751c468d631d03e (plain) (tree)
1
2
3
4
5
6
7






                                          










                                                                                  








                                           
 

                                
                       

                               




















                                                                   

                                      

                                                    
 
                                              
                                        
                    
                                                                        








                                                                           




                               
            




                                                        





                                                                                    
                                                                  

                                    


                                                                                 

                    
                                                    









                                      
                                                                      



                                            
                                                     
                      
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