"""Read FFmpeg's real timeline and output size without blocking stderr.""" from __future__ import annotations import math import subprocess import threading from host.media import NO_WINDOW def run_ffmpeg(command, duration, stage, engine, report): duration = float(duration or 0) if not math.isfinite(duration) or duration < 0: duration = 0 report(stage=stage, percent=0 if duration else None, engine=engine, speed="", eta=None, processedSeconds=0, durationSeconds=duration, written=0, message="") process = subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8", errors="replace", creationflags=NO_WINDOW) errors, finalizing = [], threading.Event() progress_lock = threading.Lock() def read_errors(): for line in process.stderr: errors.append(line) if len(errors) > 80: del errors[:40] if "Starting second pass" in line: with progress_lock: finalizing.set() report(stage="finalizing", percent=99, engine="copy", speed="", eta=None, message="Финализация файла") reader = threading.Thread(target=read_errors, daemon=True) reader.start() stats = {} elapsed = 0.0 try: for raw in process.stdout: key, _, value = raw.strip().partition("=") stats[key] = value if key != "progress": continue try: elapsed = max(elapsed, max(0, int(stats.get("out_time_us", "0")) / 1_000_000)) except ValueError: pass speed = stats.get("speed", "") try: rate = float(speed.rstrip("x")) except ValueError: rate = 0 eta = max(0, (duration - elapsed) / rate) if duration and rate > 0 and math.isfinite(rate) and not finalizing.is_set() else None try: written = max(0, int(stats.get("total_size", "0"))) except ValueError: written = None with progress_lock: is_finalizing = finalizing.is_set() report(stage="finalizing" if is_finalizing else stage, percent=99 if is_finalizing else min(99, elapsed / duration * 100) if duration else None, engine="copy" if is_finalizing else engine, speed=speed if rate > 0 and math.isfinite(rate) and not is_finalizing else "", eta=None if is_finalizing else eta, processedSeconds=min(elapsed, duration) if duration else elapsed, durationSeconds=duration, written=written, message="Финализация файла" if is_finalizing else "") process.wait() reader.join(timeout=3) return process.returncode, "".join(errors)[-2000:] finally: if process.poll() is None: process.kill() process.wait(timeout=10) for stream in (process.stdout, process.stderr): stream.close()