| 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 |
|
|