fix(worker): conform — preserve audio + map ProRes/DNxHR codecs
Three cooperating bugs left the rendered output silent and in the wrong codec: 1. executor.js trimSegment used `-frames:v` with no audio mapping. ffmpeg dropped the audio track on each segment before they reached the concat step. Add `-c:a copy -shortest` so each segment carries its original audio. 2. conform.js audioFlag was `audio === 'include' ? aac : -an`. The panel's v2.2.1 defaults send `audio: 'broadcast'`, which didn't match 'include' → `-an` explicitly stripped audio at the encode step. Switch to the opposite default: only an explicit 'none' or 'off' disables audio; everything else gets AAC 320k @ 48kHz. 3. conform.js video codec map only matched `codec === 'prores'`. The panel sends `'prores_hq'` (and the conform slide panel can send `'prores_4444'` / `'dnxhr_hq'`). All of those fell through to libx264 and silently rendered H.264 instead of the requested codec. Add a real codec map with the right prores_ks profiles (3=HQ, 4=4444) and DNxHR. Skip -crf for ProRes since the profile encodes quality. The asset-row metadata's `codec` column is normalised the same way so the new asset record matches what was actually written. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
56d7479a35
commit
6412b5c252
2 changed files with 49 additions and 6 deletions
|
|
@ -185,10 +185,16 @@ export const trimSegment = async (inputPath, outputPath, inPoint, outPoint) => {
|
|||
const inSeconds = inPoint / fps;
|
||||
const frameCount = outPoint - inPoint;
|
||||
|
||||
// `-frames:v` bounds the video output but says nothing about audio. Without
|
||||
// -c:a copy + -shortest the audio track is either dropped or runs past
|
||||
// the trimmed video. We need audio in the final concat output, so passthrough
|
||||
// the audio codec and stop on the shortest stream.
|
||||
const args = [
|
||||
'-ss', inSeconds.toString(),
|
||||
'-i', inputPath,
|
||||
'-frames:v', frameCount.toString(),
|
||||
'-c:a', 'copy',
|
||||
'-shortest',
|
||||
'-start_number', '0',
|
||||
'-y',
|
||||
outputPath,
|
||||
|
|
|
|||
|
|
@ -233,15 +233,47 @@ export const conformWorker = async (job) => {
|
|||
await job.updateProgress(70);
|
||||
console.log(`[conform] Concatenating segments for job ${jobId}`);
|
||||
|
||||
// Use re-encode instead of stream copy for consistent output
|
||||
const audioFlag = audio === 'include' ? ['-c:a', 'aac'] : ['-an'];
|
||||
// Audio: be permissive. Anything that isn't an explicit 'none' should
|
||||
// get encoded — the panel sends 'broadcast' (default), 'include' is the
|
||||
// legacy value, and there's no reason to silently drop audio for any
|
||||
// other label. 320k AAC is a safe broadcast-quality default in mp4.
|
||||
const audioFlag = (audio === 'none' || audio === 'off')
|
||||
? ['-an']
|
||||
: ['-c:a', 'aac', '-b:a', '320k', '-ar', '48000'];
|
||||
|
||||
// Codec map. The panel sends 'prores_hq' / 'prores_4444' / 'h264' / 'h265'
|
||||
// / 'dnxhr_hq'; old EDL callers send 'prores' / 'h265' / 'h264'. Match
|
||||
// both. prores_ks profiles: 0=proxy 1=lt 2=std 3=hq 4=4444.
|
||||
let videoCodec, profileFlag = [];
|
||||
if (codec === 'prores_hq' || codec === 'prores') {
|
||||
videoCodec = 'prores_ks'; profileFlag = ['-profile:v', '3'];
|
||||
} else if (codec === 'prores_4444') {
|
||||
videoCodec = 'prores_ks'; profileFlag = ['-profile:v', '4'];
|
||||
} else if (codec === 'h265' || codec === 'hevc') {
|
||||
videoCodec = 'libx265';
|
||||
} else if (codec === 'dnxhr_hq') {
|
||||
videoCodec = 'dnxhd'; profileFlag = ['-profile:v', 'dnxhr_hq'];
|
||||
} else {
|
||||
videoCodec = 'libx264';
|
||||
}
|
||||
|
||||
// prores_ks ignores -crf and uses -preset differently; libx264/x265 use
|
||||
// crf-based quality. Branch the encode args.
|
||||
const isProRes = videoCodec === 'prores_ks';
|
||||
const qualityArgs = isProRes
|
||||
? [] // ProRes profile already encodes the quality target
|
||||
: [
|
||||
'-preset', quality === 'high' ? 'slow' : quality === 'broadcast' ? 'veryslow' : 'fast',
|
||||
'-crf', quality === 'broadcast' ? '18' : quality === 'high' ? '23' : '28',
|
||||
];
|
||||
|
||||
await runFFmpeg([
|
||||
'-f', 'concat',
|
||||
'-safe', '0',
|
||||
'-i', segmentListPath,
|
||||
'-c:v', codec === 'prores' ? 'prores_ks' : codec === 'h265' ? 'libx265' : 'libx264',
|
||||
'-preset', quality === 'high' ? 'slow' : quality === 'broadcast' ? 'veryslow' : 'fast',
|
||||
'-crf', quality === 'broadcast' ? '18' : quality === 'high' ? '23' : '28',
|
||||
'-c:v', videoCodec,
|
||||
...profileFlag,
|
||||
...qualityArgs,
|
||||
...audioFlag,
|
||||
'-y', outputPath,
|
||||
]);
|
||||
|
|
@ -260,7 +292,12 @@ export const conformWorker = async (job) => {
|
|||
`conformed-${seqName.replace(/[^a-z0-9]/gi, '_')}.mp4`,
|
||||
`Conformed: ${seqName}`,
|
||||
outputKey,
|
||||
codec === 'prores' ? 'prores' : codec === 'h265' ? 'hevc' : 'h264',
|
||||
// Normalise the panel's codec id into the canonical name we store on
|
||||
// the asset row. Keep aligned with the encode branch above.
|
||||
(codec === 'prores_hq' || codec === 'prores_4444' || codec === 'prores') ? 'prores'
|
||||
: (codec === 'h265' || codec === 'hevc') ? 'hevc'
|
||||
: (codec === 'dnxhr_hq') ? 'dnxhd'
|
||||
: 'h264',
|
||||
resolution !== 'match' ? resolution : '1920x1080',
|
||||
seqFps,
|
||||
null,
|
||||
|
|
|
|||
Loading…
Reference in a new issue