Merge lp:~uriboni/camera-app/content-hub-video-source into lp:camera-app/staging
- content-hub-video-source
- Merge into staging
Proposed by
Ugo Riboni
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Florian Boucault | ||||
Approved revision: | 624 | ||||
Merged at revision: | 622 | ||||
Proposed branch: | lp:~uriboni/camera-app/content-hub-video-source | ||||
Merge into: | lp:camera-app/staging | ||||
Diff against target: |
400 lines (+173/-71) 6 files modified
VideoReview.qml (+63/-0) ViewFinderExportConfirmation.qml (+66/-49) ViewFinderOverlay.qml (+3/-1) ViewFinderView.qml (+28/-16) camera-app.qml (+11/-4) camera-contenthub.json (+2/-1) |
||||
To merge this branch: | bzr merge lp:~uriboni/camera-app/content-hub-video-source | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Florian Boucault (community) | Approve | ||
Review via email: mp+280140@code.launchpad.net |
Commit message
Enable camera-app as source for videos for content-hub export, and adjust the UI
Description of the change
Enable camera-app as source for videos for content-hub export, and adjust the UI
To post a comment you must log in.
- 623. By Ugo Riboni
-
Merge changes from staging
- 624. By Ugo Riboni
-
Cleanup
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'VideoReview.qml' | |||
2 | --- VideoReview.qml 1970-01-01 00:00:00 +0000 | |||
3 | +++ VideoReview.qml 2015-12-10 12:37:24 +0000 | |||
4 | @@ -0,0 +1,63 @@ | |||
5 | 1 | /* | ||
6 | 2 | * Copyright 2015 Canonical Ltd. | ||
7 | 3 | * | ||
8 | 4 | * This program is free software; you can redistribute it and/or modify | ||
9 | 5 | * it under the terms of the GNU General Public License as published by | ||
10 | 6 | * the Free Software Foundation; version 3. | ||
11 | 7 | * | ||
12 | 8 | * This program is distributed in the hope that it will be useful, | ||
13 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | 11 | * GNU General Public License for more details. | ||
16 | 12 | * | ||
17 | 13 | * You should have received a copy of the GNU General Public License | ||
18 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
19 | 15 | */ | ||
20 | 16 | |||
21 | 17 | import QtQuick 2.4 | ||
22 | 18 | import Ubuntu.Components 1.3 | ||
23 | 19 | |||
24 | 20 | Item { | ||
25 | 21 | property string videoPath | ||
26 | 22 | property int bottomMargin | ||
27 | 23 | |||
28 | 24 | Image { | ||
29 | 25 | id: thumbnail | ||
30 | 26 | anchors.fill: parent | ||
31 | 27 | |||
32 | 28 | fillMode: Image.PreserveAspectFit | ||
33 | 29 | sourceSize.width: width | ||
34 | 30 | sourceSize.height: height | ||
35 | 31 | |||
36 | 32 | source: videoPath ? "image://thumbnailer/%1".arg(videoPath) : "" | ||
37 | 33 | opacity: status == Image.Ready ? 1.0 : 0.0 | ||
38 | 34 | Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration } } | ||
39 | 35 | } | ||
40 | 36 | |||
41 | 37 | Item { | ||
42 | 38 | anchors.fill: parent | ||
43 | 39 | anchors.bottomMargin: parent.bottomMargin | ||
44 | 40 | |||
45 | 41 | ActivityIndicator { | ||
46 | 42 | anchors.centerIn: parent | ||
47 | 43 | visible: running | ||
48 | 44 | running: thumbnail.status != Image.Ready | ||
49 | 45 | } | ||
50 | 46 | |||
51 | 47 | Icon { | ||
52 | 48 | width: units.gu(5) | ||
53 | 49 | height: units.gu(5) | ||
54 | 50 | anchors.centerIn: parent | ||
55 | 51 | name: "media-playback-start" | ||
56 | 52 | color: "white" | ||
57 | 53 | opacity: 0.8 | ||
58 | 54 | } | ||
59 | 55 | |||
60 | 56 | MouseArea { | ||
61 | 57 | anchors.centerIn: parent | ||
62 | 58 | width: units.gu(10) | ||
63 | 59 | height: units.gu(10) | ||
64 | 60 | onClicked: Qt.openUrlExternally("video://%1".arg(videoPath)); | ||
65 | 61 | } | ||
66 | 62 | } | ||
67 | 63 | } | ||
68 | 0 | 64 | ||
69 | === modified file 'ViewFinderExportConfirmation.qml' | |||
70 | --- ViewFinderExportConfirmation.qml 2015-10-22 12:21:14 +0000 | |||
71 | +++ ViewFinderExportConfirmation.qml 2015-12-10 12:37:24 +0000 | |||
72 | @@ -20,6 +20,7 @@ | |||
73 | 20 | Item { | 20 | Item { |
74 | 21 | id: viewFinderExportConfirmation | 21 | id: viewFinderExportConfirmation |
75 | 22 | 22 | ||
76 | 23 | property bool isVideo | ||
77 | 23 | property string mediaPath | 24 | property string mediaPath |
78 | 24 | property Snapshot snapshot | 25 | property Snapshot snapshot |
79 | 25 | 26 | ||
80 | @@ -27,7 +28,7 @@ | |||
81 | 27 | viewFinder.visible = false; | 28 | viewFinder.visible = false; |
82 | 28 | viewFinderOverlay.visible = false; | 29 | viewFinderOverlay.visible = false; |
83 | 29 | mediaPath = path; | 30 | mediaPath = path; |
85 | 30 | snapshot.visible = true; | 31 | if (!isVideo) snapshot.visible = true; |
86 | 31 | visible = true; | 32 | visible = true; |
87 | 32 | } | 33 | } |
88 | 33 | 34 | ||
89 | @@ -46,54 +47,70 @@ | |||
90 | 46 | asynchronous: true | 47 | asynchronous: true |
91 | 47 | sourceComponent: Component { | 48 | sourceComponent: Component { |
92 | 48 | Item { | 49 | Item { |
141 | 49 | CircleButton { | 50 | VideoReview { |
142 | 50 | id: retryButton | 51 | id: videoReview |
143 | 51 | objectName: "retryButton" | 52 | anchors.fill: parent |
144 | 52 | 53 | bottomMargin: buttons.height | |
145 | 53 | anchors { | 54 | videoPath: mediaPath |
146 | 54 | right: validateButton.left | 55 | visible: isVideo |
147 | 55 | rightMargin: units.gu(7.5) | 56 | } |
148 | 56 | bottom: parent.bottom | 57 | |
149 | 57 | bottomMargin: units.gu(6) | 58 | Item { |
150 | 58 | } | 59 | id: buttons |
151 | 59 | 60 | anchors.bottom: parent.bottom | |
152 | 60 | iconName: "reload" | 61 | anchors.left: parent.left |
153 | 61 | onClicked: viewFinderExportConfirmation.hide() | 62 | anchors.right: parent.right |
154 | 62 | } | 63 | height: childrenRect.height |
155 | 63 | 64 | ||
156 | 64 | CircleButton { | 65 | CircleButton { |
157 | 65 | id: validateButton | 66 | id: retryButton |
158 | 66 | objectName: "validateButton" | 67 | objectName: "retryButton" |
159 | 67 | 68 | ||
160 | 68 | width: units.gu(8) | 69 | anchors { |
161 | 69 | anchors { | 70 | right: validateButton.left |
162 | 70 | bottom: parent.bottom | 71 | rightMargin: units.gu(7.5) |
163 | 71 | bottomMargin: units.gu(5) | 72 | bottom: parent.bottom |
164 | 72 | horizontalCenter: parent.horizontalCenter | 73 | bottomMargin: units.gu(6) |
165 | 73 | } | 74 | } |
166 | 74 | 75 | ||
167 | 75 | iconName: "ok" | 76 | iconName: "reload" |
168 | 76 | onClicked: { | 77 | onClicked: viewFinderExportConfirmation.hide() |
169 | 77 | viewFinderExportConfirmation.hide(); | 78 | } |
170 | 78 | main.exportContent([mediaPath]); | 79 | |
171 | 79 | } | 80 | CircleButton { |
172 | 80 | } | 81 | id: validateButton |
173 | 81 | 82 | objectName: "validateButton" | |
174 | 82 | CircleButton { | 83 | |
175 | 83 | id: cancelButton | 84 | width: units.gu(8) |
176 | 84 | objectName: "cancelButton" | 85 | anchors { |
177 | 85 | 86 | bottom: parent.bottom | |
178 | 86 | anchors { | 87 | bottomMargin: units.gu(5) |
179 | 87 | left: validateButton.right | 88 | horizontalCenter: parent.horizontalCenter |
180 | 88 | leftMargin: units.gu(7.5) | 89 | } |
181 | 89 | bottom: parent.bottom | 90 | |
182 | 90 | bottomMargin: units.gu(6) | 91 | iconName: "ok" |
183 | 91 | } | 92 | onClicked: { |
184 | 92 | 93 | viewFinderExportConfirmation.hide(); | |
185 | 93 | iconName: "close" | 94 | main.exportContent([mediaPath]); |
186 | 94 | onClicked: { | 95 | } |
187 | 95 | viewFinderExportConfirmation.hide(); | 96 | } |
188 | 96 | main.cancelExport(); | 97 | |
189 | 98 | CircleButton { | ||
190 | 99 | id: cancelButton | ||
191 | 100 | objectName: "cancelButton" | ||
192 | 101 | |||
193 | 102 | anchors { | ||
194 | 103 | left: validateButton.right | ||
195 | 104 | leftMargin: units.gu(7.5) | ||
196 | 105 | bottom: parent.bottom | ||
197 | 106 | bottomMargin: units.gu(6) | ||
198 | 107 | } | ||
199 | 108 | |||
200 | 109 | iconName: "close" | ||
201 | 110 | onClicked: { | ||
202 | 111 | viewFinderExportConfirmation.hide(); | ||
203 | 112 | main.cancelExport(); | ||
204 | 113 | } | ||
205 | 97 | } | 114 | } |
206 | 98 | } | 115 | } |
207 | 99 | } | 116 | } |
208 | 100 | 117 | ||
209 | === modified file 'ViewFinderOverlay.qml' | |||
210 | --- ViewFinderOverlay.qml 2015-12-08 15:14:04 +0000 | |||
211 | +++ ViewFinderOverlay.qml 2015-12-10 12:37:24 +0000 | |||
212 | @@ -667,7 +667,9 @@ | |||
213 | 667 | } | 667 | } |
214 | 668 | 668 | ||
215 | 669 | if (camera.captureMode == Camera.CaptureVideo) { | 669 | if (camera.captureMode == Camera.CaptureVideo) { |
217 | 670 | if (application.removableStoragePresent && settings.preferRemovableStorage) { | 670 | if (main.contentExportMode) { |
218 | 671 | camera.videoRecorder.outputLocation = application.temporaryLocation; | ||
219 | 672 | } else if (application.removableStoragePresent && settings.preferRemovableStorage) { | ||
220 | 671 | camera.videoRecorder.outputLocation = application.removableStorageVideosLocation; | 673 | camera.videoRecorder.outputLocation = application.removableStorageVideosLocation; |
221 | 672 | } else { | 674 | } else { |
222 | 673 | camera.videoRecorder.outputLocation = application.videosLocation; | 675 | camera.videoRecorder.outputLocation = application.videosLocation; |
223 | 674 | 676 | ||
224 | === modified file 'ViewFinderView.qml' | |||
225 | --- ViewFinderView.qml 2015-12-07 15:04:16 +0000 | |||
226 | +++ ViewFinderView.qml 2015-12-10 12:37:24 +0000 | |||
227 | @@ -20,6 +20,7 @@ | |||
228 | 20 | import QtMultimedia 5.0 | 20 | import QtMultimedia 5.0 |
229 | 21 | import CameraApp 0.1 | 21 | import CameraApp 0.1 |
230 | 22 | import QtGraphicalEffects 1.0 | 22 | import QtGraphicalEffects 1.0 |
231 | 23 | import Ubuntu.Content 0.1 | ||
232 | 23 | 24 | ||
233 | 24 | Item { | 25 | Item { |
234 | 25 | id: viewFinderView | 26 | id: viewFinderView |
235 | @@ -33,7 +34,6 @@ | |||
236 | 33 | signal photoTaken(string filePath) | 34 | signal photoTaken(string filePath) |
237 | 34 | signal videoShot(string filePath) | 35 | signal videoShot(string filePath) |
238 | 35 | 36 | ||
239 | 36 | |||
240 | 37 | onInViewChanged: decideCameraState() | 37 | onInViewChanged: decideCameraState() |
241 | 38 | Connections { | 38 | Connections { |
242 | 39 | target: viewFinderOverlay | 39 | target: viewFinderOverlay |
243 | @@ -58,7 +58,7 @@ | |||
244 | 58 | } | 58 | } |
245 | 59 | } | 59 | } |
246 | 60 | 60 | ||
248 | 61 | Camera { | 61 | property Camera camera: Camera { |
249 | 62 | id: camera | 62 | id: camera |
250 | 63 | captureMode: Camera.CaptureStillImage | 63 | captureMode: Camera.CaptureStillImage |
251 | 64 | cameraState: Camera.UnloadedState | 64 | cameraState: Camera.UnloadedState |
252 | @@ -100,8 +100,17 @@ | |||
253 | 100 | property alias currentZoom: camera.digitalZoom | 100 | property alias currentZoom: camera.digitalZoom |
254 | 101 | property alias maximumZoom: camera.maximumDigitalZoom | 101 | property alias maximumZoom: camera.maximumDigitalZoom |
255 | 102 | property bool switchInProgress: false | 102 | property bool switchInProgress: false |
257 | 103 | 103 | ||
258 | 104 | imageCapture { | 104 | imageCapture { |
259 | 105 | onReadyChanged: { | ||
260 | 106 | if (camera.imageCapture.ready && main.transfer) { | ||
261 | 107 | if (main.transfer.contentType === ContentType.Videos) { | ||
262 | 108 | viewFinderView.captureMode = Camera.CaptureVideo; | ||
263 | 109 | } else { | ||
264 | 110 | viewFinderView.captureMode = Camera.CaptureStillImage; | ||
265 | 111 | } | ||
266 | 112 | } | ||
267 | 113 | } | ||
268 | 105 | onCaptureFailed: { | 114 | onCaptureFailed: { |
269 | 106 | console.log("Capture failed for request " + requestId + ": " + message); | 115 | console.log("Capture failed for request " + requestId + ": " + message); |
270 | 107 | } | 116 | } |
271 | @@ -124,16 +133,18 @@ | |||
272 | 124 | console.log("Picture saved as " + path); | 133 | console.log("Picture saved as " + path); |
273 | 125 | } | 134 | } |
274 | 126 | } | 135 | } |
276 | 127 | 136 | ||
277 | 128 | videoRecorder { | 137 | videoRecorder { |
278 | 129 | onRecorderStateChanged: { | 138 | onRecorderStateChanged: { |
279 | 130 | if (videoRecorder.recorderState === CameraRecorder.StoppedState) { | 139 | if (videoRecorder.recorderState === CameraRecorder.StoppedState) { |
280 | 131 | if (photoRollHint.necessary) { | ||
281 | 132 | photoRollHint.enable(); | ||
282 | 133 | } | ||
283 | 134 | metricVideos.increment() | 140 | metricVideos.increment() |
284 | 135 | viewFinderOverlay.visible = true; | 141 | viewFinderOverlay.visible = true; |
285 | 136 | viewFinderView.videoShot(videoRecorder.actualLocation); | 142 | viewFinderView.videoShot(videoRecorder.actualLocation); |
286 | 143 | if (main.contentExportMode) { | ||
287 | 144 | viewFinderExportConfirmation.confirmExport(videoRecorder.actualLocation); | ||
288 | 145 | } else if (photoRollHint.necessary) { | ||
289 | 146 | photoRollHint.enable(); | ||
290 | 147 | } | ||
291 | 137 | } | 148 | } |
292 | 138 | } | 149 | } |
293 | 139 | } | 150 | } |
294 | @@ -168,7 +179,7 @@ | |||
295 | 168 | angle: 180 | 179 | angle: 180 |
296 | 169 | } | 180 | } |
297 | 170 | } | 181 | } |
299 | 171 | 182 | ||
300 | 172 | transform: [ | 183 | transform: [ |
301 | 173 | Scale { | 184 | Scale { |
302 | 174 | id: viewFinderSwitcherScale | 185 | id: viewFinderSwitcherScale |
303 | @@ -185,11 +196,11 @@ | |||
304 | 185 | angle: 0 | 196 | angle: 0 |
305 | 186 | } | 197 | } |
306 | 187 | ] | 198 | ] |
309 | 188 | 199 | ||
310 | 189 | 200 | ||
311 | 190 | SequentialAnimation { | 201 | SequentialAnimation { |
312 | 191 | id: viewFinderSwitcherAnimation | 202 | id: viewFinderSwitcherAnimation |
314 | 192 | 203 | ||
315 | 193 | SequentialAnimation { | 204 | SequentialAnimation { |
316 | 194 | ParallelAnimation { | 205 | ParallelAnimation { |
317 | 195 | UbuntuNumberAnimation {target: viewFinderSwitcherScale; property: "xScale"; from: 1.0; to: 0.8; duration: UbuntuAnimation.BriskDuration ; easing: UbuntuAnimation.StandardEasing} | 206 | UbuntuNumberAnimation {target: viewFinderSwitcherScale; property: "xScale"; from: 1.0; to: 0.8; duration: UbuntuAnimation.BriskDuration ; easing: UbuntuAnimation.StandardEasing} |
318 | @@ -218,16 +229,16 @@ | |||
319 | 218 | } | 229 | } |
320 | 219 | } | 230 | } |
321 | 220 | } | 231 | } |
323 | 221 | 232 | ||
324 | 222 | VideoOutput { | 233 | VideoOutput { |
325 | 223 | id: viewFinder | 234 | id: viewFinder |
327 | 224 | 235 | ||
328 | 225 | x: 0 | 236 | x: 0 |
329 | 226 | y: -viewFinderGeometry.y | 237 | y: -viewFinderGeometry.y |
330 | 227 | width: parent.width | 238 | width: parent.width |
331 | 228 | height: parent.height | 239 | height: parent.height |
332 | 229 | source: camera | 240 | source: camera |
334 | 230 | 241 | ||
335 | 231 | /* This rotation need to be applied since the camera hardware in the | 242 | /* This rotation need to be applied since the camera hardware in the |
336 | 232 | Galaxy Nexus phone is mounted at an angle inside the device, so the video | 243 | Galaxy Nexus phone is mounted at an angle inside the device, so the video |
337 | 233 | feed is rotated too. | 244 | feed is rotated too. |
338 | @@ -245,7 +256,7 @@ | |||
339 | 245 | // may change. | 256 | // may change. |
340 | 246 | orientation = Screen.primaryOrientation === Qt.PortraitOrientation ? -90 : 0; | 257 | orientation = Screen.primaryOrientation === Qt.PortraitOrientation ? -90 : 0; |
341 | 247 | } | 258 | } |
343 | 248 | 259 | ||
344 | 249 | /* Convenience item tracking the real position and size of the real video feed. | 260 | /* Convenience item tracking the real position and size of the real video feed. |
345 | 250 | Having this helps since these values depend on a lot of rules: | 261 | Having this helps since these values depend on a lot of rules: |
346 | 251 | - the feed is automatically scaled to fit the viewfinder | 262 | - the feed is automatically scaled to fit the viewfinder |
347 | @@ -253,7 +264,7 @@ | |||
348 | 253 | - the resolution and aspect ratio of the feed changes depending on the active camera | 264 | - the resolution and aspect ratio of the feed changes depending on the active camera |
349 | 254 | The item is also separated in a component so it can be unit tested. | 265 | The item is also separated in a component so it can be unit tested. |
350 | 255 | */ | 266 | */ |
352 | 256 | 267 | ||
353 | 257 | transform: Rotation { | 268 | transform: Rotation { |
354 | 258 | origin.x: viewFinder.width / 2 | 269 | origin.x: viewFinder.width / 2 |
355 | 259 | origin.y: viewFinder.height / 2 | 270 | origin.y: viewFinder.height / 2 |
356 | @@ -434,5 +445,6 @@ | |||
357 | 434 | id: viewFinderExportConfirmation | 445 | id: viewFinderExportConfirmation |
358 | 435 | anchors.fill: parent | 446 | anchors.fill: parent |
359 | 436 | snapshot: snapshot | 447 | snapshot: snapshot |
360 | 448 | isVideo: main.transfer.contentType == ContentType.Videos | ||
361 | 437 | } | 449 | } |
362 | 438 | } | 450 | } |
363 | 439 | 451 | ||
364 | === modified file 'camera-app.qml' | |||
365 | --- camera-app.qml 2015-11-20 15:36:00 +0000 | |||
366 | +++ camera-app.qml 2015-12-10 12:37:24 +0000 | |||
367 | @@ -311,10 +311,17 @@ | |||
368 | 311 | target: ContentHub | 311 | target: ContentHub |
369 | 312 | onExportRequested: { | 312 | onExportRequested: { |
370 | 313 | viewSwitcher.switchToViewFinder(); | 313 | viewSwitcher.switchToViewFinder(); |
375 | 314 | if (transferContentType === ContentType.Videos) { | 314 | |
376 | 315 | viewFinderView.captureMode = Camera.CaptureVideo; | 315 | // The exportRequested event can arrive before or after the |
377 | 316 | } else { | 316 | // app is active, but setting the recording type before the |
378 | 317 | viewFinderView.captureMode = Camera.CaptureStillImage; | 317 | // capture becomes ready does not have any effect. |
379 | 318 | // See camera.imageCapture.onReadyChanged for the other case. | ||
380 | 319 | if (viewFinderView.camera.imageCapture.ready) { | ||
381 | 320 | if (transfer.contentType === ContentType.Videos) { | ||
382 | 321 | viewFinderView.captureMode = Camera.CaptureVideo; | ||
383 | 322 | } else { | ||
384 | 323 | viewFinderView.captureMode = Camera.CaptureStillImage; | ||
385 | 324 | } | ||
386 | 318 | } | 325 | } |
387 | 319 | main.transfer = transfer; | 326 | main.transfer = transfer; |
388 | 320 | } | 327 | } |
389 | 321 | 328 | ||
390 | === modified file 'camera-contenthub.json' | |||
391 | --- camera-contenthub.json 2014-07-31 18:41:17 +0000 | |||
392 | +++ camera-contenthub.json 2015-12-10 12:37:24 +0000 | |||
393 | @@ -1,5 +1,6 @@ | |||
394 | 1 | { | 1 | { |
395 | 2 | "source": [ | 2 | "source": [ |
397 | 3 | "pictures" | 3 | "pictures", |
398 | 4 | "videos" | ||
399 | 4 | ] | 5 | ] |
400 | 5 | } | 6 | } |
It's all good in fact. Well done