feat: publish av-tools 0.2.0

This commit is contained in:
2026-07-27 01:04:21 +02:00
parent fcc537b024
commit 0a4548851c
68 changed files with 4130 additions and 605 deletions

522
scripts/opus-regression.mjs Normal file
View File

@@ -0,0 +1,522 @@
import { spawn } from 'node:child_process';
import { createHash } from 'node:crypto';
import { readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const SCRIPT_PATH = fileURLToPath(import.meta.url);
const REPOSITORY_ROOT = path.resolve(path.dirname(SCRIPT_PATH), '..');
const FIXTURE_RELATIVE_PATH = 'tests/fixtures/generated/tone.wav';
const FIXTURE_PATH = path.join(REPOSITORY_ROOT, FIXTURE_RELATIVE_PATH);
const EXPECTED_FIXTURE_BYTES = 32_102;
const EXPECTED_FIXTURE_SHA256 =
'0fd1f54d6be2f1cc7fd778775dd29cf184770d3716cf3394bf03ed54ac264c3c';
const DEFAULT_MODE_TIMEOUT_MILLISECONDS = 60_000;
const RESULT_PREFIX = 'OPUS_RESULT ';
const EVENT_PREFIX = 'OPUS_EVENT ';
const MODES = Object.freeze(['single-thread', 'multithread']);
const argumentsSet = new Set(process.argv.slice(2));
const option = (name) =>
process.argv
.slice(2)
.find((argument) => argument.startsWith(`--${name}=`))
?.slice(name.length + 3);
function boundedInteger(value, fallback, minimum, maximum) {
const parsed = Number(value);
return Number.isSafeInteger(parsed) && parsed >= minimum && parsed <= maximum
? parsed
: fallback;
}
function serializeError(error) {
if (error instanceof Error) {
return {
name: error.name,
message: error.message,
...(error.stack ? { stack: error.stack.split('\n').slice(0, 8) } : {}),
};
}
return { name: 'Error', message: String(error) };
}
function emit(prefix, value) {
process.stdout.write(`${prefix}${JSON.stringify(value)}\n`);
}
function parsePrefixedJsonLines(text, prefix) {
return text
.split(/\r?\n/u)
.filter((line) => line.startsWith(prefix))
.flatMap((line) => {
try {
return [JSON.parse(line.slice(prefix.length))];
} catch {
return [];
}
});
}
function appendBounded(current, chunk, maximumLength = 32_768) {
return `${current}${chunk}`.slice(-maximumLength);
}
function terminateProcessGroup(child) {
if (!child.pid) return;
try {
if (process.platform === 'win32') {
child.kill('SIGKILL');
} else {
process.kill(-child.pid, 'SIGKILL');
}
} catch {
child.kill('SIGKILL');
}
}
async function runIsolatedMode(mode, origin, timeoutMilliseconds) {
const startedAt = performance.now();
const child = spawn(
process.execPath,
[SCRIPT_PATH, '--child', `--mode=${mode}`, `--origin=${origin}`],
{
cwd: REPOSITORY_ROOT,
detached: process.platform !== 'win32',
stdio: ['ignore', 'pipe', 'pipe'],
}
);
let stdout = '';
let stderr = '';
let timedOut = false;
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
stdout = appendBounded(stdout, chunk);
for (const event of parsePrefixedJsonLines(chunk, EVENT_PREFIX)) {
if (event.stage && event.stage !== 'core-log') {
process.stdout.write(`[opus:${mode}] ${event.stage}\n`);
}
}
});
child.stderr.on('data', (chunk) => {
stderr = appendBounded(stderr, chunk);
});
const timeout = setTimeout(() => {
timedOut = true;
terminateProcessGroup(child);
}, timeoutMilliseconds);
const { code, signal } = await new Promise((resolve) => {
child.once('close', (exitCode, exitSignal) => {
resolve({ code: exitCode, signal: exitSignal });
});
});
clearTimeout(timeout);
const elapsedMilliseconds = Math.round(performance.now() - startedAt);
const events = parsePrefixedJsonLines(stdout, EVENT_PREFIX);
const reported = parsePrefixedJsonLines(stdout, RESULT_PREFIX).at(-1);
if (reported) {
return {
...reported,
elapsedMilliseconds,
process: { exitCode: code, signal },
};
}
const coreReady = events.find((event) => event.stage === 'core-ready');
const lastStage = [...events]
.reverse()
.find((event) => event.stage && event.stage !== 'core-log');
const relevantLogs = events
.filter((event) => event.stage === 'core-log')
.slice(-16)
.map((event) => event.message);
return {
requestedMode: mode,
status: 'fail',
failure: timedOut ? 'timeout' : 'child-exited-without-result',
message: timedOut
? `The isolated browser did not finish within ${String(timeoutMilliseconds)} ms.`
: 'The isolated browser exited without a structured result.',
...(coreReady?.actualMode ? { actualMode: coreReady.actualMode } : {}),
...(coreReady?.ffmpegVersion
? { ffmpegVersion: coreReady.ffmpegVersion }
: {}),
...(coreReady?.capabilities
? { capabilities: coreReady.capabilities }
: {}),
...(lastStage?.stage ? { lastStage: lastStage.stage } : {}),
...(relevantLogs.length > 0 ? { relevantLogs } : {}),
...(stderr.trim() ? { stderr: stderr.trim().split('\n').slice(-12) } : {}),
elapsedMilliseconds,
process: { exitCode: code, signal },
};
}
async function fixtureIdentity() {
const [bytes, fixtureStat] = await Promise.all([
readFile(FIXTURE_PATH),
stat(FIXTURE_PATH),
]);
const sha256 = createHash('sha256').update(bytes).digest('hex');
if (
fixtureStat.size !== EXPECTED_FIXTURE_BYTES ||
sha256 !== EXPECTED_FIXTURE_SHA256
) {
throw new Error(
`Opus fixture identity mismatch: expected ${String(EXPECTED_FIXTURE_BYTES)} bytes/${EXPECTED_FIXTURE_SHA256}, received ${String(fixtureStat.size)} bytes/${sha256}.`
);
}
return {
path: FIXTURE_RELATIVE_PATH,
bytes: fixtureStat.size,
sha256,
};
}
async function runMain() {
const timeoutMilliseconds = boundedInteger(
option('timeout-ms') ?? process.env.AV_OPUS_REGRESSION_TIMEOUT_MS,
DEFAULT_MODE_TIMEOUT_MILLISECONDS,
10_000,
10 * 60_000
);
const [fixture, packageText, coreLockText] = await Promise.all([
fixtureIdentity(),
readFile(path.join(REPOSITORY_ROOT, 'package.json'), 'utf8'),
readFile(
path.join(REPOSITORY_ROOT, 'scripts', 'reviewed-ffmpeg-core-lock.json'),
'utf8'
),
]);
const packageManifest = JSON.parse(packageText);
const coreLock = JSON.parse(coreLockText);
const { createServer } = await import('vite');
const server = await createServer({
configFile: path.join(REPOSITORY_ROOT, 'vite.config.ts'),
logLevel: 'warn',
server: {
host: '127.0.0.1',
port: 0,
strictPort: false,
},
});
try {
await server.listen();
const address = server.httpServer?.address();
if (!address || typeof address === 'string') {
throw new Error('Vite did not expose a local TCP address.');
}
const origin = `http://127.0.0.1:${String(address.port)}`;
const modes = [];
for (const mode of MODES) {
modes.push(await runIsolatedMode(mode, origin, timeoutMilliseconds));
}
const report = {
schemaVersion: 2,
recordedAt: new Date().toISOString(),
browser: 'chromium',
wrapperVersion: packageManifest.dependencies['@ffmpeg/ffmpeg'],
coreVersion: coreLock.coreVersion,
coreBuildId: coreLock.buildId,
coreProfile: coreLock.profile,
fixture,
perModeTimeoutMilliseconds: timeoutMilliseconds,
modes,
safe: modes.every((result) => result.status === 'pass'),
};
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
if (argumentsSet.has('--require-pass') && !report.safe) {
process.exitCode = 1;
}
} finally {
await server.close();
}
}
async function runBrowserMode(mode, origin) {
const { chromium } = await import('@playwright/test');
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage();
await page.exposeFunction('__reportOpusEvent', (event) => {
emit(EVENT_PREFIX, event);
});
await page.goto(origin, {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
return await page.evaluate(
async ({ fixtureUrl, requestedMode }) => {
const report = async (event) => {
await globalThis.__reportOpusEvent(event);
};
const replacePaths = (plan, context) => {
const replacements = new Map();
plan.inputs.forEach((input, index) => {
const actual = context.inputPaths[index];
if (actual) replacements.set(input.path, actual);
});
plan.outputs.forEach((output, index) => {
const actual = context.outputPaths[index];
if (actual) replacements.set(output.path, actual);
});
plan.temporaryFiles.forEach((temporary, index) => {
const actual = context.temporaryPaths[index];
if (actual) replacements.set(temporary.path, actual);
});
return plan.args.map((argument) =>
[...replacements].reduce(
(resolved, [virtualPath, actualPath]) =>
resolved.replaceAll(virtualPath, actualPath),
argument
)
);
};
const errorShape = (error) =>
error instanceof Error
? {
name: error.name,
message: error.message,
...(error.stack
? { stack: error.stack.split('\n').slice(0, 8) }
: {}),
}
: { name: 'Error', message: String(error) };
let manager;
const relevantLogs = [];
try {
await report({ stage: 'module-import' });
const [{ EngineManager }, { buildConvertPlan }, { AUDIO_PRESETS }] =
await Promise.all([
import('/src/ffmpeg/EngineManager.ts'),
import('/src/commands/convert.ts'),
import('/src/presets/audio-presets.ts'),
]);
manager = new EngineManager();
manager.setPreference(
requestedMode === 'single-thread'
? 'force-single-thread'
: 'prefer-multithread'
);
manager.onLog((event) => {
for (const line of event.message.split(/\r?\n/u)) {
if (
relevantLogs.length < 32 &&
/libopus|Output #|Stream mapping|Aborted\(\)|(?:^|\s)(?:size|progress)=|error|failed/iu.test(
line
)
) {
const message = line.slice(0, 600);
relevantLogs.push(message);
void report({ stage: 'core-log', message });
}
}
});
await report({ stage: 'fixture-fetch' });
const fixtureResponse = await fetch(fixtureUrl);
if (!fixtureResponse.ok) {
throw new Error(
`Fixture request failed with HTTP ${String(fixtureResponse.status)}.`
);
}
const fixtureBytes = new Uint8Array(
await fixtureResponse.arrayBuffer()
);
const sourceFile = new File([fixtureBytes], 'tone.wav', {
type: 'audio/wav',
});
await report({ stage: 'core-initializing' });
await manager.initialize();
const ready = manager.state;
if (ready.status !== 'ready') {
throw new Error('The FFmpeg engine did not reach ready state.');
}
const capabilities = {
opusMuxer: ready.capabilities.muxers.has('opus'),
libopusEncoder: ready.capabilities.encoders.has('libopus'),
};
await report({
stage: 'core-ready',
requestedMode,
actualMode: ready.mode,
ffmpegVersion: ready.capabilities.versionText,
capabilities,
});
if (ready.mode !== requestedMode) {
throw new Error(
`Requested ${requestedMode}, but the engine selected ${ready.mode}.`
);
}
if (!capabilities.opusMuxer || !capabilities.libopusEncoder) {
throw new Error(
'The pinned core does not advertise the Opus muxer and libopus encoder.'
);
}
const preset = AUDIO_PRESETS.find(
(candidate) => candidate.id === 'audio-opus'
);
if (!preset) {
throw new Error('The built-in audio-opus preset is missing.');
}
const plan = buildConvertPlan({
jobId: `opus-regression-${requestedMode}`,
source: {
id: 'tone',
sourceIndex: 0,
fileName: 'tone.wav',
extension: 'wav',
},
preset,
streamSelection: { audio: [0] },
expectedDurationSeconds: 1,
timeoutMs: 30_000,
});
await report({
stage: 'encode-started',
presetId: preset.id,
requirements: plan.requiredCapabilities,
});
let commandArguments = [];
const completed = await manager.runJob({
id: `opus-regression-${requestedMode}`,
operation: 'Opus regression',
coreWorkload: plan.operation,
inputs: [sourceFile],
outputs: plan.outputs.map((output) => ({
name: output.fileName,
mimeType: output.mediaType,
})),
expectedDurationSeconds: plan.expectedDurationSeconds,
timeoutMilliseconds: plan.timeoutMs,
buildArguments: (context) => {
commandArguments = replacePaths(plan, context);
void report({
stage: 'encode-command',
arguments: commandArguments,
});
return commandArguments;
},
});
const output = completed.outputs[0];
if (!output || output.bytes.byteLength === 0) {
throw new Error(
'The Opus encode did not produce non-empty output.'
);
}
await report({
stage: 'encode-finished',
outputBytes: output.bytes.byteLength,
});
const outputFile = new File([output.bytes], output.name, {
type: output.mimeType,
});
await report({ stage: 'reprobe-started' });
const probe = await manager.probe(outputFile, 15_000);
const probeJson = probe.json;
const streams = Array.isArray(probeJson?.streams)
? probeJson.streams
: [];
const opusStream = streams.find(
(stream) =>
stream?.codec_type === 'audio' && stream?.codec_name === 'opus'
);
const durationSeconds = Number(
probeJson?.format?.duration ?? opusStream?.duration
);
if (!opusStream || !(durationSeconds > 0)) {
throw new Error(
'The generated file did not re-probe as positive-duration Opus audio.'
);
}
const digest = new Uint8Array(
await crypto.subtle.digest('SHA-256', output.bytes)
);
const outputSha256 = [...digest]
.map((value) => value.toString(16).padStart(2, '0'))
.join('');
await report({ stage: 'reprobe-finished', durationSeconds });
return {
requestedMode,
actualMode: ready.mode,
status: 'pass',
ffmpegVersion: ready.capabilities.versionText,
capabilities,
presetId: preset.id,
commandArguments,
output: {
name: output.name,
mimeType: output.mimeType,
bytes: output.bytes.byteLength,
sha256: outputSha256,
codecName: opusStream.codec_name,
codecType: opusStream.codec_type,
durationSeconds,
},
relevantLogs,
};
} catch (error) {
const state = manager?.state;
return {
requestedMode,
...(state?.mode ? { actualMode: state.mode } : {}),
status: 'fail',
failure: 'browser-operation-failed',
error: errorShape(error),
relevantLogs,
};
} finally {
manager?.dispose();
}
},
{
fixtureUrl: `${origin}/${FIXTURE_RELATIVE_PATH}`,
requestedMode: mode,
}
);
} finally {
await browser.close();
}
}
async function runChild() {
const mode = option('mode');
const origin = option('origin');
if (!MODES.includes(mode) || !origin) {
throw new Error('Child mode requires valid --mode and --origin options.');
}
emit(EVENT_PREFIX, { stage: 'browser-launching', requestedMode: mode });
try {
const result = await runBrowserMode(mode, origin);
emit(RESULT_PREFIX, result);
} catch (error) {
emit(RESULT_PREFIX, {
requestedMode: mode,
status: 'fail',
failure: 'browser-process-failed',
error: serializeError(error),
});
}
}
try {
if (argumentsSet.has('--child')) {
await runChild();
} else {
await runMain();
}
} catch (error) {
process.stderr.write(
`Opus regression harness failed: ${JSON.stringify(serializeError(error))}\n`
);
process.exitCode = 2;
}