mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-16 01:55:51 -07:00
fix(media): tolerate slow MKV audio extraction (#195)
This commit is contained in:
@@ -4,13 +4,18 @@ import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import { buildAnimatedImageVideoFilter, MediaGenerator } from './media-generator';
|
||||
import {
|
||||
AUDIO_GENERATION_TIMEOUT_MS,
|
||||
buildAnimatedImageVideoFilter,
|
||||
MediaGenerator,
|
||||
type MediaGeneratorOptions,
|
||||
} from './media-generator';
|
||||
|
||||
async function withStubbedFfmpeg(
|
||||
run: (generator: MediaGenerator, argsPath: string) => Promise<void>,
|
||||
options: {
|
||||
logDebug?: (message: string) => void;
|
||||
now?: () => number;
|
||||
options: MediaGeneratorOptions = {},
|
||||
stubOptions: {
|
||||
skipOutput?: boolean;
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-media-generator-test-'));
|
||||
@@ -31,7 +36,9 @@ async function withStubbedFfmpeg(
|
||||
'}',
|
||||
"fs.writeFileSync(process.env.SUBMINER_TEST_FFMPEG_ARGS, JSON.stringify(args), 'utf8');",
|
||||
'const outputPath = args.at(-1);',
|
||||
"fs.writeFileSync(outputPath, 'avif', 'utf8');",
|
||||
"if (process.env.SUBMINER_TEST_FFMPEG_SKIP_OUTPUT !== '1') {",
|
||||
" fs.writeFileSync(outputPath, 'avif', 'utf8');",
|
||||
'}',
|
||||
].join('\n'),
|
||||
'utf8',
|
||||
);
|
||||
@@ -46,8 +53,14 @@ async function withStubbedFfmpeg(
|
||||
|
||||
const originalPath = process.env.PATH;
|
||||
const originalArgsPath = process.env.SUBMINER_TEST_FFMPEG_ARGS;
|
||||
const originalSkipOutput = process.env.SUBMINER_TEST_FFMPEG_SKIP_OUTPUT;
|
||||
process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ''}`;
|
||||
process.env.SUBMINER_TEST_FFMPEG_ARGS = argsPath;
|
||||
if (stubOptions.skipOutput) {
|
||||
process.env.SUBMINER_TEST_FFMPEG_SKIP_OUTPUT = '1';
|
||||
} else {
|
||||
delete process.env.SUBMINER_TEST_FFMPEG_SKIP_OUTPUT;
|
||||
}
|
||||
const generator = new MediaGenerator(tempDir, options);
|
||||
|
||||
try {
|
||||
@@ -60,6 +73,11 @@ async function withStubbedFfmpeg(
|
||||
} else {
|
||||
process.env.SUBMINER_TEST_FFMPEG_ARGS = originalArgsPath;
|
||||
}
|
||||
if (originalSkipOutput === undefined) {
|
||||
delete process.env.SUBMINER_TEST_FFMPEG_SKIP_OUTPUT;
|
||||
} else {
|
||||
process.env.SUBMINER_TEST_FFMPEG_SKIP_OUTPUT = originalSkipOutput;
|
||||
}
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
@@ -316,6 +334,65 @@ test('generateAudio keeps explicit audio stream maps for normal media paths', as
|
||||
});
|
||||
});
|
||||
|
||||
test('generateAudio bounds probing when the selected local audio stream is known', async () => {
|
||||
await withStubbedFfmpeg(async (generator, argsPath) => {
|
||||
await generator.generateAudio('/video.mkv', 10, 12, 0, 2);
|
||||
|
||||
const args = readFfmpegArgs(argsPath);
|
||||
const inputIndex = args.indexOf('-i');
|
||||
assert.ok(args.indexOf('-probesize') > -1);
|
||||
assert.ok(args.indexOf('-probesize') < inputIndex);
|
||||
assert.equal(args[args.indexOf('-probesize') + 1], '32768');
|
||||
assert.ok(args.indexOf('-analyzeduration') < inputIndex);
|
||||
assert.equal(args[args.indexOf('-analyzeduration') + 1], '0');
|
||||
});
|
||||
});
|
||||
|
||||
test('generateAudio retains normal probing for non-Matroska local media', async () => {
|
||||
await withStubbedFfmpeg(async (generator, argsPath) => {
|
||||
await generator.generateAudio('/video.mp4', 10, 12, 0, 2);
|
||||
|
||||
const args = readFfmpegArgs(argsPath);
|
||||
assert.equal(args.includes('-probesize'), false);
|
||||
assert.equal(args.includes('-analyzeduration'), false);
|
||||
});
|
||||
});
|
||||
|
||||
test('generateAudio retains a two-minute extraction timeout', async () => {
|
||||
let observedTimeout: number | undefined;
|
||||
|
||||
await withStubbedFfmpeg(
|
||||
async (generator) => {
|
||||
await generator.generateAudio('/video.mp4', 10, 12);
|
||||
},
|
||||
{
|
||||
execFile: (_file, args, options, callback) => {
|
||||
observedTimeout = options.timeout;
|
||||
const outputPath = args.at(-1);
|
||||
assert.ok(outputPath);
|
||||
fs.writeFileSync(outputPath, 'mp3', 'utf8');
|
||||
queueMicrotask(() => callback(null));
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(AUDIO_GENERATION_TIMEOUT_MS, 120_000);
|
||||
assert.equal(observedTimeout, AUDIO_GENERATION_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
test('generateAudio reports when ffmpeg exits without creating output', async () => {
|
||||
await withStubbedFfmpeg(
|
||||
async (generator) => {
|
||||
await assert.rejects(
|
||||
generator.generateAudio('/video.mp4', 10, 12),
|
||||
/FFmpeg audio generation failed: FFmpeg exited without creating an output file/,
|
||||
);
|
||||
},
|
||||
{},
|
||||
{ skipOutput: true },
|
||||
);
|
||||
});
|
||||
|
||||
test('generateAudio debug-logs cached input and completion timing', async () => {
|
||||
const logs: string[] = [];
|
||||
const times = [1000, 1052];
|
||||
|
||||
Reference in New Issue
Block a user