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

@@ -9,6 +9,8 @@ import {
buildPeakAnalysisPlan,
buildPeakNormalizationFilter,
buildPeakNormalizationPlan,
buildSceneChangeAnalysisPlan,
buildSceneThumbnailSeriesPlan,
buildSubtitleBurnPlan,
buildSubtitleExtractionPlan,
buildSubtitleMuxPlan,
@@ -25,6 +27,7 @@ import {
parseChapterJson,
parseLoudnessMeasurement,
parsePeakMeasurement,
parseSceneChangeFrames,
parseMetadataJson,
resolveThumbnailTimes,
serializeChapterJson,
@@ -360,6 +363,65 @@ describe('thumbnail and contact sheet plans', () => {
expect(plan.args.some((value) => value.includes('%03d'))).toBe(false);
});
it('analyzes a bounded number of scene changes and parses exact timestamps', () => {
const plan = buildSceneChangeAnalysisPlan({
jobId: 'scenes',
source: SOURCE,
durationSeconds: 10,
threshold: 0.35,
maxFrames: 12,
});
expect(plan.outputs).toEqual([]);
expect(plan.args).toEqual(
expect.arrayContaining([
"select='gt(scene\\,0.35)',showinfo",
'-frames:v',
'12',
'-f',
'null',
])
);
expect(plan.requiredCapabilities.filters).toEqual(['select', 'showinfo']);
expect(
parseSceneChangeFrames(`
[Parsed_showinfo_1 @ 0x1] n: 0 pts: 4096 pts_time:0.25 duration:512
n: 1 pts: 16384 pts_time:1 duration:512
[Parsed_showinfo_1 @ 0x1] n: 2 pts: 16384 pts_time:1 duration:512
invalid showinfo n: 3 pts: nope pts_time:2
`)
).toEqual([
{ pts: 4096, timeSeconds: 0.25 },
{ pts: 16384, timeSeconds: 1 },
]);
});
it('builds exact scene-selected thumbnails from analyzed PTS values', () => {
const plan = buildSceneThumbnailSeriesPlan({
jobId: 'scene-thumbs',
source: SOURCE,
durationSeconds: 10,
frames: [
{ pts: 4096, timeSeconds: 0.25 },
{ pts: 16384, timeSeconds: 1 },
],
format: 'png',
size: { width: 320 },
});
expect(plan.outputs.map((output) => output.fileName)).toEqual([
'source-thumbnail-001-250ms.png',
'source-thumbnail-002-1000ms.png',
]);
expect(plan.args.join(' ')).toContain(
"select='eq(pts\\,4096)+eq(pts\\,16384)',split=2"
);
expect(plan.requiredCapabilities.filters).toEqual([
'select',
'split',
'setpts',
'scale',
]);
});
it('degrades contact-sheet labels when drawtext is unavailable', () => {
const plan = buildContactSheetPlan({
jobId: 'sheet',
@@ -381,6 +443,49 @@ describe('thumbnail and contact sheet plans', () => {
);
expect(plan.requiredCapabilities.filters).not.toContain('drawtext');
});
it('uses a mounted reviewed font and exact scene frames for labels', () => {
const plan = buildContactSheetPlan({
jobId: 'scene-sheet',
source: SOURCE,
durationSeconds: 10,
frameCount: 2,
rows: 1,
columns: 2,
cellWidth: 240,
timestampLabels: true,
sourceLabel: true,
format: 'jpeg',
drawtextAvailable: true,
font: {
id: 'bundled-font',
sourceIndex: 1,
fileName: 'DejaVuSans.ttf',
},
sceneFrames: [
{ pts: 4096, timeSeconds: 0.25 },
{ pts: 16384, timeSeconds: 1 },
],
});
expect(plan.inputs[1]).toMatchObject({
kind: 'font',
sourceIndex: 1,
});
expect(plan.args.join(' ')).toContain(
"select='eq(pts\\,4096)+eq(pts\\,16384)'"
);
expect(plan.args.join(' ')).toContain(
"drawtext=fontfile='/input/job-scene-sheet/source-1.ttf'"
);
expect(plan.args.join(' ')).toContain('nb_frames=2');
expect(plan.requiredCapabilities.filters).toEqual([
'select',
'scale',
'setpts',
'tile',
'drawtext',
]);
});
});
describe('composed visual operations', () => {