1. Adrian Sampson
  2. beets

Commits

Peter Schnebel  committed e1d855c

experimental track / album / artist based genre stuff per item

  • Participants
  • Parent commits 0829f78
  • Branches default

Comments (0)

Files changed (2)

File beets/library.py

View file
     ('albumartist_sort',   'text', True),
     ('albumartist_credit', 'text', True, True),
     ('album',              'text', True),
-    ('genre',              'text', True),
+    ('genre',              'text', False),
     ('year',               'int',  True),
     ('month',              'int',  True),
     ('day',                'int',  True),
                 self.record[key] = value
                 self.dirty[key] = True
                 if key in ITEM_KEYS_WRITABLE:
+                    log.debug(u'setting item %s = %s', key, value)
                     self.mtime = 0 # Reset mtime on dirty.
         else:
             super(Item, self).__setattr__(key, value)
 
             # Possibly make modification on items as well.
             if key in ALBUM_KEYS_ITEM:
+                log.debug('copying %s = %s to all items' % (key, value))
                 for item in self.items():
                     setattr(item, key, value)
                     self._library.store(item)

File beetsplug/lastgenre/__init__.py

View file
     pylast.NetworkError,
 )
 
-def _lastfm_obj(obj):
-    """Given a beets item or album, look up the last.fm Track, Album or
-    Artist object for which tags should be extracted.
-    """
-    source = config['lastgenre']['source'].get()
-
-    if isinstance(obj, library.Album):
-        if source == 'artist':
-            return LASTFM.get_artist(obj.albumartist)
-        else:
-            return LASTFM.get_album(obj.albumartist, obj.album)
-
-    elif isinstance(obj, library.Item):
-        if source == 'artist':
-            return LASTFM.get_artist(obj.artist)
-        else:
-            return LASTFM.get_track(obj.artist, obj.title)
-
-    else:
-        raise TypeError('obj should be an Album or Item')
-
 def _tags_for(obj):
     """Given a pylast entity (album or track), returns a list of
     tag names for that entity. Returns an empty list if the entity is
         # Just use the flat whitelist.
         return find_allowed(tags)
 
+
 def flatten_tree(elem, path, branches):
     """Flatten nested lists/dictionaries into lists of strings
     (branches).
             continue
     return [candidate]
 
+def is_allowed(genre):
+    """Returns True if the genre is present in the genre whitelist or
+    False if not.
+    """
+    if genre is None:
+        return False
+    if genre.lower() in options['whitelist']:
+        log.debug(u'verfied genre: %s' % genre)
+        return True
+    return False
+
 def find_allowed(genres):
     """Returns the first genre that is present in the genre whitelist or
     None if no genre is suitable.
     """
     for genre in list(genres):
-        if genre.lower() in options['whitelist']:
+        if is_allowed(genre):
             return genre.title()
     return None
 
+def fetch_genre(lastfm_obj):
+    tags = []
+    tags.extend(_tags_for(lastfm_obj))
+    return _tags_to_genre(tags)
+
+def fetch_album_genre(obj):
+    lookup = u'{0}-{1}'.format(obj.albumartist, obj.album)
+    if cache['album'].has_key(lookup):
+        log.debug(u'using cache: %s = %s' % (lookup, cache['album'][lookup]))
+        return cache['album'][lookup]
+    cache['album'][lookup] = \
+          fetch_genre(LASTFM.get_album(obj.albumartist, obj.album))
+    log.debug(u'setting cache: %s = %s' % (lookup, cache['album'][lookup]))
+    return cache['album'][lookup]
+
+def fetch_album_artist_genre(obj):
+    lookup = obj.albumartist
+    if cache['artist'].has_key(lookup):
+        log.debug(u'using cache: %s = %s' % (lookup, cache['artist'][lookup]))
+        return cache['artist'][lookup]
+    cache['artist'][lookup] = \
+          fetch_genre(LASTFM.get_artist(obj.albumartist))
+    log.debug(u'setting cache: %s = %s' % (lookup, cache['artist'][lookup]))
+    return cache['artist'][lookup]
+
+def fetch_artist_genre(obj):
+    lookup = obj.artist
+    if cache['artist'].has_key(lookup):
+        log.debug(u'using cache: %s = %s' % (lookup, cache['artist'][lookup]))
+        return cache['artist'][lookup]
+    cache['artist'][lookup] = \
+          fetch_genre(LASTFM.get_artist(obj.artist))
+    log.debug(u'setting cache: %s = %s' % (lookup, cache['artist'][lookup]))
+    return cache['artist'][lookup]
+
+def fetch_track_genre(obj):
+    lookup = u'{0}-{1}'.format(obj.artist, obj.title)
+    if cache['track'].has_key(lookup):
+        log.debug(u'using cache: %s = %s' % (lookup, cache['track'][lookup]))
+        return cache['track'][lookup]
+    cache['track'][lookup] = \
+          fetch_genre(LASTFM.get_track(obj.artist, obj.title))
+    log.debug(u'setting cache: %s = %s' % (lookup, cache['track'][lookup]))
+    return cache['track'][lookup]
+
 options = {
     'whitelist': None,
     'branches': None,
     'c14n': False,
 }
+sources = []
+cache = {'artist':{}, 'album':{}, 'track':{}}
 class LastGenrePlugin(plugins.BeetsPlugin):
     def __init__(self):
         super(LastGenrePlugin, self).__init__()
             'fallback': None,
             'canonical': None,
             'source': 'album',
+            'force': False,
         })
 
-
         # Read the whitelist file.
         wl_filename = self.config['whitelist'].as_filename()
         whitelist = set()
                     whitelist.add(line)
         options['whitelist'] = whitelist
 
+        # Prepare sources
+        source = self.config['source'].get()
+        if source == 'track':
+            sources.extend(['track', 'album', 'artist'])
+        elif source == 'album':
+            sources.extend(['album', 'artist'])
+        elif source == 'artist':
+            sources.extend(['artist'])
+
         # Read the genres tree for canonicalization if enabled.
         c14n_filename = self.config['canonical'].get()
         if c14n_filename is not None:
             options['branches'] = branches
             options['c14n'] = True
 
+    def _get_album_genre(self, album, force, fallback_str):
+        log.debug(u'_get_album_genre')
+        if not force and is_allowed(album.genre):
+            # already valid and no forced lookup
+            log.debug(u"not fetching album genre. already valid")
+            return album.genre
+        result = None
+        # no track lookup for album
+        if 'album' in sources:
+            result = fetch_album_genre(album)
+            log.debug(u"last.fm album genre: %s" % result)
+            if result:
+                return result
+        if 'artist' in sources:
+            if not album.albumartist == 'Various Artists':
+                # no artist lookup for Various Artists
+                result = fetch_album_artist_genre(album)
+                log.debug(u"last.fm album artist genre: %s" % result)
+            if result:
+                return result
+        if is_allowed(album.genre):
+            return album.genre
+        if fallback_str:
+            return fallback_str
+        return None
+
+
+    def _get_item_genre(self, item, force, fallback_str):
+        if not force:
+            if is_allowed(item.genre):
+                # already valid and no forced lookup
+                log.debug(u"not fetching item genre. already valid")
+                return item.genre
+            log.debug(u"replacing invalid item genre: %s" % item.genre)
+        result = None
+        if 'track' in sources:
+            result = fetch_track_genre(item)
+            if result:
+                return result
+            log.debug(u"no last.fm track genre")
+        if 'album' in sources:
+            if item.album:
+                result = fetch_album_genre(item)
+            if result:
+                return result
+            log.debug(u"no last.fm album genre")
+        if 'artist' in sources:
+            result = fetch_artist_genre(item)
+            if result:
+                return result
+            log.debug(u"no last.fm artist genre")
+        if is_allowed(item.genre):
+            return item.genre
+        if fallback_str:
+            return fallback_str
+        return result
+
     def commands(self):
         lastgenre_cmd = ui.Subcommand('lastgenre', help='fetch genres')
         def lastgenre_func(lib, opts, args):
             # The "write to files" option corresponds to the
             # import_write config value.
             write = config['import']['write'].get(bool)
+            force = self.config['force'].get(bool)
+            fallback_str = self.config['fallback'].get()
             for album in lib.albums(ui.decargs(args)):
-                tags = []
-                lastfm_obj = _lastfm_obj(album)
-                if album.genre:
-                    tags.append(album.genre)
+                album.genre = self._get_album_genre(album, force, fallback_str)
+                log.debug(u'adding last.fm album genre: %s' % album.genre)
+                for item in album.items():
+                    item.genre = self._get_item_genre(item, force,
+                          fallback_str)
+                    log.debug(u'adding last.fm item genre: %s' % item.genre)
+                    if write:
+                        item.write()
 
-                tags.extend(_tags_for(lastfm_obj))
-                genre = _tags_to_genre(tags)
-
-                fallback_str = self.config['fallback'].get()
-                if not genre and fallback_str != None:
-                    genre = fallback_str
-                    log.debug(u'no last.fm genre found: fallback to %s' % genre)
-
-                if genre is not None:
-                    log.debug(u'adding last.fm album genre: %s' % genre)
-                    album.genre = genre
-                    if write:
-                        for item in album.items():
-                            item.write()
         lastgenre_cmd.func = lastgenre_func
         return [lastgenre_cmd]
 
     def imported(self, session, task):
         tags = []
+        fallback_str = self.config['fallback'].get()
         if task.is_album:
+            log.debug(u'imported: album')
             album = session.lib.get_album(task.album_id)
-            lastfm_obj = _lastfm_obj(album)
-            if album.genre:
-                tags.append(album.genre)
+            album.genre = self._get_album_genre(album, True, fallback_str)
+            log.debug(u'adding last.fm album genre: %s' % album.genre)
+            for item in album.items():
+                item.genre = self._get_item_genre(item, True, fallback_str)
+                log.debug(u'adding last.fm item genre: %s' % item.genre)
         else:
+            log.debug(u'imported: item')
             item = task.item
-            lastfm_obj = _lastfm_obj(item)
-            if item.genre:
-                tags.append(item.genre)
-
-        tags.extend(_tags_for(lastfm_obj))
-        genre = _tags_to_genre(tags)
-        
-        fallback_str = self.config['fallback'].get()
-        if not genre and fallback_str != None:
-            genre = fallback_str
-            log.debug(u'no last.fm genre found: fallback to %s' % genre)
-
-        if genre is not None:
-            log.debug(u'adding last.fm album genre: %s' % genre)
-
-            if task.is_album:
-                album = session.lib.get_album(task.album_id)
-                album.genre = genre
-            else:
-                item.genre = genre
-                session.lib.store(item)
+            item.genre = self._get_item_genre(item, True, fallback_str)
+            log.debug(u'adding last.fm item genre: %s' % item.genre)
+            session.lib.store(item)