1
# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
3
# Copyright (C) 2006 - Ed Catmur <ed@catmur.co.uk>
4
# Copyright (C) 2009 - Jonathan Matthew <jonathan@d14n.org>
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2, or (at your option)
11
# The Rhythmbox authors hereby grant permission for non-GPL compatible
12
# GStreamer plugins to be used and distributed together with GStreamer
13
# and Rhythmbox. This permission is above and beyond the permissions granted
14
# by the GPL license by which Rhythmbox is covered. If you modify this code
15
# you may extend this exception to your version of the code, but you are not
16
# obligated to do so. If you do not wish to do so, delete this exception
17
# statement from your version.
19
# This program is distributed in the hope that it will be useful,
20
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
# GNU General Public License for more details.
24
# You should have received a copy of the GNU General Public License
25
# along with this program; if not, write to the Free Software
26
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
33
from gi.repository import RB
34
from gi.repository import Gio
36
IMAGE_NAMES = ["cover", "album", "albumart", ".folder", "folder"]
37
ITEMS_PER_NOTIFICATION = 10
38
ART_SAVE_NAME = 'Cover.jpg'
39
ART_SAVE_FORMAT = 'jpeg'
40
ART_SAVE_SETTINGS = {"quality": "100"}
42
IGNORED_SCHEMES = ('http', 'cdda', 'daap', 'mms')
44
def file_root (f_name):
45
return os.path.splitext (f_name)[0].lower ()
47
def shared_prefix_length (a, b):
53
def get_search_props(db, entry):
54
artist = entry.get_string(RB.RhythmDBPropType.ALBUM_ARTIST)
56
artist = entry.get_string(RB.RhythmDBPropType.ARTIST)
57
album = entry.get_string(RB.RhythmDBPropType.ALBUM)
58
return (artist, album)
61
class LocalCoverArtSearch:
65
def _enum_dir_cb(self, fileenum, result, (results, on_search_completed_cb, entry, args)):
67
files = fileenum.next_files_finish(result)
68
if files is None or len(files) == 0:
69
print "okay, done; got %d files" % len(results)
70
on_search_completed_cb(self, entry, results, *args)
74
ct = f.get_attribute_string("standard::content-type")
75
# assume readable unless told otherwise
77
if f.has_attribute("access::can-read"):
78
readable = f.get_attribute_boolean("access::can-read")
79
if ct is not None and ct.startswith("image/") and readable:
80
results.append(f.get_name())
82
fileenum.next_files_async(ITEMS_PER_NOTIFICATION, glib.PRIORITY_DEFAULT, None, self._enum_dir_cb, (results, on_search_completed_cb, entry, args))
84
print "okay, probably done: %s" % e
86
sys.excepthook(*sys.exc_info())
87
on_search_completed_cb(self, entry, results, *args)
90
def _enum_children_cb(self, parent, result, (on_search_completed_cb, entry, args)):
92
enumfiles = parent.enumerate_children_finish(result)
93
enumfiles.next_files_async(ITEMS_PER_NOTIFICATION, glib.PRIORITY_DEFAULT, None, self._enum_dir_cb, ([], on_search_completed_cb, entry, args))
95
print "okay, probably done: %s" % e
97
sys.excepthook(*sys.exc_info())
98
on_search_completed_cb(self, entry, [], *args)
101
def search (self, db, entry, is_playing, on_search_completed_cb, *args):
103
self.file = Gio.file_new_for_uri(entry.get_playback_uri())
104
if self.file.get_uri_scheme() in IGNORED_SCHEMES:
105
print 'not searching for local art for %s' % (self.file.get_uri())
106
on_search_completed_cb (self, entry, [], *args)
109
self.artist, self.album = get_search_props(db, entry)
111
print 'searching for local art for %s' % (self.file.get_uri())
112
parent = self.file.get_parent()
113
enumfiles = parent.enumerate_children_async("standard::content-type,access::can-read,standard::name", 0, 0, None, self._enum_children_cb, (on_search_completed_cb, entry, args))
115
def search_next (self):
118
def get_result_meta (self, results):
121
def get_result_pixbuf (self, results):
124
def get_best_match_urls (self, results):
125
parent = self.file.get_parent()
127
# Compare lower case, without file extension
128
for name in [file_root (self.file.get_basename())] + IMAGE_NAMES:
129
for f_name in results:
130
if file_root (f_name) == name:
131
yield parent.resolve_relative_path(f_name).get_uri()
133
# look for file names containing the artist and album (case-insensitive)
134
# (mostly for jamendo downloads)
135
artist = self.artist.lower()
136
album = self.album.lower()
137
for f_name in results:
138
f_root = file_root (f_name).lower()
139
if f_root.find (artist) != -1 and f_root.find (album) != -1:
140
yield parent.resolve_relative_path(f_name).get_uri()
142
# if that didn't work, look for the longest shared prefix
143
# only accept matches longer than 2 to avoid weird false positives
145
for f_name in results:
146
pl = shared_prefix_length(f_name, self.file.get_basename())
150
if match[1] is not None:
151
yield parent.resolve_relative_path(match[1]).get_uri()
155
def _pixbuf_save (self, pixbuf, file):
156
def pixbuf_cb(buf, stream):
157
# can't be bothered doing this asynchronously..
160
def replace_cb(file, result, pixbuf):
162
stream = file.replace_finish(result)
163
pixbuf.save_to_callback(pixbuf_cb, ART_SAVE_FORMAT, ART_SAVE_SETTINGS, user_data=stream)
166
print "error creating %s: %s" % (file.get_uri(), e)
168
file.replace_async(replace_cb, user_data=pixbuf())
170
def _save_dir_cb (self, enum, result, (db, entry, parent, pixbuf)):
171
artist, album = get_search_props(db, entry)
174
files = enum.next_files_finish(result)
176
art_file = dir.resolve_relative_path(ART_SAVE_NAME)
177
print "saving local art to \"%s\"" % art_file.get_uri()
178
self._pixbuf_save (pixbuf, art_file)
183
ct = f.get_attribute_string("standard::fast-content-type")
184
if ct.startswith("image/") or ct.startswith("x-directory/"):
187
uri = parent.resolve_relative_path(f.get_name()).get_uri()
188
u_entry = db.entry_lookup_by_location (uri)
190
u_artist, u_album = get_search_props(db, u_entry)
192
print "Not saving local art; encountered media with different album (%s, %s, %s)" % (uri, u_artist, u_album)
196
print "Not saving local art; encountered unknown file (%s)" % uri
200
enum.next_files_async(ITEMS_PER_NOTIFICATION, glib.PRIORITY_DEFAULT, None, self._save_dir_cb, (db, entry, parent, pixbuf))
202
print "Error reading \"%s\": %s" % (dir, e)
204
def _save_enum_children_cb (self, parent, result, (db, entry, pixbuf)):
206
enum = parent.enumerate_children_finish(result)
207
enum.next_files_async(ITEMS_PER_NOTIFICATION, glib.PRIORITY_DEFAULT, None, self._save_dir_cb, (db, entry, parent, pixbuf))
211
def save_pixbuf (self, db, entry, pixbuf):
212
uri = entry.get_playback_uri()
213
if uri is None or uri == '':
216
f = Gio.file_new_for_uri(uri)
217
if f.get_uri_scheme() in IGNORED_SCHEMES:
218
print "not saving local art for %s" % uri
221
print 'checking whether to save local art for %s' % uri
222
parent = f.get_parent()
224
parent.enumerate_children_async("standard::content-type,access::can-read,standard::name", Gio.FileQueryInfoFlags.NONE, glib.PRIORITY_DEFAULT, None, self._save_enum_children_cb, (db, entry, pixbuf))
226
print "unable to scan directory %s: %s" % (parent.get_uri(), e)