""" 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 ``