Ava2lon commited on
Commit
af78cff
·
verified ·
1 Parent(s): 345855e

Upload 45 files

Browse files
.gitattributes CHANGED
@@ -35,6 +35,3 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  fonts/TikTok-Bold.ttf filter=lfs diff=lfs merge=lfs -text
37
  gradio_demo.png filter=lfs diff=lfs merge=lfs -text
38
- assets/edu_note.wav filter=lfs diff=lfs merge=lfs -text
39
- assets/fun_fact.wav filter=lfs diff=lfs merge=lfs -text
40
- assets/thanks.wav filter=lfs diff=lfs merge=lfs -text
 
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  fonts/TikTok-Bold.ttf filter=lfs diff=lfs merge=lfs -text
37
  gradio_demo.png filter=lfs diff=lfs merge=lfs -text
 
 
 
.gitignore CHANGED
@@ -19,7 +19,9 @@ jobs/*
19
  storage/*
20
  !storage/.gitkeep
21
 
 
 
 
22
  *.db
23
  *.sqlite
24
  *.sqlite3
25
-
 
19
  storage/*
20
  !storage/.gitkeep
21
 
22
+ ingestion/cookies/*
23
+ !ingestion/cookies/.gitkeep
24
+
25
  *.db
26
  *.sqlite
27
  *.sqlite3
 
Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM python:3.10-slim
2
 
3
  ENV PYTHONUNBUFFERED=1 \
4
  PIP_NO_CACHE_DIR=1 \
@@ -16,7 +16,9 @@ ENV PYTHONUNBUFFERED=1 \
16
  FFMPEG_TIMEOUT_SECONDS=900 \
17
  DOWNLOAD_TIMEOUT_SECONDS=60 \
18
  MAX_DOWNLOAD_BYTES=524288000 \
19
- ALLOW_PRIVATE_ASSET_URLS=false
 
 
20
 
21
  RUN apt-get update && apt-get install -y --no-install-recommends \
22
  build-essential \
@@ -53,4 +55,3 @@ RUN mkdir -p /app/temp /app/exports /app/jobs /app/models /app/storage \
53
  EXPOSE 7860
54
 
55
  CMD ["bash", "start.sh"]
56
-
 
1
+ FROM python:3.11-slim
2
 
3
  ENV PYTHONUNBUFFERED=1 \
4
  PIP_NO_CACHE_DIR=1 \
 
16
  FFMPEG_TIMEOUT_SECONDS=900 \
17
  DOWNLOAD_TIMEOUT_SECONDS=60 \
18
  MAX_DOWNLOAD_BYTES=524288000 \
19
+ ALLOW_PRIVATE_ASSET_URLS=false \
20
+ ENABLE_API_DOCS=false \
21
+ SUPPRESS_SCANNER_ACCESS_LOGS=true
22
 
23
  RUN apt-get update && apt-get install -y --no-install-recommends \
24
  build-essential \
 
55
  EXPOSE 7860
56
 
57
  CMD ["bash", "start.sh"]
 
Dockerfile.renderer CHANGED
@@ -19,6 +19,8 @@ ENV PYTHONUNBUFFERED=1 \
19
  WHISPER_DEVICE=cpu \
20
  WHISPER_MODEL_DIR=/app/models \
21
  MAX_RETRIES=3 \
 
 
22
  JOB_RETENTION_SECONDS=86400
23
 
24
  RUN apt-get update && apt-get install -y --no-install-recommends \
 
19
  WHISPER_DEVICE=cpu \
20
  WHISPER_MODEL_DIR=/app/models \
21
  MAX_RETRIES=3 \
22
+ CALLBACK_MAX_RETRIES=3 \
23
+ CALLBACK_RETRY_SECONDS=1 \
24
  JOB_RETENTION_SECONDS=86400
25
 
26
  RUN apt-get update && apt-get install -y --no-install-recommends \
N8N_ENDPOINTS.md CHANGED
@@ -91,6 +91,10 @@ Recommended n8n flow:
91
  For payloads with `callback_url`, use an n8n Webhook node's production URL to
92
  avoid polling.
93
 
 
 
 
 
94
  ## Root and Discovery
95
 
96
  | Method | Path | n8n input | Response |
@@ -105,6 +109,7 @@ These routes use the root base URL.
105
  | Method | Path | n8n input | Response |
106
  | --- | --- | --- | --- |
107
  | GET | `/health` | None | Renderer health. |
 
108
  | GET | `/monitor` | None | Runtime, queue, storage, and capability summary. |
109
  | GET | `/queue` | None | Job queue summary. |
110
  | GET | `/workers` | None | Worker and runtime settings. |
@@ -190,6 +195,8 @@ Example:
190
  | Method | Path | n8n input | Auth | Response |
191
  | --- | --- | --- | --- | --- |
192
  | POST | `/render` | JSON `RenderPayload` or `AIReelsPayload` | None | Async job. |
 
 
193
  | POST | `/render/ai-reels` | JSON `AIReelsPayload` | None | Async job. |
194
  | POST | `/render/batch` | JSON `BatchPayload` | None | Array of job IDs. |
195
  | POST | `/automation/batch` | JSON `BatchPayload` | API key | Array of job IDs. |
@@ -198,6 +205,8 @@ Example:
198
  | POST | `/render/ai-reels/upload` | Multipart `request_json` plus zero or more `files` | None | Async job. |
199
  | POST | `/assets/upload` | Multipart; one or more `files` | None | Uploaded asset references. |
200
  | POST | `/upload` | Multipart `files`; optional `expand_zip` | None | Uploaded or expanded assets. |
 
 
201
 
202
  Basic render body:
203
 
@@ -244,6 +253,36 @@ For `/render/upload`, configure n8n Form-Data as:
244
 
245
  The filename in `upload://clip.mp4` must match the uploaded binary filename.
246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  ## Platform Processing and Toolkit
248
 
249
  All routes in this table require `X-API-Key` and return asynchronous job
@@ -275,6 +314,21 @@ Toolkit example:
275
  }
276
  ```
277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  ## Transcription, Subtitles, Jobs, and Downloads
279
 
280
  | Method | Path | n8n input | Response |
@@ -283,13 +337,14 @@ Toolkit example:
283
  | POST | `/subtitles` | JSON `SubtitlePayload` | File response (`.srt` or `.ass`). |
284
  | POST | `/scene-builder` | JSON `SceneBuildPayload` | Generated scene array. |
285
  | POST | `/transcribe/upload` | Multipart `file` plus optional transcription fields | Transcription JSON. |
286
- | GET | `/status` | Query `job_id` | Job record. |
287
  | GET | `/status/{job_id}` | Replace `{job_id}` | Job record. |
288
  | POST | `/cancel/{job_id}` | Replace `{job_id}` | Updated job record. |
289
  | POST | `/admin/cleanup` | Optional query `older_than_seconds` | Cleanup counts. |
290
  | GET | `/download` | Query `job_id`; optional `token` | File response. |
291
  | GET | `/download/{job_id}` | Replace `{job_id}`; optional query `token` | File response. |
292
  | POST | `/inspect` | Query `path` | Media metadata JSON. |
 
293
 
294
  Transcription body:
295
 
@@ -392,45 +447,6 @@ strategy
392
  batch
393
  ```
394
 
395
- ## Whisper Authentication
396
-
397
- These routes exist only when the Whisper authentication subsystem starts
398
- successfully.
399
-
400
- | Method | Path | n8n input |
401
- | --- | --- | --- |
402
- | POST | `/whisper/api/auth/signup` | JSON `SignupSchema` |
403
- | POST | `/whisper/api/auth/login` | JSON `LoginSchema` |
404
- | POST | `/whisper/api/auth/verify` | JSON `TokenSchema` |
405
- | POST | `/whisper/api/auth/refresh` | JSON `TokenSchema` |
406
- | POST | `/whisper/api/auth/logout` | No body |
407
-
408
- Examples:
409
-
410
- ```json
411
- {
412
- "email": "user@example.com",
413
- "username": "studio_user",
414
- "password": "strong-password"
415
- }
416
- ```
417
-
418
- ```json
419
- {
420
- "email": "user@example.com",
421
- "password": "strong-password"
422
- }
423
- ```
424
-
425
- ```json
426
- {
427
- "token": "<JWT returned by signup or login>"
428
- }
429
- ```
430
-
431
- Logout returns success but does not currently maintain a server-side token
432
- revocation list.
433
-
434
  ## FFmpeg Editor
435
 
436
  | Method | Path | n8n input | Response |
@@ -610,6 +626,7 @@ music_volume: number = 0.316
610
  music_fade_in, music_fade_out, music_start: number = 0
611
  music_loop, music_ducking: boolean = true
612
  voice_volume: number = 1
 
613
  ```
614
 
615
  `BatchPayload`
@@ -837,29 +854,6 @@ transition: string = "fade"
837
  creative_style: string | null
838
  ```
839
 
840
- ### Authentication
841
-
842
- `SignupSchema`
843
-
844
- ```text
845
- email*: valid email
846
- username*: 3-32 letters, numbers, or underscores
847
- password*: 8-72 characters
848
- ```
849
-
850
- `LoginSchema`
851
-
852
- ```text
853
- email*: valid email
854
- password*: 1-72 characters
855
- ```
856
-
857
- `TokenSchema`
858
-
859
- ```text
860
- token*: 10-2048 characters
861
- ```
862
-
863
  ## Framework and UI Endpoints
864
 
865
  These are not business API operations:
@@ -872,9 +866,8 @@ These are not business API operations:
872
  | `/whisper/` | Whisper Gradio interface. |
873
  | `/editor/` | FFmpeg editor Gradio interface. |
874
  | `/mcp/` | MCP Streamable HTTP transport; use an MCP client rather than a normal REST request. |
875
- | `/docs` | Root FastAPI Swagger UI. |
876
- | `/redoc` | Root FastAPI ReDoc UI. |
877
- | `/openapi.json` | Root renderer OpenAPI schema. |
878
- | `/<service>/docs` | Mounted service Swagger UI when not shadowed by its UI mount. |
879
- | `/<service>/openapi.json` | Mounted service OpenAPI schema. |
880
-
 
91
  For payloads with `callback_url`, use an n8n Webhook node's production URL to
92
  avoid polling.
93
 
94
+ Render callbacks use `render.completed`, `render.failed`, and
95
+ `render.cancelled`; toolkit and analysis callbacks use `task.*`. The event is
96
+ available in both the JSON `event` property and `X-Ava2lon-Event` header.
97
+
98
  ## Root and Discovery
99
 
100
  | Method | Path | n8n input | Response |
 
109
  | Method | Path | n8n input | Response |
110
  | --- | --- | --- | --- |
111
  | GET | `/health` | None | Renderer health. |
112
+ | GET | `/healthz` | None | Health-check alias for hosting probes. |
113
  | GET | `/monitor` | None | Runtime, queue, storage, and capability summary. |
114
  | GET | `/queue` | None | Job queue summary. |
115
  | GET | `/workers` | None | Worker and runtime settings. |
 
195
  | Method | Path | n8n input | Auth | Response |
196
  | --- | --- | --- | --- | --- |
197
  | POST | `/render` | JSON `RenderPayload` or `AIReelsPayload` | None | Async job. |
198
+ | POST | `/render/variants` | JSON `RenderVariantsPayload` | None | Render group plus child jobs. |
199
+ | POST | `/render/template` | JSON `TemplateRenderPayload` | None | Async job. |
200
  | POST | `/render/ai-reels` | JSON `AIReelsPayload` | None | Async job. |
201
  | POST | `/render/batch` | JSON `BatchPayload` | None | Array of job IDs. |
202
  | POST | `/automation/batch` | JSON `BatchPayload` | API key | Array of job IDs. |
 
205
  | POST | `/render/ai-reels/upload` | Multipart `request_json` plus zero or more `files` | None | Async job. |
206
  | POST | `/assets/upload` | Multipart; one or more `files` | None | Uploaded asset references. |
207
  | POST | `/upload` | Multipart `files`; optional `expand_zip` | None | Uploaded or expanded assets. |
208
+ | GET | `/render/groups/{group_id}` | Replace `{group_id}` | None | Aggregate state and child jobs. |
209
+ | GET | `/templates/automation` | None | None | Data-driven render templates. |
210
 
211
  Basic render body:
212
 
 
253
 
254
  The filename in `upload://clip.mp4` must match the uploaded binary filename.
255
 
256
+ Render several platform outputs from one timeline with `/render/variants`:
257
+
258
+ ```json
259
+ {
260
+ "scenes": [{"start": 0, "duration": 12, "media": "https://example.com/input.mp4"}],
261
+ "callback_url": "https://n8n.example/webhook/render-events",
262
+ "variants": [
263
+ {"name": "shorts", "platform": "youtube_shorts", "output_name": "shorts.mp4"},
264
+ {"name": "reels", "platform": "instagram_reels", "output_name": "reels.mp4"},
265
+ {"name": "youtube", "platform": "youtube_1080p", "output_name": "youtube.mp4"}
266
+ ]
267
+ }
268
+ ```
269
+
270
+ Use `/render/template` for spreadsheet, CRM, and database-driven renders. An
271
+ exact placeholder preserves arrays, objects, numbers, and booleans instead of
272
+ converting them to strings.
273
+
274
+ ```json
275
+ {
276
+ "template": "vertical-captioned-clip",
277
+ "variables": {
278
+ "output_name": "customer-42.mp4",
279
+ "scenes": [{"start": 0, "duration": 8, "media": "https://example.com/42.mp4"}],
280
+ "voiceover": null,
281
+ "background_music": null
282
+ }
283
+ }
284
+ ```
285
+
286
  ## Platform Processing and Toolkit
287
 
288
  All routes in this table require `X-API-Key` and return asynchronous job
 
314
  }
315
  ```
316
 
317
+ FFmpeg automation tasks added for n8n:
318
+
319
+ | Task | Important `params` | Download artifact |
320
+ | --- | --- | --- |
321
+ | `inspect` | None | FFprobe metadata JSON |
322
+ | `loudness_analyze` | `target_lufs` | LUFS/LRA/true-peak JSON |
323
+ | `silence_detect` | `noise_db`, `duration_seconds` | Timed silence events JSON |
324
+ | `black_detect` | `pixel_threshold`, `duration_seconds` | Timed black-frame events JSON |
325
+ | `scene_detect` | `threshold` | Scene-change timestamps JSON |
326
+ | `contact_sheet` | `columns`, `rows`, `interval_seconds`, `thumbnail_width` | JPEG/PNG |
327
+ | `hls` | `segment_seconds`, `playlist_type`, `crf` | ZIP with M3U8 and TS segments |
328
+
329
+ All use the normal `POST /toolkit` body with `task`, `media`, optional `params`,
330
+ and optional `callback_url`.
331
+
332
  ## Transcription, Subtitles, Jobs, and Downloads
333
 
334
  | Method | Path | n8n input | Response |
 
337
  | POST | `/subtitles` | JSON `SubtitlePayload` | File response (`.srt` or `.ass`). |
338
  | POST | `/scene-builder` | JSON `SceneBuildPayload` | Generated scene array. |
339
  | POST | `/transcribe/upload` | Multipart `file` plus optional transcription fields | Transcription JSON. |
340
+ | GET | `/status` | Optional query `job_id` | Service status without `job_id`; job record with it. |
341
  | GET | `/status/{job_id}` | Replace `{job_id}` | Job record. |
342
  | POST | `/cancel/{job_id}` | Replace `{job_id}` | Updated job record. |
343
  | POST | `/admin/cleanup` | Optional query `older_than_seconds` | Cleanup counts. |
344
  | GET | `/download` | Query `job_id`; optional `token` | File response. |
345
  | GET | `/download/{job_id}` | Replace `{job_id}`; optional query `token` | File response. |
346
  | POST | `/inspect` | Query `path` | Media metadata JSON. |
347
+ | POST | `/media/inspect` | JSON `{ "media": "...", "callback_url": "..." }` | Async remote-safe inspection. |
348
 
349
  Transcription body:
350
 
 
447
  batch
448
  ```
449
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  ## FFmpeg Editor
451
 
452
  | Method | Path | n8n input | Response |
 
626
  music_fade_in, music_fade_out, music_start: number = 0
627
  music_loop, music_ducking: boolean = true
628
  voice_volume: number = 1
629
+ callback_url, export_target: string | null
630
  ```
631
 
632
  `BatchPayload`
 
854
  creative_style: string | null
855
  ```
856
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
857
  ## Framework and UI Endpoints
858
 
859
  These are not business API operations:
 
866
  | `/whisper/` | Whisper Gradio interface. |
867
  | `/editor/` | FFmpeg editor Gradio interface. |
868
  | `/mcp/` | MCP Streamable HTTP transport; use an MCP client rather than a normal REST request. |
869
+ | `/docs` | Root FastAPI Swagger UI when `ENABLE_API_DOCS=true`. |
870
+ | `/redoc` | Root FastAPI ReDoc UI when `ENABLE_API_DOCS=true`. |
871
+ | `/openapi.json` | Root renderer OpenAPI schema when `ENABLE_API_DOCS=true`. |
872
+ | `/<service>/docs` | Mounted service Swagger UI when `ENABLE_API_DOCS=true`. |
873
+ | `/<service>/openapi.json` | Mounted service OpenAPI schema when `ENABLE_API_DOCS=true`. |
 
README.md CHANGED
@@ -31,6 +31,10 @@ See [N8N_ENDPOINTS.md](N8N_ENDPOINTS.md) for all API endpoints, payload
31
  schemas, editor task IDs, file upload settings, asynchronous job handling, and
32
  HTTP Request node examples.
33
 
 
 
 
 
34
  ## MCP
35
 
36
  Connect an MCP client to:
 
31
  schemas, editor task IDs, file upload settings, asynchronous job handling, and
32
  HTTP Request node examples.
33
 
34
+ Public Swagger, ReDoc, and OpenAPI routes are disabled by default. Set
35
+ `ENABLE_API_DOCS=true` in the Space environment when interactive API
36
+ documentation is required.
37
+
38
  ## MCP
39
 
40
  Connect an MCP client to:
README.renderer.md CHANGED
@@ -261,6 +261,14 @@ Deletes expired job records, old exports, and staged upload folders. Default ret
261
 
262
  Probe one local asset path and return MIME type, duration, codecs, bitrate, resolution, FPS, stream list, and cache metadata.
263
 
 
 
 
 
 
 
 
 
264
  ## n8n Workflow Integration
265
 
266
  Recommended URL-based flow:
@@ -268,7 +276,7 @@ Recommended URL-based flow:
268
  1. Use an HTTP Request node with `POST https://YOUR-SPACE.hf.space/render`.
269
  2. Pass public file URLs from S3, Supabase Storage, Cloudinary, Google Drive direct-download links, or another CDN in `scenes[].media`, `voiceover`, and `background_music`.
270
  3. Set `auto_subtitles` to `true` when you want faster-whisper captions generated from `voiceover`.
271
- 4. Add `callback_url` to receive a POST when the job completes or fails.
272
  5. Poll `GET https://YOUR-SPACE.hf.space/status/{{$json.job_id}}` only if you do not use callbacks.
273
  6. Download the final MP4 from the signed `download_url`.
274
 
@@ -323,12 +331,14 @@ Templates are selected per request and do not require code changes.
323
  - Remote asset downloads are killed after `DOWNLOAD_TIMEOUT_SECONDS`.
324
  - Remote and multipart input size is capped by `MAX_DOWNLOAD_BYTES`.
325
  - faster-whisper models are loaded lazily and cached under `WHISPER_MODEL_DIR`.
326
- - Webhook callbacks are best-effort and callback failures are written to job logs.
 
 
327
  - `export_target=local` copies completed renders to `STORAGE_DIR`.
328
  - `export_target=https://...` uploads the completed MP4 with HTTP `PUT`, which works with presigned URLs from S3, Supabase Storage, and similar providers.
329
  - Jobs retry up to `MAX_RETRIES`.
330
  - Intermediate files are created under `TEMP_DIR` and removed after export.
331
- - Job records retain command history, logs, render time, output size, and failure reasons.
332
 
333
  ## Docker Deployment
334
 
 
261
 
262
  Probe one local asset path and return MIME type, duration, codecs, bitrate, resolution, FPS, stream list, and cache metadata.
263
 
264
+ ### n8n automation endpoints
265
+
266
+ - `POST /media/inspect` safely downloads a local or remote asset and returns FFprobe metadata as an asynchronous JSON artifact.
267
+ - `POST /render/template` substitutes validated JSON variables into a named or inline render template.
268
+ - `POST /render/variants` creates up to 12 platform outputs and returns a render group ID.
269
+ - `GET /render/groups/{group_id}` returns aggregate and per-variant status.
270
+ - `POST /toolkit` supports `loudness_analyze`, `silence_detect`, `black_detect`, `scene_detect`, `contact_sheet`, and `hls` tasks.
271
+
272
  ## n8n Workflow Integration
273
 
274
  Recommended URL-based flow:
 
276
  1. Use an HTTP Request node with `POST https://YOUR-SPACE.hf.space/render`.
277
  2. Pass public file URLs from S3, Supabase Storage, Cloudinary, Google Drive direct-download links, or another CDN in `scenes[].media`, `voiceover`, and `background_music`.
278
  3. Set `auto_subtitles` to `true` when you want faster-whisper captions generated from `voiceover`.
279
+ 4. Add `callback_url` to receive `render.completed`, `render.failed`, or `render.cancelled` events. Toolkit and analysis jobs use the corresponding `task.*` event names.
280
  5. Poll `GET https://YOUR-SPACE.hf.space/status/{{$json.job_id}}` only if you do not use callbacks.
281
  6. Download the final MP4 from the signed `download_url`.
282
 
 
331
  - Remote asset downloads are killed after `DOWNLOAD_TIMEOUT_SECONDS`.
332
  - Remote and multipart input size is capped by `MAX_DOWNLOAD_BYTES`.
333
  - faster-whisper models are loaded lazily and cached under `WHISPER_MODEL_DIR`.
334
+ - Webhook callbacks retry with exponential backoff. `CALLBACK_MAX_RETRIES` and `CALLBACK_RETRY_SECONDS` control delivery.
335
+ - Webhooks include `X-Ava2lon-Event` and an HMAC `X-Ava2lon-Signature` computed with the signing secret.
336
+ - Set `PUBLIC_BASE_URL` so submissions, status payloads, and callbacks contain absolute URLs.
337
  - `export_target=local` copies completed renders to `STORAGE_DIR`.
338
  - `export_target=https://...` uploads the completed MP4 with HTTP `PUT`, which works with presigned URLs from S3, Supabase Storage, and similar providers.
339
  - Jobs retry up to `MAX_RETRIES`.
340
  - Intermediate files are created under `TEMP_DIR` and removed after export.
341
+ - Job records retain command history, logs, render time, output size, SHA-256 artifact checksums, callback delivery state, and failure reasons.
342
 
343
  ## Docker Deployment
344
 
access_log_filter.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+
6
+
7
+ SCANNER_PATH_PREFIXES = (
8
+ "/.env",
9
+ "/.git",
10
+ "/.streamlit",
11
+ "/_debug",
12
+ "/_profiler",
13
+ "/_stcore",
14
+ "/actuator",
15
+ "/admin",
16
+ "/api/config",
17
+ "/api/credentials",
18
+ "/api/env",
19
+ "/api/keys",
20
+ "/api/predict",
21
+ "/api/queue/status",
22
+ "/api/secrets",
23
+ "/api/settings",
24
+ "/api/v1/",
25
+ "/api-docs",
26
+ "/app.py",
27
+ "/backup/",
28
+ "/config",
29
+ "/debug",
30
+ "/docs",
31
+ "/elmah",
32
+ "/file",
33
+ "/graphql",
34
+ "/horizon",
35
+ "/info",
36
+ "/internal",
37
+ "/main.py",
38
+ "/manifest.json",
39
+ "/metrics",
40
+ "/openapi.json",
41
+ "/phpinfo",
42
+ "/proc/",
43
+ "/prometheus",
44
+ "/redoc",
45
+ "/run/predict",
46
+ "/server-",
47
+ "/settings",
48
+ "/swagger",
49
+ "/telescope",
50
+ "/trace",
51
+ "/upload?upload_id=",
52
+ "/wp-config",
53
+ "/__phpinfo",
54
+ )
55
+
56
+
57
+ class ScannerNoiseFilter(logging.Filter):
58
+ def filter(self, record: logging.LogRecord) -> bool:
59
+ try:
60
+ path = str(record.args[2]).lower()
61
+ status_code = int(record.args[4])
62
+ except (IndexError, TypeError, ValueError):
63
+ return True
64
+
65
+ if status_code not in {404, 405}:
66
+ return True
67
+ return not path.startswith(SCANNER_PATH_PREFIXES)
68
+
69
+
70
+ def install_access_log_filter() -> None:
71
+ enabled = os.getenv("SUPPRESS_SCANNER_ACCESS_LOGS", "true").lower() in {
72
+ "1",
73
+ "true",
74
+ "yes",
75
+ }
76
+ if not enabled:
77
+ return
78
+
79
+ logger = logging.getLogger("uvicorn.access")
80
+ if not any(isinstance(item, ScannerNoiseFilter) for item in logger.filters):
81
+ logger.addFilter(ScannerNoiseFilter())
api.py CHANGED
@@ -13,9 +13,9 @@ from pydantic import BaseModel, Field
13
 
14
  from renderer.core.config import Settings
15
  from renderer.core.ingest import stage_upload
16
- from renderer.core.models import AIReelsRequest, RenderRequest, Scene
17
  from renderer.core.security import verify_download_token
18
- from renderer.core.utils import safe_filename
19
  from renderer.jobs import JobManager
20
  from renderer.platform import PlatformProcessor, supported_toolkit_tasks
21
  from renderer.scenes import Timeline
@@ -44,6 +44,7 @@ from renderer.templates import (
44
  scene_effect_metadata,
45
  )
46
  from renderer.transitions import TransitionBuilder
 
47
 
48
  settings = Settings()
49
  settings.ensure_dirs()
@@ -60,7 +61,7 @@ def _verify_api_key(api_key: str | None = Header(default=None, alias=API_KEY_HEA
60
  raise HTTPException(status_code=401, detail="Invalid or missing API key")
61
 
62
  def _submission_response(job_id: str) -> dict[str, str]:
63
- download_url = f"/download/{job_id}"
64
  try:
65
  record = job_manager.get(job_id)
66
  except KeyError:
@@ -69,7 +70,21 @@ def _submission_response(job_id: str) -> dict[str, str]:
69
  token = record.download_token
70
  if token:
71
  download_url = f"{download_url}?token={token}"
72
- return {"job_id": job_id, "status_url": f"/status/{job_id}", "download_url": download_url}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
 
75
  class ScenePayload(BaseModel):
@@ -117,6 +132,35 @@ class RenderPayload(BaseModel):
117
  metadata: dict[str, Any] = Field(default_factory=dict)
118
 
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  class AIReelsPayload(BaseModel):
121
  script: str
122
  voiceover: str
@@ -133,6 +177,8 @@ class AIReelsPayload(BaseModel):
133
  music_start: float = Field(default=0.0, ge=0)
134
  music_ducking: bool = True
135
  voice_volume: float = Field(default=1.0, ge=0, le=2)
 
 
136
 
137
 
138
  class BatchPayload(BaseModel):
@@ -361,6 +407,11 @@ def health() -> dict[str, str]:
361
  return {"status": "ok"}
362
 
363
 
 
 
 
 
 
364
  @api.get("/monitor")
365
  def monitor() -> dict[str, Any]:
366
  return _monitor_payload()
@@ -444,9 +495,15 @@ def templates_catalog() -> dict[str, Any]:
444
  "caption_templates": list_templates(),
445
  "render_presets": list_presets(),
446
  "creative_styles": creative_style_metadata(),
 
447
  }
448
 
449
 
 
 
 
 
 
450
  @api.get("/projects")
451
  def list_projects() -> dict[str, list[dict[str, Any]]]:
452
  return {"projects": project_manager.list()}
@@ -719,6 +776,38 @@ def render(payload: RenderPayload | AIReelsPayload) -> dict[str, str]:
719
  return _submission_response(job_id)
720
 
721
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
722
 
723
  @api.post("/render/ai-reels")
724
  def render_ai_reels(payload: AIReelsPayload) -> dict[str, str]:
@@ -881,6 +970,16 @@ def toolkit(payload: ToolkitPayload) -> dict[str, str]:
881
  return _submission_response(job_id)
882
 
883
 
 
 
 
 
 
 
 
 
 
 
884
  @api.post("/edit", dependencies=[Depends(_verify_api_key)])
885
  def edit(payload: ToolkitPayload) -> dict[str, str]:
886
  return toolkit(payload)
@@ -976,14 +1075,20 @@ async def transcribe_upload(
976
 
977
 
978
  @api.get("/status")
979
- def status_query(job_id: str = Query(...)) -> dict:
 
 
 
 
 
980
  return status(job_id)
981
 
982
 
983
  @api.get("/status/{job_id}")
984
  def status(job_id: str) -> dict:
985
  try:
986
- return job_manager.get(job_id).__dict__
 
987
  except KeyError as exc:
988
  raise HTTPException(status_code=404, detail="Job not found") from exc
989
 
@@ -991,7 +1096,7 @@ def status(job_id: str) -> dict:
991
  @api.post("/cancel/{job_id}")
992
  def cancel(job_id: str) -> dict:
993
  try:
994
- return job_manager.cancel(job_id).__dict__
995
  except KeyError as exc:
996
  raise HTTPException(status_code=404, detail="Job not found") from exc
997
 
@@ -1030,6 +1135,26 @@ def inspect_asset(path: str) -> dict:
1030
  raise HTTPException(status_code=400, detail=str(exc)) from exc
1031
 
1032
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1033
  def _render_request(payload: RenderPayload) -> RenderRequest:
1034
  payload = RenderPayload.model_validate(apply_creative_style(apply_preset(payload.model_dump(exclude_unset=True))))
1035
  request = RenderRequest(
 
13
 
14
  from renderer.core.config import Settings
15
  from renderer.core.ingest import stage_upload
16
+ from renderer.core.models import AIReelsRequest, JobRecord, RenderRequest, Scene
17
  from renderer.core.security import verify_download_token
18
+ from renderer.core.utils import new_id, safe_filename
19
  from renderer.jobs import JobManager
20
  from renderer.platform import PlatformProcessor, supported_toolkit_tasks
21
  from renderer.scenes import Timeline
 
44
  scene_effect_metadata,
45
  )
46
  from renderer.transitions import TransitionBuilder
47
+ from renderer.automation import automation_template_catalog, list_automation_templates, resolve_render_template
48
 
49
  settings = Settings()
50
  settings.ensure_dirs()
 
61
  raise HTTPException(status_code=401, detail="Invalid or missing API key")
62
 
63
  def _submission_response(job_id: str) -> dict[str, str]:
64
+ download_url = _public_endpoint(f"/download/{job_id}")
65
  try:
66
  record = job_manager.get(job_id)
67
  except KeyError:
 
70
  token = record.download_token
71
  if token:
72
  download_url = f"{download_url}?token={token}"
73
+ return {"job_id": job_id, "status_url": _public_endpoint(f"/status/{job_id}"), "download_url": download_url}
74
+
75
+
76
+ def _public_endpoint(path: str) -> str:
77
+ return f"{settings.public_base_url}{path}" if settings.public_base_url else path
78
+
79
+
80
+ def _status_payload(record: JobRecord) -> dict[str, Any]:
81
+ data = record.__dict__.copy()
82
+ data["status_url"] = _public_endpoint(f"/status/{record.job_id}")
83
+ data["download_url"] = _public_endpoint(f"/download/{record.job_id}?token={record.download_token}")
84
+ artifact = record.metrics.get("artifact") if isinstance(record.metrics, dict) else None
85
+ if artifact:
86
+ data["artifact"] = artifact
87
+ return data
88
 
89
 
90
  class ScenePayload(BaseModel):
 
132
  metadata: dict[str, Any] = Field(default_factory=dict)
133
 
134
 
135
+ class RenderVariantPayload(BaseModel):
136
+ name: str = "variant"
137
+ preset: str | None = None
138
+ template: str | None = None
139
+ creative_style: str | None = None
140
+ platform: str | None = None
141
+ output_name: str | None = None
142
+ preview: bool | None = None
143
+ metadata: dict[str, Any] = Field(default_factory=dict)
144
+
145
+
146
+ class RenderVariantsPayload(RenderPayload):
147
+ variants: list[RenderVariantPayload] = Field(min_length=1, max_length=12)
148
+
149
+
150
+ class TemplateRenderPayload(BaseModel):
151
+ template: str | dict[str, Any]
152
+ variables: dict[str, Any] = Field(default_factory=dict)
153
+ overrides: dict[str, Any] = Field(default_factory=dict)
154
+ callback_url: str | None = None
155
+ export_target: str | None = None
156
+
157
+
158
+ class MediaInspectPayload(BaseModel):
159
+ media: str
160
+ callback_url: str | None = None
161
+ export_target: str | None = None
162
+
163
+
164
  class AIReelsPayload(BaseModel):
165
  script: str
166
  voiceover: str
 
177
  music_start: float = Field(default=0.0, ge=0)
178
  music_ducking: bool = True
179
  voice_volume: float = Field(default=1.0, ge=0, le=2)
180
+ callback_url: str | None = None
181
+ export_target: str | None = None
182
 
183
 
184
  class BatchPayload(BaseModel):
 
407
  return {"status": "ok"}
408
 
409
 
410
+ @api.get("/healthz", include_in_schema=False)
411
+ def healthz() -> dict[str, str]:
412
+ return health()
413
+
414
+
415
  @api.get("/monitor")
416
  def monitor() -> dict[str, Any]:
417
  return _monitor_payload()
 
495
  "caption_templates": list_templates(),
496
  "render_presets": list_presets(),
497
  "creative_styles": creative_style_metadata(),
498
+ "automation_templates": automation_template_catalog(),
499
  }
500
 
501
 
502
+ @api.get("/templates/automation")
503
+ def automation_templates() -> dict[str, Any]:
504
+ return {"templates": list_automation_templates(), "catalog": automation_template_catalog()}
505
+
506
+
507
  @api.get("/projects")
508
  def list_projects() -> dict[str, list[dict[str, Any]]]:
509
  return {"projects": project_manager.list()}
 
776
  return _submission_response(job_id)
777
 
778
 
779
+ @api.post("/render/variants")
780
+ def render_variants(payload: RenderVariantsPayload) -> dict[str, Any]:
781
+ group_id = new_id("render_group")
782
+ jobs: list[dict[str, str]] = []
783
+ common = payload.model_dump(exclude={"variants"})
784
+ for variant in payload.variants:
785
+ data = dict(common)
786
+ selected = variant.model_dump(exclude_none=True)
787
+ variant_name = selected.pop("name", "variant")
788
+ variant_metadata = dict(data.get("metadata") or {})
789
+ variant_metadata.update(selected.pop("metadata", {}) or {})
790
+ variant_metadata.update({"group_id": group_id, "variant_name": variant_name})
791
+ data.update(selected)
792
+ data["metadata"] = variant_metadata
793
+ request = _render_request(RenderPayload.model_validate(data))
794
+ job_id = job_manager.submit_render(request)
795
+ jobs.append({"variant": variant_name, **_submission_response(job_id)})
796
+ return {"group_id": group_id, "group_status_url": _public_endpoint(f"/render/groups/{group_id}"), "jobs": jobs}
797
+
798
+
799
+ @api.post("/render/template")
800
+ def render_template(payload: TemplateRenderPayload) -> dict[str, str]:
801
+ try:
802
+ data = resolve_render_template(payload.template, payload.variables, payload.overrides)
803
+ data["callback_url"] = payload.callback_url or data.get("callback_url")
804
+ data["export_target"] = payload.export_target or data.get("export_target")
805
+ request = _render_request(RenderPayload.model_validate(data))
806
+ return _submission_response(job_manager.submit_render(request))
807
+ except (KeyError, TypeError, ValueError) as exc:
808
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
809
+
810
+
811
 
812
  @api.post("/render/ai-reels")
813
  def render_ai_reels(payload: AIReelsPayload) -> dict[str, str]:
 
970
  return _submission_response(job_id)
971
 
972
 
973
+ @api.post("/media/inspect")
974
+ def media_inspect(payload: MediaInspectPayload) -> dict[str, str]:
975
+ job_id = job_manager.submit_task(
976
+ lambda task_id, log: PlatformProcessor(settings, log=log).inspect_media(payload.media, task_id),
977
+ callback_url=payload.callback_url,
978
+ export_target=payload.export_target,
979
+ )
980
+ return _submission_response(job_id)
981
+
982
+
983
  @api.post("/edit", dependencies=[Depends(_verify_api_key)])
984
  def edit(payload: ToolkitPayload) -> dict[str, str]:
985
  return toolkit(payload)
 
1075
 
1076
 
1077
  @api.get("/status")
1078
+ def status_query(job_id: str | None = Query(default=None)) -> dict:
1079
+ if job_id is None:
1080
+ return {
1081
+ "status": "ok",
1082
+ "queue": job_manager.summary(),
1083
+ }
1084
  return status(job_id)
1085
 
1086
 
1087
  @api.get("/status/{job_id}")
1088
  def status(job_id: str) -> dict:
1089
  try:
1090
+ record = job_manager.get(job_id)
1091
+ return _status_payload(record)
1092
  except KeyError as exc:
1093
  raise HTTPException(status_code=404, detail="Job not found") from exc
1094
 
 
1096
  @api.post("/cancel/{job_id}")
1097
  def cancel(job_id: str) -> dict:
1098
  try:
1099
+ return _status_payload(job_manager.cancel(job_id))
1100
  except KeyError as exc:
1101
  raise HTTPException(status_code=404, detail="Job not found") from exc
1102
 
 
1135
  raise HTTPException(status_code=400, detail=str(exc)) from exc
1136
 
1137
 
1138
+ @api.get("/render/groups/{group_id}")
1139
+ def render_group_status(group_id: str) -> dict[str, Any]:
1140
+ records = job_manager.group(group_id)
1141
+ if not records:
1142
+ raise HTTPException(status_code=404, detail="Render group not found")
1143
+ jobs = [_status_payload(record) for record in records]
1144
+ states = [record.state for record in records]
1145
+ if any(state in {"PENDING", "RUNNING", "CANCEL_REQUESTED"} for state in states):
1146
+ group_state = "RUNNING"
1147
+ elif all(state == "COMPLETED" for state in states):
1148
+ group_state = "COMPLETED"
1149
+ elif any(state == "FAILED" for state in states):
1150
+ group_state = "FAILED"
1151
+ elif any(state == "CANCELLED" for state in states):
1152
+ group_state = "CANCELLED"
1153
+ else:
1154
+ group_state = "RUNNING"
1155
+ return {"group_id": group_id, "state": group_state, "total": len(jobs), "completed": states.count("COMPLETED"), "jobs": jobs}
1156
+
1157
+
1158
  def _render_request(payload: RenderPayload) -> RenderRequest:
1159
  payload = RenderPayload.model_validate(apply_creative_style(apply_preset(payload.model_dump(exclude_unset=True))))
1160
  request = RenderRequest(
app.py CHANGED
@@ -1,7 +1,10 @@
 
1
  from contextlib import AsyncExitStack, asynccontextmanager
2
 
3
- from fastapi.responses import RedirectResponse
 
4
 
 
5
  from editor_app import app as editor_app
6
  from ktts_app import app as tts_app
7
  from musicgen_app import app as music_app
@@ -10,6 +13,30 @@ from renderer_app import app
10
  from whisper_app import app as whisper_app
11
 
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  CHILD_APPS = (
14
  ("/tts", tts_app, "tts"),
15
  ("/music", music_app, "music"),
 
1
+ import os
2
  from contextlib import AsyncExitStack, asynccontextmanager
3
 
4
+ from fastapi import Request
5
+ from fastapi.responses import RedirectResponse, Response
6
 
7
+ from access_log_filter import install_access_log_filter
8
  from editor_app import app as editor_app
9
  from ktts_app import app as tts_app
10
  from musicgen_app import app as music_app
 
13
  from whisper_app import app as whisper_app
14
 
15
 
16
+ install_access_log_filter()
17
+
18
+
19
+ def _is_api_docs_path(path: str) -> bool:
20
+ return (
21
+ path.endswith("/openapi.json")
22
+ or path.endswith("/docs")
23
+ or "/docs/" in path
24
+ or path.endswith("/redoc")
25
+ )
26
+
27
+
28
+ @app.middleware("http")
29
+ async def protect_api_docs(request: Request, call_next):
30
+ docs_enabled = os.getenv("ENABLE_API_DOCS", "false").lower() in {
31
+ "1",
32
+ "true",
33
+ "yes",
34
+ }
35
+ if not docs_enabled and _is_api_docs_path(request.url.path):
36
+ return Response(status_code=404)
37
+ return await call_next(request)
38
+
39
+
40
  CHILD_APPS = (
41
  ("/tts", tts_app, "tts"),
42
  ("/music", music_app, "music"),
musicgen_app.py CHANGED
@@ -33,8 +33,7 @@ def ui_generate(prompt, duration):
33
 
34
 
35
  with gr.Blocks(
36
- title="AI Background Music Generator",
37
- theme=gr.themes.Soft()
38
  ) as demo:
39
 
40
  gr.Markdown("""
@@ -71,4 +70,9 @@ Generate royalty-free background music instantly.
71
  # ======================
72
  # Mount GRADIO as ROOT APP
73
 
74
- app = gr.mount_gradio_app(api, demo, path="/")
 
 
 
 
 
 
33
 
34
 
35
  with gr.Blocks(
36
+ title="AI Background Music Generator"
 
37
  ) as demo:
38
 
39
  gr.Markdown("""
 
70
  # ======================
71
  # Mount GRADIO as ROOT APP
72
 
73
+ app = gr.mount_gradio_app(
74
+ api,
75
+ demo,
76
+ path="/",
77
+ theme=gr.themes.Soft(),
78
+ )
requirements.txt CHANGED
@@ -2,7 +2,6 @@
2
 
3
  accelerate
4
  aiofiles>=23.2.1
5
- email-validator
6
  fastapi>=0.115.0
7
  faster-whisper==1.0.3
8
  ffmpeg-python>=0.2.0
@@ -11,7 +10,7 @@ google-auth>=2.29.0
11
  google-auth-httplib2>=0.2.0
12
  google-auth-oauthlib>=1.2.0
13
  google-generativeai>=0.7.2
14
- gradio>=5.0.0
15
  httplib2>=0.22.0
16
  huggingface_hub<1.0
17
  httpx>=0.27.0
@@ -21,21 +20,17 @@ moviepy==1.0.3
21
  mcp>=1.28.1,<2
22
  numpy>=1.26.0
23
  onnxruntime
24
- passlib[bcrypt]>=1.7.4
25
  phonemizer
26
  psutil>=5.9.8
27
- psycopg2-binary>=2.9.9
28
  pydantic>=2.7.0
29
  pytest>=8.2.0
30
  pytest-cov>=5.0.0
31
  pysrt>=1.1.2
32
- python-jose[cryptography]>=3.3.0
33
  python-magic
34
  python-multipart>=0.0.9
35
  requests>=2.31.0
36
  scipy
37
  soundfile
38
- sqlalchemy>=2.0.25
39
  torch
40
  torchaudio
41
  torchvision
 
2
 
3
  accelerate
4
  aiofiles>=23.2.1
 
5
  fastapi>=0.115.0
6
  faster-whisper==1.0.3
7
  ffmpeg-python>=0.2.0
 
10
  google-auth-httplib2>=0.2.0
11
  google-auth-oauthlib>=1.2.0
12
  google-generativeai>=0.7.2
13
+ gradio>=6.0,<7
14
  httplib2>=0.22.0
15
  huggingface_hub<1.0
16
  httpx>=0.27.0
 
20
  mcp>=1.28.1,<2
21
  numpy>=1.26.0
22
  onnxruntime
 
23
  phonemizer
24
  psutil>=5.9.8
 
25
  pydantic>=2.7.0
26
  pytest>=8.2.0
27
  pytest-cov>=5.0.0
28
  pysrt>=1.1.2
 
29
  python-magic
30
  python-multipart>=0.0.9
31
  requests>=2.31.0
32
  scipy
33
  soundfile
 
34
  torch
35
  torchaudio
36
  torchvision
requirements.whisper.txt CHANGED
@@ -50,22 +50,7 @@ google-auth-httplib2>=0.2.0
50
  # =========================
51
  google-generativeai>=0.7.2
52
 
53
- # =========================
54
- # AUTH SYSTEM (JWT + SECURITY)
55
- # =========================
56
- python-jose[cryptography]>=3.3.0
57
-
58
- # FIXED BCRYPT STACK (CRITICAL STABILITY FIX)
59
- passlib[bcrypt]>=1.7.4
60
- email-validator
61
-
62
- # =========================
63
- # DATABASE (SQLALCHEMY + POSTGRES)
64
- # =========================
65
- sqlalchemy>=2.0.25
66
- psycopg2-binary>=2.9.9
67
-
68
  # =========================
69
  # OPTIONAL STABILITY LAYER
70
  # =========================
71
- aiofiles>=23.2.1
 
50
  # =========================
51
  google-generativeai>=0.7.2
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  # =========================
54
  # OPTIONAL STABILITY LAYER
55
  # =========================
56
+ aiofiles>=23.2.1
whisper_app.py CHANGED
@@ -14,17 +14,6 @@ from utils.logger import logger
14
  from utils.job_queue import start_worker, create_job, get_job
15
  from ingestion.resolver import resolve_input
16
 
17
- # ==============================
18
- # AUTH SYSTEM (HARDENED IMPORT)
19
- # ==============================
20
- try:
21
- from auth.routes import router as auth_router
22
- from auth.database import Base, engine
23
- AUTH_ENABLED = True
24
- except Exception as e:
25
- logger.error(f"[AUTH BOOT FAILED] {e}")
26
- AUTH_ENABLED = False
27
-
28
  # ==============================
29
  # CORE PIPELINE
30
  # ==============================
@@ -50,14 +39,6 @@ from publisher.bulk import execute as bulk_execute
50
  from publisher.metadata_engine import generate_metadata
51
  from publisher.thumbnail_engine import generate_thumbnail
52
 
53
- # ==============================
54
- # SAFE bcrypt SHIELD (DO NOT FAIL BOOT)
55
- # ==============================
56
- try:
57
- import bcrypt
58
- except Exception as e:
59
- logger.warning(f"[bcrypt warning ignored] {e}")
60
-
61
  # ==============================
62
  # INIT
63
  # ==============================
@@ -66,21 +47,6 @@ UPLOAD_DIR = str(BASE_DIR / "jobs")
66
  os.makedirs(UPLOAD_DIR, exist_ok=True)
67
 
68
 
69
- # =========================================================
70
- # SAFE DB INITIALIZATION
71
- # =========================================================
72
- def init_database_safe():
73
- if not AUTH_ENABLED:
74
- logger.warning("Auth disabled - skipping DB init")
75
- return
76
-
77
- try:
78
- Base.metadata.create_all(bind=engine)
79
- logger.info("Database initialized successfully")
80
- except Exception as e:
81
- logger.error(f"Database init failed (non-fatal): {e}")
82
-
83
-
84
  # =========================================================
85
  # LIFECYCLE
86
  # =========================================================
@@ -89,9 +55,6 @@ async def lifespan(app: FastAPI):
89
 
90
  logger.info("Starting Basyx Whisper V10.1")
91
 
92
- # DB init (NON-FATAL)
93
- init_database_safe()
94
-
95
  # workers (must not block boot)
96
  try:
97
  start_worker()
@@ -123,11 +86,6 @@ app = FastAPI(
123
  lifespan=lifespan,
124
  )
125
 
126
- # AUTH ROUTER (only if available)
127
- if AUTH_ENABLED:
128
- app.include_router(auth_router)
129
-
130
-
131
  # =========================================================
132
  # TASKS
133
  # =========================================================
@@ -305,8 +263,7 @@ async def execute_router(
305
  def health():
306
  return {
307
  "status": "online",
308
- "version": "V10.1",
309
- "auth_enabled": AUTH_ENABLED
310
  }
311
 
312
 
 
14
  from utils.job_queue import start_worker, create_job, get_job
15
  from ingestion.resolver import resolve_input
16
 
 
 
 
 
 
 
 
 
 
 
 
17
  # ==============================
18
  # CORE PIPELINE
19
  # ==============================
 
39
  from publisher.metadata_engine import generate_metadata
40
  from publisher.thumbnail_engine import generate_thumbnail
41
 
 
 
 
 
 
 
 
 
42
  # ==============================
43
  # INIT
44
  # ==============================
 
47
  os.makedirs(UPLOAD_DIR, exist_ok=True)
48
 
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  # =========================================================
51
  # LIFECYCLE
52
  # =========================================================
 
55
 
56
  logger.info("Starting Basyx Whisper V10.1")
57
 
 
 
 
58
  # workers (must not block boot)
59
  try:
60
  start_worker()
 
86
  lifespan=lifespan,
87
  )
88
 
 
 
 
 
 
89
  # =========================================================
90
  # TASKS
91
  # =========================================================
 
263
  def health():
264
  return {
265
  "status": "online",
266
+ "version": "V10.1"
 
267
  }
268
 
269