Ava2lon commited on
Commit
aa4434c
·
verified ·
1 Parent(s): 025fafe

Upload 6 files

Browse files
Files changed (3) hide show
  1. .gitignore +29 -0
  2. api.py +49 -15
  3. app.py +15 -6
.gitignore ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+
5
+ temp/
6
+ exports/
7
+ jobs/
8
+ models/
9
+ storage/
10
+
11
+ .env
12
+ .env.*
13
+ !.env.example
14
+
15
+ node_modules/
16
+ .next/
17
+ out/
18
+ dist/
19
+ coverage/
20
+ *.tsbuildinfo
21
+
22
+ frontend/node_modules/
23
+ frontend/.next/
24
+ frontend/out/
25
+ frontend/.vercel/
26
+ frontend/.env
27
+ frontend/.env.*
28
+ !frontend/.env.example
29
+
api.py CHANGED
@@ -1,4 +1,4 @@
1
- from __future__ import annotations
2
 
3
  import json
4
  import shutil
@@ -19,13 +19,25 @@ from renderer.jobs import JobManager
19
  from renderer.scenes import Timeline
20
  from renderer import RenderEngine
21
  from renderer.subtitles import SubtitleEvent, SubtitleGenerator
22
- from renderer.templates import apply_preset, list_presets, list_templates
23
 
24
  settings = Settings()
25
  settings.ensure_dirs()
26
  job_manager = JobManager(settings)
27
  api = FastAPI(title="Basyx FFmpeg Rendering Engine", version="1.0.0")
28
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  class ScenePayload(BaseModel):
31
  start: float = Field(ge=0)
@@ -42,9 +54,17 @@ class RenderPayload(BaseModel):
42
  scenes: list[ScenePayload]
43
  template: str = "tiktok_classic"
44
  preset: str | None = None
 
45
  output_name: str = "render.mp4"
46
  voiceover: str | None = None
47
  background_music: str | None = None
 
 
 
 
 
 
 
48
  subtitle_format: str = "ass"
49
  auto_subtitles: bool = False
50
  subtitle_language: str | None = None
@@ -68,8 +88,16 @@ class AIReelsPayload(BaseModel):
68
  voiceover: str
69
  assets: list[str]
70
  template: str = "tiktok_classic"
 
71
  output_name: str = "ai_reel.mp4"
72
  background_music: str | None = None
 
 
 
 
 
 
 
73
 
74
 
75
  class BatchPayload(BaseModel):
@@ -112,7 +140,12 @@ def health() -> dict[str, str]:
112
 
113
  @api.get("/presets")
114
  def presets() -> dict[str, list[str]]:
115
- return {"presets": list_presets(), "caption_templates": list_templates()}
 
 
 
 
 
116
 
117
 
118
  @api.post("/render")
@@ -121,19 +154,14 @@ def render(payload: RenderPayload | AIReelsPayload) -> dict[str, str]:
121
  job_id = job_manager.submit_ai_reels(AIReelsRequest(**payload.model_dump()))
122
  else:
123
  job_id = job_manager.submit_render(_render_request(payload))
124
- record = job_manager.get(job_id)
125
- return {
126
- "job_id": job_id,
127
- "status_url": f"/status/{job_id}",
128
- "download_url": f"/download/{job_id}?token={record.download_token}",
129
- }
130
 
131
 
132
  @api.post("/render/ai-reels")
133
  def render_ai_reels(payload: AIReelsPayload) -> dict[str, str]:
134
  job_id = job_manager.submit_ai_reels(AIReelsRequest(**payload.model_dump()))
135
- record = job_manager.get(job_id)
136
- return {"job_id": job_id, "status_url": f"/status/{job_id}", "download_url": f"/download/{job_id}?token={record.download_token}"}
137
 
138
 
139
  @api.post("/render/batch")
@@ -148,8 +176,7 @@ async def render_upload(request_json: str = Form(...), files: list[UploadFile] =
148
  payload_data = _replace_upload_refs(json.loads(request_json), uploads)
149
  payload = RenderPayload.model_validate(payload_data)
150
  job_id = job_manager.submit_render(_render_request(payload))
151
- record = job_manager.get(job_id)
152
- return {"job_id": job_id, "status_url": f"/status/{job_id}", "download_url": f"/download/{job_id}?token={record.download_token}"}
153
 
154
 
155
  @api.post("/render/ai-reels/upload")
@@ -158,8 +185,7 @@ async def render_ai_reels_upload(request_json: str = Form(...), files: list[Uplo
158
  payload_data = _replace_upload_refs(json.loads(request_json), uploads)
159
  payload = AIReelsPayload.model_validate(payload_data)
160
  job_id = job_manager.submit_ai_reels(AIReelsRequest(**payload.model_dump()))
161
- record = job_manager.get(job_id)
162
- return {"job_id": job_id, "status_url": f"/status/{job_id}", "download_url": f"/download/{job_id}?token={record.download_token}"}
163
 
164
 
165
  @api.post("/assets/upload")
@@ -308,9 +334,17 @@ def _render_request(payload: RenderPayload) -> RenderRequest:
308
  scenes=[Scene(**scene.model_dump()) for scene in payload.scenes],
309
  template=payload.template,
310
  preset=payload.preset,
 
311
  output_name=payload.output_name,
312
  voiceover=payload.voiceover,
313
  background_music=payload.background_music,
 
 
 
 
 
 
 
314
  subtitle_format=payload.subtitle_format, # type: ignore[arg-type]
315
  auto_subtitles=payload.auto_subtitles,
316
  subtitle_language=payload.subtitle_language,
 
1
+ from __future__ import annotations
2
 
3
  import json
4
  import shutil
 
19
  from renderer.scenes import Timeline
20
  from renderer import RenderEngine
21
  from renderer.subtitles import SubtitleEvent, SubtitleGenerator
22
+ from renderer.templates import apply_preset, list_platform_profiles, list_presets, list_templates, platform_profile_metadata
23
 
24
  settings = Settings()
25
  settings.ensure_dirs()
26
  job_manager = JobManager(settings)
27
  api = FastAPI(title="Basyx FFmpeg Rendering Engine", version="1.0.0")
28
 
29
+ def _submission_response(job_id: str) -> dict[str, str]:
30
+ download_url = f"/download/{job_id}"
31
+ try:
32
+ record = job_manager.get(job_id)
33
+ except KeyError:
34
+ token = None
35
+ else:
36
+ token = record.download_token
37
+ if token:
38
+ download_url = f"{download_url}?token={token}"
39
+ return {"job_id": job_id, "status_url": f"/status/{job_id}", "download_url": download_url}
40
+
41
 
42
  class ScenePayload(BaseModel):
43
  start: float = Field(ge=0)
 
54
  scenes: list[ScenePayload]
55
  template: str = "tiktok_classic"
56
  preset: str | None = None
57
+ platform: str | None = None
58
  output_name: str = "render.mp4"
59
  voiceover: str | None = None
60
  background_music: str | None = None
61
+ music_volume: float = Field(default=0.316, ge=0, le=2)
62
+ music_fade_in: float = Field(default=0.0, ge=0)
63
+ music_fade_out: float = Field(default=0.0, ge=0)
64
+ music_loop: bool = True
65
+ music_start: float = Field(default=0.0, ge=0)
66
+ music_ducking: bool = True
67
+ voice_volume: float = Field(default=1.0, ge=0, le=2)
68
  subtitle_format: str = "ass"
69
  auto_subtitles: bool = False
70
  subtitle_language: str | None = None
 
88
  voiceover: str
89
  assets: list[str]
90
  template: str = "tiktok_classic"
91
+ platform: str | None = None
92
  output_name: str = "ai_reel.mp4"
93
  background_music: str | None = None
94
+ music_volume: float = Field(default=0.316, ge=0, le=2)
95
+ music_fade_in: float = Field(default=0.0, ge=0)
96
+ music_fade_out: float = Field(default=0.0, ge=0)
97
+ music_loop: bool = True
98
+ music_start: float = Field(default=0.0, ge=0)
99
+ music_ducking: bool = True
100
+ voice_volume: float = Field(default=1.0, ge=0, le=2)
101
 
102
 
103
  class BatchPayload(BaseModel):
 
140
 
141
  @api.get("/presets")
142
  def presets() -> dict[str, list[str]]:
143
+ return {"presets": list_presets(), "caption_templates": list_templates(), "platforms": list_platform_profiles()}
144
+
145
+
146
+ @api.get("/platforms")
147
+ def platforms() -> dict[str, dict[str, Any]]:
148
+ return {"platforms": platform_profile_metadata()}
149
 
150
 
151
  @api.post("/render")
 
154
  job_id = job_manager.submit_ai_reels(AIReelsRequest(**payload.model_dump()))
155
  else:
156
  job_id = job_manager.submit_render(_render_request(payload))
157
+ return _submission_response(job_id)
158
+
 
 
 
 
159
 
160
 
161
  @api.post("/render/ai-reels")
162
  def render_ai_reels(payload: AIReelsPayload) -> dict[str, str]:
163
  job_id = job_manager.submit_ai_reels(AIReelsRequest(**payload.model_dump()))
164
+ return _submission_response(job_id)
 
165
 
166
 
167
  @api.post("/render/batch")
 
176
  payload_data = _replace_upload_refs(json.loads(request_json), uploads)
177
  payload = RenderPayload.model_validate(payload_data)
178
  job_id = job_manager.submit_render(_render_request(payload))
179
+ return _submission_response(job_id)
 
180
 
181
 
182
  @api.post("/render/ai-reels/upload")
 
185
  payload_data = _replace_upload_refs(json.loads(request_json), uploads)
186
  payload = AIReelsPayload.model_validate(payload_data)
187
  job_id = job_manager.submit_ai_reels(AIReelsRequest(**payload.model_dump()))
188
+ return _submission_response(job_id)
 
189
 
190
 
191
  @api.post("/assets/upload")
 
334
  scenes=[Scene(**scene.model_dump()) for scene in payload.scenes],
335
  template=payload.template,
336
  preset=payload.preset,
337
+ platform=payload.platform,
338
  output_name=payload.output_name,
339
  voiceover=payload.voiceover,
340
  background_music=payload.background_music,
341
+ music_volume=payload.music_volume,
342
+ music_fade_in=payload.music_fade_in,
343
+ music_fade_out=payload.music_fade_out,
344
+ music_loop=payload.music_loop,
345
+ music_start=payload.music_start,
346
+ music_ducking=payload.music_ducking,
347
+ voice_volume=payload.voice_volume,
348
  subtitle_format=payload.subtitle_format, # type: ignore[arg-type]
349
  auto_subtitles=payload.auto_subtitles,
350
  subtitle_language=payload.subtitle_language,
app.py CHANGED
@@ -11,7 +11,7 @@ from api import api, job_manager, settings
11
  from renderer import RenderEngine
12
  from renderer.core.models import AIReelsRequest
13
  from renderer.scenes import Timeline
14
- from renderer.templates import list_templates
15
 
16
 
17
  def create_dashboard() -> gr.Blocks:
@@ -36,9 +36,11 @@ def create_dashboard() -> gr.Blocks:
36
  voiceover = gr.File(label="Voiceover", file_types=["audio"], type="filepath")
37
  assets = gr.File(label="Assets", file_count="multiple", type="filepath")
38
  template = gr.Dropdown(choices=list_templates(), value="tiktok_classic", label="Caption Template")
 
 
39
  ai_button = gr.Button("Submit AI Reel", variant="primary")
40
  ai_output = gr.JSON(label="Submission")
41
- ai_button.click(fn=_submit_ai_reel, inputs=[script, voiceover, assets, template], outputs=ai_output)
42
 
43
  with gr.Tab("Batch Render"):
44
  batch_json = gr.Textbox(label="Batch JSON", lines=14, value=json.dumps({"jobs": []}, indent=2))
@@ -90,7 +92,7 @@ def create_dashboard() -> gr.Blocks:
90
 
91
 
92
  def _submit_render_json(payload: str) -> dict[str, Any]:
93
- data = json.loads(payload)
94
  request = Timeline.request_from_payload(data)
95
  job_id = job_manager.submit_render(request)
96
  return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
@@ -98,12 +100,19 @@ def _submit_render_json(payload: str) -> dict[str, Any]:
98
 
99
  def _submit_batch_json(payload: str) -> dict[str, Any]:
100
  data = json.loads(payload)
101
- requests = [Timeline.request_from_payload(job) for job in data.get("jobs", [])]
102
  return {"job_ids": job_manager.submit_batch(requests)}
103
 
104
 
105
- def _submit_ai_reel(script: str, voiceover: str, assets: list[str], template: str) -> dict[str, Any]:
106
- request = AIReelsRequest(script=script, voiceover=voiceover, assets=assets or [], template=template)
 
 
 
 
 
 
 
107
  job_id = job_manager.submit_ai_reels(request)
108
  return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
109
 
 
11
  from renderer import RenderEngine
12
  from renderer.core.models import AIReelsRequest
13
  from renderer.scenes import Timeline
14
+ from renderer.templates import apply_preset, list_platform_profiles, list_templates
15
 
16
 
17
  def create_dashboard() -> gr.Blocks:
 
36
  voiceover = gr.File(label="Voiceover", file_types=["audio"], type="filepath")
37
  assets = gr.File(label="Assets", file_count="multiple", type="filepath")
38
  template = gr.Dropdown(choices=list_templates(), value="tiktok_classic", label="Caption Template")
39
+ platform = gr.Dropdown(choices=list_platform_profiles(), value="tiktok", label="Platform")
40
+ music = gr.File(label="Background Music", file_types=["audio"], type="filepath")
41
  ai_button = gr.Button("Submit AI Reel", variant="primary")
42
  ai_output = gr.JSON(label="Submission")
43
+ ai_button.click(fn=_submit_ai_reel, inputs=[script, voiceover, assets, template, platform, music], outputs=ai_output)
44
 
45
  with gr.Tab("Batch Render"):
46
  batch_json = gr.Textbox(label="Batch JSON", lines=14, value=json.dumps({"jobs": []}, indent=2))
 
92
 
93
 
94
  def _submit_render_json(payload: str) -> dict[str, Any]:
95
+ data = apply_preset(json.loads(payload))
96
  request = Timeline.request_from_payload(data)
97
  job_id = job_manager.submit_render(request)
98
  return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
 
100
 
101
  def _submit_batch_json(payload: str) -> dict[str, Any]:
102
  data = json.loads(payload)
103
+ requests = [Timeline.request_from_payload(apply_preset(job)) for job in data.get("jobs", [])]
104
  return {"job_ids": job_manager.submit_batch(requests)}
105
 
106
 
107
+ def _submit_ai_reel(script: str, voiceover: str, assets: list[str], template: str, platform: str, music: str | None) -> dict[str, Any]:
108
+ request = AIReelsRequest(
109
+ script=script,
110
+ voiceover=voiceover,
111
+ assets=assets or [],
112
+ template=template,
113
+ platform=platform,
114
+ background_music=music,
115
+ )
116
  job_id = job_manager.submit_ai_reels(request)
117
  return {"job_id": job_id, "status": f"/status/{job_id}", "download": f"/download/{job_id}"}
118