Anamorphic source fix: pred-scale na square pixel (PAL DV 720x576 SAR 64:45 \u2192 1024x576) preden track/center/blur filter dela. Brez tega so se reels iz starih MPG-2 (npr. Global Kryner Like a Virgin) razteg-nili horizontalno.

This commit is contained in:
OpenClaw Agent 2026-05-02 11:57:09 +00:00
parent 0fe1a47295
commit 46ec0cec9c

View File

@ -25,7 +25,12 @@ import numpy as np
def get_video_info(path):
"""Vrni dict z width, height, fps, duration."""
"""Vrni dict z width, height, fps, duration.
width/height so SQUARE-PIXEL dimenzije (popravljeno za anamorphic SAR).
Pri broadcast 720x576 SAR 64:45 (PAL DV) bo vrnil 1024x576 (square pixel).
Pri standardnem source z SAR 1:1 ostane nespremenjeno.
"""
cmd = [
"ffprobe", "-v", "quiet", "-print_format", "json",
"-show_streams", "-show_format", str(path)
@ -35,9 +40,38 @@ def get_video_info(path):
fps_str = vstream["r_frame_rate"]
num, den = fps_str.split("/")
fps = float(num) / float(den)
raw_w = int(vstream["width"])
raw_h = int(vstream["height"])
# SAR (sample aspect ratio) — razmerje stranic pikslja
sar = vstream.get("sample_aspect_ratio", "1:1")
try:
sar_n, sar_d = sar.split(":")
sar_n, sar_d = int(sar_n), int(sar_d)
if sar_n == 0 or sar_d == 0:
sar_n, sar_d = 1, 1
except (ValueError, AttributeError):
sar_n, sar_d = 1, 1
# Square-pixel dimenzije: če SAR != 1:1, popravi širino
if sar_n != sar_d:
sq_w = int(round(raw_w * sar_n / sar_d))
# Zaokroži na sodo (libx264 ima rad sode dimenzije)
sq_w = sq_w + (sq_w % 2)
sq_h = raw_h
print(f"📐 Anamorphic source: {raw_w}x{raw_h} SAR {sar_n}:{sar_d}{sq_w}x{sq_h} (square pixel)", file=sys.stderr)
else:
sq_w = raw_w
sq_h = raw_h
return {
"width": int(vstream["width"]),
"height": int(vstream["height"]),
"width": sq_w,
"height": sq_h,
"raw_width": raw_w,
"raw_height": raw_h,
"sar_n": sar_n,
"sar_d": sar_d,
"fps": fps,
"duration": float(data["format"]["duration"]),
}
@ -250,7 +284,7 @@ def build_center_filter(info, target_w, target_h):
return f"scale={scale_w}:{scale_h},crop={target_w}:{target_h}:(in_w-{target_w})/2:0"
def build_blur_filter(info, target_w, target_h):
def build_blur_filter(info, target_w, target_h, anamorphic_prefix=""):
"""
9:16 platno: spodaj/zgoraj blur kopija, v sredini originalni 16:9.
"""
@ -258,10 +292,11 @@ def build_blur_filter(info, target_w, target_h):
src_w = info["width"]
src_h = info["height"]
fg_h = int(target_w * src_h / src_w)
pre = (anamorphic_prefix + ",") if anamorphic_prefix else ""
return (
f"[0:v]scale={target_w}:{target_h}:force_original_aspect_ratio=increase,"
f"[0:v]{pre}scale={target_w}:{target_h}:force_original_aspect_ratio=increase,"
f"crop={target_w}:{target_h},gblur=sigma=30[bg];"
f"[0:v]scale={target_w}:{fg_h}[fg];"
f"[0:v]{pre}scale={target_w}:{fg_h}[fg];"
f"[bg][fg]overlay=0:(H-h)/2"
)
@ -364,17 +399,29 @@ def main():
info = get_video_info(work_input)
print(f"📹 Vhod: {info['width']}x{info['height']} @ {info['fps']:.2f}fps, {info['duration']:.1f}s")
# Anamorphic correction prefix: če je SAR != 1:1, najprej scale-amo na square-pixel
# in resetiramo SAR=1, šele potem track/crop filter dela na pravilnih dimenzijah.
# Brez tega: 720x576 SAR 64:45 (DAR 16:9) PAL DV se pri scale na 1920 razteguje
# nepravilno (ker filter misli da je 720 širina, dejansko prikazana širina je 1024).
if info.get("sar_n", 1) != info.get("sar_d", 1):
anamorphic_prefix = f"scale={info['width']}:{info['height']}:flags=lanczos,setsar=1,"
print(f"🔧 Anamorphic prefix: {anamorphic_prefix.rstrip(',')}", file=sys.stderr)
else:
anamorphic_prefix = ""
if args.mode == "track":
print("🔍 Detektiram obraze (OpenCV)...")
samples, _, _, _, _ = detect_face_centers(work_input, sample_fps=5)
n_with_face = sum(1 for _, x in samples if x is not None)
print(f" {n_with_face}/{len(samples)} vzorcev z obrazom")
x_at = smooth_track(samples, info["duration"], smoothing_window=4.0)
vfilter = build_track_filter(info, x_at, args.target_width, args.target_height, info["fps"])
vfilter = anamorphic_prefix + build_track_filter(info, x_at, args.target_width, args.target_height, info["fps"])
elif args.mode == "center":
vfilter = build_center_filter(info, args.target_width, args.target_height)
vfilter = anamorphic_prefix + build_center_filter(info, args.target_width, args.target_height)
elif args.mode == "blur":
vfilter = build_blur_filter(info, args.target_width, args.target_height)
# blur uporablja filter_complex z [0:v] referenco — anamorphic prefix gre v posebni veji
vfilter = build_blur_filter(info, args.target_width, args.target_height,
anamorphic_prefix=anamorphic_prefix.rstrip(","))
preset = {"fast": "veryfast", "medium": "medium", "high": "slow"}[args.quality]
crf = {"fast": "26", "medium": "21", "high": "18"}[args.quality]
@ -389,10 +436,6 @@ def main():
audio_filter.append(f"afade=t=out:st={fade_start}:d={args.fade_out}")
audio_filter_str = ",".join(audio_filter) if audio_filter else None
# Force kvadraten pixel (SAR=1:1) — fix za broadcast .MPG ki ima 64:45 anamorphic
if not vfilter.endswith(",setsar=1"):
vfilter = vfilter + ",setsar=1"
if args.mode == "blur":
# blur uporablja filter_complex
cmd = [