from __future__ import annotations from pathlib import Path from renderer.core.config import Settings from renderer.core.models import AssetMetadata from renderer.ffmpeg.command import FFmpegCommand from renderer.ffmpeg.runner import FFmpegRunner from renderer.templates import PlatformProfile class Normalizer: def __init__(self, settings: Settings, runner: FFmpegRunner) -> None: self.settings = settings self.runner = runner def needs_normalization(self, metadata: AssetMetadata, profile: PlatformProfile | None = None) -> bool: width = profile.width if profile else self.settings.output_width height = profile.height if profile else self.settings.output_height fps = profile.fps if profile else self.settings.output_fps return not ( metadata.width == width and metadata.height == height and round(metadata.fps or 0) == fps and metadata.video_codec == "h264" and (metadata.audio_codec in ("aac", None)) ) def normalize(self, path: str | Path, output: Path, duration: float | None = None, profile: PlatformProfile | None = None) -> Path: width = profile.width if profile else self.settings.output_width height = profile.height if profile else self.settings.output_height fps = profile.fps if profile else self.settings.output_fps crf = profile.crf if profile else self.settings.crf vf = ( f"scale={width}:{height}:" "force_original_aspect_ratio=increase," f"crop={width}:{height}," f"fps={fps},format=yuv420p" ) cmd = ( FFmpegCommand() .add("-hide_banner") .input(path) .add("-vf", vf, "-c:v", "libx264", "-preset", self.settings.preset, "-crf", crf) .add("-c:a", "aac", "-b:a", "128k", "-movflags", "+faststart") ) if duration: cmd.add("-t", duration) command = cmd.overwrite().add(output).build() self.runner.run(command) return output def image_to_video(self, path: str | Path, output: Path, duration: float, profile: PlatformProfile | None = None) -> Path: width = profile.width if profile else self.settings.output_width height = profile.height if profile else self.settings.output_height fps = profile.fps if profile else self.settings.output_fps crf = profile.crf if profile else self.settings.crf vf = ( f"scale={width}:{height}:" "force_original_aspect_ratio=increase," f"crop={width}:{height}," f"fps={fps},format=yuv420p" ) command = ( FFmpegCommand() .add("-hide_banner", "-loop", "1", "-t", duration) .input(path) .add("-vf", vf, "-an", "-c:v", "libx264", "-preset", self.settings.preset, "-crf", crf) .overwrite() .add(output) .build() ) self.runner.run(command) return output