CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-mpv

A python interface to the mpv media player

Pending
Overview
Eval results
Files

input-keybinding.mddocs/

Input and Key Binding

Input event handling, custom key bindings, mouse interaction, and OSD (On-Screen Display) control. Provides comprehensive control over user input and visual feedback systems.

Capabilities

Keyboard Input

Send keyboard events and manage key bindings for interactive control.

def keypress(self, name: str):
    """
    Send a key press event.

    Parameters:
    - name: Key name (e.g., 'SPACE', 'Enter', 'Esc', 'LEFT', 'a', 'ctrl+c')
    """

def keydown(self, name: str):
    """
    Send a key down event (without release).

    Parameters:
    - name: Key name
    """

def keyup(self, name: str = None):
    """
    Send a key up event.

    Parameters:
    - name: Key name (None for last key pressed)
    """

def keybind(self, name: str, command: str):
    """
    Create a simple key binding to mpv command.

    Parameters:
    - name: Key combination
    - command: mpv command string to execute
    """

Advanced Key Binding

Register Python callbacks for key events with flexible binding modes.

def register_key_binding(self, keydef: str, callback_or_cmd, mode: str = 'force'):
    """
    Register a key binding with callback or command.

    Parameters:
    - keydef: Key definition string
    - callback_or_cmd: Python function or mpv command string
    - mode: Binding mode ('force', 'weak')
           'force': Override existing bindings
           'weak': Only bind if no existing binding
    """

def unregister_key_binding(self, keydef: str):
    """
    Remove a key binding.

    Parameters:
    - keydef: Key definition to remove
    """

def key_binding(self, keydef: str, mode: str = 'force'):
    """
    Decorator for registering key binding callbacks.

    Parameters:
    - keydef: Key definition string
    - mode: Binding mode ('force', 'weak')

    Returns:
    Decorator function for callback registration
    """

def on_key_press(self, keydef: str, mode: str = 'force', repetition: bool = False):
    """
    Decorator for key press event handling.

    Parameters:
    - keydef: Key definition string
    - mode: Binding mode ('force', 'weak')
    - repetition: Whether to handle key repetition

    Returns:
    Decorator function for callback registration
    """

Mouse Input

Handle mouse events including clicks, movement, and scroll wheel.

def mouse(self, x: int, y: int, button: int = None, mode: str = 'single'):
    """
    Send mouse event.

    Parameters:
    - x, y: Mouse coordinates
    - button: Mouse button (0=left, 1=middle, 2=right)
    - mode: Click mode ('single', 'double')
    """

On-Screen Display (OSD)

Control mpv's built-in OSD for displaying information and status.

def toggle_osd(self):
    """Toggle OSD visibility through different states."""

def print_text(self, text: str):
    """
    Print text to terminal/console.

    Parameters:
    - text: Text to print
    """

def show_text(self, string: str, duration: str = '-1', level: int = 0):
    """
    Display text on the OSD.

    Parameters:
    - string: Text to display (supports property expansion)
    - duration: Display duration in milliseconds ('-1' for default)
    - level: OSD level (0=subtitles, 1=seek bar, 2=always visible)
    """

def show_progress(self):
    """Show progress bar on OSD."""

Text Processing

Process text templates with property expansion for dynamic content.

def expand_text(self, text: str) -> str:
    """
    Expand text template with property values.

    Parameters:
    - text: Template text with ${property} placeholders

    Returns:
    Expanded text with property values substituted
    """

def expand_path(self, path: str) -> str:
    """
    Expand path template with mpv path expansion.

    Parameters:
    - path: Path template

    Returns:
    Expanded path string
    """

Key Definition Format

Key definitions use mpv's key naming convention:

Basic Keys

  • Letter keys: a, b, c, etc.
  • Number keys: 0, 1, 2, etc.
  • Function keys: F1, F2, F3, etc.
  • Arrow keys: LEFT, RIGHT, UP, DOWN
  • Special keys: SPACE, Enter, Esc, Tab, Backspace

Modifier Keys

  • ctrl+key: Control modifier
  • alt+key: Alt modifier
  • shift+key: Shift modifier
  • meta+key: Meta/Windows modifier

Mouse Events

  • MOUSE_BTN0: Left mouse button
  • MOUSE_BTN1: Middle mouse button
  • MOUSE_BTN2: Right mouse button
  • WHEEL_UP, WHEEL_DOWN: Mouse wheel

Combined Modifiers

  • ctrl+alt+key: Multiple modifiers
  • shift+ctrl+F1: Function key with modifiers

Usage Examples

Basic Key Bindings

import mpv

player = mpv.MPV()

# Simple command bindings
player.keybind('SPACE', 'cycle pause')
player.keybind('f', 'cycle fullscreen')  
player.keybind('m', 'cycle mute')
player.keybind('LEFT', 'seek -10')
player.keybind('RIGHT', 'seek 10')

# Volume control
player.keybind('UP', 'add volume 5')
player.keybind('DOWN', 'add volume -5')

# Subtitle controls
player.keybind('s', 'cycle sub-visibility')
player.keybind('j', 'cycle sub')

Python Callback Bindings

# Using decorator
@player.key_binding('q')
def quit_handler():
    print("Quit requested")
    player.quit()

@player.key_binding('i')  
def info_handler():
    print(f"Playing: {player.filename}")
    print(f"Position: {player.time_pos}/{player.duration}")
    player.show_text(f"Time: ${time-pos}/${duration}")

# Using method registration
def screenshot_handler():
    filename = f"screenshot_{int(player.time_pos)}.png"
    player.screenshot_to_file(filename)
    player.show_text(f"Screenshot saved: {filename}", duration='2000')

player.register_key_binding('p', screenshot_handler)

# Advanced callback with key info
def debug_key_handler():
    pos = player.time_pos or 0
    vol = player.volume
    paused = player.pause
    
    debug_info = f"Pos:{pos:.1f} Vol:{vol} Paused:{paused}"
    print(debug_info)
    player.show_text(debug_info, level=1)

player.register_key_binding('d', debug_key_handler)

Mouse Interaction

# Mouse click handlers
def handle_mouse_click():
    # Toggle pause on mouse click
    player.pause = not player.pause

player.register_key_binding('MOUSE_BTN0', handle_mouse_click)

# Mouse wheel for volume
def wheel_up():
    player.property_add('volume', 5)
    player.show_text(f"Volume: ${volume}")

def wheel_down():
    player.property_add('volume', -5)
    player.show_text(f"Volume: ${volume}")

player.register_key_binding('WHEEL_UP', wheel_up)
player.register_key_binding('WHEEL_DOWN', wheel_down)

# Programmatic mouse events
def click_center():
    width = player.width or 800
    height = player.height or 600
    player.mouse(width // 2, height // 2, button=0)

# Double-click for fullscreen
def double_click_fullscreen():
    player.mouse(400, 300, mode='double')
    player.fullscreen = not player.fullscreen

player.register_key_binding('ctrl+MOUSE_BTN0', double_click_fullscreen)

Advanced Key Binding

# Context-sensitive bindings
def smart_seek():
    """Smart seeking based on current position."""
    pos = player.time_pos or 0
    duration = player.duration or 1
    
    # Larger jumps near beginning/end
    if pos < duration * 0.1 or pos > duration * 0.9:
        seek_amount = 30
    else:
        seek_amount = 10
    
    player.seek(seek_amount)
    player.show_text(f"Seek +{seek_amount}s")

player.register_key_binding('ctrl+RIGHT', smart_seek)

# Multi-key sequences (custom implementation)
class MultiKeyHandler:
    def __init__(self, player):
        self.player = player
        self.sequence = []
        self.timeout = None
    
    def handle_key(self, key):
        self.sequence.append(key)
        
        # Check for known sequences
        seq_str = ''.join(self.sequence)
        if seq_str == 'gg':  # Go to beginning
            self.player.seek(0, reference='absolute')
            self.reset()
        elif seq_str == 'GG':  # Go to end
            self.player.seek(100, reference='percent')  
            self.reset()
        elif len(self.sequence) >= 2:
            self.reset()
    
    def reset(self):
        self.sequence = []

multi_key = MultiKeyHandler(player)

@player.key_binding('g')
def handle_g():
    multi_key.handle_key('g')

@player.key_binding('G') 
def handle_G():
    multi_key.handle_key('G')

OSD and Text Display

# Property-based text display
@player.key_binding('t')
def show_time():
    player.show_text("Time: ${time-pos} / ${duration}")

@player.key_binding('v')
def show_volume():
    player.show_text("Volume: ${volume}%", duration='1500')

@player.key_binding('r')
def show_resolution():
    text = "Resolution: ${width}x${height} @ ${fps} FPS"
    player.show_text(text, level=1)

# Custom OSD formatting
def format_time_display():
    pos = player.time_pos or 0
    dur = player.duration or 0
    
    pos_min, pos_sec = divmod(int(pos), 60)
    dur_min, dur_sec = divmod(int(dur), 60)
    
    time_str = f"{pos_min:02d}:{pos_sec:02d} / {dur_min:02d}:{dur_sec:02d}"
    return time_str

@player.key_binding('T')
def show_formatted_time():
    time_display = format_time_display()
    player.show_text(time_display, duration='2000', level=2)

# Progress indicator
@player.key_binding('P')
def show_progress_info():
    player.show_progress()
    
    # Additional info
    percent = player.percent_pos or 0
    player.show_text(f"Progress: {percent:.1f}%", level=1)

Interactive Controls

class InteractivePlayer:
    def __init__(self):
        self.player = mpv.MPV()
        self.help_visible = False
        self.setup_bindings()
    
    def setup_bindings(self):
        """Setup all key bindings."""
        
        # Help system
        @self.player.key_binding('h')
        def toggle_help():
            self.toggle_help_display()
        
        # Playback controls
        @self.player.key_binding('SPACE')
        def toggle_pause():
            self.player.pause = not self.player.pause
            state = "Paused" if self.player.pause else "Playing"
            self.player.show_text(state, duration='1000')
        
        # Seeking with feedback
        @self.player.key_binding('l')
        def seek_forward():
            self.player.seek(10)
            pos = self.player.time_pos or 0
            self.player.show_text(f"→ {pos:.1f}s", duration='800')
        
        @self.player.key_binding('j')
        def seek_backward():
            self.player.seek(-10)
            pos = self.player.time_pos or 0
            self.player.show_text(f"← {pos:.1f}s", duration='800')
        
        # Volume with visual feedback
        @self.player.key_binding('+')
        def volume_up():
            self.player.property_add('volume', 5)
            vol = self.player.volume
            self.player.show_text(f"Volume: {vol:.0f}%", duration='1000')
        
        @self.player.key_binding('-')
        def volume_down():
            self.player.property_add('volume', -5)  
            vol = self.player.volume
            self.player.show_text(f"Volume: {vol:.0f}%", duration='1000')
    
    def toggle_help_display(self):
        """Toggle help text display."""
        if self.help_visible:
            self.player.show_text("", duration='0')  # Clear text
            self.help_visible = False
        else:
            help_text = """
Key Bindings:
SPACE - Play/Pause    h - Toggle Help
l/j - Seek ±10s      +/- - Volume ±5
f - Fullscreen       q - Quit
            """.strip()
            self.player.show_text(help_text, duration='-1', level=2)
            self.help_visible = True

# Usage
interactive_player = InteractivePlayer()
interactive_player.player.play('/path/to/video.mp4')

Dynamic Key Binding

class DynamicControls:
    def __init__(self, player):
        self.player = player
        self.mode = 'normal'
        self.setup_mode_switching()
    
    def setup_mode_switching(self):
        """Setup mode-based key bindings."""
        
        @self.player.key_binding('ESC')
        def exit_mode():
            self.set_mode('normal')
        
        @self.player.key_binding('1')
        def number_mode():
            self.set_mode('numbers')
        
        @self.player.key_binding('2')  
        def seek_mode():
            self.set_mode('seeking')
    
    def set_mode(self, mode):
        """Change control mode and update bindings."""
        self.mode = mode
        
        # Clear existing dynamic bindings
        for key in ['a', 'b', 'c', 'd']:
            try:
                self.player.unregister_key_binding(key)
            except:
                pass
        
        # Setup mode-specific bindings
        if mode == 'numbers':
            self.setup_number_mode()
        elif mode == 'seeking':
            self.setup_seek_mode()
        
        self.player.show_text(f"Mode: {mode}", duration='1500')
    
    def setup_number_mode(self):
        """Number input mode bindings."""
        def make_number_handler(num):
            def handler():
                self.player.show_text(f"Number: {num}")
            return handler
        
        for i in range(10):
            self.player.register_key_binding(
                str(i), make_number_handler(i))
    
    def setup_seek_mode(self):
        """Seek mode bindings."""
        @self.player.key_binding('a')
        def seek_10():
            self.player.seek(10)
        
        @self.player.key_binding('b')
        def seek_60():
            self.player.seek(60)
        
        @self.player.key_binding('c')
        def seek_300():
            self.player.seek(300)

# Usage
dynamic = DynamicControls(player)

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