File size: 25,105 Bytes
e990dfa 6c2642f a137ca3 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa 6c2642f e990dfa a4e6ce3 99d5f49 a4e6ce3 e990dfa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 | from __future__ import annotations
import json
from pathlib import Path
from typing import Any
import gradio as gr
import uvicorn
from api import api, job_manager, project_manager, settings
from renderer import RenderEngine
from renderer.core.models import AIReelsRequest
from renderer.scenes import Timeline
from renderer.studio import capability_catalog
from renderer.templates import apply_creative_style, apply_preset, list_creative_styles, list_platform_profiles, list_templates
def _mcp_description(summary: str, example: dict[str, Any]) -> str:
"""Build a concise MCP description with a machine-readable JSON example."""
payload = json.dumps(example, indent=2, ensure_ascii=True)
return f"{summary}\n\nExample JSON:\n```json\n{payload}\n```"
def create_dashboard() -> gr.Blocks:
with gr.Blocks(title="Ava2lon Studio AI") as dashboard:
gr.Markdown(
"# Ava2lon Studio AI\n"
"CPU-first CapCut-class video automation studio with REST API parity, async jobs, webhooks, and project JSON."
)
with gr.Tab("Projects"):
project_name = gr.Textbox(label="Project name", value="Untitled Ava2lon Project")
project_metadata = gr.Textbox(label="Metadata JSON", lines=5, value=json.dumps({"platform": "tiktok"}, indent=2))
project_create = gr.Button("Create Project", variant="primary")
project_list = gr.Button("Refresh Projects")
project_output = gr.JSON(label="Projects")
project_create.click(fn=create_project, inputs=[project_name, project_metadata], outputs=project_output, api_name="create_project", api_description=_mcp_description("Create a project from a name and JSON metadata object.", {"name": "Campaign 01", "metadata_json": '{"platform":"tiktok"}'}))
project_list.click(fn=list_projects, outputs=project_output, api_name="list_projects", api_description=_mcp_description("List saved studio projects.", {}))
with gr.Tab("Assets"):
gr.Markdown("Use `/upload` or `/assets/upload` for multipart assets, then attach them to a project with `/project/assets/add`.")
asset_project_id = gr.Textbox(label="Project ID")
asset_json = gr.Textbox(label="Asset JSON", lines=6, value=json.dumps({"path": "clip.mp4", "kind": "video"}, indent=2))
asset_button = gr.Button("Attach Asset", variant="primary")
asset_output = gr.JSON(label="Project")
asset_button.click(fn=add_project_asset, inputs=[asset_project_id, asset_json], outputs=asset_output, api_name="add_project_asset", api_description=_mcp_description("Attach a media asset JSON object to an existing project.", {"project_id": "project_123", "asset_json": '{"path":"clip.mp4","kind":"video"}'}))
with gr.Tab("Timeline"):
timeline_project_id = gr.Textbox(label="Project ID")
timeline_track_type = gr.Dropdown(choices=["video", "audio", "text", "overlay", "sticker", "subtitle"], value="video", label="Track type")
timeline_item = gr.Textbox(
label="Timeline item JSON",
lines=8,
value=json.dumps({"media": "clip.mp4", "start": 0, "duration": 5, "caption": "Hook"}, indent=2),
)
timeline_add_button = gr.Button("Add To Timeline", variant="primary")
timeline_output = gr.JSON(label="Project")
timeline_add_button.click(fn=add_timeline_item, inputs=[timeline_project_id, timeline_track_type, timeline_item], outputs=timeline_output, api_name="add_timeline_item", api_description=_mcp_description("Add a typed item to a project timeline.", {"project_id": "project_123", "track_type": "video", "item_json": '{"media":"clip.mp4","start":0,"duration":5,"caption":"Hook"}'}))
timeline_operation_json = gr.Textbox(
label="Operation JSON",
lines=8,
value=json.dumps({"operation": "split", "item_id": "clip_123", "params": {"offset": 2.5}}, indent=2),
)
timeline_operation_button = gr.Button("Apply Operation")
timeline_operation_button.click(fn=apply_timeline_operation, inputs=[timeline_project_id, timeline_operation_json], outputs=timeline_output, api_name="apply_timeline_operation", api_description=_mcp_description("Apply a timeline operation such as split, trim, insert, or ripple delete.", {"project_id": "project_123", "operation_json": '{"operation":"split","item_id":"clip_123","params":{"offset":2.5}}'}))
with gr.Tab("Templates"):
template_button = gr.Button("Load Template Catalog")
template_output = gr.JSON(label="Templates")
template_button.click(fn=load_template_catalog, outputs=template_output, api_name="load_template_catalog", api_description=_mcp_description("Load available render and caption templates.", {}))
with gr.Tab("Effects"):
effect_button = gr.Button("Load Effect Catalog")
effect_output = gr.JSON(label="Effects")
effect_button.click(fn=load_effect_catalog, outputs=effect_output, api_name="load_effect_catalog", api_description=_mcp_description("Load available video effects.", {}))
with gr.Tab("Filters"):
filter_button = gr.Button("Load Filter Catalog")
filter_output = gr.JSON(label="Filters")
filter_button.click(fn=load_filter_catalog, outputs=filter_output, api_name="load_filter_catalog", api_description=_mcp_description("Load available video filters.", {}))
with gr.Tab("Captions"):
caption_text = gr.Textbox(label="Caption source text", lines=6)
caption_button = gr.Button("Submit Caption Job", variant="primary")
caption_output = gr.JSON(label="Caption Job")
caption_button.click(fn=submit_caption_job, inputs=caption_text, outputs=caption_output, api_name="submit_caption_job", api_description=_mcp_description("Submit a caption-generation job from source text.", {"text": "Launch faster with automated rendering."}))
with gr.Tab("Audio"):
audio_button = gr.Button("Load Audio Catalog")
audio_output = gr.JSON(label="Audio")
audio_button.click(fn=load_audio_catalog, outputs=audio_output, api_name="load_audio_catalog", api_description=_mcp_description("Load audio-processing and music-generation capabilities.", {}))
with gr.Tab("AI Tools"):
ai_tool = gr.Dropdown(choices=capability_catalog()["ai_editing"], value="auto_viral_score", label="AI tool")
ai_payload = gr.Textbox(label="AI payload JSON", lines=8, value=json.dumps({"platform": "tiktok", "text": "A strong opening hook"}, indent=2))
ai_button = gr.Button("Submit AI Tool", variant="primary")
ai_output = gr.JSON(label="AI Job")
ai_button.click(fn=submit_ai_tool, inputs=[ai_tool, ai_payload], outputs=ai_output, api_name="submit_ai_tool", api_description=_mcp_description("Submit a configured AI editing tool job.", {"tool": "auto_viral_score", "payload": '{"platform":"tiktok","text":"A strong opening hook"}'}))
with gr.Tab("Rendering"):
render_json = gr.Textbox(
label="Render JSON",
lines=14,
value="",
placeholder="Paste a production render request JSON object with absolute or uploaded asset paths.",
)
render_button = gr.Button("Submit Render", variant="primary")
render_output = gr.JSON(label="Submission")
render_button.click(fn=submit_render_job, inputs=render_json, outputs=render_output, api_name="submit_render_job", api_description=_mcp_description("Submit a validated JSON video render job.", {"payload": '{"scenes":[{"start":0,"duration":5,"media":"clip.mp4","caption":"Opening hook"}],"platform":"tiktok","output_name":"render.mp4"}'}))
with gr.Tab("AI Reels"):
script = gr.Textbox(label="Script", lines=6)
voiceover = gr.File(label="Voiceover", file_types=["audio"], type="filepath")
assets = gr.File(label="Assets", file_count="multiple", type="filepath")
template = gr.Dropdown(choices=list_templates(), value="tiktok_classic", label="Caption Template")
creative_style = gr.Dropdown(choices=list_creative_styles(), value="viral_shorts", label="Creative Style")
platform = gr.Dropdown(choices=list_platform_profiles(), value="tiktok", label="Platform")
music = gr.File(label="Background Music", file_types=["audio"], type="filepath")
ai_button = gr.Button("Submit AI Reel", variant="primary")
ai_output = gr.JSON(label="Submission")
ai_button.click(fn=submit_ai_reel, inputs=[script, voiceover, assets, template, creative_style, platform, music], outputs=ai_output, api_name="submit_ai_reel", api_description=_mcp_description("Create an AI reel from a script, voiceover, and visual assets. Upload files first and use the returned paths.", {"script": "Three automation tips.", "voiceover": "/tmp/voice.wav", "assets": ["/tmp/a.jpg", "/tmp/b.mp4"], "template": "tiktok_classic", "creative_style": "viral_shorts", "platform": "tiktok", "music": None}))
with gr.Tab("Batch Render"):
batch_json = gr.Textbox(label="Batch JSON", lines=14, value=json.dumps({"jobs": []}, indent=2))
batch_button = gr.Button("Submit Batch", variant="primary")
batch_output = gr.JSON(label="Batch Submission")
batch_button.click(fn=submit_batch_render, inputs=batch_json, outputs=batch_output, api_name="submit_batch_render", api_description=_mcp_description("Submit multiple render jobs from a JSON batch.", {"payload": '{"jobs":[{"scenes":[{"start":0,"duration":3,"media":"clip.mp4"}],"output_name":"clip-a.mp4"}]}'}))
with gr.Tab("Job Status"):
status_job_id = gr.Textbox(label="Job ID")
status_button = gr.Button("Refresh")
status_output = gr.JSON(label="Status")
status_button.click(fn=get_job_status, inputs=status_job_id, outputs=status_output, api_name="get_job_status", api_description=_mcp_description("Retrieve the current state and metrics for a job.", {"job_id": "job_abc123"}))
with gr.Tab("Logs"):
logs_job_id = gr.Textbox(label="Job ID")
logs_button = gr.Button("Load Logs")
logs_output = gr.Textbox(label="Logs", lines=20)
logs_button.click(fn=get_job_logs, inputs=logs_job_id, outputs=logs_output, api_name="get_job_logs", api_description=_mcp_description("Retrieve execution logs for a job.", {"job_id": "job_abc123"}))
with gr.Tab("Downloads"):
download_job_id = gr.Textbox(label="Job ID")
download_button = gr.Button("Get Output")
download_output = gr.File(label="Rendered Video")
download_button.click(fn=get_download_path, inputs=download_job_id, outputs=download_output, api_name="get_download_path", api_description=_mcp_description("Return the completed artifact path for a job.", {"job_id": "job_abc123"}))
with gr.Tab("Transcribe"):
transcribe_audio = gr.File(label="Audio or Video", file_types=["audio", "video"], type="filepath")
transcribe_model = gr.Dropdown(
choices=["tiny", "base", "small", "medium", "large-v3"],
value=settings.whisper_model_size,
label="Whisper Model",
)
transcribe_language = gr.Textbox(label="Language", placeholder="Optional ISO code, e.g. en")
transcribe_button = gr.Button("Transcribe", variant="primary")
transcribe_output = gr.JSON(label="Transcript")
transcribe_button.click(
fn=transcribe_audio_file,
inputs=[transcribe_audio, transcribe_model, transcribe_language],
outputs=transcribe_output,
api_name="transcribe_audio_file",
api_description=_mcp_description("Transcribe an uploaded audio or video file with word timestamps. Upload the file first and use its returned path.", {"path": "/tmp/interview.mp3", "model_size": "tiny", "language": "en"}),
)
with gr.Tab("Asset Inspector"):
asset_path = gr.Textbox(label="Asset path")
inspect_button = gr.Button("Inspect")
inspect_output = gr.JSON(label="Metadata")
inspect_button.click(fn=inspect_media_asset, inputs=asset_path, outputs=inspect_output, api_name="inspect_media_asset", api_description=_mcp_description("Inspect media codecs, streams, duration, and dimensions.", {"path": "/app/storage/video.mp4"}))
with gr.Tab("AI Analysis"):
analysis_media = gr.Textbox(label="Media URL or path")
analysis_transcript = gr.Textbox(label="Transcript", lines=5)
analysis_platform = gr.Dropdown(choices=list_platform_profiles(), value="tiktok", label="Target Platform")
analysis_button = gr.Button("Submit Analysis", variant="primary")
analysis_output = gr.JSON(label="Analysis Job")
analysis_button.click(
fn=submit_media_analysis,
inputs=[analysis_media, analysis_transcript, analysis_platform],
outputs=analysis_output,
api_name="submit_media_analysis",
api_description=_mcp_description("Analyze media for highlights, pacing, platform fit, and engagement signals.", {"media": "https://example.com/video.mp4", "transcript": "A strong opening hook and useful explanation.", "platform": "tiktok"}),
)
with gr.Tab("Clip Generator"):
clip_media = gr.Textbox(label="Media URL or path")
clip_json = gr.Textbox(label="Clip JSON", lines=6, value=json.dumps([{"start": 0, "end": 8}], indent=2))
clip_button = gr.Button("Generate Clips", variant="primary")
clip_output = gr.JSON(label="Clip Job")
clip_button.click(fn=submit_clip_job, inputs=[clip_media, clip_json], outputs=clip_output, api_name="submit_clip_job", api_description=_mcp_description("Generate one or more clips from media and timed clip JSON.", {"media": "https://example.com/video.mp4", "clips_json": '[{"start":0,"end":8},{"start":20,"end":32}]'}))
with gr.Tab("Metadata"):
metadata_topic = gr.Textbox(label="Topic or transcript", lines=5)
metadata_platform = gr.Dropdown(choices=list_platform_profiles(), value="tiktok", label="Platform")
metadata_button = gr.Button("Generate Metadata", variant="primary")
metadata_output = gr.JSON(label="Metadata Job")
metadata_button.click(fn=submit_metadata_job, inputs=[metadata_topic, metadata_platform], outputs=metadata_output, api_name="submit_metadata_job", api_description=_mcp_description("Generate platform-aware title, description, hashtags, and chapters.", {"topic": "Five n8n video automation mistakes", "platform": "youtube_shorts"}))
with gr.Tab("Publishing"):
publish_media = gr.Textbox(label="Media URL or rendered output path")
publish_title = gr.Textbox(label="Title")
publish_platforms = gr.Textbox(label="Platforms", value="youtube,tiktok,instagram")
publish_button = gr.Button("Create Publish Draft", variant="primary")
publish_output = gr.JSON(label="Publish Job")
publish_button.click(fn=create_publish_draft, inputs=[publish_media, publish_title, publish_platforms], outputs=publish_output, api_name="create_publish_draft", api_description=_mcp_description("Create a publishing draft for one or more platforms.", {"media": "/app/exports/render.mp4", "title": "Automation Tips", "platforms": "youtube,tiktok,instagram"}))
with gr.Tab("Settings"):
settings_button = gr.Button("Load Settings")
settings_output = gr.JSON(label="Settings")
settings_button.click(fn=get_runtime_settings, outputs=settings_output, api_name="get_runtime_settings", api_description=_mcp_description("Return renderer configuration and runtime capability settings.", {}))
with gr.Tab("Queue Monitor"):
queue_button = gr.Button("Refresh Queue")
queue_output = gr.JSON(label="Queue")
queue_button.click(fn=get_queue_status, outputs=queue_output, api_name="get_queue_status", api_description=_mcp_description("Return queue counts and active render jobs.", {}))
return dashboard
def submit_render_job(payload: str) -> dict[str, Any]:
"""Submit a validated JSON timeline for asynchronous video rendering."""
data = apply_creative_style(apply_preset(json.loads(payload)))
request = Timeline.request_from_payload(data)
job_id = job_manager.submit_render(request)
return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
def create_project(name: str, metadata_json: str) -> dict[str, Any]:
"""Create a persistent studio project with optional JSON metadata."""
metadata = json.loads(metadata_json or "{}")
return {"project": project_manager.create(name, metadata=metadata)}
def list_projects() -> dict[str, Any]:
"""List all persistent studio projects."""
return {"projects": project_manager.list()}
def add_project_asset(project_id: str, asset_json: str) -> dict[str, Any]:
"""Attach a media asset JSON object to an existing project."""
return {"project": project_manager.add_asset(project_id, json.loads(asset_json or "{}"))}
def add_timeline_item(project_id: str, track_type: str, item_json: str) -> dict[str, Any]:
"""Add a JSON item to a video, audio, text, overlay, sticker, or subtitle track."""
return {"project": project_manager.add_to_timeline(project_id, json.loads(item_json or "{}"), track_type=track_type)}
def apply_timeline_operation(project_id: str, operation_json: str) -> dict[str, Any]:
"""Apply a JSON timeline operation such as split, trim, insert, or ripple delete."""
data = json.loads(operation_json or "{}")
return {
"project": project_manager.timeline_operation(
project_id,
data.get("operation", "drag"),
item_id=data.get("item_id"),
params=data.get("params", {}),
)
}
def _catalog_section(section: str) -> dict[str, Any]:
catalog = capability_catalog()
return {section: catalog.get(section)}
def load_template_catalog() -> dict[str, Any]:
"""Return available render and caption template capabilities."""
return _catalog_section("templates")
def load_effect_catalog() -> dict[str, Any]:
"""Return available video effect capabilities."""
return _catalog_section("effects")
def load_filter_catalog() -> dict[str, Any]:
"""Return available video filter capabilities."""
return _catalog_section("filters")
def load_audio_catalog() -> dict[str, Any]:
"""Return audio-processing and music-generation capabilities."""
catalog = capability_catalog()
return {"audio": catalog["audio"], "music": catalog["music_generator"]}
def submit_caption_job(text: str) -> dict[str, Any]:
"""Submit an asynchronous caption-generation job from source text."""
from renderer.studio import StudioTaskProcessor
job_id = job_manager.submit_task(lambda task_id, log: StudioTaskProcessor(settings, log=log).caption_generate({"text": text}, task_id))
return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
def submit_ai_tool(tool: str, payload: str) -> dict[str, Any]:
"""Submit an AI editing tool using its name and a JSON payload."""
from renderer.studio import StudioTaskProcessor
data = json.loads(payload or "{}")
job_id = job_manager.submit_task(lambda task_id, log: StudioTaskProcessor(settings, log=log).ai_tool(tool, data, task_id))
return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
def submit_batch_render(payload: str) -> dict[str, Any]:
"""Submit multiple asynchronous render jobs from a JSON jobs array."""
data = json.loads(payload)
requests = [Timeline.request_from_payload(apply_creative_style(apply_preset(job))) for job in data.get("jobs", [])]
return {"job_ids": job_manager.submit_batch(requests)}
def submit_ai_reel(
script: str,
voiceover: str,
assets: list[str],
template: str,
creative_style: str,
platform: str,
music: str | None,
) -> dict[str, Any]:
"""Create an AI reel from a script, voiceover, visual assets, style, and music."""
request = AIReelsRequest(
script=script,
voiceover=voiceover,
assets=assets or [],
template=template,
creative_style=creative_style,
platform=platform,
background_music=music,
)
job_id = job_manager.submit_ai_reels(request)
return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
def get_job_status(job_id: str) -> dict[str, Any]:
"""Return state, metrics, artifact metadata, and errors for a job ID."""
return job_manager.get(job_id).__dict__
def get_job_logs(job_id: str) -> str:
"""Return execution log messages for a job ID."""
return "\n\n".join(job_manager.get(job_id).logs)
def get_download_path(job_id: str) -> str | None:
"""Return the local artifact path when a job has completed."""
record = job_manager.get(job_id)
if record.state != "COMPLETED":
return None
return record.output_path
def inspect_media_asset(path: str) -> dict[str, Any]:
"""Inspect a local media asset for streams, codecs, duration, and dimensions."""
return RenderEngine(settings).inspect_asset(path)
def transcribe_audio_file(path: str, model_size: str, language: str) -> dict[str, Any]:
"""Transcribe an uploaded audio or video file with optional language selection."""
return RenderEngine(settings).transcribe(
path,
model_size=model_size,
language=language.strip() or None,
word_timestamps=True,
)
def submit_media_analysis(media: str, transcript: str, platform: str) -> dict[str, Any]:
"""Analyze media for highlights, pacing, retention, engagement, and platform fit."""
from renderer.platform import PlatformProcessor
job_id = job_manager.submit_task(
lambda task_id, log: PlatformProcessor(settings, log=log).analyze(media, task_id, transcript=transcript, platform=platform)
)
return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
def submit_clip_job(media: str, clips_json: str) -> dict[str, Any]:
"""Generate timed clips from a media path or URL and a JSON clip specification."""
from renderer.platform import PlatformProcessor
clips = json.loads(clips_json)
job_id = job_manager.submit_task(lambda task_id, log: PlatformProcessor(settings, log=log).clips(media, task_id, clips))
return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
def submit_metadata_job(topic: str, platform: str) -> dict[str, Any]:
"""Generate platform-aware title, description, hashtags, keywords, and chapters."""
from renderer.platform import PlatformProcessor
job_id = job_manager.submit_task(lambda task_id, log: PlatformProcessor(settings, log=log).metadata(task_id, topic=topic, platform=platform))
return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
def create_publish_draft(media: str, title: str, platforms: str) -> dict[str, Any]:
"""Create an asynchronous publishing draft for comma-separated platforms."""
from renderer.platform import PlatformProcessor
payload = {"media": media, "title": title, "platforms": [item.strip() for item in platforms.split(",") if item.strip()], "draft": True}
job_id = job_manager.submit_task(lambda task_id, log: PlatformProcessor(settings, log=log).publish(payload, task_id))
return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
def get_queue_status() -> dict[str, Any]:
"""Return queue totals, state counts, worker limits, and active jobs."""
return job_manager.summary()
def get_runtime_settings() -> dict[str, Any]:
"""Return renderer paths, worker settings, Whisper settings, and capabilities."""
return {
"product": "Ava2lon Studio AI",
"base_dir": str(settings.base_dir),
"temp_dir": str(settings.temp_dir),
"exports_dir": str(settings.exports_dir),
"storage_dir": str(settings.storage_dir),
"max_workers": settings.max_workers,
"whisper_model_size": settings.whisper_model_size,
"whisper_device": settings.whisper_device,
"capabilities": capability_catalog()["principles"],
}
app = gr.mount_gradio_app(
api,
create_dashboard(),
path="/dashboard",
root_path="/dashboard",
mcp_server=True,
)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=7860)
|