fc_pipe now muxes video + that frame's embedded audio into ONE streaming AVI
container on stdout, so ffmpeg reads a SINGLE input (-f avi -i pipe:0) instead
of a raw video pipe + a separate live audio FIFO. The two-live-pipe design
deadlocked ffmpeg (it stalled forever probing input 0); a single interleaved
stream removes that failure mode entirely.
fc_pipe.c:
- New AVI mode (argv[3] == "--avi"/"avi"). Writes RIFF('AVI ') + LIST(hdrl)
{ avih + strl(vids: strh+BITMAPINFOHEADER UYVY 16bpp) + strl(auds:
strh+WAVEFORMATEX PCM s16le 48k 2ch) } + LIST(movi) once, then per ring
entry a '00dc' video chunk followed by a '01wb' audio chunk (LE sizes,
even-pad). RIFF/movi sizes use the 0x7FFFFFFF streaming sentinel (pipe is
unseekable); dwFlags has NO index bits. Frame-coupled by construction: both
chunks come from the SAME ring entry in one read-loop iteration.
- dwScale/dwRate = fps_den/fps_num (video) and nBlockAlign/nAvgBytesPerSec
(audio). If a frame has audio_size 0, emits one frame-interval of silence
(round(48000*fps_den/fps_num) samples) so the audio timeline tracks video
and ffmpeg never starves on the audio demuxer.
- Legacy raw video-only mode retained when no avi flag is given. The old
split-stdout/audio-FIFO threaded path is removed (it was the deadlock).
fc_client.{h,c}:
- Add fc_consumer_info() / fc_stream_info_t to expose the slot header's
width/height/fps/audio params to fc_pipe for the AVI header.
capture-manager.js (_buildInputArgs deltacast/sdi framecache branch):
- Spawn fc_pipe with "--avi" (no audio FIFO). Remove the mkfifo + audio-FIFO
creation for this path.
- inputArgs: ONE input -thread_queue_size 512 -f avi [AUDIO_OFFSET_MS] -i pipe:0
(was: -f rawvideo -i pipe:0 AND -f s16le -ar 48000 -ac 2 -i <fifo>).
- audioInputIndex 0, audioFifo null. Growing VC-3/HEVC builders already map
[0:v] and audioMap 0🅰️0?; with one AVI input that resolves to 0:v / 0:a.
Validated on zampp3 against the LIVE deltacast-0-0 slot: fc_pipe --avi | ffmpeg
-f avi -i pipe:0 -> dnxhd/pcm_s24le MXF gives 360 video / 360 audio packets in
6.006s (no stall at 2 frames). A synthetic 1 kHz sine slot through the same
path yields mean_volume -9 dB / max -6 dB, proving the muxer carries real audio
end-to-end (the live SDI input currently carries no embedded audio, so the
bridge's silence fallback reads -91 dB — upstream of the muxer).
|
||
|---|---|---|
| .claude | ||
| deploy | ||
| docs | ||
| sdk | ||
| services | ||
| tools | ||
| .env.example | ||
| .gitignore | ||
| DESIGN.md | ||
| docker-compose.gpu.yml | ||
| docker-compose.worker.yml | ||
| docker-compose.yml | ||
| docker-compose.zampp3.yml | ||
| PRODUCT.md | ||
| README.md | ||
| setup-repo.sh | ||
| WORK_LOG_PLAYOUT.md | ||
Dragonflight
Self-hosted broadcast media-asset management system that replaces legacy tools like Grass Valley AMPP and FramelightX. Handles live ingest, growing-file editing, scheduling, transcoding, and asset management in a single operator-focused interface.
Repo renamed from
wild-dragon→dragonflight(2026-05-23). The old URL still redirects.
Home Dashboard
The home screen provides quick access to all major features and displays system status at a glance:
- Library — Browse projects, bins, and assets with hover-scrub previews
- Recorders — View configured capture devices and their status
- Editor — Timeline editor with cross-clip preview and render queue
- Jobs — Proxy and thumbnail queue with retry controls
- Settings — Configure storage, encoder, growing files, and capture SDK
- Dashboard — Operations view showing recent activity, job queue, and cluster health
Core Features
1. Live Ingest & Capture
Multi-protocol source capture with per-recorder codec settings
Dragonflight ingests from multiple sources simultaneously:
- SRT (Secure Reliable Transport) — caller and listener modes
- RTMP — standard streaming protocol
- SDI — via Blackmagic DeckLink cards with FFmpeg SDK 16.x patches
Each recorder can be configured with independent codec settings:
- ProRes (hi-res masters)
- H.264 / H.265 (proxies)
- DNxHR (Avid compatibility)
Audio routing and per-source configuration ensure flexibility for multi-camera productions.
2. Growing-File Editing
Live editing in Premiere Pro while capture is still writing
Editors mount the SMB landing zone directly in Premiere Pro and edit the live master file as it's being written. The included CEP (Custom Extension Panel) provides:
- Real-time clip detection and frame-accurate trimming
- One-click relink to final S3 master after promotion
- No waiting for capture to finish before editorial begins
3. Recorder Scheduler
Time-windowed recording automation
Schedule recordings with:
- One-shot, daily, or weekly recurrence
- Automatic start/stop via 15-second tick loop
- Conflict detection across recorders
- Project and bin assignment at schedule time
4. Library & Asset Management
Browse, search, and organize captured footage
The Library screen provides:
- Project and bin hierarchy
- Asset detail view with frame-anchored persistent comments
- Right-click context menu (move-to-bin, rename, delete)
- Global cmd/ctrl-K search across assets, projects, recorders, jobs, and users
- Hover-scrub preview with HLS playback
5. Jobs Queue
BullMQ-backed proxy and thumbnail generation
Automated background processing:
- Per-job retry logic with exponential backoff
- Bulk "retry all failed" for batch recovery
- Inline error messages with actionable diagnostics
- Status tracking: ingesting → processing → ready
Proxy encoder options:
- CPU-based: libx264 (H.264)
- GPU-accelerated: NVENC (NVIDIA) or VAAPI (AMD/Intel)
6. Timeline Conform & Export
FCP XML export with server-side FFmpeg rendering
The Premiere Pro panel exports FCP XML with:
- Server-side conform via FFmpeg
- Multiple output formats: H.264, H.265, ProRes
- Resolution presets: Broadcast, Web, Archive
- Batch processing with job queue integration
7. Hi-Res Auto-Relink
One-click batch relink of proxy clips to frame-accurate server-trimmed masters
After editing on proxies:
- Select clips in Premiere
- Trigger relink from the CEP panel
- Server trims hi-res segments to exact in/out points
- Concurrent trim worker pool for speed
- 24-hour TTL with automatic cleanup
8. Settings & Configuration
Centralized control for storage, encoding, and capture
Configure:
- S3 Storage — endpoint, bucket, credentials (with env-var fallback)
- Proxy Encoder — CPU vs GPU, bitrate, resolution
- Growing Files — SMB path, retention, auto-promotion
- Capture SDK — Blackmagic, AJA, or Deltacast uploader selection
9. Cluster & Distributed Capture
Primary + worker topology with remote DeckLink nodes
- Primary node runs API, scheduler, and web UI
- Worker nodes handle proxy/thumbnail jobs
- Remote capture nodes run DeckLink cards off-host
- Heartbeat health monitoring
- Automatic failover and recovery
10. Admin & User Management
Role-based access, token auth, and cluster monitoring
- User creation and role assignment
- API token generation for integrations
- Container and cluster node status
- System health dashboard
Quick Start
# Clone (repo renamed; old URL still redirects)
git clone https://forge.wilddragon.net/zgaetano/dragonflight.git
cd dragonflight
# Configure
cp .env.example .env
# Edit .env — S3 credentials + SESSION_SECRET at minimum
# Launch
docker compose up -d
# Open
open http://localhost:47434
Architecture
SDI / SRT / RTMP ──► capture (FFmpeg)
├─ HLS preview tee ──► /live/<assetId>/index.m3u8
└─ master output
├─ growing_enabled=true:
│ /growing/<projectId>/<clip>.mov
│ (Premiere mounts SMB, edits live)
│ └─► promotion worker uploads to S3
│
└─ growing_enabled=false:
multipart stream → S3
assets POST ──► proxy job ──► worker
├─ libx264 (CPU) or NVENC/VAAPI (GPU)
├─ thumbnail job
└─ status: ingesting → processing → ready
Tech Stack
- Runtime: Node.js 22, Docker Compose
- Backend: Express, PostgreSQL 16, Redis 7 + BullMQ
- Frontend: Vanilla React via in-browser Babel (no bundler), hls.js
- Media: FFmpeg 7.1 with SDK 16 DeckLink patches
- Codecs: ProRes, H.264, H.265, DNxHR, MOV/MP4/MXF containers
- Storage: S3-compatible (RustFS) for masters, proxies, thumbnails
Services
| Service | Port | Purpose |
|---|---|---|
| web-ui | 47434 | Browser SPA + capture controls |
| mam-api | 47432 | REST API + recorder orchestration + scheduler |
| capture | 47433 / 9000 / 1935 | DeckLink/SRT/RTMP ingest sidecar |
| worker | — | BullMQ proxy + thumbnail workers |
| db | 5432 | PostgreSQL 16 |
| queue | 6379 | Redis 7 |
Workflow Example: Live-to-Edit
- Operator schedules a recording on Recorder A for 14:00–15:30, assigns to "News/Segment-A" project
- Capture starts at 14:00, writes ProRes master to SMB landing zone
- Editor mounts SMB in Premiere, opens the live .mov file via the CEP panel
- Editor trims and marks in/out points while capture is still writing
- Capture finishes at 15:30, promotion worker uploads master to S3
- Editor clicks "Relink to Master" in CEP panel
- Server trims hi-res segment to exact in/out, stores for 24 hours
- Premiere relinks proxy clips to trimmed master
- Editor exports final timeline via FCP XML conform
Total time from end of capture to relinked master: ~2 minutes.
Operations
deploy/api-smoke.sh— verify every API endpoint after deploydeploy/onboard-node.sh— provision a remote worker hostdeploy/test-cluster.sh— primary↔worker connectivity smoke testdocs/GROWING_FILES_QUICKSTART.md— Premiere CEP panel install + growing-file flow
Authentication
Dragonflight uses local username/password authentication with two transports:
- Browser: session cookie (
dragonflight.sid), 8 hour absolute + 1 hour idle timeout. - Premiere panel / scripts: SHA-256-hashed bearer tokens issued from
Settings → API Tokens.
First-run setup
On a fresh install with AUTH_ENABLED=true, navigate to the web UI in a browser.
With no users in the database, the login screen renders a "First-run setup" form
instead — fill it in to create the first admin and you are logged in immediately.
Subsequent users are created from Settings → Users (any signed-in user can
create others — flat access).
Dev mode
Setting AUTH_ENABLED=false disables all auth checks; a synthetic dev user
is attached to every request. Never deploy this way. The dev user row is
seeded with a hash that no real password can match, so flipping
AUTH_ENABLED=true later does not expose the dev account.
Recovering a forgotten admin password
Any signed-in user can reset another user's password from Settings → Users.
If no one can sign in (all admins forgot their passwords), reset directly in
Postgres:
-- generate a fresh bcrypt hash with:
-- node -e "import('bcrypt').then(b => b.default.hash(process.argv[1], 12).then(h => console.log(h)))" 'new-passphrase-here'
UPDATE users SET password_hash = '<bcrypt-hash>', password_updated_at = NOW()
WHERE username = 'admin';
AUTH_ENABLED transition
When flipping AUTH_ENABLED=false → true on an existing install:
- Ensure
SESSION_SECRETis set to a stable value (rotating it logs everyone out). - Set
ALLOWED_ORIGINSto the public origin(s) of the web UI. - Set
TRUST_PROXY=truewhen behind nginx (required for rate-limit accuracy). - Restart
mam-api. - Visit the UI — first-run setup will appear if no real users exist yet.
License
Proprietary — Wild Dragon LLC, all rights reserved.