File size: 2,952 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 | from __future__ import annotations
from renderer.core.models import RenderRequest, Scene
class Timeline:
def __init__(self, scenes: list[Scene]) -> None:
self.scenes = sorted(scenes, key=lambda scene: scene.start)
self.validate()
@classmethod
def from_payload(cls, payload: dict) -> "Timeline":
return cls([Scene(**scene) for scene in payload.get("scenes", [])])
@classmethod
def request_from_payload(cls, payload: dict) -> RenderRequest:
scenes = [Scene(**scene) for scene in payload.get("scenes", [])]
return RenderRequest(
scenes=scenes,
template=payload.get("template", "tiktok_classic"),
preset=payload.get("preset"),
creative_style=payload.get("creative_style"),
platform=payload.get("platform"),
output_name=payload.get("output_name", "render.mp4"),
voiceover=payload.get("voiceover"),
background_music=payload.get("background_music"),
music_volume=payload.get("music_volume", 0.316),
music_fade_in=payload.get("music_fade_in", 0.0),
music_fade_out=payload.get("music_fade_out", 0.0),
music_loop=payload.get("music_loop", True),
music_start=payload.get("music_start", 0.0),
music_ducking=payload.get("music_ducking", True),
voice_volume=payload.get("voice_volume", 1.0),
subtitle_format=payload.get("subtitle_format", "ass"),
auto_subtitles=payload.get("auto_subtitles", False),
subtitle_language=payload.get("subtitle_language"),
whisper_model_size=payload.get("whisper_model_size"),
preview=payload.get("preview", False),
audio_normalize=payload.get("audio_normalize", False),
watermark=payload.get("watermark"),
watermark_position=payload.get("watermark_position", "bottom-right"),
intro=payload.get("intro"),
outro=payload.get("outro"),
callback_url=payload.get("callback_url"),
export_target=payload.get("export_target"),
priority=payload.get("priority", 0),
scheduled_at=payload.get("scheduled_at"),
normalize=payload.get("normalize", True),
metadata=payload.get("metadata", {}),
)
@property
def total_duration(self) -> float:
return max((scene.start + scene.duration for scene in self.scenes), default=0.0)
def validate(self) -> None:
if not self.scenes:
raise ValueError("At least one scene is required")
for scene in self.scenes:
if scene.duration <= 0:
raise ValueError("Scene duration must be greater than zero")
if scene.start < 0:
raise ValueError("Scene start must be non-negative")
if not scene.media:
raise ValueError("Scene media path is required")
|