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

@@ -16,7 +16,9 @@ describe('HelpDialog', () => {
).toBeInTheDocument();
expect(screen.getByText('n5.1.4')).toBeInTheDocument();
expect(screen.getByText('0.12.15')).toBeInTheDocument();
expect(screen.getByText('0.12.10 · ST & MT')).toBeInTheDocument();
expect(
screen.getByText('0.12.10-reviewed-stack5m.1 · ST & MT')
).toBeInTheDocument();
expect(
screen.getByText(/copyright © 2026 Albrecht Degering/i)
).toBeInTheDocument();
@@ -29,6 +31,9 @@ describe('HelpDialog', () => {
expect(
screen.getByRole('link', { name: 'GPL text', hidden: true })
).toHaveAttribute('href', expect.stringContaining('GPL-2.0-or-later'));
expect(
screen.getByRole('link', { name: 'Font licence', hidden: true })
).toHaveAttribute('href', expect.stringContaining('DEJAVU_FONTS'));
expect(
screen.getByRole('link', {
name: 'Complete inventory',

View File

@@ -26,6 +26,41 @@ afterEach(() => {
});
describe('MediaPreview', () => {
it('starts declared local video playback while inspection continues and preserves the element', () => {
const probingAsset: ImportedMediaAsset = {
id: 'asset-probing-video',
file: new File(['video'], 'lecture.mp4', {
type: 'application/octet-stream',
}),
objectUrl: 'blob:probing-video',
phase: 'probing',
};
const { container, rerender } = render(
<MediaPreview asset={probingAsset} />
);
const progressiveVideo = requiredElement(container.querySelector('video'));
expect(progressiveVideo).toHaveAttribute('src', probingAsset.objectUrl);
expect(
screen.getByText(
'Local source preview · Media inspection continues in the background.'
)
).toBeVisible();
rerender(
<MediaPreview
asset={{
...videoAsset(),
id: probingAsset.id,
file: probingAsset.file,
objectUrl: probingAsset.objectUrl,
}}
/>
);
expect(container.querySelector('video')).toBe(progressiveVideo);
});
it('offers bounded configurable proxy settings after playback fails', async () => {
const user = userEvent.setup();
const onCreateProxy = vi.fn();

View File

@@ -138,6 +138,28 @@ describe('QuickConvert', () => {
).toBeDisabled();
});
it('enables Opus after the reviewed core passed both regression modes', () => {
const engineState: EngineState = {
status: 'ready',
mode: 'single-thread',
capabilities: {
...capabilities(['mp4', 'opus']),
encoders: new Set(['libx264', 'aac', 'libopus']),
},
};
render(
<QuickConvert
asset={createAsset('asset-a', 'source-a.mp4')}
engineState={engineState}
busy={false}
onExport={vi.fn()}
/>
);
expect(screen.getByRole('option', { name: 'Opus' })).toBeEnabled();
});
it('exposes a validated user preset and returns it unchanged on export', async () => {
const user = userEvent.setup();
const onExport = vi.fn<(configuration: QuickExportConfiguration) => void>();

View File

@@ -274,6 +274,7 @@ describe('StructuralOperations', () => {
presetId: 'video-ready',
missingAudioPolicy: 'insert-silence',
subtitlePolicy: 'reject',
crossfadeSeconds: 0,
})
);
});
@@ -328,6 +329,55 @@ describe('StructuralOperations', () => {
presetId: 'video-ready',
missingAudioPolicy: 'insert-silence',
subtitlePolicy: 'drop-all',
crossfadeSeconds: 0,
})
);
});
it('emits a bounded crossfade only after its filters are verified', async () => {
const user = userEvent.setup();
const onConcat = vi.fn();
render(
<StructuralOperations
timeline={{
...TIMELINE,
clips: TIMELINE.clips.map((clip) => ({
...clip,
durationSeconds: 3,
})),
}}
exportPresets={PRESETS}
availability={capabilities('concat-normalized', 'concat-crossfade')}
onConcat={onConcat}
/>
);
await user.click(
screen.getByRole('radio', { name: /Normalized re-encode/iu })
);
await user.selectOptions(
screen.getByLabelText('Normalized concat preset'),
'video-ready'
);
await user.click(screen.getByRole('radio', { name: /Insert silence/iu }));
await user.click(
screen.getByRole('radio', { name: /Reject subtitle input/iu })
);
await user.clear(screen.getByLabelText(/^Crossfade duration/iu));
await user.type(screen.getByLabelText(/^Crossfade duration/iu), '0.5');
await user.click(
screen.getByRole('button', { name: 'Export normalized concat' })
);
await waitFor(() =>
expect(onConcat).toHaveBeenCalledWith({
operation: 'concat',
mode: 'normalized',
clipIds: ['clip-a', 'clip-b'],
presetId: 'video-ready',
missingAudioPolicy: 'insert-silence',
subtitlePolicy: 'reject',
crossfadeSeconds: 0.5,
})
);
});