Merge lp:~jamesh/mediascanner/albumart-download into lp:mediascanner

Proposed by James Henstridge
Status: Merged
Approved by: Jussi Pakkanen
Approved revision: 401
Merged at revision: 390
Proposed branch: lp:~jamesh/mediascanner/albumart-download
Merge into: lp:mediascanner
Diff against target: 867 lines (+471/-15)
22 files modified
CMakeLists.txt (+1/-0)
debian/control (+1/-0)
mediascanner.gschema.xml.in (+1/-1)
src/mediascanner-service/main.cpp (+4/-0)
src/mediascanner/CMakeLists.txt (+1/-0)
src/mediascanner/declarations.h (+2/-0)
src/mediascanner/filesystemscanner.cpp (+7/-0)
src/mediascanner/filesystemwalker.cpp (+17/-2)
src/mediascanner/filesystemwalker.h (+1/-0)
src/mediascanner/glibutils.h (+6/-0)
src/mediascanner/mediaartcache.cpp (+25/-1)
src/mediascanner/mediaartcache.h (+2/-1)
src/mediascanner/mediaartdownloader.cpp (+297/-0)
src/mediascanner/mediaartdownloader.h (+50/-0)
src/mediascanner/mediaindex.cpp (+27/-5)
src/mediascanner/mediaindex.h (+3/-3)
src/mediascanner/metadataresolver.cpp (+3/-0)
src/mediascanner/propertyschema.cpp (+16/-0)
src/mediascanner/propertyschema.h (+3/-0)
tests/auto/filesystemscannertest.cpp (+1/-0)
tests/auto/loggingtest.cpp (+1/-1)
tests/auto/propertytest.cpp (+2/-1)
To merge this branch: bzr merge lp:~jamesh/mediascanner/albumart-download
Reviewer Review Type Date Requested Status
Paweł Stołowski (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+189631@code.launchpad.net

Commit message

Download album art to the media art cache during scanning rather than storing remote URLs in the index.

Description of the change

Instead of just adding album art URLs to the index from the metadata resolver, actively download the art to the media art cache.

When reading metadata from the index later, add any art found in the cache to the results.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

Please add libsoup to debian/control

review: Needs Fixing
Revision history for this message
Paweł Stołowski (stolowski) wrote :

Also it fails for me on tests when building the deb.

Revision history for this message
Paweł Stołowski (stolowski) wrote :

I got quite a few warnings about getting image/png instead of image/jpeg. Looking at the code we ignore such album art? Can we handle it?

review: Needs Information
399. By James Henstridge

Separate out loading of the Grilo plugin from getting the source in
MediaArtDownloader, and fix the FileSystemScanner tests to set up the
lastfm plugin.

400. By James Henstridge

Add libsoup as a Build-Depend.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
401. By James Henstridge

Merge lp:~jpakkane/mediascanner/quickfixes

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

Looks good to me, functional tests confirm it works. I'm slightly concerned about the image/png case mentioned in the comment, but don't want to block on that; to my understanding it should work as long as mediascanner returns "" to the shell; if it's a problem, then it will be a minor fix for later.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2013-08-29 08:00:35 +0000
3+++ CMakeLists.txt 2013-10-09 07:38:10 +0000
4@@ -105,6 +105,7 @@
5 gstreamer-pbutils-1.0>=0.11.93
6 gudev-1.0>=175
7 liblucene++>=3.0.3.4
8+ libsoup-2.4
9 )
10
11 # http://public.kitware.com/Bug/view.php?id=14381
12
13=== modified file 'debian/control'
14--- debian/control 2013-08-28 13:33:08 +0000
15+++ debian/control 2013-10-09 07:38:10 +0000
16@@ -20,6 +20,7 @@
17 libgtest-dev,
18 libgudev-1.0-dev,
19 liblucene++-dev (>= 3.0.4),
20+ libsoup2.4-dev,
21 python,
22 Standards-Version: 3.9.4
23 Homepage: https://launchpad.net/mediascanner
24
25=== modified file 'mediascanner.gschema.xml.in'
26--- mediascanner.gschema.xml.in 2013-08-23 02:58:42 +0000
27+++ mediascanner.gschema.xml.in 2013-10-09 07:38:10 +0000
28@@ -76,7 +76,7 @@
29 dictionary of strings describing configuration parameters like API keys.
30 </description>
31
32- <default>[("grl-tmdb", "grl-tmdb", {"api-key": "@TMDB_APIKEY@"}), ("grl-lastfm-albumart", "grl-lastfm-albumart", {})]</default>
33+ <default>[("grl-tmdb", "grl-tmdb", {"api-key": "@TMDB_APIKEY@"})]</default>
34 </key>
35 </schema>
36 </schemalist>
37
38=== modified file 'src/mediascanner-service/main.cpp'
39--- src/mediascanner-service/main.cpp 2013-09-11 09:49:12 +0000
40+++ src/mediascanner-service/main.cpp 2013-10-09 07:38:10 +0000
41@@ -43,6 +43,7 @@
42 #include "mediascanner/glibutils.h"
43 #include "mediascanner/locale.h"
44 #include "mediascanner/logging.h"
45+#include "mediascanner/mediaartdownloader.h"
46 #include "mediascanner/mediaroot.h"
47 #include "mediascanner/metadataresolver.h"
48 #include "mediascanner/property.h"
49@@ -881,6 +882,9 @@
50 if (not LoadMetadataSources())
51 return false;
52
53+ // Set up the media art downloader
54+ MediaArtDownloader::LoadGriloPlugin();
55+
56 // Setup the scanner itself.
57 notifier_.reset(new CommitNotifier(this));
58 task_manager_.reset(new TaskManager("media scanner service"));
59
60=== modified file 'src/mediascanner/CMakeLists.txt'
61--- src/mediascanner/CMakeLists.txt 2013-08-27 14:41:14 +0000
62+++ src/mediascanner/CMakeLists.txt 2013-10-09 07:38:10 +0000
63@@ -38,6 +38,7 @@
64 logging.cpp
65 logging.h
66 mediaartcache.cpp
67+ mediaartdownloader.cpp
68 mediaindex.cpp
69 mediaroot.cpp
70 mediautils.cpp
71
72=== modified file 'src/mediascanner/declarations.h'
73--- src/mediascanner/declarations.h 2013-08-23 09:45:11 +0000
74+++ src/mediascanner/declarations.h 2013-10-09 07:38:10 +0000
75@@ -112,6 +112,7 @@
76
77 class CommitPolicy;
78 class Filter;
79+class MediaArtDownloader;
80 class MediaIndex;
81 class MediaInfo;
82 class MediaRoot;
83@@ -127,6 +128,7 @@
84 class Wrapper;
85
86 typedef std::shared_ptr<CommitPolicy> CommitPolicyPtr;
87+typedef std::shared_ptr<MediaArtDownloader> MediaArtDownloaderPtr;
88 typedef std::shared_ptr<MediaRootManager> MediaRootManagerPtr;
89 typedef std::shared_ptr<MetadataResolver> MetadataResolverPtr;
90 typedef std::shared_ptr<TaskManager> TaskManagerPtr;
91
92=== modified file 'src/mediascanner/filesystemscanner.cpp'
93--- src/mediascanner/filesystemscanner.cpp 2013-09-09 11:49:53 +0000
94+++ src/mediascanner/filesystemscanner.cpp 2013-10-09 07:38:10 +0000
95@@ -32,6 +32,7 @@
96 #include "mediascanner/logging.h"
97 #include "mediascanner/mediaroot.h"
98 #include "mediascanner/metadataresolver.h"
99+#include "mediascanner/mediaartdownloader.h"
100 #include "mediascanner/taskfacades.h"
101 #include "mediascanner/taskmanager.h"
102 #include "mediascanner/writablemediaindex.h"
103@@ -58,6 +59,7 @@
104 TaskManagerPtr index_task_manager,
105 TaskFacadePtr index_task_facade)
106 : metadata_resolver_(resolver)
107+ , art_downloader_(new MediaArtDownloader())
108 , file_task_manager_(new TaskManager("file system scanner"))
109 , index_task_manager_(index_task_manager)
110 , index_task_facade_(index_task_facade)
111@@ -116,6 +118,7 @@
112 }
113
114 MetadataResolverPtr metadata_resolver_;
115+ MediaArtDownloaderPtr art_downloader_;
116 TaskManagerPtr file_task_manager_;
117 TaskManagerPtr index_task_manager_;
118 TaskFacadePtr index_task_facade_;
119@@ -153,6 +156,7 @@
120 }
121
122 FileSystemWalkerPtr walker(new FileSystemWalker(root, metadata_resolver_,
123+ art_downloader_,
124 file_task_manager_,
125 index_task_manager_,
126 index_task_facade_));
127@@ -348,6 +352,9 @@
128 if (metadata_resolver_)
129 metadata_resolver_->WaitForFinished();
130
131+ if (art_downloader_)
132+ art_downloader_->WaitForFinished();
133+
134 BOOST_ASSERT(is_idle());
135
136 Idle::AddOnce(std::bind(&Private::NotifyScanningFinished,
137
138=== modified file 'src/mediascanner/filesystemwalker.cpp'
139--- src/mediascanner/filesystemwalker.cpp 2013-09-17 10:48:31 +0000
140+++ src/mediascanner/filesystemwalker.cpp 2013-10-09 07:38:10 +0000
141@@ -45,6 +45,7 @@
142 #include "mediascanner/glibutils.h"
143 #include "mediascanner/locale.h"
144 #include "mediascanner/logging.h"
145+#include "mediascanner/mediaartdownloader.h"
146 #include "mediascanner/mediaroot.h"
147 #include "mediascanner/mediautils.h"
148 #include "mediascanner/metadataresolver.h"
149@@ -97,6 +98,7 @@
150 Private(size_t group_id,
151 const MediaRoot &media_root,
152 MetadataResolverPtr resolver,
153+ MediaArtDownloaderPtr art_downloader,
154 TaskManagerPtr file_task_manager,
155 TaskManagerPtr index_task_manager,
156 TaskFacadePtr index_task_facade,
157@@ -189,6 +191,7 @@
158 const ErrorFunction report_fatal_error_;
159 const Wrapper<GCancellable> cancellable_;
160 const MetadataResolverPtr metadata_resolver_;
161+ const MediaArtDownloaderPtr art_downloader_;
162 const TaskManagerPtr file_task_manager_;
163 const TaskManagerPtr index_task_manager_;
164 const TaskFacadePtr index_task_facade_;
165@@ -220,6 +223,7 @@
166 FileSystemWalker::Private::Private(size_t group_id,
167 const MediaRoot &media_root,
168 MetadataResolverPtr resolver,
169+ MediaArtDownloaderPtr art_downloader,
170 TaskManagerPtr file_task_manager,
171 TaskManagerPtr index_task_manager,
172 TaskFacadePtr index_task_facade,
173@@ -227,6 +231,7 @@
174 : report_fatal_error_(report_fatal_error)
175 , cancellable_(take(g_cancellable_new()))
176 , metadata_resolver_(resolver)
177+ , art_downloader_(art_downloader)
178 , file_task_manager_(file_task_manager)
179 , index_task_manager_(index_task_manager)
180 , index_task_facade_(index_task_facade)
181@@ -248,12 +253,13 @@
182
183 FileSystemWalker::FileSystemWalker(const MediaRoot &media_root,
184 MetadataResolverPtr resolver,
185+ MediaArtDownloaderPtr art_downloader,
186 TaskManagerPtr file_task_manager,
187 TaskManagerPtr index_task_manager,
188 TaskFacadePtr index_task_facade)
189 : d(new Private(reinterpret_cast<size_t>(this),
190- media_root, resolver, file_task_manager,
191- index_task_manager, index_task_facade,
192+ media_root, resolver, art_downloader,
193+ file_task_manager, index_task_manager, index_task_facade,
194 d->bind_report_error_literal())) {
195 }
196
197@@ -926,6 +932,15 @@
198 metadata.add_single(schema::kTitle.bind_value(w_title));
199 }
200
201+ // If the media has artist and album, then resolve artwork
202+ if (not metadata.first(schema::kArtist).empty() &&
203+ not metadata.first(schema::kAlbum).empty()) {
204+ std::string artist = FromUnicode(metadata.first(schema::kArtist));
205+ std::string album = FromUnicode(metadata.first(schema::kAlbum));
206+
207+ art_downloader_->resolve(artist, album);
208+ }
209+
210 StoreMediaInfo(w_url, metadata);
211
212 if ((details & ScanMetadata) && metadata_resolver_)
213
214=== modified file 'src/mediascanner/filesystemwalker.h'
215--- src/mediascanner/filesystemwalker.h 2013-08-23 09:45:11 +0000
216+++ src/mediascanner/filesystemwalker.h 2013-10-09 07:38:10 +0000
217@@ -39,6 +39,7 @@
218
219 FileSystemWalker(const MediaRoot &media_root,
220 MetadataResolverPtr resolver,
221+ MediaArtDownloaderPtr art_downloader,
222 TaskManagerPtr file_task_manager,
223 TaskManagerPtr index_task_manager,
224 TaskFacadePtr index_task_facade);
225
226=== modified file 'src/mediascanner/glibutils.h'
227--- src/mediascanner/glibutils.h 2013-09-10 14:02:38 +0000
228+++ src/mediascanner/glibutils.h 2013-10-09 07:38:10 +0000
229@@ -26,6 +26,7 @@
230 #include <grilo.h>
231 #include <gst/pbutils/gstdiscoverer.h>
232 #include <gudev/gudev.h>
233+#include <libsoup/soup.h>
234
235 // Boost C++
236 #include <boost/date_time/posix_time/posix_time_duration.hpp>
237@@ -350,6 +351,11 @@
238 template<> struct CopyHelper<GUdevDevice> :
239 public ObjectCopyHelper<GUdevDevice> { };
240
241+template<> struct CopyHelper<SoupMessage> :
242+ public ObjectCopyHelper<SoupMessage> { };
243+template<> struct CopyHelper<SoupSession> :
244+ public ObjectCopyHelper<SoupSession> { };
245+
246 template<> struct CopyHelper<char> {
247 static char *Copy(char *p) { return g_strdup(p); }
248 static void Free(char *p) { g_free(p); }
249
250=== modified file 'src/mediascanner/mediaartcache.cpp'
251--- src/mediascanner/mediaartcache.cpp 2013-08-29 11:28:09 +0000
252+++ src/mediascanner/mediaartcache.cpp 2013-10-09 07:38:10 +0000
253@@ -85,7 +85,7 @@
254 }
255
256 void MediaArtCache::add_art(const std::string &artist, const std::string &album,
257- char *data, unsigned int datalen) {
258+ const char *data, unsigned int datalen) {
259 string abs_fname = get_full_filename(artist, album);
260 GError *err = nullptr;
261 if(!g_file_set_contents(abs_fname.c_str(), data, datalen, &err)) {
262@@ -108,6 +108,30 @@
263 return "";
264 }
265
266+string MediaArtCache::get_art_uri(const string &artist, const string &album) const
267+{
268+ std::string filename = get_art_file(artist, album);
269+
270+ if (filename == "") {
271+ return "";
272+ }
273+
274+ GError *err = nullptr;
275+ char *c_uri = g_filename_to_uri(filename.c_str(), "", &err);
276+ if (!c_uri) {
277+ string e("Could not convert file name ");
278+ e += filename;
279+ e += " to URI: ";
280+ e += err->message;
281+ g_error_free(err);
282+ throw runtime_error(e);
283+ }
284+
285+ std::string uri = c_uri;
286+ g_free(c_uri);
287+ return uri;
288+}
289+
290 std::string MediaArtCache::get_full_filename(const std::string &artist, const std::string & album) const {
291 return root_dir + "/" + compute_base_name(artist, album);
292 }
293
294=== modified file 'src/mediascanner/mediaartcache.h'
295--- src/mediascanner/mediaartcache.h 2013-08-29 11:28:09 +0000
296+++ src/mediascanner/mediaartcache.h 2013-10-09 07:38:10 +0000
297@@ -47,8 +47,9 @@
298 MediaArtCache();
299 bool has_art(const std::string &artist, const std::string &album) const;
300 void add_art(const std::string &artist, const std::string &album,
301- char *data, unsigned int datalen);
302+ const char *data, unsigned int datalen);
303 std::string get_art_file(const std::string &artist, const std::string &album) const;
304+ std::string get_art_uri(const std::string &artist, const std::string &album) const;
305 void clear() const;
306 void prune();
307 std::string get_cache_dir() const { return root_dir; }
308
309=== added file 'src/mediascanner/mediaartdownloader.cpp'
310--- src/mediascanner/mediaartdownloader.cpp 1970-01-01 00:00:00 +0000
311+++ src/mediascanner/mediaartdownloader.cpp 2013-10-09 07:38:10 +0000
312@@ -0,0 +1,297 @@
313+/*
314+ * This file is part of the Ubuntu TV Media Scanner
315+ * Copyright (C) 2012-2013 Canonical Ltd.
316+ *
317+ * This program is free software: you can redistribute it and/or modify
318+ * it under the terms of the GNU Lesser General Public License version 3 as
319+ * published by the Free Software Foundation.
320+ *
321+ * This program is distributed in the hope that it will be useful,
322+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
323+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
324+ * GNU Lesser General Public License for more details.
325+ *
326+ * You should have received a copy of the GNU Lesser General Public License
327+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
328+ *
329+ * Contact: Jim Hodapp <jim.hodapp@canonical.com>
330+ * Authored by: James Henstridge <james.henstridge@canonical.com>
331+ */
332+
333+#include "mediascanner/logging.h"
334+#include "mediascanner/glibutils.h"
335+#include "mediascanner/mediaartcache.h"
336+#include "mediascanner/mediaartdownloader.h"
337+
338+#define ALBUM_ART_PLUGIN "grl-lastfm-albumart"
339+#define ALBUM_ART_SOURCE "grl-lastfm-albumart"
340+
341+namespace mediascanner {
342+
343+static const logging::Domain kInfo("info/mediaartdownloader", logging::info());
344+static const logging::Domain kWarning("warning/mediaartdownloader", logging::warning());
345+
346+class MediaArtDownloader::Private {
347+public:
348+ struct ResolveData {
349+ ResolveData(Private *d,
350+ const std::string &artist,
351+ const std::string &album)
352+ : d(d)
353+ , artist(artist)
354+ , album(album) {
355+ }
356+
357+ Private *const d;
358+ const std::string artist;
359+ const std::string album;
360+
361+ guint operation_id; // Grilo operation
362+ Wrapper<SoupMessage> message;
363+ };
364+ typedef std::shared_ptr<ResolveData> ResolveDataPtr;
365+
366+ Private() {
367+ g_mutex_init(&mutex);
368+ g_cond_init(&cond);
369+ setup_resolver();
370+ setup_soup_session();
371+ }
372+
373+ ~Private() {
374+ cancel();
375+ }
376+
377+ bool is_idle() const {
378+ return jobs.empty();
379+ }
380+
381+ GMutex mutex;
382+ GCond cond;
383+ MediaArtCache cache;
384+ Wrapper<GrlSource> art_resolver;
385+ Wrapper<SoupSession> session;
386+
387+ std::vector<ResolveDataPtr> jobs;
388+
389+ void cancel();
390+ void setup_resolver();
391+ void setup_soup_session();
392+
393+ void add_job(ResolveDataPtr data);
394+ void complete(ResolveData *data);
395+
396+ static void OnResolveThumbnail(GrlSource *source, unsigned operation_id,
397+ GrlMedia *media, void *user_data,
398+ const GError *error);
399+ static void OnResponseReceived(SoupSession *session, SoupMessage *message,
400+ void *user_data);
401+};
402+
403+MediaArtDownloader::MediaArtDownloader()
404+ : d(new Private) {
405+}
406+
407+MediaArtDownloader::~MediaArtDownloader() {
408+ delete d;
409+}
410+
411+void MediaArtDownloader::LoadGriloPlugin() {
412+ const Wrapper<GrlRegistry> registry = wrap(grl_registry_get_default());
413+ Wrapper<GError> error;
414+
415+ if (not grl_registry_load_plugin_by_id(
416+ registry.get(), ALBUM_ART_PLUGIN, error.out_param())) {
417+ const std::string error_message = to_string(error);
418+ kWarning("Could not load album art plugin: {1}") % error_message;
419+ return;
420+ }
421+}
422+
423+void MediaArtDownloader::Private::setup_resolver() {
424+ const Wrapper<GrlRegistry> registry = wrap(grl_registry_get_default());
425+
426+ art_resolver = wrap(grl_registry_lookup_source(
427+ registry.get(), ALBUM_ART_SOURCE));
428+ if (not art_resolver) {
429+ kWarning("Could not load album art source");
430+ return;
431+ }
432+}
433+
434+void MediaArtDownloader::Private::setup_soup_session() {
435+ session = wrap(soup_session_async_new_with_options(
436+ SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
437+ SOUP_SESSION_SSL_STRICT, TRUE,
438+ SOUP_SESSION_USER_AGENT, "Ubuntu-Media-Scanner/1.0",
439+ NULL));
440+ soup_session_add_feature_by_type(
441+ session.get(), SOUP_TYPE_PROXY_RESOLVER_DEFAULT);
442+}
443+
444+void MediaArtDownloader::Private::add_job(ResolveDataPtr data)
445+{
446+ jobs.push_back(data);
447+}
448+
449+void MediaArtDownloader::Private::complete(ResolveData *data)
450+{
451+ for (auto it = jobs.begin(); it != jobs.end(); it++) {
452+ if (it->get() == data) {
453+ jobs.erase(it);
454+ break;
455+ }
456+ }
457+ if (is_idle())
458+ g_cond_signal(&cond);
459+}
460+
461+void MediaArtDownloader::resolve(const std::string &artist,
462+ const std::string &album) {
463+ // If we couldn't configure either of these objects, then we won't
464+ // have any luck resolving the album art.
465+ if (not d->art_resolver || not d->session)
466+ return;
467+
468+ kInfo("Resolving art for {1} - {2}") % artist % album;
469+
470+ if (d->cache.has_art(artist, album)) {
471+ return;
472+ }
473+
474+ g_mutex_lock(&d->mutex);
475+
476+ /* First check whether there is any queued requests for this art */
477+ for (auto it = d->jobs.begin(); it != d->jobs.end(); it++) {
478+ if ((*it)->artist == artist && (*it)->album == album) {
479+ kInfo("Art for {1} - {2} already being resolved") % artist % album;
480+ g_mutex_unlock(&d->mutex);
481+ return;
482+ }
483+ }
484+
485+
486+ const Private::ResolveDataPtr data(
487+ new Private::ResolveData(d, artist, album));
488+ d->add_job(data);
489+
490+ Wrapper<GrlMedia> media = take(grl_media_audio_new());
491+ grl_media_audio_set_artist(GRL_MEDIA_AUDIO(media.get()), artist.c_str());
492+ grl_media_audio_set_album(GRL_MEDIA_AUDIO(media.get()), album.c_str());
493+ Wrapper<GList> resolve_keys = take(grl_metadata_key_list_new(
494+ GRL_METADATA_KEY_THUMBNAIL,
495+ GRL_METADATA_KEY_INVALID));
496+ Wrapper<GrlOperationOptions> options =
497+ take(grl_operation_options_new(nullptr));
498+
499+ data->operation_id = grl_source_resolve(
500+ d->art_resolver.get(), media.get(), resolve_keys,
501+ options.get(), &Private::OnResolveThumbnail, data.get());
502+ g_mutex_unlock(&d->mutex);
503+}
504+
505+void MediaArtDownloader::Private::OnResolveThumbnail(
506+ GrlSource *source, unsigned operation_id, GrlMedia *media,
507+ void *user_data, const GError *error) {
508+ ResolveData *data = static_cast<ResolveData*>(user_data);
509+ Private *const d = data->d;
510+
511+ g_mutex_lock(&d->mutex);
512+ data->operation_id = 0;
513+ if (error != NULL) {
514+ kWarning("Error determining album art for {1} - {2}: {3}")
515+ % data->artist % data->album % error->message;
516+ d->complete(data);
517+ g_mutex_unlock(&d->mutex);
518+ return;
519+ }
520+
521+ const char *thumbnail = grl_media_get_thumbnail(media);
522+ if (thumbnail == NULL) {
523+ kInfo("No thumbnail found for {1} - {2}") % data->artist % data->album;
524+ d->complete(data);
525+ g_mutex_unlock(&d->mutex);
526+ return;
527+ }
528+
529+ kInfo("Thumbnail for {1} - {2}: {3}") % data->artist % data->album % thumbnail;
530+
531+ data->message = take(soup_message_new("GET", thumbnail));
532+ // queue_message() takes ownership of the message passed to it.
533+ soup_session_queue_message(d->session.get(), data->message.dup(),
534+ &OnResponseReceived, data);
535+ g_mutex_unlock(&d->mutex);
536+}
537+
538+void MediaArtDownloader::Private::OnResponseReceived(
539+ SoupSession *session, SoupMessage *message, void *user_data) {
540+ // If we have been cancelled, then our data pointer has been
541+ // cleaned up.
542+ if (message->status_code == SOUP_STATUS_CANCELLED)
543+ return;
544+
545+ ResolveData *data = static_cast<ResolveData*>(user_data);
546+ Private *const d = data->d;
547+
548+ g_mutex_lock(&d->mutex);
549+ data->message = NULL;
550+
551+ if (message->status_code != SOUP_STATUS_OK) {
552+ kWarning("Failed to retrieve album art for {1} - {2}")
553+ % data->artist % data->album;
554+ d->complete(data);
555+ g_mutex_unlock(&d->mutex);
556+ return;
557+ }
558+
559+ std::string content_type = soup_message_headers_get_content_type(
560+ message->response_headers, NULL);
561+ if (content_type != "image/jpeg") {
562+ kWarning("Expected album art for {1} - {2} to be image/jpeg, but got {3}")
563+ % data->artist % data->album % content_type;
564+ d->complete(data);
565+ g_mutex_unlock(&d->mutex);
566+ return;
567+ }
568+
569+ kInfo("Storing album art for {1} - {2}") % data->artist % data->album;
570+ d->cache.add_art(
571+ data->artist, data->album,
572+ message->response_body->data, message->response_body->length);
573+ d->complete(data);
574+ g_mutex_unlock(&d->mutex);
575+}
576+
577+bool MediaArtDownloader::is_idle() const {
578+ return d->is_idle();
579+}
580+
581+void MediaArtDownloader::WaitForFinished() {
582+ g_mutex_lock(&d->mutex);
583+ kInfo("Waiting for pending album art downloads.");
584+ while (not is_idle()) {
585+ g_cond_wait(&d->cond, &d->mutex);
586+ }
587+ kInfo("Album art downloads complete.");
588+ g_mutex_unlock(&d->mutex);
589+}
590+
591+void MediaArtDownloader::Private::cancel() {
592+ g_mutex_lock(&mutex);
593+
594+ for (const auto &data: jobs) {
595+ if (data->operation_id != 0) {
596+ grl_operation_cancel(data->operation_id);
597+ }
598+ data->operation_id = 0;
599+ if (data->message) {
600+ soup_session_cancel_message(
601+ session.get(), data->message.get(), SOUP_STATUS_CANCELLED);
602+ }
603+ data->message = NULL;
604+ }
605+
606+ g_mutex_unlock(&mutex);
607+}
608+
609+}
610
611=== added file 'src/mediascanner/mediaartdownloader.h'
612--- src/mediascanner/mediaartdownloader.h 1970-01-01 00:00:00 +0000
613+++ src/mediascanner/mediaartdownloader.h 2013-10-09 07:38:10 +0000
614@@ -0,0 +1,50 @@
615+/*
616+ * This file is part of the Ubuntu TV Media Scanner
617+ * Copyright (C) 2012-2013 Canonical Ltd.
618+ *
619+ * This program is free software: you can redistribute it and/or modify
620+ * it under the terms of the GNU Lesser General Public License version 3 as
621+ * published by the Free Software Foundation.
622+ *
623+ * This program is distributed in the hope that it will be useful,
624+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
625+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
626+ * GNU Lesser General Public License for more details.
627+ *
628+ * You should have received a copy of the GNU Lesser General Public License
629+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
630+ *
631+ * Contact: Jim Hodapp <jim.hodapp@canonical.com>
632+ * Authored by: James Henstridge <james.henstridge@canonical.com>
633+ */
634+
635+#ifndef MEDIAARTDOWNLOADER_H
636+#define MEDIAARTDOWNLOADER_H
637+
638+#include <string>
639+
640+namespace mediascanner {
641+
642+class MediaArtDownloader {
643+ class Private;
644+ friend class Private;
645+private:
646+
647+public:
648+ MediaArtDownloader();
649+ ~MediaArtDownloader();
650+
651+ static void LoadGriloPlugin();
652+
653+ void resolve(const std::string &artist, const std::string &album);
654+
655+ bool is_idle() const;
656+ void WaitForFinished();
657+
658+private:
659+ Private *const d;
660+};
661+
662+}
663+
664+#endif
665
666=== modified file 'src/mediascanner/mediaindex.cpp'
667--- src/mediascanner/mediaindex.cpp 2013-10-02 12:27:35 +0000
668+++ src/mediascanner/mediaindex.cpp 2013-10-09 07:38:10 +0000
669@@ -60,6 +60,7 @@
670 #include "mediascanner/filter.h"
671 #include "mediascanner/locale.h"
672 #include "mediascanner/logging.h"
673+#include "mediascanner/mediaartcache.h"
674 #include "mediascanner/mediaroot.h"
675 #include "mediascanner/mediautils.h"
676 #include "mediascanner/glibutils.h"
677@@ -182,6 +183,8 @@
678 typedef std::map<MediaRoot, Lucene::IndexReaderPtr> MediaReaderMap;
679 MediaReaderMap media_readers_;
680
681+ MediaArtCache art_cache_;
682+
683 private:
684 Lucene::IndexReaderPtr index_reader_;
685 Lucene::IndexSearcherPtr searcher_;
686@@ -644,6 +647,21 @@
687 }
688 }
689
690+ // If we have an artist and album name, check to see if there is
691+ // art in the cache.
692+ if (not metadata.first(schema::kArtist).empty() &&
693+ not metadata.first(schema::kAlbum).empty()) {
694+ std::string artist = FromUnicode(metadata.first(schema::kArtist));
695+ std::string album = FromUnicode(metadata.first(schema::kAlbum));
696+ std::string cover = d->art_cache_.get_art_uri(artist, album);
697+
698+ g_warning("Found cover art: %s", cover.c_str());
699+ if (not cover.empty()) {
700+ metadata.add_single(
701+ std::make_pair(schema::kCover, ToUnicode(cover)));
702+ }
703+ }
704+
705 return metadata;
706 }
707
708@@ -702,9 +720,11 @@
709
710 class MediaIndex::Collector : public Lucene::Collector {
711 public:
712- Collector(const ItemVistor &visit_item, Lucene::SearcherPtr searcher,
713- Lucene::QueryPtr query, int32_t offset, int32_t limit)
714- : visit_item_(visit_item)
715+ Collector(MediaIndex *index, const ItemVistor &visit_item,
716+ Lucene::SearcherPtr searcher, Lucene::QueryPtr query,
717+ int32_t offset, int32_t limit)
718+ : index_(index)
719+ , visit_item_(visit_item)
720 , searcher_(searcher)
721 , query_(query)
722 , offset_(offset)
723@@ -729,7 +749,7 @@
724 Lucene::DocumentPtr document = reader_->document(doc);
725
726 if (document)
727- visit_item_(ExtractProperties(document), limit_);
728+ visit_item_(index_->ExtractProperties(document), limit_);
729
730 if (limit_ > 0)
731 --limit_;
732@@ -744,6 +764,7 @@
733 }
734
735 private:
736+ MediaIndex *const index_;
737 ItemVistor visit_item_;
738 Lucene::IndexReaderPtr reader_;
739 Lucene::SearcherPtr searcher_;
740@@ -837,7 +858,8 @@
741 if (limit < 0) {
742 /* If no limit is specified, return results unsorted */
743 const Lucene::CollectorPtr media_collector =
744- newLucene<Collector>(visit_item, searcher, query, offset, limit);
745+ newLucene<Collector>(this, visit_item, searcher, query,
746+ offset, limit);
747 searcher->search(query, media_collector);
748 } else {
749 const auto collector = Lucene::TopScoreDocCollector::create(
750
751=== modified file 'src/mediascanner/mediaindex.h'
752--- src/mediascanner/mediaindex.h 2013-09-10 14:02:38 +0000
753+++ src/mediascanner/mediaindex.h 2013-10-09 07:38:10 +0000
754@@ -208,9 +208,9 @@
755
756 static Property::Set GetFields(Lucene::DocumentPtr document);
757
758- static MediaInfo ExtractProperties(Lucene::DocumentPtr document);
759- static MediaInfo ExtractProperties(Lucene::DocumentPtr document,
760- const Property::Set &fields);
761+ MediaInfo ExtractProperties(Lucene::DocumentPtr document);
762+ MediaInfo ExtractProperties(Lucene::DocumentPtr document,
763+ const Property::Set &fields);
764
765 bool Lookup(const std::wstring &url, const Property &key,
766 Property::Value *value);
767
768=== modified file 'src/mediascanner/metadataresolver.cpp'
769--- src/mediascanner/metadataresolver.cpp 2013-09-17 10:48:31 +0000
770+++ src/mediascanner/metadataresolver.cpp 2013-10-09 07:38:10 +0000
771@@ -277,6 +277,9 @@
772 const size_t removals = pending_pushes_.erase(push_id);
773 BOOST_ASSERT(removals == 1);
774
775+ if (is_idle())
776+ g_cond_signal(&cond_);
777+
778 g_mutex_unlock(&mutex_);
779 }
780
781
782=== modified file 'src/mediascanner/propertyschema.cpp'
783--- src/mediascanner/propertyschema.cpp 2013-09-09 12:17:56 +0000
784+++ src/mediascanner/propertyschema.cpp 2013-10-09 07:38:10 +0000
785@@ -220,6 +220,21 @@
786 merge_nothing) {
787 }
788
789+static bool get_network_available(const GstDiscovererInfo *media) {
790+ GNetworkMonitor *monitor = g_network_monitor_get_default();
791+ return g_network_monitor_get_network_available(monitor);
792+}
793+
794+NetworkAvailable::NetworkAvailable()
795+ : NumericProperty(L"network-available",
796+ define("lucene-network-available",
797+ N_("Network available (Lucene)"),
798+ N_("...")),
799+ Property::Category::Generic,
800+ Property::MergeReplace,
801+ bind_attr(get_network_available)) {
802+}
803+
804 // File Properties /////////////////////////////////////////////////////////////
805
806 Url::Url()
807@@ -1320,6 +1335,7 @@
808 const MaximumVideoBitRate kMaximumVideoBitRate;
809 const MeteringMode kMeteringMode;
810 const MimeType kMimeType;
811+const NetworkAvailable kNetworkAvailable;
812 const Organization kOrganization;
813 const OriginalTitle kOriginalTitle;
814 const Performer kPerformer;
815
816=== modified file 'src/mediascanner/propertyschema.h'
817--- src/mediascanner/propertyschema.h 2013-08-06 06:36:26 +0000
818+++ src/mediascanner/propertyschema.h 2013-10-09 07:38:10 +0000
819@@ -108,6 +108,9 @@
820 struct Favourite : public NumericProperty<bool> { Favourite(); }
821 extern const kFavourite;
822
823+struct NetworkAvailable : public NumericProperty<bool> { NetworkAvailable(); }
824+extern const kNetworkAvailable;
825+
826 // File Properties /////////////////////////////////////////////////////////////
827
828 // Local URL of the media, taken from File System
829
830=== modified file 'tests/auto/filesystemscannertest.cpp'
831--- tests/auto/filesystemscannertest.cpp 2013-09-11 09:49:12 +0000
832+++ tests/auto/filesystemscannertest.cpp 2013-10-09 07:38:10 +0000
833@@ -341,6 +341,7 @@
834
835 AddGlobalTestEnvironment(new LuceneEnvironment(kMediaIndexPath));
836 AddGlobalTestEnvironment(new TheMovieDbEnvironment(REQUEST_FILE));
837+ AddGlobalTestEnvironment(new LastFmEnvironment(REQUEST_FILE));
838 }
839
840 } // namespace filesystemscannertest
841
842=== modified file 'tests/auto/loggingtest.cpp'
843--- tests/auto/loggingtest.cpp 2013-09-11 09:49:12 +0000
844+++ tests/auto/loggingtest.cpp 2013-10-09 07:38:10 +0000
845@@ -205,7 +205,7 @@
846 } // namespace mediascanner
847
848 int main(int argc, char *argv[]) {
849- ::setenv("MEDIASCANNER_DEBUG", 0, true);
850+ ::setenv("MEDIASCANNER_DEBUG", "0", true);
851 mediascanner::InitTests(&argc, argv);
852 return RUN_ALL_TESTS();
853 }
854
855=== modified file 'tests/auto/propertytest.cpp'
856--- tests/auto/propertytest.cpp 2013-08-23 07:49:06 +0000
857+++ tests/auto/propertytest.cpp 2013-10-09 07:38:10 +0000
858@@ -71,7 +71,8 @@
859 &schema::kCertification,
860 &schema::kComment,
861 &schema::kKeyword,
862- &schema::kLanguage));
863+ &schema::kLanguage,
864+ &schema::kNetworkAvailable));
865
866 INSTANTIATE_TEST_CASE_P(FileProperties, PropertyTest,
867 testing::Values(&schema::kMimeType,

Subscribers

People subscribed via source and target branches