"""MPV Lua API - Clean interface for Lua scripts to call Python functions. This module provides a streamlined way for mpv Lua scripts to execute Python functions and commands without relying on the broken observe_property IPC pattern. Instead, Lua calls Python CLI directly via subprocess, and Python returns JSON responses that Lua can parse. """ import json import logging import sys from pathlib import Path from typing import Any, Dict, Optional # Add parent directory to path so we can import CLI, pipeline, cmdlet_catalog from root _SCRIPT_DIR = Path(__file__).parent _ROOT_DIR = _SCRIPT_DIR.parent if str(_ROOT_DIR) not in sys.path: sys.path.insert(0, str(_ROOT_DIR)) def setup_logging(log_file: Optional[Path] = None) -> logging.Logger: """Setup logging for MPV API calls.""" logger = logging.getLogger("mpv-lua-api") logger.setLevel(logging.DEBUG) if not logger.handlers: if log_file: handler = logging.FileHandler(str(log_file), encoding="utf-8") else: handler = logging.StreamHandler(sys.stderr) formatter = logging.Formatter( "[%(asctime)s][%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) handler.setFormatter(formatter) logger.addHandler(handler) return logger def log_to_helper(msg: str, log_file: Optional[Path] = None) -> None: """Log a message that will appear in the helper log.""" if log_file: with open(log_file, "a", encoding="utf-8") as f: f.write(f"[lua] {msg}\n") def execute_pipeline( pipeline_cmd: str, log_file: Optional[Path] = None, dry_run: bool = False, ) -> Dict[str, Any]: """Execute a pipeline command and return result as JSON. Args: pipeline_cmd: Pipeline command string (e.g. "trim-file -path ... | add-file -store ...") log_file: Optional path to helper log file for logging dry_run: If True, log but don't execute Returns: JSON object with keys: success, stdout, stderr, error, returncode """ try: if log_file: log_to_helper(f"[api] execute_pipeline cmd={pipeline_cmd}", log_file) if dry_run: return { "success": True, "stdout": "", "stderr": "DRY RUN - command not executed", "error": None, "returncode": 0, "cmd": pipeline_cmd, } # Call the CLI directly as subprocess import subprocess import shlex # Parse the pipeline command into separate arguments cmd_args = shlex.split(pipeline_cmd) result = subprocess.run( [sys.executable, "-m", "CLI"] + cmd_args, capture_output=True, text=True, cwd=str(_ROOT_DIR), env={ **dict(__import__("os").environ), "MEDEIA_MPV_CALLER": "lua" }, ) if log_file: log_to_helper( f"[api] result returncode={result.returncode} len_stdout={len(result.stdout or '')} len_stderr={len(result.stderr or '')}", log_file, ) if result.stderr: log_to_helper(f"[api] stderr: {result.stderr[:500]}", log_file) return { "success": result.returncode == 0, "stdout": result.stdout or "", "stderr": result.stderr or "", "error": None if result.returncode == 0 else result.stderr, "returncode": result.returncode, "cmd": pipeline_cmd, } except Exception as exc: msg = f"{type(exc).__name__}: {exc}" if log_file: log_to_helper(f"[api] exception {msg}", log_file) return { "success": False, "stdout": "", "stderr": str(exc), "error": msg, "returncode": 1, "cmd": pipeline_cmd, } def handle_api_request(request_json: str, log_file: Optional[Path] = None) -> str: """Handle an API request from Lua and return JSON response. Request format: { "cmd": "execute_pipeline", "pipeline": "trim-file -path ... | add-file -store ...", ... } Response format: JSON with result of the operation. """ try: request = json.loads(request_json) cmd = request.get("cmd") if cmd == "execute_pipeline": pipeline_cmd = request.get("pipeline", "") result = execute_pipeline(pipeline_cmd, log_file) return json.dumps(result) else: return json.dumps({ "success": False, "error": f"Unknown command: {cmd}", }) except Exception as exc: return json.dumps( { "success": False, "error": f"{type(exc).__name__}: {exc}", } ) if __name__ == "__main__": # When called from Lua via subprocess: # python mpv_lua_api.py if len(sys.argv) < 2: print(json.dumps({ "success": False, "error": "No request provided" })) sys.exit(1) request_json = sys.argv[1] log_file = Path(sys.argv[2]) if len(sys.argv) > 2 else None response = handle_api_request(request_json, log_file) print(response)