Fix muxing
This commit is contained in:
90
dist/server/services/muxer.js
vendored
90
dist/server/services/muxer.js
vendored
@@ -4,26 +4,80 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.muxAudioDescription = muxAudioDescription;
|
||||
exports.muxMixedAudioDescription = muxMixedAudioDescription;
|
||||
const child_process_1 = require("child_process");
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
function muxAudioDescription(videoPath, audioPath, outputPath) {
|
||||
const ext = path_1.default.extname(outputPath).toLowerCase();
|
||||
const isMkv = ext === '.mkv';
|
||||
const cmd = [
|
||||
'ffmpeg -v error',
|
||||
`-i "${videoPath}"`,
|
||||
`-i "${audioPath}"`,
|
||||
'-map 0:v',
|
||||
'-map 0:a?',
|
||||
'-map 1:a',
|
||||
'-c:v copy',
|
||||
'-c:a copy',
|
||||
isMkv
|
||||
? '-metadata:s:a:1 title="Audio Description"'
|
||||
: '-metadata:s:a:1 title="Audio Description"',
|
||||
`"${outputPath}"`,
|
||||
'-y'
|
||||
].join(' ');
|
||||
(0, child_process_1.execSync)(cmd);
|
||||
if (!fs_1.default.existsSync(videoPath)) {
|
||||
throw new Error(`mux: video not found: ${videoPath}`);
|
||||
}
|
||||
if (!fs_1.default.existsSync(audioPath)) {
|
||||
throw new Error(`mux: audio not found: ${audioPath}`);
|
||||
}
|
||||
fs_1.default.mkdirSync(path_1.default.dirname(outputPath), { recursive: true });
|
||||
// Argv form — no shell, no quoting issues, and -y is a global option (placed
|
||||
// up front, not after the output). Stderr is captured so failures aren't
|
||||
// silent.
|
||||
const args = [
|
||||
'-y',
|
||||
'-v', 'error',
|
||||
'-i', videoPath,
|
||||
'-i', audioPath,
|
||||
'-map', '0:v',
|
||||
'-map', '0:a?',
|
||||
'-map', '1:a',
|
||||
'-c:v', 'copy',
|
||||
'-c:a', 'copy',
|
||||
'-metadata:s:a:1', 'title=Audio Description',
|
||||
'-disposition:a:1', 'visual_impaired',
|
||||
outputPath,
|
||||
];
|
||||
const result = (0, child_process_1.spawnSync)('ffmpeg', args, { shell: false, encoding: 'utf-8' });
|
||||
if (result.error) {
|
||||
throw new Error(`mux: ffmpeg failed to start: ${result.error.message}`);
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
const tail = (result.stderr || '').trim().split('\n').slice(-5).join(' | ');
|
||||
throw new Error(`mux: ffmpeg exited ${result.status}: ${tail || '(no stderr)'}`);
|
||||
}
|
||||
}
|
||||
function muxMixedAudioDescription(videoPath, audioPath, outputPath) {
|
||||
if (!fs_1.default.existsSync(videoPath)) {
|
||||
throw new Error(`mux: video not found: ${videoPath}`);
|
||||
}
|
||||
if (!fs_1.default.existsSync(audioPath)) {
|
||||
throw new Error(`mux: audio not found: ${audioPath}`);
|
||||
}
|
||||
fs_1.default.mkdirSync(path_1.default.dirname(outputPath), { recursive: true });
|
||||
// Sidechain-ducked mix: original audio dips when the AD track is speaking,
|
||||
// then both are summed into a single output audio stream. The AD track is
|
||||
// already a full-length file that is silent between description segments
|
||||
// (built by combineAudioSegments), so asplit gives us one copy to drive the
|
||||
// sidechain detector and another to mix in on top.
|
||||
const filterGraph = '[1:a]asplit=2[ad_mix][ad_sc];' +
|
||||
'[0:a][ad_sc]sidechaincompress=threshold=0.03:ratio=20:attack=5:release=300:level_sc=2[ducked];' +
|
||||
'[ducked][ad_mix]amix=inputs=2:duration=first:dropout_transition=0:normalize=0[aout]';
|
||||
const args = [
|
||||
'-y',
|
||||
'-v', 'error',
|
||||
'-i', videoPath,
|
||||
'-i', audioPath,
|
||||
'-filter_complex', filterGraph,
|
||||
'-map', '0:v',
|
||||
'-map', '[aout]',
|
||||
'-c:v', 'copy',
|
||||
'-c:a', 'aac',
|
||||
'-b:a', '192k',
|
||||
outputPath,
|
||||
];
|
||||
const result = (0, child_process_1.spawnSync)('ffmpeg', args, { shell: false, encoding: 'utf-8' });
|
||||
if (result.error) {
|
||||
throw new Error(`mux: ffmpeg failed to start: ${result.error.message}`);
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
const tail = (result.stderr || '').trim().split('\n').slice(-5).join(' | ');
|
||||
throw new Error(`mux: ffmpeg exited ${result.status}: ${tail || '(no stderr)'}`);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=muxer.js.map
|
||||
Reference in New Issue
Block a user