"""
Scene render adapter (phase-2 swap for video_job.render_now).
Thin, asyncio-friendly wrapper over the existing ComfyUI /
Animate pipeline at ``app.comfy.run_workflow`` + the shared
``app.asset_registry``. No new pipeline logic — just the glue
that turns a (scene_prompt, duration_sec) pair into a registered
asset id.
Every failure path returns ``None`` so the caller (video_job.
render_now) can gracefully fall back to the stub path and keep
the player's polling loop alive.
"""
from __future__ import annotations
import asyncio
import logging
import os
from typing import Any, Dict, List, Optional
from .edit_recipes import EditRecipe, recipe_to_variables
from .persona_assets import PersonaAssets, resolve_persona_assets
from .playback_config import PlaybackConfig, load_playback_config
log = logging.getLogger(__name__)
_DEFAULT_NEGATIVE = (
"blurry, low quality, watermark, extra fingers, deformed, mutated, "
"distorted face, low-res, jpeg artifacts"
)
_IMAGE_SUFFIXES = (".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tiff")
_VIDEO_SUFFIXES = (".mp4", ".mov", ".mkv", ".webm", ".avi")
async def render_scene_async(
*,
scene_prompt: str,
duration_sec: int,
session_id: str,
persona_hint: str = "",
media_type: str = "video",
config: Optional[PlaybackConfig] = None,
edit_recipe: Optional[Any] = None,
persona_project_id: str = "",
user_id: str = "",
) -> Optional[str]:
"""Submit a workflow, extract the first usable media URL, and
register it as a durable asset. Returns the asset id or
``None`` on any failure / when the render flag is off.
``media_type='image'`` swaps to the still-image workflow
(``PlaybackConfig.image_workflow``) — a fast GPU-friendly path
for operators without the VRAM budget for full video clips. The
player already picks ``
`` vs ``