pycharm reformatting

Esse commit está contido em:
fossfreedom
2015-02-04 21:11:50 +00:00
commit b7336caf2e
11 arquivos alterados com 527 adições e 484 exclusões
+83 -77
Ver Arquivo
@@ -1,7 +1,7 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
## adapted from artsearch plugin - Copyright (C) 2012 Jonathan Matthew
# # adapted from artsearch plugin - Copyright (C) 2012 Jonathan Matthew
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,23 +25,24 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import RB
from gi.repository import GLib
from gi.repository import Gdk
from gi.repository import Gio
import rb3compat
import os, time,re
import os
import re
import threading
import discogs_client as discogs
import json
import chardet
import rb
import time
import base64
from gi.repository import RB
from gi.repository import GLib
from gi.repository import Gio
import chardet
import rb3compat
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
@@ -56,30 +57,32 @@ DISC_NUMBER_REGEXS = (
" disc *[0-9]+$",
" cd *[0-9]+$",
" volume *[0-9]")
SPOTIFY_API_URL = "https://api.spotify.com/v1/"
def file_root (f_name):
return os.path.splitext (f_name)[0].lower ()
def file_root(f_name):
return os.path.splitext(f_name)[0].lower()
class BaseSearch(object):
def __init__(self):
self.current_time = time.time()
def rate_limit(self, callback_func, args, per_second_rate):
print ("rate_limit")
print("rate_limit")
diff = time.time() - self.current_time
if diff < (1.0 / per_second_rate):
#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")
callback_func( *args )
time.sleep((1.0 / per_second_rate) - diff)
print("sleeping")
callback_func(*args)
self.current_time = time.time()
class CoverSearch(object):
def __init__(self, store, key, last_time, searches):
self.store = store
@@ -96,7 +99,7 @@ class CoverSearch(object):
inputs - True means continue with searching
- False means a search routine recommends no more searching
'''
print("next search")
print(continue_search)
if len(self.searches) == 0 and continue_search:
@@ -120,24 +123,24 @@ class CoverSearch(object):
def search_done(self, args):
self.next_search(args)
class CoverAlbumSearch:
def __init__ (self):
def __init__(self):
pass
def finished(self, results):
parent = self.file.get_parent()
continue_search = True
base = file_root (self.file.get_basename())
base = file_root(self.file.get_basename())
for f_name in results:
if file_root (f_name) == base:
if file_root(f_name) == base:
uri = parent.resolve_relative_path(f_name).get_parse_name()
found = self.get_embedded_image(uri)
if found:
continue_search = False
break
self.callback(continue_search)
def _enum_dir_cb(self, fileenum, result, results):
@@ -154,7 +157,7 @@ class CoverAlbumSearch:
readable = True
if f.has_attribute("access::can-read"):
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()
results.append(f.get_name())
@@ -163,6 +166,7 @@ class CoverAlbumSearch:
except Exception as e:
print("okay, probably done: %s" % e)
import sys
sys.excepthook(*sys.exc_info())
self.finished(results)
@@ -174,11 +178,12 @@ class CoverAlbumSearch:
except Exception as e:
print("okay, probably done: %s" % e)
import sys
sys.excepthook(*sys.exc_info())
self.callback(True)
def search (self, key, last_time, store, callback, args):
def search(self, key, last_time, store, callback, args):
# ignore last_time
print("calling search")
location = key.get_info("location")
@@ -201,13 +206,14 @@ class CoverAlbumSearch:
print('searching for local art for %s' % (self.file.get_uri()))
parent = self.file.get_parent()
enumfiles = parent.enumerate_children_async("standard::content-type,access::can-read,standard::name",
0, 0, None, self._enum_children_cb, None)
0, 0, None, self._enum_children_cb, None)
def get_embedded_image(self, search):
print("get_embedded_image")
import tempfile
imagefilename = tempfile.NamedTemporaryFile(delete=False)
key = RB.ExtDBKey.create_storage("album", self.album)
key.add_field("artist", self.artists[0])
parent = self.file.get_parent()
@@ -216,13 +222,13 @@ class CoverAlbumSearch:
try:
module = mutagen_library('mp4')
mp = module.MP4(search)
if len(mp[b'covr']) >= 1:
imagefilename.write(mp[b'covr'][0])
uri = parent.resolve_relative_path(imagefilename.name).get_uri()
imagefilename.close()
self.store.store_uri(key, RB.ExtDBSourceType.USER, uri)
return True
return True
except:
pass
@@ -235,7 +241,7 @@ class CoverAlbumSearch:
imagefilename.close()
uri = parent.resolve_relative_path(imagefilename.name).get_uri()
self.store.store_uri(key, RB.ExtDBSourceType.USER, uri)
return True
return True
except:
pass
@@ -243,18 +249,18 @@ class CoverAlbumSearch:
try:
module = mutagen_library('oggvorbis')
o = module.OggVorbis(search)
try:
pic=o['COVERART'][0]
pic = o['COVERART'][0]
except:
pic=o['METADATA_BLOCK_PICTURE'][0]
y=base64.b64decode(pic)
pic = o['METADATA_BLOCK_PICTURE'][0]
y = base64.b64decode(pic)
imagefilename.write(y)
imagefilename.close()
uri = parent.resolve_relative_path(imagefilename.name).get_uri()
self.store.store_uri(key, RB.ExtDBSourceType.USER, uri)
return True
return True
except:
pass
@@ -268,29 +274,29 @@ class CoverAlbumSearch:
imagefilename.close()
uri = parent.resolve_relative_path(imagefilename.name).get_uri()
self.store.store_uri(key, RB.ExtDBSourceType.USER, uri)
return True
return True
except:
pass
print("dont know")
imagefilename.delete=True
imagefilename.delete = True
imagefilename.close()
return False
class DiscogsSearch (object):
class DiscogsSearch(object):
def __init__(self):
discogs.user_agent = 'CoverartBrowserSearch/1.0 +https://github.com/fossfreedom/coverart-browser'
def search_url (self, artist, album):
def search_url(self, artist, album):
# Remove variants of Disc/CD [1-9] from album title before search
for exp in DISC_NUMBER_REGEXS:
p = re.compile (exp, re.IGNORECASE)
album = p.sub ('', album)
p = re.compile(exp, re.IGNORECASE)
album = p.sub('', album)
album.strip()
url = "%s/%s" % (artist,album)
url = "%s/%s" % (artist, album)
print("discogs url = %s" % url)
return url
@@ -306,7 +312,7 @@ class DiscogsSearch (object):
if url == last_url:
continue
last_url = url
try:
try:
s = discogs.Search(url)
url = s.results()[0].data['images'][0]['uri']
current_key = RB.ExtDBKey.create_storage("album", album)
@@ -322,7 +328,7 @@ class DiscogsSearch (object):
self.callback(continue_search)
return False
def search(self, key, last_time, store, callback, args):
album = key.get_field("album")
@@ -332,7 +338,7 @@ class DiscogsSearch (object):
album = None
if album == None or len(artists) == 0:
callback (True)
callback(True)
return
self.searches = []
@@ -344,16 +350,16 @@ class DiscogsSearch (object):
self.callback = callback
self.callback_args = args
threading.Thread( target=self.get_release_cb, args=(store, key, self.searches, args, callback)).start()
class CoverartArchiveSearch(BaseSearch):
threading.Thread(target=self.get_release_cb, args=(store, key, self.searches, args, callback)).start()
class CoverartArchiveSearch(BaseSearch):
def __init__(self):
super(CoverartArchiveSearch, self).__init__()
# coverartarchive URL
self.url = "http://coverartarchive.org/release/%s/"
def get_release_cb (self, data, args):
def get_release_cb(self, data, args):
(key, store, callback, cbargs) = args
if data is None:
print("coverartarchive release request returned nothing")
@@ -363,18 +369,18 @@ class CoverartArchiveSearch(BaseSearch):
resp = json.loads(data.decode('utf-8'))
image_url = resp['images'][0]['image']
print(image_url)
storekey = RB.ExtDBKey.create_storage('album', key.get_field('album'))
storekey.add_field("artist", key.get_field("artist"))
store.store_uri(storekey, RB.ExtDBSourceType.SEARCH, image_url)
callback(False)
except Exception as e:
print("exception parsing coverartarchive response: %s" % e)
callback(True)
def search(self, key, last_time, store, callback, *args):
key = key.copy() # ugh
key = key.copy() # ugh
album_id = key.get_info("musicbrainz-albumid")
if album_id is None:
print("no musicbrainz release ID for this track")
@@ -384,18 +390,19 @@ class CoverartArchiveSearch(BaseSearch):
url = self.url % (album_id)
print(url)
loader = rb.Loader()
self.rate_limit( loader.get_url, (url, self.get_release_cb, (key, store, callback, args)), 1)
class SpotifySearch (BaseSearch):
self.rate_limit(loader.get_url, (url, self.get_release_cb, (key, store, callback, args)), 1)
class SpotifySearch(BaseSearch):
def __init__(self):
super(SpotifySearch, self).__init__()
def search_url (self, artist, album):
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)
p = re.compile(exp, re.IGNORECASE)
album = p.sub('', album)
album.strip()
@@ -409,7 +416,7 @@ class SpotifySearch (BaseSearch):
return url
def album_info_cb (self, data, album_name):
def album_info_cb(self, data, album_name):
if data is None:
print("spotify query returned nothing")
self.search_next()
@@ -418,24 +425,24 @@ class SpotifySearch (BaseSearch):
encoding = chardet.detect(data)['encoding']
encoded = data.decode(encoding)
json_data = json.loads(encoded)
albums = json_data['albums']['items']
for album in albums:
if album['name'] in album_name or \
album_name in album['name']:
album_name in album['name']:
url = album['images'][0]['url']
print (url)
print(url)
self.store.store_uri(self.current_key, RB.ExtDBSourceType.SEARCH, url)
self.callback(False)
return
self.search_next()
def search_next (self):
def search_next(self):
if len(self.searches) == 0:
self.callback(True)
print ('no more searches')
print('no more searches')
return
(artist, album) = self.searches.pop(0)
self.current_key = RB.ExtDBKey.create_storage("album", album)
@@ -444,13 +451,12 @@ class SpotifySearch (BaseSearch):
self.current_key.add_field("artist", artist)
else:
self.current_key.add_field("artist", key_artist)
url = self.search_url(artist, album)
l = rb.Loader()
self.rate_limit( l.get_url, (url, self.album_info_cb, album), 2 )
self.rate_limit(l.get_url, (url, self.album_info_cb, album), 2)
def search(self, key, last_time, store, callback, args):
album = key.get_field("album")
@@ -463,7 +469,7 @@ class SpotifySearch (BaseSearch):
if album == None or len(artists) == 0:
print("can't search: no useful details")
callback (True)
callback(True)
return
self.searches = []
+39 -43
Ver Arquivo
@@ -1,7 +1,7 @@
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
## adapted from artsearch plugin - Copyright (C) 2012 Jonathan Matthew
# # adapted from artsearch plugin - Copyright (C) 2012 Jonathan Matthew
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,22 +25,17 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import RB
from gi.repository import GLib
from gi.repository import Gdk
from gi.repository import Gio
import os, time,re
import threading
import os
import json
import rb
import time
import xml.dom.minidom as dom
import re
import rb3compat
import gettext
from gi.repository import RB
import chardet
import gettext
import rb
import rb3compat
gettext.install('rhythmbox', RB.locale_dir())
if rb3compat.PYVER >= 3:
@@ -52,14 +47,16 @@ ITEMS_PER_NOTIFICATION = 10
IGNORED_SCHEMES = ('http', 'cdda', 'daap', 'mms')
REPEAT_SEARCH_PERIOD = 86400 * 7
def file_root (f_name):
return os.path.splitext (f_name)[0].lower ()
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'
API_URL = 'http://ws.audioscrobbler.com/2.0/'
def user_has_account():
session_file = os.path.join(RB.user_data_dir(), "audioscrobbler", "sessions")
@@ -90,7 +87,7 @@ class ArtistCoverSearch(object):
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"))
self.store.store(key, RB.ExtDBSourceType.NONE, None)
@@ -107,12 +104,12 @@ class ArtistCoverSearch(object):
self.next_search(args)
class LastFMArtistSearch (object):
class LastFMArtistSearch(object):
def __init__(self):
pass
def search_url (self, artist):
def search_url(self, artist):
print(("searching for (%s)" % (artist)))
url = API_URL + "?method=artist.getinfo&"
url = url + "artist=%s&" % (rb3compat.quote_plus(artist))
@@ -121,60 +118,59 @@ class LastFMArtistSearch (object):
print(("last.fm query url = %s" % url))
return url
def artist_info_cb (self, data):
def artist_info_cb(self, data):
if data is None:
print("last.fm query returned nothing")
self.callback (True)
self.callback(True)
return
encoding = chardet.detect(data)['encoding']
encoded = data.decode(encoding)
json_data = json.loads(encoded)
if 'artist' not in json_data:
print ("no artists found in data returned")
self.callback (True)
print("no artists found in data returned")
self.callback(True)
return
artist = json_data['artist']
# find image URLs
image_urls = []
if 'image' not in artist:
print ("no images found for artist")
self.callback (True)
print("no images found for artist")
self.callback(True)
return
for key in artist['image']:
for url in list(key.values()):
url.strip()
if url.endswith('.png') or url.endswith('.jpg'):
image_urls.append(url)
if len(image_urls) > 0:
# 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
site = rb3compat.urlopen(url)
meta = site.info()
if rb3compat.PYVER >= 3:
size = meta.get_all('Content-Length')[0]
else:
size = meta.getheaders("Content-Length")[0]
if int(size) > 1000:
self.store.store_uri(self.current_key, RB.ExtDBSourceType.SEARCH, str(url))
self.callback(False)
return
self.callback (True)
def search_next (self, artist):
print ("search_next")
self.callback(True)
def search_next(self, artist):
print("search_next")
artist = str(artist)
self.current_key = RB.ExtDBKey.create_storage("artist", artist)
@@ -191,7 +187,7 @@ class LastFMArtistSearch (object):
if user_has_account() == False:
print("can't search: no last.fm account details")
callback (True)
callback(True)
return
artist = key.get_field("artist")
@@ -199,7 +195,7 @@ class LastFMArtistSearch (object):
if artist == None:
print("can't search: no useful details")
callback (True)
callback(True)
return
self.store = store
+64 -61
Ver Arquivo
@@ -17,32 +17,34 @@
# along with this program; if not, write to the Free Software
# 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
from gi.repository import GLib
from gi.repository import Gdk
import rb3compat
import time
if rb3compat.PYVER >=3:
if rb3compat.PYVER >= 3:
import dbm.gnu as gdbm
else:
import gdbm
import os
import json
class Queue:
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def enqueue(self, item):
self.items.insert(0,item)
self.items.insert(0, item)
def dequeue(self):
return self.items.pop()
@@ -50,6 +52,7 @@ class Queue:
def size(self):
return len(self.items)
class CoverArtExtDB:
'''
This is a simplified version of the RB.ExtDB capability. This
@@ -65,152 +68,152 @@ class CoverArtExtDB:
:param name: `str` name of the external database.
'''
# storage for the instance references
__instances = {}
NEXT_FILE = 'next-file'
class _impl(GObject.Object):
""" Implementation of the singleton interface """
# properties
# signals
__gsignals__ = {
'added': (GObject.SIGNAL_RUN_LAST, None, (object, object, object)),
'request': (GObject.SIGNAL_RUN_LAST, None, (object, object))
}
#added (ExtDB self, ExtDBKey object, String path, Value pixbuf)
}
# added (ExtDB self, ExtDBKey object, String path, Value pixbuf)
#request (ExtDB self, ExtDBKey object, guint64 last_time)
_callback = {}
def __init__(self, name):
super(CoverArtExtDB._impl, self).__init__()
self.cachedir = RB.user_cache_dir() + "/" + name
if not os.path.exists(self.cachedir):
os.makedirs(self.cachedir)
filename = self.cachedir + "/store.db"
self.db = gdbm.open(filename, 'c')
self.queue = Queue()
self._store_request_in_progress = False
def _encode(self, param):
if rb3compat.PYVER >=3:
if rb3compat.PYVER >= 3:
return param.encode('utf-8')
else:
return param
def _decode(self, param):
if rb3compat.PYVER >=3:
if rb3compat.PYVER >= 3:
return param.decode('utf-8')
else:
return param
def _get_next_file(self):
if self._encode(CoverArtExtDB.NEXT_FILE) not in self.db:
next_file = str(0).zfill(8)
else:
next_file = self._decode(self.db[self._encode(CoverArtExtDB.NEXT_FILE)])
next_file = str(int(next_file)+1).zfill(8)
next_file = str(int(next_file) + 1).zfill(8)
self.db[self._encode(CoverArtExtDB.NEXT_FILE)] = next_file
return next_file
def _get_field_key(self, key):
return "/".join(key.get_field_names())
def _get_field_values(self, key):
field_values = ""
for field in key.get_field_names():
field_values += "#!~#".join(key.get_field_values(field))
return field_values
def _construct_key(self, key):
keyval = self._get_field_key(key) + 'values' + \
self._get_field_values(key)
self._get_field_values(key)
keyval = self._encode(keyval)
return keyval
return keyval
def store(self, key, source_type, data):
'''
:param key: `ExtDBKey`
:param source_type: `ExtDBSourceType`
:param data: `GdkPixbuf.Pixbuf`
'''
print ("store")
print("store")
self.store_uri(key, source_type, data)
def do_store_request(self, *args):
if self._store_request_in_progress:
return False
print ("do_store_request")
print("do_store_request")
self._store_request_in_progress = True
while not self.queue.isEmpty():
key, source_type, data = self.queue.dequeue()
storeval = {}
storeval['last-time'] = time.time()
filename = ''
if data and source_type != RB.ExtDBSourceType.NONE:
filename = self._get_next_file()
storeval['filename'] = filename
filename = self.cachedir + "/" + filename
if isinstance(data, GdkPixbuf.Pixbuf):
data.savev(filename, 'png', [], [])
else:
gfile = Gio.File.new_for_uri(data)
try:
found, contents, error = gfile.load_contents(None)
if not found:
print ("not found")
print("not found")
continue
except:
print ("failed to load uri %s", data)
print("failed to load uri %s", data)
self._store_request_in_progress = False
return
new = Gio.File.new_for_path(filename)
new.replace_contents(contents, '', '', False, None)
self.emit('added', key, filename, data)
else:
storeval['filename']=''
storeval['filename'] = ''
param = self._construct_key(key)
self.db[param] = json.dumps(storeval)
if param in self._callback:
callback, user_data = self._callback[param]
callback(key, filename, data, user_data)
self._store_request_in_progress = False
print ("finish store request")
print("finish store request")
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")
print("store_uri")
self.queue.enqueue((key, source_type, data))
#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`
@@ -221,10 +224,10 @@ class CoverArtExtDB:
storeval = json.loads(self._decode(self.db[lookup]))
if storeval['filename'] != '':
filename = self.cachedir + "/" + storeval['filename']
return str(filename)
def request(self, key, callback, user_data):
'''
:param key: `ExtDBKey`
@@ -234,9 +237,9 @@ class CoverArtExtDB:
where callback is
Function (ExtDBKey key, String filename, GdkPixbuf.Pixbuf data, void* user_data) boolean
'''
lookup = self._construct_key(key)
filename = ''
timeval = time.time()
if lookup in self.db:
@@ -244,7 +247,7 @@ class CoverArtExtDB:
if storeval['filename'] != '':
filename = self.cachedir + "/" + storeval['filename']
timeval = storeval['last-time']
result = False
if filename != '':
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
@@ -253,7 +256,7 @@ class CoverArtExtDB:
self._callback[lookup] = (callback, user_data)
self.emit('request', key, timeval)
result = True
return result
def __init__(self, name):
+30 -32
Ver Arquivo
@@ -18,20 +18,15 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
# define plugin
import rb
import locale
import gettext
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import RB
from gi.repository import GdkPixbuf
from gi.repository import Peas
from coverart_search_providers_prefs import GSetting
from coverart_search_providers_prefs import CoverLocale
from coverart_album_search import CoverAlbumSearch
from coverart_album_search import DiscogsSearch
from coverart_album_search import CoverSearch
from coverart_album_search import CoverartArchiveSearch
from coverart_artist_search import ArtistCoverSearch
@@ -46,12 +41,14 @@ from rb_musicbrainz import MusicBrainzSearch
from coverart_search_providers_prefs import SearchPreferences
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
@@ -62,6 +59,7 @@ def get_search_providers():
return current_providers.split(',')
class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
'''
Main class of the plugin. Manages the activation and deactivation of the
@@ -69,15 +67,15 @@ class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
'''
__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()
GObject.threads_init()
def do_activate(self):
'''
Called by Rhythmbox when the plugin is activated. It creates the
@@ -87,8 +85,8 @@ class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
#define .plugin text strings used for translation
# define .plugin text strings used for translation
plugin = _('CoverArt Browser Search Providers')
desc = _('Additional coverart search providers for Rhythmbox')
@@ -98,10 +96,9 @@ class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
self.art_store = RB.ExtDB(name="album-art")
self.req_id = self.art_store.connect("request", self.album_art_requested)
self.artist_store = CoverArtExtDB(name="artist-art")
self.artist_req_id = self.artist_store.connect("request", self.artist_art_requested)
self.peas = Peas.Engine.get_default()
loaded_plugins = self.peas.get_loaded_plugins()
@@ -110,7 +107,7 @@ class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
if 'artsearch' in loaded_plugins:
artsearch_info = self.peas.get_plugin_info('artsearch')
self._unload_artsearch( self.peas, artsearch_info )
self._unload_artsearch(self.peas, artsearch_info)
self.csi_id = self.shell.connect("create_song_info", self.create_song_info)
@@ -123,14 +120,14 @@ class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
def _unload_artsearch(self, engine, info):
engine.unload_plugin(info)
dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.WARNING,
Gtk.ButtonsType.OK,
_("Conflicting plugin found."))
Gtk.ButtonsType.OK,
_("Conflicting plugin found."))
dialog.format_secondary_text(
_("The ArtSearch plugin has been deactivated"))
_("The ArtSearch plugin has been deactivated"))
dialog.run()
dialog.destroy()
def do_deactivate(self):
'''
Called by Rhythmbox when the plugin is deactivated. It makes sure to
@@ -150,7 +147,7 @@ class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
self.art_store = None
self.artist_store = None
self.peas = None
print("CoverArtBrowser DEBUG - end do_deactivate")
def create_song_info(self, shell, song_info, is_multiple):
@@ -158,6 +155,7 @@ class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
# following only valid for rhythmbox 3.2
try:
import sys
artsearch_dir = self.peas.get_plugin_info('artsearch').get_module_dir()
sys.path.append(artsearch_dir)
from songinfo import AlbumArtPage
@@ -168,7 +166,7 @@ class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
def album_art_requested(self, store, key, last_time):
searches = []
current_list = get_search_providers()
for provider in current_list:
@@ -184,28 +182,28 @@ class CoverArtAlbumSearchPlugin(GObject.Object, Peas.Activatable):
searches.append(MusicBrainzSearch())
if provider == SearchPreferences.SPOTIFY_SEARCH:
searches.append(SpotifySearch())
#if provider == SearchPreferences.DISCOGS_SEARCH:
# if provider == SearchPreferences.DISCOGS_SEARCH:
# searches.append(DiscogsSearch())
if provider == SearchPreferences.COVERARTARCHIVE_SEARCH:
searches.append(CoverartArchiveSearch())
s = CoverSearch(store, key, last_time, searches)
return s.next_search(True)
def artist_art_requested(self, store, key, last_time):
print ("artist_art_requested")
print (store)
print (key)
print (last_time)
print("artist_art_requested")
print(store)
print(key)
print(last_time)
searches = []
searches.append(LastFMArtistSearch())
#searches.append(DiscogsSearch())
# searches.append(DiscogsSearch())
s = ArtistCoverSearch(store, key, last_time, searches)
print ("finished artist_art_requested")
print("finished artist_art_requested")
return s.next_search(True)
+46 -43
Ver Arquivo
@@ -16,6 +16,12 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import copy
import gettext
import locale
import webbrowser
from collections import OrderedDict
from gi.repository import Gio
from gi.repository import GObject
from gi.repository import Gtk
@@ -24,11 +30,7 @@ from gi.repository import RB
from gi.repository import Peas
import rb
import copy
import gettext
import locale
import webbrowser
from collections import OrderedDict
class CoverLocale:
'''
@@ -92,6 +94,7 @@ class CoverLocale:
""" Delegate access to implementation """
return setattr(self.__instance, attr, value)
class GSetting:
'''
This class manages the differentes settings that the plugins haves to
@@ -164,6 +167,7 @@ class GSetting:
""" Delegate access to implementation """
return setattr(self.__instance, attr, value)
class SearchPreferences(GObject.Object, PeasGtk.Configurable):
'''
Preferences for the CoverArt Browser Plugins. It holds the settings for
@@ -172,14 +176,14 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable):
__gtype_name__ = 'CoverArtSearchProvidersPreferences'
object = GObject.property(type=GObject.Object)
EMBEDDED_SEARCH='embedded-search'
DISCOGS_SEARCH='discogs-search'
COVERARTARCHIVE_SEARCH='coverartarchive-search'
LOCAL_SEARCH='local-search'
CACHE_SEARCH='cache-search'
LASTFM_SEARCH='lastfm-search'
SPOTIFY_SEARCH='spotify-search'
MUSICBRAINZ_SEARCH='musicbrainz-search'
EMBEDDED_SEARCH = 'embedded-search'
DISCOGS_SEARCH = 'discogs-search'
COVERARTARCHIVE_SEARCH = 'coverartarchive-search'
LOCAL_SEARCH = 'local-search'
CACHE_SEARCH = 'cache-search'
LASTFM_SEARCH = 'lastfm-search'
SPOTIFY_SEARCH = 'spotify-search'
MUSICBRAINZ_SEARCH = 'musicbrainz-search'
def __init__(self):
'''
@@ -198,47 +202,47 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable):
def display_preferences_dialog(self, plugin):
if self._first_run:
self._first_run = False
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
self._dialog = Gtk.Dialog(modal=True, destroy_with_parent=True)
self._dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
self._dialog.set_title(_('CoverArt Browser Search Providers'))
self._dialog.set_title(_('CoverArt Browser Search Providers'))
content_area = self._dialog.get_content_area()
content_area.pack_start(self._create_display_contents(plugin), True, True, 0)
helpbutton = self._dialog.add_button(Gtk.STOCK_HELP, Gtk.ResponseType.HELP)
helpbutton.connect('clicked', self._display_help)
self._dialog.show_all()
while True:
response = self._dialog.run()
if response != Gtk.ResponseType.HELP:
break
self._dialog.hide()
def _display_help(self, *args):
peas = Peas.Engine.get_default()
uri = peas.get_plugin_info('coverart_search_providers').get_help_uri()
webbrowser.open(uri)
def _create_display_contents(self, plugin):
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
self.gs = GSetting()
self.settings = self.gs.get_setting(self.gs.Path.PLUGIN)
#. TRANSLATORS: Do not translate this string.
# . TRANSLATORS: Do not translate this string.
translators = _("translator-credits")
self.provider = OrderedDict()
self.provider[self.EMBEDDED_SEARCH] = _("Embedded coverart")
self.provider[self.LOCAL_SEARCH] = _("Local folder coverart")
self.provider[self.CACHE_SEARCH] = _("Cached coverart")
@@ -247,13 +251,12 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable):
self.provider[self.COVERARTARCHIVE_SEARCH] = _("Coverart Archive Internet Provider")
self.provider[self.MUSICBRAINZ_SEARCH] = _("MusicBrainz Internet Provider")
keep_alive_translation = _("Discogs Internet Provider")
current_providers = copy.deepcopy(self.provider)
current = self.settings[self.gs.PluginKey.PROVIDERS]
current_list = current.split(',')
# create the ui
builder = Gtk.Builder()
builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
@@ -266,7 +269,7 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable):
self.launchpad_label.set_text(translators)
else:
self.launchpad_button.set_visible(False)
self.provider_liststore = builder.get_object('provider_liststore')
self.search_liststore = builder.get_object('search_liststore')
self.provider_list = builder.get_object('provider_list')
@@ -276,21 +279,21 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable):
if key in current_providers:
del current_providers[key]
self.search_liststore.append([self.provider[key], key])
for key, value in list(current_providers.items()):
self.provider_liststore.append( [value, key] )
self.provider_liststore.append([value, key])
if len(self.provider_liststore) == 0:
self.provider_liststore.append( [self.provider[self.EMBEDDED_SEARCH], self.EMBEDDED_SEARCH] )
self.provider_liststore.append([self.provider[self.EMBEDDED_SEARCH], self.EMBEDDED_SEARCH])
# return the dialog
return builder.get_object('maingrid')
def back_clicked(self, *args):
if len(self.search_liststore) == 1:
return # keep at least one search provider
return # keep at least one search provider
model, iterval = self.search_list.get_selection().get_selected()
if iterval:
@@ -311,11 +314,11 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable):
self._store_search_providers()
def _store_search_providers(self):
item = self.search_liststore.get_iter_first ()
item = self.search_liststore.get_iter_first()
current_providers = []
while ( item != None ):
current_providers.append (self.search_liststore.get_value (item, 1))
current_providers.append(self.search_liststore.get_value(item, 1))
item = self.search_liststore.iter_next(item)
self.settings[self.gs.PluginKey.PROVIDERS] = ','.join(current_providers)
@@ -328,7 +331,7 @@ class SearchPreferences(GObject.Object, PeasGtk.Configurable):
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()
+40 -38
Ver Arquivo
@@ -17,17 +17,17 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
from gi.repository import RB
from gi.repository import Gio
import base64
from mimetypes import MimeTypes
import os
import itertools
from PIL import Image
import tempfile
import importlib
from gi.repository import RB
from gi.repository import Gio
from PIL import Image
def mutagen_library(module_name):
module = None
@@ -36,21 +36,24 @@ def mutagen_library(module_name):
return library
else:
return library + "." + module_name
try:
module = importlib.import_module(lookfor('mutagen'))
except ImportError:
module = importlib.import_module(lookfor('mutagenx'))
return module
IGNORED_SCHEMES = ('http', 'cdda', 'daap', 'mms')
def anyTrue(pred,seq):
def anyTrue(pred, seq):
'''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)
return True in map(pred, seq)
class CoverArtTracks(object):
def __init__(self):
@@ -70,30 +73,30 @@ class CoverArtTracks(object):
try:
module = mutagen_library('oggvorbis')
o = module.OggVorbis(search)
# lets get all tags into a dict
# lets also remove the deprecated coverart tag and any
# old pictures
tags = {}
for orig, values in list(o.tags.items()):
if orig.lower() != 'coverart' and \
orig.lower() != 'metadata_block_picture':
tags[orig]=values
orig.lower() != 'metadata_block_picture':
tags[orig] = values
module = mutagen_library('flac')
image = module.Picture()
image.type = 3 # Cover image
image.type = 3 # Cover image
image.data = open(art_location, "rb").read()
image.mime = mimetypestr
image.desc = 'cover description'
tags.setdefault("METADATA_BLOCK_PICTURE",
[]).append(base64.b64encode(image.write()))
o.tags.update(tags)
o.save()
except:
pass
pass
def embed_flac(self, art_location, search, mimetypestr):
'''
@@ -104,12 +107,12 @@ class CoverArtTracks(object):
try:
module = mutagen_library('')
music = module.File(search)
# lets remove any old pictures
music.clear_pictures()
module = mutagen_library('flac')
image = module.Picture()
image.type = 3 # Cover image
image.type = 3 # Cover image
image.data = open(art_location, "rb").read()
image.mime = mimetypestr
image.desc = 'cover description'
@@ -128,9 +131,9 @@ class CoverArtTracks(object):
module = mutagen_library('mp4')
music = module.MP4(search)
covr = []
covr = []
data = open(art_location, "rb").read()
if mimetypestr == "image/jpeg":
covr.append(module.MP4Cover(data, module.MP4Cover.FORMAT_JPEG))
elif mimetypestr == "image/png":
@@ -153,10 +156,9 @@ class CoverArtTracks(object):
# lets remove any old pictures
music.delall('APIC')
music.add(module.APIC(encoding=0, mime=mimetypestr, type=3,
desc='', data=open(art_location, "rb").read()))
desc='', data=open(art_location, "rb").read()))
music.save()
except:
@@ -173,32 +175,32 @@ class CoverArtTracks(object):
returns True or False depending if the routine completed successfully
'''
the=anyTrue # for readability
the = anyTrue # for readability
art_location = RB.ExtDB(name='album-art').lookup(key)
if not art_location:
print ("not a valid key to a file containing art")
print("not a valid key to a file containing art")
return False
image = Image.open(art_location)
f, art_location = tempfile.mkstemp(suffix=".jpg")
print ("resizing?")
print (resize)
print("resizing?")
print(resize)
if resize > 0:
tosave = image.resize((resize,resize), Image.ANTIALIAS)
tosave = image.resize((resize, resize), Image.ANTIALIAS)
else:
tosave = image
print (art_location)
print(art_location)
tosave.save(art_location)
search = Gio.file_new_for_uri(track_uri)
if search.get_uri_scheme() in IGNORED_SCHEMES:
print('not a valid scheme %s' % (search.get_uri()))
return False
if search.get_path().lower().endswith('.ogg'):
self.embed_ogg(art_location, search.get_path(), 'image/jpeg')
@@ -207,10 +209,10 @@ class CoverArtTracks(object):
if search.get_path().lower().endswith('.mp3'):
self.embed_mp3(art_location, search.get_path(), 'image/jpeg')
if the(search.get_path().lower().endswith,(".m4a", ".m4b", ".m4p", ".mp4")):
if the(search.get_path().lower().endswith, (".m4a", ".m4b", ".m4p", ".mp4")):
self.embed_mp4(art_location, search.get_path(), 'image/jpeg')
os.remove(art_location)
return True
+162 -140
Ver Arquivo
@@ -22,14 +22,17 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import sys
import xml.etree.ElementTree as ET
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import RB
import sys
import rb
import xml.etree.ElementTree as ET
def pygobject_version():
'''
@@ -37,31 +40,33 @@ def 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)
return float(str_version.rsplit('.',1)[0])
return float(str_version.rsplit('.', 1)[0])
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)
split = str_version.rsplit('.',2)
split_compare = version.rsplit('.',2)
if int(split_compare[0])<int(split[0]):
split = str_version.rsplit('.', 2)
split_compare = version.rsplit('.', 2)
if int(split_compare[0]) < int(split[0]):
return True
if int(split_compare[1])<int(split[1]):
if int(split_compare[1]) < int(split[1]):
return True
return False
PYVER = sys.version_info[0]
if PYVER >= 3:
@@ -74,65 +79,75 @@ if PYVER >= 3:
import http.client
else:
import httplib
def responses():
if PYVER >=3:
if PYVER >= 3:
return http.client.responses
else:
return httplib.responses
def unicodestr(param, charset):
if PYVER >=3:
return param#str(param, charset)
if PYVER >= 3:
return param # str(param, charset)
else:
return unicode(param, charset)
def unicodeencode(param, charset):
if PYVER >=3:
return param#str(param).encode(charset)
if PYVER >= 3:
return param # str(param).encode(charset)
else:
return unicode(param).encode(charset)
def unicodedecode(param, charset):
if PYVER >=3:
if PYVER >= 3:
return param
else:
return param.decode(charset)
def urlparse(uri):
if PYVER >=3:
if PYVER >= 3:
return urllib.parse.urlparse(uri)
else:
return rb2urlparse(uri)
def url2pathname(url):
if PYVER >=3:
if PYVER >= 3:
return urllib.request.url2pathname(url)
else:
return urllib.url2pathname(url)
def urlopen(filename):
if PYVER >=3:
if PYVER >= 3:
return urllib.request.urlopen(filename)
else:
return urllib.urlopen(filename)
def pathname2url(filename):
if PYVER >=3:
if PYVER >= 3:
return urllib.request.pathname2url(filename)
else:
return urllib.pathname2url(filename)
def unquote(uri):
if PYVER >=3:
if PYVER >= 3:
return urllib.parse.unquote(uri)
else:
return urllib.unquote(uri)
def quote(uri, safe=None):
if PYVER >=3:
if PYVER >= 3:
if safe:
return urllib.parse.quote(uri,safe=safe)
return urllib.parse.quote(uri, safe=safe)
else:
return urllib.parse.quote(uri)
else:
@@ -140,27 +155,30 @@ def quote(uri, safe=None):
return urllib.quote(uri, safe=safe)
else:
return urllib.quote(uri)
def quote_plus(uri):
if PYVER >=3:
if PYVER >= 3:
return urllib.parse.quote_plus(uri)
else:
return urllib.quote_plus(uri)
def is_rb3(*args):
if hasattr(RB.Shell.props, 'ui_manager'):
return False
else:
return True
return True
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.
@@ -169,10 +187,10 @@ class Menu(GObject.Object):
self.plugin = plugin
self.shell = shell
self._unique_num = 0
self._rbmenu_items = {}
self._rbmenu_objects = {}
def add_menu_item(self, menubar, section_name, action):
'''
add a new menu item to the popup
@@ -191,7 +209,7 @@ class Menu(GObject.Object):
:param action: `Action` to associate with the menu item
'''
label = action.label
if is_rb3(self.shell):
app = self.shell.props.application
item = Gio.MenuItem()
@@ -201,14 +219,14 @@ class Menu(GObject.Object):
if not section_name in self._rbmenu_items:
self._rbmenu_items[section_name] = []
self._rbmenu_items[section_name].append(label)
app.add_plugin_menu_item(section_name, label, item)
else:
item = Gtk.MenuItem(label=label)
action.associate_menuitem(item)
self._rbmenu_items[label] = item
bar = self.get_menu_object(menubar)
if position == -1:
bar.append(item)
else:
@@ -245,15 +263,15 @@ class Menu(GObject.Object):
if is_rb3(self.shell):
if not section_name in self._rbmenu_items:
return
app = self.shell.props.application
for menu_item in self._rbmenu_items[section_name]:
app.remove_plugin_menu_item(section_name, menu_item)
if self._rbmenu_items[section_name]:
del self._rbmenu_items[section_name][:]
else:
if not self._rbmenu_items:
@@ -267,8 +285,8 @@ class Menu(GObject.Object):
bar.show_all()
uim.ensure_update()
def load_from_file(self, rb2_ui_filename, rb3_ui_filename ):
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
@@ -277,21 +295,22 @@ class Menu(GObject.Object):
self.builder = Gtk.Builder()
try:
from coverart_browser_prefs import CoverLocale
cl = CoverLocale()
self.builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
except:
pass
if is_rb3(self.shell):
ui_filename = rb3_ui_filename
else:
ui_filename = rb2_ui_filename
self.ui_filename = ui_filename
self.builder.add_from_file(rb.find_plugin_file(self.plugin,
ui_filename))
ui_filename))
def _connect_rb3_signals(self, signals):
def _menu_connect(action_name, func):
@@ -299,29 +318,29 @@ class Menu(GObject.Object):
action.connect('activate', func)
action.set_enabled(True)
self.shell.props.window.add_action(action)
for key,value in signals.items():
_menu_connect( key, value)
for key, value in signals.items():
_menu_connect(key, value)
def _connect_rb2_signals(self, signals):
def _menu_connect(menu_item_name, func):
menu_item = self.get_menu_object(menu_item_name)
menu_item.connect('activate', func)
for key,value in signals.items():
_menu_connect( key, value)
for key, value in signals.items():
_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
@@ -330,7 +349,7 @@ class Menu(GObject.Object):
if popup_name in self._rbmenu_objects:
return self._rbmenu_objects[popup_name]
item = self.builder.get_object(popup_name)
if is_rb3(self.shell):
app = self.shell.props.application
app.link_shared_menus(item)
@@ -338,11 +357,11 @@ class Menu(GObject.Object):
popup_menu.attach_to_widget(source, None)
else:
popup_menu = item
self._rbmenu_objects[popup_name] = popup_menu
return popup_menu
def get_menu_object(self, menu_name_or_link):
'''
utility function returns the GtkMenuItem/Gio.MenuItem
@@ -359,9 +378,9 @@ class Menu(GObject.Object):
popup_menu = app.get_plugin_menu(menu_name_or_link)
else:
popup_menu = item
print (menu_name_or_link)
print(menu_name_or_link)
self._rbmenu_objects[menu_name_or_link] = popup_menu
return popup_menu
def set_sensitive(self, menu_or_action_item, enable):
@@ -371,14 +390,14 @@ class Menu(GObject.Object):
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)
item.set_enabled(enable)
else:
item = self.get_menu_object(menu_or_action_item)
item.set_sensitive(enable)
def popup(self, source, menu_name, button, time):
'''
utility function to show the popup menu
@@ -386,16 +405,17 @@ class Menu(GObject.Object):
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
STANDARD = 0
TOGGLE = 1
def __init__(self, shell, group_name):
'''
constructor
@@ -404,12 +424,12 @@ class ActionGroup(object):
'''
self.group_name = group_name
self.shell = shell
self._actions = {}
if is_rb3(self.shell):
self.actiongroup = Gio.SimpleActionGroup()
else:
else:
self.actiongroup = Gtk.ActionGroup(group_name)
uim = self.shell.props.ui_manager
uim.insert_action_group(self.actiongroup)
@@ -417,14 +437,14 @@ class ActionGroup(object):
@property
def name(self):
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
@@ -447,8 +467,8 @@ class ActionGroup(object):
'''
args['accel'] = accel
return self.add_action(func, action_name, **args)
def add_action(self, func, action_name, **args ):
def add_action(self, func, action_name, **args):
'''
Creates an Action and adds it to the ActionGroup
@@ -465,21 +485,21 @@ class ActionGroup(object):
if 'label' in args:
label = args['label']
else:
label=action_name
label = action_name
if 'accel' in args:
accel = args['accel']
else:
accel = None
state = ActionGroup.STANDARD
state = ActionGroup.STANDARD
if 'action_state' in args:
state = args['action_state']
if is_rb3(self.shell):
if state == ActionGroup.TOGGLE:
action = Gio.SimpleAction.new_stateful(action_name, None,
GLib.Variant('b', False))
GLib.Variant('b', False))
else:
action = Gio.SimpleAction.new(action_name, None)
@@ -489,7 +509,7 @@ class ActionGroup(object):
action_type = 'app'
app = Gio.Application.get_default()
if action_type == 'app':
app.add_action(action)
else:
@@ -497,56 +517,58 @@ class ActionGroup(object):
self.actiongroup.add_action(action)
if accel:
app.add_accelerator(accel, action_type+"."+action_name, None)
app.add_accelerator(accel, action_type + "." + action_name, None)
else:
if 'stock_id' in args:
stock_id = args['stock_id']
else:
stock_id = Gtk.STOCK_CLEAR
if state == ActionGroup.TOGGLE:
action = Gtk.ToggleAction(label=label,
name=action_name,
tooltip='', stock_id=stock_id)
name=action_name,
tooltip='', stock_id=stock_id)
else:
action = Gtk.Action(label=label,
name=action_name,
tooltip='', stock_id=stock_id)
name=action_name,
tooltip='', stock_id=stock_id)
if accel:
self.actiongroup.add_action_with_accel(action, accel)
else:
self.actiongroup.add_action(action)
act = Action(self.shell, action)
act.connect('activate', func, args)
act.label = label
act.accel = accel
self._actions[action_name] = act
return act
class ApplicationShell(object):
'''
Unique class that mirrors RB.Application & RB.Shell menu functionality
'''
# storage for the instance reference
__instance = None
class __impl:
""" Implementation of the singleton interface """
def __init__(self, shell):
self.shell = shell
if is_rb3(self.shell):
self._uids = {}
else:
self._uids = []
self._action_groups = {}
def insert_action_group(self, action_group):
'''
Adds an ActionGroup to the ApplicationShell
@@ -554,7 +576,7 @@ class ApplicationShell(object):
: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
@@ -564,7 +586,7 @@ class ApplicationShell(object):
: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":
action = self.shell.props.application.lookup_action(action_name)
@@ -582,7 +604,7 @@ class ApplicationShell(object):
action = None
if actiongroup:
action = actiongroup.get_action(action_name)
if action:
return Action(self.shell, action)
else:
@@ -611,24 +633,24 @@ class ApplicationShell(object):
for elem in root.findall(".//menuitem"):
action_name = elem.attrib['action']
item_name = elem.attrib['name']
group = self._action_groups[group_name]
act = group.get_action(action_name)
item = Gio.MenuItem()
item.set_detailed_action('app.' + action_name)
item.set_label(act.label)
item.set_attribute_value("accel", GLib.Variant("s", act.accel))
app = Gio.Application.get_default()
index = menu+action_name
app.add_plugin_menu_item(menu,
index, item)
index = menu + action_name
app.add_plugin_menu_item(menu,
index, item)
self._uids[index] = menu
else:
uim = self.shell.props.ui_manager
self._uids.append(uim.add_ui_from_string(ui_string))
uim.ensure_update()
def add_browser_menuitems(self, ui_string, group_name):
'''
utility function to add popup menu items to existing browser popups
@@ -648,19 +670,19 @@ class ApplicationShell(object):
root = ET.fromstring(ui_string)
for elem in root.findall("./popup"):
popup_name = elem.attrib['name']
menuelem = elem.find('.//menuitem')
action_name = menuelem.attrib['action']
item_name = menuelem.attrib['name']
group = self._action_groups[group_name]
act = group.get_action(action_name)
item = Gio.MenuItem()
item.set_detailed_action('win.' + action_name)
item.set_label(act.label)
app = Gio.Application.get_default()
if popup_name == 'QueuePlaylistViewPopup':
plugin_type = 'queue-popup'
elif popup_name == 'BrowserSourceViewPopup':
@@ -670,11 +692,11 @@ class ApplicationShell(object):
elif popup_name == 'PodcastViewPopup':
plugin_type = 'podcast-episode-popup'
else:
print ("unknown type %s" % plugin_type)
index = plugin_type+action_name
print("unknown type %s" % plugin_type)
index = plugin_type + action_name
app.add_plugin_menu_item(plugin_type, index, item)
self._uids[index]=plugin_type
self._uids[index] = plugin_type
else:
uim = self.shell.props.ui_manager
self._uids.append(uim.add_ui_from_string(ui_string))
@@ -686,9 +708,8 @@ class ApplicationShell(object):
'''
if is_rb3(self.shell):
for uid in self._uids:
Gio.Application.get_default().remove_plugin_menu_item(self._uids[uid],
uid)
Gio.Application.get_default().remove_plugin_menu_item(self._uids[uid],
uid)
else:
uim = self.shell.props.ui_manager
for uid in self._uids:
@@ -713,11 +734,12 @@ class ApplicationShell(object):
""" Delegate access to implementation """
return setattr(self.__instance, attr, value)
class Action(object):
'''
class that wraps around either a Gio.Action or a Gtk.Action
'''
def __init__(self, shell, action):
'''
constructor.
@@ -727,19 +749,19 @@ class Action(object):
'''
self.shell = shell
self.action = action
self._label = ''
self._accel = ''
self._current_state = False
self._do_update_state = True
def connect(self, address, func, args):
self._connect_func = func
self._connect_args = args
if address == 'activate':
func = self._activate
if is_rb3(self.shell):
self.action.connect(address, func, args)
else:
@@ -749,9 +771,9 @@ class Action(object):
if self._do_update_state:
self._current_state = not self._current_state
self.set_state(self._current_state)
self._connect_func(action, None, self._connect_args)
@property
def label(self):
'''
@@ -764,21 +786,21 @@ class Action(object):
return self.action.get_label()
else:
return self._label
@label.setter
def label(self, new_label):
if not is_rb3(self.shell):
self.action.set_label(new_label)
self._label = new_label
@property
def accel(self):
'''
get the accelerator associated with the Action
'''
return self._accel
@accel.setter
def accel(self, new_accelerator):
if new_accelerator:
@@ -796,7 +818,7 @@ class Action(object):
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
@@ -813,7 +835,7 @@ class Action(object):
self.action.activate(None)
else:
self.action.activate()
def set_active(self, value):
'''
activate or deactivate a stateful action signal
@@ -822,7 +844,7 @@ class Action(object):
:param value: `boolean` state value
'''
if is_rb3(self.shell):
self.action.change_state(GLib.Variant('b', value))
self._current_state = value
@@ -831,7 +853,7 @@ class Action(object):
self._do_update_state = True
else:
self.action.set_active(value)
def get_active(self):
'''
get the state of the action
@@ -851,7 +873,7 @@ class Action(object):
'''
if is_rb3(self.shell):
menuitem.set_detailed_action('win.'+self.action.get_name())
menuitem.set_detailed_action('win.' + self.action.get_name())
else:
menuitem.set_related_action(self.action)
+22 -18
Ver Arquivo
@@ -26,21 +26,23 @@
import xml.dom.minidom as dom
import re
import rb3compat
from coverart_album_search import BaseSearch
if rb3compat.PYVER >= 3:
import configparser
else:
import ConfigParser as configparser
import os
import time
import rb
from gi.repository import RB
import gettext
gettext.install('rhythmbox', RB.locale_dir())
# allow repeat searches once a week
@@ -65,6 +67,7 @@ DISC_NUMBER_REGEXS = (
" cd *[0-9]+$"
)
def user_has_account():
session_file = os.path.join(RB.user_data_dir(), "audioscrobbler", "sessions")
@@ -78,16 +81,17 @@ def user_has_account():
except:
return False
class LastFMSearch (BaseSearch):
class LastFMSearch(BaseSearch):
def __init__(self):
super(LastFMSearch, self).__init__()
def search_url (self, artist, album, album_mbid):
def search_url(self, artist, album, album_mbid):
# 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)
p = re.compile(exp, re.IGNORECASE)
album = p.sub('', album)
album.strip()
@@ -105,7 +109,7 @@ class LastFMSearch (BaseSearch):
return url
def album_info_cb (self, data):
def album_info_cb(self, data):
if data is None:
print("last.fm query returned nothing")
self.search_next()
@@ -129,19 +133,19 @@ class LastFMSearch (BaseSearch):
if len(image_urls) > 0:
# 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()
if rb3compat.PYVER >= 3:
size = meta.get_all('Content-Length')[0]
else:
size = meta.getheaders("Content-Length")[0]
if int(size) > 1000:
print(size)
self.store.store_uri(self.current_key, RB.ExtDBSourceType.SEARCH, url)
self.callback(False)
else:
@@ -149,12 +153,12 @@ class LastFMSearch (BaseSearch):
else:
self.search_next()
def search_next (self):
def search_next(self):
if len(self.searches) == 0:
self.callback(True)
return
print ("search_next")
print (self.searches)
print("search_next")
print(self.searches)
(artist, album, album_mbid) = self.searches.pop(0)
self.current_key = RB.ExtDBKey.create_storage("album", album)
key_artist = self.key.get_field("artist")
@@ -166,14 +170,14 @@ class LastFMSearch (BaseSearch):
url = self.search_url(artist, album, album_mbid)
l = rb.Loader()
self.rate_limit( l.get_url, (url, self.album_info_cb), 5 )
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:
print("can't search: no last.fm account details")
callback (True)
callback(True)
return
album = key.get_field("album")
@@ -187,7 +191,7 @@ class LastFMSearch (BaseSearch):
if album == None or len(artists) == 0:
print("can't search: no useful details")
callback (True)
callback(True)
return
self.searches = []
+17 -12
Ver Arquivo
@@ -28,24 +28,26 @@
import os
from gi.repository import RB
from gi.repository import GObject, GLib, Gio
IMAGE_NAMES = ["cover", "album", "albumart", "front", ".folder", "folder"]
ITEMS_PER_NOTIFICATION = 10
IGNORED_SCHEMES = ('http', 'cdda', 'daap', 'mms')
def file_root (f_name):
return os.path.splitext (f_name)[0].lower ()
def shared_prefix_length (a, b):
def file_root(f_name):
return os.path.splitext(f_name)[0].lower()
def shared_prefix_length(a, b):
l = 0
while a[l] == b[l]:
l = l+1
l = l + 1
return l
class LocalSearch:
def __init__ (self):
def __init__(self):
pass
def finished(self, results):
@@ -56,9 +58,9 @@ class LocalSearch:
key.add_field("artist", self.artists[0])
# Compare lower case, without file extension
for name in [file_root (self.file.get_basename())] + IMAGE_NAMES:
for name in [file_root(self.file.get_basename())] + IMAGE_NAMES:
for f_name in results:
if file_root (f_name) == name:
if file_root(f_name) == name:
uri = parent.resolve_relative_path(f_name).get_uri()
self.store.store_uri(key, RB.ExtDBSourceType.USER, uri)
continue_search = False
@@ -68,12 +70,12 @@ class LocalSearch:
album = self.album.lower()
print(album)
for f_name in results:
f_root = file_root (f_name).lower()
f_root = file_root(f_name).lower()
print(f_root)
for artist in self.artists:
testartist = artist.lower()
print(artist)
if f_root.find (testartist) != -1 and f_root.find (album) != -1:
if f_root.find(testartist) != -1 and f_root.find(album) != -1:
nkey = RB.ExtDBKey.create_storage("album", self.album)
nkey.add_field("artist", artist)
uri = parent.resolve_relative_path(f_name).get_uri()
@@ -120,6 +122,7 @@ class LocalSearch:
except Exception as e:
print("okay, probably done: %s" % e)
import sys
sys.excepthook(*sys.exc_info())
self.finished(results)
@@ -131,11 +134,12 @@ class LocalSearch:
except Exception as e:
print("okay, probably done: %s" % e)
import sys
sys.excepthook(*sys.exc_info())
self.callback(True)
def search (self, key, last_time, store, callback, args):
def search(self, key, last_time, store, callback, args):
# ignore last_time
location = key.get_info("location")
@@ -158,4 +162,5 @@ class LocalSearch:
print('searching for local art for %s' % (self.file.get_uri()))
parent = self.file.get_parent()
enumfiles = parent.enumerate_children_async("standard::content-type,access::can-read,standard::name", 0, 0, None, self._enum_children_cb, None)
enumfiles = parent.enumerate_children_async("standard::content-type,access::can-read,standard::name", 0, 0,
None, self._enum_children_cb, None)
+16 -15
Ver Arquivo
@@ -25,14 +25,15 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import xml.dom.minidom as dom
import time
from gi.repository import RB
import rb
import rb3compat
import os
from gi.repository import RB
import time
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/"
@@ -47,12 +48,12 @@ MUSICBRAINZ_VARIOUS_ARTISTS = "89ad4ac3-39f7-470e-963a-56509c546377"
# Amazon URL bits
AMAZON_IMAGE_URL = "http://images.amazon.com/images/P/%s.01.LZZZZZZZ.jpg"
class MusicBrainzSearch(BaseSearch):
def __init__(self):
super(MusicBrainzSearch, self).__init__()
def get_release_cb (self, data, args):
def get_release_cb(self, data, args):
(key, store, callback, cbargs) = args
if data is None:
print("musicbrainz release request returned nothing")
@@ -86,16 +87,16 @@ class MusicBrainzSearch(BaseSearch):
image_url = AMAZON_IMAGE_URL % asin
print("got url %s" % image_url)
#now get file size before downloading
# now get file size before downloading
site = rb3compat.urlopen(image_url)
meta = site.info()
if rb3compat.PYVER >= 3:
size = meta.get_all('Content-Length')[0]
else:
size = meta.getheaders("Content-Length")[0]
if int(size) > 1000:
print(size)
store.store_uri(storekey, RB.ExtDBSourceType.SEARCH, image_url)
@@ -111,7 +112,7 @@ class MusicBrainzSearch(BaseSearch):
print("exception parsing musicbrainz response: %s" % e)
callback(True)
def try_search_artist_album (self, key, store, callback, *args):
def try_search_artist_album(self, key, store, callback, *args):
album = key.get_field("album")
artist = key.get_field("artist")
@@ -124,13 +125,13 @@ class MusicBrainzSearch(BaseSearch):
url = MUSICBRAINZ_SEARCH_URL % (rb3compat.quote(query, safe=':'),)
loader = rb.Loader()
self.rate_limit( loader.get_url, (url, self.get_release_cb, (key, store, callback, args)), 1)
self.rate_limit(loader.get_url, (url, self.get_release_cb, (key, store, callback, args)), 1)
def search(self, key, last_time, store, callback, *args):
self.current_time = time.time()
key = key.copy() # ugh
key = key.copy() # ugh
album_id = key.get_info("musicbrainz-albumid")
if album_id is None:
print("no musicbrainz release ID for this track")
@@ -147,4 +148,4 @@ class MusicBrainzSearch(BaseSearch):
url = MUSICBRAINZ_RELEASE_URL % (album_id)
loader = rb.Loader()
self.rate_limit( loader.get_url, (url, self.get_release_cb, (key, store, callback, args)), 1)
self.rate_limit(loader.get_url, (url, self.get_release_cb, (key, store, callback, args)), 1)
+8 -5
Ver Arquivo
@@ -25,21 +25,24 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import os.path
import rb3compat
import rb
import gettext
from gi.repository import RB
import gettext
import rb3compat
gettext.install('rhythmbox', RB.locale_dir())
ART_FOLDER = os.path.expanduser(os.path.join(RB.user_cache_dir(), 'covers'))
USEFUL = os.path.exists(ART_FOLDER)
class OldCacheSearch(object):
def __init__(self):
pass
def filename (self, album, artist, extension):
def filename(self, album, artist, extension):
artist = artist.replace('/', '-')
album = album.replace('/', '-')
return os.path.join(ART_FOLDER, '%s - %s.%s' % (artist, album, extension))
@@ -52,7 +55,7 @@ class OldCacheSearch(object):
if not USEFUL:
callback(True)
return
album = key.get_field("album")
artists = key.get_field_values("artist") or []