pycharm reformatting
Esse commit está contido em:
+83
-77
@@ -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
@@ -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
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 []
|
||||
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário