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