aboutsummaryrefslogtreecommitdiffstats
path: root/examples/multimedia
diff options
context:
space:
mode:
Diffstat (limited to 'examples/multimedia')
-rw-r--r--examples/multimedia/audiooutput.py300
-rw-r--r--examples/multimedia/audiooutput/audiooutput.py276
-rw-r--r--examples/multimedia/audiooutput/audiooutput.pyproject3
-rw-r--r--examples/multimedia/audiooutput/doc/audiooutput.pngbin0 -> 5082 bytes
-rw-r--r--examples/multimedia/audiooutput/doc/audiooutput.rst14
-rw-r--r--examples/multimedia/audiosource/audiosource.py227
-rw-r--r--examples/multimedia/audiosource/audiosource.pyproject3
-rw-r--r--examples/multimedia/audiosource/doc/audiosource.pngbin0 -> 11897 bytes
-rw-r--r--examples/multimedia/audiosource/doc/audiosource.rst13
-rw-r--r--examples/multimedia/camera.py169
-rw-r--r--examples/multimedia/camera/camera.py369
-rw-r--r--examples/multimedia/camera/camera.pyproject12
-rw-r--r--examples/multimedia/camera/camera.ui497
-rw-r--r--examples/multimedia/camera/camera_mobile.ui504
-rw-r--r--examples/multimedia/camera/doc/camera.rst14
-rw-r--r--examples/multimedia/camera/doc/camera.webpbin0 -> 11666 bytes
-rw-r--r--examples/multimedia/camera/imagesettings.py56
-rw-r--r--examples/multimedia/camera/imagesettings.ui123
-rw-r--r--examples/multimedia/camera/main.py17
-rw-r--r--examples/multimedia/camera/metadatadialog.py86
-rw-r--r--examples/multimedia/camera/shutter.svg (renamed from examples/multimedia/shutter.svg)0
-rw-r--r--examples/multimedia/camera/ui_camera.py232
-rw-r--r--examples/multimedia/camera/ui_camera_mobile.py251
-rw-r--r--examples/multimedia/camera/ui_imagesettings.py94
-rw-r--r--examples/multimedia/camera/ui_videosettings.py178
-rw-r--r--examples/multimedia/camera/ui_videosettings_mobile.py176
-rw-r--r--examples/multimedia/camera/videosettings.py167
-rw-r--r--examples/multimedia/camera/videosettings.ui213
-rw-r--r--examples/multimedia/camera/videosettings_mobile.ui207
-rw-r--r--examples/multimedia/multimedia.pyproject3
-rw-r--r--examples/multimedia/player.py157
-rw-r--r--examples/multimedia/player/doc/player.pngbin0 -> 2804 bytes
-rw-r--r--examples/multimedia/player/doc/player.rst9
-rw-r--r--examples/multimedia/player/player.py194
-rw-r--r--examples/multimedia/player/player.pyproject3
-rw-r--r--examples/multimedia/screencapture/doc/screencapture.rst42
-rw-r--r--examples/multimedia/screencapture/doc/screencapture.webpbin0 -> 53592 bytes
-rw-r--r--examples/multimedia/screencapture/main.py20
-rw-r--r--examples/multimedia/screencapture/screencapture.pyproject3
-rw-r--r--examples/multimedia/screencapture/screencapturepreview.py162
-rw-r--r--examples/multimedia/screencapture/screenlistmodel.py38
-rw-r--r--examples/multimedia/screencapture/windowlistmodel.py30
42 files changed, 4233 insertions, 629 deletions
diff --git a/examples/multimedia/audiooutput.py b/examples/multimedia/audiooutput.py
deleted file mode 100644
index c6c7b9f93..000000000
--- a/examples/multimedia/audiooutput.py
+++ /dev/null
@@ -1,300 +0,0 @@
-
-#############################################################################
-##
-## Copyright (C) 2013 Riverbank Computing Limited.
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-"""PySide2 port of the multimedia/audiooutput example from Qt v5.x, originating from PyQt"""
-
-from math import pi, sin
-from struct import pack
-
-from PySide2.QtCore import QByteArray, QIODevice, Qt, QTimer, qWarning
-from PySide2.QtMultimedia import (QAudio, QAudioDeviceInfo, QAudioFormat,
- QAudioOutput)
-from PySide2.QtWidgets import (QApplication, QComboBox, QHBoxLayout, QLabel,
- QMainWindow, QPushButton, QSlider, QVBoxLayout, QWidget)
-
-
-class Generator(QIODevice):
-
- def __init__(self, format, durationUs, sampleRate, parent):
- super(Generator, self).__init__(parent)
-
- self.m_pos = 0
- self.m_buffer = QByteArray()
-
- self.generateData(format, durationUs, sampleRate)
-
- def start(self):
- self.open(QIODevice.ReadOnly)
-
- def stop(self):
- self.m_pos = 0
- self.close()
-
- def generateData(self, format, durationUs, sampleRate):
- pack_format = ''
-
- if format.sampleSize() == 8:
- if format.sampleType() == QAudioFormat.UnSignedInt:
- scaler = lambda x: ((1.0 + x) / 2 * 255)
- pack_format = 'B'
- elif format.sampleType() == QAudioFormat.SignedInt:
- scaler = lambda x: x * 127
- pack_format = 'b'
- elif format.sampleSize() == 16:
- if format.sampleType() == QAudioFormat.UnSignedInt:
- scaler = lambda x: (1.0 + x) / 2 * 65535
- pack_format = '<H' if format.byteOrder() == QAudioFormat.LittleEndian else '>H'
- elif format.sampleType() == QAudioFormat.SignedInt:
- scaler = lambda x: x * 32767
- pack_format = '<h' if format.byteOrder() == QAudioFormat.LittleEndian else '>h'
-
- assert(pack_format != '')
-
- channelBytes = format.sampleSize() // 8
- sampleBytes = format.channelCount() * channelBytes
-
- length = (format.sampleRate() * format.channelCount() * (format.sampleSize() // 8)) * durationUs // 100000
-
- self.m_buffer.clear()
- sampleIndex = 0
- factor = 2 * pi * sampleRate / format.sampleRate()
-
- while length != 0:
- x = sin((sampleIndex % format.sampleRate()) * factor)
- packed = pack(pack_format, int(scaler(x)))
-
- for _ in range(format.channelCount()):
- self.m_buffer.append(packed)
- length -= channelBytes
-
- sampleIndex += 1
-
- def readData(self, maxlen):
- data = QByteArray()
- total = 0
-
- while maxlen > total:
- chunk = min(self.m_buffer.size() - self.m_pos, maxlen - total)
- data.append(self.m_buffer.mid(self.m_pos, chunk))
- self.m_pos = (self.m_pos + chunk) % self.m_buffer.size()
- total += chunk
-
- return data.data()
-
- def writeData(self, data):
- return 0
-
- def bytesAvailable(self):
- return self.m_buffer.size() + super(Generator, self).bytesAvailable()
-
-
-class AudioTest(QMainWindow):
-
- PUSH_MODE_LABEL = "Enable push mode"
- PULL_MODE_LABEL = "Enable pull mode"
- SUSPEND_LABEL = "Suspend playback"
- RESUME_LABEL = "Resume playback"
-
- DurationSeconds = 1
- ToneSampleRateHz = 600
- DataSampleRateHz = 44100
-
- def __init__(self):
- super(AudioTest, self).__init__()
-
- self.m_device = QAudioDeviceInfo.defaultOutputDevice()
- self.m_output = None
-
- self.initializeWindow()
- self.initializeAudio()
-
- def initializeWindow(self):
- layout = QVBoxLayout()
-
- self.m_deviceBox = QComboBox()
- self.m_deviceBox.activated[int].connect(self.deviceChanged)
- for deviceInfo in QAudioDeviceInfo.availableDevices(QAudio.AudioOutput):
- self.m_deviceBox.addItem(deviceInfo.deviceName(), deviceInfo)
-
- layout.addWidget(self.m_deviceBox)
-
- self.m_modeButton = QPushButton()
- self.m_modeButton.clicked.connect(self.toggleMode)
- self.m_modeButton.setText(self.PUSH_MODE_LABEL)
-
- layout.addWidget(self.m_modeButton)
-
- self.m_suspendResumeButton = QPushButton(
- clicked=self.toggleSuspendResume)
- self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
-
- layout.addWidget(self.m_suspendResumeButton)
-
- volumeBox = QHBoxLayout()
- volumeLabel = QLabel("Volume:")
- self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100,
- singleStep=10)
- self.m_volumeSlider.valueChanged.connect(self.volumeChanged)
-
- volumeBox.addWidget(volumeLabel)
- volumeBox.addWidget(self.m_volumeSlider)
-
- layout.addLayout(volumeBox)
-
- window = QWidget()
- window.setLayout(layout)
-
- self.setCentralWidget(window)
-
- def initializeAudio(self):
- self.m_pullTimer = QTimer(self)
- self.m_pullTimer.timeout.connect(self.pullTimerExpired)
- self.m_pullMode = True
-
- self.m_format = QAudioFormat()
- self.m_format.setSampleRate(self.DataSampleRateHz)
- self.m_format.setChannelCount(1)
- self.m_format.setSampleSize(16)
- self.m_format.setCodec('audio/pcm')
- self.m_format.setByteOrder(QAudioFormat.LittleEndian)
- self.m_format.setSampleType(QAudioFormat.SignedInt)
-
- info = QAudioDeviceInfo(QAudioDeviceInfo.defaultOutputDevice())
- if not info.isFormatSupported(self.m_format):
- qWarning("Default format not supported - trying to use nearest")
- self.m_format = info.nearestFormat(self.m_format)
-
- self.m_generator = Generator(self.m_format,
- self.DurationSeconds * 1000000, self.ToneSampleRateHz, self)
-
- self.createAudioOutput()
-
- def createAudioOutput(self):
- self.m_audioOutput = QAudioOutput(self.m_device, self.m_format)
- self.m_audioOutput.notify.connect(self.notified)
- self.m_audioOutput.stateChanged.connect(self.handleStateChanged)
-
- self.m_generator.start()
- self.m_audioOutput.start(self.m_generator)
- self.m_volumeSlider.setValue(self.m_audioOutput.volume() * 100)
-
- def deviceChanged(self, index):
- self.m_pullTimer.stop()
- self.m_generator.stop()
- self.m_audioOutput.stop()
- self.m_device = self.m_deviceBox.itemData(index)
-
- self.createAudioOutput()
-
- def volumeChanged(self, value):
- if self.m_audioOutput is not None:
- self.m_audioOutput.setVolume(value / 100.0)
-
- def notified(self):
- qWarning("bytesFree = %d, elapsedUSecs = %d, processedUSecs = %d" % (
- self.m_audioOutput.bytesFree(),
- self.m_audioOutput.elapsedUSecs(),
- self.m_audioOutput.processedUSecs()))
-
- def pullTimerExpired(self):
- if self.m_audioOutput is not None and self.m_audioOutput.state() != QAudio.StoppedState:
- chunks = self.m_audioOutput.bytesFree() // self.m_audioOutput.periodSize()
- for _ in range(chunks):
- data = self.m_generator.read(self.m_audioOutput.periodSize())
- if data is None or len(data) != self.m_audioOutput.periodSize():
- break
-
- self.m_output.write(data)
-
- def toggleMode(self):
- self.m_pullTimer.stop()
- self.m_audioOutput.stop()
-
- if self.m_pullMode:
- self.m_modeButton.setText(self.PULL_MODE_LABEL)
- self.m_output = self.m_audioOutput.start()
- self.m_pullMode = False
- self.m_pullTimer.start(20)
- else:
- self.m_modeButton.setText(self.PUSH_MODE_LABEL)
- self.m_pullMode = True
- self.m_audioOutput.start(self.m_generator)
-
- self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
-
- def toggleSuspendResume(self):
- if self.m_audioOutput.state() == QAudio.SuspendedState:
- qWarning("status: Suspended, resume()")
- self.m_audioOutput.resume()
- self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
- elif self.m_audioOutput.state() == QAudio.ActiveState:
- qWarning("status: Active, suspend()")
- self.m_audioOutput.suspend()
- self.m_suspendResumeButton.setText(self.RESUME_LABEL)
- elif self.m_audioOutput.state() == QAudio.StoppedState:
- qWarning("status: Stopped, resume()")
- self.m_audioOutput.resume()
- self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
- elif self.m_audioOutput.state() == QAudio.IdleState:
- qWarning("status: IdleState")
-
- stateMap = {
- QAudio.ActiveState: "ActiveState",
- QAudio.SuspendedState: "SuspendedState",
- QAudio.StoppedState: "StoppedState",
- QAudio.IdleState: "IdleState"}
-
- def handleStateChanged(self, state):
- qWarning("state = " + self.stateMap.get(state, "Unknown"))
-
-
-if __name__ == '__main__':
-
- import sys
-
- app = QApplication(sys.argv)
- app.setApplicationName("Audio Output Test")
-
- audio = AudioTest()
- audio.show()
-
- sys.exit(app.exec_())
diff --git a/examples/multimedia/audiooutput/audiooutput.py b/examples/multimedia/audiooutput/audiooutput.py
new file mode 100644
index 000000000..06d52f68a
--- /dev/null
+++ b/examples/multimedia/audiooutput/audiooutput.py
@@ -0,0 +1,276 @@
+# Copyright (C) 2013 Riverbank Computing Limited.
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 port of the multimedia/audiooutput example from Qt v5.x, originating from PyQt"""
+
+import sys
+from math import pi, sin
+from struct import pack
+
+from PySide6.QtCore import (QByteArray, QIODevice, Qt, QSysInfo, QTimer,
+ qWarning, Slot)
+from PySide6.QtMultimedia import (QAudio, QAudioFormat,
+ QAudioSink, QMediaDevices)
+from PySide6.QtWidgets import (QApplication, QComboBox, QHBoxLayout, QLabel,
+ QMainWindow, QPushButton, QSlider,
+ QVBoxLayout, QWidget)
+
+
+class Generator(QIODevice):
+
+ def __init__(self, format, durationUs, sampleRate, parent):
+ super().__init__(parent)
+
+ self.m_pos = 0
+ self.m_buffer = QByteArray()
+
+ self.generate_data(format, durationUs, sampleRate)
+
+ def start(self):
+ self.open(QIODevice.ReadOnly)
+
+ def stop(self):
+ self.m_pos = 0
+ self.close()
+
+ def generate_data(self, fmt, durationUs, sampleRate):
+ pack_format = ''
+
+ sample_size = fmt.bytesPerSample() * 8
+ if sample_size == 8:
+ if fmt.sampleFormat() == QAudioFormat.UInt8:
+ scaler = lambda x: ((1.0 + x) / 2 * 255) # noqa: E731
+ pack_format = 'B'
+ elif fmt.sampleFormat() == QAudioFormat.Int16:
+ scaler = lambda x: x * 127 # noqa: E731
+ pack_format = 'b'
+ elif sample_size == 16:
+ little_endian = QSysInfo.ByteOrder == QSysInfo.LittleEndian
+ if fmt.sampleFormat() == QAudioFormat.UInt8:
+ scaler = lambda x: (1.0 + x) / 2 * 65535 # noqa: E731
+ pack_format = '<H' if little_endian else '>H'
+ elif fmt.sampleFormat() == QAudioFormat.Int16:
+ scaler = lambda x: x * 32767 # noqa: E731
+ pack_format = '<h' if little_endian else '>h'
+
+ assert pack_format != ''
+
+ channel_bytes = fmt.bytesPerSample()
+
+ length = (fmt.sampleRate() * fmt.channelCount() * channel_bytes) * durationUs // 100000
+
+ self.m_buffer.clear()
+ sample_index = 0
+ factor = 2 * pi * sampleRate / fmt.sampleRate()
+
+ while length != 0:
+ x = sin((sample_index % fmt.sampleRate()) * factor)
+ packed = pack(pack_format, int(scaler(x)))
+
+ for _ in range(fmt.channelCount()):
+ self.m_buffer.append(packed)
+ length -= channel_bytes
+
+ sample_index += 1
+
+ def readData(self, maxlen):
+ data = QByteArray()
+ total = 0
+
+ while maxlen > total:
+ chunk = min(self.m_buffer.size() - self.m_pos, maxlen - total)
+ data.append(self.m_buffer.mid(self.m_pos, chunk))
+ self.m_pos = (self.m_pos + chunk) % self.m_buffer.size()
+ total += chunk
+
+ return data.data()
+
+ def writeData(self, data):
+ return 0
+
+ def bytesAvailable(self):
+ return self.m_buffer.size() + super(Generator, self).bytesAvailable()
+
+
+class AudioTest(QMainWindow):
+
+ PUSH_MODE_LABEL = "Enable push mode"
+ PULL_MODE_LABEL = "Enable pull mode"
+ SUSPEND_LABEL = "Suspend playback"
+ RESUME_LABEL = "Resume playback"
+
+ DURATION_SECONDS = 1
+ TONE_SAMPLE_RATE_HZ = 600
+ DATA_SAMPLE_RATE_HZ = 44100
+
+ def __init__(self, devices):
+ super().__init__()
+
+ self.m_devices = devices
+ self.m_device = self.m_devices[0]
+ self.m_output = None
+
+ self.initialize_window()
+ self.initialize_audio()
+
+ def initialize_window(self):
+
+ central_widget = QWidget()
+ layout = QVBoxLayout(central_widget)
+
+ self.m_deviceBox = QComboBox()
+ self.m_deviceBox.activated[int].connect(self.device_changed)
+ for deviceInfo in self.m_devices:
+ self.m_deviceBox.addItem(deviceInfo.description(), deviceInfo)
+
+ layout.addWidget(self.m_deviceBox)
+
+ self.m_modeButton = QPushButton()
+ self.m_modeButton.clicked.connect(self.toggle_mode)
+ self.m_modeButton.setText(self.PUSH_MODE_LABEL)
+
+ layout.addWidget(self.m_modeButton)
+
+ self.m_suspendResumeButton = QPushButton(clicked=self.toggle_suspend_resume)
+ self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
+
+ layout.addWidget(self.m_suspendResumeButton)
+
+ volume_box = QHBoxLayout()
+ volume_label = QLabel("Volume:")
+ self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100, singleStep=10)
+ self.m_volumeSlider.valueChanged.connect(self.volume_changed)
+
+ volume_box.addWidget(volume_label)
+ volume_box.addWidget(self.m_volumeSlider)
+
+ layout.addLayout(volume_box)
+
+ self.setCentralWidget(central_widget)
+
+ def initialize_audio(self):
+ self.m_pullTimer = QTimer(self)
+ self.m_pullTimer.timeout.connect(self.pull_timer_expired)
+ self.m_pullMode = True
+
+ self.m_format = QAudioFormat()
+ self.m_format.setSampleRate(self.DATA_SAMPLE_RATE_HZ)
+ self.m_format.setChannelCount(1)
+ self.m_format.setSampleFormat(QAudioFormat.Int16)
+
+ info = self.m_devices[0]
+ if not info.isFormatSupported(self.m_format):
+ qWarning("Default format not supported - trying to use nearest")
+ self.m_format = info.nearestFormat(self.m_format)
+
+ self.m_generator = Generator(self.m_format, self.DURATION_SECONDS * 1000000,
+ self.TONE_SAMPLE_RATE_HZ, self)
+
+ self.create_audio_output()
+
+ def create_audio_output(self):
+ self.m_audioSink = QAudioSink(self.m_device, self.m_format)
+ self.m_audioSink.stateChanged.connect(self.handle_state_changed)
+
+ self.m_generator.start()
+ self.m_audioSink.start(self.m_generator)
+ self.m_volumeSlider.setValue(self.m_audioSink.volume() * 100)
+
+ def closeEvent(self, e):
+ self.stop()
+ e.accept()
+
+ def stop(self):
+ self.m_pullTimer.stop()
+ self.m_generator.stop()
+ self.m_audioSink.stop()
+
+ @Slot(int)
+ def device_changed(self, index):
+ self.stop()
+ self.m_device = self.m_deviceBox.itemData(index)
+
+ self.create_audio_output()
+
+ @Slot(int)
+ def volume_changed(self, value):
+ if self.m_audioSink is not None:
+ self.m_audioSink.setVolume(value / 100.0)
+
+ @Slot()
+ def notified(self):
+ bytes_free = self.m_audioSink.bytesFree()
+ elapsed = self.m_audioSink.elapsedUSecs()
+ processed = self.m_audioSink.processedUSecs()
+ qWarning(f"bytesFree = {bytes_free}, "
+ f"elapsedUSecs = {elapsed}, "
+ f"processedUSecs = {processed}")
+
+ @Slot()
+ def pull_timer_expired(self):
+ if self.m_audioSink is not None and self.m_audioSink.state() != QAudio.StoppedState:
+ bytes_free = self.m_audioSink.bytesFree()
+ data = self.m_generator.read(bytes_free)
+ if data:
+ self.m_output.write(data)
+
+ @Slot()
+ def toggle_mode(self):
+ self.m_pullTimer.stop()
+ self.m_audioSink.stop()
+
+ if self.m_pullMode:
+ self.m_modeButton.setText(self.PULL_MODE_LABEL)
+ self.m_output = self.m_audioSink.start()
+ self.m_pullMode = False
+ self.m_pullTimer.start(20)
+ else:
+ self.m_modeButton.setText(self.PUSH_MODE_LABEL)
+ self.m_pullMode = True
+ self.m_audioSink.start(self.m_generator)
+
+ self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
+
+ @Slot()
+ def toggle_suspend_resume(self):
+ if self.m_audioSink.state() == QAudio.SuspendedState:
+ qWarning("status: Suspended, resume()")
+ self.m_audioSink.resume()
+ self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
+ elif self.m_audioSink.state() == QAudio.ActiveState:
+ qWarning("status: Active, suspend()")
+ self.m_audioSink.suspend()
+ self.m_suspendResumeButton.setText(self.RESUME_LABEL)
+ elif self.m_audioSink.state() == QAudio.StoppedState:
+ qWarning("status: Stopped, resume()")
+ self.m_audioSink.resume()
+ self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
+ elif self.m_audioSink.state() == QAudio.IdleState:
+ qWarning("status: IdleState")
+
+ state_map = {
+ QAudio.ActiveState: "ActiveState",
+ QAudio.SuspendedState: "SuspendedState",
+ QAudio.StoppedState: "StoppedState",
+ QAudio.IdleState: "IdleState"}
+
+ @Slot("QAudio::State")
+ def handle_state_changed(self, state):
+ state = self.state_map.get(state, 'Unknown')
+ qWarning(f"state = {state}")
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ app.setApplicationName("Audio Output Test")
+
+ devices = QMediaDevices.audioOutputs()
+ if not devices:
+ print('No audio outputs found.', file=sys.stderr)
+ sys.exit(-1)
+
+ audio = AudioTest(devices)
+ audio.show()
+
+ sys.exit(app.exec())
diff --git a/examples/multimedia/audiooutput/audiooutput.pyproject b/examples/multimedia/audiooutput/audiooutput.pyproject
new file mode 100644
index 000000000..399a7c648
--- /dev/null
+++ b/examples/multimedia/audiooutput/audiooutput.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["audiooutput.py"]
+}
diff --git a/examples/multimedia/audiooutput/doc/audiooutput.png b/examples/multimedia/audiooutput/doc/audiooutput.png
new file mode 100644
index 000000000..d19f959cd
--- /dev/null
+++ b/examples/multimedia/audiooutput/doc/audiooutput.png
Binary files differ
diff --git a/examples/multimedia/audiooutput/doc/audiooutput.rst b/examples/multimedia/audiooutput/doc/audiooutput.rst
new file mode 100644
index 000000000..fac7e33e1
--- /dev/null
+++ b/examples/multimedia/audiooutput/doc/audiooutput.rst
@@ -0,0 +1,14 @@
+Audio Output Example
+====================
+
+.. tags:: Android
+
+Audio Output demonstrates the basic use cases of QAudioOutput.
+
+This example provides a tone generator to supply continuous audio playback. The
+first button allows pause and resume of the playback, and the second button
+allows toggling between push and pull modes of operation.
+
+.. image:: audiooutput.png
+ :width: 400
+ :alt: Audio Output Screenshot
diff --git a/examples/multimedia/audiosource/audiosource.py b/examples/multimedia/audiosource/audiosource.py
new file mode 100644
index 000000000..a78beb584
--- /dev/null
+++ b/examples/multimedia/audiosource/audiosource.py
@@ -0,0 +1,227 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""
+PySide6 port of Qt6 example examples/multimedia/audiosources
+
+Audio Devices demonstrates how to create a simple application to list and test
+the configuration for the various audio devices available on the target device
+or desktop PC.
+
+Note: This Python example is not fully complete as compared to its C++ counterpart.
+Only the push mode works at the moment. For the pull mode to work, the class
+QIODevice have python bindings that needs to be fixed.
+"""
+import os
+import sys
+from typing import Optional
+
+import PySide6
+from PySide6.QtCore import QByteArray, QMargins, Qt, Slot, qWarning
+from PySide6.QtGui import QPainter, QPalette
+from PySide6.QtMultimedia import QAudio, QAudioDevice, QAudioFormat, QAudioSource, QMediaDevices
+from PySide6.QtWidgets import (QApplication, QComboBox, QPushButton, QSlider, QVBoxLayout,
+ QWidget, QLabel)
+
+is_android = os.environ.get('ANDROID_ARGUMENT')
+
+if is_android or sys.platform == "darwin":
+ from PySide6.QtCore import QMicrophonePermission
+
+
+class AudioInfo:
+ def __init__(self, format: QAudioFormat):
+ super().__init__()
+ self.m_format = format
+ self.m_level = 0.0
+
+ def calculate_level(self, data: bytes, length: int) -> float:
+ channel_bytes: int = int(self.m_format.bytesPerSample())
+ sample_bytes: int = int(self.m_format.bytesPerFrame())
+ num_samples: int = int(length / sample_bytes)
+
+ maxValue: float = 0
+ m_offset: int = 0
+
+ for i in range(num_samples):
+ for j in range(self.m_format.channelCount()):
+ value = 0
+ if len(data) > m_offset:
+ data_sample = data[m_offset:]
+ value = self.m_format.normalizedSampleValue(data_sample)
+ maxValue = max(value, maxValue)
+ m_offset = m_offset + channel_bytes
+
+ return maxValue
+
+
+class RenderArea(QWidget):
+ def __init__(self, parent: Optional[PySide6.QtWidgets.QWidget] = None) -> None:
+ super().__init__(parent=parent)
+ self.m_level = 0
+ self.setBackgroundRole(QPalette.Base)
+ self.setAutoFillBackground(True)
+ self.setMinimumHeight(30)
+ self.setMinimumWidth(200)
+
+ def set_level(self, value):
+ self.m_level = value
+ self.update()
+
+ def paintEvent(self, event: PySide6.QtGui.QPaintEvent) -> None:
+ with QPainter(self) as painter:
+ painter.setPen(Qt.black)
+ frame = painter.viewport() - QMargins(10, 10, 10, 10)
+
+ painter.drawRect(frame)
+
+ if self.m_level == 0.0:
+ return
+
+ pos: int = round((frame.width() - 1) * self.m_level)
+ painter.fillRect(
+ frame.left() + 1, frame.top() + 1, pos, frame.height() - 1, Qt.red
+ )
+
+
+class InputTest(QWidget):
+ def __init__(self) -> None:
+ super().__init__()
+ self.m_devices = QMediaDevices(self)
+ self.m_pullMode = False
+ 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)
+ permission = QMicrophonePermission()
+ permission_status = qApp.checkPermission(permission) # noqa: F821
+ if permission_status == Qt.PermissionStatus.Undetermined:
+ qApp.requestPermission(permission, self, self.initialize) # noqa: F821
+ return
+ if permission_status == Qt.PermissionStatus.Denied:
+ qWarning("Microphone permission is not granted!")
+ self.initializeErrorWindow()
+ return
+ elif permission_status == Qt.PermissionStatus.Granted:
+ print("[AudioSource] Microphone permission granted")
+
+ self.initialize_window()
+ self.initialize_audio(QMediaDevices.defaultAudioInput())
+
+ def initialize_window(self):
+ self.layout = QVBoxLayout(self)
+
+ self.m_canvas = RenderArea(self)
+ self.layout.addWidget(self.m_canvas)
+
+ self.m_device_box = QComboBox(self)
+ default_device_info = QMediaDevices.defaultAudioInput()
+ self.m_device_box.addItem(
+ default_device_info.description(), default_device_info
+ )
+
+ for device_info in self.m_devices.audioInputs():
+ if device_info != default_device_info:
+ self.m_device_box.addItem(device_info.description(), device_info)
+
+ self.m_device_box.activated[int].connect(self.device_changed)
+ self.layout.addWidget(self.m_device_box)
+
+ self.m_volume_slider = QSlider(Qt.Horizontal, self)
+ self.m_volume_slider.setRange(0, 100)
+ self.m_volume_slider.setValue(100)
+ self.m_volume_slider.valueChanged.connect(self.slider_changed)
+ self.layout.addWidget(self.m_volume_slider)
+
+ self.m_mode_button = QPushButton(self)
+ self.m_mode_button.clicked.connect(self.toggle_mode)
+ self.layout.addWidget(self.m_mode_button)
+
+ self.m_suspend_resume_button = QPushButton(self)
+ self.m_suspend_resume_button.clicked.connect(self.toggle_suspend)
+ self.layout.addWidget(self.m_suspend_resume_button)
+
+ def initializeErrorWindow(self):
+ self.layout = QVBoxLayout(self)
+ error_label = QLabel(self.tr("Microphone permission is not granted!"))
+ error_label.setWordWrap(True)
+ error_label.setAlignment(Qt.AlignCenter)
+ self.layout.addWidget(error_label)
+
+ def initialize_audio(self, device_info: QAudioDevice):
+ format = QAudioFormat()
+ format.setSampleRate(8000)
+ format.setChannelCount(1)
+ format.setSampleFormat(QAudioFormat.Int16)
+
+ self.m_audio_info = AudioInfo(format)
+
+ self.m_audio_input = QAudioSource(device_info, format)
+ initial_volume = QAudio.convertVolume(
+ self.m_audio_input.volume(),
+ QAudio.LinearVolumeScale,
+ QAudio.LogarithmicVolumeScale,
+ )
+ self.m_volume_slider.setValue(int(round(initial_volume * 100)))
+ self.toggle_mode()
+
+ @Slot()
+ def toggle_mode(self):
+ self.m_audio_input.stop()
+ self.toggle_suspend()
+
+ self.m_mode_button.setText("Enable pull mode")
+ io = self.m_audio_input.start()
+
+ def push_mode_slot():
+ len = self.m_audio_input.bytesAvailable()
+ buffer_size = 4096
+ if len > buffer_size:
+ len = buffer_size
+ buffer: QByteArray = io.read(len)
+ if len > 0:
+ level = self.m_audio_info.calculate_level(buffer, len)
+ self.m_canvas.set_level(level)
+
+ io.readyRead.connect(push_mode_slot)
+
+ @Slot()
+ def toggle_suspend(self):
+ # toggle suspend/resume
+ state = self.m_audio_input.state()
+ if (state == QAudio.SuspendedState) or (state == QAudio.StoppedState):
+ self.m_audio_input.resume()
+ self.m_suspend_resume_button.setText("Suspend recording")
+ elif state == QAudio.ActiveState:
+ self.m_audio_input.suspend()
+ self.m_suspend_resume_button.setText("Resume recording")
+ # else no-op
+
+ @Slot(int)
+ def device_changed(self, index):
+ self.m_audio_input.stop()
+ self.m_audio_input.disconnect(self)
+ self.initialize_audio(self.m_device_box.itemData(index))
+
+ @Slot(int)
+ def slider_changed(self, value):
+ linearVolume = QAudio.convertVolume(
+ value / float(100), QAudio.LogarithmicVolumeScale, QAudio.LinearVolumeScale
+ )
+
+ self.m_audio_input.setVolume(linearVolume)
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ app.setApplicationName("Audio Sources Example")
+ input = InputTest()
+ input.show()
+ sys.exit(app.exec())
diff --git a/examples/multimedia/audiosource/audiosource.pyproject b/examples/multimedia/audiosource/audiosource.pyproject
new file mode 100644
index 000000000..c09e77303
--- /dev/null
+++ b/examples/multimedia/audiosource/audiosource.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["audiosource.py"]
+}
diff --git a/examples/multimedia/audiosource/doc/audiosource.png b/examples/multimedia/audiosource/doc/audiosource.png
new file mode 100644
index 000000000..cac183b75
--- /dev/null
+++ b/examples/multimedia/audiosource/doc/audiosource.png
Binary files differ
diff --git a/examples/multimedia/audiosource/doc/audiosource.rst b/examples/multimedia/audiosource/doc/audiosource.rst
new file mode 100644
index 000000000..3a247c503
--- /dev/null
+++ b/examples/multimedia/audiosource/doc/audiosource.rst
@@ -0,0 +1,13 @@
+Audio Source Example
+====================
+
+.. tags:: Android
+
+A Python application that demonstrates the analogous example in C++
+`Audio Source Example <https://doc-snapshots.qt.io/qt6-dev/qtmultimedia-multimedia-audiosource-example.html>`_
+
+
+.. image:: audiosource.png
+ :width: 400
+ :alt: audiosource example
+
diff --git a/examples/multimedia/camera.py b/examples/multimedia/camera.py
deleted file mode 100644
index 5d069d5cf..000000000
--- a/examples/multimedia/camera.py
+++ /dev/null
@@ -1,169 +0,0 @@
-
-#############################################################################
-##
-## Copyright (C) 2017 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-"""PySide2 Multimedia Camera Example"""
-
-import os, sys
-from PySide2.QtCore import QDate, QDir, QStandardPaths, Qt, QUrl
-from PySide2.QtGui import QClipboard, QGuiApplication, QDesktopServices, QIcon
-from PySide2.QtGui import QImage, QPixmap
-from PySide2.QtWidgets import (QAction, qApp, QApplication, QHBoxLayout, QLabel,
- QMainWindow, QPushButton, QTabWidget, QToolBar, QVBoxLayout, QWidget)
-from PySide2.QtMultimedia import QCamera, QCameraImageCapture, QCameraInfo
-from PySide2.QtMultimediaWidgets import QCameraViewfinder
-
-class ImageView(QWidget):
- def __init__(self, previewImage, fileName):
- super(ImageView, self).__init__()
-
- self.fileName = fileName
-
- mainLayout = QVBoxLayout(self)
- self.imageLabel = QLabel()
- self.imageLabel.setPixmap(QPixmap.fromImage(previewImage))
- mainLayout.addWidget(self.imageLabel)
-
- topLayout = QHBoxLayout()
- self.fileNameLabel = QLabel(QDir.toNativeSeparators(fileName))
- self.fileNameLabel.setTextInteractionFlags(Qt.TextBrowserInteraction)
-
- topLayout.addWidget(self.fileNameLabel)
- topLayout.addStretch()
- copyButton = QPushButton("Copy")
- copyButton.setToolTip("Copy file name to clipboard")
- topLayout.addWidget(copyButton)
- copyButton.clicked.connect(self.copy)
- launchButton = QPushButton("Launch")
- launchButton.setToolTip("Launch image viewer")
- topLayout.addWidget(launchButton)
- launchButton.clicked.connect(self.launch)
- mainLayout.addLayout(topLayout)
-
- def copy(self):
- QGuiApplication.clipboard().setText(self.fileNameLabel.text())
-
- def launch(self):
- QDesktopServices.openUrl(QUrl.fromLocalFile(self.fileName))
-
-class MainWindow(QMainWindow):
- def __init__(self):
- super(MainWindow, self).__init__()
-
- self.cameraInfo = QCameraInfo.defaultCamera()
- self.camera = QCamera(self.cameraInfo)
- self.camera.setCaptureMode(QCamera.CaptureStillImage)
- self.imageCapture = QCameraImageCapture(self.camera)
- self.imageCapture.imageCaptured.connect(self.imageCaptured)
- self.imageCapture.imageSaved.connect(self.imageSaved)
- self.currentPreview = QImage()
-
- toolBar = QToolBar()
- self.addToolBar(toolBar)
-
- fileMenu = self.menuBar().addMenu("&File")
- shutterIcon = QIcon(os.path.join(os.path.dirname(__file__),
- "shutter.svg"))
- self.takePictureAction = QAction(shutterIcon, "&Take Picture", self,
- shortcut="Ctrl+T",
- triggered=self.takePicture)
- self.takePictureAction.setToolTip("Take Picture")
- fileMenu.addAction(self.takePictureAction)
- toolBar.addAction(self.takePictureAction)
-
- exitAction = QAction(QIcon.fromTheme("application-exit"), "E&xit",
- self, shortcut="Ctrl+Q", triggered=self.close)
- fileMenu.addAction(exitAction)
-
- aboutMenu = self.menuBar().addMenu("&About")
- aboutQtAction = QAction("About &Qt", self, triggered=qApp.aboutQt)
- aboutMenu.addAction(aboutQtAction)
-
- self.tabWidget = QTabWidget()
- self.setCentralWidget(self.tabWidget)
-
- self.cameraViewfinder = QCameraViewfinder()
- self.camera.setViewfinder(self.cameraViewfinder)
- self.tabWidget.addTab(self.cameraViewfinder, "Viewfinder")
-
- if self.camera.status() != QCamera.UnavailableStatus:
- name = self.cameraInfo.description()
- self.setWindowTitle("PySide2 Camera Example (" + name + ")")
- self.statusBar().showMessage("Starting: '" + name + "'", 5000)
- self.camera.start()
- else:
- self.setWindowTitle("PySide2 Camera Example")
- self.takePictureAction.setEnabled(False)
- self.statusBar().showMessage("Camera unavailable", 5000)
-
- def nextImageFileName(self):
- picturesLocation = QStandardPaths.writableLocation(QStandardPaths.PicturesLocation)
- dateString = QDate.currentDate().toString("yyyyMMdd")
- pattern = picturesLocation + "/pyside2_camera_" + dateString + "_{:03d}.jpg"
- n = 1
- while True:
- result = pattern.format(n)
- if not os.path.exists(result):
- return result
- n = n + 1
- return None
-
- def takePicture(self):
- self.currentPreview = QImage()
- self.camera.searchAndLock()
- self.imageCapture.capture(self.nextImageFileName())
- self.camera.unlock()
-
- def imageCaptured(self, id, previewImage):
- self.currentPreview = previewImage
-
- def imageSaved(self, id, fileName):
- index = self.tabWidget.count()
- imageView = ImageView(self.currentPreview, fileName)
- self.tabWidget.addTab(imageView, "Capture #{}".format(index))
- self.tabWidget.setCurrentIndex(index)
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
- mainWin = MainWindow()
- availableGeometry = app.desktop().availableGeometry(mainWin)
- mainWin.resize(availableGeometry.width() / 3, availableGeometry.height() / 2)
- mainWin.show()
- sys.exit(app.exec_())
diff --git a/examples/multimedia/camera/camera.py b/examples/multimedia/camera/camera.py
new file mode 100644
index 000000000..fa379c807
--- /dev/null
+++ b/examples/multimedia/camera/camera.py
@@ -0,0 +1,369 @@
+# 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)
diff --git a/examples/multimedia/camera/camera.pyproject b/examples/multimedia/camera/camera.pyproject
new file mode 100644
index 000000000..9067b1dfa
--- /dev/null
+++ b/examples/multimedia/camera/camera.pyproject
@@ -0,0 +1,12 @@
+{
+ "files": ["main.py",
+ "camera.py",
+ "camera.ui",
+ "camera_mobile.ui",
+ "imagesettings.py",
+ "imagesettings.ui",
+ "metadatadialog.py",
+ "videosettings.py",
+ "videosettings.ui",
+ "videosettings_mobile.ui"]
+}
diff --git a/examples/multimedia/camera/camera.ui b/examples/multimedia/camera/camera.ui
new file mode 100644
index 000000000..4584e909c
--- /dev/null
+++ b/examples/multimedia/camera/camera.ui
@@ -0,0 +1,497 @@
+<?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>
diff --git a/examples/multimedia/camera/camera_mobile.ui b/examples/multimedia/camera/camera_mobile.ui
new file mode 100644
index 000000000..7f269b17b
--- /dev/null
+++ b/examples/multimedia/camera/camera_mobile.ui
@@ -0,0 +1,504 @@
+<?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>
diff --git a/examples/multimedia/camera/doc/camera.rst b/examples/multimedia/camera/doc/camera.rst
new file mode 100644
index 000000000..7fc75a387
--- /dev/null
+++ b/examples/multimedia/camera/doc/camera.rst
@@ -0,0 +1,14 @@
+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.
+
+.. image:: camera.webp
+ :width: 678
+ :alt: Camera Screenshot
diff --git a/examples/multimedia/camera/doc/camera.webp b/examples/multimedia/camera/doc/camera.webp
new file mode 100644
index 000000000..11ed18792
--- /dev/null
+++ b/examples/multimedia/camera/doc/camera.webp
Binary files differ
diff --git a/examples/multimedia/camera/imagesettings.py b/examples/multimedia/camera/imagesettings.py
new file mode 100644
index 000000000..9ca1d92ce
--- /dev/null
+++ b/examples/multimedia/camera/imagesettings.py
@@ -0,0 +1,56 @@
+# 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))
diff --git a/examples/multimedia/camera/imagesettings.ui b/examples/multimedia/camera/imagesettings.ui
new file mode 100644
index 000000000..8c59ca01d
--- /dev/null
+++ b/examples/multimedia/camera/imagesettings.ui
@@ -0,0 +1,123 @@
+<?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>
diff --git a/examples/multimedia/camera/main.py b/examples/multimedia/camera/main.py
new file mode 100644
index 000000000..fd4dd32e3
--- /dev/null
+++ b/examples/multimedia/camera/main.py
@@ -0,0 +1,17 @@
+# 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())
diff --git a/examples/multimedia/camera/metadatadialog.py b/examples/multimedia/camera/metadatadialog.py
new file mode 100644
index 000000000..97d0a36e5
--- /dev/null
+++ b/examples/multimedia/camera/metadatadialog.py
@@ -0,0 +1,86 @@
+# 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])
diff --git a/examples/multimedia/shutter.svg b/examples/multimedia/camera/shutter.svg
index 18493361d..18493361d 100644
--- a/examples/multimedia/shutter.svg
+++ b/examples/multimedia/camera/shutter.svg
diff --git a/examples/multimedia/camera/ui_camera.py b/examples/multimedia/camera/ui_camera.py
new file mode 100644
index 000000000..690cf3352
--- /dev/null
+++ b/examples/multimedia/camera/ui_camera.py
@@ -0,0 +1,232 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'camera.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
+ QCursor, QFont, QFontDatabase, QGradient,
+ QIcon, QImage, QKeySequence, QLinearGradient,
+ QPainter, QPalette, QPixmap, QRadialGradient,
+ QTransform)
+from PySide6.QtMultimediaWidgets import QVideoWidget
+from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QLabel,
+ QMainWindow, QMenu, QMenuBar, QPushButton,
+ QSizePolicy, QSlider, QSpacerItem, QStackedWidget,
+ QStatusBar, QTabWidget, QWidget)
+
+class Ui_Camera(object):
+ def setupUi(self, Camera):
+ if not Camera.objectName():
+ Camera.setObjectName(u"Camera")
+ Camera.resize(668, 429)
+ self.actionExit = QAction(Camera)
+ self.actionExit.setObjectName(u"actionExit")
+ self.actionStartCamera = QAction(Camera)
+ self.actionStartCamera.setObjectName(u"actionStartCamera")
+ self.actionStopCamera = QAction(Camera)
+ self.actionStopCamera.setObjectName(u"actionStopCamera")
+ self.actionSettings = QAction(Camera)
+ self.actionSettings.setObjectName(u"actionSettings")
+ self.actionAbout_Qt = QAction(Camera)
+ self.actionAbout_Qt.setObjectName(u"actionAbout_Qt")
+ self.centralwidget = QWidget(Camera)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.gridLayout_3 = QGridLayout(self.centralwidget)
+ self.gridLayout_3.setObjectName(u"gridLayout_3")
+ self.captureWidget = QTabWidget(self.centralwidget)
+ self.captureWidget.setObjectName(u"captureWidget")
+ self.tab_2 = QWidget()
+ self.tab_2.setObjectName(u"tab_2")
+ self.gridLayout = QGridLayout(self.tab_2)
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.verticalSpacer_2 = QSpacerItem(20, 161, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.gridLayout.addItem(self.verticalSpacer_2, 3, 0, 1, 1)
+
+ self.takeImageButton = QPushButton(self.tab_2)
+ self.takeImageButton.setObjectName(u"takeImageButton")
+ self.takeImageButton.setEnabled(False)
+
+ self.gridLayout.addWidget(self.takeImageButton, 0, 0, 1, 1)
+
+ self.exposureCompensation = QSlider(self.tab_2)
+ self.exposureCompensation.setObjectName(u"exposureCompensation")
+ self.exposureCompensation.setMinimum(-4)
+ self.exposureCompensation.setMaximum(4)
+ self.exposureCompensation.setPageStep(2)
+ self.exposureCompensation.setOrientation(Qt.Horizontal)
+ self.exposureCompensation.setTickPosition(QSlider.TicksAbove)
+
+ self.gridLayout.addWidget(self.exposureCompensation, 5, 0, 1, 1)
+
+ self.label = QLabel(self.tab_2)
+ self.label.setObjectName(u"label")
+
+ self.gridLayout.addWidget(self.label, 4, 0, 1, 1)
+
+ self.captureWidget.addTab(self.tab_2, "")
+ self.tab = QWidget()
+ self.tab.setObjectName(u"tab")
+ self.gridLayout_2 = QGridLayout(self.tab)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.recordButton = QPushButton(self.tab)
+ self.recordButton.setObjectName(u"recordButton")
+
+ self.gridLayout_2.addWidget(self.recordButton, 0, 0, 1, 1)
+
+ self.pauseButton = QPushButton(self.tab)
+ self.pauseButton.setObjectName(u"pauseButton")
+
+ self.gridLayout_2.addWidget(self.pauseButton, 1, 0, 1, 1)
+
+ self.stopButton = QPushButton(self.tab)
+ self.stopButton.setObjectName(u"stopButton")
+
+ self.gridLayout_2.addWidget(self.stopButton, 2, 0, 1, 1)
+
+ self.verticalSpacer = QSpacerItem(20, 76, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.gridLayout_2.addItem(self.verticalSpacer, 3, 0, 1, 1)
+
+ self.muteButton = QPushButton(self.tab)
+ self.muteButton.setObjectName(u"muteButton")
+ self.muteButton.setCheckable(True)
+
+ self.gridLayout_2.addWidget(self.muteButton, 4, 0, 1, 1)
+
+ self.metaDataButton = QPushButton(self.tab)
+ self.metaDataButton.setObjectName(u"metaDataButton")
+ self.metaDataButton.setCheckable(True)
+
+ self.gridLayout_2.addWidget(self.metaDataButton, 5, 0, 1, 1)
+
+ self.captureWidget.addTab(self.tab, "")
+
+ self.gridLayout_3.addWidget(self.captureWidget, 1, 1, 1, 2)
+
+ self.stackedWidget = QStackedWidget(self.centralwidget)
+ self.stackedWidget.setObjectName(u"stackedWidget")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
+ sizePolicy.setHorizontalStretch(1)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth())
+ self.stackedWidget.setSizePolicy(sizePolicy)
+ palette = QPalette()
+ brush = QBrush(QColor(255, 255, 255, 255))
+ brush.setStyle(Qt.SolidPattern)
+ palette.setBrush(QPalette.Active, QPalette.Base, brush)
+ brush1 = QBrush(QColor(145, 145, 145, 255))
+ brush1.setStyle(Qt.SolidPattern)
+ palette.setBrush(QPalette.Active, QPalette.Window, brush1)
+ palette.setBrush(QPalette.Inactive, QPalette.Base, brush)
+ palette.setBrush(QPalette.Inactive, QPalette.Window, brush1)
+ palette.setBrush(QPalette.Disabled, QPalette.Base, brush1)
+ palette.setBrush(QPalette.Disabled, QPalette.Window, brush1)
+ self.stackedWidget.setPalette(palette)
+ self.viewfinderPage = QWidget()
+ self.viewfinderPage.setObjectName(u"viewfinderPage")
+ self.gridLayout_5 = QGridLayout(self.viewfinderPage)
+ self.gridLayout_5.setObjectName(u"gridLayout_5")
+ self.viewfinder = QVideoWidget(self.viewfinderPage)
+ self.viewfinder.setObjectName(u"viewfinder")
+
+ self.gridLayout_5.addWidget(self.viewfinder, 0, 0, 1, 1)
+
+ self.stackedWidget.addWidget(self.viewfinderPage)
+ self.previewPage = QWidget()
+ self.previewPage.setObjectName(u"previewPage")
+ self.gridLayout_4 = QGridLayout(self.previewPage)
+ self.gridLayout_4.setObjectName(u"gridLayout_4")
+ self.lastImagePreviewLabel = QLabel(self.previewPage)
+ self.lastImagePreviewLabel.setObjectName(u"lastImagePreviewLabel")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.MinimumExpanding)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ sizePolicy1.setHeightForWidth(self.lastImagePreviewLabel.sizePolicy().hasHeightForWidth())
+ self.lastImagePreviewLabel.setSizePolicy(sizePolicy1)
+ self.lastImagePreviewLabel.setFrameShape(QFrame.Box)
+
+ self.gridLayout_4.addWidget(self.lastImagePreviewLabel, 0, 0, 1, 1)
+
+ self.stackedWidget.addWidget(self.previewPage)
+
+ self.gridLayout_3.addWidget(self.stackedWidget, 0, 0, 2, 1)
+
+ Camera.setCentralWidget(self.centralwidget)
+ self.menubar = QMenuBar(Camera)
+ self.menubar.setObjectName(u"menubar")
+ self.menubar.setGeometry(QRect(0, 0, 668, 19))
+ self.menuFile = QMenu(self.menubar)
+ self.menuFile.setObjectName(u"menuFile")
+ self.menuDevices = QMenu(self.menubar)
+ self.menuDevices.setObjectName(u"menuDevices")
+ self.menuHelp = QMenu(self.menubar)
+ self.menuHelp.setObjectName(u"menuHelp")
+ Camera.setMenuBar(self.menubar)
+ self.statusbar = QStatusBar(Camera)
+ self.statusbar.setObjectName(u"statusbar")
+ Camera.setStatusBar(self.statusbar)
+
+ self.menubar.addAction(self.menuFile.menuAction())
+ self.menubar.addAction(self.menuDevices.menuAction())
+ self.menubar.addAction(self.menuHelp.menuAction())
+ self.menuFile.addAction(self.actionStartCamera)
+ self.menuFile.addAction(self.actionStopCamera)
+ self.menuFile.addSeparator()
+ self.menuFile.addAction(self.actionSettings)
+ self.menuFile.addSeparator()
+ self.menuFile.addAction(self.actionExit)
+ self.menuHelp.addAction(self.actionAbout_Qt)
+
+ self.retranslateUi(Camera)
+ self.recordButton.clicked.connect(Camera.record)
+ self.stopButton.clicked.connect(Camera.stop)
+ self.pauseButton.clicked.connect(Camera.pause)
+ self.actionExit.triggered.connect(Camera.close)
+ self.takeImageButton.clicked.connect(Camera.takeImage)
+ self.muteButton.toggled.connect(Camera.setMuted)
+ self.exposureCompensation.valueChanged.connect(Camera.setExposureCompensation)
+ self.actionSettings.triggered.connect(Camera.configureCaptureSettings)
+ self.actionStartCamera.triggered.connect(Camera.startCamera)
+ self.actionStopCamera.triggered.connect(Camera.stopCamera)
+
+ self.captureWidget.setCurrentIndex(0)
+ self.stackedWidget.setCurrentIndex(0)
+
+
+ QMetaObject.connectSlotsByName(Camera)
+ # setupUi
+
+ def retranslateUi(self, Camera):
+ Camera.setWindowTitle(QCoreApplication.translate("Camera", u"Camera", None))
+ self.actionExit.setText(QCoreApplication.translate("Camera", u"Quit", None))
+#if QT_CONFIG(shortcut)
+ self.actionExit.setShortcut(QCoreApplication.translate("Camera", u"Ctrl+Q", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionStartCamera.setText(QCoreApplication.translate("Camera", u"Start Camera", None))
+ self.actionStopCamera.setText(QCoreApplication.translate("Camera", u"Stop Camera", None))
+ self.actionSettings.setText(QCoreApplication.translate("Camera", u"Change Settings", None))
+ self.actionAbout_Qt.setText(QCoreApplication.translate("Camera", u"About Qt", None))
+ self.takeImageButton.setText(QCoreApplication.translate("Camera", u"Capture Photo", None))
+ self.label.setText(QCoreApplication.translate("Camera", u"Exposure Compensation:", None))
+ self.captureWidget.setTabText(self.captureWidget.indexOf(self.tab_2), QCoreApplication.translate("Camera", u"Image", None))
+ self.recordButton.setText(QCoreApplication.translate("Camera", u"Record", None))
+ self.pauseButton.setText(QCoreApplication.translate("Camera", u"Pause", None))
+ self.stopButton.setText(QCoreApplication.translate("Camera", u"Stop", None))
+ self.muteButton.setText(QCoreApplication.translate("Camera", u"Mute", None))
+ self.metaDataButton.setText(QCoreApplication.translate("Camera", u"Set metadata", None))
+ self.captureWidget.setTabText(self.captureWidget.indexOf(self.tab), QCoreApplication.translate("Camera", u"Video", None))
+ self.lastImagePreviewLabel.setText("")
+ self.menuFile.setTitle(QCoreApplication.translate("Camera", u"File", None))
+ self.menuDevices.setTitle(QCoreApplication.translate("Camera", u"Devices", None))
+ self.menuHelp.setTitle(QCoreApplication.translate("Camera", u"Help", None))
+ # retranslateUi
+
diff --git a/examples/multimedia/camera/ui_camera_mobile.py b/examples/multimedia/camera/ui_camera_mobile.py
new file mode 100644
index 000000000..5cdd81f1e
--- /dev/null
+++ b/examples/multimedia/camera/ui_camera_mobile.py
@@ -0,0 +1,251 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'camera_mobile.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
+ QCursor, QFont, QFontDatabase, QGradient,
+ QIcon, QImage, QKeySequence, QLinearGradient,
+ QPainter, QPalette, QPixmap, QRadialGradient,
+ QTransform)
+from PySide6.QtMultimediaWidgets import QVideoWidget
+from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QHBoxLayout,
+ QLabel, QMainWindow, QMenu, QMenuBar,
+ QPushButton, QSizePolicy, QSlider, QSpacerItem,
+ QStackedWidget, QStatusBar, QTabWidget, QVBoxLayout,
+ QWidget)
+
+class Ui_Camera(object):
+ def setupUi(self, Camera):
+ if not Camera.objectName():
+ Camera.setObjectName(u"Camera")
+ Camera.resize(668, 429)
+ self.actionExit = QAction(Camera)
+ self.actionExit.setObjectName(u"actionExit")
+ self.actionStartCamera = QAction(Camera)
+ self.actionStartCamera.setObjectName(u"actionStartCamera")
+ self.actionStopCamera = QAction(Camera)
+ self.actionStopCamera.setObjectName(u"actionStopCamera")
+ self.actionSettings = QAction(Camera)
+ self.actionSettings.setObjectName(u"actionSettings")
+ self.centralwidget = QWidget(Camera)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.gridLayout_3 = QGridLayout(self.centralwidget)
+ self.gridLayout_3.setObjectName(u"gridLayout_3")
+ self.captureWidget = QTabWidget(self.centralwidget)
+ self.captureWidget.setObjectName(u"captureWidget")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.captureWidget.sizePolicy().hasHeightForWidth())
+ self.captureWidget.setSizePolicy(sizePolicy)
+ self.tab_2 = QWidget()
+ self.tab_2.setObjectName(u"tab_2")
+ self.gridLayout = QGridLayout(self.tab_2)
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.exposureCompensation = QSlider(self.tab_2)
+ self.exposureCompensation.setObjectName(u"exposureCompensation")
+ self.exposureCompensation.setMinimum(-4)
+ self.exposureCompensation.setMaximum(4)
+ self.exposureCompensation.setPageStep(2)
+ self.exposureCompensation.setOrientation(Qt.Horizontal)
+ self.exposureCompensation.setTickPosition(QSlider.TicksAbove)
+
+ self.gridLayout.addWidget(self.exposureCompensation, 4, 0, 1, 1)
+
+ self.label = QLabel(self.tab_2)
+ self.label.setObjectName(u"label")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ sizePolicy1.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
+ self.label.setSizePolicy(sizePolicy1)
+
+ self.gridLayout.addWidget(self.label, 3, 0, 1, 1)
+
+ self.takeImageButton = QPushButton(self.tab_2)
+ self.takeImageButton.setObjectName(u"takeImageButton")
+ self.takeImageButton.setEnabled(False)
+ icon = QIcon()
+ icon.addFile(u":/images/shutter.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.takeImageButton.setIcon(icon)
+
+ self.gridLayout.addWidget(self.takeImageButton, 0, 0, 1, 1)
+
+ self.captureWidget.addTab(self.tab_2, "")
+ self.tab = QWidget()
+ self.tab.setObjectName(u"tab")
+ self.gridLayout_2 = QGridLayout(self.tab)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.recordButton = QPushButton(self.tab)
+ self.recordButton.setObjectName(u"recordButton")
+
+ self.verticalLayout.addWidget(self.recordButton)
+
+ self.pauseButton = QPushButton(self.tab)
+ self.pauseButton.setObjectName(u"pauseButton")
+
+ self.verticalLayout.addWidget(self.pauseButton)
+
+ self.stopButton = QPushButton(self.tab)
+ self.stopButton.setObjectName(u"stopButton")
+
+ self.verticalLayout.addWidget(self.stopButton)
+
+
+ self.horizontalLayout.addLayout(self.verticalLayout)
+
+ self.verticalLayout_2 = QVBoxLayout()
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalSpacer = QSpacerItem(20, 10, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.verticalLayout_2.addItem(self.verticalSpacer)
+
+ self.muteButton = QPushButton(self.tab)
+ self.muteButton.setObjectName(u"muteButton")
+ self.muteButton.setCheckable(True)
+
+ self.verticalLayout_2.addWidget(self.muteButton)
+
+ self.metaDataButton = QPushButton(self.tab)
+ self.metaDataButton.setObjectName(u"metaDataButton")
+ self.metaDataButton.setCheckable(True)
+
+ self.verticalLayout_2.addWidget(self.metaDataButton)
+
+
+ self.horizontalLayout.addLayout(self.verticalLayout_2)
+
+
+ self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 1)
+
+ self.captureWidget.addTab(self.tab, "")
+
+ self.gridLayout_3.addWidget(self.captureWidget, 1, 1, 1, 2)
+
+ self.stackedWidget = QStackedWidget(self.centralwidget)
+ self.stackedWidget.setObjectName(u"stackedWidget")
+ sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ sizePolicy2.setHorizontalStretch(1)
+ sizePolicy2.setVerticalStretch(0)
+ sizePolicy2.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth())
+ self.stackedWidget.setSizePolicy(sizePolicy2)
+ palette = QPalette()
+ brush = QBrush(QColor(255, 255, 255, 255))
+ brush.setStyle(Qt.SolidPattern)
+ palette.setBrush(QPalette.Active, QPalette.Base, brush)
+ brush1 = QBrush(QColor(145, 145, 145, 255))
+ brush1.setStyle(Qt.SolidPattern)
+ palette.setBrush(QPalette.Active, QPalette.Window, brush1)
+ palette.setBrush(QPalette.Inactive, QPalette.Base, brush)
+ palette.setBrush(QPalette.Inactive, QPalette.Window, brush1)
+ palette.setBrush(QPalette.Disabled, QPalette.Base, brush1)
+ palette.setBrush(QPalette.Disabled, QPalette.Window, brush1)
+ self.stackedWidget.setPalette(palette)
+ self.viewfinderPage = QWidget()
+ self.viewfinderPage.setObjectName(u"viewfinderPage")
+ self.gridLayout_5 = QGridLayout(self.viewfinderPage)
+ self.gridLayout_5.setObjectName(u"gridLayout_5")
+ self.viewfinder = QVideoWidget(self.viewfinderPage)
+ self.viewfinder.setObjectName(u"viewfinder")
+ sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ sizePolicy3.setHorizontalStretch(0)
+ sizePolicy3.setVerticalStretch(0)
+ sizePolicy3.setHeightForWidth(self.viewfinder.sizePolicy().hasHeightForWidth())
+ self.viewfinder.setSizePolicy(sizePolicy3)
+
+ self.gridLayout_5.addWidget(self.viewfinder, 0, 0, 1, 1)
+
+ self.stackedWidget.addWidget(self.viewfinderPage)
+ self.previewPage = QWidget()
+ self.previewPage.setObjectName(u"previewPage")
+ self.gridLayout_4 = QGridLayout(self.previewPage)
+ self.gridLayout_4.setObjectName(u"gridLayout_4")
+ self.lastImagePreviewLabel = QLabel(self.previewPage)
+ self.lastImagePreviewLabel.setObjectName(u"lastImagePreviewLabel")
+ sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.MinimumExpanding)
+ sizePolicy4.setHorizontalStretch(0)
+ sizePolicy4.setVerticalStretch(0)
+ sizePolicy4.setHeightForWidth(self.lastImagePreviewLabel.sizePolicy().hasHeightForWidth())
+ self.lastImagePreviewLabel.setSizePolicy(sizePolicy4)
+ self.lastImagePreviewLabel.setFrameShape(QFrame.Box)
+
+ self.gridLayout_4.addWidget(self.lastImagePreviewLabel, 0, 0, 1, 1)
+
+ self.stackedWidget.addWidget(self.previewPage)
+
+ self.gridLayout_3.addWidget(self.stackedWidget, 0, 2, 1, 1)
+
+ Camera.setCentralWidget(self.centralwidget)
+ self.menubar = QMenuBar(Camera)
+ self.menubar.setObjectName(u"menubar")
+ self.menubar.setGeometry(QRect(0, 0, 668, 22))
+ self.menuFile = QMenu(self.menubar)
+ self.menuFile.setObjectName(u"menuFile")
+ self.menuDevices = QMenu(self.menubar)
+ self.menuDevices.setObjectName(u"menuDevices")
+ Camera.setMenuBar(self.menubar)
+ self.statusbar = QStatusBar(Camera)
+ self.statusbar.setObjectName(u"statusbar")
+ Camera.setStatusBar(self.statusbar)
+
+ self.menubar.addAction(self.menuFile.menuAction())
+ self.menubar.addAction(self.menuDevices.menuAction())
+ self.menuFile.addAction(self.actionStartCamera)
+ self.menuFile.addAction(self.actionStopCamera)
+ self.menuFile.addSeparator()
+ self.menuFile.addAction(self.actionSettings)
+ self.menuFile.addSeparator()
+ self.menuFile.addAction(self.actionExit)
+
+ self.retranslateUi(Camera)
+ self.recordButton.clicked.connect(Camera.record)
+ self.stopButton.clicked.connect(Camera.stop)
+ self.pauseButton.clicked.connect(Camera.pause)
+ self.actionExit.triggered.connect(Camera.close)
+ self.takeImageButton.clicked.connect(Camera.takeImage)
+ self.muteButton.toggled.connect(Camera.setMuted)
+ self.exposureCompensation.valueChanged.connect(Camera.setExposureCompensation)
+ self.actionSettings.triggered.connect(Camera.configureCaptureSettings)
+ self.actionStartCamera.triggered.connect(Camera.startCamera)
+ self.actionStopCamera.triggered.connect(Camera.stopCamera)
+
+ self.captureWidget.setCurrentIndex(0)
+ self.stackedWidget.setCurrentIndex(0)
+
+
+ QMetaObject.connectSlotsByName(Camera)
+ # setupUi
+
+ def retranslateUi(self, Camera):
+ Camera.setWindowTitle(QCoreApplication.translate("Camera", u"Camera", None))
+ self.actionExit.setText(QCoreApplication.translate("Camera", u"Close", None))
+ self.actionStartCamera.setText(QCoreApplication.translate("Camera", u"Start Camera", None))
+ self.actionStopCamera.setText(QCoreApplication.translate("Camera", u"Stop Camera", None))
+ self.actionSettings.setText(QCoreApplication.translate("Camera", u"Change Settings", None))
+ self.label.setText(QCoreApplication.translate("Camera", u"Exposure Compensation:", None))
+ self.takeImageButton.setText(QCoreApplication.translate("Camera", u"Capture Photo", None))
+ self.captureWidget.setTabText(self.captureWidget.indexOf(self.tab_2), QCoreApplication.translate("Camera", u"Image", None))
+ self.recordButton.setText(QCoreApplication.translate("Camera", u"Record", None))
+ self.pauseButton.setText(QCoreApplication.translate("Camera", u"Pause", None))
+ self.stopButton.setText(QCoreApplication.translate("Camera", u"Stop", None))
+ self.muteButton.setText(QCoreApplication.translate("Camera", u"Mute", None))
+ self.metaDataButton.setText(QCoreApplication.translate("Camera", u"Set metadata", None))
+ self.captureWidget.setTabText(self.captureWidget.indexOf(self.tab), QCoreApplication.translate("Camera", u"Video", None))
+ self.lastImagePreviewLabel.setText("")
+ self.menuFile.setTitle(QCoreApplication.translate("Camera", u"File", None))
+ self.menuDevices.setTitle(QCoreApplication.translate("Camera", u"Devices", None))
+ # retranslateUi
+
diff --git a/examples/multimedia/camera/ui_imagesettings.py b/examples/multimedia/camera/ui_imagesettings.py
new file mode 100644
index 000000000..a3fba7789
--- /dev/null
+++ b/examples/multimedia/camera/ui_imagesettings.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'imagesettings.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDialog,
+ QDialogButtonBox, QGridLayout, QGroupBox, QLabel,
+ QSizePolicy, QSlider, QSpacerItem, QWidget)
+
+class Ui_ImageSettingsUi(object):
+ def setupUi(self, ImageSettingsUi):
+ if not ImageSettingsUi.objectName():
+ ImageSettingsUi.setObjectName(u"ImageSettingsUi")
+ ImageSettingsUi.resize(332, 270)
+ self.gridLayout = QGridLayout(ImageSettingsUi)
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.groupBox_2 = QGroupBox(ImageSettingsUi)
+ self.groupBox_2.setObjectName(u"groupBox_2")
+ self.gridLayout_2 = QGridLayout(self.groupBox_2)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.label_8 = QLabel(self.groupBox_2)
+ self.label_8.setObjectName(u"label_8")
+
+ self.gridLayout_2.addWidget(self.label_8, 0, 0, 1, 2)
+
+ self.imageResolutionBox = QComboBox(self.groupBox_2)
+ self.imageResolutionBox.setObjectName(u"imageResolutionBox")
+
+ self.gridLayout_2.addWidget(self.imageResolutionBox, 1, 0, 1, 2)
+
+ self.label_6 = QLabel(self.groupBox_2)
+ self.label_6.setObjectName(u"label_6")
+
+ self.gridLayout_2.addWidget(self.label_6, 2, 0, 1, 2)
+
+ self.imageCodecBox = QComboBox(self.groupBox_2)
+ self.imageCodecBox.setObjectName(u"imageCodecBox")
+
+ self.gridLayout_2.addWidget(self.imageCodecBox, 3, 0, 1, 2)
+
+ self.label_7 = QLabel(self.groupBox_2)
+ self.label_7.setObjectName(u"label_7")
+
+ self.gridLayout_2.addWidget(self.label_7, 4, 0, 1, 1)
+
+ self.imageQualitySlider = QSlider(self.groupBox_2)
+ self.imageQualitySlider.setObjectName(u"imageQualitySlider")
+ self.imageQualitySlider.setMaximum(4)
+ self.imageQualitySlider.setOrientation(Qt.Horizontal)
+
+ self.gridLayout_2.addWidget(self.imageQualitySlider, 4, 1, 1, 1)
+
+
+ self.gridLayout.addWidget(self.groupBox_2, 0, 0, 1, 1)
+
+ self.verticalSpacer = QSpacerItem(20, 14, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.gridLayout.addItem(self.verticalSpacer, 1, 0, 1, 1)
+
+ self.buttonBox = QDialogButtonBox(ImageSettingsUi)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
+
+ self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 1)
+
+
+ self.retranslateUi(ImageSettingsUi)
+ self.buttonBox.accepted.connect(ImageSettingsUi.accept)
+ self.buttonBox.rejected.connect(ImageSettingsUi.reject)
+
+ QMetaObject.connectSlotsByName(ImageSettingsUi)
+ # setupUi
+
+ def retranslateUi(self, ImageSettingsUi):
+ ImageSettingsUi.setWindowTitle(QCoreApplication.translate("ImageSettingsUi", u"Image Settings", None))
+ self.groupBox_2.setTitle(QCoreApplication.translate("ImageSettingsUi", u"Image", None))
+ self.label_8.setText(QCoreApplication.translate("ImageSettingsUi", u"Resolution:", None))
+ self.label_6.setText(QCoreApplication.translate("ImageSettingsUi", u"Image Format:", None))
+ self.label_7.setText(QCoreApplication.translate("ImageSettingsUi", u"Quality:", None))
+ # retranslateUi
+
diff --git a/examples/multimedia/camera/ui_videosettings.py b/examples/multimedia/camera/ui_videosettings.py
new file mode 100644
index 000000000..eec626f27
--- /dev/null
+++ b/examples/multimedia/camera/ui_videosettings.py
@@ -0,0 +1,178 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'videosettings.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDialog,
+ QDialogButtonBox, QGridLayout, QGroupBox, QHBoxLayout,
+ QLabel, QSizePolicy, QSlider, QSpacerItem,
+ QSpinBox, QVBoxLayout, QWidget)
+
+class Ui_VideoSettingsUi(object):
+ def setupUi(self, VideoSettingsUi):
+ if not VideoSettingsUi.objectName():
+ VideoSettingsUi.setObjectName(u"VideoSettingsUi")
+ VideoSettingsUi.resize(686, 499)
+ self.gridLayout_3 = QGridLayout(VideoSettingsUi)
+ self.gridLayout_3.setObjectName(u"gridLayout_3")
+ self.buttonBox = QDialogButtonBox(VideoSettingsUi)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
+
+ self.gridLayout_3.addWidget(self.buttonBox, 4, 1, 1, 1)
+
+ self.groupBox_2 = QGroupBox(VideoSettingsUi)
+ self.groupBox_2.setObjectName(u"groupBox_2")
+ self.gridLayout_2 = QGridLayout(self.groupBox_2)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.label_8 = QLabel(self.groupBox_2)
+ self.label_8.setObjectName(u"label_8")
+
+ self.gridLayout_2.addWidget(self.label_8, 0, 0, 1, 2)
+
+ self.videoCodecBox = QComboBox(self.groupBox_2)
+ self.videoCodecBox.setObjectName(u"videoCodecBox")
+
+ self.gridLayout_2.addWidget(self.videoCodecBox, 5, 0, 1, 2)
+
+ self.label_9 = QLabel(self.groupBox_2)
+ self.label_9.setObjectName(u"label_9")
+
+ self.gridLayout_2.addWidget(self.label_9, 2, 0, 1, 2)
+
+ self.label_6 = QLabel(self.groupBox_2)
+ self.label_6.setObjectName(u"label_6")
+
+ self.gridLayout_2.addWidget(self.label_6, 4, 0, 1, 2)
+
+ self.videoFormatBox = QComboBox(self.groupBox_2)
+ self.videoFormatBox.setObjectName(u"videoFormatBox")
+
+ self.gridLayout_2.addWidget(self.videoFormatBox, 1, 0, 1, 2)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.fpsSpinBox = QSpinBox(self.groupBox_2)
+ self.fpsSpinBox.setObjectName(u"fpsSpinBox")
+
+ self.horizontalLayout.addWidget(self.fpsSpinBox)
+
+ self.fpsSlider = QSlider(self.groupBox_2)
+ self.fpsSlider.setObjectName(u"fpsSlider")
+ self.fpsSlider.setOrientation(Qt.Horizontal)
+
+ self.horizontalLayout.addWidget(self.fpsSlider)
+
+
+ self.gridLayout_2.addLayout(self.horizontalLayout, 3, 0, 1, 2)
+
+
+ self.gridLayout_3.addWidget(self.groupBox_2, 2, 1, 1, 1)
+
+ self.widget = QWidget(VideoSettingsUi)
+ self.widget.setObjectName(u"widget")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
+ self.widget.setSizePolicy(sizePolicy)
+ self.verticalLayout_3 = QVBoxLayout(self.widget)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.groupBox_3 = QGroupBox(self.widget)
+ self.groupBox_3.setObjectName(u"groupBox_3")
+ self.verticalLayout_2 = QVBoxLayout(self.groupBox_3)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.label_2 = QLabel(self.groupBox_3)
+ self.label_2.setObjectName(u"label_2")
+
+ self.verticalLayout_2.addWidget(self.label_2)
+
+ self.audioCodecBox = QComboBox(self.groupBox_3)
+ self.audioCodecBox.setObjectName(u"audioCodecBox")
+
+ self.verticalLayout_2.addWidget(self.audioCodecBox)
+
+ self.label_5 = QLabel(self.groupBox_3)
+ self.label_5.setObjectName(u"label_5")
+
+ self.verticalLayout_2.addWidget(self.label_5)
+
+ self.audioSampleRateBox = QSpinBox(self.groupBox_3)
+ self.audioSampleRateBox.setObjectName(u"audioSampleRateBox")
+
+ self.verticalLayout_2.addWidget(self.audioSampleRateBox)
+
+
+ self.verticalLayout_3.addWidget(self.groupBox_3)
+
+ self.groupBox = QGroupBox(self.widget)
+ self.groupBox.setObjectName(u"groupBox")
+ self.verticalLayout = QVBoxLayout(self.groupBox)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.label_3 = QLabel(self.groupBox)
+ self.label_3.setObjectName(u"label_3")
+
+ self.verticalLayout.addWidget(self.label_3)
+
+ self.qualitySlider = QSlider(self.groupBox)
+ self.qualitySlider.setObjectName(u"qualitySlider")
+ self.qualitySlider.setMaximum(4)
+ self.qualitySlider.setOrientation(Qt.Horizontal)
+
+ self.verticalLayout.addWidget(self.qualitySlider)
+
+ self.label_4 = QLabel(self.groupBox)
+ self.label_4.setObjectName(u"label_4")
+
+ self.verticalLayout.addWidget(self.label_4)
+
+ self.containerFormatBox = QComboBox(self.groupBox)
+ self.containerFormatBox.setObjectName(u"containerFormatBox")
+
+ self.verticalLayout.addWidget(self.containerFormatBox)
+
+
+ self.verticalLayout_3.addWidget(self.groupBox)
+
+
+ self.gridLayout_3.addWidget(self.widget, 2, 0, 1, 1)
+
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.gridLayout_3.addItem(self.verticalSpacer, 3, 0, 1, 1)
+
+
+ self.retranslateUi(VideoSettingsUi)
+ self.buttonBox.accepted.connect(VideoSettingsUi.accept)
+ self.buttonBox.rejected.connect(VideoSettingsUi.reject)
+
+ QMetaObject.connectSlotsByName(VideoSettingsUi)
+ # setupUi
+
+ def retranslateUi(self, VideoSettingsUi):
+ VideoSettingsUi.setWindowTitle(QCoreApplication.translate("VideoSettingsUi", u"Video Settings", None))
+ self.groupBox_2.setTitle(QCoreApplication.translate("VideoSettingsUi", u"Video", None))
+ self.label_8.setText(QCoreApplication.translate("VideoSettingsUi", u"Camera Format", None))
+ self.label_9.setText(QCoreApplication.translate("VideoSettingsUi", u"Framerate:", None))
+ self.label_6.setText(QCoreApplication.translate("VideoSettingsUi", u"Video Codec:", None))
+ self.groupBox_3.setTitle(QCoreApplication.translate("VideoSettingsUi", u"Audio", None))
+ self.label_2.setText(QCoreApplication.translate("VideoSettingsUi", u"Audio Codec:", None))
+ self.label_5.setText(QCoreApplication.translate("VideoSettingsUi", u"Sample Rate:", None))
+ self.label_3.setText(QCoreApplication.translate("VideoSettingsUi", u"Quality:", None))
+ self.label_4.setText(QCoreApplication.translate("VideoSettingsUi", u"File Format:", None))
+ # retranslateUi
+
diff --git a/examples/multimedia/camera/ui_videosettings_mobile.py b/examples/multimedia/camera/ui_videosettings_mobile.py
new file mode 100644
index 000000000..50fb8e081
--- /dev/null
+++ b/examples/multimedia/camera/ui_videosettings_mobile.py
@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'videosettings_mobile.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDialog,
+ QDialogButtonBox, QGridLayout, QGroupBox, QHBoxLayout,
+ QLabel, QSizePolicy, QSlider, QSpinBox,
+ QVBoxLayout, QWidget)
+
+class Ui_VideoSettingsUi(object):
+ def setupUi(self, VideoSettingsUi):
+ if not VideoSettingsUi.objectName():
+ VideoSettingsUi.setObjectName(u"VideoSettingsUi")
+ VideoSettingsUi.resize(329, 591)
+ self.gridLayout_3 = QGridLayout(VideoSettingsUi)
+ self.gridLayout_3.setObjectName(u"gridLayout_3")
+ self.widget = QWidget(VideoSettingsUi)
+ self.widget.setObjectName(u"widget")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
+ self.widget.setSizePolicy(sizePolicy)
+ self.verticalLayout_3 = QVBoxLayout(self.widget)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.groupBox_3 = QGroupBox(self.widget)
+ self.groupBox_3.setObjectName(u"groupBox_3")
+ self.verticalLayout_2 = QVBoxLayout(self.groupBox_3)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.label_2 = QLabel(self.groupBox_3)
+ self.label_2.setObjectName(u"label_2")
+
+ self.verticalLayout_2.addWidget(self.label_2)
+
+ self.audioCodecBox = QComboBox(self.groupBox_3)
+ self.audioCodecBox.setObjectName(u"audioCodecBox")
+
+ self.verticalLayout_2.addWidget(self.audioCodecBox)
+
+ self.label_5 = QLabel(self.groupBox_3)
+ self.label_5.setObjectName(u"label_5")
+
+ self.verticalLayout_2.addWidget(self.label_5)
+
+ self.audioSampleRateBox = QSpinBox(self.groupBox_3)
+ self.audioSampleRateBox.setObjectName(u"audioSampleRateBox")
+
+ self.verticalLayout_2.addWidget(self.audioSampleRateBox)
+
+
+ self.verticalLayout_3.addWidget(self.groupBox_3)
+
+ self.groupBox = QGroupBox(self.widget)
+ self.groupBox.setObjectName(u"groupBox")
+ self.verticalLayout = QVBoxLayout(self.groupBox)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.label_3 = QLabel(self.groupBox)
+ self.label_3.setObjectName(u"label_3")
+
+ self.verticalLayout.addWidget(self.label_3)
+
+ self.qualitySlider = QSlider(self.groupBox)
+ self.qualitySlider.setObjectName(u"qualitySlider")
+ self.qualitySlider.setMaximum(4)
+ self.qualitySlider.setOrientation(Qt.Horizontal)
+
+ self.verticalLayout.addWidget(self.qualitySlider)
+
+ self.label_4 = QLabel(self.groupBox)
+ self.label_4.setObjectName(u"label_4")
+
+ self.verticalLayout.addWidget(self.label_4)
+
+ self.containerFormatBox = QComboBox(self.groupBox)
+ self.containerFormatBox.setObjectName(u"containerFormatBox")
+
+ self.verticalLayout.addWidget(self.containerFormatBox)
+
+
+ self.verticalLayout_3.addWidget(self.groupBox)
+
+
+ self.gridLayout_3.addWidget(self.widget, 2, 0, 1, 1)
+
+ self.groupBox_2 = QGroupBox(VideoSettingsUi)
+ self.groupBox_2.setObjectName(u"groupBox_2")
+ self.gridLayout_2 = QGridLayout(self.groupBox_2)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.label = QLabel(self.groupBox_2)
+ self.label.setObjectName(u"label")
+
+ self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1)
+
+ self.videoCodecBox = QComboBox(self.groupBox_2)
+ self.videoCodecBox.setObjectName(u"videoCodecBox")
+
+ self.gridLayout_2.addWidget(self.videoCodecBox, 6, 0, 1, 2)
+
+ self.label_8 = QLabel(self.groupBox_2)
+ self.label_8.setObjectName(u"label_8")
+
+ self.gridLayout_2.addWidget(self.label_8, 0, 0, 1, 2)
+
+ self.label_6 = QLabel(self.groupBox_2)
+ self.label_6.setObjectName(u"label_6")
+
+ self.gridLayout_2.addWidget(self.label_6, 5, 0, 1, 2)
+
+ self.videoFormatBox = QComboBox(self.groupBox_2)
+ self.videoFormatBox.setObjectName(u"videoFormatBox")
+
+ self.gridLayout_2.addWidget(self.videoFormatBox, 1, 0, 1, 2)
+
+ self.buttonBox = QDialogButtonBox(self.groupBox_2)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
+
+ self.gridLayout_2.addWidget(self.buttonBox, 7, 0, 1, 1)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.fpsSpinBox = QSpinBox(self.groupBox_2)
+ self.fpsSpinBox.setObjectName(u"fpsSpinBox")
+ self.fpsSpinBox.setMinimum(8)
+ self.fpsSpinBox.setMaximum(30)
+
+ self.horizontalLayout.addWidget(self.fpsSpinBox)
+
+ self.fpsSlider = QSlider(self.groupBox_2)
+ self.fpsSlider.setObjectName(u"fpsSlider")
+ self.fpsSlider.setOrientation(Qt.Horizontal)
+
+ self.horizontalLayout.addWidget(self.fpsSlider)
+
+
+ self.gridLayout_2.addLayout(self.horizontalLayout, 3, 0, 1, 1)
+
+
+ self.gridLayout_3.addWidget(self.groupBox_2, 3, 0, 1, 1)
+
+
+ self.retranslateUi(VideoSettingsUi)
+ self.buttonBox.accepted.connect(VideoSettingsUi.accept)
+ self.buttonBox.rejected.connect(VideoSettingsUi.reject)
+
+ QMetaObject.connectSlotsByName(VideoSettingsUi)
+ # setupUi
+
+ def retranslateUi(self, VideoSettingsUi):
+ VideoSettingsUi.setWindowTitle(QCoreApplication.translate("VideoSettingsUi", u"Video Settings", None))
+ self.groupBox_3.setTitle(QCoreApplication.translate("VideoSettingsUi", u"Audio", None))
+ self.label_2.setText(QCoreApplication.translate("VideoSettingsUi", u"Audio Codec:", None))
+ self.label_5.setText(QCoreApplication.translate("VideoSettingsUi", u"Sample Rate:", None))
+ self.label_3.setText(QCoreApplication.translate("VideoSettingsUi", u"Quality:", None))
+ self.label_4.setText(QCoreApplication.translate("VideoSettingsUi", u"File Format:", None))
+ self.groupBox_2.setTitle(QCoreApplication.translate("VideoSettingsUi", u"Video", None))
+ self.label.setText(QCoreApplication.translate("VideoSettingsUi", u"Frames per second:", None))
+ self.label_8.setText(QCoreApplication.translate("VideoSettingsUi", u"Camera Format:", None))
+ self.label_6.setText(QCoreApplication.translate("VideoSettingsUi", u"Video Codec:", None))
+ # retranslateUi
+
diff --git a/examples/multimedia/camera/videosettings.py b/examples/multimedia/camera/videosettings.py
new file mode 100644
index 000000000..a88cb39ed
--- /dev/null
+++ b/examples/multimedia/camera/videosettings.py
@@ -0,0 +1,167 @@
+# 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)
diff --git a/examples/multimedia/camera/videosettings.ui b/examples/multimedia/camera/videosettings.ui
new file mode 100644
index 000000000..3c1f71f11
--- /dev/null
+++ b/examples/multimedia/camera/videosettings.ui
@@ -0,0 +1,213 @@
+<?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>
diff --git a/examples/multimedia/camera/videosettings_mobile.ui b/examples/multimedia/camera/videosettings_mobile.ui
new file mode 100644
index 000000000..6584f07f9
--- /dev/null
+++ b/examples/multimedia/camera/videosettings_mobile.ui
@@ -0,0 +1,207 @@
+<?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>
diff --git a/examples/multimedia/multimedia.pyproject b/examples/multimedia/multimedia.pyproject
deleted file mode 100644
index a0b8b441c..000000000
--- a/examples/multimedia/multimedia.pyproject
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "files": ["player.py", "audiooutput.py", "camera.py"]
-}
diff --git a/examples/multimedia/player.py b/examples/multimedia/player.py
deleted file mode 100644
index 509e914f9..000000000
--- a/examples/multimedia/player.py
+++ /dev/null
@@ -1,157 +0,0 @@
-
-#############################################################################
-##
-## Copyright (C) 2017 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-"""PySide2 Multimedia player example"""
-
-import sys
-from PySide2.QtCore import SLOT, QStandardPaths, Qt
-from PySide2.QtGui import QIcon, QKeySequence
-from PySide2.QtWidgets import (QAction, qApp, QApplication, QDialog, QFileDialog,
- QMainWindow, QMenu, QMenuBar, QSlider, QStyle, QToolBar)
-from PySide2.QtMultimedia import QMediaPlayer, QMediaPlaylist
-from PySide2.QtMultimediaWidgets import QVideoWidget
-
-class MainWindow(QMainWindow):
-
- def __init__(self):
- super(MainWindow, self).__init__()
-
- self.playlist = QMediaPlaylist()
- self.player = QMediaPlayer()
-
- toolBar = QToolBar()
- self.addToolBar(toolBar)
-
- fileMenu = self.menuBar().addMenu("&File")
- openAction = QAction(QIcon.fromTheme("document-open"),
- "&Open...", self, shortcut=QKeySequence.Open,
- triggered=self.open)
- fileMenu.addAction(openAction)
- exitAction = QAction(QIcon.fromTheme("application-exit"), "E&xit",
- self, shortcut="Ctrl+Q", triggered=self.close)
- fileMenu.addAction(exitAction)
-
- playMenu = self.menuBar().addMenu("&Play")
- playIcon = self.style().standardIcon(QStyle.SP_MediaPlay)
- self.playAction = toolBar.addAction(playIcon, "Play")
- self.playAction.triggered.connect(self.player.play)
- playMenu.addAction(self.playAction)
-
- previousIcon = self.style().standardIcon(QStyle.SP_MediaSkipBackward)
- self.previousAction = toolBar.addAction(previousIcon, "Previous")
- self.previousAction.triggered.connect(self.previousClicked)
- playMenu.addAction(self.previousAction)
-
- pauseIcon = self.style().standardIcon(QStyle.SP_MediaPause)
- self.pauseAction = toolBar.addAction(pauseIcon, "Pause")
- self.pauseAction.triggered.connect(self.player.pause)
- playMenu.addAction(self.pauseAction)
-
- nextIcon = self.style().standardIcon(QStyle.SP_MediaSkipForward)
- self.nextAction = toolBar.addAction(nextIcon, "Next")
- self.nextAction.triggered.connect(self.playlist.next)
- playMenu.addAction(self.nextAction)
-
- stopIcon = self.style().standardIcon(QStyle.SP_MediaStop)
- self.stopAction = toolBar.addAction(stopIcon, "Stop")
- self.stopAction.triggered.connect(self.player.stop)
- playMenu.addAction(self.stopAction)
-
- self.volumeSlider = QSlider()
- self.volumeSlider.setOrientation(Qt.Horizontal)
- self.volumeSlider.setMinimum(0)
- self.volumeSlider.setMaximum(100)
- self.volumeSlider.setFixedWidth(app.desktop().availableGeometry(self).width() / 10)
- self.volumeSlider.setValue(self.player.volume())
- self.volumeSlider.setTickInterval(10)
- self.volumeSlider.setTickPosition(QSlider.TicksBelow)
- self.volumeSlider.setToolTip("Volume")
- self.volumeSlider.valueChanged.connect(self.player.setVolume)
- toolBar.addWidget(self.volumeSlider)
-
- aboutMenu = self.menuBar().addMenu("&About")
- aboutQtAct = QAction("About &Qt", self, triggered=qApp.aboutQt)
- aboutMenu.addAction(aboutQtAct)
-
- self.videoWidget = QVideoWidget()
- self.setCentralWidget(self.videoWidget)
- self.player.setPlaylist(self.playlist)
- self.player.stateChanged.connect(self.updateButtons)
- self.player.setVideoOutput(self.videoWidget)
-
- self.updateButtons(self.player.state())
-
- def open(self):
- fileDialog = QFileDialog(self)
- supportedMimeTypes = QMediaPlayer.supportedMimeTypes()
- if not supportedMimeTypes:
- supportedMimeTypes.append("video/x-msvideo") # AVI
- fileDialog.setMimeTypeFilters(supportedMimeTypes)
- moviesLocation = QStandardPaths.writableLocation(QStandardPaths.MoviesLocation)
- fileDialog.setDirectory(moviesLocation)
- if fileDialog.exec_() == QDialog.Accepted:
- self.playlist.addMedia(fileDialog.selectedUrls()[0])
- self.player.play()
-
- def previousClicked(self):
- # Go to previous track if we are within the first 5 seconds of playback
- # Otherwise, seek to the beginning.
- if self.player.position() <= 5000:
- self.playlist.previous()
- else:
- player.setPosition(0)
-
- def updateButtons(self, state):
- mediaCount = self.playlist.mediaCount()
- self.playAction.setEnabled(mediaCount > 0
- and state != QMediaPlayer.PlayingState)
- self.pauseAction.setEnabled(state == QMediaPlayer.PlayingState)
- self.stopAction.setEnabled(state != QMediaPlayer.StoppedState)
- self.previousAction.setEnabled(self.player.position() > 0)
- self.nextAction.setEnabled(mediaCount > 1)
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
- mainWin = MainWindow()
- availableGeometry = app.desktop().availableGeometry(mainWin)
- mainWin.resize(availableGeometry.width() / 3, availableGeometry.height() / 2)
- mainWin.show()
- sys.exit(app.exec_())
diff --git a/examples/multimedia/player/doc/player.png b/examples/multimedia/player/doc/player.png
new file mode 100644
index 000000000..f751d4a82
--- /dev/null
+++ b/examples/multimedia/player/doc/player.png
Binary files differ
diff --git a/examples/multimedia/player/doc/player.rst b/examples/multimedia/player/doc/player.rst
new file mode 100644
index 000000000..fdf5fa920
--- /dev/null
+++ b/examples/multimedia/player/doc/player.rst
@@ -0,0 +1,9 @@
+Player Example
+==============
+
+Media Player demonstrates a simple multimedia player that can play audio and or
+video files using various codecs.
+
+.. image:: player.png
+ :width: 400
+ :alt: Player Screenshot
diff --git a/examples/multimedia/player/player.py b/examples/multimedia/player/player.py
new file mode 100644
index 000000000..d28f2887e
--- /dev/null
+++ b/examples/multimedia/player/player.py
@@ -0,0 +1,194 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 Multimedia player example"""
+
+import sys
+from PySide6.QtCore import QStandardPaths, Qt, Slot
+from PySide6.QtGui import QAction, QIcon, QKeySequence
+from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog,
+ QMainWindow, QSlider, QStyle, QToolBar)
+from PySide6.QtMultimedia import (QAudioOutput, QMediaFormat,
+ QMediaPlayer)
+from PySide6.QtMultimediaWidgets import QVideoWidget
+
+
+AVI = "video/x-msvideo" # AVI
+
+
+MP4 = 'video/mp4'
+
+
+def get_supported_mime_types():
+ result = []
+ for f in QMediaFormat().supportedFileFormats(QMediaFormat.Decode):
+ mime_type = QMediaFormat(f).mimeType()
+ result.append(mime_type.name())
+ return result
+
+
+class MainWindow(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self._playlist = [] # FIXME 6.3: Replace by QMediaPlaylist?
+ self._playlist_index = -1
+ self._audio_output = QAudioOutput()
+ self._player = QMediaPlayer()
+ self._player.setAudioOutput(self._audio_output)
+
+ self._player.errorOccurred.connect(self._player_error)
+
+ tool_bar = QToolBar()
+ self.addToolBar(tool_bar)
+
+ file_menu = self.menuBar().addMenu("&File")
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen)
+ open_action = QAction(icon, "&Open...", self,
+ shortcut=QKeySequence.Open, triggered=self.open)
+ file_menu.addAction(open_action)
+ tool_bar.addAction(open_action)
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit)
+ exit_action = QAction(icon, "E&xit", self,
+ shortcut="Ctrl+Q", triggered=self.close)
+ file_menu.addAction(exit_action)
+
+ play_menu = self.menuBar().addMenu("&Play")
+ style = self.style()
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart,
+ style.standardIcon(QStyle.SP_MediaPlay))
+ self._play_action = tool_bar.addAction(icon, "Play")
+ self._play_action.triggered.connect(self._player.play)
+ play_menu.addAction(self._play_action)
+
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaSkipBackward,
+ style.standardIcon(QStyle.SP_MediaSkipBackward))
+ self._previous_action = tool_bar.addAction(icon, "Previous")
+ self._previous_action.triggered.connect(self.previous_clicked)
+ play_menu.addAction(self._previous_action)
+
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackPause,
+ style.standardIcon(QStyle.SP_MediaPause))
+ self._pause_action = tool_bar.addAction(icon, "Pause")
+ self._pause_action.triggered.connect(self._player.pause)
+ play_menu.addAction(self._pause_action)
+
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaSkipForward,
+ style.standardIcon(QStyle.SP_MediaSkipForward))
+ self._next_action = tool_bar.addAction(icon, "Next")
+ self._next_action.triggered.connect(self.next_clicked)
+ play_menu.addAction(self._next_action)
+
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStop,
+ style.standardIcon(QStyle.SP_MediaStop))
+ self._stop_action = tool_bar.addAction(icon, "Stop")
+ self._stop_action.triggered.connect(self._ensure_stopped)
+ play_menu.addAction(self._stop_action)
+
+ self._volume_slider = QSlider()
+ self._volume_slider.setOrientation(Qt.Horizontal)
+ self._volume_slider.setMinimum(0)
+ self._volume_slider.setMaximum(100)
+ available_width = self.screen().availableGeometry().width()
+ self._volume_slider.setFixedWidth(available_width / 10)
+ self._volume_slider.setValue(self._audio_output.volume())
+ self._volume_slider.setTickInterval(10)
+ self._volume_slider.setTickPosition(QSlider.TicksBelow)
+ self._volume_slider.setToolTip("Volume")
+ self._volume_slider.valueChanged.connect(self._audio_output.setVolume)
+ tool_bar.addWidget(self._volume_slider)
+
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout)
+ about_menu = self.menuBar().addMenu("&About")
+ about_qt_act = QAction(icon, "About &Qt", self, triggered=qApp.aboutQt) # noqa: F821
+ about_menu.addAction(about_qt_act)
+
+ self._video_widget = QVideoWidget()
+ self.setCentralWidget(self._video_widget)
+ self._player.playbackStateChanged.connect(self.update_buttons)
+ self._player.setVideoOutput(self._video_widget)
+
+ self.update_buttons(self._player.playbackState())
+ self._mime_types = []
+
+ def closeEvent(self, event):
+ self._ensure_stopped()
+ event.accept()
+
+ @Slot()
+ def open(self):
+ self._ensure_stopped()
+ file_dialog = QFileDialog(self)
+
+ is_windows = sys.platform == 'win32'
+ if not self._mime_types:
+ self._mime_types = get_supported_mime_types()
+ if (is_windows and AVI not in self._mime_types):
+ self._mime_types.append(AVI)
+ elif MP4 not in self._mime_types:
+ self._mime_types.append(MP4)
+
+ file_dialog.setMimeTypeFilters(self._mime_types)
+
+ default_mimetype = AVI if is_windows else MP4
+ if default_mimetype in self._mime_types:
+ file_dialog.selectMimeTypeFilter(default_mimetype)
+
+ movies_location = QStandardPaths.writableLocation(QStandardPaths.MoviesLocation)
+ file_dialog.setDirectory(movies_location)
+ if file_dialog.exec() == QDialog.Accepted:
+ url = file_dialog.selectedUrls()[0]
+ self._playlist.append(url)
+ self._playlist_index = len(self._playlist) - 1
+ self._player.setSource(url)
+ self._player.play()
+
+ @Slot()
+ def _ensure_stopped(self):
+ if self._player.playbackState() != QMediaPlayer.StoppedState:
+ self._player.stop()
+
+ @Slot()
+ def previous_clicked(self):
+ # Go to previous track if we are within the first 5 seconds of playback
+ # Otherwise, seek to the beginning.
+ if self._player.position() <= 5000 and self._playlist_index > 0:
+ self._playlist_index -= 1
+ self._playlist.previous()
+ self._player.setSource(self._playlist[self._playlist_index])
+ else:
+ self._player.setPosition(0)
+
+ @Slot()
+ def next_clicked(self):
+ if self._playlist_index < len(self._playlist) - 1:
+ self._playlist_index += 1
+ self._player.setSource(self._playlist[self._playlist_index])
+
+ @Slot("QMediaPlayer::PlaybackState")
+ def update_buttons(self, state):
+ media_count = len(self._playlist)
+ self._play_action.setEnabled(media_count > 0 and state != QMediaPlayer.PlayingState)
+ self._pause_action.setEnabled(state == QMediaPlayer.PlayingState)
+ self._stop_action.setEnabled(state != QMediaPlayer.StoppedState)
+ self._previous_action.setEnabled(self._player.position() > 0)
+ self._next_action.setEnabled(media_count > 1)
+
+ def show_status_message(self, message):
+ self.statusBar().showMessage(message, 5000)
+
+ @Slot("QMediaPlayer::Error", str)
+ def _player_error(self, error, error_string):
+ print(error_string, file=sys.stderr)
+ self.show_status_message(error_string)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ main_win = MainWindow()
+ available_geometry = main_win.screen().availableGeometry()
+ main_win.resize(available_geometry.width() / 3,
+ available_geometry.height() / 2)
+ main_win.show()
+ sys.exit(app.exec())
diff --git a/examples/multimedia/player/player.pyproject b/examples/multimedia/player/player.pyproject
new file mode 100644
index 000000000..2e16f4505
--- /dev/null
+++ b/examples/multimedia/player/player.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["player.py"]
+}
diff --git a/examples/multimedia/screencapture/doc/screencapture.rst b/examples/multimedia/screencapture/doc/screencapture.rst
new file mode 100644
index 000000000..116d7773b
--- /dev/null
+++ b/examples/multimedia/screencapture/doc/screencapture.rst
@@ -0,0 +1,42 @@
+Screen Capture Example
+======================
+
+Screen Capture demonstrates how to capture a screen or window using
+``QScreenCapture`` and ``QWindowCapture``. The example shows a list of screens
+and windows and displays a live preview of the selected item using a
+``QMediaCaptureSession`` and a ``QVideoWidget``. Capturing can be started and
+stopped with a ``QPushButton``.
+
+Application Structure
++++++++++++++++++++++
+
+The example consists of three custom classes. The UI and all screen capture
+functionality is implemented in the class ``ScreenCapturePreview``. The classes
+``ScreenListModel`` and ``WindowListModel`` only serve as models behind the two
+``QListView`` widgets. The main function creates a ``ScreenCapturePreview``
+object, which in turn creates instances of ``QScreenCapture`` and
+``QWindowCapture``, and a ``QMediaCaptureSession`` and ``QVideoWidget``, in
+addition to all the UI widgets.
+
+The screen and window models are populated with the return values of
+``QGuiApplication.screens()`` and ``QWindowCapture.capturableWindows()``,
+respectively.
+
+When a list item is selected, it is connected to the ``QScreenCapture`` object
+with ``QScreenCapture.setScreen()``, or to the ``QWindowCapture`` object with
+``QWindowCapture.setWindow().`` The capture object is connected to the
+``QMediaCaptureSession`` object with
+``QMediaCaptureSession.setScreenCapture()`` and
+``QMediaCaptureSession.setWindowCapture()``, respectively. The capture session
+in turn is connected to the ``QVideoWidget`` object with
+``QMediaCaptureSession.setVideoOutput()``. Thus, the capture output is
+previewed in the video widget on the right hand side of the UI.
+
+The start/stop button calls ``QScreenCapture.start()`` and ``QScreenCapture.stop()``,
+or ``QWindowCapture.start()`` and ``QWindowCapture.stop()``.
+
+A QMessageBox pops up if an ``errorOccurred`` signal is emitted.
+
+.. image. screencapture.webp
+ :width: 600
+ :alt: screen capture example
diff --git a/examples/multimedia/screencapture/doc/screencapture.webp b/examples/multimedia/screencapture/doc/screencapture.webp
new file mode 100644
index 000000000..58ad36c7f
--- /dev/null
+++ b/examples/multimedia/screencapture/doc/screencapture.webp
Binary files differ
diff --git a/examples/multimedia/screencapture/main.py b/examples/multimedia/screencapture/main.py
new file mode 100644
index 000000000..f445bac03
--- /dev/null
+++ b/examples/multimedia/screencapture/main.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 port of the QtMultiMedia Screen Capture Example from Qt v6.x"""
+
+import sys
+
+from PySide6.QtCore import QCoreApplication
+from PySide6.QtWidgets import QApplication
+
+from screencapturepreview import ScreenCapturePreview
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ QCoreApplication.setApplicationName("screencapture")
+ QCoreApplication.setOrganizationName("QtProject")
+ screen_capture_preview = ScreenCapturePreview()
+ screen_capture_preview.show()
+ sys.exit(app.exec())
diff --git a/examples/multimedia/screencapture/screencapture.pyproject b/examples/multimedia/screencapture/screencapture.pyproject
new file mode 100644
index 000000000..dfec6c901
--- /dev/null
+++ b/examples/multimedia/screencapture/screencapture.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["main.py", "screencapturepreview.py", "screenlistmodel.py", "windowlistmodel.py"]
+}
diff --git a/examples/multimedia/screencapture/screencapturepreview.py b/examples/multimedia/screencapture/screencapturepreview.py
new file mode 100644
index 000000000..c7e0c596a
--- /dev/null
+++ b/examples/multimedia/screencapture/screencapturepreview.py
@@ -0,0 +1,162 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from enum import Enum, auto
+
+from PySide6.QtMultimediaWidgets import QVideoWidget
+from PySide6.QtMultimedia import (QCapturableWindow, QMediaCaptureSession,
+ QScreenCapture, QWindowCapture)
+from PySide6.QtWidgets import (QGridLayout, QLabel, QListView,
+ QMessageBox, QPushButton, QWidget)
+from PySide6.QtGui import QAction, QGuiApplication
+from PySide6.QtCore import QItemSelection, Qt, Slot
+
+from screenlistmodel import ScreenListModel
+from windowlistmodel import WindowListModel
+
+
+class SourceType(Enum):
+ Screen = auto()
+ Window = auto()
+
+
+class ScreenCapturePreview(QWidget):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ self._source = SourceType.Screen
+
+ self._screen_capture = QScreenCapture(self)
+ self._media_capture_session = QMediaCaptureSession(self)
+ self._video_widget = QVideoWidget(self)
+ self._screen_list_view = QListView(self)
+ self._screen_label = QLabel("Select screen to capture:", self)
+ self._video_widget_label = QLabel("Capture output:", self)
+ self._start_stop_button = QPushButton(self)
+ self._status_label = QLabel(self)
+
+ self._screen_list_model = ScreenListModel(self)
+
+ # Setup QScreenCapture with initial source:
+ self.setScreen(QGuiApplication.primaryScreen())
+ self._screen_capture.start()
+ self._media_capture_session.setScreenCapture(self._screen_capture)
+ self._media_capture_session.setVideoOutput(self._video_widget)
+
+ self._screen_list_view.setModel(self._screen_list_model)
+
+ self._window_list_view = QListView(self)
+ self._window_capture = QWindowCapture(self)
+ self._media_capture_session.setWindowCapture(self._window_capture)
+ self._window_label = QLabel("Select window to capture:", self)
+
+ self._window_list_model = WindowListModel(self)
+ self._window_list_view.setModel(self._window_list_model)
+ update_action = QAction("Update windows List", self)
+ update_action.triggered.connect(self._window_list_model.populate)
+ self._window_list_view.addAction(update_action)
+ self._window_list_view.setContextMenuPolicy(Qt.ActionsContextMenu)
+
+ grid_layout = QGridLayout(self)
+ grid_layout.addWidget(self._screen_label, 0, 0)
+ grid_layout.addWidget(self._screen_list_view, 1, 0)
+ grid_layout.addWidget(self._start_stop_button, 4, 0)
+ grid_layout.addWidget(self._video_widget_label, 0, 1)
+ grid_layout.addWidget(self._video_widget, 1, 1, 4, 1)
+ grid_layout.addWidget(self._window_label, 2, 0)
+ grid_layout.addWidget(self._window_list_view, 3, 0)
+ grid_layout.addWidget(self._status_label, 5, 0, 1, 2)
+
+ grid_layout.setColumnStretch(1, 1)
+ grid_layout.setRowStretch(1, 1)
+ grid_layout.setColumnMinimumWidth(0, 400)
+ grid_layout.setColumnMinimumWidth(1, 400)
+ grid_layout.setRowMinimumHeight(3, 1)
+
+ selection_model = self._screen_list_view.selectionModel()
+ selection_model.selectionChanged.connect(self.on_current_screen_selection_changed)
+ selection_model = self._window_list_view.selectionModel()
+ selection_model.selectionChanged.connect(self.on_current_window_selection_changed)
+
+ self._start_stop_button.clicked.connect(self.on_start_stop_button_clicked)
+ self._screen_capture.errorOccurred.connect(self.on_screen_capture_error_occured,
+ Qt.QueuedConnection)
+ self._window_capture.errorOccurred.connect(self.on_window_capture_error_occured,
+ Qt.QueuedConnection)
+ self.update_active(SourceType.Screen, True)
+
+ @Slot(QItemSelection)
+ def on_current_screen_selection_changed(self, selection):
+ self.clear_error_string()
+ indexes = selection.indexes()
+ if indexes:
+ self._screen_capture.setScreen(self._screen_list_model.screen(indexes[0]))
+ self.update_active(SourceType.Screen, self.is_active())
+ self._window_list_view.clearSelection()
+ else:
+ self._screen_capture.setScreen(None)
+
+ @Slot(QItemSelection)
+ def on_current_window_selection_changed(self, selection):
+ self.clear_error_string()
+ indexes = selection.indexes()
+ if indexes:
+ window = self._window_list_model.window(indexes[0])
+ if not window.isValid():
+ m = "The window is no longer valid. Update the list of windows?"
+ answer = QMessageBox.question(self, "Invalid window", m)
+ if answer == QMessageBox.Yes:
+ self.update_active(SourceType.Window, False)
+ self._window_list_view.clearSelection()
+ self._window_list_model.populate()
+ return
+ self._window_capture.setWindow(window)
+ self.update_active(SourceType.Window, self.is_active())
+ self._screen_list_view.clearSelection()
+ else:
+ self._window_capture.setWindow(QCapturableWindow())
+
+ @Slot(QWindowCapture.Error, str)
+ def on_window_capture_error_occured(self, error, error_string):
+ self.set_error_string("QWindowCapture: Error occurred " + error_string)
+
+ @Slot(QScreenCapture.Error, str)
+ def on_screen_capture_error_occured(self, error, error_string):
+ self.set_error_string("QScreenCapture: Error occurred " + error_string)
+
+ def set_error_string(self, t):
+ self._status_label.setStyleSheet("background-color: rgb(255, 0, 0);")
+ self._status_label.setText(t)
+
+ def clear_error_string(self):
+ self._status_label.clear()
+ self._status_label.setStyleSheet("")
+
+ @Slot()
+ def on_start_stop_button_clicked(self):
+ self.clear_error_string()
+ self.update_active(self._source_type, not self.is_active())
+
+ def update_start_stop_button_text(self):
+ active = self.is_active()
+ if self._source_type == SourceType.Window:
+ m = "Stop window capture" if active else "Start window capture"
+ self._start_stop_button.setText(m)
+ elif self._source_type == SourceType.Screen:
+ m = "Stop screen capture" if active else "Start screen capture"
+ self._start_stop_button.setText(m)
+
+ def update_active(self, source_type, active):
+ self._source_type = source_type
+ self._screen_capture.setActive(active and source_type == SourceType.Screen)
+ self._window_capture.setActive(active and source_type == SourceType.Window)
+
+ self.update_start_stop_button_text()
+
+ def is_active(self):
+ if self._source_type == SourceType.Window:
+ return self._window_capture.isActive()
+ if self._source_type == SourceType.Screen:
+ return self._screen_capture.isActive()
+ return False
diff --git a/examples/multimedia/screencapture/screenlistmodel.py b/examples/multimedia/screencapture/screenlistmodel.py
new file mode 100644
index 000000000..72bb306e3
--- /dev/null
+++ b/examples/multimedia/screencapture/screenlistmodel.py
@@ -0,0 +1,38 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtCore import QAbstractListModel, Qt, Slot
+
+
+class ScreenListModel(QAbstractListModel):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ app = qApp # noqa: F821
+ app.screenAdded.connect(self.screens_changed)
+ app.screenRemoved.connect(self.screens_changed)
+ app.primaryScreenChanged.connect(self.screens_changed)
+
+ def rowCount(self, index):
+ return len(QGuiApplication.screens())
+
+ def data(self, index, role):
+ screen_list = QGuiApplication.screens()
+
+ if role == Qt.DisplayRole:
+ screen = screen_list[index.row()]
+ w = screen.size().width()
+ h = screen.size().height()
+ dpi = screen.logicalDotsPerInch()
+ return f'"{screen.name()}" {w}x{h}, {dpi}DPI'
+
+ return None
+
+ def screen(self, index):
+ return QGuiApplication.screens()[index.row()]
+
+ @Slot()
+ def screens_changed(self):
+ self.beginResetModel()
+ self.endResetModel()
diff --git a/examples/multimedia/screencapture/windowlistmodel.py b/examples/multimedia/screencapture/windowlistmodel.py
new file mode 100644
index 000000000..079040ec2
--- /dev/null
+++ b/examples/multimedia/screencapture/windowlistmodel.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QAbstractListModel, Qt, Slot
+from PySide6.QtMultimedia import QWindowCapture
+
+
+class WindowListModel(QAbstractListModel):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._window_list = QWindowCapture.capturableWindows()
+
+ def rowCount(self, QModelIndex):
+ return len(self._window_list)
+
+ def data(self, index, role):
+ if role == Qt.DisplayRole:
+ window = self._window_list[index.row()]
+ return window.description()
+ return None
+
+ def window(self, index):
+ return self._window_list[index.row()]
+
+ @Slot()
+ def populate(self):
+ self.beginResetModel()
+ self._window_list = QWindowCapture.capturableWindows()
+ self.endResetModel()