aboutsummaryrefslogtreecommitdiffstats
path: root/examples/multimedia/camera/camera.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/multimedia/camera/camera.py')
-rw-r--r--examples/multimedia/camera/camera.py493
1 files changed, 345 insertions, 148 deletions
diff --git a/examples/multimedia/camera/camera.py b/examples/multimedia/camera/camera.py
index bbcba0b02..fa379c807 100644
--- a/examples/multimedia/camera/camera.py
+++ b/examples/multimedia/camera/camera.py
@@ -1,172 +1,369 @@
-# Copyright (C) 2022 The Qt Company Ltd.
+# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 Multimedia Camera Example"""
-
import os
import sys
-from PySide6.QtCore import QDate, QDir, QStandardPaths, Qt, QUrl, Slot
-from PySide6.QtGui import QAction, QGuiApplication, QDesktopServices, QIcon
-from PySide6.QtGui import QImage, QPixmap
-from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel,
- QMainWindow, QPushButton, QTabWidget, QToolBar, QVBoxLayout, QWidget)
-from PySide6.QtMultimedia import (QCamera, QImageCapture,
- QCameraDevice, QMediaCaptureSession,
- QMediaDevices)
-from PySide6.QtMultimediaWidgets import QVideoWidget
-
-
-class ImageView(QWidget):
- def __init__(self, previewImage, fileName):
+from pathlib import Path
+
+from PySide6.QtMultimedia import (QAudioInput, QCamera, QCameraDevice,
+ QImageCapture, QMediaCaptureSession,
+ QMediaDevices, QMediaMetaData,
+ QMediaRecorder)
+from PySide6.QtWidgets import QDialog, QMainWindow, QMessageBox
+from PySide6.QtGui import QAction, QActionGroup, QIcon, QImage, QPixmap
+from PySide6.QtCore import QDateTime, QDir, QTimer, Qt, Slot, qWarning
+
+from metadatadialog import MetaDataDialog
+from imagesettings import ImageSettings
+from videosettings import VideoSettings, is_android
+
+if is_android or sys.platform == "darwin":
+ from PySide6.QtCore import QMicrophonePermission, QCameraPermission
+
+if is_android:
+ from ui_camera_mobile import Ui_Camera
+else:
+ from ui_camera import Ui_Camera
+
+
+class Camera(QMainWindow):
+ def __init__(self):
super().__init__()
- self._file_name = fileName
+ self._video_devices_group = None
+ self.m_devices = QMediaDevices()
+ self.m_imageCapture = None
+ self.m_captureSession = QMediaCaptureSession()
+ self.m_camera = None
+ self.m_mediaRecorder = None
+
+ self.m_isCapturingImage = False
+ self.m_applicationExiting = False
+ self.m_doImageCapture = True
+
+ self.m_metaDataDialog = None
+
+ self._ui = Ui_Camera()
+ self._ui.setupUi(self)
+ image = Path(__file__).parent / "shutter.svg"
+ self._ui.takeImageButton.setIcon(QIcon(os.fspath(image)))
+ if not is_android:
+ self._ui.actionAbout_Qt.triggered.connect(qApp.aboutQt) # noqa: F821
+
+ # disable all buttons by default
+ self.updateCameraActive(False)
+ self.readyForCapture(False)
+ self._ui.recordButton.setEnabled(False)
+ self._ui.pauseButton.setEnabled(False)
+ self._ui.stopButton.setEnabled(False)
+ self._ui.metaDataButton.setEnabled(False)
+
+ # try to actually initialize camera & mic
+ self.initialize()
+
+ @Slot()
+ def initialize(self):
+ if is_android or sys.platform == "darwin":
+ is_nuitka = "__compiled__" in globals()
+ if not is_nuitka and sys.platform == "darwin":
+ print("This example does not work on macOS when Python is run in interpreted mode."
+ "For this example to work on macOS, package the example using pyside6-deploy"
+ "For more information, read `Notes for Developer` in the documentation")
+ sys.exit(0)
+
+ # camera
+ cam_permission = QCameraPermission()
+ cam_permission_status = qApp.checkPermission(cam_permission) # noqa: F821
+ if cam_permission_status == Qt.PermissionStatus.Undetermined:
+ qApp.requestPermission(cam_permission, self, self.initialize) # noqa: F821
+ return
+ if cam_permission_status == Qt.PermissionStatus.Denied:
+ qWarning("Camera permission is not granted!")
+ return
+ elif cam_permission_status == Qt.PermissionStatus.Granted:
+ print("[AudioSource] Camera permission granted")
+
+ # microphone
+ microphone_permission = QMicrophonePermission()
+ microphone_permission_status = qApp.checkPermission(microphone_permission) # noqa: F821
+ if microphone_permission_status == Qt.PermissionStatus.Undetermined:
+ qApp.requestPermission(microphone_permission, self, self.initialize) # noqa: F821
+ return
+ if microphone_permission_status == Qt.PermissionStatus.Denied:
+ qWarning("Microphone permission is not granted!")
+ self.initializeErrorWindow()
+ return
+ elif microphone_permission_status == Qt.PermissionStatus.Granted:
+ print("[AudioSource] Microphone permission granted")
+
+ self.m_audioInput = QAudioInput()
+ self.m_captureSession.setAudioInput(self.m_audioInput)
+
+ # Camera devices
+
+ self._video_devices_group = QActionGroup(self)
+ self._video_devices_group.setExclusive(True)
+ self.updateCameras()
+ self.m_devices.videoInputsChanged.connect(self.updateCameras)
+
+ self._video_devices_group.triggered.connect(self.updateCameraDevice)
+ self._ui.captureWidget.currentChanged.connect(self.updateCaptureMode)
+
+ self._ui.metaDataButton.clicked.connect(self.showMetaDataDialog)
+ self._ui.exposureCompensation.valueChanged.connect(self.setExposureCompensation)
+
+ self.setCamera(QMediaDevices.defaultVideoInput())
+
+ @Slot(QCameraDevice)
+ def setCamera(self, cameraDevice):
+ self.m_camera = QCamera(cameraDevice)
+ self.m_captureSession.setCamera(self.m_camera)
+
+ self.m_camera.activeChanged.connect(self.updateCameraActive)
+ self.m_camera.errorOccurred.connect(self.displayCameraError)
+
+ if not self.m_mediaRecorder:
+ self.m_mediaRecorder = QMediaRecorder()
+ self.m_captureSession.setRecorder(self.m_mediaRecorder)
+ self.m_mediaRecorder.recorderStateChanged.connect(self.updateRecorderState)
+ self.m_mediaRecorder.durationChanged.connect(self.updateRecordTime)
+ self.m_mediaRecorder.errorChanged.connect(self.displayRecorderError)
+
+ if not self.m_imageCapture:
+ self.m_imageCapture = QImageCapture()
+ self.m_captureSession.setImageCapture(self.m_imageCapture)
+ self.m_imageCapture.readyForCaptureChanged.connect(self.readyForCapture)
+ self.m_imageCapture.imageCaptured.connect(self.processCapturedImage)
+ self.m_imageCapture.imageSaved.connect(self.imageSaved)
+ self.m_imageCapture.errorOccurred.connect(self.displayCaptureError)
+
+ self.m_captureSession.setVideoOutput(self._ui.viewfinder)
+
+ self.updateCameraActive(self.m_camera.isActive())
+ self.updateRecorderState(self.m_mediaRecorder.recorderState())
+ self.readyForCapture(self.m_imageCapture.isReadyForCapture())
+
+ self.updateCaptureMode()
+
+ self.m_camera.start()
+
+ def keyPressEvent(self, event):
+ if event.isAutoRepeat():
+ return
+
+ key = event.key()
+ if key == Qt.Key_CameraFocus:
+ self.displayViewfinder()
+ event.accept()
+ elif key == Qt.Key_Camera:
+ if self.m_doImageCapture:
+ self.takeImage()
+ else:
+ if self.m_mediaRecorder.recorderState() == QMediaRecorder.RecordingState:
+ self.stop()
+ else:
+ self.record()
+
+ event.accept()
+ else:
+ super().keyPressEvent(event)
+
+ @Slot()
+ def updateRecordTime(self):
+ d = self.m_mediaRecorder.duration() / 1000
+ self._ui.statusbar.showMessage(f"Recorded {d} sec")
+
+ @Slot(int, QImage)
+ def processCapturedImage(self, requestId, img):
+ scaled_image = img.scaled(self._ui.viewfinder.size(), Qt.KeepAspectRatio,
+ Qt.SmoothTransformation)
- main_layout = QVBoxLayout(self)
- self._image_label = QLabel()
- self._image_label.setPixmap(QPixmap.fromImage(previewImage))
- main_layout.addWidget(self._image_label)
+ self._ui.lastImagePreviewLabel.setPixmap(QPixmap.fromImage(scaled_image))
- top_layout = QHBoxLayout()
- self._file_name_label = QLabel(QDir.toNativeSeparators(fileName))
- self._file_name_label.setTextInteractionFlags(Qt.TextBrowserInteraction)
+ # Display captured image for 4 seconds.
+ self.displayCapturedImage()
+ QTimer.singleShot(4000, self.displayViewfinder)
- top_layout.addWidget(self._file_name_label)
- top_layout.addStretch()
- copy_button = QPushButton("Copy")
- copy_button.setToolTip("Copy file name to clipboard")
- top_layout.addWidget(copy_button)
- copy_button.clicked.connect(self.copy)
- launch_button = QPushButton("Launch")
- launch_button.setToolTip("Launch image viewer")
- top_layout.addWidget(launch_button)
- launch_button.clicked.connect(self.launch)
- main_layout.addLayout(top_layout)
+ @Slot()
+ def configureCaptureSettings(self):
+ if self.m_doImageCapture:
+ self.configureImageSettings()
+ else:
+ self.configureVideoSettings()
@Slot()
- def copy(self):
- QGuiApplication.clipboard().setText(self._file_name_label.text())
+ def configureVideoSettings(self):
+ settings_dialog = VideoSettings(self.m_mediaRecorder)
+
+ if settings_dialog.exec():
+ settings_dialog.apply_settings()
@Slot()
- def launch(self):
- QDesktopServices.openUrl(QUrl.fromLocalFile(self._file_name))
+ def configureImageSettings(self):
+ settings_dialog = ImageSettings(self.m_imageCapture)
+ if settings_dialog.exec():
+ settings_dialog.apply_image_settings()
-class MainWindow(QMainWindow):
- def __init__(self):
- super().__init__()
+ @Slot()
+ def record(self):
+ self.m_mediaRecorder.record()
+ self.updateRecordTime()
- self._capture_session = None
- self._camera = None
- self._camera_info = None
- self._image_capture = None
+ @Slot()
+ def pause(self):
+ self.m_mediaRecorder.pause()
- available_cameras = QMediaDevices.videoInputs()
- if available_cameras:
- self._camera_info = available_cameras[0]
- self._camera = QCamera(self._camera_info)
- self._camera.errorOccurred.connect(self._camera_error)
- self._image_capture = QImageCapture(self._camera)
- self._image_capture.imageCaptured.connect(self.image_captured)
- self._image_capture.imageSaved.connect(self.image_saved)
- self._image_capture.errorOccurred.connect(self._capture_error)
- self._capture_session = QMediaCaptureSession()
- self._capture_session.setCamera(self._camera)
- self._capture_session.setImageCapture(self._image_capture)
-
- self._current_preview = QImage()
-
- tool_bar = QToolBar()
- self.addToolBar(tool_bar)
-
- file_menu = self.menuBar().addMenu("&File")
- shutter_icon = QIcon(os.path.join(os.path.dirname(__file__),
- "shutter.svg"))
- self._take_picture_action = QAction(shutter_icon, "&Take Picture", self,
- shortcut="Ctrl+T",
- triggered=self.take_picture)
- self._take_picture_action.setToolTip("Take Picture")
- file_menu.addAction(self._take_picture_action)
- tool_bar.addAction(self._take_picture_action)
-
- exit_action = QAction(QIcon.fromTheme("application-exit"), "E&xit",
- self, shortcut="Ctrl+Q", triggered=self.close)
- file_menu.addAction(exit_action)
-
- about_menu = self.menuBar().addMenu("&About")
- about_qt_action = QAction("About &Qt", self, triggered=qApp.aboutQt)
- about_menu.addAction(about_qt_action)
-
- self._tab_widget = QTabWidget()
- self.setCentralWidget(self._tab_widget)
-
- self._camera_viewfinder = QVideoWidget()
- self._tab_widget.addTab(self._camera_viewfinder, "Viewfinder")
-
- if self._camera and self._camera.error() == QCamera.NoError:
- name = self._camera_info.description()
- self.setWindowTitle(f"PySide6 Camera Example ({name})")
- self.show_status_message(f"Starting: '{name}'")
- self._capture_session.setVideoOutput(self._camera_viewfinder)
- self._take_picture_action.setEnabled(self._image_capture.isReadyForCapture())
- self._image_capture.readyForCaptureChanged.connect(self._take_picture_action.setEnabled)
- self._camera.start()
+ @Slot()
+ def stop(self):
+ self.m_mediaRecorder.stop()
+
+ @Slot(bool)
+ def setMuted(self, muted):
+ self.m_captureSession.audioInput().setMuted(muted)
+
+ @Slot()
+ def takeImage(self):
+ self.m_isCapturingImage = True
+ self.m_imageCapture.captureToFile()
+
+ @Slot(int, QImageCapture.Error, str)
+ def displayCaptureError(self, id, error, errorString):
+ QMessageBox.warning(self, "Image Capture Error", errorString)
+ self.m_isCapturingImage = False
+
+ @Slot()
+ def startCamera(self):
+ self.m_camera.start()
+
+ @Slot()
+ def stopCamera(self):
+ self.m_camera.stop()
+
+ @Slot()
+ def updateCaptureMode(self):
+ tab_index = self._ui.captureWidget.currentIndex()
+ self.m_doImageCapture = (tab_index == 0)
+
+ @Slot(bool)
+ def updateCameraActive(self, active):
+ if active:
+ self._ui.actionStartCamera.setEnabled(False)
+ self._ui.actionStopCamera.setEnabled(True)
+ self._ui.captureWidget.setEnabled(True)
+ self._ui.actionSettings.setEnabled(True)
else:
- self.setWindowTitle("PySide6 Camera Example")
- self._take_picture_action.setEnabled(False)
- self.show_status_message("Camera unavailable")
+ self._ui.actionStartCamera.setEnabled(True)
+ self._ui.actionStopCamera.setEnabled(False)
+ self._ui.captureWidget.setEnabled(False)
+ self._ui.actionSettings.setEnabled(False)
+
+ @Slot(QMediaRecorder.RecorderState)
+ def updateRecorderState(self, state):
+ if state == QMediaRecorder.StoppedState:
+ self._ui.recordButton.setEnabled(True)
+ self._ui.pauseButton.setEnabled(True)
+ self._ui.stopButton.setEnabled(False)
+ self._ui.metaDataButton.setEnabled(True)
+ elif state == QMediaRecorder.PausedState:
+ self._ui.recordButton.setEnabled(True)
+ self._ui.pauseButton.setEnabled(False)
+ self._ui.stopButton.setEnabled(True)
+ self._ui.metaDataButton.setEnabled(False)
+ elif state == QMediaRecorder.RecordingState:
+ self._ui.recordButton.setEnabled(False)
+ self._ui.pauseButton.setEnabled(True)
+ self._ui.stopButton.setEnabled(True)
+ self._ui.metaDataButton.setEnabled(False)
+
+ @Slot(int)
+ def setExposureCompensation(self, index):
+ self.m_camera.setExposureCompensation(index * 0.5)
- def show_status_message(self, message):
- self.statusBar().showMessage(message, 5000)
+ @Slot()
+ def displayRecorderError(self):
+ if self.m_mediaRecorder.error() != QMediaRecorder.NoError:
+ QMessageBox.warning(self, "Capture Error",
+ self.m_mediaRecorder.errorString())
- def closeEvent(self, event):
- if self._camera and self._camera.isActive():
- self._camera.stop()
- event.accept()
-
- def next_image_file_name(self):
- pictures_location = QStandardPaths.writableLocation(QStandardPaths.PicturesLocation)
- date_string = QDate.currentDate().toString("yyyyMMdd")
- pattern = f"{pictures_location}/pyside6_camera_{date_string}_{{:03d}}.jpg"
- n = 1
- while True:
- result = pattern.format(n)
- if not os.path.exists(result):
- return result
- n = n + 1
- return None
-
- @Slot()
- def take_picture(self):
- self._current_preview = QImage()
- self._image_capture.captureToFile(self.next_image_file_name())
+ @Slot()
+ def displayCameraError(self):
+ if self.m_camera.error() != QCamera.NoError:
+ QMessageBox.warning(self, "Camera Error",
+ self.m_camera.errorString())
- @Slot(int, QImage)
- def image_captured(self, id, previewImage):
- self._current_preview = previewImage
+ @Slot(QAction)
+ def updateCameraDevice(self, action):
+ self.setCamera(QCameraDevice(action))
+
+ @Slot()
+ def displayViewfinder(self):
+ self._ui.stackedWidget.setCurrentIndex(0)
+
+ @Slot()
+ def displayCapturedImage(self):
+ self._ui.stackedWidget.setCurrentIndex(1)
+
+ @Slot(bool)
+ def readyForCapture(self, ready):
+ self._ui.takeImageButton.setEnabled(ready)
@Slot(int, str)
- def image_saved(self, id, fileName):
- index = self._tab_widget.count()
- image_view = ImageView(self._current_preview, fileName)
- self._tab_widget.addTab(image_view, f"Capture #{index}")
- self._tab_widget.setCurrentIndex(index)
+ def imageSaved(self, id, fileName):
+ f = QDir.toNativeSeparators(fileName)
+ self._ui.statusbar.showMessage(f"Captured \"{f}\"")
- @Slot(int, QImageCapture.Error, str)
- def _capture_error(self, id, error, error_string):
- print(error_string, file=sys.stderr)
- self.show_status_message(error_string)
-
- @Slot(QCamera.Error, str)
- def _camera_error(self, error, error_string):
- print(error_string, file=sys.stderr)
- self.show_status_message(error_string)
-
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
- main_win = MainWindow()
- available_geometry = main_win.screen().availableGeometry()
- main_win.resize(available_geometry.width() / 3, available_geometry.height() / 2)
- main_win.show()
- sys.exit(app.exec())
+ self.m_isCapturingImage = False
+ if self.m_applicationExiting:
+ self.close()
+
+ def closeEvent(self, event):
+ if self.m_isCapturingImage:
+ self.setEnabled(False)
+ self.m_applicationExiting = True
+ event.ignore()
+ else:
+ event.accept()
+
+ @Slot()
+ def updateCameras(self):
+ self._ui.menuDevices.clear()
+ available_cameras = QMediaDevices.videoInputs()
+ for cameraDevice in available_cameras:
+ video_device_action = QAction(cameraDevice.description(),
+ self._video_devices_group)
+ video_device_action.setCheckable(True)
+ video_device_action.setData(cameraDevice)
+ if cameraDevice == QMediaDevices.defaultVideoInput():
+ video_device_action.setChecked(True)
+
+ self._ui.menuDevices.addAction(video_device_action)
+
+ @Slot()
+ def showMetaDataDialog(self):
+ if not self.m_metaDataDialog:
+ self.m_metaDataDialog = MetaDataDialog(self)
+ self.m_metaDataDialog.setAttribute(Qt.WA_DeleteOnClose, False)
+ if self.m_metaDataDialog.exec() == QDialog.Accepted:
+ self.saveMetaData()
+
+ @Slot()
+ def saveMetaData(self):
+ data = QMediaMetaData()
+ for i in range(0, QMediaMetaData.NumMetaData):
+ val = self.m_metaDataDialog.m_metaDataFields[i].text()
+ if val:
+ key = QMediaMetaData.Key(i)
+ if key == QMediaMetaData.CoverArtImage:
+ cover_art = QImage(val)
+ data.insert(key, cover_art)
+ elif key == QMediaMetaData.ThumbnailImage:
+ thumbnail = QImage(val)
+ data.insert(key, thumbnail)
+ elif key == QMediaMetaData.Date:
+ date = QDateTime.fromString(val)
+ data.insert(key, date)
+ else:
+ data.insert(key, val)
+
+ self.m_mediaRecorder.setMetaData(data)