diff options
Diffstat (limited to 'examples/multimedia')
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 Binary files differnew file mode 100644 index 000000000..d19f959cd --- /dev/null +++ b/examples/multimedia/audiooutput/doc/audiooutput.png 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 Binary files differnew file mode 100644 index 000000000..cac183b75 --- /dev/null +++ b/examples/multimedia/audiosource/doc/audiosource.png 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 Binary files differnew file mode 100644 index 000000000..11ed18792 --- /dev/null +++ b/examples/multimedia/camera/doc/camera.webp 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 Binary files differnew file mode 100644 index 000000000..f751d4a82 --- /dev/null +++ b/examples/multimedia/player/doc/player.png 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 Binary files differnew file mode 100644 index 000000000..58ad36c7f --- /dev/null +++ b/examples/multimedia/screencapture/doc/screencapture.webp 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() |