# Video pack slice A: engine and licence gate

Gate completed before page implementation, 2026-09-30. Chosen: browser WebCodecs
(VideoDecoder/VideoEncoder/AudioDecoder/AudioEncoder), a module Web Worker and
OffscreenCanvas, with the following unmodified npm distribution files. Nothing
uses SharedArrayBuffer, COOP/COEP, a CDN or uploaded media. Browser codecs are
provided by the browser/OS; no codec binaries are redistributed by this page.

| Route / dependency | Pin and evidence | Licence | Served code bytes | Chromium measurement |
| --- | --- | --- | ---: | --- |
| (a) WebCodecs + mp4box | mp4box **2.4.1**, [source](https://github.com/gpac/mp4box.js/tree/v2.4.1), [exact package](https://registry.npmjs.org/mp4box/-/mp4box-2.4.1.tgz), package/LICENSE checked | BSD-3-Clause, [licence](https://raw.githubusercontent.com/gpac/mp4box.js/v2.4.1/LICENSE): “Redistribution and use in source and binary forms” | 328,197 (all.mjs + styp + rolldown runtime) | Combined route below |
| MP4 output | mp4-muxer **5.2.2**, [source](https://github.com/Vanilagy/mp4-muxer/tree/v5.2.2), [exact package](https://registry.npmjs.org/mp4-muxer/-/mp4-muxer-5.2.2.tgz), package/LICENSE checked | MIT, [licence](https://raw.githubusercontent.com/Vanilagy/mp4-muxer/v5.2.2/LICENSE): “Permission is hereby granted, free of charge” | 69,011 | Combined route below |
| WebM output | webm-muxer **5.1.4**, [source](https://github.com/Vanilagy/webm-muxer/tree/v5.1.4), [exact package](https://registry.npmjs.org/webm-muxer/-/webm-muxer-5.1.4.tgz), package/LICENSE checked | MIT, [licence in pinned distribution](https://registry.npmjs.org/webm-muxer/-/webm-muxer-5.1.4.tgz), package/LICENSE: “Permission is hereby granted, free of charge”; full local text: video/LICENSE-webm-muxer.txt | 64,944 | Combined route below |
| (b) Default ffmpeg.wasm, **REJECTED** | @ffmpeg/core **0.12.10**, [package metadata](https://registry.npmjs.org/@ffmpeg/core/0.12.10), [exact build](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/v0.12.10/Dockerfile) includes --enable-gpl, --enable-libx264, --enable-libx265 | GPL-2.0-or-later, forbidden | 64,689,644 unpacked package bytes (not served) | Not downloaded or executed; forbidden licence |
| (b) LGPL-only single-thread core | No published core with verifiable pin, --disable-gpl build and required encoding was established. [Upstream build instructions](https://ffmpegwasm.netlify.app/docs/contribution/core/) explain custom builds; [OpenTranscribe's build](https://docs.opentranscribe.app/docs/developer-guide/ffmpeg-wasm-build/) is a stream-copy-only LGPL build, not an H.264 resize encoder. No reproducible OpenH264 wasm build was produced here. | FFmpeg can be LGPL-2.1+; [upstream licence](https://ffmpeg.org/legal.html), OpenH264 BSD-2-Clause; these alone do not establish the licence of a binary | N/A | Not benchmarked: no qualified binary. This is not a claim that none exists. |

The three packaged licence files are copied in full into video/LICENSE-*.txt.
The .mjs files are byte-for-byte copies from the pinned packages, including
mp4box's two relative imports. No dependencies outside these files are needed.
mp4-muxer/webm-muxer are deprecated upstream but their MIT pins are valid;
Mediabunny's current MPL licence is outside this ticket's allowed list.

## Before-page benchmark

System ffmpeg generated a **20 s 1920×1080 30 fps H.264 + AAC MP4** using
`testsrc2` and a 440 Hz sine, libx264 ultrafast, yuv420p, AAC, faststart. System
ffmpeg and its fixtures are test-only; neither ships. A real Python HTTP server
served the prototype to **Google Chrome 150.0.7871.114**, headless, GPU disabled,
on this Linux host (Intel Core i7-9700, 8 logical CPUs). No fetch/network/worker/codec implementation was stubbed.
The browser runs with `--password-store=basic` so the sandbox does not wait for
the desktop keyring; temporary XDG directories isolate its profile. No NSS shim
was needed or retained. HTTPS production and HTTP localhost are secure contexts.

Trimming **0–5 s** and resizing to **1080×1920 Fill, centre crop** took
**1,918.7 ms**, including demux, decode, canvas scaling, encode and final mux.
ffprobe verified 1080×1920 VP9 video, Opus audio and approximately 5 seconds.
This Linux Chrome exposes no AAC encoder, so the real job chose WebM via
isConfigSupported. MP4 H.264 + AAC is selected when both encoders are supported;
muted MP4 needs only H.264. The libraries download **462,152 bytes**; the prototype
worker, engine and core brought total code before the job to **476,803 bytes**.
The benchmark fixture was served only as a test transport; visitor Files are
local blobs and contribute **zero network bytes**. No engine files load before
a visitor adds a clip. Final page/first-clip bytes and joined-flow timings are
recorded below after DOM tests.

## Coverage and limits

MP4 and QuickTime MOV containers, H.264 and browser-supported HEVC, AAC and Opus
are demuxed into WebCodecs. HEVC decode is probed with the actual track config;
unsupported HEVC gets the explicit phone-video explanation. Output encoder
support is probed for the selected dimensions, frame rate, channels and sample
rate. There is no browser-name assumption. Each joined clip is resampled onto
one frame grid; audio is cropped/rebased and converted to stereo 48 kHz with
silence for clips without audio. Input edit lists and quarter-turn display
rotation are applied. Fit uses the selected bar colour; Fill centre-crops.

Encoded input samples for one clip and the final output are held in memory;
decoded video is limited to the codec queues plus one held source frame. Phones
may run out of memory on large files. Warnings above ten minutes or 2 GB do not
block export. Browser/OS codec variation and actual Safari/Edge hardware still
need manual checks; Chromium real-codec tests are the automated evidence.

## Final page and real DOM validation

The Python test starts a real localhost ThreadingHTTPServer serving site/;
Chromium uses actual module-worker GETs and its own download manager. No fetch or
worker networking is replaced. Chromium's built-in media-control data icons and
local blob URLs do not use HTTP; every observed HTTP request is a same-origin
GET under /video-editor/ or /vendor/. Source media are supplied through the real
file input from temporary fixtures, never through the server.

Final first-use network measurement (fresh profile, uncompressed static files):
**29,678 payload bytes at page load**, with **zero engine/library bytes**;
**478,245 additional payload bytes on first clip** (total **507,923 bytes before
export**). Including HTTP response headers: 30,442 bytes at page load and
478,897 on first clip. Chromium reports 482,348 decoded resource bytes for the
first-clip stage; that includes a 4,103-byte core script reused from cache, so it
is not counted as a new download. The library payload is 462,152 bytes. Licence
texts and this document are available locally but are only fetched if opened.

Three consecutive runs of `tests/test_video_editor.py`, six tests per run:

| Run | Result | 6 s 640×360 30 fps + 4 s 480×480 24 fps, first trimmed 1–4 s, 9:16 Fill |
| --- | --- | --- |
| 1 | 6 tests OK | 7,154.4 ms |
| 2 | 6 tests OK | 7,054.5 ms |
| 3 | 6 tests OK | 5,235.3 ms |

Each output was 7.00 seconds, 1080×1920 VP9 + Opus WebM. Each muted export was
H.264 MP4 with no audio stream. Tests also passed cancellation during encoding,
worker restart, keyboard reordering, scrubbing, a 390 px viewport, QuickTime MOV
H.264 + AAC input and Opus-in-MP4 input. Fixtures use B-frames and differing
source frame rates; audio input is 44.1 kHz and output 48 kHz. The variability in
wall time is host load; these are measurements, not phone performance promises.

Reproduce with:

```
/home/g5-local/ventures/apitc/core/.venv/bin/python -m unittest discover -s tests -p test_video_editor.py
```

Before-page fixture generation (test-only, no shipped assets):

```
ffmpeg -f lavfi -i testsrc2=size=1920x1080:rate=30 -f lavfi -i sine=frequency=440:sample_rate=48000 -t 20 -c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac -movflags +faststart benchmark.mp4
```

The benchmark used the same `video-worker.js` inspect/export protocol with one
local File, in/out 0–5, shape 9:16, mode fill, mute false. The final implementation
is formatted and has additional validation; the 1,918.7 ms measurement above is
from the prototype before page code, not a benchmark of the final UI.

Whole suite: `/home/g5-local/ventures/apitc/core/.venv/bin/python -m unittest discover -s tests`
ended **580 tests, OK, zero failures/errors/skips**, in **255.666 s**. Its additional
video run exported the joined flow in **4,061.7 ms**. The worktree needed the
existing converter/node_modules dependencies and an empty .cowerx directory
for baseline tests; neither is a shipped asset. `git diff --check` passed.
All changes are left uncommitted on build/61a-video-pack; this page is not live.
