--- name: pyqt-multimedia description: "PyQt/PySide multimedia - audio playback, video playback, camera, audio recording, media player" metadata: author: mte90 version: 1.0.0 tags: - python - qt - pyqt - multimedia - audio - video - camera - media --- # PyQt/PySide Multimedia Audio and video playback, camera capture, and media processing in PyQt/PySide. ## Overview Qt Multimedia provides classes for audio, video, and camera functionality: - **QMediaPlayer** - Audio/video playback - **QVideoWidget** - Video display - **QAudioOutput** - Audio output management - **QCamera** - Camera capture - **QMediaRecorder** - Audio/video recording --- ## Audio Playback ### Basic Audio Player ```python from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput from PyQt6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget, QSlider, QLabel from PyQt6.QtCore import Qt, QUrl import sys class AudioPlayer(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Audio Player") self.player = QMediaPlayer() self.audio_output = QAudioOutput() self.player.setAudioOutput(self.audio_output) # UI self.play_btn = QPushButton("Play") self.pause_btn = QPushButton("Pause") self.stop_btn = QPushButton("Stop") self.label = QLabel("No file loaded") self.volume_slider = QSlider(Qt.Orientation.Horizontal) self.volume_slider.setRange(0, 100) self.volume_slider.setValue(50) layout = QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.play_btn) layout.addWidget(self.pause_btn) layout.addWidget(self.stop_btn) layout.addWidget(self.volume_slider) self.setLayout(layout) # Connections self.play_btn.clicked.connect(self.player.play) self.pause_btn.clicked.connect(self.player.pause) self.stop_btn.clicked.connect(self.player.stop) self.volume_slider.valueChanged.connect( lambda v: self.audio_output.setVolume(v / 100) ) self.player.positionChanged.connect(self.update_position) def load_file(self, filepath): self.player.setSource(QUrl.fromLocalFile(filepath)) self.label.setText(filepath.split('/')[-1]) def update_position(self, position): # position in milliseconds seconds = position // 1000 minutes = seconds // 60 seconds = seconds % 60 print(f"{minutes:02d}:{seconds:02d}") if __name__ == "__main__": app = QApplication(sys.argv) window = AudioPlayer() window.show() sys.exit(app.exec()) ``` ### Audio Playlist ```python from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput from PyQt6.QtCore import QUrl, QModelIndex from PyQt6.QtWidgets import QListView class PlaylistPlayer: def __init__(self, playlist_view: QListView): self.player = QMediaPlayer() self.audio_output = QAudioOutput() self.player.setAudioOutput(self.audio_output) self.playlist = [] # List of file paths self.current_index = -1 def add_to_playlist(self, filepath): self.playlist.append(filepath) def play_index(self, index: int): if 0 <= index < len(self.playlist): self.current_index = index self.player.setSource(QUrl.fromLocalFile(self.playlist[index])) self.player.play() def next(self): if self.playlist: self.current_index = (self.current_index + 1) % len(self.playlist) self.play_index(self.current_index) def previous(self): if self.playlist: self.current_index = (self.current_index - 1) % len(self.playlist) self.play_index(self.current_index) ``` --- ## Video Playback ### Video Player with Controls ```python from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput from PyQt6.QtMultimediaWidgets import QVideoWidget from PyQt6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QSlider, QLabel ) from PyQt6.QtCore import Qt, QUrl, QTimer from PyQt6.QtGui import QAction import sys class VideoPlayer(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Video Player") self.resize(800, 600) # Media player self.player = QMediaPlayer() self.audio_output = QAudioOutput() self.player.setAudioOutput(self.audio_output) # Video widget self.video_widget = QVideoWidget() # Controls self.play_btn = QPushButton("Play") self.pause_btn = QPushButton("Pause") self.stop_btn = QPushButton("Stop") self.position_slider = QSlider(Qt.Orientation.Horizontal) self.position_slider.setRange(0, 0) self.time_label = QLabel("00:00 / 00:00") self.volume_slider = QSlider(Qt.Orientation.Horizontal) self.volume_slider.setRange(0, 100) self.volume_slider.setValue(50) # Layout control_layout = QHBoxLayout() control_layout.addWidget(self.play_btn) control_layout.addWidget(self.pause_btn) control_layout.addWidget(self.stop_btn) control_layout.addWidget(self.position_slider) control_layout.addWidget(self.time_label) control_layout.addWidget(self.volume_slider) main_layout = QVBoxLayout() main_layout.addWidget(self.video_widget) main_layout.addLayout(control_layout) self.setLayout(main_layout) # Connect self.player.setVideoOutput(self.video_widget) self.play_btn.clicked.connect(self.player.play) self.pause_btn.clicked.connect(self.player.pause) self.stop_btn.clicked.connect(self.stop) self.player.positionChanged.connect(self.position_changed) self.player.durationChanged.connect(self.duration_changed) self.volume_slider.valueChanged.connect( lambda v: self.audio_output.setVolume(v / 100) ) def load_video(self, filepath): self.player.setSource(QUrl.fromLocalFile(filepath)) def stop(self): self.player.stop() self.position_slider.setValue(0) def position_changed(self, position): self.position_slider.setValue(position) self.update_time_label() def duration_changed(self, duration): self.position_slider.setRange(0, duration) self.update_time_label() def update_time_label(self): pos = self.player.position() // 1000 dur = self.player.duration() // 1000 pos_m, pos_s = divmod(pos, 60) dur_m, dur_s = divmod(dur, 60) self.time_label.setText( f"{pos_m:02d}:{pos_s:02d} / {dur_m:02d}:{dur_s:02d}" ) def keyPressEvent(self, event): if event.key() == Qt.Key.Key_Space: if self.player.playbackState() == QMediaPlayer.PlaybackState.PlayingState: self.player.pause() else: self.player.play() elif event.key() == Qt.Key.Key_Left: self.player.setPosition(max(0, self.player.position() - 5000)) elif event.key() == Qt.Key.Key_Right: self.player.setPosition( min(self.player.duration(), self.player.position() + 5000) ) ``` ### Fullscreen Video ```python class FullscreenVideoPlayer(VideoPlayer): def __init__(self): super().__init__() self.is_fullscreen = False self.video_widget.doubleClicked.connect(self.toggle_fullscreen) def toggle_fullscreen(self): if self.is_fullscreen: self.showNormal() else: self.showFullScreen() self.is_fullscreen = not self.is_fullscreen ``` --- ## Camera Capture ### Display Camera Feed ```python from PyQt6.QtMultimedia import QCamera, QMediaDevices from PyQt6.QtMultimediaWidgets import QVideoWidget from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton from PyQt6.QtCore import Qt import sys class CameraViewer(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Camera Viewer") # Get available cameras self.cameras = QMediaDevices.videoInputs() if not self.cameras: print("No cameras available") return # Create camera self.camera = QCamera(self.cameras[0]) # Video widget self.video_widget = QVideoWidget() # Buttons self.start_btn = QPushButton("Start") self.stop_btn = QPushButton("Stop") layout = QVBoxLayout() layout.addWidget(self.video_widget) layout.addWidget(self.start_btn) layout.addWidget(self.stop_btn) self.setLayout(layout) # Connect camera to widget self.camera.setVideoOutput(self.video_widget) self.start_btn.clicked.connect(self.camera.start) self.stop_btn.clicked.connect(self.camera.stop) def closeEvent(self, event): self.camera.stop() super().closeEvent(event) ``` ### Capture Photo ```python from PyQt6.QtMultimedia import QCamera, QMediaCaptureSession, QImageCapture from PyQt6.QtMultimediaWidgets import QVideoWidget from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel from PyQt6.QtCore import QUrl class CameraCapture(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Camera Capture") self.camera = QCamera() self.capture_session = QMediaCaptureSession() self.capture_session.setCamera(self.camera) self.video_widget = QVideoWidget() self.capture_session.setVideoOutput(self.video_widget) # Image capture self.image_capture = QImageCapture() self.capture_session.setImageCapture(self.image_capture) self.capture_btn = QPushButton("Capture Photo") self.preview_label = QLabel() layout = QVBoxLayout() layout.addWidget(self.video_widget) layout.addWidget(self.capture_btn) layout.addWidget(self.preview_label) self.setLayout(layout) self.capture_btn.clicked.connect(self.capture_photo) self.camera.start() def capture_photo(self): self.image_capture.captureToFile() def handle_captured(self, id, filePath): print(f"Photo saved to: {filePath}") ``` ### Record Video ```python from PyQt6.QtMultimedia import QCamera, QMediaCaptureSession, QMediaRecorder from PyQt6.QtMultimediaWidgets import QVideoWidget from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel class VideoRecorder(QWidget): def __init__(self): super().__init__() self.camera = QCamera() self.capture_session = QMediaCaptureSession() self.capture_session.setCamera(self.camera) self.video_widget = QVideoWidget() self.capture_session.setVideoOutput(self.video_widget) # Recorder self.recorder = QMediaRecorder() self.capture_session.setRecorder(self.recorder) self.record_btn = QPushButton("Start Recording") self.status_label = QLabel("Ready") layout = QVBoxLayout() layout.addWidget(self.video_widget) layout.addWidget(self.record_btn) layout.addWidget(self.status_label) self.setLayout(layout) self.record_btn.clicked.connect(self.toggle_recording) self.recorder.recorderStateChanged.connect(self.update_status) def toggle_recording(self): if self.recorder.recorderState() == QMediaRecorder.RecorderState.RecordingState: self.recorder.stop() else: self.recorder.setOutputLocation(QUrl.fromLocalFile("output.mp4")) self.recorder.record() def update_status(self, state): states = { QMediaRecorder.RecorderState.StoppedState: "Stopped", QMediaRecorder.RecorderState.RecordingState: "Recording", QMediaRecorder.RecorderState.PausedState: "Paused" } self.status_label.setText(states.get(state, "Unknown")) ``` --- ## Audio Recording ### Microphone Input ```python from PyQt6.QtMultimedia import QMediaRecorder, QMediaCaptureSession, QAudioInput from PyQt6.QtCore import QUrl, QStandardPaths from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel import os class AudioRecorder(QWidget): def __init__(self): super().__init__() self.recorder = QMediaRecorder() self.capture_session = QMediaCaptureSession() self.audio_input = QAudioInput() self.capture_session.setAudioInput(self.audio_input) self.capture_session.setRecorder(self.recorder) self.record_btn = QPushButton("Start Recording") self.status_label = QLabel("Ready") layout = QVBoxLayout() layout.addWidget(self.record_btn) layout.addWidget(self.status_label) self.setLayout(layout) self.record_btn.clicked.connect(self.toggle_recording) self.recorder.recorderStateChanged.connect(self.update_status) # Default output location documents = QStandardPaths.writableLocation( QStandardPaths.StandardLocation.MoviesLocation ) self.output_path = os.path.join(documents, "recording.mp3") def toggle_recording(self): if self.recorder.recorderState() == QMediaRecorder.RecorderState.RecordingState: self.recorder.stop() else: self.recorder.setOutputLocation(QUrl.fromLocalFile(self.output_path)) self.recorder.record() def update_status(self, state): if state == QMediaRecorder.RecorderState.RecordingState: self.record_btn.setText("Stop Recording") self.status_label.setText("Recording...") else: self.record_btn.setText("Start Recording") self.status_label.setText("Ready") ``` --- ## GStreamer Backend ### Install GStreamer (Linux) ```bash # Ubuntu/Debian sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly # For audio/video codecs sudo apt-get install gstreamer1.0-libav ``` ### Install on Windows PySide6 on Windows typically uses DirectShow or WMF backends. Install K-Lite Codec Pack for additional codec support. --- ## Common Issues ### No Audio/Video Output ```python # Check available codecs from PyQt6.QtMultimedia import QMediaDevices print("Audio outputs:", QMediaDevices.audioOutputs()) print("Video outputs:", QMediaDevices.videoOutputs()) # Check camera print("Cameras:", QMediaDevices.videoInputs()) ``` ### Format Not Supported ```python # Convert using QMediaEncoder with specific codec from PyQt6.QtMultimedia import QMediaRecorder, QMediaFormat recorder = QMediaRecorder() recorder.setMediaFormat(QMediaFormat.MediaFormat.MPEG4) recorder.setAudioCodec(QMediaFormat.AudioCodec.AAC) recorder.setVideoCodec(QMediaFormat.VideoCodec.H264) ``` --- ## Best Practices ### Audio/Video Capture ```python # ✅ GOOD: Check availability first from PyQt6.QtMultimedia import QMediaDevices if not QMediaDevices.audioInputs(): print("No microphone available") return # ✅ GOOD: Set output location before recording recorder.setOutputLocation(QUrl.fromLocalFile(path)) recorder.record() # Start after setting location ``` ### Playback ```python # ✅ GOOD: Check player state player.play() # Wait for state change signal, don't assume immediate playback # ✅ GOOD: Handle missing codecs # Install K-Lite on Windows, gstreamer on Linux ``` ### Resource Management ```python # ✅ GOOD: Clean up resources def closeEvent(self, event): self.player.stop() self.recorder.stop() self.camera.stop() super().closeEvent(event) ``` ### Do: - Check device availability before use - Set output location before recording - Clean up on close ### Don't: - Record without checking available disk space - Use unsupported formats - Forget to stop capture sessions --- ## References - **Qt Multimedia**: https://doc.qt.io/qt-6/qtmultimedia-index.html - **PySide6 Multimedia**: https://doc.qt.io/qt-6/multimedia.html - **GStreamer**: https://gstreamer.freedesktop.org/