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

View File

@@ -28,6 +28,8 @@ import {
buildPeakNormalizationPlan,
buildPreviewProxyPlan,
buildRemuxPlan,
buildSceneChangeAnalysisPlan,
buildSceneThumbnailSeriesPlan,
buildSingleThumbnailPlan,
buildSplitPlan,
buildStaticWaveformPlan,
@@ -44,6 +46,7 @@ import {
createConcatListEntries,
parseLoudnessMeasurement,
parsePeakMeasurement,
parseSceneChangeFrames,
resolveThumbnailTimes,
streamMapArguments,
type ConcatSource,
@@ -63,8 +66,16 @@ const NATIVE_SUBTITLES_FILTER_AVAILABLE =
encoding: 'utf8',
}).stdout ?? ''
);
const NATIVE_DRAWTEXT_FILTER_AVAILABLE =
NATIVE_FFMPEG_AVAILABLE &&
/\bdrawtext\b/u.test(
spawnSync('ffmpeg', ['-hide_banner', '-filters'], {
encoding: 'utf8',
}).stdout ?? ''
);
const describeNative = NATIVE_FFMPEG_AVAILABLE ? describe : describe.skip;
const itNativeSubtitles = NATIVE_SUBTITLES_FILTER_AVAILABLE ? it : it.skip;
const itNativeDrawtext = NATIVE_DRAWTEXT_FILTER_AVAILABLE ? it : it.skip;
interface NativeExecution {
readonly outputPaths: readonly string[];
@@ -752,6 +763,33 @@ describeNative('native execution of structured command plans', () => {
Number(probe(normalized.outputPaths[0]!).format?.duration)
).toBeCloseTo(1.6, 1);
const crossfaded = runNative(
buildNormalizedConcatPlan({
jobId: 'crossfade-concat-native',
sources: [
concatSource('first', 0, 'compatible-a.mp4', 128, 72),
concatSource('second', 1, 'compatible-b.mp4', 128, 72),
],
targetExtension: 'mp4',
targetMuxer: 'mp4',
preset: preset('mp4-h264-balanced'),
width: 128,
height: 72,
frameRate: 10,
sampleRate: 16_000,
channelLayout: 'mono',
missingAudioPolicy: 'reject',
crossfadeSeconds: 0.2,
}),
{
first: fixture('compatible-a.mp4'),
second: fixture('compatible-b.mp4'),
}
);
expect(
Number(probe(crossfaded.outputPaths[0]!).format?.duration)
).toBeCloseTo(1.8, 1);
const videoOnly = join(suiteDirectory, 'compatible-video-only.mp4');
nativeCommand(
[
@@ -788,6 +826,7 @@ describeNative('native execution of structured command plans', () => {
sampleRate: 16_000,
channelLayout: 'mono',
missingAudioPolicy: 'insert-silence',
crossfadeSeconds: 0.2,
}),
{ first: fixture('compatible-a.mp4'), 'video-only': videoOnly }
);
@@ -1185,8 +1224,80 @@ describeNative('native execution of structured command plans', () => {
codec_name: 'png',
width: 166,
});
const sceneAnalysis = runNative(
buildSceneChangeAnalysisPlan({
jobId: 'scene-analysis-native',
source: media,
durationSeconds: 2,
threshold: 0.01,
maxFrames: 4,
}),
{ media: mediaFile },
'info'
);
const sceneFrames = parseSceneChangeFrames(sceneAnalysis.stderr);
expect(sceneFrames.length).toBeGreaterThan(0);
const sceneSeries = runNative(
buildSceneThumbnailSeriesPlan({
jobId: 'scene-series-native',
source: media,
durationSeconds: 2,
frames: sceneFrames,
size: { width: 80 },
format: 'png',
}),
{ media: mediaFile }
);
expect(sceneSeries.outputPaths).toHaveLength(sceneFrames.length);
const sceneSheet = runNative(
buildContactSheetPlan({
jobId: 'scene-contact-sheet-native',
source: media,
durationSeconds: 2,
frameCount: sceneFrames.length,
rows: 1,
columns: sceneFrames.length,
cellWidth: 80,
spacing: 2,
sceneFrames,
format: 'png',
}),
{ media: mediaFile }
);
expect(statSync(sceneSheet.outputPaths[0]!).size).toBeGreaterThan(500);
}, 120_000);
itNativeDrawtext(
'executes contact-sheet labels with the reviewed bundled font',
() => {
const media = source('media', 0, 'pattern-av.mp4');
const labeledSheet = runNative(
buildContactSheetPlan({
jobId: 'labeled-contact-sheet-native',
source: media,
durationSeconds: 2,
frameCount: 2,
rows: 1,
columns: 2,
cellWidth: 120,
timestampLabels: true,
sourceLabel: true,
drawtextAvailable: true,
font: source('font', 1, 'DejaVuSans.ttf'),
format: 'png',
}),
{
media: fixture('pattern-av.mp4'),
font: resolve(process.cwd(), 'public/fonts/DejaVuSans.ttf'),
}
);
expect(statSync(labeledSheet.outputPaths[0]!).size).toBeGreaterThan(500);
},
120_000
);
it('executes a transformed, faded, normalized sequential timeline', () => {
const execution = runNative(
buildTimelineExportPlan({