Merge lp:~michihenning/thumbnailer/add-preview-image-extraction into lp:thumbnailer/devel

Proposed by Michi Henning
Status: Merged
Approved by: James Henstridge
Approved revision: 284
Merged at revision: 286
Proposed branch: lp:~michihenning/thumbnailer/add-preview-image-extraction
Merge into: lp:thumbnailer/devel
Diff against target: 251 lines (+127/-25)
5 files modified
debian/control (+2/-0)
src/thumbnailer.cpp (+1/-1)
src/vs-thumb/thumbnailextractor.cpp (+64/-24)
tests/stress/stress_test.cpp (+16/-0)
tests/vs-thumb/vs-thumb_test.cpp (+44/-0)
To merge this branch: bzr merge lp:~michihenning/thumbnailer/add-preview-image-extraction
Reviewer Review Type Date Requested Status
James Henstridge Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+271063@code.launchpad.net

Commit message

vs-thumb now looks for both image and preview image tags to extract artwork.

Description of the change

vs-thumb now looks for both image and preview image tags to extract artwork.

To post a comment you must log in.
Revision history for this message
James Henstridge (jamesh) wrote :

This looks it works by accident. A file can contain multiple images, so the code in question is iterating through them to find one that represents the cover. It stops when it hits the last image.

If there are no IMAGE tags in the file, it will ask for the 0th PREVIEW_IMAGE and generally do the right thing.

If there is a single image in the file, the first iteration of the loop will find that image, and on the second iteration it will get an error and look for PREVIEW_IMAGE at index 1. So if there is a single PREVIEW_IMAGE tag, we'll ignore it.

I think we want to iterate through all IMAGE tags and then all PREVIEW_IMAGE tags here.

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

I think I've discovered the image type handling bug. Since you're modifying the code in question, I'll just note it here rather than creating a conflicting MP.

The following code:

        auto caps = gst_sample_get_caps(sample);
        auto structure = gst_caps_get_structure(caps, 0);

Needs to be changed to:

        auto structure = gst_sample_get_info(sample);

It seems the sample code I based this on was doing it wrong.

Revision history for this message
Michi Henning (michihenning) wrote :

Bloody awesome, thank you! Works like a charm, we are getting coverage on the cover art branch now.

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

Added missing gstreamer initialization to stress test.

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

Merged devel.

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

This mostly looks good now. I've left a few inline comments about areas that could be improved though.

Revision history for this message
Michi Henning (michihenning) wrote :

Not happy about that either. It's awkward. I'm planning to re-factor vs-thumb anyway, so we get better diagnostics with time stamps. That'll be a good time to do something about the image extraction too, so could we let this slide for now? Functionally, it's correct, I believe.

Revision history for this message
James Henstridge (jamesh) wrote :

Lets merge this.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2015-09-15 03:22:34 +0000
3+++ debian/control 2015-09-20 10:12:45 +0000
4@@ -7,6 +7,8 @@
5 cmake-extras (>= 0.4),
6 debhelper (>= 9),
7 google-mock,
8+ gstreamer1.0-fluendo-mp3,
9+ gstreamer1.0-plugins-bad-faad,
10 gstreamer1.0-plugins-good,
11 libapparmor-dev,
12 libboost-filesystem-dev,
13
14=== modified file 'src/thumbnailer.cpp'
15--- src/thumbnailer.cpp 2015-09-15 09:37:17 +0000
16+++ src/thumbnailer.cpp 2015-09-20 10:12:45 +0000
17@@ -629,7 +629,6 @@
18 Image full_size(string(raw_data.data(), raw_data.size()));
19 return RequestBase::ImageData(full_size, RequestBase::CachePolicy::cache_fullsize, Location::remote);
20 }
21- qDebug() << "common_fetch(): Request failed:" << artreply->error_string();
22 if (artreply->not_found_error())
23 {
24 return RequestBase::ImageData(RequestBase::FetchStatus::not_found, Location::remote);
25@@ -638,6 +637,7 @@
26 {
27 return RequestBase::ImageData(RequestBase::FetchStatus::no_network, Location::remote);
28 }
29+ qCritical() << "common_fetch(): Request failed:" << artreply->error_string();
30 return RequestBase::ImageData(RequestBase::FetchStatus::error, Location::remote);
31 }
32
33
34=== modified file 'src/vs-thumb/thumbnailextractor.cpp'
35--- src/vs-thumb/thumbnailextractor.cpp 2015-09-10 06:58:42 +0000
36+++ src/vs-thumb/thumbnailextractor.cpp 2015-09-20 10:12:45 +0000
37@@ -171,14 +171,59 @@
38
39 struct ThumbnailExtractor::Private
40 {
41+ void find_audio_cover(GstTagList* tags, char const* tag_name);
42+
43+ enum ImageType { cover, other };
44+
45 gobj_ptr<GstElement> playbin;
46 gint64 duration = -1;
47
48 std::unique_ptr<GstSample, decltype(&gst_sample_unref)> sample{nullptr, gst_sample_unref};
49 GdkPixbufRotation sample_rotation = GDK_PIXBUF_ROTATE_NONE;
50 bool sample_raw = true;
51+ ImageType image_type;
52 };
53
54+// Look for an image with the specified tag. If we find a cover image, image_type is set to cover,
55+// and sample points at the image. If we find some other (non-cover) image, image_type is set to other,
56+// and sample points at the image. Otherwise, if we can't find any image at all, sample is set to nullptr.
57+
58+void ThumbnailExtractor::Private::find_audio_cover(GstTagList* tags, const char* tag_name)
59+{
60+ image_type = ThumbnailExtractor::Private::other;
61+
62+ bool found_cover = false;
63+ for (int i = 0; !found_cover; i++)
64+ {
65+ GstSample* s;
66+ if (!gst_tag_list_get_sample_index(tags, tag_name, i, &s))
67+ {
68+ break;
69+ }
70+ assert(s);
71+ // Check the type of this image
72+ int type = GST_TAG_IMAGE_TYPE_UNDEFINED;
73+ auto structure = gst_sample_get_info(s);
74+ if (structure)
75+ {
76+ gst_structure_get_enum(structure, "image-type", GST_TYPE_TAG_IMAGE_TYPE, &type);
77+ }
78+ if (type == GST_TAG_IMAGE_TYPE_FRONT_COVER)
79+ {
80+ sample.reset(gst_sample_ref(s));
81+ found_cover = true;
82+ image_type = ThumbnailExtractor::Private::cover;
83+ }
84+ else if (type == GST_TAG_IMAGE_TYPE_UNDEFINED && !sample)
85+ {
86+ // Save the first unknown image tag, but continue scanning
87+ // in case there is one marked as the cover.
88+ sample.reset(gst_sample_ref(s));
89+ }
90+ gst_sample_unref(s);
91+ }
92+}
93+
94 /* GstPlayFlags flags from playbin.
95 *
96 * GStreamer does not install headers for the enums of individual
97@@ -294,6 +339,7 @@
98 g_signal_emit_by_name(p->playbin.get(), "get-video-tags", 0, &tags);
99 if (tags)
100 {
101+ // TODO: The "flip-rotate-*" transforms defined in gst/gsttaglist.h need to be added here.
102 char* orientation = nullptr;
103 if (gst_tag_list_get_string_index(tags, GST_TAG_IMAGE_ORIENTATION, 0, &orientation) && orientation != nullptr)
104 {
105@@ -326,32 +372,26 @@
106 p->sample.reset();
107 p->sample_rotation = GDK_PIXBUF_ROTATE_NONE;
108 p->sample_raw = false;
109- bool found_cover = false;
110- for (int i = 0; !found_cover; i++)
111+
112+ p->find_audio_cover(tags, GST_TAG_IMAGE);
113+ if (!p->sample || p->image_type == ThumbnailExtractor::Private::other)
114 {
115- GstSample* sample;
116- if (!gst_tag_list_get_sample_index(tags, GST_TAG_IMAGE, i, &sample))
117- {
118- break;
119- }
120- // Check the type of this image
121- auto caps = gst_sample_get_caps(sample);
122- auto structure = gst_caps_get_structure(caps, 0);
123- int type = GST_TAG_IMAGE_TYPE_UNDEFINED;
124- gst_structure_get_enum(structure, "image-type", GST_TYPE_TAG_IMAGE_TYPE, &type);
125- if (type == GST_TAG_IMAGE_TYPE_FRONT_COVER)
126- {
127- p->sample.reset(gst_sample_ref(sample));
128- found_cover = true;
129- }
130- else if (type == GST_TAG_IMAGE_TYPE_UNDEFINED && !p->sample)
131- {
132- // Save the first unknown image tag, but continue scanning
133- // in case there is one marked as the cover.
134- p->sample.reset(gst_sample_ref(sample));
135- }
136- gst_sample_unref(sample);
137+ // Save whatever the first look-up returned (possibly nothing).
138+ auto provisional_sample = std::move(p->sample);
139+
140+ // We didn't find a full-size image, or we found an image that is not marked as the cover.
141+ // Try to find a preview image instead.
142+ p->find_audio_cover(tags, GST_TAG_PREVIEW_IMAGE);
143+ if (p->image_type == ThumbnailExtractor::Private::other && provisional_sample)
144+ {
145+ // We found a preview image that is *not* a cover, and the
146+ // first look-up for a normal image returned something
147+ // full-size that is *also* not a cover. Return the image
148+ // from the first look-up in preference to this one.
149+ p->sample = std::move(provisional_sample);
150+ }
151 }
152+
153 gst_tag_list_unref(tags);
154 return bool(p->sample);
155 }
156
157=== modified file 'tests/stress/stress_test.cpp'
158--- tests/stress/stress_test.cpp 2015-07-31 06:23:59 +0000
159+++ tests/stress/stress_test.cpp 2015-09-20 10:12:45 +0000
160@@ -23,6 +23,19 @@
161 #include <utils/dbusserver.h>
162 #include <utils/supports_decoder.h>
163
164+#pragma GCC diagnostic push
165+#pragma GCC diagnostic ignored "-Wold-style-cast"
166+#pragma GCC diagnostic ignored "-Wcast-qual"
167+#pragma GCC diagnostic ignored "-Wcast-align"
168+#ifdef __clang__
169+#pragma clang diagnostic ignored "-Wparentheses-equality"
170+#endif
171+#include <gst/gst.h>
172+#ifdef __clang__
173+#pragma clang diagnostic pop
174+#endif
175+#pragma GCC diagnostic pop
176+
177 #include <boost/filesystem.hpp>
178 #include <gtest/gtest.h>
179 #include <QSignalSpy>
180@@ -296,6 +309,9 @@
181
182 int main(int argc, char** argv)
183 {
184+ // Need this because we are using a static test fixture and can't rely an global constructor order.
185+ gst_init(&argc, &argv);
186+
187 QCoreApplication app(argc, argv);
188
189 setenv("GSETTINGS_BACKEND", "memory", true);
190
191=== modified file 'tests/vs-thumb/vs-thumb_test.cpp'
192--- tests/vs-thumb/vs-thumb_test.cpp 2015-09-10 06:58:42 +0000
193+++ tests/vs-thumb/vs-thumb_test.cpp 2015-09-20 10:12:45 +0000
194@@ -50,6 +50,8 @@
195 const char MP4_LANDSCAPE_TEST_FILE[] = TESTDATADIR "/gegl-landscape.mp4";
196 const char MP4_PORTRAIT_TEST_FILE[] = TESTDATADIR "/gegl-portrait.mp4";
197 const char VORBIS_TEST_FILE[] = TESTDATADIR "/testsong.ogg";
198+const char AAC_TEST_FILE[] = TESTDATADIR "/testsong.m4a";
199+const char MP3_TEST_FILE[] = TESTDATADIR "/testsong.mp3";
200
201 class ExtractorTest : public ::testing::Test
202 {
203@@ -181,6 +183,48 @@
204 EXPECT_EQ(gdk_pixbuf_get_height(image.get()), 200);
205 }
206
207+TEST_F(ExtractorTest, extract_aac_cover_art)
208+{
209+ if (!supports_decoder("audio/mpeg"))
210+ {
211+ fprintf(stderr, "No support for AAC decoder\n");
212+ return;
213+ }
214+
215+ ThumbnailExtractor extractor;
216+
217+ std::string outfile = tempdir + "/out.jpg";
218+ extractor.set_uri(filename_to_uri(AAC_TEST_FILE));
219+ ASSERT_FALSE(extractor.has_video());
220+ ASSERT_TRUE(extractor.extract_audio_cover_art());
221+ extractor.save_screenshot(outfile);
222+
223+ auto image = load_image(outfile);
224+ EXPECT_EQ(gdk_pixbuf_get_width(image.get()), 200);
225+ EXPECT_EQ(gdk_pixbuf_get_height(image.get()), 200);
226+}
227+
228+TEST_F(ExtractorTest, extract_mp3_cover_art)
229+{
230+ if (!supports_decoder("audio/mpeg"))
231+ {
232+ fprintf(stderr, "No support for MP3 decoder\n");
233+ return;
234+ }
235+
236+ ThumbnailExtractor extractor;
237+
238+ std::string outfile = tempdir + "/out.jpg";
239+ extractor.set_uri(filename_to_uri(MP3_TEST_FILE));
240+ ASSERT_FALSE(extractor.has_video());
241+ ASSERT_TRUE(extractor.extract_audio_cover_art());
242+ extractor.save_screenshot(outfile);
243+
244+ auto image = load_image(outfile);
245+ EXPECT_EQ(gdk_pixbuf_get_width(image.get()), 200);
246+ EXPECT_EQ(gdk_pixbuf_get_height(image.get()), 200);
247+}
248+
249 TEST_F(ExtractorTest, file_not_found)
250 {
251 ThumbnailExtractor extractor;

Subscribers

People subscribed via source and target branches

to all changes: