| from __future__ import annotations |
|
|
| import re |
| from copy import deepcopy |
| from typing import Any |
|
|
|
|
| PLACEHOLDER = re.compile(r"{{\s*([A-Za-z_][A-Za-z0-9_.-]*)\s*}}") |
|
|
| AUTOMATION_TEMPLATES: dict[str, dict[str, Any]] = { |
| "vertical-captioned-clip": { |
| "preset": "tiktok_9_16_fast", |
| "output_name": "{{output_name}}", |
| "scenes": "{{scenes}}", |
| "voiceover": "{{voiceover}}", |
| "background_music": "{{background_music}}", |
| "auto_subtitles": True, |
| "audio_normalize": True, |
| }, |
| "podcast-clip": { |
| "preset": "podcast_square", |
| "output_name": "{{output_name}}", |
| "scenes": "{{scenes}}", |
| "auto_subtitles": True, |
| "audio_normalize": True, |
| }, |
| "product-promo": { |
| "preset": "capcut_product_launch", |
| "output_name": "{{output_name}}", |
| "scenes": "{{scenes}}", |
| "watermark": "{{watermark}}", |
| "background_music": "{{background_music}}", |
| }, |
| } |
|
|
|
|
| def list_automation_templates() -> list[str]: |
| return sorted(AUTOMATION_TEMPLATES) |
|
|
|
|
| def automation_template_catalog() -> dict[str, dict[str, Any]]: |
| return deepcopy(AUTOMATION_TEMPLATES) |
|
|
|
|
| def resolve_render_template( |
| template: str | dict[str, Any], |
| variables: dict[str, Any], |
| overrides: dict[str, Any] | None = None, |
| ) -> dict[str, Any]: |
| if isinstance(template, str): |
| if template not in AUTOMATION_TEMPLATES: |
| raise ValueError(f"Unknown automation template: {template}") |
| source = deepcopy(AUTOMATION_TEMPLATES[template]) |
| elif isinstance(template, dict): |
| source = deepcopy(template) |
| else: |
| raise ValueError("Template must be a registered name or JSON object") |
|
|
| rendered = _substitute(source, variables) |
| if not isinstance(rendered, dict): |
| raise ValueError("Rendered template must produce a JSON object") |
| return _deep_merge(rendered, overrides or {}) |
|
|
|
|
| def _substitute(value: Any, variables: dict[str, Any]) -> Any: |
| if isinstance(value, dict): |
| return {key: _substitute(item, variables) for key, item in value.items()} |
| if isinstance(value, list): |
| return [_substitute(item, variables) for item in value] |
| if not isinstance(value, str): |
| return value |
|
|
| exact = PLACEHOLDER.fullmatch(value) |
| if exact: |
| return deepcopy(_lookup(variables, exact.group(1))) |
|
|
| return PLACEHOLDER.sub(lambda match: str(_lookup(variables, match.group(1))), value) |
|
|
|
|
| def _lookup(variables: dict[str, Any], key: str) -> Any: |
| value: Any = variables |
| for part in key.split("."): |
| if not isinstance(value, dict) or part not in value: |
| raise ValueError(f"Missing template variable: {key}") |
| value = value[part] |
| return value |
|
|
|
|
| def _deep_merge(base: dict[str, Any], overrides: dict[str, Any]) -> dict[str, Any]: |
| output = deepcopy(base) |
| for key, value in overrides.items(): |
| if isinstance(value, dict) and isinstance(output.get(key), dict): |
| output[key] = _deep_merge(output[key], value) |
| else: |
| output[key] = deepcopy(value) |
| return output |
|
|