diff --git a/services/capture/src/capture-manager.js b/services/capture/src/capture-manager.js index 6d858ff..98180c1 100644 --- a/services/capture/src/capture-manager.js +++ b/services/capture/src/capture-manager.js @@ -149,10 +149,9 @@ class CaptureManager { const out = execSync('ffmpeg -hide_banner -sources decklink 2>&1', { encoding: 'utf-8', timeout: 5000 }); const names = []; for (const line of out.split('\n')) { - // Device name lines are indented (start with one or more spaces). - // Header/blank lines are skipped. - const m = line.match(/^ {2,}(.+?)\s*$/); - if (m && m[1]) names.push(m[1]); + // DeckLink source lines: " 81:76669a80:00000000 [DeckLink Duo (1)] (none)" + const m = line.match(/^\s+[0-9a-f:]+\s+\[([^\]]+)\]/); + if (m) names.push(m[1]); } if (names[idx]) { deckLinkName = names[idx]; @@ -230,12 +229,16 @@ class CaptureManager { catch (err) { console.error('[capture] could not create growing dir:', err.message); } } - // Network sources cannot be opened by two FFmpeg processes simultaneously - // (one socket = one consumer). For SRT/RTMP the BullMQ worker generates - // the proxy after the recording stops. - const proxyKey = (sourceType === 'sdi' && proxyEnabled) - ? `projects/${projectId}/proxies/${clipName}.${proxyExt}` - : null; + // DeckLink hardware does NOT support concurrent capture from the same port. + // Opening a second ffmpeg process on the same DeckLink input while the first + // is already capturing causes "Cannot Autodetect input stream or No signal" + // on the second process — making the proxy empty and potentially crashing the + // container before the hires upload completes. + // + // Treat SDI the same as SRT/RTMP: set proxyKey=null here and let the BullMQ + // worker generate the proxy from the hires master after the recording stops. + // The stop handler sets needsProxy=true so the worker picks it up. + const proxyKey = null; const startedAt = new Date().toISOString(); @@ -318,39 +321,8 @@ class CaptureManager { } }); - // SDI only: spawn a second ffmpeg for the proxy. - // DeckLink cards allow concurrent reads; network sockets do not. - if (!isNetwork && proxyEnabled) { - const proxyCodecArgs = buildEncodeArgs({ - codec: proxyVideoCodec, - videoBitrate: proxyVideoBitrate, - framerate: proxyFramerate, - audioCodec: proxyAudioCodec, - audioBitrate: proxyAudioBitrate, - audioChannels: proxyAudioChannels, - container: proxyContainer, - isNetwork: false, - isProxy: true, - }); - - console.log('[capture] proxy ffmpeg args:', proxyCodecArgs.join(' ')); - - const proxyProcess = spawn('ffmpeg', [ - ...inputArgs, - ...sdiFilterArgs, - ...proxyCodecArgs, - '-movflags', '+frag_keyframe+empty_moov', - 'pipe:1', - ], { stdio: ['ignore', 'pipe', 'pipe'] }); - - const proxyUpload = createUploadStream(S3_BUCKET, proxyKey, proxyProcess.stdout); - processes.proxy = proxyProcess; - uploads.proxy = proxyUpload; - - proxyProcess.stderr.on('data', (data) => { - console.error(`[PROXY] ${data}`); - }); - } + // Proxy is generated after stop by the BullMQ worker (same as SRT/RTMP). + // DeckLink hardware does not support two concurrent readers on the same port. this.state.recording = true; this.state.sessionId = sessionId; diff --git a/services/capture/src/routes/capture.js b/services/capture/src/routes/capture.js index 394dd9c..f26f108 100644 --- a/services/capture/src/routes/capture.js +++ b/services/capture/src/routes/capture.js @@ -96,17 +96,13 @@ router.get('/devices', (req, res) => { } // Parse ffmpeg output for DeckLink device names. - // ffmpeg -sources decklink output: - // Auto-detected sources for decklink: - // DeckLink Duo 2 - // DeckLink Duo 2 (2) - // Device name lines are indented (2+ leading spaces); header/blank lines are not. + // DeckLink source lines: " 81:76669a80:00000000 [DeckLink Duo (1)] (none)" const lines = output.split('\n'); let deviceIndex = 0; for (const line of lines) { - const match = line.match(/^ {2,}(.+?)\s*$/); - if (match && match[1]) { + const match = line.match(/^\s+[0-9a-f:]+\s+\[([^\]]+)\]/); + if (match) { devices.push({ index: deviceIndex, name: match[1], @@ -144,8 +140,8 @@ router.post('/probe', async (req, res) => { const raw = execSync('ffmpeg -hide_banner -sources decklink 2>&1', { encoding: 'utf-8', timeout: 5000 }); const devices = []; for (const line of raw.split('\n')) { - const m = line.match(/^ {2,}(.+?)\s*$/); - if (m && m[1]) devices.push(m[1]); + const m = line.match(/^\s+[0-9a-f:]+\s+\[([^\]]+)\]/); + if (m) devices.push(m[1]); } return res.json({ ok: true, source_type, devices }); } catch (err) {