feat: publish av-tools 0.2.0
This commit is contained in:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user