A python interface to the mpv media player
—
Screenshot capture with multiple output formats and overlay system for displaying images and text on top of video content. Supports various image formats, positioning, and dynamic overlay management.
Capture screenshots in various formats and configurations.
def screenshot(self, includes: str = 'subtitles', mode: str = 'single'):
"""
Take a screenshot and save to default location.
Parameters:
- includes: What to include ('subtitles', 'video', 'window')
- mode: Screenshot mode ('single', 'each-frame')
'single': Take one screenshot
'each-frame': Screenshot every frame
"""
def screenshot_to_file(self, filename: str, includes: str = 'subtitles'):
"""
Take a screenshot and save to specific file.
Parameters:
- filename: Output filename (extension determines format)
- includes: What to include ('subtitles', 'video', 'window')
"""
def screenshot_raw(self, includes: str = 'subtitles'):
"""
Take a screenshot and return raw image data.
Parameters:
- includes: What to include ('subtitles', 'video', 'window')
Returns:
Dictionary with image data, width, height, stride, and format information
Note: Requires Pillow for image processing if includes != 'video'
"""Create and manage image and text overlays on video content.
def allocate_overlay_id(self) -> int:
"""
Allocate a unique overlay ID.
Returns:
Integer overlay ID for use with overlay functions
"""
def free_overlay_id(self, overlay_id: int):
"""
Free an allocated overlay ID.
Parameters:
- overlay_id: ID to free for reuse
"""
def remove_overlay(self, overlay_id: int):
"""
Remove an overlay by ID.
Parameters:
- overlay_id: ID of overlay to remove
"""Display images on top of video content with positioning control.
def create_image_overlay(self, img=None, pos=(0, 0)) -> 'ImageOverlay':
"""
Create an image overlay.
Parameters:
- img: PIL Image object or image data
- pos: (x, y) position tuple
Returns:
ImageOverlay object for managing the overlay
"""
def overlay_add(self, overlay_id: int, x: int, y: int, file_or_fd, offset: int, fmt: str, w: int, h: int, stride: int):
"""
Add a low-level overlay.
Parameters:
- overlay_id: Overlay identifier
- x, y: Position coordinates
- file_or_fd: File path or file descriptor
- offset: Byte offset in file
- fmt: Image format ('bgra', 'rgba', etc.)
- w, h: Image dimensions
- stride: Bytes per row
"""
def overlay_remove(self, overlay_id: int):
"""
Remove a low-level overlay.
Parameters:
- overlay_id: Overlay identifier to remove
"""Display images from files with efficient file-based rendering.
def create_file_overlay(self, filename: str = None, size: tuple = None, stride: int = None, pos: tuple = (0, 0)) -> 'FileOverlay':
"""
Create a file-based overlay.
Parameters:
- filename: Path to image file
- size: (width, height) tuple
- stride: Bytes per row (auto-calculated if not provided)
- pos: (x, y) position tuple
Returns:
FileOverlay object for managing the overlay
"""Display text and graphics using mpv's On-Screen Display system.
def osd_overlay(self, overlay_id: int, data: str, res_x: int = 0, res_y: int = 720, z: int = 0, hidden: bool = False):
"""
Create an OSD overlay with ASS subtitle formatting.
Parameters:
- overlay_id: Overlay identifier
- data: ASS-formatted text/graphics data
- res_x: X resolution (0 for auto)
- res_y: Y resolution (720 default)
- z: Z-order (higher values on top)
- hidden: Whether overlay starts hidden
"""
def osd_overlay_remove(self, overlay_id: int):
"""
Remove an OSD overlay.
Parameters:
- overlay_id: Overlay identifier to remove
"""class ImageOverlay:
"""Manages image-based overlays with PIL integration."""
def __init__(self, m: 'MPV', overlay_id: int, img=None, pos: tuple = (0, 0)):
"""
Initialize image overlay.
Parameters:
- m: MPV instance
- overlay_id: Unique overlay identifier
- img: PIL Image object or None
- pos: (x, y) position tuple
"""
def update(self, img=None, pos: tuple = None):
"""
Update overlay image and/or position.
Parameters:
- img: New PIL Image object (None to keep current)
- pos: New (x, y) position (None to keep current)
"""
def remove(self):
"""Remove this overlay from the video."""class FileOverlay:
"""Manages file-based overlays for efficient image display."""
def __init__(self, m: 'MPV', overlay_id: int, filename: str = None, size: tuple = None, stride: int = None, pos: tuple = (0, 0)):
"""
Initialize file overlay.
Parameters:
- m: MPV instance
- overlay_id: Unique overlay identifier
- filename: Path to image file
- size: (width, height) image dimensions
- stride: Bytes per row
- pos: (x, y) position tuple
"""
def update(self, filename: str = None, size: tuple = None, stride: int = None, pos: tuple = None):
"""
Update overlay file and/or properties.
Parameters:
- filename: New image file path
- size: New (width, height) dimensions
- stride: New bytes per row
- pos: New (x, y) position
"""
def remove(self):
"""Remove this overlay from the video."""import mpv
player = mpv.MPV()
player.play('/path/to/video.mp4')
player.wait_until_playing()
# Take screenshot to default location
player.screenshot()
# Save to specific file
player.screenshot_to_file('/path/to/screenshot.png')
# Different screenshot modes
player.screenshot(includes='video') # Video only, no subtitles
player.screenshot(includes='window') # Entire window
player.screenshot(includes='subtitles') # Video with subtitles (default)
# Save in different formats
player.screenshot_to_file('capture.jpg') # JPEG format
player.screenshot_to_file('capture.png') # PNG format
player.screenshot_to_file('capture.webp') # WebP format# Get raw screenshot data for processing
screenshot_data = player.screenshot_raw()
print(f"Image size: {screenshot_data['width']}x{screenshot_data['height']}")
print(f"Format: {screenshot_data['format']}")
print(f"Stride: {screenshot_data['stride']}")
# Access raw pixel data
pixel_data = screenshot_data['data']
# Convert to PIL Image (if Pillow is available)
if 'pil_image' in screenshot_data:
pil_img = screenshot_data['pil_image']
pil_img.save('processed_screenshot.png')
# Apply image processing
from PIL import ImageEnhance
enhancer = ImageEnhance.Brightness(pil_img)
bright_img = enhancer.enhance(1.5)
bright_img.save('bright_screenshot.png')from PIL import Image
player = mpv.MPV()
player.play('/path/to/video.mp4')
# Create image overlay
logo = Image.open('/path/to/logo.png')
overlay = player.create_image_overlay(logo, pos=(50, 50))
# Update overlay position
overlay.update(pos=(100, 100))
# Update overlay image
new_logo = Image.open('/path/to/new_logo.png')
overlay.update(img=new_logo)
# Remove overlay
overlay.remove()# More efficient for static images
overlay = player.create_file_overlay(
filename='/path/to/watermark.png',
pos=(10, 10)
)
# Update file overlay
overlay.update(
filename='/path/to/different_watermark.png',
pos=(20, 20)
)
# Clean up
overlay.remove()# Simple text overlay
overlay_id = player.allocate_overlay_id()
player.osd_overlay(
overlay_id,
"Hello World!",
res_y=720,
z=1
)
# Formatted text with ASS styling
ass_text = "{\\an7}{\\fs24}{\\c&H00FF00&}Green Text in Top-Left"
player.osd_overlay(overlay_id, ass_text)
# Complex OSD with positioning and styling
complex_osd = """
{\\an1}{\\pos(10,500)}{\\fs20}{\\c&HFFFFFF&}Title: {\\c&H00FFFF&}Video Name
{\\an1}{\\pos(10,530)}{\\fs16}{\\c&HCCCCCC&}Duration: 01:23:45
{\\an1}{\\pos(10,550)}{\\fs16}{\\c&HCCCCCC&}Quality: 1080p
"""
player.osd_overlay(overlay_id, complex_osd)
# Remove OSD overlay
player.osd_overlay_remove(overlay_id)
player.free_overlay_id(overlay_id)import time
import threading
from PIL import Image, ImageDraw, ImageFont
class LiveOverlay:
def __init__(self, player):
self.player = player
self.overlay = None
self.running = False
def start_time_overlay(self):
"""Display current time as overlay."""
self.overlay = self.player.create_image_overlay(pos=(10, 10))
self.running = True
def update_time():
while self.running:
# Create time image
img = Image.new('RGBA', (200, 50), (0, 0, 0, 128))
draw = ImageDraw.Draw(img)
current_time = time.strftime('%H:%M:%S')
draw.text((10, 15), current_time, fill='white')
# Update overlay
self.overlay.update(img=img)
time.sleep(1)
self.thread = threading.Thread(target=update_time)
self.thread.start()
def stop(self):
self.running = False
if hasattr(self, 'thread'):
self.thread.join()
if self.overlay:
self.overlay.remove()
# Usage
player = mpv.MPV()
live_overlay = LiveOverlay(player)
player.play('/path/to/video.mp4')
live_overlay.start_time_overlay()
# Later...
live_overlay.stop()# Create multiple overlays with different Z-orders
overlays = []
# Background overlay (lowest Z-order)
bg_overlay_id = player.allocate_overlay_id()
player.osd_overlay(bg_overlay_id,
"{\\pos(10,10)}{\\c&H000000&}{\\1a&H80&}Background",
z=0)
# Foreground overlay (higher Z-order)
fg_overlay_id = player.allocate_overlay_id()
player.osd_overlay(fg_overlay_id,
"{\\pos(15,15)}{\\c&HFFFFFF&}Foreground Text",
z=1)
# Image overlay (highest priority)
logo_overlay = player.create_image_overlay(
Image.open('/path/to/logo.png'),
pos=(200, 200)
)
overlays.extend([bg_overlay_id, fg_overlay_id, logo_overlay])
# Clean up all overlays
for overlay_id in [bg_overlay_id, fg_overlay_id]:
player.osd_overlay_remove(overlay_id)
player.free_overlay_id(overlay_id)
logo_overlay.remove()# Automated screenshot processing
def process_screenshots():
player.play('/path/to/video.mp4')
player.wait_until_playing()
# Take screenshots at intervals
duration = player.duration
interval = 10 # Every 10 seconds
screenshots = []
for pos in range(0, int(duration), interval):
player.seek(pos, reference='absolute')
time.sleep(0.1) # Wait for seek
# Capture and process
data = player.screenshot_raw(includes='video')
if 'pil_image' in data:
img = data['pil_image']
# Apply processing
processed = img.resize((320, 240))
screenshots.append(processed)
# Create thumbnail montage
if screenshots:
montage_width = 5 * 320 # 5 thumbnails wide
montage_height = ((len(screenshots) + 4) // 5) * 240
montage = Image.new('RGB', (montage_width, montage_height))
for i, thumb in enumerate(screenshots):
x = (i % 5) * 320
y = (i // 5) * 240
montage.paste(thumb, (x, y))
montage.save('video_montage.png')
# Usage
process_screenshots()class InteractiveOverlay:
def __init__(self, player):
self.player = player
self.overlay_id = player.allocate_overlay_id()
self.visible = True
def toggle_info_display(self):
"""Toggle information overlay."""
if self.visible:
self.hide_info()
else:
self.show_info()
def show_info(self):
"""Show current playback information."""
pos = self.player.time_pos or 0
duration = self.player.duration or 0
filename = self.player.filename or "Unknown"
info_text = f"""
{{\\an7}}{{\\pos(10,10)}}{{\\fs16}}{{\\c&HFFFFFF&}}
File: {{\\c&H00FFFF&}}{filename}
Time: {{\\c&H00FF00&}}{pos:.1f}s / {duration:.1f}s
Volume: {{\\c&HFF8000&}}{self.player.volume}%
"""
self.player.osd_overlay(self.overlay_id, info_text, z=10)
self.visible = True
def hide_info(self):
"""Hide information overlay."""
self.player.osd_overlay_remove(self.overlay_id)
self.visible = False
def cleanup(self):
"""Clean up overlay resources."""
if self.visible:
self.hide_info()
self.player.free_overlay_id(self.overlay_id)
# Bind to key press
interactive = InteractiveOverlay(player)
@player.key_binding('i')
def toggle_info():
interactive.toggle_info_display()
# Clean up when done
# interactive.cleanup()Install with Tessl CLI
npx tessl i tessl/pypi-mpv@1.0.2