diff --git a/README.md b/README.md index b173887..c02f6f4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -coverart-search-providers v1.2.1 +coverart-search-providers v1.2.2-testing ========================= Drop in Rhythmbox replacement for the default CoverArt Search plugin to provide new and updated coverart search providers both local and by internet image hosts @@ -52,12 +52,11 @@ v1.1 - fix bug to carry on search after binning poor MusicBrainz image download - restructure rate-limits to ensure faster downloads whilst keeping to providers rate-limits -v1.2 +v1.2.x - support song-info dialog for Rhythmbox 3.2 - use RB's own embedded art search method as well as mutagen for wider embedded-art coverage - -v1.2.1 - bug fix - for locales not found, exit the language installer gracefully + - PEP8 cleanup; remove odd embedded provider duplicate Recommended order for Search Providers diff --git a/coverart_album_search.py b/coverart_album_search.py index dd23661..59ad903 100644 --- a/coverart_album_search.py +++ b/coverart_album_search.py @@ -42,7 +42,6 @@ import discogs_client as discogs import rb from coverart_search_tracks import mutagen_library - ITEMS_PER_NOTIFICATION = 10 IGNORED_SCHEMES = ('http', 'cdda', 'daap', 'mms') REPEAT_SEARCH_PERIOD = 86400 * 7 @@ -73,7 +72,7 @@ class BaseSearch(object): print("rate_limit") diff = time.time() - self.current_time if diff < (1.0 / per_second_rate): - #Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, + # Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, # int((1.0 / per_second_rate) *100), delay, None) time.sleep((1.0 / per_second_rate) - diff) print("sleeping") @@ -91,14 +90,14 @@ class CoverSearch(object): self.searches = searches def next_search(self, continue_search): - ''' + """ main routine that calls the search routine for each search provider unless one of the searches has found something outputs - return False means that nothing found inputs - True means continue with searching - False means a search routine recommends no more searching - ''' + """ print("next search") print(continue_search) @@ -107,7 +106,7 @@ class CoverSearch(object): album = self.key.get_field("album") if not album: return False - + key = RB.ExtDBKey.create_storage("album", album) key.add_field("artist", self.key.get_field("artist")) self.store.store(key, RB.ExtDBSourceType.NONE, None) @@ -151,7 +150,7 @@ class CoverAlbumSearch: try: files = fileenum.next_files_finish(result) if files is None or len(files) == 0: - #print "okay, done; got %d files" % len(results) + # print "okay, done; got %d files" % len(results) self.finished(results) return @@ -163,7 +162,7 @@ class CoverAlbumSearch: readable = f.get_attribute_boolean("access::can-read") if ct is not None and ct.startswith("audio/") and readable: - #print "_enum_dir_cb %s " % f.get_name() + # print "_enum_dir_cb %s " % f.get_name() results.append(f.get_name()) fileenum.next_files_async(ITEMS_PER_NOTIFICATION, GLib.PRIORITY_DEFAULT, None, self._enum_dir_cb, results) @@ -174,7 +173,6 @@ class CoverAlbumSearch: sys.excepthook(*sys.exc_info()) self.finished(results) - def _enum_children_cb(self, parent, result, data): try: enumfiles = parent.enumerate_children_finish(result) @@ -186,7 +184,6 @@ class CoverAlbumSearch: sys.excepthook(*sys.exc_info()) self.callback(True) - def search(self, key, last_time, store, callback, args): # ignore last_time print("calling search") @@ -220,7 +217,7 @@ class CoverAlbumSearch: if not self.album: return False - + key = RB.ExtDBKey.create_storage("album", self.album) key.add_field("artist", self.artists[0]) parent = self.file.get_parent() @@ -241,7 +238,7 @@ class CoverAlbumSearch: print("possible flac") try: - #flac + # flac module = mutagen_library('') music = module.File(search) imagefilename.write(music.pictures[0].data) @@ -344,7 +341,7 @@ class DiscogsSearch(object): if album in ("", _("Unknown")): album = None - if album == None or len(artists) == 0: + if album is None or len(artists) == 0: callback(True) return @@ -406,7 +403,6 @@ class SpotifySearch(BaseSearch): def search_url(self, artist, album): # Remove variants of Disc/CD [1-9] from album title before search - orig_album = album for exp in DISC_NUMBER_REGEXS: p = re.compile(exp, re.IGNORECASE) album = p.sub('', album) @@ -422,7 +418,6 @@ class SpotifySearch(BaseSearch): print("spotify query url = %s" % url) return url - def album_info_cb(self, data, album_name): if data is None: print("spotify query returned nothing") @@ -474,7 +469,7 @@ class SpotifySearch(BaseSearch): if album in ("", _("Unknown")): album = None - if album == None or len(artists) == 0: + if album is None or len(artists) == 0: print("can't search: no useful details") callback(True) return diff --git a/coverart_artist_search.py b/coverart_artist_search.py index 9235125..f45d447 100644 --- a/coverart_artist_search.py +++ b/coverart_artist_search.py @@ -35,7 +35,6 @@ import chardet import rb import rb3compat - gettext.install('rhythmbox', RB.locale_dir()) if rb3compat.PYVER >= 3: @@ -51,6 +50,7 @@ REPEAT_SEARCH_PERIOD = 86400 * 7 def file_root(f_name): return os.path.splitext(f_name)[0].lower() + # this API key belongs to foss.freedom@gmail.com # and was generated specifically for this use API_KEY = '844353bce568b93accd9ca47674d6c3e' @@ -79,14 +79,14 @@ class ArtistCoverSearch(object): self.searches = searches def next_search(self, continue_search): - ''' + """ main routine that calls the search routine for each search provider unless one of the searches has found something outputs - return False means that nothing found inputs - True means continue with searching - False means a search routine recommends no more searching - ''' + """ if len(self.searches) == 0 and continue_search: key = RB.ExtDBKey.create_storage("artist", self.key.get_field("artist")) @@ -110,7 +110,7 @@ class LastFMArtistSearch(object): def search_url(self, artist): - print(("searching for (%s)" % (artist))) + print("searching for (%s)" % (artist)) url = API_URL + "?method=artist.getinfo&" url = url + "artist=%s&" % (rb3compat.quote_plus(artist)) url = url + "format=json&" @@ -153,7 +153,7 @@ class LastFMArtistSearch(object): # images tags appear in order of increasing size, and we want the largest. probably. url = image_urls.pop() - #last check - ensure the size is relatively large to hide false positives + # last check - ensure the size is relatively large to hide false positives site = rb3compat.urlopen(url) meta = site.info() @@ -180,12 +180,12 @@ class LastFMArtistSearch(object): l.get_url(url, self.artist_info_cb) def search(self, key, last_time, store, callback, args): - #if last_time > (time.time() - REPEAT_SEARCH_PERIOD): + # if last_time > (time.time() - REPEAT_SEARCH_PERIOD): # print("we already tried this one") # callback (True) # return - if user_has_account() == False: + if not user_has_account(): print("can't search: no last.fm account details") callback(True) return @@ -193,7 +193,7 @@ class LastFMArtistSearch(object): artist = key.get_field("artist") self.key = key - if artist == None: + if artist is None: print("can't search: no useful details") callback(True) return diff --git a/coverart_extdb.py b/coverart_extdb.py index fb4fe26..809a0ae 100644 --- a/coverart_extdb.py +++ b/coverart_extdb.py @@ -18,24 +18,19 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. import time - from gi.repository import RB from gi.repository import GObject from gi.repository import GdkPixbuf from gi.repository import Gio - +import os +import json import rb3compat - if rb3compat.PYVER >= 3: import dbm.gnu as gdbm else: import gdbm -import os -import json - - class Queue: def __init__(self): self.items = [] @@ -54,7 +49,7 @@ class Queue: class CoverArtExtDB: - ''' + """ This is a simplified version of the RB.ExtDB capability. This resolves the bugs in the extant capability, primarily around using signals for none "album-art" databases. @@ -67,7 +62,7 @@ class CoverArtExtDB: ported to python3 - this uses the analagous gdbm format. :param name: `str` name of the external database. - ''' + """ # storage for the instance references __instances = {} @@ -84,7 +79,7 @@ class CoverArtExtDB: } # added (ExtDB self, ExtDBKey object, String path, Value pixbuf) - #request (ExtDB self, ExtDBKey object, guint64 last_time) + # request (ExtDB self, ExtDBKey object, guint64 last_time) _callback = {} @@ -138,11 +133,11 @@ class CoverArtExtDB: return keyval def store(self, key, source_type, data): - ''' + """ :param key: `ExtDBKey` :param source_type: `ExtDBSourceType` :param data: `GdkPixbuf.Pixbuf` - ''' + """ print("store") self.store_uri(key, source_type, data) @@ -201,23 +196,23 @@ class CoverArtExtDB: return False def store_uri(self, key, source_type, data): - ''' + """ :param key: `ExtDBKey` :param source_type: `ExtDBSourceType` :param data: `str` which is a uri - ''' + """ print("store_uri") self.queue.enqueue((key, source_type, data)) - #Gio.io_scheduler_push_job(self.do_store_request, None, + # Gio.io_scheduler_push_job(self.do_store_request, None, # GLib.PRIORITY_DEFAULT, None) self.do_store_request() def lookup(self, key): - ''' + """ :param key: `ExtDBKey` - ''' + """ lookup = self._construct_key(key) filename = '' if lookup in self.db: @@ -227,16 +222,15 @@ class CoverArtExtDB: return str(filename) - def request(self, key, callback, user_data): - ''' + """ :param key: `ExtDBKey` :param callback: `Function` callback :param user_data: `Value` where callback is Function (ExtDBKey key, String filename, GdkPixbuf.Pixbuf data, void* user_data) boolean - ''' + """ lookup = self._construct_key(key) @@ -276,4 +270,3 @@ class CoverArtExtDB: def __setattr__(self, attr, value): """ Delegate access to implementation """ return setattr(self.__dict__['_CoverArtExtDB__instance'], attr, value) - diff --git a/coverart_search_providers.py b/coverart_search_providers.py index dcfe8e6..fc6864f 100644 --- a/coverart_search_providers.py +++ b/coverart_search_providers.py @@ -44,16 +44,16 @@ import rb3compat def lastfm_connected(): - ''' + """ returns True/False if connected to lastfm - ''' + """ return user_has_account() def get_search_providers(): - ''' + """ returns an array of search providers - ''' + """ gs = GSetting() setting = gs.get_setting(gs.Path.PLUGIN) current_providers = setting[gs.PluginKey.PROVIDERS] @@ -62,27 +62,27 @@ def get_search_providers(): class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable): - ''' + """ Main class of the plugin. Manages the activation and deactivation of the plugin. - ''' + """ __gtype_name = 'CoverArtAlbumSearchPlugin' object = GObject.property(type=GObject.Object) def __init__(self): - ''' + """ Initialises the plugin object. - ''' + """ GObject.Object.__init__(self) if not rb3compat.compare_pygobject_version('3.9'): GObject.threads_init() def do_activate(self): - ''' + """ Called by Rhythmbox when the plugin is activated. It creates the plugin's source and connects signals to manage the plugin's preferences. - ''' + """ cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) @@ -128,12 +128,11 @@ class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable): dialog.run() dialog.destroy() - def do_deactivate(self): - ''' + """ Called by Rhythmbox when the plugin is deactivated. It makes sure to free all the resources used by the plugin. - ''' + """ print("CoverArtBrowser DEBUG - do_deactivate") self.shell.disconnect(self.csi_id) diff --git a/coverart_search_providers_prefs.py b/coverart_search_providers_prefs.py index a95b36d..cdf913a 100644 --- a/coverart_search_providers_prefs.py +++ b/coverart_search_providers_prefs.py @@ -33,9 +33,9 @@ import rb class CoverLocale: - ''' + """ This class manages the locale - ''' + """ # storage for the instance reference __instance = None @@ -43,18 +43,18 @@ class CoverLocale: """ Implementation of the singleton interface """ # below public variables and methods that can be called for CoverLocale def __init__(self): - ''' + """ Initializes the singleton interface, assigning all the constants used to access the plugin's settings. - ''' + """ self.Locale = self._enum( RB='rhythmbox', LOCALE_DOMAIN='coverart_search_providers') def switch_locale(self, locale_type): - ''' + """ Change the locale - ''' + """ locale.setlocale(locale.LC_ALL, '') locale.bindtextdomain(locale_type, RB.locale_dir()) locale.textdomain(locale_type) @@ -63,17 +63,17 @@ class CoverLocale: gettext.install(locale_type) def get_locale(self): - ''' + """ return the string representation of the users locale for example en_US - ''' + """ return locale.getdefaultlocale()[0] def _enum(self, **enums): - ''' + """ Create an enumn. - ''' + """ return type('Enum', (), enums) def __init__(self): @@ -96,10 +96,10 @@ class CoverLocale: class GSetting: - ''' + """ This class manages the differentes settings that the plugins haves to access to read or write. - ''' + """ # storage for the instance reference __instance = None @@ -107,10 +107,10 @@ class GSetting: """ Implementation of the singleton interface """ # below public variables and methods that can be called for GSetting def __init__(self): - ''' + """ Initializes the singleton interface, asigning all the constants used to access the plugin's settings. - ''' + """ self.Path = self._enum( PLUGIN='org.gnome.rhythmbox.plugins.coverart_search_providers') @@ -120,9 +120,9 @@ class GSetting: self.setting = {} def get_setting(self, path): - ''' + """ Return an instance of Gio.Settings pointing at the selected path. - ''' + """ try: setting = self.setting[path] except: @@ -132,21 +132,21 @@ class GSetting: return setting def get_value(self, path, key): - ''' + """ Return the value saved on key from the settings path. - ''' + """ return self.get_setting(path)[key] def set_value(self, path, key, value): - ''' + """ Set the passed value to key in the settings path. - ''' + """ self.get_setting(path)[key] = value def _enum(self, **enums): - ''' + """ Create an enumn. - ''' + """ return type('Enum', (), enums) def __init__(self): @@ -169,10 +169,10 @@ class GSetting: class SearchPreferences(GObject.Object, PeasGtk.Configurable): - ''' + """ Preferences for the CoverArt Browser Plugins. It holds the settings for the plugin and also is the responsible of creating the preferences dialog. - ''' + """ __gtype_name__ = 'CoverArtSearchProvidersPreferences' object = GObject.property(type=GObject.Object) @@ -186,17 +186,17 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable): MUSICBRAINZ_SEARCH = 'musicbrainz-search' def __init__(self): - ''' + """ Initialises the preferences, getting an instance of the settings saved by Gio. - ''' + """ GObject.Object.__init__(self) self._first_run = True def do_create_configure_widget(self): - ''' + """ Creates the plugin's preferences dialog - ''' + """ return self._create_display_contents(self) def display_preferences_dialog(self, plugin): @@ -255,7 +255,9 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable): current_providers = copy.deepcopy(self.provider) current = self.settings[self.gs.PluginKey.PROVIDERS] + print (current) current_list = current.split(',') + print (current_list) # create the ui builder = Gtk.Builder() @@ -283,8 +285,8 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable): for key, value in list(current_providers.items()): self.provider_liststore.append([value, key]) - if len(self.provider_liststore) == 0: - self.provider_liststore.append([self.provider[self.EMBEDDED_SEARCH], self.EMBEDDED_SEARCH]) + #if len(self.provider_liststore) == 0: + # self.provider_liststore.append([self.provider[self.EMBEDDED_SEARCH], self.EMBEDDED_SEARCH]) # return the dialog return builder.get_object('maingrid') @@ -317,7 +319,7 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable): item = self.search_liststore.get_iter_first() current_providers = [] - while ( item != None ): + while (item is not None): current_providers.append(self.search_liststore.get_value(item, 1)) item = self.search_liststore.iter_next(item) @@ -326,17 +328,16 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable): def on_up_button_clicked(self, *args): selection = self.search_list.get_selection() sel = selection.get_selected() - if not sel[1] == None: + if not sel[1] is None: previous = self.search_liststore.iter_previous(sel[1]) if previous: self.search_liststore.swap(sel[1], previous) self._store_search_providers() - def on_down_button_clicked(self, *args): selection = self.search_list.get_selection() sel = selection.get_selected() - if not sel[1] == None: + if not sel[1] is None: next = self.search_liststore.iter_next(sel[1]) if next: self.search_liststore.swap(sel[1], next) diff --git a/coverart_search_tracks.py b/coverart_search_tracks.py index 1b41795..9d72a3b 100644 --- a/coverart_search_tracks.py +++ b/coverart_search_tracks.py @@ -49,9 +49,9 @@ IGNORED_SCHEMES = ('http', 'cdda', 'daap', 'mms') def anyTrue(pred, seq): - '''Returns True if a True predicate is found, False + """Returns True if a True predicate is found, False otherwise. Quits as soon as the first True is found - ''' + """ return True in map(pred, seq) @@ -65,11 +65,11 @@ class CoverArtTracks(object): return mimetype[0] def embed_ogg(self, art_location, search, mimetypestr): - ''' + """ :art_location: file path to the picture to embed :search: file path to the music track file :mimetypestr: mimetype of the picture to embed - ''' + """ try: module = mutagen_library('oggvorbis') o = module.OggVorbis(search) @@ -91,7 +91,7 @@ class CoverArtTracks(object): image.mime = mimetypestr image.desc = 'cover description' tags.setdefault("METADATA_BLOCK_PICTURE", - []).append(base64.b64encode(image.write())) + []).append(base64.b64encode(image.write())) o.tags.update(tags) o.save() @@ -99,11 +99,11 @@ class CoverArtTracks(object): pass def embed_flac(self, art_location, search, mimetypestr): - ''' + """ :art_location: file path to the picture to embed :search: file path to the music track file :mimetypestr: mimetype of the picture to embed - ''' + """ try: module = mutagen_library('') music = module.File(search) @@ -122,11 +122,11 @@ class CoverArtTracks(object): pass def embed_mp4(self, art_location, search, mimetypestr): - ''' + """ :art_location: file path to the picture to embed :search: file path to the music track file :mimetypestr: mimetype of the picture to embed - ''' + """ try: module = mutagen_library('mp4') music = module.MP4(search) @@ -145,11 +145,11 @@ class CoverArtTracks(object): pass def embed_mp3(self, art_location, search, mimetypestr): - ''' + """ :art_location: file path to the picture to embed :search: file path to the music track file :mimetypestr: mimetype of the picture to embed - ''' + """ try: module = mutagen_library('id3') music = module.ID3(search) @@ -165,7 +165,7 @@ class CoverArtTracks(object): pass def embed(self, track_uri, key, resize=-1): - ''' + """ embed tracks with the coverart :track_uri: nominally RB.RhythmDBPropType.LOCATION @@ -173,7 +173,7 @@ class CoverArtTracks(object): :resize: int this is the size of the embedded image to resize to returns True or False depending if the routine completed successfully - ''' + """ the = anyTrue # for readability @@ -181,7 +181,7 @@ class CoverArtTracks(object): if not isinstance(art_location, str): art_location = art_location[0] - + if not art_location: print("not a valid key to a file containing art") return False diff --git a/discogs_client.py b/discogs_client.py index db1ff7a..184c718 100644 --- a/discogs_client.py +++ b/discogs_client.py @@ -1,20 +1,22 @@ -__version_info__ = (1,1,1) +__version_info__ = (1, 1, 1) __version__ = '1.1.1' -import requests import json -import rb3compat - from collections import defaultdict +import requests + +import rb3compat + api_uri = 'http://api.discogs.com' user_agent = None + class APIBase(object): def __init__(self): self._cached_response = None self._params = {} - self._headers = { 'accept-encoding': 'gzip, deflate' } + self._headers = {'accept-encoding': 'gzip, deflate'} def __str__(self): return '<%s "%s">' % (self.__class__.__name__, self._id) @@ -36,20 +38,21 @@ class APIBase(object): if not self._check_user_agent(): raise DiscogsAPIError('Invalid or no User-Agent set') try: - #gs = GSetting() - #setting = gs.get_setting(gs.Path.PLUGIN) - #type_val = setting[gs.PluginKey.PROXY_TYPE] - #if type_val == 0: + # gs = GSetting() + # setting = gs.get_setting(gs.Path.PLUGIN) + # type_val = setting[gs.PluginKey.PROXY_TYPE] + # if type_val == 0: # type_name = 'http' - #elif type_val == 1: + # elif type_val == 1: # type_name = 'https' - #elif type_val == 2: + # elif type_val == 2: # type_name = 'ftp' - - #proxy_name = setting[gs.PluginKey.PROXY_VALUE] - proxydict = {'http':''} - #proxydict[type_name] = proxy_name - self._cached_response = requests.get(self._uri, params=self._params, headers=self._headers, proxies=proxydict) + + # proxy_name = setting[gs.PluginKey.PROXY_VALUE] + proxydict = {'http': ''} + # proxydict[type_name] = proxy_name + self._cached_response = requests.get(self._uri, params=self._params, headers=self._headers, + proxies=proxydict) except: raise DiscogsAPIError('bad response') @@ -61,10 +64,9 @@ class APIBase(object): @property def _uri(self): - import urllib return '%s/%s/%s' % (api_uri, self._uri_name, rb3compat.quote(rb3compat.unicodeencode(self._id, 'utf-8'))) - #return '%s/%s/%s' % (api_uri, self._uri_name, urllib.quote(unicode(self._id).encode('utf-8'))) - + # return '%s/%s/%s' % (api_uri, self._uri_name, urllib.quote(unicode(self._id).encode('utf-8'))) + @property def data(self): if self._response.content and self._response.status_code == 200: @@ -74,9 +76,11 @@ class APIBase(object): status_code = self._response.status_code raise DiscogsAPIError('%s %s' % (status_code, rb3compat.responses()[status_code])) + class DiscogsAPIError(BaseException): pass + def _parse_credits(extraartists): """ Parse release and track level credits @@ -94,16 +98,18 @@ def _parse_credits(extraartists): _credits[role].append(artist_dict) return _credits + def _class_from_string(api_string): class_map = { - 'master': MasterRelease, - 'release': Release, - 'artist': Artist, - 'label': Label + 'master': MasterRelease, + 'release': Release, + 'artist': Artist, + 'label': Label } return class_map[api_string] + class Artist(APIBase): def __init__(self, name, anv=None): self._id = name @@ -134,7 +140,7 @@ class Artist(APIBase): @property def releases(self): # TODO: Implement fetch many release IDs - #return [Release(r.get('id') for r in self.data.get('releases')] + # return [Release(r.get('id') for r in self.data.get('releases')] if not self._releases: self._params.update({'releases': '1'}) self._clear_cache() @@ -143,6 +149,7 @@ class Artist(APIBase): self._releases.append(_class_from_string(r['type'])(r['id'])) return self._releases + class Release(APIBase): def __init__(self, id): self._id = id @@ -168,7 +175,7 @@ class Release(APIBase): @property def labels(self): if not self._labels: - self._labels = [Label(l['name']) for l in self.data.get('labels', [])] + self._labels = [Label(l['name']) for l in self.data.get('labels', [])] return self._labels @property @@ -199,6 +206,7 @@ class Release(APIBase): def title(self): return self.data.get('title') + class MasterRelease(APIBase): def __init__(self, id): self._id = id @@ -240,6 +248,7 @@ class MasterRelease(APIBase): def tracklist(self): return self.key_release.tracklist + class Label(APIBase): def __init__(self, name): self._id = name @@ -266,6 +275,7 @@ class Label(APIBase): self._clear_cache() return self.data.get('releases') + class Search(APIBase): def __init__(self, query, page=1): self._id = query diff --git a/rb3compat.py b/rb3compat.py index f0ee30c..f6f2bbe 100644 --- a/rb3compat.py +++ b/rb3compat.py @@ -35,10 +35,10 @@ import rb def pygobject_version(): - ''' + """ returns float of the major and minor parts of a pygobject version e.g. version (3, 9, 5) return float(3.9) - ''' + """ to_number = lambda t: ".".join(str(v) for v in t) str_version = to_number(GObject.pygobject_version) @@ -47,10 +47,10 @@ def pygobject_version(): def compare_pygobject_version(version): - ''' + """ return True if version is less than pygobject_version i.e. 3.9 < 3.11 - ''' + """ to_number = lambda t: ".".join(str(v) for v in t) str_version = to_number(GObject.pygobject_version) @@ -172,17 +172,17 @@ def is_rb3(*args): class Menu(GObject.Object): - ''' + """ Menu object used to create window popup menus - ''' + """ __gsignals__ = { 'pre-popup': (GObject.SIGNAL_RUN_LAST, None, ()) } def __init__(self, plugin, shell): - ''' + """ Initializes the menu. - ''' + """ super(Menu, self).__init__() self.plugin = plugin self.shell = shell @@ -192,22 +192,22 @@ class Menu(GObject.Object): self._rbmenu_objects = {} def add_menu_item(self, menubar, section_name, action): - ''' + """ add a new menu item to the popup :param menubar: `str` is the name GtkMenu (or ignored for RB2.99+) :param section_name: `str` is the name of the section to add the item to (RB2.99+) :param action: `Action` to associate with the menu item - ''' + """ return self.insert_menu_item(menubar, section_name, -1, action) def insert_menu_item(self, menubar, section_name, position, action): - ''' + """ add a new menu item to the popup :param menubar: `str` is the name GtkMenu (or ignored for RB2.99+) :param section_name: `str` is the name of the section to add the item to (RB2.99+) :param position: `int` position to add to GtkMenu (ignored for RB2.99+) :param action: `Action` to associate with the menu item - ''' + """ label = action.label if is_rb3(self.shell): @@ -238,11 +238,11 @@ class Menu(GObject.Object): return item def insert_separator(self, menubar, at_position): - ''' + """ add a separator to the popup (only required for RB2.98 and earlier) :param menubar: `str` is the name GtkMenu (or ignored for RB2.99+) :param position: `int` position to add to GtkMenu (ignored for RB2.99+) - ''' + """ if not is_rb3(self.shell): menu_item = Gtk.SeparatorMenuItem().new() menu_item.set_visible(True) @@ -255,11 +255,11 @@ class Menu(GObject.Object): uim.ensure_update() def remove_menu_items(self, menubar, section_name): - ''' + """ utility function to remove all menuitems associated with the menu section :param menubar: `str` is the name of the GtkMenu containing the menu items (ignored for RB2.99+) :param section_name: `str` is the name of the section containing the menu items (for RB2.99+ only) - ''' + """ if is_rb3(self.shell): if not section_name in self._rbmenu_items: return @@ -287,11 +287,11 @@ class Menu(GObject.Object): uim.ensure_update() def load_from_file(self, rb2_ui_filename, rb3_ui_filename): - ''' + """ utility function to load the menu structure :param rb2_ui_filename: `str` RB2.98 and below UI file :param rb3_ui_filename: `str` RB2.99 and higher UI file - ''' + """ self.builder = Gtk.Builder() try: from coverart_browser_prefs import CoverLocale @@ -331,21 +331,21 @@ class Menu(GObject.Object): _menu_connect(key, value) def connect_signals(self, signals): - ''' + """ connect all signal handlers with their menuitem counterparts :param signals: `dict` key is the name of the menuitem and value is the function callback when the menu is activated - ''' + """ if is_rb3(self.shell): self._connect_rb3_signals(signals) else: self._connect_rb2_signals(signals) def get_gtkmenu(self, source, popup_name): - ''' + """ utility function to obtain the GtkMenu from the menu UI file :param popup_name: `str` is the name menu-id in the UI file - ''' + """ if popup_name in self._rbmenu_objects: return self._rbmenu_objects[popup_name] item = self.builder.get_object(popup_name) @@ -363,10 +363,10 @@ class Menu(GObject.Object): return popup_menu def get_menu_object(self, menu_name_or_link): - ''' + """ utility function returns the GtkMenuItem/Gio.MenuItem :param menu_name_or_link: `str` to search for in the UI file - ''' + """ if menu_name_or_link in self._rbmenu_objects: return self._rbmenu_objects[menu_name_or_link] item = self.builder.get_object(menu_name_or_link) @@ -384,12 +384,12 @@ class Menu(GObject.Object): return popup_menu def set_sensitive(self, menu_or_action_item, enable): - ''' + """ utility function to enable/disable a menu-item :param menu_or_action_item: `GtkMenuItem` or `Gio.SimpleAction` that is to be enabled/disabled :param enable: `bool` value to enable/disable - ''' + """ if is_rb3(self.shell): item = self.shell.props.window.lookup_action(menu_or_action_item) @@ -399,29 +399,29 @@ class Menu(GObject.Object): item.set_sensitive(enable) def popup(self, source, menu_name, button, time): - ''' + """ utility function to show the popup menu - ''' + """ self.emit('pre-popup') menu = self.get_gtkmenu(source, menu_name) menu.popup(None, None, None, None, button, time) class ActionGroup(object): - ''' + """ container for all Actions used to associate with menu items - ''' + """ # action_state STANDARD = 0 TOGGLE = 1 def __init__(self, shell, group_name): - ''' + """ constructor :param shell: `RBShell` :param group_name: `str` unique name for the object to create - ''' + """ self.group_name = group_name self.shell = shell @@ -439,22 +439,22 @@ class ActionGroup(object): return self.group_name def remove_actions(self): - ''' + """ utility function to remove all actions associated with the ActionGroup - ''' + """ for action in self.actiongroup.list_actions(): self.actiongroup.remove_action(action) def get_action(self, action_name): - ''' + """ utility function to obtain the Action from the ActionGroup :param action_name: `str` is the Action unique name - ''' + """ return self._actions[action_name] def add_action_with_accel(self, func, action_name, accel, **args): - ''' + """ Creates an Action with an accelerator and adds it to the ActionGroup :param func: function callback used when user activates the action @@ -464,12 +464,12 @@ class ActionGroup(object): Notes: see notes for add_action - ''' + """ args['accel'] = accel return self.add_action(func, action_name, **args) def add_action(self, func, action_name, **args): - ''' + """ Creates an Action and adds it to the ActionGroup :param func: function callback used when user activates the action @@ -481,7 +481,7 @@ class ActionGroup(object): key value of "action_type" is the RB2.99 Gio.Action type ("win" or "app") by default it assumes all actions are "win" type key value of "action_state" determines what action state to create - ''' + """ if 'label' in args: label = args['label'] else: @@ -550,9 +550,9 @@ class ActionGroup(object): class ApplicationShell(object): - ''' + """ Unique class that mirrors RB.Application & RB.Shell menu functionality - ''' + """ # storage for the instance reference __instance = None @@ -570,22 +570,22 @@ class ApplicationShell(object): self._action_groups = {} def insert_action_group(self, action_group): - ''' + """ Adds an ActionGroup to the ApplicationShell :param action_group: `ActionGroup` to add - ''' + """ self._action_groups[action_group.name] = action_group def lookup_action(self, action_group_name, action_name, action_type='app'): - ''' + """ looks up (finds) an action created by another plugin. If found returns an Action or None if no matching Action. :param action_group_name: `str` is the Gtk.ActionGroup name (ignored for RB2.99+) :param action_name: `str` unique name for the action to look for :param action_type: `str` RB2.99+ action type ("win" or "app") - ''' + """ if is_rb3(self.shell): if action_type == "app": @@ -611,7 +611,7 @@ class ApplicationShell(object): return None def add_app_menuitems(self, ui_string, group_name, menu='tools'): - ''' + """ utility function to add application menu items. For RB2.99 all application menu items are added to the "tools" section of the @@ -627,7 +627,7 @@ class ApplicationShell(object): :param group_name: `str` unique name of the ActionGroup to add menu items to :param menu: `str` RB2.99 menu section to add to - nominally either 'tools' or 'view' - ''' + """ if is_rb3(self.shell): root = ET.fromstring(ui_string) for elem in root.findall(".//menuitem"): @@ -652,7 +652,7 @@ class ApplicationShell(object): uim.ensure_update() def add_browser_menuitems(self, ui_string, group_name): - ''' + """ utility function to add popup menu items to existing browser popups For RB2.99 all menu items are are assumed to be of action_type "win". @@ -665,7 +665,7 @@ class ApplicationShell(object): this string is in XML format :param group_name: `str` unique name of the ActionGroup to add menu items to - ''' + """ if is_rb3(self.shell): root = ET.fromstring(ui_string) for elem in root.findall("./popup"): @@ -703,9 +703,9 @@ class ApplicationShell(object): uim.ensure_update() def cleanup(self): - ''' + """ utility remove any menuitems created. - ''' + """ if is_rb3(self.shell): for uid in self._uids: Gio.Application.get_default().remove_plugin_menu_item(self._uids[uid], @@ -736,17 +736,17 @@ class ApplicationShell(object): class Action(object): - ''' + """ class that wraps around either a Gio.Action or a Gtk.Action - ''' + """ def __init__(self, shell, action): - ''' + """ constructor. :param shell: `RBShell` :param action: `Gio.Action` or `Gtk.Action` - ''' + """ self.shell = shell self.action = action @@ -776,12 +776,12 @@ class Action(object): @property def label(self): - ''' + """ get the menu label associated with the Action for RB2.99+ actions dont have menu labels so this is managed manually - ''' + """ if not is_rb3(self.shell): return self.action.get_label() else: @@ -796,9 +796,9 @@ class Action(object): @property def accel(self): - ''' + """ get the accelerator associated with the Action - ''' + """ return self._accel @accel.setter @@ -809,41 +809,41 @@ class Action(object): self._accel = '' def get_sensitive(self): - ''' + """ get the sensitivity (enabled/disabled) state of the Action returns boolean - ''' + """ if is_rb3(self.shell): return self.action.get_enabled() else: return self.action.get_sensitive() def set_state(self, value): - ''' + """ set the state of a stateful action - this is applicable only to RB2.99+ - ''' + """ if is_rb3(self.shell) and self.action.props.state_type: self.action.change_state(GLib.Variant('b', value)) def activate(self): - ''' + """ invokes the activate signal for the action - ''' + """ if is_rb3(self.shell): self.action.activate(None) else: self.action.activate() def set_active(self, value): - ''' + """ activate or deactivate a stateful action signal For consistency with earlier RB versions, this will fire the activate signal for the action :param value: `boolean` state value - ''' + """ if is_rb3(self.shell): self.action.change_state(GLib.Variant('b', value)) @@ -855,11 +855,11 @@ class Action(object): self.action.set_active(value) def get_active(self): - ''' + """ get the state of the action returns `boolean` state value - ''' + """ if is_rb3(self.shell): returnval = self._current_state else: @@ -868,13 +868,11 @@ class Action(object): return returnval def associate_menuitem(self, menuitem): - ''' + """ links a menu with the action - ''' + """ if is_rb3(self.shell): menuitem.set_detailed_action('win.' + self.action.get_name()) else: menuitem.set_related_action(self.action) - - diff --git a/rb_embedded.py b/rb_embedded.py index 7ffa4d0..8f0b934 100644 --- a/rb_embedded.py +++ b/rb_embedded.py @@ -27,57 +27,56 @@ from gi.repository import RB from gi.repository import Gst, GstPbutils + class EmbeddedSearch(object): + def finished_cb(self, discoverer): + self.callback(True) - def finished_cb(self, discoverer): - self.callback(True) + def discovered_cb(self, discoverer, info, error): + tags = info.get_tags() + if tags is None: + return - def discovered_cb(self, discoverer, info, error): - tags = info.get_tags() - if tags is None: - return + for tagname in ('image', 'preview-image'): + (found, sample) = tags.get_sample(tagname) + if not found: + print("no %s" % tagname) + continue - for tagname in ('image', 'preview-image'): - (found, sample) = tags.get_sample(tagname) - if not found: - print("no %s" % tagname) - continue + pixbuf = RB.gst_process_embedded_image(tags, tagname) + if not pixbuf: + print("no pixbuf in %s" % tagname) + continue - pixbuf = RB.gst_process_embedded_image(tags, tagname) - if not pixbuf: - print("no pixbuf in %s" % tagname) - continue + print("trying to store pixbuf from %s" % tagname) + key = RB.ExtDBKey.create_storage("album", self.search_key.get_field("album")) + artists = self.search_key.get_field_values("artist") + key.add_field("artist", artists[0]) + self.store.store(key, RB.ExtDBSourceType.EMBEDDED, pixbuf) + return - print("trying to store pixbuf from %s" % tagname) - key = RB.ExtDBKey.create_storage("album", self.search_key.get_field("album")) - artists = self.search_key.get_field_values("artist") - key.add_field("artist", artists[0]) - self.store.store(key, RB.ExtDBSourceType.EMBEDDED, pixbuf) - return + def search(self, key, last_time, store, callback, args): + location = key.get_info("location") + if location is None: + print("not searching, we don't have a location") + callback(True) + return + if location.startswith("file://") is False: + print("not searching in non-local file %s" % location) + callback(True) + return - def search (self, key, last_time, store, callback, args): - location = key.get_info("location") - if location is None: - print("not searching, we don't have a location") - callback(True) - return + # should avoid checking the playing entry, since the player already handles that - if location.startswith("file://") is False: - print("not searching in non-local file %s" % location) - callback(True) - return + self.callback = callback + self.callback_args = args + self.store = store + self.search_key = key - # should avoid checking the playing entry, since the player already handles that - - self.callback = callback - self.callback_args = args - self.store = store - self.search_key = key - - print("discovering %s" % location) - self.discoverer = GstPbutils.Discoverer(timeout=Gst.SECOND*5) - self.discoverer.connect('finished', self.finished_cb) - self.discoverer.connect('discovered', self.discovered_cb) - self.discoverer.start() - self.discoverer.discover_uri_async(location) + print("discovering %s" % location) + self.discoverer = GstPbutils.Discoverer(timeout=Gst.SECOND * 5) + self.discoverer.connect('finished', self.finished_cb) + self.discoverer.connect('discovered', self.discovered_cb) + self.discoverer.start() + self.discoverer.discover_uri_async(location) diff --git a/rb_lastfm.py b/rb_lastfm.py index a83a272..6961577 100644 --- a/rb_lastfm.py +++ b/rb_lastfm.py @@ -30,7 +30,6 @@ import re import rb3compat from coverart_album_search import BaseSearch - if rb3compat.PYVER >= 3: import configparser else: @@ -108,7 +107,6 @@ class LastFMSearch(BaseSearch): print("last.fm query url = %s" % url) return url - def album_info_cb(self, data): if data is None: print("last.fm query returned nothing") @@ -172,7 +170,6 @@ class LastFMSearch(BaseSearch): l = rb.Loader() self.rate_limit(l.get_url, (url, self.album_info_cb), 5) - def search(self, key, last_time, store, callback, args): if user_has_account() == False: diff --git a/rb_local.py b/rb_local.py index e7ef265..178125d 100644 --- a/rb_local.py +++ b/rb_local.py @@ -128,7 +128,6 @@ class LocalSearch: sys.excepthook(*sys.exc_info()) self.finished(results) - def _enum_children_cb(self, parent, result, data): try: enumfiles = parent.enumerate_children_finish(result) @@ -140,7 +139,6 @@ class LocalSearch: sys.excepthook(*sys.exc_info()) self.callback(True) - def search(self, key, last_time, store, callback, args): # ignore last_time @@ -158,10 +156,10 @@ class LocalSearch: self.album = key.get_field("album") if not self.album: - print ('no album name') + print('no album name') callback(args) return - + self.artists = key.get_field_values("artist") self.store = store self.callback = callback diff --git a/rb_musicbrainz.py b/rb_musicbrainz.py index 7feb451..4725a08 100644 --- a/rb_musicbrainz.py +++ b/rb_musicbrainz.py @@ -34,6 +34,7 @@ import rb3compat from coverart_album_search import BaseSearch + # musicbrainz URLs MUSICBRAINZ_RELEASE_URL = "http://musicbrainz.org/ws/2/release/%s?inc=artists" MUSICBRAINZ_RELEASE_PREFIX = "http://musicbrainz.org/release/" diff --git a/rb_oldcache.py b/rb_oldcache.py index 63cb021..9e1f3ec 100644 --- a/rb_oldcache.py +++ b/rb_oldcache.py @@ -31,7 +31,6 @@ from gi.repository import RB import rb3compat - gettext.install('rhythmbox', RB.locale_dir()) ART_FOLDER = os.path.expanduser(os.path.join(RB.user_cache_dir(), 'covers'))