Merge lp:~jamesh/mediascanner2/folder-coverart into lp:mediascanner2

Proposed by James Henstridge
Status: Merged
Approved by: Michi Henning
Approved revision: 325
Merged at revision: 315
Proposed branch: lp:~jamesh/mediascanner2/folder-coverart
Merge into: lp:mediascanner2
Diff against target: 730 lines (+453/-36)
16 files modified
CMakeLists.txt (+1/-1)
debian/changelog (+9/-0)
debian/libmediascanner-2.0-3.symbols (+2/-0)
debian/libmediascanner-2.0-4.shlibs (+1/-1)
src/mediascanner/Album.cc (+25/-8)
src/mediascanner/Album.hh (+4/-0)
src/mediascanner/CMakeLists.txt (+1/-0)
src/mediascanner/FolderArtCache.cc (+169/-0)
src/mediascanner/MediaFile.cc (+10/-4)
src/mediascanner/MediaStore.cc (+1/-1)
src/mediascanner/internal/FolderArtCache.hh (+55/-0)
src/ms-dbus/dbus-codec.cc (+4/-2)
src/ms-dbus/dbus-codec.hh (+1/-1)
test/test_dbus.cc (+3/-2)
test/test_mfbuilder.cc (+164/-16)
test/test_util.cc (+3/-0)
To merge this branch: bzr merge lp:~jamesh/mediascanner2/folder-coverart
Reviewer Review Type Date Requested Status
Michi Henning (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+278099@code.launchpad.net

Commit message

If a folder contains an image file named {cover,album,albumart,.folder,folder}.{jpeg,jpg,png} use it as album art for songs in preference to online art if the songs do not have embedded art.

Description of the change

Add folder based cover art detection to the MediaFile::getArtUri() method, using a small cache to reduce work when requesting art for multiple files from an album.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
321. By James Henstridge

Remove unneeded #define.

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

Add new constructor for album, explicitly passing the has_thumbnail boolean.

323. By James Henstridge

Add Album::getHasThumbnail() for use by dbus code, and make MediaStore
pass in has_thumbnail to Album.

324. By James Henstridge

Add folder artwork support to Album class.

325. By James Henstridge

Bump version number to account for API additions.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michi Henning (michihenning) wrote :

Looks good! I tested on the phone and, after dropping a folder.jpg file into a dir with songs without artwork, the image was used by the music app.

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 2015-11-07 09:13:53 +0000
3+++ CMakeLists.txt 2015-11-23 03:14:55 +0000
4@@ -1,7 +1,7 @@
5 project(mediascanner2 CXX C)
6 cmake_minimum_required(VERSION 2.8.9)
7
8-set(MEDIASCANNER_VERSION "0.108")
9+set(MEDIASCANNER_VERSION "0.109")
10
11 execute_process(
12 COMMAND /bin/sh ${CMAKE_CURRENT_SOURCE_DIR}/get-soversion.sh
13
14=== modified file 'debian/changelog'
15--- debian/changelog 2015-11-09 01:56:39 +0000
16+++ debian/changelog 2015-11-23 03:14:55 +0000
17@@ -1,3 +1,12 @@
18+mediascanner2 (0.109-0ubuntu1) UNRELEASED; urgency=medium
19+
20+ * If a folder contains an image file named
21+ {cover,album,albumart,.folder,folder}.{jpeg,jpg,png} use it as album
22+ art for songs in preference to online art if the songs do not have
23+ embedded art. (LP: #1372000)
24+
25+ -- James Henstridge <james.henstridge@canonical.com> Mon, 23 Nov 2015 11:12:59 +0800
26+
27 mediascanner2 (0.108+16.04.20151109-0ubuntu1) xenial; urgency=medium
28
29 * Move the metadata extractor to a separate process to isolate bugs in
30
31=== modified file 'debian/libmediascanner-2.0-3.symbols'
32--- debian/libmediascanner-2.0-3.symbols 2015-09-21 10:56:50 +0000
33+++ debian/libmediascanner-2.0-3.symbols 2015-11-23 03:14:55 +0000
34@@ -5,11 +5,13 @@
35 (c++)"mediascanner::Album::Album(mediascanner::Album const&)@Base" 0.105+14.10.20140903
36 (c++)"mediascanner::Album::Album(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@Base" 0.105+14.10.20140903
37 (c++)"mediascanner::Album::Album(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@Base" 0.105+14.10.20140903
38+ (c++)"mediascanner::Album::Album(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool)@Base" 0replaceme
39 (c++)"mediascanner::Album::getArtFile() const@Base" 0.105+14.10.20140903
40 (c++)"mediascanner::Album::getArtist() const@Base" 0.105+14.10.20140903
41 (c++)"mediascanner::Album::getArtUri() const@Base" 0.105+14.10.20140903
42 (c++)"mediascanner::Album::getDate() const@Base" 0.105+14.10.20140903
43 (c++)"mediascanner::Album::getGenre() const@Base" 0.105+14.10.20140903
44+ (c++)"mediascanner::Album::getHasThumbnail() const@Base" 0replaceme
45 (c++)"mediascanner::Album::getTitle() const@Base" 0.101+14.10.20140613
46 (c++)"mediascanner::Album::operator=(mediascanner::Album&&)@Base" 0.105+14.10.20140903
47 (c++)"mediascanner::Album::operator=(mediascanner::Album const&)@Base" 0.105+14.10.20140903
48
49=== modified file 'debian/libmediascanner-2.0-4.shlibs'
50--- debian/libmediascanner-2.0-4.shlibs 2015-09-21 10:20:38 +0000
51+++ debian/libmediascanner-2.0-4.shlibs 2015-11-23 03:14:55 +0000
52@@ -1,1 +1,1 @@
53-libmediascanner-2.0 4 libmediascanner-2.0-4 (>= 0.107)
54+libmediascanner-2.0 4 libmediascanner-2.0-4 (>= 0.109)
55
56=== modified file 'src/mediascanner/Album.cc'
57--- src/mediascanner/Album.cc 2014-09-03 07:00:02 +0000
58+++ src/mediascanner/Album.cc 2015-11-23 03:14:55 +0000
59@@ -18,6 +18,7 @@
60 */
61
62 #include "Album.hh"
63+#include "internal/FolderArtCache.hh"
64 #include "internal/utils.hh"
65
66 using namespace std;
67@@ -30,13 +31,14 @@
68 string date;
69 string genre;
70 string filename;
71+ bool has_thumbnail;
72
73 Private() {}
74 Private(const string &title, const string &artist,
75 const string &date, const string &genre,
76- const string &filename)
77+ const string &filename, bool has_thumbnail)
78 : title(title), artist(artist), date(date), genre(genre),
79- filename(filename) {}
80+ filename(filename), has_thumbnail(has_thumbnail) {}
81 Private(const Private &other) {
82 *this = other;
83 }
84@@ -46,13 +48,19 @@
85 }
86
87 Album::Album(const std::string &title, const std::string &artist)
88- : Album(title, artist, "", "", "") {
89+ : Album(title, artist, "", "", "", false) {
90 }
91
92 Album::Album(const std::string &title, const std::string &artist,
93 const std::string &date, const std::string &genre,
94 const std::string &filename)
95- : p(new Private(title, artist, date, genre, filename)) {
96+ : Album(title, artist, date, genre, filename, !filename.empty()) {
97+}
98+
99+Album::Album(const std::string &title, const std::string &artist,
100+ const std::string &date, const std::string &genre,
101+ const std::string &filename, bool has_thumbnail)
102+ : p(new Private(title, artist, date, genre, filename, has_thumbnail)) {
103 }
104
105 Album::Album(const Album &other) : p(new Private(*other.p)) {
106@@ -100,11 +108,19 @@
107 return p->filename;
108 }
109
110+bool Album::getHasThumbnail() const noexcept {
111+ return p->has_thumbnail;
112+}
113+
114 std::string Album::getArtUri() const {
115- if (p->filename.empty()) {
116+ if (p->has_thumbnail) {
117+ return make_thumbnail_uri(getUri(p->filename));
118+ } else {
119+ auto standalone = FolderArtCache::get().get_art_for_file(p->filename);
120+ if (!standalone.empty()) {
121+ return make_thumbnail_uri(getUri(standalone));
122+ }
123 return make_album_art_uri(p->artist, p->title);
124- } else {
125- return make_thumbnail_uri(getUri(p->filename));
126 }
127 }
128
129@@ -113,7 +129,8 @@
130 p->artist == other.p->artist &&
131 p->date == other.p->date &&
132 p->genre == other.p->genre &&
133- p->filename == other.p->filename;
134+ p->filename == other.p->filename &&
135+ p->has_thumbnail == other.p->has_thumbnail;
136 }
137
138 bool Album::operator!=(const Album &other) const {
139
140=== modified file 'src/mediascanner/Album.hh'
141--- src/mediascanner/Album.hh 2014-09-03 07:00:02 +0000
142+++ src/mediascanner/Album.hh 2015-11-23 03:14:55 +0000
143@@ -32,6 +32,9 @@
144 Album(const std::string &title, const std::string &artist,
145 const std::string &date, const std::string &genre,
146 const std::string &filename);
147+ Album(const std::string &title, const std::string &artist,
148+ const std::string &date, const std::string &genre,
149+ const std::string &filename, bool has_thumbnail);
150 Album(const Album &other);
151 Album(Album &&other);
152 ~Album();
153@@ -44,6 +47,7 @@
154 const std::string& getDate() const noexcept;
155 const std::string& getGenre() const noexcept;
156 const std::string& getArtFile() const noexcept;
157+ bool getHasThumbnail() const noexcept;
158 std::string getArtUri() const;
159 bool operator==(const Album &other) const;
160 bool operator!=(const Album &other) const;
161
162=== modified file 'src/mediascanner/CMakeLists.txt'
163--- src/mediascanner/CMakeLists.txt 2015-07-14 08:59:01 +0000
164+++ src/mediascanner/CMakeLists.txt 2015-11-23 03:14:55 +0000
165@@ -6,6 +6,7 @@
166 Album.cc
167 MediaStore.cc
168 MediaStoreBase.cc
169+ FolderArtCache.cc
170 utils.cc
171 mozilla/fts3_porter.c
172 mozilla/Normalize.c
173
174=== added file 'src/mediascanner/FolderArtCache.cc'
175--- src/mediascanner/FolderArtCache.cc 1970-01-01 00:00:00 +0000
176+++ src/mediascanner/FolderArtCache.cc 2015-11-23 03:14:55 +0000
177@@ -0,0 +1,169 @@
178+/*
179+ * Copyright (C) 2015 Canonical, Ltd.
180+ *
181+ * Authors:
182+ * James Henstridge <james.henstridge@canonical.com>
183+ *
184+ * This program is free software: you can redistribute it and/or modify
185+ * it under the terms of the GNU Lesser General Public License version 3 as
186+ * published by the Free Software Foundation.
187+ *
188+ * This program is distributed in the hope that it will be useful,
189+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
190+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
191+ * GNU Lesser General Public License for more details.
192+ *
193+ * You should have received a copy of the GNU Lesser General Public License
194+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
195+ */
196+
197+#include "internal/FolderArtCache.hh"
198+
199+#include <algorithm>
200+#include <array>
201+#include <cctype>
202+#include <cerrno>
203+#include <cstdlib>
204+#include <cstring>
205+#include <dirent.h>
206+#include <memory>
207+#include <stdexcept>
208+#include <sys/types.h>
209+#include <sys/stat.h>
210+#include <unistd.h>
211+
212+using namespace std;
213+
214+namespace {
215+
216+const int CACHE_SIZE = 50;
217+
218+string detect_albumart(string directory) {
219+ static const array<const char*, 5> art_basenames = {
220+ "cover",
221+ "album",
222+ "albumart",
223+ ".folder",
224+ "folder",
225+ };
226+ static const array<const char*, 3> art_extensions = {
227+ "jpeg",
228+ "jpg",
229+ "png",
230+ };
231+ if (!directory.empty() && directory[directory.size()-1] != '/') {
232+ directory += "/";
233+ }
234+ unique_ptr<DIR, decltype(&closedir)> dir(
235+ opendir(directory.c_str()), &closedir);
236+ if (!dir) {
237+ return "";
238+ }
239+
240+ const int dirent_size = sizeof(dirent) + fpathconf(dirfd(dir.get()), _PC_NAME_MAX) + 1;
241+ unique_ptr<struct dirent, decltype(&free)> entry(
242+ reinterpret_cast<struct dirent*>(malloc(dirent_size)), &free);
243+
244+ string detected;
245+ int best_score = 0;
246+ struct dirent *de = nullptr;
247+ while (readdir_r(dir.get(), entry.get(), &de) == 0 && de) {
248+ const string filename(de->d_name);
249+ auto dot = filename.rfind('.');
250+ // Ignore files with no extension
251+ if (dot == string::npos) {
252+ continue;
253+ }
254+ auto basename = filename.substr(0, dot);
255+ auto extension = filename.substr(dot+1);
256+
257+ // Does the file name match one of the required names when
258+ // converted to lower case?
259+ transform(basename.begin(), basename.end(),
260+ basename.begin(), ::tolower);
261+ transform(extension.begin(), extension.end(),
262+ extension.begin(), ::tolower);
263+ auto base_pos = find(art_basenames.begin(), art_basenames.end(), basename);
264+ if (base_pos == art_basenames.end()) {
265+ continue;
266+ }
267+ auto ext_pos = find(art_extensions.begin(), art_extensions.end(), extension);
268+ if (ext_pos == art_extensions.end()) {
269+ continue;
270+ }
271+
272+ int score = (base_pos - art_basenames.begin()) * art_basenames.size() +
273+ (ext_pos - art_extensions.begin());
274+ if (detected.empty() || score < best_score) {
275+ detected = filename;
276+ best_score = score;
277+ }
278+ }
279+ if (detected.empty()) {
280+ return detected;
281+ }
282+ return directory + detected;
283+}
284+
285+}
286+
287+namespace mediascanner {
288+
289+FolderArtCache::FolderArtCache() = default;
290+FolderArtCache::~FolderArtCache() = default;
291+
292+// Get a singleton instance of the cache
293+FolderArtCache& FolderArtCache::get() {
294+ static FolderArtCache cache;
295+ return cache;
296+}
297+
298+string FolderArtCache::get_art_for_directory(const string &directory) {
299+ struct stat s;
300+ if (lstat(directory.c_str(), &s) < 0) {
301+ return "";
302+ }
303+ if (!S_ISDIR(s.st_mode)) {
304+ return "";
305+ }
306+ FolderArtInfo info;
307+ bool update = false;
308+ try {
309+ info = cache_.at(directory);
310+ } catch (const out_of_range &) {
311+ // Fall back to checking the previous iteration of the cache
312+ try {
313+ info = old_cache_.at(directory);
314+ update = true;
315+ } catch (const out_of_range &) {
316+ }
317+ }
318+
319+ if (info.dir_mtime.tv_sec != s.st_mtim.tv_sec ||
320+ info.dir_mtime.tv_nsec != s.st_mtim.tv_nsec) {
321+ info.art = detect_albumart(directory);
322+ info.dir_mtime = s.st_mtim;
323+ update = true;
324+ }
325+
326+ if (update) {
327+ cache_[directory] = info;
328+ // Start new cache generation if we've exceeded the size.
329+ if (cache_.size() > CACHE_SIZE) {
330+ old_cache_ = move(cache_);
331+ cache_.clear();
332+ }
333+ }
334+ return info.art;
335+}
336+
337+string FolderArtCache::get_art_for_file(const string &filename) {
338+ auto slash = filename.rfind('/');
339+ if (slash == string::npos) {
340+ return "";
341+ }
342+ auto directory = filename.substr(0, slash + 1);
343+ return get_art_for_directory(directory);
344+}
345+
346+}
347
348=== modified file 'src/mediascanner/MediaFile.cc'
349--- src/mediascanner/MediaFile.cc 2015-07-07 04:36:02 +0000
350+++ src/mediascanner/MediaFile.cc 2015-11-23 03:14:55 +0000
351@@ -20,6 +20,7 @@
352 #include "MediaFile.hh"
353 #include "MediaFileBuilder.hh"
354 #include "internal/MediaFilePrivate.hh"
355+#include "internal/FolderArtCache.hh"
356 #include "internal/utils.hh"
357 #include <stdexcept>
358
359@@ -155,12 +156,17 @@
360
361 std::string MediaFile::getArtUri() const {
362 switch (p->type) {
363- case AudioMedia:
364+ case AudioMedia: {
365 if (p->has_thumbnail) {
366 return make_thumbnail_uri(getUri());
367- } else {
368- return make_album_art_uri(getAuthor(), getAlbum());
369- }
370+ }
371+ auto standalone = FolderArtCache::get().get_art_for_file(p->filename);
372+ if(!standalone.empty()) {
373+ return make_thumbnail_uri(mediascanner::getUri(standalone));
374+ }
375+ return make_album_art_uri(getAuthor(), getAlbum());
376+ }
377+
378 default:
379 return make_thumbnail_uri(getUri());
380 }
381
382=== modified file 'src/mediascanner/MediaStore.cc'
383--- src/mediascanner/MediaStore.cc 2015-10-07 06:49:12 +0000
384+++ src/mediascanner/MediaStore.cc 2015-11-23 03:14:55 +0000
385@@ -604,7 +604,7 @@
386 const string genre = query.getText(3);
387 const string filename = query.getText(4);
388 const bool has_thumbnail = query.getInt(5);
389- return Album(album, album_artist, date, genre, has_thumbnail ? filename : "");
390+ return Album(album, album_artist, date, genre, filename, has_thumbnail);
391 }
392
393 static vector<Album> collect_albums(Statement &query) {
394
395=== added file 'src/mediascanner/internal/FolderArtCache.hh'
396--- src/mediascanner/internal/FolderArtCache.hh 1970-01-01 00:00:00 +0000
397+++ src/mediascanner/internal/FolderArtCache.hh 2015-11-23 03:14:55 +0000
398@@ -0,0 +1,55 @@
399+/*
400+ * Copyright (C) 2015 Canonical, Ltd.
401+ *
402+ * Authors:
403+ * James Henstridge <james.henstridge@canonical.com>
404+ *
405+ * This program is free software: you can redistribute it and/or modify
406+ * it under the terms of the GNU Lesser General Public License version 3 as
407+ * published by the Free Software Foundation.
408+ *
409+ * This program is distributed in the hope that it will be useful,
410+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
411+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
412+ * GNU Lesser General Public License for more details.
413+ *
414+ * You should have received a copy of the GNU Lesser General Public License
415+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
416+ */
417+
418+#ifndef FOLDERARTCACHE_HH
419+#define FOLDERARTCACHE_HH
420+
421+#include <cstdint>
422+#include <ctime>
423+#include <map>
424+#include <string>
425+
426+namespace mediascanner {
427+
428+struct FolderArtInfo {
429+ std::string art;
430+ struct timespec dir_mtime {0, 0};
431+};
432+
433+class FolderArtCache final {
434+public:
435+ FolderArtCache();
436+ ~FolderArtCache();
437+
438+ FolderArtCache(const FolderArtCache &other) = delete;
439+ FolderArtCache& operator=(const FolderArtCache &other) = delete;
440+
441+ // Get a singleton instance of the cache
442+ static FolderArtCache& get();
443+
444+ std::string get_art_for_directory(const std::string &directory);
445+ std::string get_art_for_file(const std::string &filename);
446+private:
447+ std::map<std::string, FolderArtInfo> cache_;
448+ std::map<std::string, FolderArtInfo> old_cache_;
449+};
450+
451+}
452+
453+#endif
454
455=== modified file 'src/ms-dbus/dbus-codec.cc'
456--- src/ms-dbus/dbus-codec.cc 2015-07-07 05:00:16 +0000
457+++ src/ms-dbus/dbus-codec.cc 2015-11-23 03:14:55 +0000
458@@ -104,14 +104,16 @@
459 core::dbus::encode_argument(w, album.getDate());
460 core::dbus::encode_argument(w, album.getGenre());
461 core::dbus::encode_argument(w, album.getArtFile());
462+ core::dbus::encode_argument(w, album.getHasThumbnail());
463 out.close_structure(std::move(w));
464 }
465
466 void Codec<Album>::decode_argument(Message::Reader &in, Album &album) {
467 auto r = in.pop_structure();
468 string title, artist, date, genre, art_file;
469- r >> title >> artist >> date >> genre >> art_file;
470- album = Album(title, artist, date, genre, art_file);
471+ bool has_thumbnail;
472+ r >> title >> artist >> date >> genre >> art_file >> has_thumbnail;
473+ album = Album(title, artist, date, genre, art_file, has_thumbnail);
474 }
475
476 void Codec<Filter>::encode_argument(Message::Writer &out, const Filter &filter) {
477
478=== modified file 'src/ms-dbus/dbus-codec.hh'
479--- src/ms-dbus/dbus-codec.hh 2015-07-07 05:00:16 +0000
480+++ src/ms-dbus/dbus-codec.hh 2015-11-23 03:14:55 +0000
481@@ -82,7 +82,7 @@
482 return true;
483 }
484 static const std::string &signature() {
485- static const std::string s = "(sssss)";
486+ static const std::string s = "(sssssb)";
487 return s;
488 }
489 };
490
491=== modified file 'test/test_dbus.cc'
492--- test/test_dbus.cc 2015-07-07 05:00:16 +0000
493+++ test/test_dbus.cc 2015-11-23 03:14:55 +0000
494@@ -53,10 +53,10 @@
495 }
496
497 TEST_F(MediaStoreDBusTests, album_codec) {
498- mediascanner::Album album("title", "artist", "date", "genre", "art_file");
499+ mediascanner::Album album("title", "artist", "date", "genre", "art_file", true);
500 message->writer() << album;
501
502- EXPECT_EQ("(sssss)", message->signature());
503+ EXPECT_EQ("(sssssb)", message->signature());
504 EXPECT_EQ(core::dbus::helper::TypeMapper<mediascanner::Album>::signature(), message->signature());
505
506 mediascanner::Album album2;
507@@ -66,6 +66,7 @@
508 EXPECT_EQ("date", album2.getDate());
509 EXPECT_EQ("genre", album2.getGenre());
510 EXPECT_EQ("art_file", album2.getArtFile());
511+ EXPECT_EQ(true, album2.getHasThumbnail());
512 EXPECT_EQ(album, album2);
513 }
514
515
516=== modified file 'test/test_mfbuilder.cc'
517--- test/test_mfbuilder.cc 2015-07-07 08:21:29 +0000
518+++ test/test_mfbuilder.cc 2015-11-23 03:14:55 +0000
519@@ -17,26 +17,60 @@
520 * along with this program. If not, see <http://www.gnu.org/licenses/>.
521 */
522
523-#include<gtest/gtest.h>
524-#include"mediascanner/MediaFile.hh"
525-#include"mediascanner/MediaFileBuilder.hh"
526-#include<stdexcept>
527+#include "test_config.h"
528+#include "mediascanner/Album.hh"
529+#include "mediascanner/MediaFile.hh"
530+#include "mediascanner/MediaFileBuilder.hh"
531+
532+#include <gtest/gtest.h>
533+
534+#include <chrono>
535+#include <cstdlib>
536+#include <fcntl.h>
537+#include <sys/stat.h>
538+#include <sys/types.h>
539+#include <stdexcept>
540+#include <thread>
541+#include <vector>
542
543 using namespace mediascanner;
544
545 class MFBTest : public ::testing::Test {
546- protected:
547- MFBTest() {
548- }
549-
550- virtual ~MFBTest() {
551- }
552-
553- virtual void SetUp() {
554- }
555-
556- virtual void TearDown() {
557- }
558+protected:
559+ MFBTest() = default;
560+ virtual ~MFBTest() = default;
561+
562+ virtual void SetUp() override {
563+ tmpdir = TEST_DIR "/mfbuilder-test.XXXXXX";
564+ ASSERT_NE(nullptr, mkdtemp(&tmpdir[0]));
565+ }
566+
567+ virtual void TearDown() override {
568+ if (!tmpdir.empty()) {
569+ std::string cmd = "rm -rf " + tmpdir;
570+ ASSERT_EQ(0, system(cmd.c_str()));
571+ }
572+ }
573+
574+ void touch(const std::string &fname, bool sleep=false) {
575+ if (sleep) {
576+ // Ensure time stamps change
577+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
578+ }
579+ int fd = open(fname.c_str(), O_CREAT, 0600);
580+ ASSERT_GT(fd, 0);
581+ ASSERT_EQ(0, close(fd));
582+ }
583+
584+ void remove(const std::string &fname, bool sleep=false) {
585+ if (sleep) {
586+ // Ensure time stamps change
587+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
588+ }
589+ ASSERT_EQ(0, unlink(fname.c_str()));
590+ }
591+
592+ std::string tmpdir;
593 };
594
595 TEST_F(MFBTest, basic) {
596@@ -209,6 +243,120 @@
597 EXPECT_EQ("image://thumbnailer/file:///foo/bar/baz.mp4", mf.getArtUri());
598 }
599
600+TEST_F(MFBTest, folder_art) {
601+ std::string fname = tmpdir + "/dummy.mp3";
602+ MediaFile media = MediaFileBuilder(fname)
603+ .setType(AudioMedia)
604+ .setTitle("Title")
605+ .setAuthor("Artist")
606+ .setAlbum("Album");
607+
608+ EXPECT_EQ("image://albumart/artist=Artist&album=Album", media.getArtUri());
609+ touch(tmpdir + "/folder.jpg", true);
610+ EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri();
611+ remove(tmpdir + "/folder.jpg", true);
612+ EXPECT_EQ("image://albumart/artist=Artist&album=Album", media.getArtUri());
613+}
614+
615+TEST_F(MFBTest, folder_art_case_insensitive) {
616+ std::string fname = tmpdir + "/dummy.mp3";
617+ MediaFile media = MediaFileBuilder(fname)
618+ .setType(AudioMedia)
619+ .setTitle("Title")
620+ .setAuthor("Artist")
621+ .setAlbum("Album");
622+
623+ touch(tmpdir + "/FOLDER.JPG");
624+ EXPECT_NE(std::string::npos, media.getArtUri().find("/FOLDER.JPG")) << media.getArtUri();
625+}
626+
627+TEST_F(MFBTest, folder_art_precedence) {
628+ std::string fname = tmpdir + "/dummy.mp3";
629+ MediaFile media = MediaFileBuilder(fname)
630+ .setType(AudioMedia)
631+ .setTitle("Title")
632+ .setAuthor("Artist")
633+ .setAlbum("Album");
634+
635+ touch(tmpdir + "/cover.jpg");
636+ touch(tmpdir + "/album.jpg");
637+ touch(tmpdir + "/albumart.jpg");
638+ touch(tmpdir + "/.folder.jpg");
639+ touch(tmpdir + "/folder.jpeg");
640+ touch(tmpdir + "/folder.jpg");
641+ touch(tmpdir + "/folder.png");
642+
643+ EXPECT_NE(std::string::npos, media.getArtUri().find("/cover.jpg")) << media.getArtUri();
644+ remove(tmpdir + "/cover.jpg", true);
645+
646+ EXPECT_NE(std::string::npos, media.getArtUri().find("/album.jpg")) << media.getArtUri();
647+ remove(tmpdir + "/album.jpg", true);
648+
649+ EXPECT_NE(std::string::npos, media.getArtUri().find("/albumart.jpg")) << media.getArtUri();
650+ remove(tmpdir + "/albumart.jpg", true);
651+
652+ EXPECT_NE(std::string::npos, media.getArtUri().find("/.folder.jpg")) << media.getArtUri();
653+ remove(tmpdir + "/.folder.jpg", true);
654+
655+ EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpeg")) << media.getArtUri();
656+ remove(tmpdir + "/folder.jpeg", true);
657+
658+ EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri();
659+ remove(tmpdir + "/folder.jpg", true);
660+
661+ EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.png")) << media.getArtUri();
662+}
663+
664+TEST_F(MFBTest, folder_art_cache_coverage) {
665+ std::vector<MediaFile> files;
666+ for (int i = 0; i < 100; i++) {
667+ std::string directory = tmpdir + "/" + std::to_string(i);
668+ ASSERT_EQ(0, mkdir(directory.c_str(), 0700));
669+ touch(directory + "/folder.jpg");
670+
671+ std::string fname = directory + "/dummy.mp3";
672+ files.emplace_back(MediaFileBuilder(fname)
673+ .setType(AudioMedia)
674+ .setTitle("Title")
675+ .setAuthor("Artist")
676+ .setAlbum("Album"));
677+ }
678+
679+ // Check art for a number of files smaller than the cache size twice
680+ for (int i = 0; i < 10; i++) {
681+ const auto &media = files[i];
682+ EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri();
683+ }
684+ for (int i = 0; i < 10; i++) {
685+ const auto &media = files[i];
686+ EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri();
687+ }
688+
689+ // Now check a larger number of files twice
690+ for (const auto &media : files) {
691+ EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri();
692+ }
693+ for (const auto &media : files) {
694+ EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri();
695+ }
696+}
697+
698+TEST_F(MFBTest, album_art) {
699+ std::string fname = tmpdir + "/dummy.mp3";
700+
701+ // File with embedded art
702+ Album album("Album", "Artist", "2015-11-23", "Rock", fname, true);
703+ EXPECT_NE(std::string::npos, album.getArtUri().find("/dummy.mp3")) << album.getArtUri();
704+
705+ // No embedded art
706+ album = Album("Album", "Artist", "2015-11-23", "Rock", fname, false);
707+ EXPECT_EQ("image://albumart/artist=Artist&album=Album", album.getArtUri());
708+
709+ // No embedded art, but folder art available
710+ touch(tmpdir + "/folder.jpg", true);
711+ EXPECT_NE(std::string::npos, album.getArtUri().find("/folder.jpg")) << album.getArtUri();
712+}
713+
714 int main(int argc, char **argv) {
715 ::testing::InitGoogleTest(&argc, argv);
716 return RUN_ALL_TESTS();
717
718=== modified file 'test/test_util.cc'
719--- test/test_util.cc 2014-05-09 14:04:48 +0000
720+++ test/test_util.cc 2015-11-23 03:14:55 +0000
721@@ -19,6 +19,9 @@
722
723 #include <gtest/gtest.h>
724 #include"../src/mediascanner/internal/utils.hh"
725+#include"../src/mediascanner/MediaFile.hh"
726+#include"../src/mediascanner/MediaFileBuilder.hh"
727+
728 #include "test_config.h"
729
730 using namespace mediascanner;

Subscribers

People subscribed via source and target branches