# Pyqt Multimedia

> PyQt/PySide multimedia - audio playback, video playback, camera, audio recording, media player

- Skill: `mte90/pyqt-multimedia` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mte90/pyqt-multimedia`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mte90/pyqt-multimedia/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: mte90 (https://skillmd.com/u/mte90)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mte90/pyqt-multimedia

---


# 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/

## Video Playback (PySide6)

### QMediaPlayer + QVideoWidget

```python
from PySide6.QtMultimedia import QMediaPlayer
from PySide6.QtMultimediaWidgets import QVideoWidget
from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QSlider, QLabel, QMainWindow
)
from PySide6.QtCore import Qt, QUrl, QTimer
from PySide6.QtGui import QAction
import sys

class VideoPlayer(QMainWindow):
    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)
        
        # Toolbar layout
        toolbar = QHBoxLayout()
        toolbar.addWidget(self.play_btn)
        toolbar.addWidget(self.pause_btn)
        toolbar.addWidget(self.stop_btn)
        toolbar.addWidget(self.position_slider)
        toolbar.addWidget(self.time_label)
        toolbar.addWidget(self.volume_slider)
        toolbar.addStretch()
        
        # Main layout
        main_layout = QVBoxLayout()
        main_layout.addWidget(self.video_widget)
        main_layout.addLayout(toolbar)
        self.setCentralWidget(QWidget())
        self.centralLayout = self.centralWidget().layout()
        self.centralLayout.addLayout(toolbar)
        
        # Signal connections
        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.player.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)
        )
        
        # Keyboard shortcuts
        self.installEventFilter(self)
        
    def load_video(self, filepath):
        """Load a video file."""
        self.player.setSource(QUrl.fromLocalFile(filepath))
        
    def position_changed(self, position):
        """Update position slider and time display."""
        self.position_slider.setValue(position)
        self.update_time_label()
        
    def duration_changed(self, duration):
        """Update slider range and initial time display."""
        self.position_slider.setRange(0, duration)
        self.update_time_label()
        
    def update_time_label(self):
        """Convert position/duration to MM:SS format."""
        pos = self.player.position() // 1000
        dur = self.player.duration() // 1000 if self.player.duration() > 0 else 0
        
        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):
        """Handle keyboard shortcuts."""
        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)
            )
        elif event.key() == Qt.Key.Key_k:
            self.player.seek(5)
        elif event.key() == Qt.Key.Key_j:
            self.player.seek(-5)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    # Set application properties
    app.setApplicationName("Video Player")
    app.setOrganizationName("MyOrg")
    
    window = VideoPlayer()
    window.show()
    sys.exit(app.exec())
```

**Controls Reference:**
| Method | Description |
|--------|-------------|
| `play()` | Start playback |
| `pause()` | Pause playback |
| `stop()` | Stop playback and reset position |
| `setPosition(pos)` | Set playback position (milliseconds) |
| `seek(seconds)` | Seek relative to current position |
| `playbackState()` | Returns current state (PlayingState, PausedState, StoppedState) |
| `position()` | Current playback position in milliseconds |
| `duration()` | Total duration in milliseconds |

**Supported Formats:**
- **Audio/Video files**: `.mp4`, `.avi`, `.mkv`, `.mov`, `.flv`, `.webm`, `.wmv`
- **Audio files**: `.mp3`, `.wav`, `.ogg`, `.flac`, `.aac`
- **Real-time**: Camera feed, network streams (RTSP, HTTP)

Note: Codec support varies by platform. On Linux, ensure GStreamer backends are installed.

---

## Camera Access (PySide6)

### QCamera + QMediaCaptureSession

```python
from PySide6.QtMultimedia import QCamera, QMediaDevices, QMediaCaptureSession
from PySide6.QtMultimediaWidgets import QVideoWidget
from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QLabel, QComboBox, QDialogButtonBox
)
from PySide6.QtCore import Qt, QUrl
import sys
from typing import Optional

class CameraViewer(QWidget):
    """Camera viewer with live preview."""
    
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Camera Viewer")
        self.setMinimumSize(640, 480)
        
        # Available cameras
        self.cameras = QMediaDevices.videoInputs()
        if not self.cameras:
            self.init_no_camera()
            return
        
        # Selected camera
        self.camera = QCamera(self.cameras[0])
        
        # Video widget for preview
        self.video_widget = QVideoWidget()
        
        # UI components
        self.camera_label = QLabel("No cameras found")
        self.camera_combo = QComboBox()
        self.camera_combo.addItems([f"Camera {i}" for i in range(len(self.cameras))])
        self.start_btn = QPushButton("Start")
        self.stop_btn = QPushButton("Stop")
        self.preview_label = QLabel("Ready")
        
        # Layout
        layout = QVBoxLayout()
        
        # Camera selection
        select_layout = QHBoxLayout()
        select_layout.addWidget(QLabel("Camera:"))
        select_layout.addWidget(self.camera_combo)
        layout.addLayout(select_layout)
        
        # Video preview
        layout.addWidget(self.camera_label)
        layout.addWidget(self.video_widget)
        
        # Controls
        control_layout = QHBoxLayout()
        control_layout.addWidget(self.start_btn)
        control_layout.addWidget(self.stop_btn)
        layout.addLayout(control_layout)
        
        # Status
        layout.addWidget(self.preview_label)
        layout.addStretch()
        
        self.setLayout(layout)
        
        # Setup camera
        self.setup_camera()
        
        # Connect signals
        self.start_btn.clicked.connect(self.start_camera)
        self.stop_btn.clicked.connect(self.stop_camera)
        
    def init_no_camera(self):
        """Initialize UI when no camera is available."""
        self.camera_label.setText("No camera detected")
        self.preview_label.setText("No camera found")
        self.camera_combo.hide()
        
    def setup_camera(self):
        """Configure camera and connect to video output."""
        camera_index = self.camera_combo.currentIndex()
        camera = self.cameras[camera_index]
        
        self.camera = QCamera(camera)
        self.camera.setVideoOutput(self.video_widget)
        
    def start_camera(self):
        """Start camera and enable preview."""
        camera_index = self.camera_combo.currentIndex()
        
        camera = self.cameras[camera_index]
        self.camera = QCamera(camera)
        
        self.camera.setVideoOutput(self.video_widget)
        self.camera.start()
        self.preview_label.setText("Camera active")
        self.start_btn.setText("Restart")
        self.stop_btn.setEnabled(True)
        
    def stop_camera(self):
        """Stop camera and disable preview."""
        self.camera.stop()
        self.preview_label.setText("Camera stopped")
        self.start_btn.setText("Start")
        self.start_btn.setEnabled(False)
        self.stop_btn.setEnabled(False)
        
    def closeEvent(self, event):
        """Clean up camera resources."""
        self.camera.stop()
        super().closeEvent(event)

class CameraCapture(QWidget):
    """Camera with photo capture functionality."""
    
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Camera Capture")
        self.setMinimumSize(640, 480)
        
        self.camera = QCamera()
        self.capture_session = QMediaCaptureSession()
        self.image_capture = QImageCapture()
        
        self.video_widget = QVideoWidget()
        self.capture_session.setVideoOutput(self.video_widget)
        
        self.capture_session.setCamera(self.camera)
        self.capture_session.setImageCapture(self.image_capture)
        
        # Image capture settings
        self.image_capture.setCaptureMode(
            QImageCapture.CaptureMode.Photo
        )
        self.image_capture.setResolution(
            self.image_capture.resolutionHighest()
        )
        
        self.image_capture.setCompressionType(
            QImageCapture.CompressionType.Jpeg
        )
        self.image_capture.setJpegQuality(85)
        
        self.capture_btn = QPushButton("Capture Photo")
        self.capture_btn.setEnabled(False)
        self.status_label = QLabel("Ready")
        
        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.video_widget)
        layout.addWidget(self.capture_btn)
        layout.addWidget(self.status_label)
        self.setLayout(layout)
        
        # Connect signals
        self.capture_btn.clicked.connect(self.capture_photo)
        self.camera.start()
        
    def capture_photo(self):
        """Capture photo from camera."""
        if self.camera.isRunning():
            self.capture_session.setImageCapture(self.image_capture)
            self.image_capture.triggerCapture(
                self.image_capture.imageSize()
            )
            self.status_label.setText("Capturing...")
            
            # Handle captured image
            self.image_capture.imageReady.connect(self.on_image_ready)
        else:
            self.status_label.setText("Camera not running")
            
    def on_image_ready(self, id: int, filePath: str):
        """Callback when image is captured."""
        self.status_label.setText(f"Photo saved: {filePath}")
        self.capture_btn.setEnabled(True)
        
        # Display captured image
        from PySide6.QtGui import QPixmap, QImage
        pixmap = QPixmap(filePath)
        self.video_widget.setPixmap(pixmap.scaled(
            self.video_widget.size(),
            Qt.AspectRatioMode.KeepAspectRatio,
            Qt.TransformationMode.SmoothTransformation
        ))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    window = CameraViewer()
    window.show()
    sys.exit(app.exec())
```

**Camera Device Enumeration:**

```python
from PySide6.QtMultimedia import QMediaDevices

# Get all video input devices (cameras)
cameras = QMediaDevices.videoInputs()
print(f"Available cameras: {len(cameras)}")
for i, camera in enumerate(cameras):
    print(f"  Camera {i}: {camera}")

# Get all video output devices (displays)
video_outputs = QMediaDevices.videoOutputs()

# Get audio input devices (microphones)
audio_inputs = QMediaDevices.audioInputs()

# Get audio output devices (speakers)
audio_outputs = QMediaDevices.audioOutputs()
```

**Photo Capture Options:**

```python
# Set capture mode
self.image_capture.setCaptureMode(
    QImageCapture.CaptureMode.Photo
)

# Set resolution
self.image_capture.setResolution(
    QImageCapture.Resolution.QVGA  # or QualityBestResolution
)

# Set compression
self.image_capture.setCompressionType(
    QImageCapture.CompressionType.Jpeg
)
self.image_capture.setJpegQuality(85)

# Set custom capture dimensions
self.image_capture.setCaptureSize(640, 480)

# Manual capture trigger
self.image_capture.triggerCapture(QSize(640, 480))
```

---

## Media Recording

### QMediaRecorder Setup

```python
from PySide6.QtMultimedia import (
    QMediaRecorder, QMediaCaptureSession, QCamera, QMediaDevices
)
from PySide6.QtMultimediaWidgets import QVideoWidget
from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QLabel, QFileDialog
)
from PySide6.QtCore import QUrl, QStandardPaths
from PySide6.QtGui import QDesktopServices
import os
import sys

class MediaRecorder(QWidget):
    """Complete media recording with audio/video support."""
    
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Media Recorder")
        self.setMinimumSize(800, 600)
        
        # Camera setup
        self.camera = QCamera(QMediaDevices.videoInputs()[0])
        self.capture_session = QMediaCaptureSession()
        self.capture_session.setCamera(self.camera)
        self.capture_session.setVideoOutput(self.video_widget)
        
        # Video widget
        self.video_widget = QVideoWidget()
        
        # Recorder
        self.recorder = QMediaRecorder()
        self.capture_session.setRecorder(self.recorder)
        
        # UI components
        self.record_btn = QPushButton("Start Recording")
        self.record_btn.setEnabled(False)
        self.stop_btn = QPushButton("Stop Recording")
        self.stop_btn.setEnabled(False)
        
        self.status_label = QLabel("Ready")
        self.progress_label = QLabel("")
        
        self.recorder.recorderStateChanged.connect(self.update_status)
        self.recorder.durationChanged.connect(self.duration_changed)
        
        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.video_widget)
        
        control_layout = QHBoxLayout()
        control_layout.addWidget(self.record_btn)
        control_layout.addWidget(self.stop_btn)
        control_layout.addWidget(self.progress_label)
        layout.addLayout(control_layout)
        
        layout.addWidget(self.status_label)
        layout.addStretch()
        
        self.setLayout(layout)
        
        # Setup recorder defaults
        self.setup_recorder_defaults()
        
        # Connect signals
        self.record_btn.clicked.connect(self.toggle_recording)
        self.stop_btn.clicked.connect(self.stop_recording)
        
    def setup_recorder_defaults(self):
        """Configure default recording settings."""
        # Default output location in Videos folder
        documents = QStandardPaths.writableLocation(
            QStandardPaths.StandardLocation.MoviesLocation
        )
        
        # Get video extension based on codec
        self.output_format = "mp4"
        self.output_path = os.path.join(documents, f"recording_{self._get_timestamp()}.{self.output_format}")
        
    def _get_timestamp(self):
        """Generate timestamp string."""
        from datetime import datetime
        return datetime.now().strftime("%Y%m%d_%H%M%S")
        
    def toggle_recording(self):
        """Toggle recording on/off."""
        state = self.recorder.recorderState()
        
        if state == QMediaRecorder.RecorderState.RecordingState:
            self.recorder.stop()
        else:
            self.recorder.setOutputLocation(QUrl.fromLocalFile(self.output_path))
            self.recorder.record()
            
    def stop_recording(self):
        """Stop current recording."""
        self.recorder.stop()
        self.status_label.setText("Recording stopped")
        self.record_btn.setEnabled(True)
        self.record_btn.setText("Record Again")
        self.stop_btn.setEnabled(False)
        
    def update_status(self, state):
        """Update status based on recorder state."""
        states = {
            QMediaRecorder.RecorderState.StoppedState: "Ready to record",
            QMediaRecorder.RecorderState.RecordingState: "Recording...",
            QMediaRecorder.RecorderState.PausedState: "Paused",
            QMediaRecorder.RecorderState.ErrorState: "Error occurred"
        }
        
        self.status_label.setText(states.get(state, "Unknown"))
        
    def duration_changed(self, duration):
        """Update progress based on recording duration."""
        if duration > 0:
            seconds = duration // 1000
            self.progress_label.setText(f"Recording: {seconds} seconds")
    
    def closeEvent(self, event):
        """Clean up resources."""
        self.recorder.stop()
        self.camera.stop()
        super().closeEvent(event)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    window = MediaRecorder()
    window.show()
    sys.exit(app.exec())
```

### Audio Recording Example

```python
from PySide6.QtMultimedia import QMediaRecorder, QMediaCaptureSession
from PySide6.QtCore import QUrl, QStandardPaths, QMimeType
import os
import sys
from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QFileDialog
)

class AudioRecorder(QWidget):
    """Audio recording from microphone."""
    
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Audio Recorder")
        
        self.recorder = QMediaRecorder()
        self.capture_session = QMediaCaptureSession()
        
        # Audio input setup
        audio_inputs = QStandardPaths.writableLocation(
            QStandardPaths.StandardLocation.MoviesLocation
        )
        
        self.status_label = QLabel("Ready")
        self.record_btn = QPushButton("Start Recording")
        
        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
        self.output_path = os.path.join(
            audio_inputs, "recording.wav"
        )
        
    def toggle_recording(self):
        """Toggle recording state."""
        state = self.recorder.recorderState()
        
        if state == QMediaRecorder.RecorderState.RecordingState:
            self.recorder.stop()
            self.status_label.setText("Recording stopped")
        else:
            self.recorder.setOutputLocation(QUrl.fromLocalFile(self.output_path))
            self.recorder.setAudioFormat(
                QMediaRecorder.AudioFormat.WAV,
                44100,
                2,
                2,
                -16,
                -1
            )
            self.recorder.record()
            self.status_label.setText("Recording...")
            
    def update_status(self, state):
        """Update status label."""
        if state == QMediaRecorder.RecorderState.RecordingState:
            self.status_label.setText("Recording...")
            self.record_btn.setText("Stop")
        else:
            self.status_label.setText("Recording saved")
            self.record_btn.setText("Start Recording")
    
    def save_recording(self):
        """Save recording to file dialog."""
        file_path, _ = QFileDialog.getSaveFileName(
            self, "Save Recording", self.output_path,
            "Audio Files (*.wav *.mp3 *.ogg *.flac);;All Files (*)"
        )
        if file_path:
            self.recorder.setOutputLocation(QUrl.fromLocalFile(file_path))
            self.output_path = file_path

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = AudioRecorder()
    window.show()
    sys.exit(app.exec())
```

### Video Recording Example

```python
from PySide6.QtMultimedia import QMediaRecorder, QMediaCaptureSession
from PySide6.QtCore import QUrl, QStandardPaths
import os
import sys
from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QPushButton, QLabel
)

class VideoRecorder(QWidget):
    """Video recording with encoding options."""
    
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Video Recorder")
        
        self.camera = QCamera(QStandardPaths.writableLocation(
            QStandardPaths.StandardLocation.VideosLocation
        ) + "camera.mp4")
        
        self.capture_session = QMediaCaptureSession()
        self.capture_session.setCamera(self.camera)
        
        self.recorder = QMediaRecorder()
        self.capture_session.setRecorder(self.recorder)
        
        self.status_label = QLabel("Ready")
        self.record_btn = QPushButton("Start Recording")
        
        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)
        
    def toggle_recording(self):
        """Start/stop video recording."""
        state = self.recorder.recorderState()
        
        if state == QMediaRecorder.RecorderState.RecordingState:
            self.recorder.stop()
        else:
            self.recorder.setMediaFormat(self._get_video_format())
            self.recorder.setVideoCodec(self.recorder.videoFormat().videoCodec())
            self.recorder.setAudioCodec(self.recorder.audioFormat().audioCodec())
            self.recorder.setOutputLocation(QUrl.fromLocalFile("video.mp4"))
            self.recorder.record()
            
    def _get_video_format(self):
        """Get H.264 video format."""
        return QMediaFormat(
            QMediaFormat.MediaFormat.MPEG4,
            self.camera.videoResolution(),
            QMediaFormat.VelocityLevel.Normal,
            2000000,  # 2 Mbps bitrate
            QMediaFormat.ResolutionSelector.Variable
        )
        
    def update_status(self, state):
        """Update status."""
        if state == QMediaRecorder.RecorderState.RecordingState:
            self.status_label.setText("Recording video...")
        else:
            self.status_label.setText("Recording complete")
    
    def save_recording(self):
        """Open save dialog."""
        file_path, _ = QFileDialog.getSaveFileName(
            self, "Save Video", "recording.mp4",
            "Video Files (*.mp4 *.avi *.mkv);;All Files (*)"
        )
        if file_path:
            self.recorder.setOutputLocation(QUrl.fromLocalFile(file_path))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = VideoRecorder()
    window.show()
    sys.exit(app.exec())
```

### Encoding Settings

```python
from PySide6.QtMultimedia import QMediaFormat, QMediaRecorder

# Configure video format
video_format = QMediaFormat()
video_format.

…(truncated)
