Commit graph

1053 commits

Author SHA1 Message Date
2812705d1c feat(recorders): expose XDCAM HD422 bitrate field in growing mode
Growing mode now shows an editable 'XDCAM HD422 bitrate (Mbps)' input (codec stays fixed to the growing MXF path). Default seeds to 50 Mbps for growing, 25 for GPU master. Backend already honored recording_video_bitrate via _buildGrowingOrchestrator -b:v/minrate/maxrate; this surfaces the control in the config modal.
2026-06-04 11:52:07 +00:00
91d0d755a5 fix(node-agent): proper stdcopy demux for container logs (clean line starts) 2026-06-04 05:29:56 +00:00
179a740453 feat(admin): cluster-wide Logs page + fix container log demux + poll containers
- mam-api: dockerLogs() + demuxDockerStream() — the local container-log path
  JSON.parsed Docker's raw multiplexed stream and always returned '(no logs)';
  now strips stdcopy framing and returns readable text (tail configurable).
- web-ui: new Logs admin page — every container across every node grouped by
  node in a left rail, live-follow log viewer with filter + copy on the right.
  Reuses the now-working /cluster/containers/:node/:id/logs endpoint.
- web-ui: Containers screen now polls every 5s (was load-once) so the
  cross-cluster view stays live without manual refresh.
- icons: add server + file glyphs (were referenced but missing -> blank).
- nav: Logs wired into the Admin sidebar section + routes + breadcrumbs.
2026-06-04 05:28:17 +00:00
1348db8f33 fix(node-agent): import crypto — auth was ALWAYS failing on remote nodes
THE root cause of 'container view only shows the primary': checkAgentAuth used
crypto.timingSafeEqual but 'crypto' was never imported (ES module). The call
threw ReferenceError, the try/catch swallowed it, _bearerEq returned false, so
EVERY bearer-token check on a node-agent failed. The primary's own containers
showed only because the local node-agent has no NODE_TOKEN (auth skipped).

Adding 'import crypto from crypto' makes token comparison work, so the primary
mam-api can now read containers + logs from every node.
2026-06-04 05:21:33 +00:00
4ad145f00a debug(node-agent): log token prefix/suffix on auth reject 2026-06-04 05:20:13 +00:00
90bd82f49a debug(node-agent): log auth reject token lengths 2026-06-04 05:18:34 +00:00
70c873ae95 fix(cluster): shared CLUSTER_READ_TOKEN so mam-api sees containers on ALL nodes
/cluster/containers only returned the primary's containers: mam-api fanned out
to each node-agent with a single NODE_AGENT_TOKEN, but each node-agent only
accepted its own bound NODE_TOKEN, so remote nodes returned 401 and were
silently dropped (UI showed 'only zampp1').

node-agent now ALSO accepts a shared CLUSTER_READ_TOKEN (= mam-api's
NODE_AGENT_TOKEN) for the read-only container/log endpoints, so the aggregate
container view + per-container logs work across the whole cluster.
2026-06-04 05:14:44 +00:00
d6b0b3a9a6 fix(capture): restore proven-clean wallclock audio (match de509c6 baseline)
Removing wallclock made A/V length drift far worse (audio 11.8% long). The
known-clean config used wallclock + master aresample=async=1; the leading
silence is a standby backlog artifact addressed by the bridge live-edge flush +
record-start audio FIFO drain, not by changing the timestamp source.
2026-06-04 05:06:40 +00:00
55a72af905 fix(capture): derive audio PTS from sample count (kill 2.5s leading silence)
The persistent ~2.5s of leading silence was the master aresample=async=1 PADDING
the audio to reconcile a PTS-origin mismatch: video PTS starts at frame 0
(-framerate), but -use_wallclock_as_timestamps stamped the first audio chunk at
its wall-clock arrival time (~2.5s after the ffmpeg graph opened). aresample
filled the gap with silence.

Drop wallclock: audio PTS now comes from the 48kHz sample count starting at 0 —
the same origin as video frame 0 — so the streams align with no pad. The bridge
already hands live audio (backlog flushed on attach), so no rate reference is
needed from wallclock.
2026-06-04 05:01:05 +00:00
e9e883d06e fix(deltacast-bridge): flush queued audio backlog to live edge on reader attach
The ~2.5s of leading silence at record start was the VHD audio slot QUEUE: while
the recorder is idle (no FIFO reader), the bridge blocks on open(O_WRONLY) but the
board keeps buffering audio slots. When the record ffmpeg attaches, the bridge
streamed that stale backlog first — heard as leading silence and pushing audio
out of alignment with the live video.

On each reader attach, drain slots that lock FAST (already-queued backlog) and
stop at the first lock that takes ~a frame period (= waiting on a live slot), so
the reader is handed the live edge, A/V aligned.
2026-06-04 04:54:32 +00:00
b1a2249f36 fix(capture): align A/V at record start (kill leading silence + length drift)
Root cause of 'silent first ~1s then clean' + ~0.5% audio-too-long: in standby
the bridge keeps filling the audio FIFO while the idle-preview consumes only
video, so when recording starts ffmpeg reads a ~0.5s backlog of stale audio,
AND the video-only pre-roll discards video frames the audio never had.

Fix: (1) skip the video-only pre-roll in standby (warm slot = no unstable
frames), (2) drain the audio FIFO non-blocking immediately before ffmpeg opens
it, so audio starts at the live edge aligned with the first real video frame.
2026-06-04 04:49:53 +00:00
fffb6b63b5 fix(capture): revert 16ch audio to clean 2ch — fixes pitch/rate regression
The 16ch interleave in the deltacast bridge produced audio at HALF the correct
sample rate (measured 24224 vs 48000 samples/s/ch), which broke A/V sync and
pitch. Per the working baseline (audio was clean before the channel selector),
revert the bridge audio thread to the original single-group 2ch extraction and
the capture-manager audio input to -ac 2 + wallclock + aresample.

KEPT the good fixes: long-GOP HEVC for non-growing (NVENC realtime, no frame
drops) and GPU-only codec list. 16ch/channel-select is shelved for a separate,
properly-validated change.
2026-06-04 04:33:34 +00:00
b28393eb76 Revert "fix(capture): skip video-only pre-roll in standby to stop A/V pitch drift"
This reverts commit 51b66d882f.
2026-06-04 04:28:11 +00:00
51b66d882f fix(capture): skip video-only pre-roll in standby to stop A/V pitch drift
The pre-roll drained only the video pipe (fc_pipe) while the audio FIFO kept
buffering, so ffmpeg read ~PRE_ROLL_SECONDS of surplus pre-roll audio — making
audio longer than video, which when synced compresses audio ~0.5% (pitch-up,
measured: 2591573 audio samples vs 2579395 expected for the video duration).

In standby the framecache slot is already warm (no unstable startup frames), so
the drain is unnecessary; skipping it lets ffmpeg open video and audio together
from the same instant. Cold on-demand spawns keep the brief drain.
2026-06-04 04:24:08 +00:00
07eea02109 fix(capture): restore audio wallclock (throughput) + remove CPU codec options
- restore -use_wallclock_as_timestamps on audio input: without it ffmpeg's raw
  s16le reader stalled the graph (NVENC idle at 9%, ~half frames dropped). With
  it + long-GOP HEVC the encoder runs realtime and A/V length stays locked.
- remove all CPU codec options (prores*, dnxh*, libx264/265) from recorder UI;
  GPU NVENC only (hevc_nvenc / h264_nvenc). 3x L4 cluster, no reason for CPU.
- GPU codec defaults in env builders + proxy default h264_nvenc.
2026-06-04 04:14:59 +00:00
0ea22e1e53 fix(capture): gate all-intra HEVC on growing-files; normal record uses long-GOP
The hevc_nvenc codec was hardcoded to all-intra (-force_key_frames expr:1), which
is ~4x the NVENC load. Applied to every recording it exceeded the L4's realtime
budget at 1080p59.94 10-bit -> fc_pipe dropped ~half the frames -> video came out
shorter than the (correct) audio -> A/V drift + pitch-up on playback.

Now all-intra is used ONLY when growing-files is on (where it's required for the
editable head). Normal recordings use efficient long-GOP HEVC (2s GOP, 2 B-frames)
which NVENC sustains in realtime with zero drops.
2026-06-04 04:09:14 +00:00
8e5405c3f9 fix(capture): derive deltacast audio PTS from sample count, not wall-clock
Removing -use_wallclock_as_timestamps on the SDI audio input. The bridge writes
SDI-clock-paced samples, so PTS from the 48kHz sample count shares the video's
clock domain and the audio length tracks the video length exactly. Wall-clock
timestamps made audio length = real elapsed time, which drifted ~1% longer than
the frame-count video when the encoder dipped under realtime (pitch-up).
2026-06-04 04:01:54 +00:00
51f939b1fe fix(deltacast-bridge): use group-0 sample count as authoritative audio length
Taking the MAX sample count across the 4 audio groups could emit more audio
frames per slot than group 0 (the SDI-clock reference), drifting the audio
stream slightly longer than video — heard as a ~1% pitch-up. Group 0 paces the
timeline exactly as the original 2ch path did; shorter groups are silence-padded
to its length, never extending it.
2026-06-04 04:01:25 +00:00
095306d9cf feat(recorders): 16ch SDI audio capture + per-recorder channel select + menu redesign
Audio:
- deltacast-bridge: always extract all 4 SDI audio groups (16ch), interleave to
  one 16ch s16le stream per port FIFO; format JSON reports audio_channels:16
- capture-manager: declare FIFO as 16ch input; keep first N discrete channels
  (2/8/16) via pan channelmap on the master (no downmix); HLS preview stays
  stereo. effAudioChannels drives -ac on the master container.
- config modal: Audio channels select (2/8/16)
- channel count already flows mam-api->node-agent->capture via RECORDING_AUDIO_CHANNELS

UI redesign (production craft):
- recorders grouped into per-node hardware 'rack' cards (online/offline state)
- lifecycle accent rail: grey DISABLED / green ENABLED / pulsing-red RECORDING
- promoted capture-port chip, monospaced metadata, Enable as primary CTA
- dedicated recorder CSS block; built on existing design tokens
2026-06-04 03:34:41 +00:00
de509c66ab feat(recorders): hardware-identity model with Enable/Disable lifecycle
Recorders are now physical capture ports, not user-created rows:
- migration 036: label, enabled, auto_provisioned + UNIQUE(node_id,device_index)
  (the structural fix that makes two recorders sharing a port impossible)
- mam-api: auto-provision one recorder row per port from heartbeat capabilities
  (reconcileRecordersForNode); create-once, never overwrites operator config
- mam-api: POST /:id/enable + /:id/disable (provision/teardown standby sidecar);
  PATCH accepts label; config persists across enable/disable
- node-agent: freeCapturePort() force-removes any container on a capture port
  before standby/start — eliminates the EADDRINUSE collisions
- web-ui: recorder menu grouped by node (online/offline), Enable/Disable toggle,
  per-recorder config modal (codec/bitrate/growing/label/project), friendly
  label over hardware name, no destructive delete

Fixes the delete/recreate churn that orphaned standby sidecars and collided on
capture ports during this session's outage.
2026-06-04 03:14:43 +00:00
bf4632b911 feat(mam-api): extract ensureStandbySidecar + add POST /recorders/reconcile-standby
Re-provisions the persistent standby sidecar for SDI/deltacast recorders that
lost theirs (manual cleanup, node redeploy, wiped /dev/shm). Without this the
recorder falls back to slow on-demand spawn on /start, which can collide on the
capture port (EADDRINUSE). Idempotent; { force:true } recreates even when a
container_id is already set.
2026-06-04 03:05:00 +00:00
5668c03615 chore(capture): remove stale legacy FIFO path + pin capture profile
- capture-manager: remove dead legacy deltacast FIFO video path (FC_SLOT_ID
  is now always set by node-agent, framecache mandatory on all SDI nodes)
- node-agent: correct stale comment about legacy FIFO fallback
- onboard-node.sh: harden detect_sdi (device-node checks, not just lspci) and
  persist COMPOSE_PROFILES so framecache survives every redeploy on SDI nodes
- remove committed capture.js.bak

Root cause of this session's outage: zampp3 came up without the capture
compose profile, so framecache never started; the bridge published to shm
with no consumer and recorders showed 'receiving' with no real capture.
2026-06-04 02:50:57 +00:00
Wild Dragon Dev
4045e30cd2 fix(node-agent): make http server handler async 2026-06-04 01:54:38 +00:00
Wild Dragon Dev
df6ca084ff feat(web-ui): add Node column to Containers screen + integrated log viewer 2026-06-04 01:48:44 +00:00
Wild Dragon Dev
2f13c8d8b1 feat(mam-api): aggregate containers from all nodes + proxy logs 2026-06-04 01:42:13 +00:00
Wild Dragon Dev
a90adb5b52 feat(node-agent): add /containers and /sidecar/:id/logs endpoints 2026-06-04 01:40:44 +00:00
Wild Dragon Dev
8efcf5c545 feat(capture): remove build-with-decklink.sh script 2026-06-04 01:27:41 +00:00
Wild Dragon Dev
e5abbede43 debug(fc_writer): add trace logs for GET slots path 2026-06-04 01:13:19 +00:00
Wild Dragon Dev
cc489f7774 fix(fc_writer): handle 409 Conflict by fetching existing slot details via GET 2026-06-04 01:12:06 +00:00
Wild Dragon Dev
5b72ee167d fix(decklink-bridge): prevent redundant fc_writer_open loops via last_format tracking 2026-06-04 01:10:47 +00:00
Wild Dragon Dev
d957ce74ae fix(decklink-bridge): avoid redundant fc_writer_open calls in reopen_slot 2026-06-04 01:09:08 +00:00
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
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
b5235e0a2c fix(node-agent): always mount /dev/shm into sidecars for framecache access 2026-06-03 19:02:04 +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
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
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
Wild Dragon Dev
b700902200 feat(framecache): phase 4 — capture-manager reads from framecache
- services/framecache/client/fc_pipe.c: new slot→stdout pipe adapter
  - Opens framecache slot as consumer (independent cursor per instance)
  - Streams raw UYVY422 frames to stdout continuously
  - SIGPIPE detection via write() return — exits cleanly on ffmpeg exit
  - SIGTERM/SIGINT clean stop from capture-manager
  - Periodic stats to stderr (every 300 frames)
  - Exit codes: 0=clean, 1=slot not found, 2=EPIPE

- services/framecache/CMakeLists.txt: add fc_pipe target + install
- services/framecache/Dockerfile: copy fc_pipe to runtime image

- services/capture/Dockerfile:
  - New fc-pipe-builder stage (builds fc_pipe from framecache sources)
  - Copies fc_pipe binary to /usr/local/bin/fc_pipe in runtime image

- services/capture/src/capture-manager.js:
  - _buildInputArgs: new framecache path for deltacast + sdi/blackmagic
    when FC_SLOT_ID env is set (injected by node-agent from bridge fmt JSON)
    - Spawns fc_pipe <slot_id> as child process
    - Uses pipe:0 as ffmpeg rawvideo input 0
    - Audio FIFO (unchanged) as ffmpeg input 1
    - Falls back to legacy FIFO path when FC_SLOT_ID unset
  - audioMap: covers blackmagic via framecache (input 1 for audio FIFO)
  - isInterlacedSource: covers blackmagic interlaced signals
  - hiresStdio: pipe stdin when bridgeProcess set (fc_pipe stdout→ffmpeg)
  - Non-growing spawn: pipes fc_pipe.stdout → ffmpeg.stdin
  - Growing orchestrator spawn: pipes fc_pipe.stdout → bash.stdin
  - sdiHlsDir: covers blackmagic source type
  - Session state stores _fcPipeProcess for clean stop
  - stop(): sends SIGTERM to fc_pipe after ffmpeg SIGINT
2026-06-03 15:32:40 +00:00
Wild Dragon Dev
b2c63de2fa feat(framecache): phase 3 — decklink-bridge writes to shm
- services/capture/decklink-bridge/main.cpp: new C++ DeckLink bridge
  - IDeckLinkInputCallback (VideoInputFrameArrived) writes UYVY422
    frames to framecache slot via fc_writer_write()
  - VideoInputFormatChanged reopens slot with new resolution/fps
  - bmdVideoInputEnableFormatDetection: auto-detects signal format
  - bmdFormat8BitYUV (UYVY422) — same pixel format as deltacast-bridge
  - Audio written from callback to named FIFO (same pattern as deltacast)
  - Silence thread keeps audio FIFO open between sessions
  - slot_id: decklink-<NODE_ID>-<device_idx>
  - Format JSON emitted on first frame (includes slot_id)
  - LEGACY_FIFO compile flag mirrors deltacast-bridge
  - --devices csv, --fc-url, --audio-pipe-dir, --signal-timeout args

- services/capture/decklink-bridge/CMakeLists.txt:
  - Reuses fc_writer.c from deltacast-bridge (shared writer module)
  - Links rt + dl + pthread; DeckLink SDK via dlopen at runtime
  - LEGACY_FIFO option

- services/capture/Dockerfile:
  - New decklink-bridge-builder stage (g++ + DeckLink SDK headers)
  - Copies decklink-bridge binary to /usr/local/bin/decklink-bridge

- services/node-agent/index.js:
  - FC_URL + FC_NODE_ID constants (from env vars, passed to all bridges)
  - startDecklinkBridge(deviceIndices) / stopDecklinkBridge() functions
    mirror deltacast bridge lifecycle management
  - deltacast startDeltacastBridge: adds --fc-url arg + NODE_ID env
  - sidecar start: injects FC_URL into all sidecar envs; sets IpcMode=host
    for deltacast + blackmagic sidecars; starts decklink-bridge for sdi/
    blackmagic source types; injects FC_SLOT_ID from fmt JSON
  - sidecar stop: stopDecklinkBridge() when last blackmagic sidecar stops
2026-06-03 15:25:25 +00:00
Wild Dragon Dev
0d479d043d feat(framecache): phase 2 — deltacast-bridge writes to shm ring
- fc_writer.h/fc_writer.c: new framecache slot writer module
  - Registers slot via POST /slots to framecache HTTP API on signal lock
  - Opens shm file returned by API (O_RDWR + mmap MAP_SHARED)
  - fc_writer_write(): atomic write_cursor advance + sem_post per frame
  - fc_writer_close(): DELETE /slots/:id + munmap + sem_close
  - HTTP calls via raw POSIX sockets (no libcurl dependency)
  - Parses host:port from FC_URL env var or --fc-url arg

- main.c changes:
  - PortState gains slot_id, fc_url, fc_writer fields
  - --fc-url CLI arg + FC_URL env var (default http://localhost:7435)
  - On signal lock: fc_writer_open() before thread launch;
    falls back to FIFO if framecache unreachable (fc_writer == NULL)
  - video_thread: shm path primary (fc_writer != NULL),
    FIFO path fallback (fc_writer == NULL or LEGACY_FIFO=1)
  - Format JSON now includes slot_id field for node-agent consumption
  - Cleanup: fc_writer_close() before VHD_CloseBoardHandle

- CMakeLists.txt:
  - Add fc_writer.c to build
  - Link rt (shm_open, sem_open)
  - LEGACY_FIFO option (OFF by default) for nodes without framecache

Audio thread unchanged — audio stays in FIFO (shm audio is roadmap).
2026-06-03 15:13:20 +00:00
Wild Dragon Dev
1573bf8954 feat(framecache): phase 1 — framecache container + consumer library
- services/framecache/: new standalone container
  - slot.h/slot.c: shm ring buffer (120 frames, FC_MAGIC header, atomic
    write_cursor, POSIX semaphore per slot)
  - registry.h/registry.c: in-memory slot registry + /dev/shm/framecache/
    registry.json persistence
  - framecache.c: HTTP API server (libmicrohttpd, port 7435)
    POST /slots, GET /slots, GET /slots/:id, DELETE /slots/:id, GET /health
  - fc_client.h/fc_client.c: consumer library — fc_consumer_open/read/close
    with per-consumer cursor, timeout via sem_timedwait, automatic skip+count
    when consumer falls behind writer by > ring_depth frames
  - fc_test_consumer.c: dev utility to attach to any slot and print fps/stats
  - CMakeLists.txt: framecache server + fc_client static lib + test consumer
  - Dockerfile: builder + slim runtime stages

- docker-compose.worker.yml: add framecache service (profile: capture,
  ipc: host, shm_size from FC_SHM_SIZE_GB env var, healthcheck)

- .env.example: document FC_SHM_SIZE_GB with per-node guidance
2026-06-03 14:53:51 +00:00
c269468014 fix(scheduler): orphan grace window must use recorder.updated_at not asset.updated_at — asset is created at recording START not STOP 2026-06-03 14:03:32 +00:00
108390e823 fix(scheduler): add 90s grace before marking stopped-recorder live assets as error 2026-06-03 12:51:41 +00:00
7704988978 fix(recorders): resolve syntax error caused by double declaration of proto variable 2026-06-03 12:17:06 +00:00
a00e90ecc8 fix(merge): resolve conflict in screens-library.jsx 2026-06-03 10:43:59 +00:00
c21260c9b0 fix(ampp): require auth on AMPP endpoint 2026-06-03 10:42:57 +00:00
d16d19c26d fix(node-agent): use timingSafeEqual for token comparison 2026-06-03 10:42:57 +00:00
63f05cd652 fix(audit): critical security hardening and ops reliability fixes 2026-06-03 10:42:57 +00:00
OpenCode
dbef15ae0a fix(library): clicking project in rail now filters assets to that project instead of navigating away 2026-06-03 04:11:32 +00:00
OpenCode
99bd6a8c9c fix(library): auto-expand all bins on load so nested children visible by default 2026-06-03 04:06:48 +00:00
OpenCode
4e6142f455 fix(web-ui): orange pulse logo (bigger, no canvas), fix library missing expandedBins state 2026-06-03 04:02:17 +00:00
OpenCode
02d502baaf fix(web-ui): restore full screens-home.jsx with DragonFlame + Home + Dashboard 2026-06-03 03:58:35 +00:00
OpenCode
00a7af7c54 feat(web-ui): nested bins tree + DragonFlame CSS restored (complete) 2026-06-03 03:48:29 +00:00
cb9ef9c14e fix(web-ui): restore correct styles-fixes.css with DragonFlame logo CSS + upload actual screens-library.jsx nested bins: styles-modal.css 2026-06-02 23:35:12 -04:00
f48a0b73ee feat(web-ui): nested bins tree in library sidebar + bin filter includes descendants: styles-fixes.css 2026-06-02 23:34:14 -04:00
463cc3694d feat(web-ui): nested bins tree, DragonFlame logo, recorder modal 2x2 grid, cleanup .bak
- Library: nested bins with expand/collapse tree in sidebar
  - buildBinTree() + collectDescendantIds() helpers
  - BinTreeNodes recursive component with hover sub-bin create (+) button
  - Selecting a parent bin shows assets from all descendant bins too
- Home: canvas DragonFlame particle animation behind logo (90 flame + 30 spark), logo 140px
- Recorder modal: source-type-grid 3-col → 2x2 so Deltacast card no longer overflows
- CSS: launcher background radial gradient taller; launcher-logo-wrap 160x200px
- Cleanup: remove capture.js.bak: screens-home.jsx
2026-06-02 23:33:58 -04:00
bd662f6917 fix(migration): wrap ALTER TYPE ADD VALUE in DO block with IF NOT EXISTS check 2026-06-03 00:41:04 +00:00
a04ef2de3a feat(promotion): implement manual growing files promotion via BullMQ queue + pending_migration status + right click Move to S3 2026-06-03 00:38:50 +00:00
62b9a90291 fix(recorders): stop capture containers in the background to prevent API TimeoutError on large file uploads 2026-06-03 00:22:36 +00:00
600af4564e fix: restore screens-library.jsx and visuals.jsx to clean state + add deriveGrowingRaster scanHint fix 2026-06-03 00:19:55 +00:00
d8df8755e2 fix: main.c migrate-to-library + deltacast audit cleanup 2026-06-02 19:01:31 -04:00
5525041901 feat/fix: visuals.jsx — growing migrate flow + deltacast cleanup 2026-06-02 18:40:16 -04:00