ffmpeg / renderer /scenes /timeline.py
Ava2lon's picture
Upload 34 files
9845310 verified
Raw
History Blame Contribute Delete
2.95 kB
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")