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:
parent
0fe1a47295
commit
46ec0cec9c
@ -25,7 +25,12 @@ import numpy as np
|
|||||||
|
|
||||||
|
|
||||||
def get_video_info(path):
|
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 = [
|
cmd = [
|
||||||
"ffprobe", "-v", "quiet", "-print_format", "json",
|
"ffprobe", "-v", "quiet", "-print_format", "json",
|
||||||
"-show_streams", "-show_format", str(path)
|
"-show_streams", "-show_format", str(path)
|
||||||
@ -35,9 +40,38 @@ def get_video_info(path):
|
|||||||
fps_str = vstream["r_frame_rate"]
|
fps_str = vstream["r_frame_rate"]
|
||||||
num, den = fps_str.split("/")
|
num, den = fps_str.split("/")
|
||||||
fps = float(num) / float(den)
|
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 {
|
return {
|
||||||
"width": int(vstream["width"]),
|
"width": sq_w,
|
||||||
"height": int(vstream["height"]),
|
"height": sq_h,
|
||||||
|
"raw_width": raw_w,
|
||||||
|
"raw_height": raw_h,
|
||||||
|
"sar_n": sar_n,
|
||||||
|
"sar_d": sar_d,
|
||||||
"fps": fps,
|
"fps": fps,
|
||||||
"duration": float(data["format"]["duration"]),
|
"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"
|
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.
|
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_w = info["width"]
|
||||||
src_h = info["height"]
|
src_h = info["height"]
|
||||||
fg_h = int(target_w * src_h / src_w)
|
fg_h = int(target_w * src_h / src_w)
|
||||||
|
pre = (anamorphic_prefix + ",") if anamorphic_prefix else ""
|
||||||
return (
|
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"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"
|
f"[bg][fg]overlay=0:(H-h)/2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -364,17 +399,29 @@ def main():
|
|||||||
info = get_video_info(work_input)
|
info = get_video_info(work_input)
|
||||||
print(f"📹 Vhod: {info['width']}x{info['height']} @ {info['fps']:.2f}fps, {info['duration']:.1f}s")
|
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":
|
if args.mode == "track":
|
||||||
print("🔍 Detektiram obraze (OpenCV)...")
|
print("🔍 Detektiram obraze (OpenCV)...")
|
||||||
samples, _, _, _, _ = detect_face_centers(work_input, sample_fps=5)
|
samples, _, _, _, _ = detect_face_centers(work_input, sample_fps=5)
|
||||||
n_with_face = sum(1 for _, x in samples if x is not None)
|
n_with_face = sum(1 for _, x in samples if x is not None)
|
||||||
print(f" {n_with_face}/{len(samples)} vzorcev z obrazom")
|
print(f" {n_with_face}/{len(samples)} vzorcev z obrazom")
|
||||||
x_at = smooth_track(samples, info["duration"], smoothing_window=4.0)
|
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":
|
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":
|
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]
|
preset = {"fast": "veryfast", "medium": "medium", "high": "slow"}[args.quality]
|
||||||
crf = {"fast": "26", "medium": "21", "high": "18"}[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.append(f"afade=t=out:st={fade_start}:d={args.fade_out}")
|
||||||
audio_filter_str = ",".join(audio_filter) if audio_filter else None
|
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":
|
if args.mode == "blur":
|
||||||
# blur uporablja filter_complex
|
# blur uporablja filter_complex
|
||||||
cmd = [
|
cmd = [
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user