From 12476ca22e57a5efa5a4e700ce355315c8cb4f93 Mon Sep 17 00:00:00 2001 From: imenem Date: Thu, 7 Mar 2013 17:59:52 +0200 Subject: [PATCH 1/2] Added Discogs search plugin. --- beetsplug/abstract_search.py | 54 +++++++++++++++++++ beetsplug/discogs.py | 100 +++++++++++++++++++++++++++++++++++ setup.py | 1 + 3 files changed, 155 insertions(+) create mode 100644 beetsplug/abstract_search.py create mode 100644 beetsplug/discogs.py diff --git a/beetsplug/abstract_search.py b/beetsplug/abstract_search.py new file mode 100644 index 0000000000..955a1cf56f --- /dev/null +++ b/beetsplug/abstract_search.py @@ -0,0 +1,54 @@ +"""Abstract plugin for new search +service support for the autotagger. +""" +global str + +import logging +import abc + +from os.path import dirname, basename + +from beets.plugins import BeetsPlugin +from beets.autotag import match + +log = logging.getLogger('beets') + +# Plugin structure and autotagging logic. + +class AbstractSearchPlugin(BeetsPlugin): + name = '' + + def __init__(self): + self.name = self.__class__.__name__ + super(AbstractSearchPlugin, self).__init__() + + def candidates(self, items): + try: + artist, album = self._metadata_from_items(items) + albums = self._search(artist, album) + + return map(self._album_info, albums) + except BaseException as e: + log.error(self.name + ' search error: ' + str(e)) + return [] + + @abc.abstractmethod + def _search(self, artist, album): + log.debug(self.name + ' search for: ' + artist + ' - ' + album) + return [] + + @abc.abstractmethod + def _album_info(self, album): + pass + + def _metadata_from_items(self, items): + artist, album, artist_consensus = match.current_metadata(items) + + va_likely = ((not artist_consensus) or + (artist.lower() in match.VA_ARTISTS) or + any(item.comp for item in items)) + + if va_likely: + return u'', album + else: + return artist, album diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py new file mode 100644 index 0000000000..8eb4f98005 --- /dev/null +++ b/beetsplug/discogs.py @@ -0,0 +1,100 @@ +"""Adds Discogs album search support to the +autotagger. Requires the discogs-client library. +""" +import string + +from time import strptime + +from beetsplug.abstract_search import AbstractSearchPlugin +from beets.autotag import hooks + +import discogs_client +from discogs_client import Artist, Release, Search, DiscogsAPIError + +discogs_client.user_agent = 'curl/7.28.0' + +# Plugin structure and autotagging logic. + +class DiscogsPlugin(AbstractSearchPlugin): + def __init__(self): + super(DiscogsPlugin, self).__init__() + + def _search(self, artist, album): + super(DiscogsPlugin, self)._search(artist, album) + try: + albums = Search(artist + ' ' + album).results()[0:5] + return filter(lambda album: isinstance(album, Release), albums) + except DiscogsAPIError as e: + if str(e).startswith('404'): + return [] + else: + raise e + + def _album_info(self, album): + return hooks.AlbumInfo( + album.title, + None, + self._artists_names(album.artists), + None, + map(self._track_info, album.tracklist) + ) + + def _track_info(self, track): + disk_number, position = self._position(track['position']) + + return hooks.TrackInfo( + track['title'], + None, + self._artists_names(track['artists']), + None, + self._duration(track['duration']), + position, + disk_number + ) + + def _artists_names(self, artists): + filtered = filter(lambda artist: isinstance(artist, Artist), artists) + names = map(lambda artist: artist.name, filtered) + + return ' and '.join(names) + + def _position(self, position): + try: + original = position + """Convert track position from u'1', u'2' or u'A', u'B' to 1, 2 etc""" + position = position.encode('ascii').lower() # Convert from unicode to lovercase ascii + + if not len(position): + return 0, 0 + + first = position[0] + + if string.ascii_lowercase.find(first) != -1: + number = ord(first) - 96 + + if len(position) == 1: + replace = '%i' % number # Letter is track number + else: + replace = '%i-' % number # Letter is vinyl side + + position = position.replace(first, replace) + + if position.find('-') == -1: + position = '1-' + position # If no disk number, set to 1 + + result = map(int, position.split('-')) + + if len(result) == 2: + return result + else: + return 0, 0 + except ValueError: + return 0, 0 + + def _duration(self, duration): + try: + duration = strptime(duration.encode('ascii'), '%M:%S') + except ValueError: + return 0 + else: + return duration.tm_min * 60 + duration.tm_sec \ No newline at end of file diff --git a/setup.py b/setup.py index 9097d170c5..76087750ab 100755 --- a/setup.py +++ b/setup.py @@ -75,6 +75,7 @@ def _read(fn): 'munkres', 'unidecode', 'musicbrainzngs>=0.2', + 'discogs_client>=1.1', 'pyyaml', ] + (['colorama'] if (sys.platform == 'win32') else []) From dde4bfab8c121cb8bc7d1563987abfc8f5d894cc Mon Sep 17 00:00:00 2001 From: Tai Lee Date: Wed, 22 May 2013 08:10:20 +1000 Subject: [PATCH 2/2] Update signature for item_candidates() in chroma plugin. --- beetsplug/chroma.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/chroma.py b/beetsplug/chroma.py index a2dfc63e66..3b2731af0d 100644 --- a/beetsplug/chroma.py +++ b/beetsplug/chroma.py @@ -134,7 +134,7 @@ def candidates(self, items, artist, album, va_likely): log.debug('acoustid album candidates: %i' % len(albums)) return albums - def item_candidates(self, item): + def item_candidates(self, item, artist, title): if item.path not in _matches: return []