File size: 3,060 Bytes
345855e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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