Commit graph

875 commits

Author SHA1 Message Date
Claude
f8eda7fc37 Merge design/overhaul-tasteskill: Flame Blue accent, home recomposition, motion layer
UI overhaul:
- Flame Blue (#1025A1) accent — official Wild Dragon brand standard
- Home: compact left-aligned header, primary ops + secondary 4-col rows
- Typography: hyphen fixes, sidebar version removed, topbar 48px
- Motion: staggered tile entry, button scale press, nav transitions
- Jobs stats: semantic card variants (active/failed/total)

🤖 Generated with Claude Code
2026-06-02 13:28:54 +00:00
4062be101d design: swap accent to Flame Blue #1025A1 (Wild Dragon brand standard)
Replace the electric amber placeholder with the official brand accent from
the Wild Dragon Forge Noir Edition identity guide (Pantone 286 C).

- --accent: #1025A1 (Flame Blue)
- --accent-hover: #1830B8 (Ember-adjacent)
- --accent-text: #C7CFEA (Halo — readable on dark surfaces, 13:1 contrast)
- Primary button text reverts to white (11.7:1 contrast on Flame Blue)
- Avatar uses Flame Blue background + white initials
- All rgba hardcodes updated to match new hue across CSS and JSX

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-02 08:56:29 -04:00
e54b8403e7 design: overhaul pass — amber accent, home recomposition, motion layer
Lever 1 (color): Replace #5B7CFA AI-blue with electric amber #E8821C across
all accent tokens, tile tones, logo glows, and hardcoded rgba values. Dark
text on amber primary buttons for WCAG AA contrast.

Lever 2 (home): Collapse centered logo hero into compact left-aligned header.
Split tile grid into primary ops row (Library, Recorders, Playout) + secondary
4-col row (Downloads, Jobs, Dashboard, Settings) with reduced visual weight.

Lever 3 (typography): Remove v1.2.0 from sidebar. Fix em-dashes to hyphens or
periods across all visible UI strings (option labels, body copy, error messages).
Topbar height 56px -> 48px.

Lever 4 (motion): Staggered entry animation for launcher tiles
(prefers-reduced-motion gated). Tactile scale(0.97) on primary/record buttons.
Smooth 150ms nav active-item transitions.

Lever 5 (blocks): Jobs stats row semantic card variants (amber glow when
active, red border when failed, quiet muted style for Total).

Lever 6 (spacing): Topbar 48px, launcher inner gap tightened, status left-aligned.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-02 08:20:15 -04:00
Claude
f218650b85 fix(scheduler): mark orphaned live assets error immediately when recorder stops
When a capture sidecar crashes before finalize() runs (e.g. wrong node,
filter error, hardware fault), the asset stays 'live' indefinitely — library
shows 'Recording' badge for up to 120 minutes until the stale-timeout fires.

Add an orphan check that runs every scheduler tick: if an asset is 'live'
and its recorder is 'stopped', mark it 'error' immediately. This runs before
the 120-minute staleness guard so the library clears within 15 seconds.

🤖 Generated with Claude Code
2026-06-02 11:43:22 +00:00
Claude
08be8fea77 fix(deltacast-bridge): video EPIPE reopens FIFO instead of stopping port permanently
When a capture sidecar stopped/restarted, the bridge video thread got EPIPE
on the FIFO write, set g_port_stop[port]=1, and the port went dead — requiring
a full bridge restart to recover. Subsequent record attempts on that port would
hang in 'connecting' forever.

Fix: mirror the audio thread pattern — on EPIPE, close the FIFO and loop back
to open() blocking for the next reader. Hardware lock errors (SDK failures)
still stop the port via g_port_stop as before. Only reader-disconnect (EPIPE)
now recovers gracefully.

This was the cause of port 6 (Ghost) failure in the burn test.

🤖 Generated with Claude Code
2026-06-02 11:33:17 +00:00
Claude
858c9f7b97 fix(deltacast-bridge): call VHD_SetBiDirCfg before board open + set channel SDI mode
ROOT CAUSE of 'connecting' hangs and intermittent port failures:
The DELTA-12G-e-h 8c is a bidirectional card. Without calling
VHD_SetBiDirCfg(board_index, VHD_BIDIR_80) before streaming, the
board remains in its default bi-dir config (likely 4RX/4TX) — so
RX stream opens fail with VHDERR_RESOURCEUNAVAILABLE on channels
configured as TX, causing random 'connecting' hangs per the SDK docs.

Per SDK Tools.cpp SetNbChannels() pattern:
1. Open temporary board handle
2. Check IS_BIDIR + channel counts
3. Call VHD_SetBiDirCfg(board_index, VHD_BIDIR_80) for 8ch bidir
4. Close temp handle, then open real board handle for streaming

Also add VHD_SetChannelProperty(VHD_CHANNEL_MODE_SDI) for ASI-type
channels per Sample_RX.cpp — required for 12G-ASI/3G-ASI channel
types to correctly detect incoming video standard.

🤖 Generated with Claude Code
2026-06-02 11:23:39 +00:00
Claude
59294856a2 feat(library): remove Audio tab (revisit later)
🤖 Generated with Claude Code
2026-06-02 11:18:30 +00:00
Claude
d509876444 feat(monitors): always-on tiles with last-frame freeze, elapsed timer, port badge
- MonitorTile now persists lastAssetId in local state so tiles continue
  showing content after a recording stops (frozen HLS, then thumbnail)
- When recording stops: HlsPreview stays alive briefly while segments are
  hot, then fetches /api/v1/assets/{id}/thumbnail for a frozen still
- Idle tiles that never recorded show FauxFrame with IDLE badge as before
- Per-tile elapsed timer ticks every second using feed.started_at
- capturePort badge (Port N / SDI N) visible on each tile
- Monitors poll interval tightened from 5s -> 3s for faster live_asset_id pickup

🤖 Generated with Claude Code
2026-06-02 11:15:21 +00:00
Claude
e97a289722 fix(node-agent): handle bridge spawn ENOENT + add pid:host for process detection
Two bugs causing deltacast recorder 500s:

1. startDeltacastBridge() had no proc.on('error') handler — ENOENT when
   deltacast-bridge binary not in container PATH crashed the entire node-agent
   process (unhandled error event). Added error handler that logs and clears
   _dcBridge so the sidecar start continues.

2. node-agent container lacked pid:host — _dcBridgeProcessAlive() scans /proc
   but without host PID namespace it only sees container PIDs, so it always
   returned false and tried to re-spawn the bridge (hitting bug 1).
   pid:host lets the /proc scan see the host's deltacast-bridge systemd process.

🤖 Generated with Claude Code
2026-06-02 10:48:09 +00:00
Claude
6895fbc5af feat(ingest): show capture port on recorder cards + growing indicator on schedule blocks
- Recorder cards now display a Port chip showing the bridge port (deltacast)
  or SDI device index (blackmagic) so operators can see at a glance which
  physical input each recorder is bound to.

- Schedule blocks render with a green accent + flame glyph when the backing
  recorder has growing_enabled=true, so the EPG view distinguishes
  edit-while-record slots from regular close-then-publish recordings.

🤖 Generated with Claude Code
2026-06-02 04:09:17 +00:00
Zac
8a675992c2 fix(deltacast-bridge): configure both stereo channels + INTERFACE for audio extraction (SDK Sample_RXAudio) 2026-06-02 02:01:33 +00:00
Zac Gaetano
4d1b23959c Merge: library recording thumbnails live preview 2026-06-02 01:36:00 +00:00
Zac Gaetano
d1d3033ed5 Merge: recorder card live framerate + elapsed timer 2026-06-02 01:36:00 +00:00
Zac
46dc17ffb1 fix(web-ui): show live HLS preview for recording assets in library
Live/in-progress assets had no thumbnail_s3_key, so AssetThumb fell
through to FauxFrame (black box) and then an absolute red border div
was drawn on top, producing the 'black box with red outline' symptom.

Fix: when asset.status === 'live', render a new LiveThumb component
instead of FauxFrame + border overlay. LiveThumb attaches hls.js (or
native HLS on Safari) to /live/<assetId>/index.m3u8, shows a muted
live video feed, and displays a 'Connecting…' placeholder with a
record icon + live-colour border while the manifest loads. Falls back
to a 'Recording…' placeholder if hls.js is unavailable or playback
fails after retries.

The red border overlay is removed from the non-live path; the LIVE
badge rendered by AssetCard's thumb-status div still appears on top
of the live video.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 01:35:27 +00:00
Claude
c083d1006a fix: deltacast audio missing after ffmpeg restart (EPIPE cascade + stale FIFO guard)
Root cause A (main.c): audio_thread set the global g_stop flag on EPIPE
(ffmpeg reader died). This killed ALL port threads across the entire bridge
process. Bridge process then exited with all 8 ports gone.

Root cause B (node-agent/index.js): startDeltacastBridge() skipped respawn
when FIFOs existed in /dev/shm/deltacast, even if the bridge process was dead.
Next ffmpeg opened the audio FIFO read-end and blocked forever (no writer) →
no audio (or video) for any new recording.

Fix A (main.c):
- Add per-port atomic g_port_stop[MAX_PORTS] flags.
- Audio thread: on EPIPE, close the FIFO fd and loop back to reopen it.
  The VHD ANC stream stays open across reconnects. Other ports unaffected.
- Video thread: on EPIPE or stream error, set only g_port_stop[port], not
  the global g_stop. Other ports keep running.
- MAX_PORTS #define moved before globals so g_port_stop[MAX_PORTS] compiles.

Fix B (node-agent/index.js):
- Add _dcBridgeProcessAlive() — scans /proc/<pid>/cmdline for deltacast-bridge.
- startDeltacastBridge(): if FIFOs exist but no live bridge process is found,
  spawn a fresh bridge instead of silently returning. Detects bridges started
  externally (e.g. sudo on the host before node-agent started).

Requires: bridge rebuild + restart on zampp3. No capture image rebuild needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 01:35:07 +00:00
Zac
9ec2997f53 fix(recorder-card): show live framerate and ticking elapsed from capture signal
- _normRecorder: add framerate field (recording_framerate || 'native');
  remove stale static elapsed calc (was computing at poll time, never ticked)
- RecorderRow: replace frozen useMemo elapsed with a live 1s setInterval
  that anchors to liveStatus.duration (authoritative from capture container)
  and falls back to wall-clock diff from recorder.started_at so the counter
  starts immediately on record and never freezes between 3s status polls
- displayFramerate: shows currentFps (2dp + 'fps') when recorder is live and
  currentFps > 0; falls back to configured recording_framerate or 'native'
- Framerate stat block: always visible (was conditional on currentFps != null);
  replaced the separate FPS-only block with a unified Framerate stat
- Also fixes latent padStart(2, '00') typo on minutes field in old elapsed calc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 01:34:34 +00:00
Zac
4a3bf18f7f fix(web-ui): show live HLS preview for recording assets in library
Live/in-progress assets had no thumbnail_s3_key, so AssetThumb fell
through to FauxFrame (black box) and then an absolute red border div
was drawn on top, producing the 'black box with red outline' symptom.

Fix: when asset.status === 'live', render a new LiveThumb component
instead of FauxFrame + border overlay. LiveThumb attaches hls.js (or
native HLS on Safari) to /live/<assetId>/index.m3u8, shows a muted
live video feed, and displays a 'Connecting…' placeholder with a
record icon + pulsing live-colour border while the manifest loads.
Falls back to a 'Recording…' placeholder if hls.js is unavailable
or playback fails after retries.

The red border overlay is removed from the non-live path; the LIVE
badge rendered by AssetCard's thumb-status div still appears on top
of the live video.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 01:34:17 +00:00
Zac
1c068b470e fix(capture-manager): default deltacast framerate to 60000/1001 (1080p59.94) 2026-06-02 01:15:42 +00:00
Zac
e4154ea83a fix(node-agent): skip bridge spawn if FIFOs already exist (external bridge) 2026-06-02 01:04:15 +00:00
Zac Gaetano
cf928d1a46 Merge: remove Capture nav, add Playout warning 2026-06-02 00:27:26 +00:00
Zac Gaetano
b697d356b2 fix(node-agent): inject per-port bridge format JSON into deltacast sidecar env
Capture bridge emits per-port format JSON on signal lock. Node-agent
now caches these by port and injects DELTACAST_VIDEO_SIZE, DELTACAST_FRAMERATE,
DELTACAST_INTERLACED into the sidecar env so capture-manager uses
actual signal dimensions instead of hardcoded 1920x1080/25fps defaults.
2026-06-02 00:27:18 +00:00
Claude
a61e385693 feat(deltacast): replace per-port bridges with shared multi-port daemon
The old architecture spawned one deltacast-capture per recorder port; each
called VHD_OpenBoardHandle, triggering a BufMngr.c:781 OOB fault in the
delta_x300 kernel driver whenever two opens raced.

Fix: a single deltacast-bridge daemon opens the board once, opens RX
streams for all requested ports concurrently, and writes each port's
video/audio to named FIFOs (/dev/shm/deltacast/video-<N>.fifo,
/dev/shm/deltacast/audio-<N>.fifo). Capture sidecars read from those
FIFOs directly — no board handle, no race, no flock.

Changes:
  services/capture/deltacast-bridge/main.c
    - Complete rewrite: --ports csv arg, board opened once, one
      video+audio thread pair per port, FIFO paths per port, format
      JSON emitted per port on signal lock, SIGTERM clean shutdown.
    - flock/serialize logic removed (no longer needed).
    - --port single-port compat alias retained.
  services/capture/deltacast-bridge/CMakeLists.txt
    - Rename target deltacast-capture -> deltacast-bridge.
    - POST_BUILD symlink deltacast-capture -> deltacast-bridge for compat.
  services/capture/src/capture-manager.js
    - deltacast _buildInputArgs: remove bridge spawn; wait up to 30s
      for FIFOs to exist (bridge may be starting); return rawvideo +
      s16le FIFO inputArgs. bridgeProcess=null.
    - audioMap: keyed on sourceType instead of bridgeProcess (both
      inputs are always present for deltacast).
    - Remove readFirstStderrLine helper (no longer needed).
    - Remove bridgeProcess.stdout.pipe / processes.bridge stop signal.
  services/node-agent/index.js
    - Add import spawn for bridge daemon management.
    - Add startDeltacastBridge / stopDeltacastBridge: host-process
      lifecycle for the shared bridge, ref-counted by sidecar count.
    - handleSidecarStart: on deltacast, increment counter + start bridge;
      decrement on container create/start failure.
    - handleSidecarStop: decrement counter; stop bridge when last sidecar.
    - _containerSourceType map tracks containerId->sourceType for stop.
    - Old acquireDcLock mutex retained but no longer called.
2026-06-02 00:21:52 +00:00
Zac
bf7189218a fix(web-ui): remove Capture nav item, add Playout testing warning
Remove 'Capture' from the Operations nav section in shell.jsx — users
configure recorders via the Recorders page; the Capture route/component
is left intact for any remaining references.

Also remove 'capture' from the ingest open-group list (it was listed
as an ingest child despite living in Operations, now moot).

Add a prominent amber warning banner at the top of the Playout page
body (screens-playout.jsx) to make clear the feature is in testing and
not ready for production use.

No cherry-pick from fix/library-and-signal-indicator — all commits on
that branch are already present on main.
2026-06-02 00:17:04 +00:00
cad1e52c38 fix(deltacast-bridge): reset signal-wait deadline AFTER acquiring flock
The signal timeout deadline was set at process start before waiting for
the flock. Bridges queued behind earlier ports waited minutes for the
lock, then found their 30s signal deadline had already expired before
they even opened the board, causing false "no signal" failures on ports
that have live signal.

Fix: move clock_gettime deadline initialisation to AFTER flock acquired
and board opened, so the full sig_timeout is always available for signal
detection regardless of queue wait time.
2026-06-01 19:32:01 -04:00
Zac Gaetano
6979f07307 fix(capture-manager): raise readFirstStderrLine timeout 35s->300s for flock queue
With 8 deltacast bridges serializing via flock (each holding the lock
for ~35s during signal wait + settle), the last bridge in queue waits
~280s before getting the lock. The 35s readFirstStderrLine timeout was
firing before those bridges could even open the board, causing them to
fail silently while the bridge was still queued. 300s (5min) covers
8 bridges * 35s each with margin.
2026-06-01 23:23:07 +00:00
Zac Gaetano
eba8e94887 fix(capture-manager): readFirstStderrLine skips non-JSON bridge log lines
The flock-based board serialization in deltacast-bridge emits [board] log
lines to stderr before the JSON format line. readFirstStderrLine was
failing on the first non-JSON line. Now loops over complete lines,
skips any not starting with {, and waits for the actual JSON.
2026-06-01 23:09:21 +00:00
a06b5ed304 fix(capture-manager): readFirstStderrLine skips non-JSON bridge log lines
The deltacast bridge now emits [board] log lines before the format JSON
(while waiting for flock). readFirstStderrLine was parsing the first line
only and failing with 'invalid JSON'. Now it accumulates all lines and
skips any that do not start with '{', continuing to wait for the JSON
format line. Error lines ({\"error\":...}) still reject immediately.
2026-06-01 19:07:15 -04:00
7d704d3af3 fix(deltacast-bridge): serialize VHD_OpenBoardHandle via flock to prevent BufMngr wedge
Concurrent VHD_OpenBoardHandle calls from multiple capture sidecars
trigger delta_x300 BufMngr.c:781 array-index-out-of-bounds, wedging all
RX channels until the module is reloaded. The node-agent stagger only
delays container start — the bridge binary starts ~2s later and can still
race. This fix acquires an exclusive flock on /dev/shm/deltacast/bridge.lock
before VHD_OpenBoardHandle and holds it until signal lock succeeds (then
adds a 4s settle before releasing so the board's buffer queues stabilize).
Lock is released on signal failure too so the next bridge is never
permanently blocked. All 8 channels can now start safely by serializing
through the same lock file mounted into every sidecar.
2026-06-01 18:54:00 -04:00
b324878db9 fix(node-agent): serialize deltacast sidecar opens to prevent BufMngr wedge
Simultaneous VHD_OpenBoardHandle calls from 8 sidecars trigger a kernel
array-index-out-of-bounds in delta_x300 BufMngr.c:781 that wedges all
RX channels. Serialize deltacast-only sidecar starts through a
promise-chain mutex with a configurable settle delay
(DELTACAST_START_STAGGER_MS, default 3500ms). All other source types
(SDI, SRT, RTMP) are unaffected — they bypass the mutex entirely.
2026-06-01 18:47:55 -04:00
Zac Gaetano
9809cdd13e fix(capture): lower GROWING_DEFAULT_BITRATE 50M→25M
25 Mbps is sufficient for XDCAM HD422 1080i/1080p at broadcast quality
and halves storage use. Operators can still override via recording_video_bitrate.
2026-06-01 22:42:07 +00:00
75f265534e fix(web-ui): default recorder bitrate 60→25 Mbps, HEVC preset 60→25 2026-06-01 18:41:36 -04:00
8b8a19c465 fix(node-agent): serialize deltacast sidecar opens to prevent BufMngr wedge
Simultaneous VHD_OpenBoardHandle calls from 8 sidecars trips a kernel
array-index-out-of-bounds in BufMngr.c:781 (delta_x300 v6.34.1). Fix:
a process-wide promise-chain mutex gates deltacast sidecar starts so only
one board open is in flight at a time, with a configurable settle delay
(DELTACAST_START_STAGGER_MS, default 3500ms) before releasing the lock.
SDI, SRT, RTMP and all other source types are unaffected.
2026-06-01 18:41:35 -04:00
9adcae0329 fix(capture): connect deltacast growing-master filtergraph (split output 1 unconnected)
The growing-master ffmpeg orchestrator declared split=2[vhi][vlo] but only
consumed [vlo] inside the `if (hlsDir)` block. For deltacast sources the
caller passed hlsDir=null (the ternary only matched sourceType==='sdi'), so
[vlo] was left unconnected → ffmpeg aborted with "Filter 'split' has output 1
(vlo) unconnected" / "Error binding filtergraph inputs/outputs" → 0 frames →
no HLS → "playback failed" on all deltacast previews.

Fix:
- Pass sdiHlsDir for deltacast as well as sdi (deltacast also produces the
  2nd-output HLS preview from the single SDI read).
- Make the orchestrator filter_complex conditional: split=2[vhi][vlo] when an
  HLS dir is present, split=1[vhi] (master only) otherwise, so no split output
  is ever orphaned regardless of source type.

Restores deltacast growing-master capture (master MXF + HLS preview). No poster
tap (the incomplete recorder-thumbnails poster on the deploy node added an
mjpeg output that destabilised the shared ffmpeg; tracked separately on the
feature/recorder-thumbnails branch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 16:10:21 -04:00
63654ea0ed feat(deltacast): persist selected channel as source_config.port
The Deltacast picker's selected index is the capture channel on the single
board. Write it into source_config.port (in addition to device_index) so the
capture sidecar maps "pick channel N" to the bridge's --port N. device_index is
retained for backward-compatible display/fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 15:00:48 -04:00
0eac34529b feat(deltacast): wire source_config.port/board into capture start
Parse the recorder's SOURCE_CONFIG JSON in the bootstrap and pass the deltacast
capture channel (`port`) and optional `board` into captureManager.start(), so a
recorder can select which of the board's 8 channels to capture.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 14:58:58 -04:00
d6e515e1a8 fix(capture): map deltacast audio from input 1; per-recorder channel/port; fix bridge stdin pipe
- Audio map: the deltacast bridge delivers audio on a separate FIFO wired as
  ffmpeg input 1, so the finalized master + HLS preview (and the growing
  orchestrator) now map audio via `audioMap` (1🅰️0? for deltacast, 0🅰️0? for
  DeckLink SDI / network) instead of an unconditional 0🅰️0?. Without this the
  deltacast master/preview carried no audio.
- Channel/port: spawn the bridge with --device = board index (default 0) and
  --port = source_config.port (falling back to the device index), so a recorder
  can capture from any of the board's 8 channels. Adds `port`/`board` params to
  start() and _buildInputArgs().
- Bridge stdin: the finalized-master ffmpeg reads the bridge's raw video from
  pipe:0, so its stdin must be 'pipe' when a bridge is present (was 'ignore',
  which made hiresProcess.stdin null and threw "Cannot read properties of null
  (reading 'on')" at bridgeProcess.stdout.pipe(...)).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 14:57:53 -04:00
3d3c8c48de fix(deltacast): always open audio FIFO writer (silence fallback) to stop ffmpeg input deadlock
ffmpeg opens all inputs before processing; input 1 is the audio FIFO. The
bridge previously opened the FIFO writer only after VHD_OpenStreamHandle +
VHD_StartStream succeeded, returning early on failure / no embedded audio and
never opening the FIFO -> ffmpeg blocked forever on input 1 -> 0 fps and an
empty HLS preview. Now the FIFO writer is opened unconditionally and first,
and the audio thread feeds a continuous, wall-clock-paced s16le stereo stream
(real samples when available, otherwise silence). SIGPIPE is ignored so a
dying ffmpeg returns EPIPE instead of killing the bridge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 14:52:24 -04:00
b65ce5b0b7 fix(web-ui): recorder modal body scrolls (min-height:0) so codec/destination aren't clipped at zoom 2026-06-01 13:49:10 -04:00
12d76edc42 feat(deltacast): support 8 RX channels (ports 0-7) on DELTA-12G-elp-h 2026-06-01 13:31:06 -04:00
551377e4c9 fix(node-agent): mount /dev/shm/deltacast into capture sidecars so VideoMaster SDK detects the Deltacast board 2026-06-01 12:56:12 -04:00
b875376887 Merge remote-tracking branch 'wilddragon/main' 2026-06-01 09:22:02 -04:00
252aa713d4 fix(capture): drop dur-patch; rdd9 self-maintains growing Duration
On-node empirical testing of this bmx v1.6 build showed that raw2bmx's
rdd9 writer with --part already maintains a live, correct header Duration
as the file grows: ffprobe reads a growing duration mid-write (e.g. 2.04s
of a 10s clip while still recording), and the structural-metadata
Duration fields (tags 02020008 / 30020008) hold the real frame count
(0x33 = 51), not -1.

The dur-patch.py added in the previous commit searched the header for
Duration=-1 (0xFF*8) and found 0 fields on rdd9 ("[dur-patch] 0 Duration
fields"), so it was a no-op. Worse, opening the MXF r+b to patch it while
raw2bmx appends over CIFS is a concurrency hazard. Remove it entirely and
rely on raw2bmx's native growing Duration. rdd9 + --index-follows remains
the Premiere-recommended growing flavour (Sony XDCAM essence, index in the
essence partition).

Verified on-node (ffprobe/byte-probe). Live edit-while-record in Premiere
itself still requires user confirmation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 09:07:28 -04:00
068e2eaa87 fix(node-agent): NODE_NAME override to prevent cloned-VM hostname collision
The cluster heartbeat upserts cluster_nodes ON CONFLICT (hostname), so two
machines reporting the same os.hostname() clobber each other's row. A cloned
capture VM whose /etc/hostname was "zampp1" (same as the primary) caused its
4 DeckLink cards to land on the primary's row, then get overwritten by the
primary's cardless heartbeat — so the New Recorder modal showed "No SDI
devices auto-detected" despite healthy hardware.

- node-agent now reports process.env.NODE_NAME || os.hostname() as its cluster
  identity, so node identity is explicit and collision-proof.
- docker-compose.worker.yml exposes NODE_NAME to the container.
- onboard-node.sh always writes NODE_NAME to the node .env (defaults to the OS
  hostname) so future onboarding pins identity even on cloned images.

Live remediation already applied to the zampp2 capture node: compose hostname
pinned to zampp2 and its node token rebound to zampp2; DB now reports bmd=4
for zampp2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 09:00:34 -04:00
e3be8745d3 fix(capture): restore frame counting + growing MXF for Premiere
The previous sed/python in-place edits on the node broke capture: the
hires stderr parser was written with literal 0x08 BACKSPACE bytes instead
of regex word boundaries, so it never matched ffmpeg output.
framesReceived stayed 0, the shutdown handler saw "no frames" and marked
every asset as an error even though video was captured. The ffmpeg base
args had also been changed to -progress pipe:2, whose key=value output
puts frame= and fps= on separate lines and does not match a combined
regex.

Fixes:
- Parser: single robust regex matching ffmpeg's classic -stats line
  (frame= and fps= together). No backspace bytes, no word boundaries.
- ffmpeg base args back to -stats (drop -progress pipe:2).

Growing-file (Premiere edit-while-record), per bmx thread 87ac5750 and
Drastic/Softron edit-while-ingest docs:
- raw2bmx clip type op1a -> rdd9 (Sony XDCAM / RDD-9, the flavour Premiere
  reads while growing) with --index-follows so the IndexTableSegment is
  written in the same partition as the essence it indexes (lets a reader
  re-scanning body partitions seek toward the record head). NOT --avid-gf
  (Avid OP-Atom, Media-Composer-only, needs a companion AAF).
- dur-patch.py: overwrite header Duration=-1 to 0 immediately at
  clip-open (Premiere rejects -1 on import), then track the live frame
  count every 3s from the last body partition IndexTableSegment. Shipped
  as services/capture/dur-patch.py (/app/dur-patch.py in the image).

Deployed to wild-dragon-capture:latest on zampp2 via overlay build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 09:00:23 -04:00
7d408035ac fix(capture): fix VHD_CORE_SP_BUFFERQUEUE_DEPTH constant name for SDK 6.34 2026-06-01 08:59:06 -04:00
884c8829a0 merge: integrate remote changes + deltacast bridge capture
Merges remote main (raw2bmx growing-file, playout, auth) with local
Deltacast SDI capture implementation (bridge binary, _buildInputArgs,
start/stop lifecycle).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 08:41:05 -04:00
962c7c8f20 fix(capture): log SDK errors in video loop, warn on unsupported port index 2026-06-01 08:00:55 -04:00
b46abc9b1a fix(capture): shell injection, stale FIFO, stderr listener, bridge exit handler 2026-06-01 08:00:06 -04:00
adfbeac217 feat(capture): accept deltacast as valid source_type in /start handler 2026-06-01 07:56:43 -04:00
d4924b044f feat(capture): wire bridge process lifecycle into start/stop for deltacast 2026-06-01 07:55:34 -04:00