feat: publish av-tools 0.1.0

This commit is contained in:
2026-07-24 14:58:03 +02:00
commit fcc537b024
254 changed files with 63270 additions and 0 deletions

View File

@@ -0,0 +1,359 @@
/// <reference types="node" />
import { execFileSync } from 'node:child_process';
import { basename, join } from 'node:path';
import { expect, test } from '@playwright/test';
import {
createCancellationFixture,
GENERATED_FIXTURES,
observeRuntime,
PRIMARY_FIXTURE,
type TemporaryMediaFixture,
} from './browser-helpers';
function mediaInput(page: import('@playwright/test').Page) {
return page.locator('input[type="file"][accept*="audio"]');
}
async function selectSingleThread(
page: import('@playwright/test').Page
): Promise<void> {
await page.getByText('Engine & browser').click();
await page.getByLabel('Engine mode').selectOption('force-single-thread');
}
function assertLocalOnly(runtime: ReturnType<typeof observeRuntime>): void {
const networkUrls = runtime.requests.filter((url) => /^https?:/u.test(url));
expect(
networkUrls.every((url) => new URL(url).origin === 'http://127.0.0.1:4173')
).toBe(true);
const materialFailures = runtime.failedRequests.filter(
(failure) =>
!(failure.startsWith('blob:') && failure.endsWith('net::ERR_ABORTED'))
);
// Chromium aborts the superseded local preview blob when React switches from
// the source object URL to the generated result object URL.
expect(materialFailures).toEqual([]);
expect(runtime.pageErrors).toEqual([]);
expect(runtime.consoleErrors).toEqual([]);
}
test.describe.serial('real pinned FFmpeg browser runtime', () => {
let cancellationFixture: TemporaryMediaFixture;
test.beforeAll(() => {
cancellationFixture = createCancellationFixture();
});
test.afterAll(() => {
cancellationFixture.dispose();
});
test('loads and queries the multithread core under cross-origin isolation', async ({
page,
}) => {
const runtime = observeRuntime(page);
await page.goto('/');
const environment = await page.evaluate(() => ({
isolated: globalThis.crossOriginIsolated,
sharedArrayBuffer: typeof globalThis.SharedArrayBuffer,
}));
test.skip(
!environment.isolated || environment.sharedArrayBuffer !== 'function',
'Chromium did not expose SharedArrayBuffer in this server context'
);
await page.getByText('Engine & browser').click();
await page.getByRole('button', { name: 'Initialize engine' }).click();
await expect(
page.locator('.engine-pill').filter({ hasText: 'Multithreaded' })
).toBeVisible({ timeout: 120_000 });
await expect(page.getByText('0.12.15 / 0.12.10')).toBeVisible();
await expect(page.getByText('Isolation').locator('..')).toContainText(
'Enabled'
);
const expectedAssets = [
'/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.js',
'/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.wasm',
'/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.worker.js',
];
for (const suffix of expectedAssets) {
expect(
runtime.responses.some(
(response) => response.status === 200 && response.url.endsWith(suffix)
)
).toBe(true);
}
const wasm = runtime.responses.find((response) =>
response.url.endsWith('/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.wasm')
);
expect(wasm?.contentType).toContain('application/wasm');
expect(
runtime.requests.some((url) => url.includes('/vendor/ffmpeg/0.12.10/st/'))
).toBe(false);
await mediaInput(page).setInputFiles(PRIMARY_FIXTURE);
await expect(page.locator('.source-summary .phase--ready')).toHaveText(
'Ready',
{ timeout: 120_000 }
);
await page.getByRole('button', { name: 'Convert', exact: true }).click();
const result = page.locator('.result-card');
await expect(result).toContainText('pattern-av-convert.mp4', {
timeout: 120_000,
});
await expect(result).toContainText(/created · verified|completed locally/u);
const outputBytes = await page
.locator('.quick-preview video')
.evaluate(async (element) => {
const response = await fetch((element as HTMLVideoElement).src);
return (await response.arrayBuffer()).byteLength;
});
expect(outputBytes).toBeGreaterThan(1_000);
assertLocalOnly(runtime);
});
test('falls back to the single-thread core when the preferred multithread core cannot load', async ({
page,
}) => {
const runtime = observeRuntime(page);
await page.route('**/vendor/ffmpeg/0.12.10/mt/**', (route) =>
route.abort('failed')
);
await page.goto('/');
const environment = await page.evaluate(() => ({
isolated: globalThis.crossOriginIsolated,
sharedArrayBuffer: typeof globalThis.SharedArrayBuffer,
}));
test.skip(
!environment.isolated || environment.sharedArrayBuffer !== 'function',
'Chromium did not expose SharedArrayBuffer in this server context'
);
await page.getByText('Engine & browser').click();
await page.getByLabel('Engine mode').selectOption('prefer-multithread');
await page.getByRole('button', { name: 'Initialize engine' }).click();
await expect(
page
.locator('.engine-pill')
.filter({ hasText: 'Single-threaded compatibility mode' })
).toBeVisible({ timeout: 120_000 });
await expect(
page.getByText(
'The multithread core failed to load. The app recovered with the single-thread compatibility core.'
)
).toBeVisible();
expect(
runtime.requests.some((url) => url.includes('/vendor/ffmpeg/0.12.10/mt/'))
).toBe(true);
expect(
runtime.responses.some(
(response) =>
response.status === 200 &&
response.url.endsWith('/vendor/ffmpeg/0.12.10/st/ffmpeg-core.wasm')
)
).toBe(true);
expect(runtime.pageErrors).toEqual([]);
expect(runtime.consoleErrors).toEqual([]);
});
test('probes H.264/AAC media and produces a verified H.264/AAC result with the single-thread core', async ({
page,
}) => {
await page.addInitScript(() => {
Reflect.deleteProperty(globalThis, 'showSaveFilePicker');
});
const runtime = observeRuntime(page);
await page.goto('/');
await selectSingleThread(page);
expect(
runtime.requests.some((url) => url.includes('/vendor/ffmpeg/'))
).toBe(false);
await mediaInput(page).setInputFiles(PRIMARY_FIXTURE);
await expect(page.locator('.source-summary .phase--ready')).toHaveText(
'Ready',
{ timeout: 120_000 }
);
await expect(page.locator('.source-summary')).toContainText(
'pattern-av.mp4'
);
await expect(page.locator('.source-summary')).toContainText('0:02.00');
await expect(
page.getByRole('checkbox', { name: /#0 · h264/u })
).toBeChecked();
await expect(
page.getByRole('checkbox', { name: /#1 · aac/u })
).toBeChecked();
await expect(
page
.locator('.engine-pill')
.filter({ hasText: 'Single-threaded compatibility mode' })
).toBeVisible();
await page.getByRole('button', { name: 'Convert', exact: true }).click();
const result = page.locator('.result-card');
await expect(result).toBeVisible({ timeout: 120_000 });
await expect(result).toContainText('pattern-av-convert.mp4');
await expect(result).toContainText(/created · verified|completed locally/u);
const resultVideo = page.locator('.quick-preview video');
await expect(resultVideo).toHaveAttribute('src', /^blob:/u);
const outputBytes = await resultVideo.evaluate(async (element) => {
const video = element as HTMLVideoElement;
const response = await fetch(video.src);
return new Uint8Array(await response.arrayBuffer()).length;
});
expect(outputBytes).toBeGreaterThan(1_000);
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Save result' }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe('pattern-av-convert.mp4');
const downloadedPath = await download.path();
expect(downloadedPath).not.toBeNull();
const probeJson = execFileSync(
'ffprobe',
[
'-v',
'error',
'-show_entries',
'stream=codec_name,codec_type',
'-of',
'json',
downloadedPath as string,
],
{ encoding: 'utf8' }
);
const nativeProbe = JSON.parse(probeJson) as {
streams?: Array<{ codec_name?: string; codec_type?: string }>;
};
expect(nativeProbe.streams).toEqual(
expect.arrayContaining([
expect.objectContaining({
codec_name: 'h264',
codec_type: 'video',
}),
expect.objectContaining({
codec_name: 'aac',
codec_type: 'audio',
}),
])
);
expect(
runtime.requests.some((url) =>
url.endsWith('/vendor/ffmpeg/0.12.10/st/ffmpeg-core.wasm')
)
).toBe(true);
assertLocalOnly(runtime);
});
test('refreshes retained tile-filter state before a fourth contact sheet', async ({
page,
}) => {
test.setTimeout(120_000);
const runtime = observeRuntime(page);
await page.goto('/');
await selectSingleThread(page);
await page.getByRole('tab', { name: 'Edit' }).click();
await mediaInput(page)
.first()
.setInputFiles(join(GENERATED_FIXTURES, 'compatible-a.mp4'));
await expect(
page
.locator('.media-card')
.filter({ hasText: 'compatible-a.mp4' })
.locator('.phase--ready')
).toBeVisible({ timeout: 120_000 });
const advanced = page.locator('.advanced-operations');
await advanced
.getByRole('tab', { name: 'Thumbnails and contact sheets' })
.click();
await advanced
.getByRole('spinbutton', { name: 'Frames', exact: true })
.fill('4');
await advanced
.getByRole('spinbutton', { name: 'Rows', exact: true })
.fill('2');
await advanced
.getByRole('spinbutton', { name: 'Columns', exact: true })
.fill('2');
await advanced
.getByRole('spinbutton', { name: /^Cell width/u })
.fill('128');
const results = page.locator('.result-card');
const create = advanced.getByRole('button', {
name: 'Generate contact sheet',
});
for (let index = 1; index <= 4; index += 1) {
await create.click();
await expect(results).toHaveCount(index, { timeout: 120_000 });
await expect(page.getByRole('alert')).toHaveCount(0);
}
await expect(
results.filter({ hasText: 'compatible-a-contact-sheet' })
).toHaveCount(4);
expect(
runtime.requests.filter((url) =>
url.endsWith('/vendor/ffmpeg/0.12.10/st/ffmpeg-core.wasm')
)
).toHaveLength(2);
assertLocalOnly(runtime);
});
test('cancels an active transcode, recreates the engine and completes another job', async ({
page,
}) => {
const runtime = observeRuntime(page);
await page.goto('/');
await selectSingleThread(page);
await mediaInput(page).setInputFiles(cancellationFixture.filePath);
await expect(page.locator('.source-summary .phase--ready')).toHaveText(
'Ready',
{ timeout: 120_000 }
);
await page.getByRole('button', { name: 'Convert', exact: true }).click();
const cancelButton = page.getByRole('button', {
name: 'Cancel & recover',
});
await expect(cancelButton).toBeVisible({ timeout: 30_000 });
await cancelButton.click();
await expect(page.getByRole('alert')).toContainText(
'The operation was cancelled and the engine was recreated.',
{ timeout: 120_000 }
);
await expect(
page
.locator('.engine-pill')
.filter({ hasText: 'Single-threaded compatibility mode' })
).toBeVisible({ timeout: 120_000 });
await page
.getByRole('button', {
name: `Remove ${basename(cancellationFixture.filePath)}`,
})
.click();
await mediaInput(page).setInputFiles(PRIMARY_FIXTURE);
await expect(page.locator('.source-summary .phase--ready')).toHaveText(
'Ready',
{ timeout: 120_000 }
);
await page.getByRole('button', { name: 'Convert', exact: true }).click();
await expect(page.locator('.result-card')).toContainText(
'pattern-av-convert.mp4',
{ timeout: 120_000 }
);
await expect(page.getByRole('alert')).toHaveCount(0);
assertLocalOnly(runtime);
});
});