367 lines
16 KiB
Markdown
367 lines
16 KiB
Markdown
# Audio & Video Tools
|
||
|
||
av-tools is a standalone, local-first browser application for converting and
|
||
lightly editing audio/video. Media is processed by self-hosted FFmpeg
|
||
WebAssembly in a browser worker: there is no upload API, server transcoder,
|
||
telemetry, runtime CDN, remote-media fetch or third-party script.
|
||
|
||
It is also a Toolbox SDK application. The same static build runs directly or
|
||
under Toolbox Portal with shared shell, theme, help and app switching.
|
||
|
||
> **Release status:** 0.2.1 is licensed GPL-3.0-or-later. Its canonical
|
||
> release consists of the exact reviewed `v0.2.1` source, production
|
||
> ZIP/checksum and Corresponding Source bundle published together. See
|
||
> [Licensing](#licensing).
|
||
|
||
## Product scope
|
||
|
||
Two workflows keep the product intentionally smaller than a nonlinear editor:
|
||
|
||
- **Quick Convert:** drop/import media, inspect streams, choose a
|
||
capability-checked preset/streams, convert, preview when the browser can, and
|
||
download.
|
||
- **Edit:** a media bin and sequential non-overlapping clip timeline with
|
||
non-destructive settings, one export at a time.
|
||
|
||
The 0.2 release covers typed/capability-gated conversion and remux,
|
||
fast/accurate trim, split, compatible/normalized concatenate, reorder, crop,
|
||
resize, frame-rate change, audio extraction/stream removal, waveform,
|
||
peak/two-pass loudness normalization, gain, audio/video fades, metadata,
|
||
chapters, SRT/WebVTT/ASS text subtitles, thumbnails, contact sheets, presets,
|
||
versioned project save/load, reattachment, progress, cancellation/recovery,
|
||
diagnostics and explicit browser export.
|
||
|
||
The Edit workspace now exposes structural exports directly: fast stream-copy
|
||
or preset-backed accurate trim uses the selected clip's timeline range, while
|
||
fast concatenate requires explicit compatibility diagnostics and normalized
|
||
concatenate requires a preset plus an explicit missing-audio policy. Normalized
|
||
joins can apply a uniform, capability-gated 0.04–10 second audio/video
|
||
crossfade at every edit, with clip-material checks before queueing.
|
||
Operations enter a bounded, in-memory application queue with at most eight
|
||
scheduled operations and one FFmpeg execution at a time.
|
||
|
||
Thumbnail series and contact sheets support even, interval, chapter, marker or
|
||
bounded scene-change selection. Scene analysis records exact source
|
||
presentation timestamps before extraction. Contact-sheet labels use only the
|
||
bundled, SHA-256-verified DejaVu Sans 2.37 file and remain disabled unless the
|
||
active core exposes `drawtext`; there is no remote/system-font fallback.
|
||
|
||
When a source cannot be played directly, the preview panel can create a
|
||
disposable H.264/AAC MP4 proxy. The user can bound it to 1–300 seconds and
|
||
choose a maximum width from 480–1920 pixels. A proxy is reused from the local
|
||
derivative cache only when the source identity and settings match.
|
||
|
||
A control is not considered complete merely because its UI or command builder
|
||
exists. The exact milestone status is the tested matrix in the final release
|
||
report; [CHANGELOG.md](CHANGELOG.md) lists current blockers.
|
||
|
||
## Engine and format model
|
||
|
||
Exact runtime packages:
|
||
|
||
- `@ffmpeg/ffmpeg` 0.12.15;
|
||
- `@ffmpeg/util` 0.12.2;
|
||
- reviewed FFmpeg core build `0.12.10-reviewed-stack5m.1` in single-thread and
|
||
multithread profiles.
|
||
|
||
The reviewed cores contain **FFmpeg n5.1.4** (libavcodec 59.37.100), built
|
||
with x264, x265, libvpx, LAME, Ogg/Theora/Opus/Vorbis, zlib, libwebp,
|
||
FreeType/FriBidi/HarfBuzz/libass and zimg. Both ~32 MiB WASM cores and their
|
||
JS/worker files are verified against a tracked build lock, unpacked from a
|
||
deterministic repository artifact and served from the app's own origin.
|
||
`npm run build` does not download them.
|
||
|
||
Every engine load queries actual demuxers, muxers, decoders, encoders and
|
||
filters. Presets remain disabled when requirements are absent; an extension or
|
||
FFmpeg build flag is not a compatibility promise.
|
||
|
||
To bound native heap retained by heterogeneous long-running sessions, the app
|
||
refreshes an idle core before its seventeenth media execution and before a
|
||
fourth contact sheet uses the pinned core's retained `tile`-filter state.
|
||
Queue state, sources and completed results stay in application memory; only
|
||
the worker is recreated. A bare core abort also triggers recovery for a retry.
|
||
|
||
Generated fixtures cover MP4/H.264/AAC, WebM/VP8/Vorbis, WAV/PCM, MP3, FLAC,
|
||
metadata, chapters, attached cover art, SRT, WebVTT, ASS and malformed input.
|
||
All 15 fixtures were generated/re-probed with native FFmpeg 7.1.3 for test
|
||
preparation and pass the repository checksum/probe verifier. Chromium
|
||
automation uses the pinned ST core for a broad operation matrix and separately
|
||
proves a real MT H.264/AAC conversion. That does not establish cross-browser
|
||
support or exhaustive MT parity, and fixture generation is not evidence that
|
||
“all formats” work. See
|
||
[format support](docs/FORMAT_SUPPORT.md) and
|
||
[fixture provenance](tests/fixtures/README.md).
|
||
|
||
The recorded pinned-core capability inventory contains 341 demuxers, 173
|
||
muxers, 515 decoders, 204 encoders and 465 filters (1,698 entries total).
|
||
These are discovered capabilities, not product or browser-playback guarantees.
|
||
|
||
Browser playback is independent of FFmpeg readability. MP4/H.264/AAC,
|
||
WebM/VP8/Vorbis, MP3 and WAV are common preview candidates, but actual codec,
|
||
browser and platform support is checked at runtime. Matroska and many other
|
||
valid outputs may be download-only. A preview proxy may require re-encoding.
|
||
|
||
The 2026-07-26 Chromium regression harness ran the real built-in `audio-opus`
|
||
plan against both reviewed cores. Each selected the requested ST/MT mode,
|
||
produced non-empty Opus and passed codec/duration re-probing. Opus presets are
|
||
therefore enabled when runtime capability discovery also succeeds. Exact
|
||
commands and evidence boundaries are in
|
||
[FFmpeg build](docs/FFMPEG_BUILD.md).
|
||
|
||
## Execution modes
|
||
|
||
- **Multithread** is selected only in secure, cross-origin-isolated contexts
|
||
with `SharedArrayBuffer`.
|
||
- **Single-thread compatibility mode** is the automatic fallback when
|
||
isolation is unavailable or MT loading fails. It remains fully worker-based
|
||
but may be slower.
|
||
|
||
MT requires every response in the page to support:
|
||
|
||
```text
|
||
Cross-Origin-Opener-Policy: same-origin
|
||
Cross-Origin-Embedder-Policy: require-corp
|
||
Cross-Origin-Resource-Policy: same-origin
|
||
```
|
||
|
||
Serve `.wasm` as `application/wasm`, keep all core files same-origin and allow
|
||
`'wasm-unsafe-eval'` plus workers in CSP. Blob media preview additionally needs
|
||
`media-src 'self' blob:`. The manifest intentionally does **not** require
|
||
cross-origin isolation because ST is supported.
|
||
|
||
## Limits, storage and privacy
|
||
|
||
ffmpeg.wasm documents a hard **2 GiB input ceiling**. Real browser limits are
|
||
often lower because sources, decoded frames, filters and outputs coexist. The
|
||
initial application policy warns at 256 MiB input, blocks a single or total
|
||
input above 1.5 GiB, warns at a 512 MiB output estimate and blocks an estimated
|
||
output above 1 GiB. The in-memory queue accepts at most eight scheduled
|
||
operations and runs only one FFmpeg operation at a time. Other defaults bound
|
||
clips, streams, dimensions/pixels, thumbnails, contact-sheet cells and runtime.
|
||
Refreshing or closing the page discards queued work.
|
||
|
||
Estimates are not guarantees. Lower resolution, duration, frame rate, pass
|
||
count and output count can reduce failure risk. WebAssembly does not offer
|
||
desktop FFmpeg performance or hardware encoding.
|
||
|
||
Source `File` objects and preview/result Blob URLs are temporary and object URLs
|
||
are revoked on replacement/cleanup. Per-job FFmpeg files/mounts are deleted;
|
||
cancellation discards the engine filesystem. The mounted application currently
|
||
uses IndexedDB for validated user presets; project JSON is explicitly
|
||
saved/loaded by the user and the operation queue remains in memory. Optional
|
||
OPFS caching is limited to regenerable derivatives and is clearable; source
|
||
bytes are **not persisted by default**. OPFS belongs to the current origin, so
|
||
changing scheme/host/port does not move cached data. See
|
||
[storage](docs/STORAGE.md).
|
||
|
||
Imported media, names, tags and subtitles are untrusted. They are rendered as
|
||
text, paths are generated, operations build argument arrays rather than shell
|
||
commands, logs are bounded/redacted, and extracted URLs are inert. See
|
||
[security](docs/SECURITY.md).
|
||
|
||
## Development
|
||
|
||
Requirements: Node.js 22+, npm matching the lockfile, and a modern browser.
|
||
Native FFmpeg is a development/test dependency for fixture regeneration and
|
||
the conditional native command-plan integration suite; it is not a production
|
||
runtime dependency.
|
||
|
||
```sh
|
||
git clone git@git.add-ideas.de:lotobo/av-tools.git
|
||
cd av-tools
|
||
npm ci
|
||
npm run dev
|
||
```
|
||
|
||
The token-free `.npmrc` selects the published Toolbox SDK scope. Do not add
|
||
credentials to repository configuration.
|
||
|
||
Core assets are generated local build inputs:
|
||
|
||
```sh
|
||
npm run vendor:ffmpeg
|
||
npm run vendor:verify-runtime
|
||
```
|
||
|
||
The runtime verifier checks the tracked deterministic core artifact, source
|
||
lock, build driver and generated same-origin files. The stricter
|
||
`npm run vendor:verify` publication gate additionally requires and hashes the
|
||
ignored Corresponding Source archive generated by `npm run source:package`.
|
||
|
||
Regenerate or verify the tiny synthetic fixtures:
|
||
|
||
```sh
|
||
npm run fixtures:generate
|
||
npm run fixtures:verify
|
||
```
|
||
|
||
## Checks
|
||
|
||
```sh
|
||
npm run manifest:check
|
||
npm run vendor:verify-runtime
|
||
npm run typecheck
|
||
npm run lint
|
||
npm run format:check
|
||
npm test
|
||
npm run build
|
||
npm run toolbox:check
|
||
npm run test:browser
|
||
npm run evidence:large-preview # set AV_TOOLS_LARGE_MEDIA first
|
||
npm run evidence:opus
|
||
```
|
||
|
||
`npm run check` combines the non-browser production checks. Browser tests cover
|
||
the real pinned cores, MT-to-ST fallback, cancellation/recovery, generated
|
||
fixtures, nested deployment, Toolbox context and the absence of runtime CDN
|
||
traffic.
|
||
|
||
`npm run evidence:opus` independently contains each ST/MT browser process,
|
||
prints one JSON report and exits normally even when the result is unsafe.
|
||
`npm run test:opus-regression` runs the same evidence as the positive Opus
|
||
enablement gate and exits nonzero unless both modes produce non-empty,
|
||
re-probeable Opus. It is a mandatory release gate for the reviewed core
|
||
profile.
|
||
|
||
`AV_TOOLS_LARGE_MEDIA=/absolute/video.mp4 npm run evidence:large-preview`
|
||
performs the opt-in large-file regression: the native video element must mount
|
||
and advance before bounded inspection finishes, remain the same element after
|
||
the authoritative probe completes and produce no browser error.
|
||
|
||
The latest recorded local Vitest run on 2026-07-26 completed **313 passing
|
||
tests and two conditional skips**. It includes broad structured-command
|
||
execution against native FFmpeg 7.1.3; the skipped native subtitle burn-in and
|
||
labeled contact-sheet cases require `subtitles` and `drawtext` filters that are
|
||
unavailable in that host build.
|
||
|
||
The Chromium suite exercises the pinned ST core across conversion,
|
||
trim/concat/split, bounded crossfades, analysis, waveform, scene-selected image
|
||
generation, bundled-font contact-sheet labels, metadata, chapter, subtitle,
|
||
queue and recovery workflows. The MT core is directly initialized and queried,
|
||
and completes a real non-empty, re-probeable H.264/AAC conversion; the same
|
||
path is benchmarked with 2-second 160×90 and 6-second 640×360 inputs. Nested
|
||
static/Toolbox-context loading is also covered. This evidence is
|
||
Chromium-specific: Firefox, Safari, mobile and exhaustive MT operation parity
|
||
remain unrecorded. The exact one-run performance record and its limitations are
|
||
in [performance observations](docs/PERFORMANCE.md).
|
||
|
||
## Deployment
|
||
|
||
Deploy the contents of `dist/` beneath any static path; URLs are base-relative
|
||
and no server route fallback is required. Production examples:
|
||
|
||
- [Nginx](deployment/nginx.example.conf)
|
||
- [Caddy](deployment/caddy.example)
|
||
- [generic static headers](deployment/headers.md)
|
||
|
||
The policy permits same-origin WebAssembly and Blob workers/media without
|
||
broad `'unsafe-eval'`. Keep stable entry/manifest/notices revalidated. Exact
|
||
versioned core files and Vite content-hashed assets may be cached immutable.
|
||
When isolation headers cannot be configured, ST remains the supported
|
||
fallback.
|
||
|
||
## Toolbox integration
|
||
|
||
The one typed `de.add-ideas.av-tools` manifest generates
|
||
`public/toolbox-app.json`. `AppShell` receives the relative
|
||
`./toolbox-app.json` URL and falls back to standalone behavior when Toolbox
|
||
context is absent or invalid. av-tools does not duplicate suite
|
||
personalization or app switching.
|
||
|
||
The manifest uses `entry: "./"`, relative icon/assets, top-level navigation,
|
||
local processing/no upload/no telemetry, and advertises workers, secure
|
||
context and IndexedDB. Its `source` object identifies the canonical av-tools
|
||
repository and the `GPL-3.0-or-later` SPDX licence.
|
||
|
||
Portal must consume an independently built ZIP; it must not build or import
|
||
av-tools source. Immutable Portal revision
|
||
`5c052bc6d1073a66600cfddc44c0e16da34bede8` (`v0.6.0`) provides the required
|
||
isolation, narrow WebAssembly CSP, Blob media policy and explicit WASM MIME
|
||
mapping. The active Portal worktree additionally extends immutable caching to
|
||
the exact reviewed FFmpeg build profile without accepting mutable-looking
|
||
suffixes. Its exact reviewed and staged
|
||
boundaries are documented in
|
||
[Portal requirements](docs/PORTAL_REQUIREMENTS.md).
|
||
|
||
## Release
|
||
|
||
After all checks, normal packaging would produce deterministic:
|
||
|
||
```text
|
||
release/av-tools-<version>.zip
|
||
release/av-tools-<version>.zip.sha256
|
||
```
|
||
|
||
The ZIP root contains `index.html`, `toolbox-app.json`, `CHANGELOG.md`,
|
||
`LICENSES/`, `SOURCE.md`, `THIRD_PARTY_NOTICES.md`, `assets/` and all
|
||
`vendor/` core files. It excludes `node_modules`, sources, source maps and
|
||
fixtures. Paths are sorted, timestamps/modes normalized and the SHA-256
|
||
sidecar is verified.
|
||
|
||
Normal packaging requires the root GPL licence and matching package/manifest
|
||
metadata:
|
||
|
||
```sh
|
||
npm run package:release
|
||
npm run portal:smoke -- \
|
||
--portal-root ../toolbox-portal
|
||
```
|
||
|
||
The smoke harness clones the selected Portal `HEAD`; it never consumes
|
||
uncommitted Portal files silently. Full commands and safety behavior are in
|
||
[release documentation](docs/RELEASE.md).
|
||
|
||
## Licensing
|
||
|
||
av-tools-owned code and documentation are Copyright © 2026 Albrecht Degering
|
||
and licensed **GPL-3.0-or-later**. See [LICENSE](LICENSE). The application
|
||
provides a visible licence, source and no-warranty notice in its Help dialog.
|
||
Third-party components retain their copyright and licence terms.
|
||
|
||
The wrapper/util are MIT. The reviewed 0.12.10 cores statically include
|
||
GPL-enabled FFmpeg/external codec code with additional terms. The branch-based
|
||
x264/LAME inputs have been resolved to immutable commits in the source record.
|
||
A binary publication must include equivalent access to the reviewed
|
||
Corresponding Source and notices. See:
|
||
|
||
- [third-party notices](THIRD_PARTY_NOTICES.md)
|
||
- [source record](SOURCE.md)
|
||
- [licence texts/status](LICENSES/README.md)
|
||
|
||
The complete distributed application must not be called MIT-only. Publishing a
|
||
binary or hosted JS/WASM build requires equivalent access to its corresponding
|
||
source; linking only to a component homepage is not asserted to satisfy that
|
||
obligation. Codec patent requirements are separate from copyright licensing.
|
||
|
||
## Known limitations
|
||
|
||
The auditable list is in
|
||
[known limitations](docs/KNOWN_LIMITATIONS.md). In summary:
|
||
|
||
- No server transcoder, remote URL input, live capture, DRM or encrypted media.
|
||
- No overlapping video tracks, arbitrary compositing, collaboration or
|
||
professional nonlinear editing.
|
||
- No “all formats,” unlimited file size, desktop performance or hardware
|
||
encoding guarantee.
|
||
- Stream-copy trim is fast but not promised frame-accurate.
|
||
- Re-encoding is not lossless; lossless codec output cannot restore discarded
|
||
source information.
|
||
- Metadata removal is best effort, not an anonymity guarantee.
|
||
- Browser preview supports fewer combinations than FFmpeg.
|
||
- MT depends on isolation; ST can still hit memory/time limits.
|
||
- Opus enablement is supported by Chromium ST/MT fixture evidence, not a
|
||
cross-browser or arbitrary-input guarantee.
|
||
- Projects do not contain source bytes by default and require reattachment.
|
||
- Cross-browser/mobile and exhaustive MT performance matrices remain
|
||
unrecorded; published support claims must stay within the tested evidence.
|
||
|
||
## Roadmap
|
||
|
||
1. Extend the evidence matrix beyond Chromium to Firefox, Safari and mobile;
|
||
broaden MT operation parity and replace the one-run performance observation
|
||
with repeated cold/warm measurements.
|
||
2. Evaluate optional editing breadth without weakening local-first guarantees:
|
||
additional reviewed transition shapes, richer scene-ranking controls and
|
||
explicit transition audio-curve choices beyond the current bounded uniform
|
||
crossfade.
|