ffmpeg / app.py
Ava2lon's picture
Upload 6 files
6c45e6d verified
Raw
History Blame Contribute Delete
17.8 kB
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)