CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-mpv

A python interface to the mpv media player

Pending
Overview
Eval results
Files

screenshots-overlays.mddocs/

Screenshots and Overlays

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.

Capabilities

Screenshot Capture

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'
    """

Overlay Management

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
    """

Image Overlays

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
    """

File-Based Overlays

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
    """

OSD Overlays

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
    """

Overlay Classes

ImageOverlay Class

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."""

FileOverlay Class

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."""

Usage Examples

Basic Screenshots

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

Raw Screenshot Data

# 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')

Image Overlays

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()

File-Based Overlays

# 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()

OSD Text Overlays

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

Dynamic Overlays

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()

Multi-Layer Overlays

# 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()

Screenshot-Based Processing

# 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()

Interactive Overlay Controls

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

docs

advanced-rendering.md

core-playback.md

event-handling.md

index.md

input-keybinding.md

playlist-media.md

property-management.md

screenshots-overlays.md

streaming.md

tile.json