Skip to content

Commit 756e978

Browse files
committed
Validate baked rotation output
1 parent adc0b89 commit 756e978

2 files changed

Lines changed: 92 additions & 1 deletion

File tree

tests/test_transcode_rotation.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from unittest.mock import patch
77

88
from videomerge.models import Canvas, CodecPlan, Orientation, ToolPaths, VideoFile
9-
from videomerge.transcode import build_video_filter, preprocess_file
9+
from videomerge.transcode import build_video_filter, preprocess_file, validate_preprocessed_output
1010

1111

1212
class TranscodeRotationTests(unittest.TestCase):
@@ -58,9 +58,62 @@ def fake_run_command(args, logger, dry_run=False): # type: ignore[no-untyped-de
5858
)
5959

6060
self.assertLess(captured_args.index("-noautorotate"), captured_args.index("-i"))
61+
self.assertLess(captured_args.index("-display_rotation:v:0"), captured_args.index("-i"))
62+
self.assertEqual(captured_args[captured_args.index("-display_rotation:v:0") + 1], "0")
6163
self.assertIn("transpose=1", captured_args[captured_args.index("-vf") + 1])
6264
self.assertEqual(captured_args[captured_args.index("-metadata:s:v:0") + 1], "rotate=0")
6365

66+
def test_validation_requires_canvas_display_size_and_zero_rotation(self) -> None:
67+
source = _rotated_video()
68+
output = _rotated_video().__class__(
69+
path=Path("out.mp4"),
70+
container="mp4",
71+
video_codec="h264",
72+
audio_codec="aac",
73+
width=720,
74+
height=1280,
75+
display_width=720,
76+
display_height=1280,
77+
aspect_ratio="720:1280",
78+
frame_rate="30/1",
79+
frame_rate_float=30.0,
80+
pixel_format="yuv420p",
81+
duration=32.55,
82+
has_audio=True,
83+
orientation=Orientation.portrait,
84+
rotation=0,
85+
)
86+
87+
with patch("videomerge.transcode.probe_file", return_value=output):
88+
validate_preprocessed_output(
89+
Path("out.mp4"),
90+
source,
91+
Canvas(720, 1280),
92+
ToolPaths(ffmpeg=Path("ffmpeg"), ffprobe=Path("ffprobe")),
93+
logging.getLogger("test"),
94+
)
95+
6496

6597
if __name__ == "__main__":
6698
unittest.main()
99+
100+
101+
def _rotated_video() -> VideoFile:
102+
return VideoFile(
103+
path=Path("11.MP4"),
104+
container="mp4",
105+
video_codec="h264",
106+
audio_codec="aac",
107+
width=1280,
108+
height=720,
109+
display_width=720,
110+
display_height=1280,
111+
aspect_ratio="720:1280",
112+
frame_rate="88830000/2959519",
113+
frame_rate_float=30.015,
114+
pixel_format="yuv420p",
115+
duration=32.55,
116+
has_audio=True,
117+
orientation=Orientation.portrait,
118+
rotation=90,
119+
)

videomerge/transcode.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import tempfile
55
from pathlib import Path
66

7+
from .errors import CommandError, ProbeError
78
from .models import Canvas, CodecPlan, ToolPaths, VideoFile
9+
from .probe import probe_file
810
from .utils import run_command
911

1012

@@ -68,6 +70,8 @@ def preprocess_file(
6870
"-y",
6971
"-hide_banner",
7072
"-noautorotate",
73+
"-display_rotation:v:0",
74+
"0",
7175
"-i",
7276
file.path,
7377
]
@@ -142,6 +146,8 @@ def preprocess_file(
142146
codec_plan.output_audio_encoder,
143147
)
144148
run_command(args, logger, dry_run=dry_run)
149+
if not dry_run:
150+
validate_preprocessed_output(output_path, file, canvas, tools, logger)
145151

146152

147153
def build_video_filter(rotation: int, canvas: Canvas, fps: float, pad_color: str) -> str:
@@ -162,3 +168,35 @@ def build_video_filter(rotation: int, canvas: Canvas, fps: float, pad_color: str
162168
]
163169
)
164170
return ",".join(filters)
171+
172+
173+
def validate_preprocessed_output(
174+
output_path: Path,
175+
source_file: VideoFile,
176+
canvas: Canvas,
177+
tools: ToolPaths,
178+
logger: logging.Logger,
179+
) -> None:
180+
try:
181+
media = probe_file(output_path, tools, logger)
182+
except ProbeError as exc:
183+
raise CommandError(f"Could not validate preprocessed file {output_path}: {exc}") from exc
184+
185+
if media.rotation != 0:
186+
raise CommandError(
187+
f"Preprocessed file still has rotation metadata: {source_file.path.name} -> "
188+
f"{output_path} rotation={media.rotation}"
189+
)
190+
if media.display_width != canvas.width or media.display_height != canvas.height:
191+
raise CommandError(
192+
f"Preprocessed file display size mismatch: {source_file.path.name} -> {output_path} "
193+
f"display={media.display_width}x{media.display_height}, expected={canvas.label}"
194+
)
195+
196+
logger.info(
197+
"Validated preprocessed output: %s | display=%dx%d rotation=%d",
198+
output_path.name,
199+
media.display_width,
200+
media.display_height,
201+
media.rotation,
202+
)

0 commit comments

Comments
 (0)