ffmpeg / renderer /templates /creative.py
Ava2lon's picture
Upload 38 files
fe7106b verified
Raw
History Blame Contribute Delete
11.8 kB
from __future__ import annotations
from copy import deepcopy
from dataclasses import dataclass, field
from typing import Any
@dataclass(frozen=True)
class CreativeStyle:
key: str
label: str
description: str
platform: str
template: str
scene_duration: float
transition_sequence: tuple[str, ...]
scene_effect_sequence: tuple[str, ...]
render_defaults: dict[str, Any] = field(default_factory=dict)
metadata: dict[str, Any] = field(default_factory=dict)
def metadata_payload(self) -> dict[str, Any]:
return {
"key": self.key,
"label": self.label,
"description": self.description,
"platform": self.platform,
"template": self.template,
"scene_duration": self.scene_duration,
"transition_sequence": list(self.transition_sequence),
"scene_effect_sequence": list(self.scene_effect_sequence),
"render_defaults": deepcopy(self.render_defaults),
"metadata": deepcopy(self.metadata),
}
SCENE_EFFECTS: dict[str, dict[str, str]] = {
"none": {"label": "None", "filter": ""},
"sharp_pop": {
"label": "Sharp Pop",
"filter": "eq=contrast=1.08:saturation=1.20:brightness=0.01,unsharp=5:5:0.8:3:3:0.4",
},
"clean_beauty": {
"label": "Clean Beauty",
"filter": "hqdn3d=1.5:1.5:6:6,eq=saturation=1.08:contrast=1.03",
},
"warm_glow": {
"label": "Warm Glow",
"filter": "eq=contrast=1.04:saturation=1.18:gamma_r=1.04:gamma_b=0.96,gblur=sigma=0.25",
},
"cinematic": {
"label": "Cinematic",
"filter": "eq=contrast=1.14:saturation=0.95:brightness=-0.015,vignette=PI/6",
},
"dreamy": {
"label": "Dreamy",
"filter": "gblur=sigma=0.6,eq=contrast=1.04:saturation=1.18:brightness=0.02",
},
"flash_pop": {
"label": "Flash Pop",
"filter": "eq=contrast=1.16:saturation=1.25:brightness=0.035",
},
"grain": {
"label": "Fine Grain",
"filter": "noise=alls=8:allf=t+u,eq=contrast=1.07:saturation=1.02",
},
"motion_blur": {
"label": "Motion Blur",
"filter": "tmix=frames=3:weights='1 2 1',eq=contrast=1.05:saturation=1.08",
},
"noir": {
"label": "Noir",
"filter": "hue=s=0,eq=contrast=1.18:brightness=-0.02",
},
"glitch": {
"label": "Glitch",
"filter": "rgbashift=rh=4:bh=-4,eq=contrast=1.12:saturation=1.18",
},
"rgb_split": {
"label": "RGB Split",
"filter": "rgbashift=rh=3:gv=1:bh=-3",
},
"vhs": {
"label": "VHS",
"filter": "noise=alls=18:allf=t+u,eq=saturation=0.82:contrast=1.08",
},
"crt": {
"label": "CRT",
"filter": "vignette=PI/4,noise=alls=10:allf=t+u,eq=contrast=1.15:saturation=0.9",
},
"bloom": {
"label": "Bloom",
"filter": "gblur=sigma=0.35,eq=brightness=0.025:saturation=1.14",
},
"glow": {
"label": "Glow",
"filter": "gblur=sigma=0.45,eq=contrast=1.05:brightness=0.03",
},
"chromatic_aberration": {
"label": "Chromatic Aberration",
"filter": "rgbashift=rh=2:rv=1:bh=-2:bv=-1",
},
"neon": {
"label": "Neon",
"filter": "eq=contrast=1.2:saturation=1.55:brightness=0.02",
},
"cyberpunk": {
"label": "Cyberpunk",
"filter": "eq=contrast=1.18:saturation=1.45:gamma_r=1.08:gamma_b=1.18",
},
"comic": {
"label": "Comic",
"filter": "edgedetect=low=0.08:high=0.25,eq=contrast=1.2:saturation=1.35",
},
"cartoon": {
"label": "Cartoon",
"filter": "edgedetect=low=0.05:high=0.2,eq=saturation=1.45:contrast=1.15",
},
"anime": {
"label": "Anime",
"filter": "eq=saturation=1.35:contrast=1.12:brightness=0.02,unsharp=5:5:0.6",
},
"sketch": {
"label": "Sketch",
"filter": "edgedetect=low=0.03:high=0.18,hue=s=0",
},
"oil_painting": {
"label": "Oil Painting",
"filter": "gblur=sigma=0.7,eq=saturation=1.25:contrast=1.1",
},
"pixel_art": {
"label": "Pixel Art",
"filter": "scale=iw/8:ih/8,scale=iw*8:ih*8:flags=neighbor",
},
}
CREATIVE_STYLES: dict[str, CreativeStyle] = {
"viral_shorts": CreativeStyle(
key="viral_shorts",
label="Viral Shorts",
description="Fast vertical pacing, punchy captions, bright contrast, and whip-style movement.",
platform="tiktok",
template="tiktok_zoom",
scene_duration=2.4,
transition_sequence=("whip", "flash", "zoom", "glitch"),
scene_effect_sequence=("sharp_pop", "flash_pop", "clean_beauty"),
render_defaults={
"subtitle_format": "ass",
"auto_subtitles": True,
"audio_normalize": True,
"music_volume": 0.24,
"music_fade_in": 0.25,
"music_fade_out": 0.8,
"music_ducking": True,
"normalize": True,
},
metadata={"energy": "high", "best_for": "hooks, offers, memes, short promos"},
),
"product_demo": CreativeStyle(
key="product_demo",
label="Product Demo",
description="Clean cuts, readable captions, and polished color for launches and tutorials.",
platform="instagram_reels",
template="modern_minimal",
scene_duration=3.2,
transition_sequence=("slide", "wipe_left", "push", "dissolve"),
scene_effect_sequence=("clean_beauty", "sharp_pop"),
render_defaults={
"subtitle_format": "ass",
"auto_subtitles": True,
"audio_normalize": True,
"music_volume": 0.18,
"music_fade_in": 0.3,
"music_fade_out": 0.7,
"music_ducking": True,
"normalize": True,
},
metadata={"energy": "medium", "best_for": "software demos, ecommerce, tutorials"},
),
"story_vlog": CreativeStyle(
key="story_vlog",
label="Story Vlog",
description="Warm color, softer movement, and natural pacing for personality-led edits.",
platform="instagram_reels",
template="tiktok_classic",
scene_duration=3.8,
transition_sequence=("fade", "smooth_right", "dissolve"),
scene_effect_sequence=("warm_glow", "dreamy", "clean_beauty"),
render_defaults={
"subtitle_format": "ass",
"auto_subtitles": True,
"audio_normalize": True,
"music_volume": 0.22,
"music_fade_in": 0.5,
"music_fade_out": 1.0,
"music_ducking": True,
"normalize": True,
},
metadata={"energy": "medium", "best_for": "founder updates, day-in-life edits, testimonials"},
),
"podcast_clip": CreativeStyle(
key="podcast_clip",
label="Podcast Clip",
description="Square-safe framing, calmer captions, and narration-first audio treatment.",
platform="instagram_feed_square",
template="podcast_style",
scene_duration=5.0,
transition_sequence=("fade", "dissolve"),
scene_effect_sequence=("clean_beauty", "sharp_pop"),
render_defaults={
"subtitle_format": "ass",
"auto_subtitles": True,
"audio_normalize": True,
"music_volume": 0.12,
"music_fade_in": 0.6,
"music_fade_out": 1.2,
"music_ducking": True,
"normalize": True,
},
metadata={"energy": "low", "best_for": "interviews, audiograms, education"},
),
"cinematic_story": CreativeStyle(
key="cinematic_story",
label="Cinematic Story",
description="Deeper contrast, film grain, and slower transitions for mini-documentary edits.",
platform="youtube_shorts",
template="modern_minimal",
scene_duration=4.2,
transition_sequence=("dissolve", "fadeblack", "smooth_left"),
scene_effect_sequence=("cinematic", "grain"),
render_defaults={
"subtitle_format": "ass",
"auto_subtitles": True,
"audio_normalize": True,
"music_volume": 0.26,
"music_fade_in": 0.8,
"music_fade_out": 1.4,
"music_ducking": True,
"normalize": True,
},
metadata={"energy": "low", "best_for": "brand films, travel, documentary shorts"},
),
"news_explainer": CreativeStyle(
key="news_explainer",
label="News Explainer",
description="Readable lower-third style captions with stable landscape or vertical exports.",
platform="youtube_1080p",
template="news_style",
scene_duration=4.0,
transition_sequence=("wipe_left", "slide", "fade"),
scene_effect_sequence=("sharp_pop", "clean_beauty"),
render_defaults={
"subtitle_format": "ass",
"auto_subtitles": False,
"audio_normalize": True,
"music_volume": 0.1,
"music_fade_in": 0.5,
"music_fade_out": 1.0,
"music_ducking": True,
"normalize": True,
},
metadata={"energy": "medium", "best_for": "explainers, news, thought leadership"},
),
}
def apply_creative_style(payload: dict[str, Any]) -> dict[str, Any]:
style_key = payload.get("creative_style") or payload.get("metadata", {}).get("creative_style")
if not style_key:
return payload
style = get_creative_style(str(style_key))
output = deepcopy(payload)
output["creative_style"] = style.key
_set_default(output, "platform", style.platform)
_set_default(output, "template", style.template)
for key, value in style.render_defaults.items():
_set_default(output, key, deepcopy(value))
metadata = deepcopy(style.metadata)
metadata.update(output.get("metadata", {}))
metadata["creative_style"] = style.key
metadata["creative_style_label"] = style.label
output["metadata"] = metadata
scenes = output.get("scenes")
if isinstance(scenes, list):
output["scenes"] = [_style_scene(scene, style, index) for index, scene in enumerate(scenes)]
return output
def get_creative_style(key: str | None) -> CreativeStyle:
if not key:
return CREATIVE_STYLES["viral_shorts"]
return CREATIVE_STYLES.get(key, CREATIVE_STYLES["viral_shorts"])
def list_creative_styles() -> list[str]:
return list(CREATIVE_STYLES.keys())
def creative_style_metadata() -> dict[str, dict[str, Any]]:
return {key: style.metadata_payload() for key, style in CREATIVE_STYLES.items()}
def list_scene_effects() -> list[str]:
return list(SCENE_EFFECTS.keys())
def scene_effect_metadata() -> dict[str, dict[str, str]]:
return {key: {"label": value["label"]} for key, value in SCENE_EFFECTS.items()}
def scene_effect_filter(key: str | None) -> str:
if not key:
return ""
return SCENE_EFFECTS.get(key, SCENE_EFFECTS["none"])["filter"]
def _set_default(payload: dict[str, Any], key: str, value: Any) -> None:
if key not in payload or payload[key] in (None, ""):
payload[key] = value
def _style_scene(scene: Any, style: CreativeStyle, index: int) -> Any:
if not isinstance(scene, dict):
return scene
styled = deepcopy(scene)
transition = styled.get("transition")
if transition in (None, "", "fade"):
styled["transition"] = style.transition_sequence[index % len(style.transition_sequence)]
if styled.get("effect") in (None, ""):
styled["effect"] = style.scene_effect_sequence[index % len(style.scene_effect_sequence)]
_set_default(styled, "background", "blur")
_set_default(styled, "layout", "fill")
return styled