| 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 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) |
| project_list.click(fn=_list_projects, outputs=project_output) |
|
|
| 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) |
|
|
| 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=_timeline_add, inputs=[timeline_project_id, timeline_track_type, timeline_item], outputs=timeline_output) |
|
|
| 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=_timeline_operation, inputs=[timeline_project_id, timeline_operation_json], outputs=timeline_output) |
|
|
| with gr.Tab("Templates"): |
| template_button = gr.Button("Load Template Catalog") |
| template_output = gr.JSON(label="Templates") |
| template_button.click(fn=lambda: _catalog_section("templates"), outputs=template_output) |
|
|
| with gr.Tab("Effects"): |
| effect_button = gr.Button("Load Effect Catalog") |
| effect_output = gr.JSON(label="Effects") |
| effect_button.click(fn=lambda: _catalog_section("effects"), outputs=effect_output) |
|
|
| with gr.Tab("Filters"): |
| filter_button = gr.Button("Load Filter Catalog") |
| filter_output = gr.JSON(label="Filters") |
| filter_button.click(fn=lambda: _catalog_section("filters"), outputs=filter_output) |
|
|
| 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_generation, inputs=caption_text, outputs=caption_output) |
|
|
| with gr.Tab("Audio"): |
| audio_button = gr.Button("Load Audio Catalog") |
| audio_output = gr.JSON(label="Audio") |
| audio_button.click(fn=lambda: {"audio": capability_catalog()["audio"], "music": capability_catalog()["music_generator"]}, outputs=audio_output) |
|
|
| 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) |
|
|
| 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_json, inputs=render_json, outputs=render_output) |
|
|
| 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) |
|
|
| 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_json, inputs=batch_json, outputs=batch_output) |
|
|
| 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=_job_status, inputs=status_job_id, outputs=status_output) |
|
|
| 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=_job_logs, inputs=logs_job_id, outputs=logs_output) |
|
|
| 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=_download_path, inputs=download_job_id, outputs=download_output) |
|
|
| 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_file, |
| inputs=[transcribe_audio, transcribe_model, transcribe_language], |
| outputs=transcribe_output, |
| ) |
|
|
| 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_asset, inputs=asset_path, outputs=inspect_output) |
|
|
| 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_analysis, |
| inputs=[analysis_media, analysis_transcript, analysis_platform], |
| outputs=analysis_output, |
| ) |
|
|
| 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_clips, inputs=[clip_media, clip_json], outputs=clip_output) |
|
|
| 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, inputs=[metadata_topic, metadata_platform], outputs=metadata_output) |
|
|
| 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=_submit_publish, inputs=[publish_media, publish_title, publish_platforms], outputs=publish_output) |
|
|
| with gr.Tab("Settings"): |
| settings_button = gr.Button("Load Settings") |
| settings_output = gr.JSON(label="Settings") |
| settings_button.click(fn=_settings_payload, outputs=settings_output) |
|
|
| with gr.Tab("Queue Monitor"): |
| queue_button = gr.Button("Refresh Queue") |
| queue_output = gr.JSON(label="Queue") |
| queue_button.click(fn=_queue_status, outputs=queue_output) |
|
|
| return dashboard |
|
|
|
|
| def _submit_render_json(payload: str) -> dict[str, Any]: |
| 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]: |
| metadata = json.loads(metadata_json or "{}") |
| return {"project": project_manager.create(name, metadata=metadata)} |
|
|
|
|
| def _list_projects() -> dict[str, Any]: |
| return {"projects": project_manager.list()} |
|
|
|
|
| def _add_project_asset(project_id: str, asset_json: str) -> dict[str, Any]: |
| return {"project": project_manager.add_asset(project_id, json.loads(asset_json or "{}"))} |
|
|
|
|
| def _timeline_add(project_id: str, track_type: str, item_json: str) -> dict[str, Any]: |
| return {"project": project_manager.add_to_timeline(project_id, json.loads(item_json or "{}"), track_type=track_type)} |
|
|
|
|
| def _timeline_operation(project_id: str, operation_json: str) -> dict[str, Any]: |
| 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 _submit_caption_generation(text: str) -> dict[str, Any]: |
| 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]: |
| 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_json(payload: str) -> dict[str, Any]: |
| 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]: |
| 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 _job_status(job_id: str) -> dict[str, Any]: |
| return job_manager.get(job_id).__dict__ |
|
|
|
|
| def _job_logs(job_id: str) -> str: |
| return "\n\n".join(job_manager.get(job_id).logs) |
|
|
|
|
| def _download_path(job_id: str) -> str | None: |
| record = job_manager.get(job_id) |
| if record.state != "COMPLETED": |
| return None |
| return record.output_path |
|
|
|
|
| def _inspect_asset(path: str) -> dict[str, Any]: |
| return RenderEngine(settings).inspect_asset(path) |
|
|
|
|
| def _transcribe_file(path: str, model_size: str, language: str) -> dict[str, Any]: |
| return RenderEngine(settings).transcribe( |
| path, |
| model_size=model_size, |
| language=language.strip() or None, |
| word_timestamps=True, |
| ) |
|
|
|
|
| def _submit_analysis(media: str, transcript: str, platform: str) -> dict[str, Any]: |
| 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_clips(media: str, clips_json: str) -> dict[str, Any]: |
| 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(topic: str, platform: str) -> dict[str, Any]: |
| 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 _submit_publish(media: str, title: str, platforms: str) -> dict[str, Any]: |
| 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 _queue_status() -> dict[str, Any]: |
| return job_manager.summary() |
|
|
|
|
| def _settings_payload() -> dict[str, Any]: |
| 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") |
|
|
|
|
| if __name__ == "__main__": |
| uvicorn.run(app, host="0.0.0.0", port=7860) |
|
|