Merge lp:~uriboni/camera-app/content-hub-video-source into lp:camera-app/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
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

Revision history for this message
Florian Boucault (fboucault) wrote :

It's all good in fact. Well done

review: Approve

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

Subscribers

People subscribed via source and target branches