""" Generate and cache low-bitrate preview proxies for browser playback. Goal: - Improve Remotion Player preview smoothness by serving smaller/faster-to-decode videos. - Keep original `source_path` for accurate export; only swap `source_url` for preview. Design: - Deterministic cache key based on (path, mtime, size). - Proxy lives under config.TEMP_DIR / "proxy". - Generated with ffmpeg: scale/pad + fps downsample + faststart + no audio. """ from __future__ import annotations import hashlib import os from pathlib import Path from typing import Optional, Tuple import config from modules import ffmpeg_utils def _key_for_file(path: str) -> str: st = os.stat(path) raw = f"{path}|{st.st_mtime_ns}|{st.st_size}".encode("utf-8") return hashlib.sha1(raw).hexdigest() # short, deterministic def ensure_video_proxy( source_path: str, *, target_w: int = 540, target_h: int = 960, target_fps: int = 24, crf: int = 28, preset: str = "veryfast", ) -> Tuple[Optional[str], Optional[str]]: """ Ensure a preview proxy exists for source_path. Returns: (proxy_path, proxy_url) or (None, None) if source_path invalid. """ if not source_path or not os.path.exists(source_path): return None, None proxy_dir = Path(config.TEMP_DIR) / "proxy" proxy_dir.mkdir(parents=True, exist_ok=True) key = _key_for_file(source_path) out_name = f"proxy_{key}.mp4" out_path = proxy_dir / out_name if out_path.exists() and out_path.stat().st_size > 1024: return str(out_path), f"/static/temp/proxy/{out_name}" vf = ( f"scale={target_w}:{target_h}:force_original_aspect_ratio=decrease," f"pad={target_w}:{target_h}:(ow-iw)/2:(oh-ih)/2:black," f"fps={target_fps}" ) cmd = [ ffmpeg_utils.FFMPEG_PATH, "-y", "-i", source_path, "-an", # preview 不要音轨,减少解码负担(旁白/BGM 走单独轨道) "-vf", vf, "-c:v", "libx264", "-preset", preset, "-crf", str(crf), "-tune", "fastdecode", "-pix_fmt", "yuv420p", "-movflags", "+faststart", str(out_path), ] ffmpeg_utils._run_ffmpeg(cmd) if out_path.exists() and out_path.stat().st_size > 1024: return str(out_path), f"/static/temp/proxy/{out_name}" return None, None