From f38ba27f2466321f25077c5052d6f43e751b0821 Mon Sep 17 00:00:00 2001 From: chekazoid Date: Thu, 16 Jul 2026 11:56:00 +0300 Subject: [PATCH] fix(render): detect portrait sources that carry rotation metadata is_portrait_source() compared the stored stream dimensions, so footage recorded vertically on a phone -- a landscape stream plus a rotation entry in the display matrix -- was classified as landscape. ffmpeg auto-rotates on decode, so the filter graph received a portrait frame and scale=1920:-2 stretched it to 1920x3414. Read the rotation side data (falling back to the pre-ffmpeg-5 rotate tag) and swap the dimensions when rotation is an odd multiple of 90. #29 covered sources stored portrait; this covers sources rotated by metadata. Co-Authored-By: Claude Opus 4.8 --- helpers/render.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/helpers/render.py b/helpers/render.py index 0d02cffa..ee87b8a7 100644 --- a/helpers/render.py +++ b/helpers/render.py @@ -132,15 +132,33 @@ def is_hdr_source(video: Path) -> bool: def is_portrait_source(video: Path) -> bool: - """Return True if the video's height > width (portrait / vertical).""" + """Return True if the video displays taller than wide (portrait / vertical). + + Phone cameras store vertical footage as a landscape stream plus a rotation + entry. ffmpeg auto-rotates on decode, so the displayed dimensions -- not the + stored ones -- decide which axis to scale by. + """ try: out = subprocess.run( ["ffprobe", "-v", "error", "-select_streams", "v:0", - "-show_entries", "stream=width,height", - "-of", "csv=p=0", str(video)], + "-show_entries", "stream=width,height:stream_side_data=rotation:stream_tags=rotate", + "-of", "json", str(video)], capture_output=True, text=True, check=True, ) - w, h = map(int, out.stdout.strip().split(",")) + stream = json.loads(out.stdout)["streams"][0] + w, h = int(stream["width"]), int(stream["height"]) + + rotation = 0 + for side_data in stream.get("side_data_list", []): + if "rotation" in side_data: + rotation = int(side_data["rotation"]) + break + else: + # ffmpeg < 5 exposes the display matrix as a stream tag instead. + rotation = int(stream.get("tags", {}).get("rotate", 0)) + + if rotation % 180 != 0: + w, h = h, w return h > w except Exception: return False