1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2013 Riverbank Computing Limited.
7## Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
8## All rights reserved.
9##
10## This file is part of the examples of PyQt.
11##
12## $QT_BEGIN_LICENSE:BSD$
13## You may use this file under the terms of the BSD license as follows:
14##
15## "Redistribution and use in source and binary forms, with or without
16## modification, are permitted provided that the following conditions are
17## met:
18##   * Redistributions of source code must retain the above copyright
19##     notice, this list of conditions and the following disclaimer.
20##   * Redistributions in binary form must reproduce the above copyright
21##     notice, this list of conditions and the following disclaimer in
22##     the documentation and/or other materials provided with the
23##     distribution.
24##   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
25##     the names of its contributors may be used to endorse or promote
26##     products derived from this software without specific prior written
27##     permission.
28##
29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
40## $QT_END_LICENSE$
41##
42#############################################################################
43
44
45from PyQt5.QtCore import QByteArray, qFuzzyCompare, Qt, QTimer
46from PyQt5.QtGui import QPalette, QPixmap
47from PyQt5.QtMultimedia import (QAudioEncoderSettings, QCamera,
48        QCameraImageCapture, QImageEncoderSettings, QMediaMetaData,
49        QMediaRecorder, QMultimedia, QVideoEncoderSettings)
50from PyQt5.QtWidgets import (QAction, QActionGroup, QApplication, QDialog,
51        QMainWindow, QMessageBox)
52
53from ui_camera import Ui_Camera
54from ui_imagesettings import Ui_ImageSettingsUi
55from ui_videosettings import Ui_VideoSettingsUi
56
57
58class ImageSettings(QDialog):
59
60    def __init__(self, imageCapture, parent=None):
61        super(ImageSettings, self).__init__(parent)
62
63        self.ui = Ui_ImageSettingsUi()
64        self.imagecapture = imageCapture
65
66        self.ui.setupUi(self)
67
68        self.ui.imageCodecBox.addItem("Default image format", "")
69        for codecName in self.imagecapture.supportedImageCodecs():
70            description = self.imagecapture.imageCodecDescription(codecName)
71            self.ui.imageCodecBox.addItem(codecName + ": " + description,
72                    codecName)
73
74        self.ui.imageQualitySlider.setRange(0, QMultimedia.VeryHighQuality)
75
76        self.ui.imageResolutionBox.addItem("Default resolution")
77        supportedResolutions, _ = self.imagecapture.supportedResolutions()
78        for resolution in supportedResolutions:
79            self.ui.imageResolutionBox.addItem(
80                    "%dx%d" % (resolution.width(), resolution.height()),
81                    resolution)
82
83    def imageSettings(self):
84        settings = self.imagecapture.encodingSettings()
85        settings.setCodec(self.boxValue(self.ui.imageCodecBox))
86        settings.setQuality(
87                QMultimedia.EncodingQuality(
88                        self.ui.imageQualitySlider.value()))
89        settings.setResolution(self.boxValue(self.ui.imageResolutionBox))
90
91        return settings
92
93    def setImageSettings(self, settings):
94        self.selectComboBoxItem(self.ui.imageCodecBox, settings.codec())
95        self.selectComboBoxItem(self.ui.imageResolutionBox,
96                settings.resolution())
97        self.ui.imageQualitySlider.setValue(settings.quality())
98
99    @staticmethod
100    def boxValue(box):
101        idx = box.currentIndex()
102        if idx == -1:
103            return None
104
105        return box.itemData(idx)
106
107    @staticmethod
108    def selectComboBoxItem(box, value):
109        for i in range(box.count()):
110            if box.itemData(i) == value:
111                box.setCurrentIndex(i)
112                break
113
114
115class VideoSettings(QDialog):
116
117    def __init__(self, mediaRecorder, parent=None):
118        super(VideoSettings, self).__init__(parent)
119
120        self.ui = Ui_VideoSettingsUi()
121        self.mediaRecorder = mediaRecorder
122
123        self.ui.setupUi(self)
124
125        self.ui.audioCodecBox.addItem("Default audio codec", "")
126        for codecName in self.mediaRecorder.supportedAudioCodecs():
127            description = self.mediaRecorder.audioCodecDescription(codecName)
128            self.ui.audioCodecBox.addItem(codecName + ": " + description,
129                    codecName)
130
131        supportedSampleRates, _ = self.mediaRecorder.supportedAudioSampleRates()
132        for sampleRate in supportedSampleRates:
133            self.ui.audioSampleRateBox.addItem(str(sampleRate), sampleRate)
134
135        self.ui.audioQualitySlider.setRange(0, QMultimedia.VeryHighQuality)
136
137        self.ui.videoCodecBox.addItem("Default video codec", "")
138        for codecName in self.mediaRecorder.supportedVideoCodecs():
139            description = self.mediaRecorder.videoCodecDescription(codecName)
140            self.ui.videoCodecBox.addItem(codecName + ": " + description,
141                    codecName)
142
143        self.ui.videoQualitySlider.setRange(0, QMultimedia.VeryHighQuality)
144
145        self.ui.videoResolutionBox.addItem("Default")
146        supportedResolutions, _ = self.mediaRecorder.supportedResolutions()
147        for resolution in supportedResolutions:
148            self.ui.videoResolutionBox.addItem(
149                    "%dx%d" % (resolution.width(), resolution.height()),
150                    resolution)
151
152        self.ui.videoFramerateBox.addItem("Default")
153        supportedFrameRates, _ = self.mediaRecorder.supportedFrameRates()
154        for rate in supportedFrameRates:
155            self.ui.videoFramerateBox.addItem("%0.2f" % rate, rate)
156
157        self.ui.containerFormatBox.addItem("Default container", "")
158        for format in self.mediaRecorder.supportedContainers():
159            self.ui.containerFormatBox.addItem(
160                    format + ":" + self.mediaRecorder.containerDescription(
161                            format),
162                    format)
163
164    def audioSettings(self):
165        settings = self.mediaRecorder.audioSettings()
166        settings.setCodec(self.boxValue(self.ui.audioCodecBox))
167        settings.setQuality(
168                QMultimedia.EncodingQuality(
169                        self.ui.audioQualitySlider.value()))
170        settings.setSampleRate(self.boxValue(self.ui.audioSampleRateBox))
171
172        return settings
173
174    def setAudioSettings(self, settings):
175        self.selectComboBoxItem(self.ui.audioCodecBox, settings.codec())
176        self.selectComboBoxItem(self.ui.audioSampleRateBox,
177                settings.sampleRate())
178        self.ui.audioQualitySlider.setValue(settings.quality())
179
180    def videoSettings(self):
181        settings = self.mediaRecorder.videoSettings()
182        settings.setCodec(self.boxValue(self.ui.videoCodecBox))
183        settings.setQuality(
184                QMultimedia.EncodingQuality(
185                        self.ui.videoQualitySlider.value()))
186        settings.setResolution(self.boxValue(self.ui.videoResolutionBox))
187        settings.setFrameRate(self.boxValue(self.ui.videoFramerateBox))
188
189        return settings
190
191    def setVideoSettings(self, settings):
192        self.selectComboBoxItem(self.ui.videoCodecBox, settings.codec())
193        self.selectComboBoxItem(self.ui.videoResolutionBox,
194                settings.resolution())
195        self.ui.videoQualitySlider.setValue(settings.quality())
196
197        for i in range(1, self.ui.videoFramerateBox.count()):
198            itemRate = self.ui.videoFramerateBox.itemData(i)
199            if qFuzzyCompare(itemRate, settings.frameRate()):
200                self.ui.videoFramerateBox.setCurrentIndex(i)
201                break
202
203    def format(self):
204        return self.boxValue(self.ui.containerFormatBox)
205
206    def setFormat(self, format):
207        self.selectComboBoxItem(self.ui.containerFormatBox, format)
208
209    @staticmethod
210    def boxValue(box):
211        idx = box.currentIndex()
212        if idx == -1:
213            return None
214
215        return box.itemData(idx)
216
217    @staticmethod
218    def selectComboBoxItem(box, value):
219        for i in range(box.count()):
220            if box.itemData(i) == value:
221                box.setCurrentIndex(i)
222                break
223
224
225class Camera(QMainWindow):
226
227    def __init__(self, parent=None):
228        super(Camera, self).__init__(parent)
229
230        self.ui = Ui_Camera()
231        self.camera = None
232        self.imageCapture = None
233        self.mediaRecorder = None
234        self.isCapturingImage = False
235        self.applicationExiting = False
236
237        self.imageSettings = QImageEncoderSettings()
238        self.audioSettings = QAudioEncoderSettings()
239        self.videoSettings = QVideoEncoderSettings()
240        self.videoContainerFormat = ''
241
242        self.ui.setupUi(self)
243
244        cameraDevice = QByteArray()
245
246        videoDevicesGroup = QActionGroup(self)
247        videoDevicesGroup.setExclusive(True)
248
249        for deviceName in QCamera.availableDevices():
250            description = QCamera.deviceDescription(deviceName)
251            videoDeviceAction = QAction(description, videoDevicesGroup)
252            videoDeviceAction.setCheckable(True)
253            videoDeviceAction.setData(deviceName)
254
255            if cameraDevice.isEmpty():
256                cameraDevice = deviceName
257                videoDeviceAction.setChecked(True)
258
259            self.ui.menuDevices.addAction(videoDeviceAction)
260
261        videoDevicesGroup.triggered.connect(self.updateCameraDevice)
262        self.ui.captureWidget.currentChanged.connect(self.updateCaptureMode)
263
264        self.ui.lockButton.hide()
265
266        self.setCamera(cameraDevice)
267
268    def setCamera(self, cameraDevice):
269        if cameraDevice.isEmpty():
270            self.camera = QCamera()
271        else:
272            self.camera = QCamera(cameraDevice)
273
274        self.camera.stateChanged.connect(self.updateCameraState)
275        self.camera.error.connect(self.displayCameraError)
276
277        self.mediaRecorder = QMediaRecorder(self.camera)
278        self.mediaRecorder.stateChanged.connect(self.updateRecorderState)
279
280        self.imageCapture = QCameraImageCapture(self.camera)
281
282        self.mediaRecorder.durationChanged.connect(self.updateRecordTime)
283        self.mediaRecorder.error.connect(self.displayRecorderError)
284
285        self.mediaRecorder.setMetaData(QMediaMetaData.Title, "Test Title")
286
287        self.ui.exposureCompensation.valueChanged.connect(
288                self.setExposureCompensation)
289
290        self.camera.setViewfinder(self.ui.viewfinder)
291
292        self.updateCameraState(self.camera.state())
293        self.updateLockStatus(self.camera.lockStatus(), QCamera.UserRequest)
294        self.updateRecorderState(self.mediaRecorder.state())
295
296        self.imageCapture.readyForCaptureChanged.connect(self.readyForCapture)
297        self.imageCapture.imageCaptured.connect(self.processCapturedImage)
298        self.imageCapture.imageSaved.connect(self.imageSaved)
299
300        self.camera.lockStatusChanged.connect(self.updateLockStatus)
301
302        self.ui.captureWidget.setTabEnabled(0,
303                self.camera.isCaptureModeSupported(QCamera.CaptureStillImage))
304        self.ui.captureWidget.setTabEnabled(1,
305                self.camera.isCaptureModeSupported(QCamera.CaptureVideo))
306
307        self.updateCaptureMode()
308        self.camera.start()
309
310    def keyPressEvent(self, event):
311        if event.isAutoRepeat():
312            return
313
314        if event.key() == Qt.Key_CameraFocus:
315            self.displayViewfinder()
316            self.camera.searchAndLock()
317            event.accept()
318        elif event.key() == Qt.Key_Camera:
319            if self.camera.captureMode() == QCamera.CaptureStillImage:
320                self.takeImage()
321            elif self.mediaRecorder.state() == QMediaRecorder.RecordingState:
322                self.stop()
323            else:
324                self.record()
325
326            event.accept()
327        else:
328            super(Camera, self).keyPressEvent(event)
329
330    def keyReleaseEvent(self, event):
331        if event.isAutoRepeat():
332            return
333
334        if event.key() == Qt.Key_CameraFocus:
335            self.camera.unlock()
336        else:
337            super(Camera, self).keyReleaseEvent(event)
338
339    def updateRecordTime(self):
340        msg = "Recorded %d sec" % (self.mediaRecorder.duration() // 1000)
341        self.ui.statusbar.showMessage(msg)
342
343    def processCapturedImage(self, requestId, img):
344        scaledImage = img.scaled(self.ui.viewfinder.size(), Qt.KeepAspectRatio,
345                Qt.SmoothTransformation)
346
347        self.ui.lastImagePreviewLabel.setPixmap(QPixmap.fromImage(scaledImage))
348
349        self.displayCapturedImage()
350        QTimer.singleShot(4000, self.displayViewfinder)
351
352    def configureCaptureSettings(self):
353        if self.camera.captureMode() == QCamera.CaptureStillImage:
354            self.configureImageSettings()
355        elif self.camera.captureMode() == QCamera.CaptureVideo:
356            self.configureVideoSettings()
357
358    def configureVideoSettings(self):
359        settingsDialog = VideoSettings(self.mediaRecorder)
360
361        settingsDialog.setAudioSettings(self.audioSettings)
362        settingsDialog.setVideoSettings(self.videoSettings)
363        settingsDialog.setFormat(self.videoContainerFormat)
364
365        if settingsDialog.exec_():
366            self.audioSettings = settingsDialog.audioSettings()
367            self.videoSettings = settingsDialog.videoSettings()
368            self.videoContainerFormat = settingsDialog.format()
369
370            self.mediaRecorder.setEncodingSettings(self.audioSettings,
371                    self.videoSettings, self.videoContainerFormat)
372
373    def configureImageSettings(self):
374        settingsDialog = ImageSettings(self.imageCapture)
375
376        settingsDialog.setImageSettings(self.imageSettings)
377
378        if settingsDialog.exec_():
379            self.imageSettings = settingsDialog.imageSettings()
380            self.imageCapture.setEncodingSettings(self.imageSettings)
381
382    def record(self):
383        self.mediaRecorder.record()
384        self.updateRecordTime()
385
386    def pause(self):
387        self.mediaRecorder.pause()
388
389    def stop(self):
390        self.mediaRecorder.stop()
391
392    def setMuted(self, muted):
393        self.mediaRecorder.setMuted(muted)
394
395    def toggleLock(self):
396        if self.camera.lockStatus() in (QCamera.Searching, QCamera.Locked):
397            self.camera.unlock()
398        elif self.camera.lockStatus() == QCamera.Unlocked:
399            self.camera.searchAndLock()
400
401    def updateLockStatus(self, status, reason):
402        indicationColor = Qt.black
403
404        if status == QCamera.Searching:
405            self.ui.statusbar.showMessage("Focusing...")
406            self.ui.lockButton.setText("Focusing...")
407            indicationColor = Qt.yellow
408        elif status == QCamera.Locked:
409            self.ui.lockButton.setText("Unlock")
410            self.ui.statusbar.showMessage("Focused", 2000)
411            indicationColor = Qt.darkGreen
412        elif status == QCamera.Unlocked:
413            self.ui.lockButton.setText("Focus")
414
415            if reason == QCamera.LockFailed:
416                self.ui.statusbar.showMessage("Focus Failed", 2000)
417                indicationColor = Qt.red
418
419        palette = self.ui.lockButton.palette()
420        palette.setColor(QPalette.ButtonText, indicationColor)
421        self.ui.lockButton.setPalette(palette)
422
423    def takeImage(self):
424        self.isCapturingImage = True
425        self.imageCapture.capture()
426
427    def startCamera(self):
428        self.camera.start()
429
430    def stopCamera(self):
431        self.camera.stop()
432
433    def updateCaptureMode(self):
434        tabIndex = self.ui.captureWidget.currentIndex()
435        captureMode = QCamera.CaptureStillImage if tabIndex == 0 else QCamera.CaptureVideo
436
437        if self.camera.isCaptureModeSupported(captureMode):
438            self.camera.setCaptureMode(captureMode)
439
440    def updateCameraState(self, state):
441        if state == QCamera.ActiveState:
442            self.ui.actionStartCamera.setEnabled(False)
443            self.ui.actionStopCamera.setEnabled(True)
444            self.ui.captureWidget.setEnabled(True)
445            self.ui.actionSettings.setEnabled(True)
446        elif state in (QCamera.UnloadedState, QCamera.LoadedState):
447            self.ui.actionStartCamera.setEnabled(True)
448            self.ui.actionStopCamera.setEnabled(False)
449            self.ui.captureWidget.setEnabled(False)
450            self.ui.actionSettings.setEnabled(False)
451
452    def updateRecorderState(self, state):
453        if state == QMediaRecorder.StoppedState:
454            self.ui.recordButton.setEnabled(True)
455            self.ui.pauseButton.setEnabled(True)
456            self.ui.stopButton.setEnabled(False)
457        elif state == QMediaRecorder.PausedState:
458            self.ui.recordButton.setEnabled(True)
459            self.ui.pauseButton.setEnabled(False)
460            self.ui.stopButton.setEnabled(True)
461        elif state == QMediaRecorder.RecordingState:
462            self.ui.recordButton.setEnabled(False)
463            self.ui.pauseButton.setEnabled(True)
464            self.ui.stopButton.setEnabled(True)
465
466    def setExposureCompensation(self, index):
467        self.camera.exposure().setExposureCompensation(index * 0.5)
468
469    def displayRecorderError(self):
470        QMessageBox.warning(self, "Capture error",
471                self.mediaRecorder.errorString())
472
473    def displayCameraError(self):
474        QMessageBox.warning(self, "Camera error", self.camera.errorString())
475
476    def updateCameraDevice(self, action):
477        self.setCamera(action.data())
478
479    def displayViewfinder(self):
480        self.ui.stackedWidget.setCurrentIndex(0)
481
482    def displayCapturedImage(self):
483        self.ui.stackedWidget.setCurrentIndex(1)
484
485    def readyForCapture(self, ready):
486        self.ui.takeImageButton.setEnabled(ready)
487
488    def imageSaved(self, id, fileName):
489        self.isCapturingImage = False
490
491        if self.applicationExiting:
492            self.close()
493
494    def closeEvent(self, event):
495        if self.isCapturingImage:
496            self.setEnabled(False)
497            self.applicationExiting = True
498            event.ignore()
499        else:
500            event.accept()
501
502
503if __name__ == '__main__':
504
505    import sys
506
507    app = QApplication(sys.argv)
508
509    camera = Camera()
510    camera.show()
511
512    sys.exit(app.exec_())
513