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())}