File size: 12,082 Bytes
e990dfa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
af78cff
 
 
 
 
 
 
 
e990dfa
 
 
 
 
 
 
af78cff
e990dfa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
af78cff
 
 
e990dfa
 
 
 
af78cff
e990dfa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
---
title: Basyx FFmpeg Rendering Engine
emoji: 🎬
colorFrom: green
colorTo: yellow
sdk: docker
app_port: 7860
pinned: false
license: mit
---

# Basyx FFmpeg Rendering Engine

Basyx FFmpeg is a CPU-first rendering backend for short-form video automation. It is designed for Hugging Face Docker Spaces running on 2-8 CPU cores with 8-16 GB RAM and no GPU. The engine uses FFmpeg and FFprobe subprocess pipelines and never loads complete videos into Python memory.

## Architecture

```mermaid
flowchart TD
    API["FastAPI REST API"] --> Jobs["Job Manager"]
    Dashboard["Gradio Operator Dashboard"] --> Jobs
    Jobs --> Engine["Render Engine"]
    Engine --> Assets["Asset Probe + Metadata Cache"]
    Engine --> Ingest["Remote URL + Upload Ingestion"]
    Engine --> Normalize["Normalization"]
    Engine --> Scenes["Scene Timeline"]
    Engine --> Subtitles["SRT / ASS Subtitle Engine"]
    Engine --> Transitions["Transition Filter Builder"]
    Engine --> Audio["Voiceover + Ducking Mixer"]
    Engine --> Exports["Export Manager"]
    Ingest --> FFmpeg["FFmpeg / FFprobe"]
    Normalize --> FFmpeg
    Scenes --> FFmpeg
    Subtitles --> FFmpeg
    Transitions --> FFmpeg
    Audio --> FFmpeg
```

Package layout:

```text
renderer/
  core/          settings, models, ingestion, orchestration
  ffmpeg/        command builder, runner, probing, normalization
  subtitles/     SRT and ASS generation
  transitions/   xfade graph generation
  audio/         voiceover and music ducking
  scenes/        timeline validation
  templates/     caption templates
  exports/       final deliverables
  jobs/          durable job records and retries
```

## REST API

All API endpoints are served at the Space root. Asset fields may be absolute container paths or public `http`/`https` URLs. Remote assets are downloaded into a job-local temp directory with timeout and size limits before rendering.

### `POST /render`

Submit one render job.

```json
{
  "template": "tiktok_classic",
  "output_name": "campaign_clip.mp4",
  "voiceover": "https://cdn.example.com/voiceover.wav",
  "background_music": "https://cdn.example.com/music.mp3",
  "subtitle_format": "ass",
  "auto_subtitles": true,
  "subtitle_language": "en",
  "whisper_model_size": "tiny",
  "preset": "tiktok_9_16_fast",
  "callback_url": "https://n8n.example.com/webhook/render-complete",
  "export_target": "local",
  "audio_normalize": true,
  "preview": false,
  "normalize": true,
  "scenes": [
    {
      "start": 0,
      "duration": 5,
      "media": "https://cdn.example.com/scene1.mp4",
      "caption": "Launch faster with automated rendering",
      "transition": "fade"
    }
  ]
}
```

Response:

```json
{
  "job_id": "job_abc123",
  "status_url": "/status/job_abc123",
  "download_url": "/download/job_abc123?token=..."
}
```

### `GET /presets`

Returns available render presets and caption templates. Presets let n8n submit compact requests such as `tiktok_9_16_fast`, `youtube_shorts_hd`, `podcast_square`, `reels_with_subtitles`, and `draft_preview`.

### `POST /render/ai-reels`

Submit a single-call AI Reels job using an existing voiceover and asset list. TTS is intentionally provider-pluggable in v1; the production path requires a supplied voiceover.

```json
{
  "script": "Launch faster with automated rendering.",
  "voiceover": "https://cdn.example.com/voiceover.wav",
  "assets": ["https://cdn.example.com/scene1.jpg", "https://cdn.example.com/scene2.mp4"],
  "template": "youtube_shorts",
  "output_name": "ai_reel.mp4"
}
```

### `POST /render/batch`

Submit multiple render jobs. The worker pool defaults to one active render to avoid RAM exhaustion.

```json
{
  "jobs": [
    {
      "template": "modern_minimal",
      "output_name": "clip_a.mp4",
      "scenes": [{"start": 0, "duration": 3, "media": "https://cdn.example.com/a.mp4"}]
    }
  ]
}
```

### `POST /scene-builder`

Build a timeline from a script and asset list.

```json
{
  "script": "One script can be split across several short scenes.",
  "assets": ["https://cdn.example.com/a.mp4", "https://cdn.example.com/b.jpg"],
  "duration": 8,
  "transition": "fade"
}
```

### `POST /assets/upload`

Upload files from automation tools such as n8n and receive staged paths that can be used in `/render`.

Multipart form field:

- `files`: one or more images, videos, GIFs, or audio files.

Response:

```json
{
  "assets": [
    {
      "filename": "clip.mp4",
      "path": "/app/temp/uploads/abc/clip.mp4",
      "reference": "upload://clip.mp4"
    }
  ]
}
```

### `POST /render/upload`

Submit a render job and files in one multipart request.

Multipart fields:

- `request_json`: render payload JSON.
- `files`: uploaded files referenced by `upload://filename`.

Example `request_json`:

```json
{
  "template": "tiktok_classic",
  "output_name": "n8n_clip.mp4",
  "scenes": [
    {
      "start": 0,
      "duration": 5,
      "media": "upload://clip.mp4",
      "caption": "Rendered from n8n"
    }
  ],
  "voiceover": "upload://voice.wav"
}
```

### `POST /render/ai-reels/upload`

Multipart AI Reels endpoint. Use `upload://filename` inside `voiceover`, `background_music`, and `assets`.

### `POST /transcribe`

Transcribe local or remote audio/video with faster-whisper.

```json
{
  "audio": "https://cdn.example.com/voiceover.wav",
  "model_size": "tiny",
  "language": "en",
  "task": "transcribe",
  "word_timestamps": true,
  "vad_filter": true
}
```

Response includes `text`, detected `language`, segment timings, and optional word timings. Use this endpoint from n8n when you want subtitles before rendering.

### `POST /transcribe/upload`

Multipart faster-whisper endpoint for direct n8n file uploads.

Multipart fields:

- `file`: audio or video file.
- `model_size`: optional Whisper model, default `WHISPER_MODEL_SIZE`.
- `language`: optional ISO language code.
- `word_timestamps`: optional boolean, default `true`.

### `POST /subtitles`

Generate SRT or ASS files from timed events.

```json
{
  "format": "ass",
  "template": "tiktok_zoom",
  "events": [
    {"start": 0, "end": 1.2, "text": "Hello world"}
  ]
}
```

### `GET /status/{job_id}`

Returns job state, logs, FFmpeg commands, metrics, output path, and failure reason.

States: `PENDING`, `RUNNING`, `FAILED`, `COMPLETED`.

### `GET /download/{job_id}`

Returns the completed MP4 deliverable. Downloads require the signed token returned by render submission. If the job is still running, the endpoint returns `409`.

### `POST /cancel/{job_id}`

Requests job cancellation. Pending jobs are marked `CANCELLED`; running jobs are marked `CANCEL_REQUESTED` and stop before the next guarded execution point.

### `POST /admin/cleanup`

Deletes expired job records, old exports, and staged upload folders. Default retention is controlled by `JOB_RETENTION_SECONDS`.

### `POST /inspect`

Probe one local asset path and return MIME type, duration, codecs, bitrate, resolution, FPS, stream list, and cache metadata.

### n8n automation endpoints

- `POST /media/inspect` safely downloads a local or remote asset and returns FFprobe metadata as an asynchronous JSON artifact.
- `POST /render/template` substitutes validated JSON variables into a named or inline render template.
- `POST /render/variants` creates up to 12 platform outputs and returns a render group ID.
- `GET /render/groups/{group_id}` returns aggregate and per-variant status.
- `POST /toolkit` supports `loudness_analyze`, `silence_detect`, `black_detect`, `scene_detect`, `contact_sheet`, and `hls` tasks.

## n8n Workflow Integration

Recommended URL-based flow:

1. Use an HTTP Request node with `POST https://YOUR-SPACE.hf.space/render`.
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`.
3. Set `auto_subtitles` to `true` when you want faster-whisper captions generated from `voiceover`.
4. Add `callback_url` to receive `render.completed`, `render.failed`, or `render.cancelled` events. Toolkit and analysis jobs use the corresponding `task.*` event names.
5. Poll `GET https://YOUR-SPACE.hf.space/status/{{$json.job_id}}` only if you do not use callbacks.
6. Download the final MP4 from the signed `download_url`.

Multipart flow:

1. Use an HTTP Request node set to multipart form-data.
2. Send `request_json` as a text field.
3. Attach files under the `files` field.
4. Reference those files in JSON as `upload://exact-filename.ext`.

Security defaults:

- Only `http` and `https` remote assets are accepted.
- Private, localhost, link-local, and multicast hosts are blocked by default.
- Set `ALLOW_PRIVATE_ASSET_URLS=true` only for trusted self-hosted deployments.
- `MAX_DOWNLOAD_BYTES` limits remote downloads and multipart upload totals.
- `WHISPER_MODEL_SIZE=tiny` and `WHISPER_COMPUTE_TYPE=int8` are the recommended CPU defaults.
- `BASYX_SIGNING_SECRET` should be set as a Space secret before public deployment.

## Operator Dashboard

The dashboard is available at `/dashboard`. It provides production operator tools for render submission, batch submission, AI Reels submission, job status, logs, downloads, and asset inspection.

## Caption Templates

Built-in templates:

- `tiktok_classic`
- `tiktok_zoom`
- `alex_hormozi`
- `modern_minimal`
- `youtube_shorts`
- `podcast_style`
- `news_style`

Templates are selected per request and do not require code changes.

## CPU And RAM Tuning

- Keep `MAX_RENDER_WORKERS=1` for 8 GB RAM deployments.
- Use `OUTPUT_PRESET=veryfast` or `ultrafast` for faster CPU rendering.
- Use `OUTPUT_CRF=23-28` to balance quality and file size.
- Keep source assets near the target duration to reduce normalization work.
- Prefer pre-trimmed voiceovers and assets for batch workloads.
- Start with `WHISPER_MODEL_SIZE=tiny` or `base` on free/CPU Spaces.
- Use `preview=true` for low-resolution draft renders.
- Use `audio_normalize=true` for voiceover or podcast-style content.

## Reliability

- FFmpeg subprocesses are killed after `FFMPEG_TIMEOUT_SECONDS`.
- Remote asset downloads are killed after `DOWNLOAD_TIMEOUT_SECONDS`.
- Remote and multipart input size is capped by `MAX_DOWNLOAD_BYTES`.
- faster-whisper models are loaded lazily and cached under `WHISPER_MODEL_DIR`.
- Webhook callbacks retry with exponential backoff. `CALLBACK_MAX_RETRIES` and `CALLBACK_RETRY_SECONDS` control delivery.
- Webhooks include `X-Ava2lon-Event` and an HMAC `X-Ava2lon-Signature` computed with the signing secret.
- Set `PUBLIC_BASE_URL` so submissions, status payloads, and callbacks contain absolute URLs.
- `export_target=local` copies completed renders to `STORAGE_DIR`.
- `export_target=https://...` uploads the completed MP4 with HTTP `PUT`, which works with presigned URLs from S3, Supabase Storage, and similar providers.
- Jobs retry up to `MAX_RETRIES`.
- Intermediate files are created under `TEMP_DIR` and removed after export.
- Job records retain command history, logs, render time, output size, SHA-256 artifact checksums, callback delivery state, and failure reasons.

## Docker Deployment

Build locally:

```bash
docker build -t basyx-ffmpeg .
docker run --rm -p 7860:7860 basyx-ffmpeg
```

Open:

- API: `http://localhost:7860/health`
- Dashboard: `http://localhost:7860/dashboard`

## Hugging Face Spaces Deployment

Create a Docker Space, push this repository, and keep the README metadata above. The container listens on port `7860`, and the Space exposes the FastAPI service plus the dashboard.

## Testing

Run:

```bash
pytest --cov=renderer --cov-report=term-missing
```

Integration tests use mocked FFmpeg/FFprobe where possible and small generated media where necessary.

## Known v1 Limits

- Bundled TTS is not included. The AI Reels endpoint requires a supplied voiceover and keeps narration generation behind a provider interface for future integration.
- Advanced caption animation is implemented through ASS effects and FFmpeg-compatible filter behavior, not GPU animation layers.
- Batch rendering is intentionally sequential by default for constrained CPU/RAM Spaces.