Camera Example#

Tags: Android

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.

Camera Screenshot

Download this example

# 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
import sys
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._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)

        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>
<?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="sizePolicy">
       <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
      <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="4" 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="3" column="0">
         <widget class="QLabel" name="label">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
            <horstretch>0</horstretch>
            <verstretch>0</verstretch>
           </sizepolicy>
          </property>
          <property name="text">
           <string>Exposure Compensation:</string>
          </property>
         </widget>
        </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>
          <property name="icon">
           <iconset>
            <normaloff>:/images/shutter.svg</normaloff>:/images/shutter.svg</iconset>
          </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">
         <layout class="QHBoxLayout" name="horizontalLayout">
          <item>
           <layout class="QVBoxLayout" name="verticalLayout">
            <item>
             <widget class="QPushButton" name="recordButton">
              <property name="text">
               <string>Record</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QPushButton" name="pauseButton">
              <property name="text">
               <string>Pause</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QPushButton" name="stopButton">
              <property name="text">
               <string>Stop</string>
              </property>
             </widget>
            </item>
           </layout>
          </item>
          <item>
           <layout class="QVBoxLayout" name="verticalLayout_2">
            <item>
             <spacer name="verticalSpacer">
              <property name="orientation">
               <enum>Qt::Vertical</enum>
              </property>
              <property name="sizeHint" stdset="0">
               <size>
                <width>20</width>
                <height>10</height>
               </size>
              </property>
             </spacer>
            </item>
            <item>
             <widget class="QPushButton" name="muteButton">
              <property name="text">
               <string>Mute</string>
              </property>
              <property name="checkable">
               <bool>true</bool>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QPushButton" name="metaDataButton">
              <property name="text">
               <string>Set metadata</string>
              </property>
              <property name="checkable">
               <bool>true</bool>
              </property>
             </widget>
            </item>
           </layout>
          </item>
         </layout>
        </item>
       </layout>
      </widget>
     </widget>
    </item>
    <item row="0" column="2">
     <widget class="QStackedWidget" name="stackedWidget">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
        <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">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
            <horstretch>0</horstretch>
            <verstretch>0</verstretch>
           </sizepolicy>
          </property>
         </widget>
        </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>22</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>
   <addaction name="menuFile"/>
   <addaction name="menuDevices"/>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
  <action name="actionExit">
   <property name="text">
    <string>Close</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>
 </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

import os
from PySide6.QtMultimedia import (QCameraFormat, QMediaFormat, QMediaRecorder,
                                  QVideoFrameFormat)
from PySide6.QtWidgets import QDialog

is_android = os.environ.get('ANDROID_ARGUMENT')

if is_android:
    from ui_videosettings_mobile import Ui_VideoSettingsUi
else:
    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>
<?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>329</width>
    <height>591</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Video Settings</string>
  </property>
  <layout class="QGridLayout" name="gridLayout_3">
   <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">
    <widget class="QGroupBox" name="groupBox_2">
     <property name="title">
      <string>Video</string>
     </property>
     <layout class="QGridLayout" name="gridLayout_2">
      <item row="2" column="0">
       <widget class="QLabel" name="label">
        <property name="text">
         <string>Frames per second:</string>
        </property>
       </widget>
      </item>
      <item row="6" column="0" colspan="2">
       <widget class="QComboBox" name="videoCodecBox"/>
      </item>
      <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="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="7" 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>
      <item row="3" column="0">
       <layout class="QHBoxLayout" name="horizontalLayout">
        <item>
         <widget class="QSpinBox" name="fpsSpinBox">
          <property name="minimum">
           <number>8</number>
          </property>
          <property name="maximum">
           <number>30</number>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QSlider" name="fpsSlider">
          <property name="orientation">
           <enum>Qt::Horizontal</enum>
          </property>
         </widget>
        </item>
       </layout>
      </item>
     </layout>
    </widget>
   </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>