Merge lp:~michihenning/thumbnailer/add-preview-image-extraction into lp:thumbnailer/devel
- add-preview-image-extraction
- Merge into devel
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Henstridge | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Review via email:
|
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:273
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:279
http://
Executed test runs:
ABORTED: http://
ABORTED: http://
ABORTED: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:277
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
ABORTED: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:277
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:280
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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_
auto structure = gst_caps_
Needs to be changed to:
auto structure = gst_sample_
It seems the sample code I based this on was doing it wrong.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michi Henning (michihenning) wrote : | # |
Bloody awesome, thank you! Works like a charm, we are getting coverage on the cover art branch now.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:281
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:282
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 283. By Michi Henning
-
Added missing gstreamer initialization to stress test.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:283
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 284. By Michi Henning
-
Merged devel.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:284
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:284
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
James Henstridge (jamesh) wrote : | # |
This mostly looks good now. I've left a few inline comments about areas that could be improved though.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
James Henstridge (jamesh) wrote : | # |
Lets merge this.
Preview Diff
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; |
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.