2026-05-13 17:24:10 +02:00
|
|
|
"use strict";
|
|
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
exports.muxAudioDescription = muxAudioDescription;
|
2026-05-15 04:10:06 +02:00
|
|
|
exports.muxMixedAudioDescription = muxMixedAudioDescription;
|
2026-05-13 17:24:10 +02:00
|
|
|
const child_process_1 = require("child_process");
|
|
|
|
|
const path_1 = __importDefault(require("path"));
|
2026-05-15 04:10:06 +02:00
|
|
|
const fs_1 = __importDefault(require("fs"));
|
2026-05-13 17:24:10 +02:00
|
|
|
function muxAudioDescription(videoPath, audioPath, outputPath) {
|
2026-05-15 04:10:06 +02:00
|
|
|
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)'}`);
|
|
|
|
|
}
|
2026-05-13 17:24:10 +02:00
|
|
|
}
|
|
|
|
|
//# sourceMappingURL=muxer.js.map
|