Camera Example#
The Camera Example shows how to use the API to capture a still image or video.
The Camera Example demonstrates how you can use Qt Multimedia to implement some basic Camera functionality to take still images and record video clips with audio.
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
"""PySide6 port of the QtMultiMedia camera example from Qt v6.x"""
import sys
from PySide6.QtWidgets import QApplication
from camera import Camera
if __name__ == "__main__":
app = QApplication(sys.argv)
camera = Camera()
camera.show()
sys.exit(app.exec())
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
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
from ui_camera import Ui_Camera
from metadatadialog import MetaDataDialog
from imagesettings import ImageSettings
from videosettings import VideoSettings
class Camera(QMainWindow):
def __init__(self):
super().__init__()
self._video_devices_group = None
self.m_devices = QMediaDevices()
self.m_imageCapture = None
self.m_captureSession = QMediaCaptureSession()
self.m_camera = None
self.m_audioInput = QAudioInput()
self.m_captureSession.setAudioInput(self.m_audioInput)
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)))
self._ui.actionAbout_Qt.triggered.connect(qApp.aboutQt)
# 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._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)
self._ui.lastImagePreviewLabel.setPixmap(QPixmap.fromImage(scaled_image))
# Display captured image for 4 seconds.
self.displayCapturedImage()
QTimer.singleShot(4000, self.displayViewfinder)
@Slot()
def configureCaptureSettings(self):
if self.m_doImageCapture:
self.configureImageSettings()
else:
self.configureVideoSettings()
@Slot()
def configureVideoSettings(self):
settings_dialog = VideoSettings(self.m_mediaRecorder)
if settings_dialog.exec():
settings_dialog.apply_settings()
@Slot()
def configureImageSettings(self):
settings_dialog = ImageSettings(self.m_imageCapture)
if settings_dialog.exec():
settings_dialog.apply_image_settings()
@Slot()
def record(self):
self.m_mediaRecorder.record()
self.updateRecordTime()
@Slot()
def pause(self):
self.m_mediaRecorder.pause()
@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._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)
@Slot()
def displayRecorderError(self):
if self.m_mediaRecorder.error() != QMediaRecorder.NoError:
QMessageBox.warning(self, "Capture Error",
self.m_mediaRecorder.errorString())
@Slot()
def displayCameraError(self):
if self.m_camera.error() != QCamera.NoError:
QMessageBox.warning(self, "Camera Error",
self.m_camera.errorString())
@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 imageSaved(self, id, fileName):
f = QDir.toNativeSeparators(fileName)
self._ui.statusbar.showMessage(f"Captured \"{f}\"")
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)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Camera</class>
<widget class="QMainWindow" name="Camera">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>668</width>
<height>429</height>
</rect>
</property>
<property name="windowTitle">
<string>Camera</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1" colspan="2">
<widget class="QTabWidget" name="captureWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Image</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>161</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="takeImageButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Capture Photo</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QSlider" name="exposureCompensation">
<property name="minimum">
<number>-4</number>
</property>
<property name="maximum">
<number>4</number>
</property>
<property name="pageStep">
<number>2</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Exposure Compensation:</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Video</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QPushButton" name="recordButton">
<property name="text">
<string>Record</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="pauseButton">
<property name="text">
<string>Pause</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="stopButton">
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>76</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<widget class="QPushButton" name="muteButton">
<property name="text">
<string>Mute</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QPushButton" name="metaDataButton">
<property name="text">
<string>Set metadata</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="QStackedWidget" name="stackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="palette">
<palette>
<active>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>145</red>
<green>145</green>
<blue>145</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>145</red>
<green>145</green>
<blue>145</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>145</red>
<green>145</green>
<blue>145</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>145</red>
<green>145</green>
<blue>145</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="viewfinderPage">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QVideoWidget" name="viewfinder" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="previewPage">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="lastImagePreviewLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>668</width>
<height>19</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionStartCamera"/>
<addaction name="actionStopCamera"/>
<addaction name="separator"/>
<addaction name="actionSettings"/>
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuDevices">
<property name="title">
<string>Devices</string>
</property>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout_Qt"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuDevices"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionExit">
<property name="text">
<string>Quit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="actionStartCamera">
<property name="text">
<string>Start Camera</string>
</property>
</action>
<action name="actionStopCamera">
<property name="text">
<string>Stop Camera</string>
</property>
</action>
<action name="actionSettings">
<property name="text">
<string>Change Settings</string>
</property>
</action>
<action name="actionAbout_Qt">
<property name="text">
<string>About Qt</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>QVideoWidget</class>
<extends>QWidget</extends>
<header>qvideowidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>recordButton</sender>
<signal>clicked()</signal>
<receiver>Camera</receiver>
<slot>record()</slot>
<hints>
<hint type="sourcelabel">
<x>647</x>
<y>149</y>
</hint>
<hint type="destinationlabel">
<x>61</x>
<y>238</y>
</hint>
</hints>
</connection>
<connection>
<sender>stopButton</sender>
<signal>clicked()</signal>
<receiver>Camera</receiver>
<slot>stop()</slot>
<hints>
<hint type="sourcelabel">
<x>647</x>
<y>225</y>
</hint>
<hint type="destinationlabel">
<x>140</x>
<y>236</y>
</hint>
</hints>
</connection>
<connection>
<sender>pauseButton</sender>
<signal>clicked()</signal>
<receiver>Camera</receiver>
<slot>pause()</slot>
<hints>
<hint type="sourcelabel">
<x>647</x>
<y>187</y>
</hint>
<hint type="destinationlabel">
<x>234</x>
<y>237</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionExit</sender>
<signal>triggered()</signal>
<receiver>Camera</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>154</x>
<y>130</y>
</hint>
</hints>
</connection>
<connection>
<sender>takeImageButton</sender>
<signal>clicked()</signal>
<receiver>Camera</receiver>
<slot>takeImage()</slot>
<hints>
<hint type="sourcelabel">
<x>625</x>
<y>132</y>
</hint>
<hint type="destinationlabel">
<x>603</x>
<y>169</y>
</hint>
</hints>
</connection>
<connection>
<sender>muteButton</sender>
<signal>toggled(bool)</signal>
<receiver>Camera</receiver>
<slot>setMuted(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>647</x>
<y>377</y>
</hint>
<hint type="destinationlabel">
<x>5</x>
<y>280</y>
</hint>
</hints>
</connection>
<connection>
<sender>exposureCompensation</sender>
<signal>valueChanged(int)</signal>
<receiver>Camera</receiver>
<slot>setExposureCompensation(int)</slot>
<hints>
<hint type="sourcelabel">
<x>559</x>
<y>367</y>
</hint>
<hint type="destinationlabel">
<x>665</x>
<y>365</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionSettings</sender>
<signal>triggered()</signal>
<receiver>Camera</receiver>
<slot>configureCaptureSettings()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>333</x>
<y>210</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionStartCamera</sender>
<signal>triggered()</signal>
<receiver>Camera</receiver>
<slot>startCamera()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>333</x>
<y>210</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionStopCamera</sender>
<signal>triggered()</signal>
<receiver>Camera</receiver>
<slot>stopCamera()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>333</x>
<y>210</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>record()</slot>
<slot>pause()</slot>
<slot>stop()</slot>
<slot>enablePreview(bool)</slot>
<slot>configureCaptureSettings()</slot>
<slot>takeImage()</slot>
<slot>startCamera()</slot>
<slot>toggleLock()</slot>
<slot>setMuted(bool)</slot>
<slot>stopCamera()</slot>
<slot>setExposureCompensation(int)</slot>
</slots>
</ui>
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtMultimedia import QImageCapture
from PySide6.QtWidgets import QDialog
from PySide6.QtCore import QSize
from ui_imagesettings import Ui_ImageSettingsUi
def box_value(box):
idx = box.currentIndex()
return None if idx == -1 else box.itemData(idx)
def select_combo_box_item(box, value):
idx = box.findData(value)
if idx != -1:
box.setCurrentIndex(idx)
class ImageSettings(QDialog):
def __init__(self, imageCapture, parent=None):
super().__init__(parent)
self.imagecapture = imageCapture
self._ui = Ui_ImageSettingsUi()
self._ui.setupUi(self)
# image codecs
self._ui.imageCodecBox.addItem("Default image format",
QImageCapture.UnspecifiedFormat)
supported_image_formats = QImageCapture.supportedFormats()
for f in supported_image_formats:
description = QImageCapture.fileFormatDescription(f)
name = QImageCapture.fileFormatName(f)
self._ui.imageCodecBox.addItem(f"{name} : {description}", f)
self._ui.imageQualitySlider.setRange(0, QImageCapture.VeryHighQuality.value)
self._ui.imageResolutionBox.addItem("Default Resolution", QSize())
camera = imageCapture.captureSession().camera()
supported_resolutions = camera.cameraDevice().photoResolutions()
for resolution in supported_resolutions:
w, h = resolution.width(), resolution.height()
self._ui.imageResolutionBox.addItem(f"{w}x{h}", resolution)
select_combo_box_item(self._ui.imageCodecBox, imageCapture.fileFormat())
select_combo_box_item(self._ui.imageResolutionBox, imageCapture.resolution())
self._ui.imageQualitySlider.setValue(imageCapture.quality().value)
def apply_image_settings(self):
self.imagecapture.setFileFormat(box_value(self._ui.imageCodecBox))
q = self._ui.imageQualitySlider.value()
self.imagecapture.setQuality(QImageCapture.Quality(q))
self.imagecapture.setResolution(box_value(self._ui.imageResolutionBox))
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ImageSettingsUi</class>
<widget class="QDialog" name="ImageSettingsUi">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>332</width>
<height>270</height>
</rect>
</property>
<property name="windowTitle">
<string>Image Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Image</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Resolution:</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QComboBox" name="imageResolutionBox"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Image Format:</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QComboBox" name="imageCodecBox"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Quality:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSlider" name="imageQualitySlider">
<property name="maximum">
<number>4</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>14</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ImageSettingsUi</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>322</x>
<y>272</y>
</hint>
<hint type="destinationlabel">
<x>44</x>
<y>230</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ImageSettingsUi</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>405</x>
<y>262</y>
</hint>
<hint type="destinationlabel">
<x>364</x>
<y>227</y>
</hint>
</hints>
</connection>
</connections>
</ui>
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtMultimedia import QMediaMetaData
from PySide6.QtWidgets import (QDialog, QDialogButtonBox, QFileDialog,
QFormLayout, QHBoxLayout, QLineEdit,
QPushButton, QScrollArea, QVBoxLayout, QWidget)
from PySide6.QtCore import QDateTime, QDir, Slot
IMAGE_FILTER = "Image Files (*.png *.jpg *.bmp)"
def default_value(key):
if key == QMediaMetaData.Title:
return "Qt Camera Example"
if key == QMediaMetaData.Author:
return "The Qt Company"
if key == QMediaMetaData.Date:
return QDateTime.currentDateTime().toString()
return ""
class MetaDataDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.m_metaDataFields = []
meta_data_layout = QFormLayout()
for i in range(0, QMediaMetaData.NumMetaData):
key = QMediaMetaData.Key(i)
label = QMediaMetaData.metaDataKeyToString(QMediaMetaData.Key(key))
line_edit = QLineEdit(default_value(key))
line_edit.setClearButtonEnabled(True)
self.m_metaDataFields.append(line_edit)
if key == QMediaMetaData.ThumbnailImage:
open_thumbnail = QPushButton("Open")
open_thumbnail.clicked.connect(self.open_thumbnail_image)
layout = QHBoxLayout()
layout.addWidget(line_edit)
layout.addWidget(open_thumbnail)
meta_data_layout.addRow(label, layout)
elif key == QMediaMetaData.CoverArtImage:
open_cover_art = QPushButton("Open")
open_cover_art.clicked.connect(self.open_cover_art_image)
layout = QHBoxLayout()
layout.addWidget(line_edit)
layout.addWidget(open_cover_art)
meta_data_layout.addRow(label, layout)
else:
meta_data_layout.addRow(label, line_edit)
viewport = QWidget()
viewport.setLayout(meta_data_layout)
scroll_area = QScrollArea()
scroll_area.setWidget(viewport)
dialog_layout = QVBoxLayout(self)
dialog_layout.addWidget(scroll_area)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
dialog_layout.addWidget(button_box)
self.setWindowTitle("Set Metadata")
self.resize(400, 300)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
@Slot()
def open_thumbnail_image(self):
dir = QDir.currentPath()
file_name = QFileDialog.getOpenFileName(self, "Open Image", dir,
IMAGE_FILTER)
if file_name:
i = QMediaMetaData.ThumbnailImage.value
self.m_metaDataFields[i].setText(file_name[0])
@Slot()
def open_cover_art_image(self):
dir = QDir.currentPath()
file_name = QFileDialog.getOpenFileName(self, "Open Image", dir,
IMAGE_FILTER)
if file_name:
i = QMediaMetaData.CoverArtImage.value
self.m_metaDataFields[i].setText(file_name[0])
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtMultimedia import (QCameraFormat, QMediaFormat, QMediaRecorder,
QVideoFrameFormat)
from PySide6.QtWidgets import QDialog
from ui_videosettings import Ui_VideoSettingsUi
def box_value(box):
idx = box.currentIndex()
return None if idx == -1 else box.itemData(idx)
def select_combo_box_item(box, value):
idx = box.findData(value)
if idx != -1:
box.setCurrentIndex(idx)
def to_formatted_string(cameraFormat):
pf = cameraFormat.pixelFormat()
format_name = QVideoFrameFormat.pixelFormatToString(pf)
w = cameraFormat.resolution().width()
h = cameraFormat.resolution().height()
min_rate = int(cameraFormat.minFrameRate())
max_rate = int(cameraFormat.maxFrameRate())
return f"{format_name} {w}x{h} {min_rate}-{max_rate}FPS"
class VideoSettings(QDialog):
def __init__(self, mediaRecorder, parent=None):
super().__init__(parent)
self._media_recorder = mediaRecorder
self.m_updatingFormats = False
self._ui = Ui_VideoSettingsUi()
self._ui.setupUi(self)
# sample rate:
audio_device = self._media_recorder.captureSession().audioInput().device()
self._ui.audioSampleRateBox.setRange(audio_device.minimumSampleRate(),
audio_device.maximumSampleRate())
# camera format
self._ui.videoFormatBox.addItem("Default camera format",
QCameraFormat())
camera = self._media_recorder.captureSession().camera()
video_formats = camera.cameraDevice().videoFormats()
for format in video_formats:
self._ui.videoFormatBox.addItem(to_formatted_string(format), format)
self._ui.videoFormatBox.currentIndexChanged.connect(self.video_format_changed)
self.set_fps_range(camera.cameraFormat())
self._ui.fpsSlider.valueChanged.connect(self._ui.fpsSpinBox.setValue)
self._ui.fpsSpinBox.valueChanged.connect(self._ui.fpsSlider.setValue)
self.update_formats_and_codecs()
self._ui.audioCodecBox.currentIndexChanged.connect(self.update_formats_and_codecs)
self._ui.videoCodecBox.currentIndexChanged.connect(self.update_formats_and_codecs)
self._ui.containerFormatBox.currentIndexChanged.connect(self.update_formats_and_codecs)
self._ui.qualitySlider.setRange(0, QMediaRecorder.VeryHighQuality.value)
format = self._media_recorder.mediaFormat()
select_combo_box_item(self._ui.containerFormatBox, format.fileFormat())
select_combo_box_item(self._ui.audioCodecBox, format.audioCodec())
select_combo_box_item(self._ui.videoCodecBox, format.videoCodec())
self._ui.qualitySlider.setValue(self._media_recorder.quality().value)
self._ui.audioSampleRateBox.setValue(self._media_recorder.audioSampleRate())
select_combo_box_item(self._ui.videoFormatBox, camera.cameraFormat())
self._ui.fpsSlider.setValue(self._media_recorder.videoFrameRate())
self._ui.fpsSpinBox.setValue(self._media_recorder.videoFrameRate())
def apply_settings(self):
format = QMediaFormat()
format.setFileFormat(box_value(self._ui.containerFormatBox))
format.setAudioCodec(box_value(self._ui.audioCodecBox))
format.setVideoCodec(box_value(self._ui.videoCodecBox))
self._media_recorder.setMediaFormat(format)
q = self._ui.qualitySlider.value()
self._media_recorder.setQuality(QMediaRecorder.Quality(q))
self._media_recorder.setAudioSampleRate(self._ui.audioSampleRateBox.value())
camera_format = box_value(self._ui.videoFormatBox)
self._media_recorder.setVideoResolution(camera_format.resolution())
self._media_recorder.setVideoFrameRate(self._ui.fpsSlider.value())
camera = self._media_recorder.captureSession().camera()
camera.setCameraFormat(camera_format)
def update_formats_and_codecs(self):
if self.m_updatingFormats:
return
self.m_updatingFormats = True
format = QMediaFormat()
if self._ui.containerFormatBox.count():
format.setFileFormat(box_value(self._ui.containerFormatBox))
if self._ui.audioCodecBox.count():
format.setAudioCodec(box_value(self._ui.audioCodecBox))
if self._ui.videoCodecBox.count():
format.setVideoCodec(box_value(self._ui.videoCodecBox))
current_index = 0
self._ui.audioCodecBox.clear()
self._ui.audioCodecBox.addItem("Default audio codec",
QMediaFormat.AudioCodec.Unspecified)
for codec in format.supportedAudioCodecs(QMediaFormat.Encode):
if codec == format.audioCodec():
current_index = self._ui.audioCodecBox.count()
desc = QMediaFormat.audioCodecDescription(codec)
self._ui.audioCodecBox.addItem(desc, codec)
self._ui.audioCodecBox.setCurrentIndex(current_index)
current_index = 0
self._ui.videoCodecBox.clear()
self._ui.videoCodecBox.addItem("Default video codec",
QMediaFormat.VideoCodec.Unspecified)
for codec in format.supportedVideoCodecs(QMediaFormat.Encode):
if codec == format.videoCodec():
current_index = self._ui.videoCodecBox.count()
desc = QMediaFormat.videoCodecDescription(codec)
self._ui.videoCodecBox.addItem(desc, codec)
self._ui.videoCodecBox.setCurrentIndex(current_index)
current_index = 0
self._ui.containerFormatBox.clear()
self._ui.containerFormatBox.addItem("Default file format",
QMediaFormat.UnspecifiedFormat)
for container in format.supportedFileFormats(QMediaFormat.Encode):
if container == format.fileFormat():
current_index = self._ui.containerFormatBox.count()
desc = QMediaFormat.fileFormatDescription(container)
self._ui.containerFormatBox.addItem(desc, container)
self._ui.containerFormatBox.setCurrentIndex(current_index)
self.m_updatingFormats = False
def video_format_changed(self):
camera_format = box_value(self._ui.videoFormatBox)
self.set_fps_range(camera_format)
def set_fps_range(self, format):
min_fr = format.minFrameRate()
max_fr = format.maxFrameRate()
self._ui.fpsSlider.setRange(min_fr, max_fr)
self._ui.fpsSpinBox.setRange(min_fr, max_fr)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VideoSettingsUi</class>
<widget class="QDialog" name="VideoSettingsUi">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>686</width>
<height>499</height>
</rect>
</property>
<property name="windowTitle">
<string>Video Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="4" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Video</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Camera Format</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QComboBox" name="videoCodecBox"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Framerate:</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Video Codec:</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QComboBox" name="videoFormatBox"/>
</item>
<item row="3" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSpinBox" name="fpsSpinBox"/>
</item>
<item>
<widget class="QSlider" name="fpsSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Audio</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Audio Codec:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="audioCodecBox"/>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Sample Rate:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="audioSampleRateBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Quality:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="qualitySlider">
<property name="maximum">
<number>4</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>File Format:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="containerFormatBox"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>VideoSettingsUi</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>322</x>
<y>272</y>
</hint>
<hint type="destinationlabel">
<x>44</x>
<y>230</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>VideoSettingsUi</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>405</x>
<y>262</y>
</hint>
<hint type="destinationlabel">
<x>364</x>
<y>227</y>
</hint>
</hints>
</connection>
</connections>
</ui>