72 lines
3.1 KiB
Python
72 lines
3.1 KiB
Python
"""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()
|