Merge lp:~renatofilho/mediaplayer-app/fix-1116768 into lp:mediaplayer-app
- fix-1116768
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gustavo Pichorim Boiko |
Approved revision: | 74 |
Merged at revision: | 69 |
Proposed branch: | lp:~renatofilho/mediaplayer-app/fix-1116768 |
Merge into: | lp:mediaplayer-app |
Diff against target: |
365 lines (+171/-40) 13 files modified
CMakeLists.txt (+3/-0) src/qml/player/Controls.qml (+5/-5) src/qml/player/SceneFrame.qml (+2/-1) src/thumbnail-pipeline-gst.cpp (+58/-17) src/thumbnail-pipeline-gst.h (+5/-2) src/thumbnail-provider.cpp (+3/-8) tests/CMakeLists.txt (+3/-1) tests/autopilot/mediaplayer_app/tests/test_player_with_video.py (+1/-6) tests/images/CMakeLists.txt (+1/-0) tests/unittest/CMakeLists.txt (+30/-0) tests/unittest/test-config.h.in (+1/-0) tests/unittest/thumbnailtest.cpp (+57/-0) tests/videos/CMakeLists.txt (+2/-0) |
To merge this branch: | bzr merge lp:~renatofilho/mediaplayer-app/fix-1116768 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Pichorim Boiko (community) | Approve | ||
PS Jenkins bot | continuous-integration | Approve | |
Review via email: mp+157104@code.launchpad.net |
Commit message
Avoid black thumbnails on scene selector.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
- 69. By Renato Araujo Oliveira Filho
-
Fixed scene selector for small videos.
Returned a invalid QImage if a good frame was not found, during the thumbnail generation. - 70. By Renato Araujo Oliveira Filho
-
Removed invalid comment.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:70
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 71. By Renato Araujo Oliveira Filho
-
uses standard deviation to discovery a good image for thumbnail.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:71
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 72. By Renato Araujo Oliveira Filho
-
Created unit test to check for valid thumbnails.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:72
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Gustavo Pichorim Boiko (boiko) wrote : | # |
Please use data-driven tests for trying the isMeaningful functions instead of one function per image.
- 73. By Renato Araujo Oliveira Filho
-
Make sure that image and videos sample data get processed first then unittest project to correct export the variables.
- 74. By Renato Araujo Oliveira Filho
-
Updated test to use data-driven tests.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:74
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Gustavo Pichorim Boiko (boiko) wrote : | # |
Looks good now.
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2013-02-21 17:40:40 +0000 |
3 | +++ CMakeLists.txt 2013-04-04 22:28:27 +0000 |
4 | @@ -19,6 +19,9 @@ |
5 | |
6 | set(MEDIAPLAYER_DIR ${CMAKE_INSTALL_DATADIR}/media-player) |
7 | |
8 | +# Tests |
9 | +enable_testing() |
10 | + |
11 | add_subdirectory(data) |
12 | add_subdirectory(sdk) |
13 | add_subdirectory(src) |
14 | |
15 | === modified file 'src/qml/player/Controls.qml' |
16 | --- src/qml/player/Controls.qml 2013-03-06 15:10:16 +0000 |
17 | +++ src/qml/player/Controls.qml 2013-04-04 22:28:27 +0000 |
18 | @@ -276,12 +276,12 @@ |
19 | _sceneSelectorModel.clear() |
20 | var frameSize = video.duration / 10; |
21 | for (var i = 0; i < 10; ++i) { |
22 | - // TODO: discuss this with designers |
23 | - // shift 3s to avoid black frame in the position 0 |
24 | var pos = Math.floor(i * frameSize); |
25 | - _sceneSelectorModel.append({"thumbnail": "image://video/" + video.source + "/" + (pos + 3000), |
26 | - "start" : pos, |
27 | - "duration" : frameSize}) |
28 | + if (pos <= video.duration) { |
29 | + _sceneSelectorModel.append({"thumbnail": "image://video/" + video.source + "/" + pos, |
30 | + "start" : pos, |
31 | + "duration" : frameSize}) |
32 | + } |
33 | } |
34 | } |
35 | } |
36 | |
37 | === modified file 'src/qml/player/SceneFrame.qml' |
38 | --- src/qml/player/SceneFrame.qml 2013-03-06 13:33:38 +0000 |
39 | +++ src/qml/player/SceneFrame.qml 2013-04-04 22:28:27 +0000 |
40 | @@ -53,13 +53,14 @@ |
41 | } |
42 | } |
43 | |
44 | - |
45 | image: Image { |
46 | id: _image |
47 | |
48 | fillMode: Image.PreserveAspectCrop |
49 | smooth: true |
50 | asynchronous: true |
51 | + sourceSize.width: _shape.width |
52 | + sourceSize.height: _shape.height |
53 | } |
54 | } |
55 | |
56 | |
57 | === modified file 'src/thumbnail-pipeline-gst.cpp' |
58 | --- src/thumbnail-pipeline-gst.cpp 2013-02-12 14:30:57 +0000 |
59 | +++ src/thumbnail-pipeline-gst.cpp 2013-04-04 22:28:27 +0000 |
60 | @@ -16,6 +16,7 @@ |
61 | |
62 | #include <QDebug> |
63 | #include <gst/gst.h> |
64 | +#include <math.h> |
65 | |
66 | #include "thumbnail-pipeline-gst.h" |
67 | |
68 | @@ -152,31 +153,71 @@ |
69 | return QImage(); |
70 | } |
71 | |
72 | -QImage ThumbnailPipeline::parseImage(ThumbnailImageData *buffer) |
73 | +QImage ThumbnailPipeline::parseImage(ThumbnailImageData *buffer) const |
74 | { |
75 | return parseImageGst(buffer); |
76 | } |
77 | |
78 | -QImage ThumbnailPipeline::request(qint64 time) |
79 | +// use standard deviation of the histogram to discovery a good image |
80 | +bool ThumbnailPipeline::isMeaningful(QImage img) |
81 | +{ |
82 | + const static int threshold = 15; |
83 | + const float average = (img.height() * img.width()) / 256; |
84 | + int histogram[256]; |
85 | + |
86 | + memset(histogram, 0, sizeof(int) * 256); |
87 | + for(int h=0, hMax = img.height(); h < hMax; h++) { |
88 | + for(int w=0, wMax = img.width(); w < wMax; w++) { |
89 | + histogram[qGray(img.pixel(w, h))]++; |
90 | + } |
91 | + } |
92 | + |
93 | + float sum = 0; |
94 | + for(int i=0; i < 256; i++) { |
95 | + sum += pow(average - histogram[i], 2); |
96 | + } |
97 | + |
98 | + return ((sqrt(sum / 256) / average) <= threshold); |
99 | +} |
100 | + |
101 | +QImage ThumbnailPipeline::request(qint64 time, QSize size, bool skipBlack) |
102 | { |
103 | if (m_pipeline == 0) { |
104 | qWarning() << "Pipiline not ready"; |
105 | return QImage(); |
106 | } |
107 | |
108 | - gst_element_seek (m_pipeline, 1.0, |
109 | - GST_FORMAT_TIME, static_cast<GstSeekFlags>(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), |
110 | - GST_SEEK_TYPE_SET, time * GST_MSECOND, |
111 | - GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); |
112 | - |
113 | - /* And wait for this seek to complete */ |
114 | - gst_element_get_state (m_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); |
115 | - |
116 | - /* get frame */ |
117 | - ThumbnailImageData *buf = 0; |
118 | - |
119 | - g_signal_emit_by_name (m_pipeline, "convert-frame", m_caps, &buf); |
120 | - QImage img = parseImage (buf); |
121 | - |
122 | - return img; |
123 | + |
124 | + QImage firstImage; |
125 | + while(time < m_duration) { |
126 | + gst_element_seek (m_pipeline, 1.0, |
127 | + GST_FORMAT_TIME, static_cast<GstSeekFlags>(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), |
128 | + GST_SEEK_TYPE_SET, time * GST_MSECOND, |
129 | + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); |
130 | + |
131 | + /* And wait for this seek to complete */ |
132 | + gst_element_get_state (m_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); |
133 | + |
134 | + /* get frame */ |
135 | + ThumbnailImageData *buf = 0; |
136 | + |
137 | + g_signal_emit_by_name (m_pipeline, "convert-frame", m_caps, &buf); |
138 | + QImage img = parseImage (buf); |
139 | + if (size.isValid()) { |
140 | + img = img.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); |
141 | + } |
142 | + |
143 | + if (skipBlack && !isMeaningful (img)) { |
144 | + if (firstImage.isNull()) { |
145 | + firstImage = img.copy(); |
146 | + } |
147 | + time += 1000; |
148 | + continue; |
149 | + } else { |
150 | + return img; |
151 | + } |
152 | + } |
153 | + |
154 | + // return the original frame if any other was found |
155 | + return firstImage; |
156 | } |
157 | |
158 | === modified file 'src/thumbnail-pipeline-gst.h' |
159 | --- src/thumbnail-pipeline-gst.h 2013-02-12 14:30:57 +0000 |
160 | +++ src/thumbnail-pipeline-gst.h 2013-04-04 22:28:27 +0000 |
161 | @@ -30,7 +30,8 @@ |
162 | void setUri(const QString &uri); |
163 | QString uri() const; |
164 | |
165 | - QImage request(qint64 time); |
166 | + QImage request(qint64 time, QSize size, bool skipBlack=true); |
167 | + static bool isMeaningful(QImage img); |
168 | |
169 | private: |
170 | GstElement *m_pipeline; |
171 | @@ -41,7 +42,9 @@ |
172 | void setup(); |
173 | bool start(); |
174 | void stop(); |
175 | - QImage parseImage(ThumbnailImageData *buffer); |
176 | + |
177 | + QImage parseImage(ThumbnailImageData *buffer) const; |
178 | + |
179 | }; |
180 | |
181 | #endif |
182 | |
183 | === modified file 'src/thumbnail-provider.cpp' |
184 | --- src/thumbnail-provider.cpp 2013-02-12 14:30:57 +0000 |
185 | +++ src/thumbnail-provider.cpp 2013-04-04 22:28:27 +0000 |
186 | @@ -88,15 +88,10 @@ |
187 | if (m_cache.contains(time)) { |
188 | img = m_cache[time]; |
189 | } else { |
190 | - img = m_player->request(time).copy(); |
191 | + img = m_player->request(time, requestedSize).copy(); |
192 | m_cache.insert(time, img); |
193 | } |
194 | |
195 | - if (requestedSize.isValid()) { |
196 | - *size = requestedSize; |
197 | - return img.scaled(requestedSize); |
198 | - } else { |
199 | - *size = img.size(); |
200 | - return img; |
201 | - } |
202 | + *size = img.size(); |
203 | + return img; |
204 | } |
205 | |
206 | === modified file 'tests/CMakeLists.txt' |
207 | --- tests/CMakeLists.txt 2013-03-04 14:17:19 +0000 |
208 | +++ tests/CMakeLists.txt 2013-04-04 22:28:27 +0000 |
209 | @@ -1,2 +1,4 @@ |
210 | +add_subdirectory(videos) |
211 | +add_subdirectory(images) |
212 | add_subdirectory(autopilot) |
213 | -add_subdirectory(videos) |
214 | +add_subdirectory(unittest) |
215 | |
216 | === modified file 'tests/autopilot/mediaplayer_app/tests/test_player_with_video.py' |
217 | --- tests/autopilot/mediaplayer_app/tests/test_player_with_video.py 2013-04-01 22:17:06 +0000 |
218 | +++ tests/autopilot/mediaplayer_app/tests/test_player_with_video.py 2013-04-04 22:28:27 +0000 |
219 | @@ -9,16 +9,11 @@ |
220 | |
221 | from __future__ import absolute_import |
222 | |
223 | -from testtools.matchers import Equals, NotEquals, GreaterThan |
224 | +from testtools.matchers import Equals, GreaterThan |
225 | from autopilot.matchers import Eventually |
226 | |
227 | from mediaplayer_app.tests import MediaplayerAppTestCase |
228 | |
229 | -import unittest |
230 | -import time |
231 | -import os |
232 | -from os import path |
233 | - |
234 | class TestPlayerWithVideo(MediaplayerAppTestCase): |
235 | """Tests the main media player features while playing a video """ |
236 | |
237 | |
238 | === added directory 'tests/images' |
239 | === added file 'tests/images/CMakeLists.txt' |
240 | --- tests/images/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
241 | +++ tests/images/CMakeLists.txt 2013-04-04 22:28:27 +0000 |
242 | @@ -0,0 +1,1 @@ |
243 | +project(sample_images) |
244 | |
245 | === added file 'tests/images/frame0.png' |
246 | Binary files tests/images/frame0.png 1970-01-01 00:00:00 +0000 and tests/images/frame0.png 2013-04-04 22:28:27 +0000 differ |
247 | === added file 'tests/images/frame10.png' |
248 | Binary files tests/images/frame10.png 1970-01-01 00:00:00 +0000 and tests/images/frame10.png 2013-04-04 22:28:27 +0000 differ |
249 | === added file 'tests/images/frame100.png' |
250 | Binary files tests/images/frame100.png 1970-01-01 00:00:00 +0000 and tests/images/frame100.png 2013-04-04 22:28:27 +0000 differ |
251 | === added file 'tests/images/frame20.png' |
252 | Binary files tests/images/frame20.png 1970-01-01 00:00:00 +0000 and tests/images/frame20.png 2013-04-04 22:28:27 +0000 differ |
253 | === added directory 'tests/unittest' |
254 | === added file 'tests/unittest/CMakeLists.txt' |
255 | --- tests/unittest/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
256 | +++ tests/unittest/CMakeLists.txt 2013-04-04 22:28:27 +0000 |
257 | @@ -0,0 +1,30 @@ |
258 | +add_definitions(-DTEST_SUITE) |
259 | + |
260 | +if(NOT CTEST_TESTING_TIMEOUT) |
261 | + set(CTEST_TESTING_TIMEOUT 60) |
262 | +endif() |
263 | + |
264 | +include_directories(${CMAKE_BINARY_DIR} |
265 | + ${mediaplayer_src_SOURCE_DIR} |
266 | + ${GSTLIB_INCLUDE_DIRS} |
267 | +) |
268 | + |
269 | +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test-config.h.in |
270 | + ${CMAKE_CURRENT_BINARY_DIR}/test-config.h) |
271 | + |
272 | +# thumbnail-test ############################################################## |
273 | +add_executable(thumbnailtest |
274 | + thumbnailtest.cpp |
275 | + ${mediaplayer_src_SOURCE_DIR}/thumbnail-pipeline-gst.cpp |
276 | +) |
277 | + |
278 | +qt5_use_modules(thumbnailtest Gui Core Test) |
279 | +add_test(thumbnailtest thumbnailtest -xunitxml -o test_thumbnailtest.xml) |
280 | +set_tests_properties(thumbnailtest PROPERTIES |
281 | + TIMEOUT ${CTEST_TESTING_TIMEOUT} |
282 | + ENVIRONMENT "QT_QPA_PLATFORM=minimal") |
283 | +target_link_libraries(thumbnailtest |
284 | + ${GSTLIB_LDFLAGS}) |
285 | +############################################################################### |
286 | + |
287 | + |
288 | |
289 | === added file 'tests/unittest/test-config.h.in' |
290 | --- tests/unittest/test-config.h.in 1970-01-01 00:00:00 +0000 |
291 | +++ tests/unittest/test-config.h.in 2013-04-04 22:28:27 +0000 |
292 | @@ -0,0 +1,1 @@ |
293 | +static const char* SAMPLE_IMAGES_DIR = "@sample_images_SOURCE_DIR@"; |
294 | |
295 | === added file 'tests/unittest/thumbnailtest.cpp' |
296 | --- tests/unittest/thumbnailtest.cpp 1970-01-01 00:00:00 +0000 |
297 | +++ tests/unittest/thumbnailtest.cpp 2013-04-04 22:28:27 +0000 |
298 | @@ -0,0 +1,57 @@ |
299 | +/* |
300 | + * Copyright 2013 Canonical Ltd. |
301 | + * |
302 | + * This program is free software; you can redistribute it and/or modify |
303 | + * it under the terms of the GNU Lesser General Public License as published by |
304 | + * the Free Software Foundation; version 3. |
305 | + * |
306 | + * This program is distributed in the hope that it will be useful, |
307 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
308 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
309 | + * GNU Lesser General Public License for more details. |
310 | + * |
311 | + * You should have received a copy of the GNU Lesser General Public License |
312 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
313 | + * |
314 | + * Authors: |
315 | + * Renato Araujo Oliveira Filho <renato@canonical.com> |
316 | + */ |
317 | + |
318 | +#include "thumbnail-pipeline-gst.h" |
319 | +#include "test-config.h" |
320 | + |
321 | +#include <QtTest> |
322 | +#include <QDebug> |
323 | +#include <QImage> |
324 | + |
325 | +class ThumbnailTest : public QObject |
326 | +{ |
327 | + Q_OBJECT |
328 | +private Q_SLOTS: |
329 | + void testImageIsMeaningful_data() |
330 | + { |
331 | + QTest::addColumn<QString>("path"); |
332 | + QTest::addColumn<bool>("expectedResult"); |
333 | + |
334 | + QTest::newRow("black image") << QString(SAMPLE_IMAGES_DIR) + "/frame0.png" << false; |
335 | + QTest::newRow("non black image") << QString(SAMPLE_IMAGES_DIR) + "/frame10.png" << true; |
336 | + QTest::newRow("collored image") << QString(SAMPLE_IMAGES_DIR) + "/frame20.png" << true; |
337 | + QTest::newRow("almost black image") << QString(SAMPLE_IMAGES_DIR) + "/frame100.png" << false; |
338 | + } |
339 | + |
340 | + void testImageIsMeaningful() |
341 | + { |
342 | + QFETCH(QString, path); |
343 | + QFETCH(bool, expectedResult); |
344 | + |
345 | + QImage img = QImage(path); |
346 | + QVERIFY(!img.isNull()); |
347 | + |
348 | + bool result = ThumbnailPipeline::isMeaningful(img); |
349 | + QCOMPARE(result, expectedResult); |
350 | + } |
351 | +}; |
352 | + |
353 | +QTEST_MAIN(ThumbnailTest) |
354 | + |
355 | +#include "thumbnailtest.moc" |
356 | |
357 | === modified file 'tests/videos/CMakeLists.txt' |
358 | --- tests/videos/CMakeLists.txt 2013-03-04 14:17:19 +0000 |
359 | +++ tests/videos/CMakeLists.txt 2013-04-04 22:28:27 +0000 |
360 | @@ -1,3 +1,5 @@ |
361 | +project(sample_videos) |
362 | + |
363 | set(MEDIAPLAYER_TEST_VIDEOS small.mp4) |
364 | |
365 | install(FILES ${MEDIAPLAYER_TEST_VIDEOS} |
FAILED: Continuous integration, rev:68 jenkins. qa.ubuntu. com/job/ mediaplayer- app-ci/ 18/ jenkins. qa.ubuntu. com/job/ mediaplayer- app-quantal- armhf-ci/ 18 jenkins. qa.ubuntu. com/job/ mediaplayer- app-quantal- armhf-ci/ 18/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mediaplayer- app-quantal- i386-ci/ 18 jenkins. qa.ubuntu. com/job/ mediaplayer- app-raring- armhf-ci/ 17 jenkins. qa.ubuntu. com/job/ mediaplayer- app-raring- armhf-ci/ 17/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mediaplayer- app-raring- i386-ci/ 17 jenkins. qa.ubuntu. com/job/ generic- mediumtests- runner/ 618
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ mediaplayer- app-ci/ 18/rebuild
http://