studio / renderer /templates /platforms.py
Ava2lon's picture
Upload 170 files
345855e verified
Raw
History Blame Contribute Delete
6.51 kB
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
@dataclass(frozen=True)
class PlatformProfile:
key: str
label: str
width: int
height: int
fps: int = 30
video_codec: str = "libx264"
audio_codec: str = "aac"
audio_bitrate: str = "128k"
audio_sample_rate: int = 48000
crf: int = 23
maxrate: str | None = None
bufsize: str | None = None
max_duration_seconds: int | None = None
recommended_duration_seconds: tuple[int, int] | None = None
safe_zones: dict[str, int] = field(default_factory=dict)
notes: tuple[str, ...] = ()
@property
def aspect_ratio(self) -> str:
return f"{self.width}:{self.height}"
def metadata(self) -> dict[str, Any]:
return {
"platform": self.key,
"label": self.label,
"width": self.width,
"height": self.height,
"fps": self.fps,
"aspect_ratio": self.aspect_ratio,
"video_codec": self.video_codec,
"audio_codec": self.audio_codec,
"audio_bitrate": self.audio_bitrate,
"max_duration_seconds": self.max_duration_seconds,
"recommended_duration_seconds": self.recommended_duration_seconds,
"safe_zones": self.safe_zones,
"notes": list(self.notes),
}
COMMON_VERTICAL_SAFE_ZONES = {
"top_px": 220,
"bottom_px": 340,
"left_px": 80,
"right_px": 80,
}
PLATFORM_PROFILES: dict[str, PlatformProfile] = {
"tiktok": PlatformProfile(
key="tiktok",
label="TikTok vertical",
width=1080,
height=1920,
fps=30,
maxrate="8M",
bufsize="16M",
max_duration_seconds=600,
recommended_duration_seconds=(12, 60),
safe_zones=COMMON_VERTICAL_SAFE_ZONES,
notes=(
"9:16 vertical MP4 keeps the frame full-screen in the For You feed.",
"Use licensed or original music to avoid muted audio or takedowns.",
),
),
"instagram_reels": PlatformProfile(
key="instagram_reels",
label="Instagram Reels",
width=1080,
height=1920,
fps=30,
maxrate="8M",
bufsize="16M",
max_duration_seconds=180,
recommended_duration_seconds=(7, 90),
safe_zones=COMMON_VERTICAL_SAFE_ZONES,
notes=(
"9:16 is the safest Reels export to avoid cropping or blank space.",
"Keep captions and logos away from top and bottom app chrome.",
),
),
"facebook_reels": PlatformProfile(
key="facebook_reels",
label="Facebook Reels",
width=1080,
height=1920,
fps=30,
maxrate="8M",
bufsize="16M",
max_duration_seconds=180,
recommended_duration_seconds=(7, 90),
safe_zones=COMMON_VERTICAL_SAFE_ZONES,
),
"youtube_shorts": PlatformProfile(
key="youtube_shorts",
label="YouTube Shorts",
width=1080,
height=1920,
fps=30,
audio_bitrate="192k",
maxrate="10M",
bufsize="20M",
max_duration_seconds=180,
recommended_duration_seconds=(15, 60),
safe_zones={"top_px": 180, "bottom_px": 300, "left_px": 80, "right_px": 80},
notes=(
"YouTube categorizes square or vertical videos up to 3 minutes as Shorts.",
"Avoid Content ID-claimed music in Shorts longer than 60 seconds.",
),
),
"youtube_1080p": PlatformProfile(
key="youtube_1080p",
label="YouTube 1080p landscape",
width=1920,
height=1080,
fps=30,
audio_bitrate="192k",
maxrate="12M",
bufsize="24M",
recommended_duration_seconds=(60, 600),
notes=("16:9 H.264/AAC output for standard YouTube uploads.",),
),
"instagram_feed_square": PlatformProfile(
key="instagram_feed_square",
label="Instagram feed square",
width=1080,
height=1080,
fps=30,
maxrate="8M",
bufsize="16M",
max_duration_seconds=3600,
recommended_duration_seconds=(5, 60),
safe_zones={"top_px": 80, "bottom_px": 120, "left_px": 80, "right_px": 80},
),
"instagram_feed_portrait": PlatformProfile(
key="instagram_feed_portrait",
label="Instagram feed portrait",
width=1080,
height=1350,
fps=30,
maxrate="8M",
bufsize="16M",
max_duration_seconds=3600,
recommended_duration_seconds=(5, 60),
safe_zones={"top_px": 80, "bottom_px": 140, "left_px": 80, "right_px": 80},
),
"snapchat_spotlight": PlatformProfile(
key="snapchat_spotlight",
label="Snapchat Spotlight",
width=1080,
height=1920,
fps=30,
maxrate="8M",
bufsize="16M",
max_duration_seconds=60,
recommended_duration_seconds=(5, 30),
safe_zones=COMMON_VERTICAL_SAFE_ZONES,
notes=("Vertical, fast-paced edits perform best in Spotlight.",),
),
"pinterest_idea_pins": PlatformProfile(
key="pinterest_idea_pins",
label="Pinterest Idea Pins",
width=1080,
height=1920,
fps=30,
maxrate="8M",
bufsize="16M",
max_duration_seconds=300,
recommended_duration_seconds=(6, 45),
safe_zones={"top_px": 160, "bottom_px": 240, "left_px": 80, "right_px": 80},
notes=("Use clear text overlays and evergreen discovery keywords.",),
),
"linkedin_video": PlatformProfile(
key="linkedin_video",
label="LinkedIn video",
width=1920,
height=1080,
fps=30,
audio_bitrate="192k",
maxrate="10M",
bufsize="20M",
max_duration_seconds=600,
recommended_duration_seconds=(30, 180),
safe_zones={"top_px": 80, "bottom_px": 100, "left_px": 80, "right_px": 80},
notes=("Landscape explainers and square clips both work; captions are strongly recommended.",),
),
}
def get_platform_profile(key: str | None) -> PlatformProfile:
if not key:
return PLATFORM_PROFILES["tiktok"]
return PLATFORM_PROFILES.get(key, PLATFORM_PROFILES["tiktok"])
def list_platform_profiles() -> list[str]:
return sorted(PLATFORM_PROFILES)
def platform_profile_metadata() -> dict[str, dict[str, Any]]:
return {key: profile.metadata() for key, profile in sorted(PLATFORM_PROFILES.items())}