Commit graph

1091 commits

Author SHA1 Message Date
Wild Dragon Dev
21ba7595b3 fix(node-agent): await async cleanup + fix syntax 2026-06-04 00:57:22 +00:00
Wild Dragon Dev
315b31a68b fix(node-agent): await stopDecklinkBridge and clean up stale occurrences 2026-06-04 00:54:29 +00:00
Wild Dragon Dev
d1b40f5303 fix(node-agent): pass correct FC_URL and Cmd to containerized decklink-bridge 2026-06-04 00:51:14 +00:00
Wild Dragon Dev
6ee8dd5694 feat(node-agent): containerized decklink-bridge + async bridge management 2026-06-04 00:46:19 +00:00
Wild Dragon Dev
8ca7c79acd fix(node-agent): mount decklink-bridge wrapper script as file (not dir) 2026-06-04 00:43:19 +00:00
Wild Dragon Dev
fb0ce320a5 build(node-agent): mount host /usr/local/bin to expose decklink-bridge wrapper 2026-06-04 00:42:31 +00:00
Wild Dragon Dev
6481760dff revert(capture): Dockerfile copy paths to root-relative for compose build 2026-06-04 00:39:24 +00:00
Wild Dragon Dev
650a100d17 build(capture): include decklink-bridge in runtime image 2026-06-04 00:37:49 +00:00
Wild Dragon Dev
400cb786ab fix(decklink-bridge): use IDeckLinkVideoBuffer QueryInterface to get raw bytes 2026-06-04 00:35:16 +00:00
Wild Dragon Dev
74055e79f8 fix(decklink-bridge): use GetFrameInternalBufferBytes instead of GetBytes 2026-06-04 00:28:19 +00:00
Wild Dragon Dev
a5aed86349 fix(recorders): kill stale standby container before on-demand respawn to prevent EADDRINUSE 2026-06-03 23:04:17 +00:00
Wild Dragon Dev
a096226072 fix(capture): remove -use_wallclock_as_timestamps from framecache video input
The framecache ring delivers frame-accurate frames at exactly the SDI clock
rate. -use_wallclock_as_timestamps was wrong for this source — it stamped
frames by ffmpeg arrival time rather than capture time, causing the recorded
file to report wrong framerates (e.g. 56.06 instead of 59.94) and a
glitchy first second at startup (NVENC cold-start backlog bunched timestamps).

Fix: remove -use_wallclock_as_timestamps from the rawvideo (pipe:0) input
and rely on -framerate for correct CFR timestamps from frame 0.
Audio keeps its FIFO wallclock; aresample=async=1 on the master output
resamples audio to align with the CFR video PTS.
2026-06-03 22:30:03 +00:00
Wild Dragon Dev
7631527f46 fix(capture): add auth header to finalize call in POST /capture/stop 2026-06-03 22:11:15 +00:00
Wild Dragon Dev
a22bda44a7 fix(recorders): set PRE_ROLL_SECONDS=1 for sdi/deltacast/blackmagic sidecars 2026-06-03 22:07:16 +00:00
Wild Dragon Dev
3d4880d944 fix(capture): reduce pre-roll to 1s in standby mode (slot already warm) 2026-06-03 22:05:11 +00:00
Wild Dragon Dev
ef57900583 feat(recorders): always-on standby sidecars for deltacast, sdi, blackmagic
Sidecars now spawn at recorder CREATE time instead of /start time.
The container boots in STANDBY=1 mode (idle preview only, no ffmpeg master).
On /start, mam-api sends per-session params (CLIP_NAME, ASSET_ID, PROJECT_ID)
to the running sidecar via HTTP POST /capture/start — ffmpeg starts in <1s.
On /stop, mam-api calls HTTP POST /capture/stop — container stays alive in
standby, ready for the next take immediately.
Container is only killed on recorder DELETE.

This eliminates: Docker create/start overhead (~1-2s), bridge startup (~2-5s),
and pre-roll wait (~5s). Latency from 'record' click to first encoded frame
drops from ~10s to ~1s.

Changes:
- capture/src/index.js: boot in standby when STANDBY=1 env is set; still
  start idle preview (live thumbnail visible before recording)
- capture/src/routes/capture.js: POST /start accepts full codec params and
  asset_id in body (skips mam-api asset creation when asset_id provided)
- node-agent/index.js: handleSidecarStandby() + POST /sidecar/standby route;
  warms bridge at recorder create time
- recorders.js POST /: spawn standby sidecar after DB insert (non-fatal)
- recorders.js POST /:id/start: HTTP fast-path to standby sidecar; falls
  back to on-demand spawn if standby not available
- recorders.js POST /:id/stop: HTTP /capture/stop, keep container in standby
- recorders.js GET /:id/status: use port-based URL for local capture status
2026-06-03 21:59:33 +00:00
Wild Dragon Dev
7172447644 fix(capture): remove leftover localMasterPath from session state 2026-06-03 21:42:35 +00:00
Wild Dragon Dev
37b325e1d8 fix(capture): restore direct-to-S3 streaming (pipe:1 + fragmented MOV)
Reverts the local-temp+faststart approach from 549ca6c. Masters now stream
ffmpeg stdout directly to S3 via multipart upload — no local disk consumed
on the worker. Uses +frag_keyframe+empty_moov+default_base_moof which
Premiere Pro 25.x handles natively (to be confirmed separately).

Zero /tmp/capture files. Worker disk stays flat during recording.
2026-06-03 21:40:58 +00:00
Wild Dragon Dev
dc66833247 fix: declare all slot functions in slot.h to prevent 64-bit pointer truncation
fc_slot_create, fc_slot_destroy, fc_slot_open, fc_slot_close, and
fc_slot_write_frame were defined in slot.c but never declared in slot.h.
Any translation unit calling them without seeing a proper prototype
would fall back to implicit int return (32 bits), truncating 64-bit
pointers and causing SIGSEGV on dereference.

This affected framecache.c (POST /slots → fc_slot_create, DELETE
→ fc_slot_destroy) and other callers.
2026-06-03 20:16:35 +00:00
Wild Dragon Dev
2198199a9f fix: inline accessors in slot.h now that struct fc_slot is a complete type 2026-06-03 20:11:14 +00:00
Wild Dragon Dev
f318e9c501 fix: move struct fc_slot definition to slot.h and declare accessors to fix 64-bit pointer truncation
The struct fc_slot was defined only in slot.c, making it an incomplete type
in slot.h. The inline accessor functions (fc_slot_id, fc_slot_header, etc.)
in slot.h could not compile because they referenced incomplete struct
members. The compiler fell back to implicit int return type, truncating
64-bit pointers to 32 bits, causing SIGSEGV in registry_add() when
strncpy received a truncated slot_id pointer.

Fix: move the struct definition to slot.h and add proper function
declarations for the accessors (definitions stay in slot.c).
2026-06-03 20:10:31 +00:00
Wild Dragon Dev
902d985ca8 framecache: add SIGPIPE ignore, signal logging, and init:true for stable POST handling 2026-06-03 20:05:55 +00:00
Wild Dragon Dev
0ed1254fd9 fix(framecache): bind port 7435 to host loopback so host bridges can register slots 2026-06-03 19:29:04 +00:00
Wild Dragon Dev
b5235e0a2c fix(node-agent): always mount /dev/shm into sidecars for framecache access 2026-06-03 19:02:04 +00:00
Wild Dragon Dev
5686e65df9 fix(docker): mount /dev/shm into capture sidecars for framecache access 2026-06-03 18:55:44 +00:00
Wild Dragon Dev
ccaef50c09 fix(decklink): cast videoFrame to base type for GetBytes, re-enable build 2026-06-03 18:45:15 +00:00
Wild Dragon Dev
522faacdcc fix(capture): remove stale CMakeCache on rebuilds 2026-06-03 18:28:22 +00:00
Wild Dragon Dev
a1a0823812 fix(framecache): install wget for healthcheck; make node-agent devices optional 2026-06-03 18:21:30 +00:00
Wild Dragon Dev
cd67dfceea fix(docker): remove hardcoded DeckLink devices to support nodes without hardware 2026-06-03 18:16:54 +00:00
Wild Dragon Dev
5eaf71b70c fix(capture): correct npm install COPY path in Dockerfile 2026-06-03 18:14:39 +00:00
Wild Dragon Dev
69eefdb512 fix(framecache): remove fc_test_consumer from docker image 2026-06-03 18:13:10 +00:00
Wild Dragon Dev
04e6646e6e fix(framecache): remove static assertion temporarily to bypass build failure 2026-06-03 18:10:44 +00:00
Wild Dragon Dev
91f80c05bc fix(framecache): correct fc_header_t size assertion 2026-06-03 18:08:28 +00:00
Wild Dragon Dev
aff3c0ece2 fix(framecache): add missing time.h includes 2026-06-03 18:05:38 +00:00
Wild Dragon Dev
38b31d6170 fix(capture): temporarily disable decklink-bridge build stage 2026-06-03 18:02:50 +00:00
Wild Dragon Dev
aa646dbb71 fix(capture): fix redefined 'expected' variable in decklink-bridge 2026-06-03 17:40:40 +00:00
Wild Dragon Dev
6294e98dc3 fix(capture): copy full deltacast-bridge dir for fc_writer to ensure include path 2026-06-03 17:38:59 +00:00
Wild Dragon Dev
4b018cb8cb fix(capture): fix decklink-bridge include path for fc_writer 2026-06-03 17:34:52 +00:00
Wild Dragon Dev
36740de86b fix(capture): update compose context to root for framecache dependency 2026-06-03 17:28:04 +00:00
Wild Dragon Dev
d193b84466 fix(capture): correct Dockerfile copy path for framecache source 2026-06-03 17:25:00 +00:00
Wild Dragon Dev
7a89c83ff4 fix(capture): correct Dockerfile COPY paths for root context 2026-06-03 17:22:25 +00:00
Wild Dragon Dev
d138265245 fix(capture): move fc_client back to framecache, rely on root build context 2026-06-03 17:12:46 +00:00
Wild Dragon Dev
b6545e61a9 fix(capture): add 5s pre-roll delay to stabilize SDI, remove framerate display
- services/capture/src/capture-manager.js:
  - Added 5s pre-roll delay for Deltacast, Blackmagic, and SDI capture paths.
  - Activates when  is present.
  - Spawns the  process immediately, drains/discards the unstable frames for 5 seconds, and then pipes the stdout of the  to the actual  process.
  - Keeps the master output perfectly clean from frame 0.

- services/web-ui/public/screens-ingest.jsx:
  - Removed currentFps framerate display from both recording and idle status states.
2026-06-03 17:08:09 +00:00
Wild Dragon Dev
9dc86aa3b6 Merge branch 'feat/unified-framecache' 2026-06-03 16:59:44 +00:00
Wild Dragon Dev
07d1fc9e72 fix(scheduler): allow 'starting' and 'stopping' statuses in DB
The scheduler tick loop updates a schedule's status to 'starting' and 'stopping'
in the database while it initiates the API calls to the recorder container. The
original CHECK constraint in recorder_schedules rejected these two statuses,
causing the scheduler to crash on constraint violation and never start the job.
2026-06-03 16:54:35 +00:00
Wild Dragon Dev
01211fef7a fix(framecache): address critical bugs from code review
C-Bug 1 (Torn read): fc_client.c zero-copy pointer replaced with consumer-owned
copy buffer + post-copy cursor revalidation to prevent reading torn frames when
the writer laps a slow consumer. New FC_LAPPED return code.
C-Bug 3 (Semaphore busy-spin): fc_client.c drains the semaphore (sem_trywait)
so the count never accumulates, relying entirely on write_cursor diff for
availability. Prevents 100% CPU loops + EOVERFLOW.
C-Bug 4 (GET /slots stack overflow): framecache.c uses heap allocation with
explicit bounds checking for JSON serialization instead of a 64KB stack buffer.
C-Bug 6 (DeckLink race): decklink-bridge uses pthread_mutex_t around fc_writer
calls and reopen_slot to prevent UAF/double-free from concurrent SDK callbacks.
C-Bug 2-net (Resolution resync): net_ingest explicitly scales to target W:H
so ffmpeg always outputs exactly frame_size bytes, ignoring source resolution
changes.
C-Bug 8 (strdup leak): net_ingest uses static caller-owned buffers for ffmpeg
args instead of strdup across listener reconnects.
C-Bug 9 (PROT_READ segfault): removed atomic write to hdr->dropped_frames from
the consumer read loop (which maps shm read-only).
2026-06-03 16:25:34 +00:00
97d725537b fix(capture): use ffmpeg rolling fps value for currentFps display — fixes wrong framerate shown on recorder tiles 2026-06-03 16:14:22 +00:00
d654f7c8a1 fix(mam-api): remove stitchedS3Stream workaround — RustFS range bug fixed in beta.6 (#143) 2026-06-03 16:07:32 +00:00
eeaa1c1b58 fix(uxp): remove broken v2.2.3 ccx — stay on v2.2.2 2026-06-03 16:07:32 +00:00
Wild Dragon Dev
99723da00f feat(framecache): phase 5 — network ingest (RTMP/SRT) via framecache
- services/framecache/src/net_ingest.c: new network ingest process
  - Spawns ffmpeg to decode SRT/RTMP → raw UYVY422 stdout
  - Reads decoded frames and writes into framecache slot via shm
  - Registers slot with framecache HTTP API on startup
  - Deregisters slot on clean exit (SIGTERM)
  - Reconnect loop for listener mode (stays alive between sessions)
  - --url, --slot-id, --fc-url, --width, --height, --fps-num/den,
    --source-type, --listen, --listen-port, --stream-key args
  - Emits format JSON to stderr on first frame

- services/framecache/CMakeLists.txt: add net_ingest target
- services/framecache/Dockerfile: copy net_ingest to runtime image

- services/node-agent/index.js:
  - startNetIngest() / stopNetIngest(): lifecycle management per recorder
  - Spawns net_ingest before sidecar start for srt/rtmp sourceTypes
  - Injects FC_SLOT_ID=net-<containerId> into sidecar env
  - Sets IpcMode=host for network sidecars using framecache
  - Maps temp id → real containerId after container create
  - stopNetIngest() called on sidecar stop
  - NET_INGEST_BIN env var (default: docker exec framecache net_ingest)

- services/capture/src/capture-manager.js:
  - _buildInputArgs srt/rtmp: framecache path when FC_SLOT_ID set
    (spawns fc_pipe, uses pipe:0 rawvideo input — same as SDI path)
  - Falls back to direct URL when FC_SLOT_ID not set (legacy path)
  - audioMap: network via framecache uses '0🅰️0?' (video-only fc_pipe,
    no audio FIFO — audio-in-shm is roadmap)
  - HLS tee: sdiHlsDir covers network-via-framecache; legacy tee gated
    on !FC_SLOT_ID to avoid duplicate HLS outputs
  - fc_pipe piped to ffmpeg stdin for network framecache path

- docker-compose.worker.yml: FC_URL + NET_INGEST_BIN in node-agent env
2026-06-03 15:37:17 +00:00