File size: 17,755 Bytes
6268049
 
 
 
 
 
 
 
 
6c45e6d
6268049
 
 
6c45e6d
39035d1
6268049
 
 
6c45e6d
6268049
6c45e6d
 
6268049
6c45e6d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6268049
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39035d1
aa4434c
 
6268049
 
39035d1
6268049
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee22b52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6268049
 
 
 
 
 
39035d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c45e6d
 
 
 
 
39035d1
 
 
 
 
98fa87c
6268049
 
 
39035d1
6268049
 
 
 
 
6c45e6d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6268049
 
39035d1
6268049
 
 
39035d1
 
 
 
 
 
 
 
 
aa4434c
 
 
 
 
39035d1
aa4434c
 
 
6268049
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee22b52
 
 
 
 
 
 
 
 
39035d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c45e6d
 
 
 
 
 
 
 
 
 
 
 
 
 
6268049
 
 
 
 
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
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)