feat: publish av-tools 0.1.0
This commit is contained in:
141
docs/ARCHITECTURE.md
Normal file
141
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Architecture
|
||||
|
||||
av-tools is a static, local-first React application. The browser receives code
|
||||
and the two self-hosted FFmpeg cores; media bytes do not leave the origin
|
||||
through an application backend. Toolbox Portal assembles the already-built
|
||||
artifact and never compiles its source.
|
||||
|
||||
## Layers and boundaries
|
||||
|
||||
```text
|
||||
Toolbox AppShell / standalone context
|
||||
└─ React application
|
||||
├─ Quick Convert and sequential Edit workflows
|
||||
├─ project + typed preset state
|
||||
├─ media inspection / preview / result views
|
||||
├─ bounded application queue + resource policy
|
||||
└─ typed command plans
|
||||
└─ EngineManager
|
||||
├─ @ffmpeg/ffmpeg wrapper worker
|
||||
├─ ST or MT same-origin core
|
||||
└─ per-job virtual filesystem
|
||||
|
||||
Browser storage boundary
|
||||
├─ object URLs (ephemeral previews)
|
||||
├─ IndexedDB (validated user presets)
|
||||
└─ OPFS cache (optional derivatives; never source in the current app)
|
||||
```
|
||||
|
||||
- `src/toolbox/` owns the one typed manifest and suite integration.
|
||||
- `src/media/` normalizes ffprobe JSON, duration/timecode, stream selection,
|
||||
playback MIME decisions and safe names.
|
||||
- `src/project/` owns the versioned, non-destructive project document and
|
||||
migrations. A source is represented by identity/metadata and must be
|
||||
reattached after load; the current application does not persist source bytes.
|
||||
- `src/presets/` defines typed built-ins and validates user presets against
|
||||
detected capabilities.
|
||||
- `src/commands/` converts validated operations to a `CommandPlan`. A plan has
|
||||
generated input/output paths, argument arrays, expected outputs,
|
||||
capabilities and cleanup intent; it is not a shell string.
|
||||
- `src/ffmpeg/` selects/loads the engine, discovers capabilities, mounts
|
||||
inputs, executes one job, reads outputs, sanitizes diagnostics and cleans.
|
||||
- `src/jobs/` provides typed queue, progress, cancellation, recovery and
|
||||
durable-metadata infrastructure. The mounted application scheduler is a
|
||||
bounded in-memory promise chain around `EngineManager`.
|
||||
- `src/waveform/`, `src/storage/` and `src/export/` keep bounded analysis,
|
||||
persistence and browser-download responsibilities outside the UI.
|
||||
|
||||
The DOM receives normalized values as text/attributes. It never renders media
|
||||
metadata as HTML. Command builders never accept arbitrary virtual paths or a
|
||||
raw command field.
|
||||
|
||||
## FFmpeg lifecycle
|
||||
|
||||
1. Inspect secure-context, worker, `crossOriginIsolated` and
|
||||
`SharedArrayBuffer` availability.
|
||||
2. Select MT only when all MT prerequisites are true (or ST when explicitly
|
||||
selected). A failed MT load is terminated and retried once with ST.
|
||||
3. Load wrapper/core assets via base-relative, same-origin URLs.
|
||||
4. Query version/build configuration/formats/codecs/encoders/filters
|
||||
sequentially and publish immutable capability state.
|
||||
5. For one job, validate resource estimates and structured settings, allocate
|
||||
a generated directory, mount/write inputs, run a generated argument array,
|
||||
read only declared outputs, and re-probe applicable results.
|
||||
6. Return result bytes and an export report, then unmount/delete virtual files.
|
||||
7. Retain the idle engine for the next sequential job; terminate it on mode
|
||||
changes, unrecoverable errors or cancellation.
|
||||
|
||||
The FFmpeg instance is a single process. `maxConcurrentFFmpegJobs` is one and
|
||||
no probe/export operations overlap on it. The application-facing scheduler is
|
||||
also bounded: it retains at most eight scheduled operations in memory,
|
||||
including the active operation, and advances its promise chain after either
|
||||
success or failure. Queue state is deliberately not persisted across a page
|
||||
reload.
|
||||
|
||||
## Project and edit model
|
||||
|
||||
A project is versioned JSON containing source descriptors, a sequential clip
|
||||
timeline, non-destructive clip in/out and transform/audio settings, stream
|
||||
selection, metadata/chapter/subtitle edits and export preset references. Clips
|
||||
do not overlap. There is no arbitrary compositing graph, collaborative state or
|
||||
server-side document.
|
||||
|
||||
The structural-export panel maps the selected clip's current range to either a
|
||||
stream-copy or preset-backed trim plan. It maps the visible timeline order to
|
||||
either a compatibility-checked stream-copy concat plan or a normalized
|
||||
re-encode plan. Normalized concat requires the user to choose what happens when
|
||||
some clips lack audio; unchecked fast compatibility fails closed.
|
||||
|
||||
Migrations are explicit version-to-version functions. Unknown future versions,
|
||||
invalid shapes and unsafe source references fail with actionable validation
|
||||
errors. Source bytes are not embedded in normal project JSON. Reattachment
|
||||
matches stable descriptors and asks the user before associating a local file.
|
||||
|
||||
## Command plans and filesystem
|
||||
|
||||
Plans use arrays such as `['-i', generatedInput, ...]`; no shell evaluates
|
||||
them. Filtergraph builders escape values for their exact FFmpeg grammar.
|
||||
Generated paths contain a job id plus bounded safe basename and never reuse a
|
||||
user-controlled directory. Expected output paths are declared before
|
||||
execution, so the result reader cannot enumerate arbitrary output.
|
||||
|
||||
Small inputs may be copied into FFmpeg's in-memory filesystem. Where supported,
|
||||
WORKERFS mounts `File`/`Blob` inputs to avoid an initial JavaScript copy; it
|
||||
does not remove WASM address-space/output constraints. OPFS is optional for
|
||||
regenerable derivatives and uses asynchronous origin-private file handles with
|
||||
an in-memory fallback. See `docs/STORAGE.md`.
|
||||
|
||||
## Result flow
|
||||
|
||||
Output bytes become a Blob with an operation-derived filename and MIME type.
|
||||
The UI creates a short-lived object URL only when the browser can preview that
|
||||
container/codec, exposes an explicit save/download action, and includes probe
|
||||
data plus warnings in the export report. Revoking a preview does not invalidate
|
||||
the retained Blob until the result is cleared. Batch derivatives may be
|
||||
assembled into a bounded ZIP.
|
||||
|
||||
For unsupported direct playback, a preview proxy is a disposable H.264/AAC MP4
|
||||
derivative rather than a source mutation. Duration and maximum width are
|
||||
bounded by validated user controls. Its cache identity includes the source
|
||||
fingerprint and proxy settings, so only an exact matching derivative is reused;
|
||||
cache failure falls back to normal generation.
|
||||
|
||||
## Cancellation and recovery
|
||||
|
||||
ffmpeg.wasm cancellation terminates the active wrapper/core worker rather than
|
||||
pretending a native FFmpeg process can receive a signal. The job becomes
|
||||
cancelled, outstanding promises are rejected, object URLs/mounts are released
|
||||
where possible, and that engine generation is discarded. The next job creates
|
||||
a fresh engine and re-runs capability discovery. A cancellation is complete
|
||||
only after the stale worker cannot publish a result.
|
||||
|
||||
## Integration boundaries
|
||||
|
||||
`AppShell` owns suite chrome, theme/personalization, help and app switching.
|
||||
av-tools passes `manifestUrl="./toolbox-app.json"` and works when context is
|
||||
missing, malformed or unavailable. It does not embed another switcher.
|
||||
|
||||
The manifest advertises secure context, workers, IndexedDB and top-level
|
||||
navigation. It does not require cross-origin isolation because ST is a valid
|
||||
fallback. Portal receives only the deterministic ZIP/checksum and places it
|
||||
under a locked target such as `apps/av/`.
|
||||
143
docs/FFMPEG_BUILD.md
Normal file
143
docs/FFMPEG_BUILD.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# FFmpeg WebAssembly build record
|
||||
|
||||
av-tools ships the published ESM assets from `@ffmpeg/core` and
|
||||
`@ffmpeg/core-mt` **0.12.10**. The browser wrapper is `@ffmpeg/ffmpeg`
|
||||
**0.12.15** and helpers are `@ffmpeg/util` **0.12.2**. All versions are exact,
|
||||
not ranges.
|
||||
|
||||
## Underlying build
|
||||
|
||||
The matching ffmpeg.wasm upstream release is `v12.15` at
|
||||
`71aa99d37c02a7b4c435275ca9ef50e612f6efa1`. Its Dockerfile selects:
|
||||
|
||||
- FFmpeg **n5.1.4** (libavcodec 59.37.100);
|
||||
- Emscripten SDK **3.1.40**;
|
||||
- production `-O3 -msimd128`;
|
||||
- pthread flags for the MT build and explicit thread disabling for ST.
|
||||
|
||||
The build configuration embedded in both copied WASM files is:
|
||||
|
||||
```text
|
||||
--target-os=none
|
||||
--arch=x86_32
|
||||
--enable-cross-compile
|
||||
--disable-asm
|
||||
--disable-stripping
|
||||
--disable-programs
|
||||
--disable-doc
|
||||
--disable-debug
|
||||
--disable-runtime-cpudetect
|
||||
--disable-autodetect
|
||||
--nm=emnm
|
||||
--ar=emar
|
||||
--ranlib=emranlib
|
||||
--cc=emcc
|
||||
--cxx=em++
|
||||
--objcc=emcc
|
||||
--dep-cc=emcc
|
||||
--extra-cflags='-I/opt/include -O3 -msimd128'
|
||||
--extra-cxxflags='-I/opt/include -O3 -msimd128'
|
||||
--enable-gpl
|
||||
--enable-libx264
|
||||
--enable-libx265
|
||||
--enable-libvpx
|
||||
--enable-libmp3lame
|
||||
--enable-libtheora
|
||||
--enable-libvorbis
|
||||
--enable-libopus
|
||||
--enable-zlib
|
||||
--enable-libwebp
|
||||
--enable-libfreetype
|
||||
--enable-libfribidi
|
||||
--enable-libass
|
||||
--enable-libzimg
|
||||
```
|
||||
|
||||
The ST string also has `--disable-pthreads --disable-w32threads
|
||||
--disable-os2threads`; the MT build supplies Emscripten pthread flags instead.
|
||||
At runtime the application executes `-version`, `-buildconf`, `-formats`,
|
||||
`-codecs`, `-encoders` and `-filters` sequentially. Presets are enabled only
|
||||
when the active core reports every required muxer, encoder and filter. Build
|
||||
flags alone are not treated as a capability guarantee.
|
||||
|
||||
## External libraries
|
||||
|
||||
The upstream build references x264 `4-cores`, x265 3.4, libvpx v1.13.1, LAME
|
||||
`master`, Ogg v1.3.4, Theora v1.1.1, Opus v1.3.1, Vorbis v1.3.3, zlib v1.2.11,
|
||||
libwebp v1.3.2, FreeType VER-2-10-4, FriBidi v1.0.9, HarfBuzz 5.2.0, libass
|
||||
0.15.0 and zimg release-3.0.5. These resolve to x264
|
||||
`33cac6b77d5b9259c552156013a817ab23119612`, x265
|
||||
`2bb5520e9596f361bf0ed81b3b8da0d7fd999069` and LAME
|
||||
`2badea1974ae36cb8312afe99cff1e6b3b5decee`. All three commits predate the
|
||||
ffmpeg.wasm v12.15 release; the branch-based x264/LAME commits remain the
|
||||
respective branch tips. `SOURCE.md` records retrieval commands.
|
||||
|
||||
## Shipped files
|
||||
|
||||
| Mode | File | Bytes | SHA-256 |
|
||||
| ---- | -------------------------- | -------: | ------------------------------------------------------------------ |
|
||||
| ST | `st/ffmpeg-core.js` | 111804 | `67a48f11645f85439f3fde4f2119042c16b374b910206b7a7a24f342e28dcae3` |
|
||||
| ST | `st/ffmpeg-core.wasm` | 32232419 | `9f57947a5bd530d8f00c5b3f2cb2a3492faa7e5d823315342d6a8656d0a6b7b7` |
|
||||
| MT | `mt/ffmpeg-core.js` | 128947 | `270a2e6ff945e173238610669a3f7132df5f9c52698a9bf708cf5c2ab6bda0de` |
|
||||
| MT | `mt/ffmpeg-core.wasm` | 32718323 | `be2c97605366b78f3f13e21b52e81a55a79e1f29c133b03a68ec187b1a2ec41a` |
|
||||
| MT | `mt/ffmpeg-core.worker.js` | 2115 | `f77898d631dc010b45c29c23cb4379c611a7d7b131bf591d08a656bb729a4ca3` |
|
||||
|
||||
`npm run vendor:ffmpeg` copies these files from `node_modules` to
|
||||
`public/vendor/ffmpeg/0.12.10/`; `npm run vendor:verify` checks versions,
|
||||
sizes, paths and digests. Vite then copies them unchanged into `dist`. A normal
|
||||
production build performs no network download.
|
||||
|
||||
## Multithread Chromium guardrail
|
||||
|
||||
The pinned multithread core can stall in Chromium when FFmpeg lets codecs use
|
||||
the browser's full reported logical-core count. This is tracked upstream in
|
||||
[ffmpeg.wasm issue 654](https://github.com/ffmpegwasm/ffmpeg.wasm/issues/654).
|
||||
av-tools keeps the pthread core but adds `-threads 2` as an input and output
|
||||
codec option for MT jobs. ST arguments remain unchanged. A regular browser
|
||||
test performs an actual MT H.264/AAC conversion; loading the core alone is not
|
||||
accepted as functional evidence.
|
||||
|
||||
Every execution also has a watchdog: an explicit shorter plan timeout wins;
|
||||
otherwise the timeout is at least two minutes, scales to thirty times the
|
||||
expected media duration, and is capped at the displayed four-hour product
|
||||
limit. Timeout is still a failure, not a successful partial export.
|
||||
|
||||
## Long-session heap guardrail
|
||||
|
||||
The pinned Emscripten core was observed to end a later heterogeneous browser
|
||||
workflow with a bare `Aborted()` even though the same command passed in a
|
||||
fresh core and all per-job virtual files had been removed. Its `tile` filter
|
||||
also aborted deterministically on a fourth consecutive contact sheet in local
|
||||
stress runs. av-tools therefore refreshes an idle core before its seventeenth
|
||||
media execution and before a fourth contact-sheet execution. Application queue
|
||||
state, sources and completed results remain outside the worker and are not
|
||||
discarded. A bare core abort also triggers recreation after cleanup so a
|
||||
manual or queued retry starts on a fresh instance.
|
||||
|
||||
This is a conservative browser-memory guardrail, not a guarantee that sixteen
|
||||
large jobs or three large contact sheets fit in memory. Cancellation and
|
||||
failed filesystem-cleanup recovery continue to recreate the core immediately.
|
||||
|
||||
## Opus regression risk
|
||||
|
||||
Pinned core 0.12.10 predates upstream ffmpeg.wasm fixes associated with stack
|
||||
size and Opus zero-byte/out-of-bounds failures (including upstream issues
|
||||
`#591` and `#775`). Therefore:
|
||||
|
||||
- decoder/encoder listing is not enough to certify Opus;
|
||||
- real ST and MT fixture encodes must produce a non-empty, re-probeable output;
|
||||
- the Opus audio and VP9/Opus presets must remain disabled with a diagnostic
|
||||
if that smoke fails;
|
||||
- a core upgrade requires new checksums, capability snapshots, legal review
|
||||
and regression tests.
|
||||
|
||||
This is a known pinned-core risk, not a claim that every Opus job fails.
|
||||
|
||||
## Licence consequence
|
||||
|
||||
The core packages declare GPL-2.0-or-later and the actual build enables GPL and
|
||||
links external codecs. The wrapper/util are MIT. av-tools is
|
||||
GPL-3.0-or-later, and the distributed application must not be described as
|
||||
MIT-only. Publish the reviewed Corresponding Source and external notices with
|
||||
the binary release. See `LICENSE`, `LICENSES/`, `THIRD_PARTY_NOTICES.md` and
|
||||
`SOURCE.md`.
|
||||
123
docs/FORMAT_SUPPORT.md
Normal file
123
docs/FORMAT_SUPPORT.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Format and feature support
|
||||
|
||||
FFmpeg capability is determined from the **loaded core**, not from filename
|
||||
extensions or this document. The app queries muxers, demuxers, decoders,
|
||||
encoders and filters; a preset is disabled with missing components listed.
|
||||
Browser playback is a separate capability checked with MIME/media APIs.
|
||||
|
||||
## 0.1.0 candidate fixture matrix
|
||||
|
||||
All 15 files are generated and re-probed with native FFmpeg 7.1.3 for test
|
||||
preparation and pass the checksum/probe verifier. That proves fixture
|
||||
provenance, not ffmpeg.wasm behavior. A 2026-07-24 Vitest run passed 303 tests
|
||||
with one conditional native subtitle-filter skip and includes broad execution
|
||||
of typed plans against native FFmpeg 7.1.3.
|
||||
|
||||
Chromium automation also runs a broad operation matrix against the real pinned
|
||||
ST core. The real MT core is queried and completes an H.264/AAC conversion, but
|
||||
the complete ST operation matrix has not been duplicated under MT. The
|
||||
following table distinguishes exact browser evidence from still-pending
|
||||
coverage; it must not be generalized to Firefox, Safari or mobile.
|
||||
|
||||
| Input | Fixture | Expected streams | Browser preview expectation | Recorded pinned-core Chromium evidence |
|
||||
| ------------------- | ---------------------------------- | -------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------- |
|
||||
| MP4 / H.264 + AAC | `pattern-av.mp4`, compatible clips | H.264 video, AAC audio | commonly playable after `canPlayType`/media load succeeds | broad ST operations; real MT conversion; exhaustive MT pending |
|
||||
| WebM / VP8 + Vorbis | `pattern.webm` | VP8 video, Vorbis audio | commonly playable in Chromium/Firefox; browser-dependent elsewhere | ST probe, remux and VP8/Vorbis re-encode; MT pending |
|
||||
| WAV / PCM s16le | `tone.wav` | mono PCM | commonly playable | ST WAV output re-probed; exact input fixture and MT pending |
|
||||
| MP3 | `tone.mp3` | mono MP3 | commonly playable | ST MP3 outputs re-probed; exact input fixture and MT pending |
|
||||
| FLAC | `tone.flac` | mono FLAC | browser/version-dependent | ST input and FLAC output re-probed; MT pending |
|
||||
| MP3 + attached PNG | `attached-cover.mp3` | MP3 audio, attached-picture PNG | audio may play; cover rendering is app-owned | ST probe/stream selection; MT pending |
|
||||
| MP4 tags | `metadata.mp4` | H.264/AAC | same as MP4 | ST import/removal and output metadata round-trip; MT pending |
|
||||
| MP4 chapters | `chapters.mp4` | H.264/AAC/data plus two chapters | media may play; browser does not expose all chapter data | ST generated-output chapter round-trip; fixture import pending |
|
||||
| malformed MP4 | `truncated.mp4` | no usable streams expected | must not preview | ST bounded failure, cleanup and recovery; MT pending |
|
||||
|
||||
The runtime query for pinned core 0.12.10 recorded 341 demuxers, 173 muxers,
|
||||
515 decoders, 204 encoders and 465 filters (1,698 entries total). Runtime
|
||||
discovery only says that FFmpeg exposes an item; the application must still
|
||||
build a valid plan, the input must be compatible, resources must fit and the
|
||||
browser may still be unable to preview the result.
|
||||
|
||||
## Output presets
|
||||
|
||||
Preset availability always depends on detected runtime requirements:
|
||||
|
||||
| Preset family | Required examples | Browser result preview |
|
||||
| ------------------ | ---------------------------------------- | ------------------------------------------------------ |
|
||||
| MP4 H.264/AAC | `mp4`, `libx264`, `aac` | usually, but verify actual output |
|
||||
| WebM VP9/Opus | `webm`, `libvpx-vp9`, `libopus` | browser-dependent; Opus core regression gate applies |
|
||||
| WebM VP8/Vorbis | `webm`, `libvpx`, `libvorbis` | commonly Chromium/Firefox |
|
||||
| Matroska H.264/AAC | `matroska`, `libx264`, `aac` | generally download-only in browsers |
|
||||
| animated WebP | `webp`, `libwebp_anim` | browser-dependent image preview |
|
||||
| GIF | `gif`, `gif`, `palettegen`, `paletteuse` | broadly image-previewable |
|
||||
| MP3 | `mp3`, `libmp3lame` | commonly playable |
|
||||
| M4A/AAC | `ipod`, `aac` | commonly playable |
|
||||
| Ogg Vorbis | `ogg`, `libvorbis` | browser-dependent |
|
||||
| Opus | `opus`, `libopus` | browser-dependent; pinned-core regression gate applies |
|
||||
| WAV/PCM | `wav`, `pcm_s16le` | commonly playable |
|
||||
| FLAC | `flac`, `flac` | browser-dependent |
|
||||
|
||||
“Lossless” applies only to a lossless audio codec such as PCM/FLAC relative to
|
||||
its decoded samples. It does not make a lossy source lossless. Stream-copy
|
||||
remux avoids re-encoding compatible streams but may change container metadata.
|
||||
|
||||
## Structural editing
|
||||
|
||||
- The Edit workspace exposes fast/accurate trim for the selected timeline range
|
||||
and fast/normalized concat for clips in visible timeline order. Availability
|
||||
still fails closed against loaded-core capabilities.
|
||||
- Fast trim uses stream copy where container/stream/timestamp constraints are
|
||||
compatible. It can begin at a nearby keyframe and is not promised
|
||||
frame-accurate.
|
||||
- Accurate trim decodes/re-encodes selected streams and can still be limited by
|
||||
source timestamps.
|
||||
- Stream-copy concat requires compatible codec parameters, time bases,
|
||||
dimensions, sample layout and selected streams; the UI requires a completed
|
||||
compatibility check before enabling it. Otherwise normalized re-encoding is
|
||||
required.
|
||||
- Normalized concat uses an explicit export preset and requires an explicit
|
||||
insert-silence, drop-all-audio or reject policy when clips have mismatched
|
||||
audio presence.
|
||||
- Crop, resize, frame-rate conversion, fades, normalization, burn-in and
|
||||
contact sheets require specific filters discovered at runtime.
|
||||
- Even-dimension correction is applied when the selected pixel format/encoder
|
||||
requires it and is reported in the plan/result.
|
||||
|
||||
## Subtitles
|
||||
|
||||
The text fixtures cover SRT, WebVTT and ASS import with explicit time shift.
|
||||
The Chromium ST matrix converts a generated SRT to WebVTT with a time shift,
|
||||
soft-muxes it into Matroska, burns it into H.264 video and extracts the
|
||||
soft-muxed stream. The exact standalone WebVTT and ASS fixtures and equivalent
|
||||
MT paths are not separately browser-smoked. Soft-subtitle muxing depends on
|
||||
container support (for example converted text in MP4 versus text/ASS in
|
||||
Matroska). ASS burn-in requires `subtitles`/libass and local font support; no
|
||||
remote font is fetched. Bitmap subtitle streams are identified but are not
|
||||
claimed to support arbitrary conversion or OCR. Unsupported container/codec
|
||||
combinations must remain disabled with reasons.
|
||||
|
||||
## Metadata and chapters
|
||||
|
||||
Format and stream tags can be inspected. Edits/removal are limited to fields
|
||||
the selected muxer writes; “remove metadata” is best effort and the output is
|
||||
re-probed to report remaining structural/encoder/container fields. Stream
|
||||
language, title and dispositions are separate from format tags.
|
||||
|
||||
Chapters use validated integer time bases, monotonic non-overlapping ranges and
|
||||
escaped titles. Some output containers do not support chapters. Browser media
|
||||
elements do not reliably expose them, so ffprobe round-trip is authoritative.
|
||||
|
||||
## Known unsupported or conditional cases
|
||||
|
||||
- remote protocols, live capture, DRM/encrypted media and network playlists;
|
||||
- hardware-accelerated encoding or desktop-FFmpeg performance;
|
||||
- files above the 2 GiB WASM input ceiling or stricter product policy;
|
||||
- arbitrary filters/commands, arbitrary compositing and nonlinear overlap;
|
||||
- frame-accurate stream-copy trimming;
|
||||
- complete metadata erasure/anonymity;
|
||||
- browser preview of every FFmpeg-readable result;
|
||||
- Opus presets until real non-empty ST/MT encoding smoke passes for core
|
||||
0.12.10;
|
||||
- any capability omitted by the active core's runtime query.
|
||||
|
||||
Update this file only from re-probed generated/public-domain fixtures and
|
||||
record the engine mode, browser and exact core version.
|
||||
81
docs/KNOWN_LIMITATIONS.md
Normal file
81
docs/KNOWN_LIMITATIONS.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Known limitations
|
||||
|
||||
This is the conservative 0.1.0 development baseline. A capability appearing in
|
||||
FFmpeg output does not remove these product/browser/resource limits.
|
||||
|
||||
## Functional
|
||||
|
||||
- The editor is a sequential light editor: no overlapping tracks, arbitrary
|
||||
compositing, collaborative state or professional nonlinear workflow.
|
||||
- Remote URLs/protocols, live capture, DRM/encrypted media and server-side
|
||||
transcoding are outside scope.
|
||||
- Fast stream-copy trim can start at a nearby keyframe; it is not promised
|
||||
frame-accurate. Accurate mode requires decoding/re-encoding.
|
||||
- Stream-copy concatenate works only when selected streams and codec/container
|
||||
parameters are compatible. Other input needs normalized re-encoding.
|
||||
- Re-encoding is not lossless. PCM/FLAC can preserve decoded audio samples but
|
||||
cannot restore information already lost in a source.
|
||||
- Metadata removal is best effort. Muxers may add structural/encoder fields,
|
||||
unknown structures can remain, and this is not an anonymity guarantee.
|
||||
- Subtitle support is text/container/filter-dependent. No OCR or universal
|
||||
bitmap-subtitle conversion is promised; burn-in never fetches remote fonts.
|
||||
- Projects omit source bytes by default and therefore require local-file
|
||||
reattachment after reopening.
|
||||
- The operation queue is bounded to eight scheduled items, runs one FFmpeg
|
||||
operation at a time and exists only in page memory. Reloading or closing the
|
||||
page discards queued work.
|
||||
- A file-generating batch and its managed result collection are bounded to 255
|
||||
outputs, leaving the 256th ZIP entry available for an optional export report.
|
||||
- Preview proxies are bounded, lossy H.264/AAC derivatives for local playback;
|
||||
they do not replace the source or guarantee that every browser can play the
|
||||
result.
|
||||
|
||||
## Format and browser
|
||||
|
||||
- There is no “all FFmpeg formats supported” claim. The active core gates
|
||||
presets from runtime formats/codecs/encoders/filters.
|
||||
- FFmpeg-readable output may be download-only because browser media elements
|
||||
support fewer container/codec combinations.
|
||||
- Browser support, codec policy and memory differ across Chromium, Firefox and
|
||||
Safari and across desktop/mobile versions.
|
||||
- Core 0.12.10 has an Opus regression risk. Opus remains release-gated until
|
||||
non-empty, re-probeable ST and MT fixture outputs pass.
|
||||
- MT needs HTTPS, COOP/COEP/CORP and `SharedArrayBuffer`. ST fallback works
|
||||
without isolation but is not a guarantee that a large job fits.
|
||||
- Top-level navigation is supported; iframe embedding is not.
|
||||
|
||||
## Resource and performance
|
||||
|
||||
- ffmpeg.wasm documents a 2 GiB input ceiling; the application uses stricter
|
||||
1.5 GiB hard input/total and 1 GiB estimated-output defaults. Practical
|
||||
limits may be far lower.
|
||||
- Only one FFmpeg job runs at once. Core startup downloads/instantiates roughly
|
||||
32 MiB of WASM and capability discovery adds latency.
|
||||
- To bound retained native heap state in long heterogeneous sessions, an idle
|
||||
core is refreshed before its seventeenth media execution and before a fourth
|
||||
contact sheet. The queue and completed results survive, but that next job
|
||||
pays core startup and capability discovery latency again.
|
||||
- WebAssembly can be substantially slower than native FFmpeg and has no
|
||||
supported hardware encoder.
|
||||
- Mobile/background-tab throttling, browser quota eviction and page
|
||||
termination can interrupt a job. Progress is an estimate.
|
||||
- OPFS/IndexedDB are origin-private, quota-managed and not guaranteed durable.
|
||||
|
||||
## Deployment and release
|
||||
|
||||
- A Portal deployment must permit `media-src 'self' blob:`, serve WASM as
|
||||
`application/wasm`, retain the isolation headers and cache only versioned
|
||||
FFmpeg assets as immutable. These are deployment requirements and require
|
||||
verification against the live container.
|
||||
- The application is GPL-3.0-or-later and its native legal files and pinned
|
||||
source identities are included in the release record. Publish the production
|
||||
ZIP/checksum, exact `v0.1.0` source and FFmpeg Corresponding Source bundle
|
||||
together; development-only smoke artifacts must not be distributed.
|
||||
- Chromium covers a broad real pinned-core ST operation matrix, a real MT
|
||||
H.264/AAC conversion, MT-to-ST fallback, nested static/Toolbox-context
|
||||
loading and one two-size ST/MT performance baseline. Firefox, Safari, mobile,
|
||||
exhaustive MT feature parity and repeated performance measurements remain
|
||||
unrecorded. Live Portal container/header verification is a deployment gate.
|
||||
|
||||
See `docs/FORMAT_SUPPORT.md`, `docs/PERFORMANCE.md`,
|
||||
`docs/PORTAL_REQUIREMENTS.md` and `docs/RELEASE.md` for testable detail.
|
||||
81
docs/PERFORMANCE.md
Normal file
81
docs/PERFORMANCE.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Performance and resource observations
|
||||
|
||||
ffmpeg.wasm runs native media code compiled to WebAssembly. It is normally much
|
||||
slower than desktop FFmpeg and may hold source, decoded frames, filters and
|
||||
output at once. No hardware encoder is used.
|
||||
|
||||
## Fixed download footprint
|
||||
|
||||
Uncompressed self-hosted core assets for 0.12.10:
|
||||
|
||||
| Mode | JavaScript/worker | WASM | Total |
|
||||
| ------------- | ------------------: | -----------: | -----------: |
|
||||
| single-thread | 111,804 B | 32,232,419 B | 32,344,223 B |
|
||||
| multithread | 128,947 B + 2,115 B | 32,718,323 B | 32,849,385 B |
|
||||
|
||||
Only the selected mode is loaded. HTTP compression may reduce JavaScript
|
||||
transfer but not necessarily WASM to the same degree; report transfer and
|
||||
decoded sizes separately. Versioned core assets may be cached immutably.
|
||||
|
||||
## Benchmark record
|
||||
|
||||
One opt-in pinned-core baseline was recorded on 2026-07-24 with Chrome for
|
||||
Testing 149.0.7827.55 (Playwright 1.61.1), Linux 7.0.0-28 on Ubuntu 24.04,
|
||||
Intel Core i7-8700 (6 cores/12 threads) and 31 GiB RAM.
|
||||
`navigator.hardwareConcurrency` reported 12 and every sample was
|
||||
cross-origin-isolated. Each row used a fresh browser context; the first core
|
||||
fetch may populate the browser HTTP cache, so later rows are explicitly
|
||||
potentially warm-cache observations.
|
||||
|
||||
Preset: MP4, H.264/libx264 CRF 23 medium, AAC. Times are milliseconds.
|
||||
“Export end-to-end” spans the Convert click through result construction,
|
||||
cleanup, verification re-probe and visible result. The three narrower engine
|
||||
columns come from the export validation report.
|
||||
|
||||
| Source | Input bytes | Mode | Init | Probe | FFmpeg exec | Output read | Cleanup | Export end-to-end | Output bytes | Observed JS heap |
|
||||
| --------------------------------------- | ----------: | ----------------- | ----: | ----: | ----------: | ----------: | ------: | ----------------: | -----------: | ---------------: |
|
||||
| 2 s, 160×90 H.264/AAC synthetic pattern | 34,057 | ST | 294.4 | 131.4 | 315.6 | 1.0 | 1.2 | 422.4 | 68,700 | 10,000,000 B |
|
||||
| same | 34,057 | MT (`-threads 2`) | 914.5 | 126.4 | 294.3 | 1.3 | 1.9 | 416.8 | 68,753 | 10,000,000 B |
|
||||
| 6 s, 640×360 H.264/AAC synthetic loop | 526,632 | ST | 270.2 | 163.6 | 6,854.1 | 1.3 | 0.8 | 7,030.9 | 585,981 | 15,200,000 B |
|
||||
| same | 526,632 | MT (`-threads 2`) | 946.3 | 121.6 | 4,053.1 | 1.6 | 0.5 | 4,497.5 | 585,884 | 11,200,000 B |
|
||||
|
||||
Command:
|
||||
|
||||
```sh
|
||||
npm run benchmark:browser
|
||||
```
|
||||
|
||||
Result: **1 passed (18.2 s)**; test body 17.6 s. Chromium exposed
|
||||
`performance.memory.usedJSHeapSize`, but this coarse post-operation value is
|
||||
not a peak-WASM-memory measurement. One run is not a statistical distribution;
|
||||
cold-cache repetitions, median/range, mobile and other-browser measurements
|
||||
remain release-quality evidence gaps. Never present this workstation as a
|
||||
universal promise.
|
||||
|
||||
## Measurement boundaries
|
||||
|
||||
- initialization: wrapper construction through successful capability query;
|
||||
- probe: input mount/write through parsed ffprobe JSON;
|
||||
- conversion: accepted command through exit code;
|
||||
- output read: exit through Blob/result construction;
|
||||
- cleanup: final result read through job directory unmount/delete;
|
||||
- cancellation: cancel request through worker termination and recoverable idle
|
||||
state (fresh-engine load is a separate measure).
|
||||
|
||||
Progress events are experimental estimates and not a timer. Use monotonic
|
||||
browser timing around lifecycle boundaries.
|
||||
|
||||
## Product limits
|
||||
|
||||
Default soft input warning is 256 MiB; hard single/total input is 1.5 GiB.
|
||||
Estimated output warns at 512 MiB and is blocked at 1 GiB. FFmpeg jobs are
|
||||
serial; the application retains at most eight scheduled operations in its
|
||||
in-memory queue. Timeline clips are capped at 100, streams per input at 64,
|
||||
images at 8192×8192/67,108,864 pixels, generated output files at 255,
|
||||
contact-sheet cells at 500 and runtime at four hours. The documented
|
||||
ffmpeg.wasm input ceiling is 2 GiB, but real limits can be substantially lower
|
||||
on mobile or memory-constrained browsers.
|
||||
|
||||
Lowering resolution, duration, frame rate, passes and output count reduces
|
||||
work. MT can be faster but consumes more memory/CPU and requires isolation; ST
|
||||
is not inherently a low-memory guarantee.
|
||||
115
docs/PORTAL_REQUIREMENTS.md
Normal file
115
docs/PORTAL_REQUIREMENTS.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Toolbox Portal requirements
|
||||
|
||||
av-tools is built and packaged independently. Toolbox Portal consumes the
|
||||
exact ZIP and SHA-256 from a reviewed release lock; it must never import the
|
||||
app source into its React build.
|
||||
|
||||
## Required serving behavior
|
||||
|
||||
All app, wrapper, core, WASM and worker responses are same-origin and served
|
||||
over HTTPS (or trusted localhost). Apply:
|
||||
|
||||
```text
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Resource-Policy: same-origin
|
||||
X-Content-Type-Options: nosniff
|
||||
```
|
||||
|
||||
Serve `.wasm` as `application/wasm`, JavaScript modules as a JavaScript MIME
|
||||
type and JSON as `application/json`. Test real responses under
|
||||
`/apps/av/vendor/ffmpeg/0.12.10/...`; a file-extension allowlist alone does not
|
||||
prove the MIME type.
|
||||
|
||||
Suggested CSP:
|
||||
|
||||
```text
|
||||
default-src 'self';
|
||||
script-src 'self' 'wasm-unsafe-eval';
|
||||
worker-src 'self' blob:;
|
||||
connect-src 'self';
|
||||
img-src 'self' blob: data:;
|
||||
media-src 'self' blob:;
|
||||
font-src 'self';
|
||||
style-src 'self';
|
||||
object-src 'none';
|
||||
frame-src 'none';
|
||||
frame-ancestors 'none';
|
||||
base-uri 'none';
|
||||
form-action 'none';
|
||||
```
|
||||
|
||||
`'wasm-unsafe-eval'` is the narrow WebAssembly permission; broad
|
||||
`'unsafe-eval'` is unnecessary. `worker-src blob:` covers the wrapper-created
|
||||
worker path. `media-src blob:` permits local source/result previews.
|
||||
|
||||
## Immutable reference and active-worktree state
|
||||
|
||||
Portal revision `bda9da044d61c109afd9969d357cc27f76648938` already sends COOP
|
||||
`same-origin`, COEP `require-corp`, CORP `same-origin`, `script-src 'self'
|
||||
'wasm-unsafe-eval'`, and `worker-src 'self' blob:`. Its packaged nginx CSP does
|
||||
**not** declare `media-src`; `default-src 'self'` therefore blocks local
|
||||
`blob:` audio/video URLs. The expected browser diagnostic is equivalent to:
|
||||
|
||||
```text
|
||||
Refused to load media from 'blob:…' because it violates
|
||||
"default-src 'self'".
|
||||
```
|
||||
|
||||
Smallest required Portal change: append exactly
|
||||
`media-src 'self' blob:;` to that CSP. This permits only same-origin and
|
||||
in-memory Blob media; it does not permit remote media hosts, inline script or
|
||||
eval. If the Portal is not changed, conversion/download can remain available
|
||||
but Blob source/result preview must show a clear disabled reason.
|
||||
|
||||
As inspected on 2026-07-24, the active
|
||||
`/mnt/DATA/git/toolbox-portal/deploy/nginx.conf` contains that exact directive
|
||||
as a local worktree patch. The worktree still resolves to the immutable
|
||||
revision above, so the patch is useful for local deployment testing but is not
|
||||
a committed Portal revision and must not be cited as published release
|
||||
evidence. The clean-reference assembly smoke therefore continues to require
|
||||
`--allow-known-header-gap` until a reviewed Portal commit contains the fix.
|
||||
|
||||
The nginx profile includes `.wasm` in its static allowlist/gzip types but
|
||||
relies on the base image `mime.types`. Verify `Content-Type:
|
||||
application/wasm` against the pinned image; add an explicit mapping if the
|
||||
response differs.
|
||||
|
||||
## Caching
|
||||
|
||||
The immutable Portal reference's cache map makes Vite content-hashed assets
|
||||
immutable but leaves versioned FFmpeg cores at `no-cache`. Correctness is
|
||||
unaffected.
|
||||
For performance, the exact directory
|
||||
`apps/av/vendor/ffmpeg/0.12.10/{st,mt}/` may receive:
|
||||
|
||||
```text
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
```
|
||||
|
||||
Only do this while a version path is immutable and verified against
|
||||
`version.json`. Keep `index.html`, manifest/catalog/release JSON and legal
|
||||
notices revalidated.
|
||||
|
||||
## Nested path and context
|
||||
|
||||
The artifact uses `base: './'`, `entry: './'`, a relative icon, a relative
|
||||
manifest URL and relative core URLs. Portal may therefore target `av`, yielding
|
||||
`apps/av/`. Do not rewrite files to root-absolute `/assets` or `/vendor`.
|
||||
Use top-level navigation; embedding is unsupported.
|
||||
|
||||
The app remains usable without valid Toolbox context. It validates context
|
||||
version/origin and falls back to standalone shell behavior without moving or
|
||||
duplicating suite controls.
|
||||
|
||||
## Fallback contract
|
||||
|
||||
MT requires all isolation headers plus secure context and
|
||||
`SharedArrayBuffer`. When any prerequisite or MT load fails, the app terminates
|
||||
that instance and loads ST. The manifest intentionally declares
|
||||
`crossOriginIsolated: false` because isolation is not an unconditional
|
||||
requirement; it declares workers, secure context, IndexedDB and top-level
|
||||
context.
|
||||
|
||||
See `scripts/portal-assembly-smoke.mjs` and `docs/RELEASE.md` for the temporary,
|
||||
non-mutating assembly procedure.
|
||||
40
docs/REFERENCE_IMPLEMENTATIONS.md
Normal file
40
docs/REFERENCE_IMPLEMENTATIONS.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Reference implementations
|
||||
|
||||
Inspection date: **2026-07-24**.
|
||||
|
||||
The references were cloned beside av-tools under
|
||||
`/mnt/DATA/git/references/`; no reference repository was nested in this
|
||||
working tree. Revisions are full immutable commit ids.
|
||||
|
||||
| Repository | Revision inspected | Used for | Copied or adapted |
|
||||
| -------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| av-tools, `git@git.add-ideas.de:zemion/av-tools.git` | no revision: canonical `main` was unborn and had no commits | canonical destination and licence/history check | the empty repository was scaffolded in place; no prior code existed |
|
||||
| Toolbox SDK, `https://git.add-ideas.de/zemion/toolbox-sdk.git` | `53c40a61ba1581246f65773fcbb1c1cfd31ac98e` (SDK 0.2.2) | manifest schema/definition, `AppShell`, context behavior, shared styles, token-free registry, nested-path checks and toolchain baseline | published 0.2.2 packages are direct dependencies; documented usage patterns were followed; no SDK source file was copied |
|
||||
| Toolbox Portal, `https://git.add-ideas.de/zemion/toolbox-portal.git` | `bda9da044d61c109afd9969d357cc27f76648938` | release ZIP/sidecar/lock formats, independent assembly, catalogue behavior, path security, nested deployment and production headers | no portal application source was copied; the documented artifact contract informed an independent release packager and smoke harness |
|
||||
| ffmpeg.wasm, `https://github.com/ffmpegwasm/ffmpeg.wasm.git` | `f876f907c7e9b9bf51d4ed0b913a855a63ae63fc` | current API, worker lifecycle, filesystem, loading, ST/MT behavior, licence documentation and build layout | no reference-worktree code was copied; exact published npm packages are dependencies and their five core assets are copied verbatim by the vendor script |
|
||||
|
||||
For the **pinned** package set, the more specific upstream release tag
|
||||
`v12.15` at `71aa99d37c02a7b4c435275ca9ef50e612f6efa1` was also inspected. It
|
||||
contains `@ffmpeg/ffmpeg` 0.12.15 and core/core-mt 0.12.10. That revision's
|
||||
build selects FFmpeg n5.1.4.
|
||||
|
||||
The user's active `/mnt/DATA/git/toolbox-portal` worktree still has Portal
|
||||
revision `bda9da044d61c109afd9969d357cc27f76648938` as `HEAD`, but currently
|
||||
contains a local `media-src 'self' blob:` nginx CSP patch. That mutable
|
||||
worktree state is documented for deployment testing only; it does not replace
|
||||
the immutable reference in the table.
|
||||
|
||||
Official behavior references:
|
||||
|
||||
- `https://ffmpegwasm.netlify.app/docs/overview/`
|
||||
- `https://ffmpegwasm.netlify.app/docs/getting-started/usage/`
|
||||
- `https://ffmpegwasm.netlify.app/docs/api/ffmpeg/classes/ffmpeg/`
|
||||
- `https://ffmpegwasm.netlify.app/docs/faq/`
|
||||
- `https://ffmpeg.org/ffmpeg.html`
|
||||
- `https://ffmpeg.org/ffprobe.html`
|
||||
- `https://ffmpeg.org/ffmpeg-formats.html`
|
||||
- `https://ffmpeg.org/ffmpeg-filters.html`
|
||||
- `https://ffmpeg.org/ffmpeg-utils.html`
|
||||
|
||||
Command plans are based on those primary references and runtime capability
|
||||
queries, not unverified command snippets.
|
||||
130
docs/RELEASE.md
Normal file
130
docs/RELEASE.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Release process
|
||||
|
||||
av-tools is licensed GPL-3.0-or-later. A publishable release is the exact
|
||||
reviewed source tag, production ZIP/checksum, Corresponding Source and
|
||||
third-party notices distributed together. The steps below fail closed where
|
||||
practical.
|
||||
|
||||
## 1. Resolve legal/source identity
|
||||
|
||||
The repository already declares the application licence and immutable
|
||||
third-party source identities. Before any distribution:
|
||||
|
||||
1. commit the reviewed GPL-3.0-or-later source;
|
||||
2. create and verify annotated tag `v0.1.0` at that exact commit;
|
||||
3. push the commit and tag to the canonical public repository;
|
||||
4. assemble, review and publish corresponding source and exact external
|
||||
notices for both core packages and linked libraries;
|
||||
5. keep the manifest/package metadata, `SOURCE.md`, ZIP and tag version aligned.
|
||||
|
||||
`SOURCE.md` pins FFmpeg, ffmpeg.wasm, x264 and LAME by full commit. Do not
|
||||
describe the complete bundle as MIT-only.
|
||||
|
||||
## 2. Reproducible checks
|
||||
|
||||
With Node 22+ and the lockfile:
|
||||
|
||||
```sh
|
||||
npm ci
|
||||
npm run fixtures:verify
|
||||
npm run check
|
||||
npm run test:browser
|
||||
```
|
||||
|
||||
`check` verifies manifest/vendor assets, types, lint/format, unit tests,
|
||||
production build and `toolbox-check dist`. Browser tests must exercise ST, MT,
|
||||
ST fallback, cancellation/recovery, nested paths and no runtime CDN request.
|
||||
Record exact results in the release report.
|
||||
|
||||
## 3. Package
|
||||
|
||||
Normal packaging validates the root GPL version 3 text and matching
|
||||
`GPL-3.0-or-later` package/manifest metadata:
|
||||
|
||||
```sh
|
||||
npm run package:release
|
||||
npm run source:package
|
||||
```
|
||||
|
||||
The legacy development-only escape hatch is retained only for checking an
|
||||
otherwise unlicensed temporary fixture:
|
||||
|
||||
```sh
|
||||
npm run package:release -- --allow-unlicensed-development-smoke
|
||||
```
|
||||
|
||||
Pass `--force` only to replace the exact same-version ZIP and sidecar. Output:
|
||||
|
||||
```text
|
||||
release/av-tools-0.1.0.zip
|
||||
release/av-tools-0.1.0.zip.sha256
|
||||
release/ffmpeg-core-0.12.10-corresponding-source.tar.xz
|
||||
release/ffmpeg-core-0.12.10-corresponding-source.tar.xz.sha256
|
||||
```
|
||||
|
||||
Verify independently:
|
||||
|
||||
```sh
|
||||
node scripts/checksum-release.mjs
|
||||
(cd release && sha256sum -c ffmpeg-core-0.12.10-corresponding-source.tar.xz.sha256)
|
||||
unzip -l release/av-tools-0.1.0.zip
|
||||
tar -tJf release/ffmpeg-core-0.12.10-corresponding-source.tar.xz
|
||||
```
|
||||
|
||||
The packager:
|
||||
|
||||
- rejects symlinks, special files, traversal, absolute/ambiguous/duplicate
|
||||
archive paths and credential-like files;
|
||||
- rejects source maps and local absolute paths in text release files;
|
||||
- requires entry, manifest, icon and every declared asset;
|
||||
- requires root `CHANGELOG.md`, `SOURCE.md`, `THIRD_PARTY_NOTICES.md`,
|
||||
`LICENSES/`, `assets/` and `vendor/`;
|
||||
- adds installed runtime npm licence files;
|
||||
- sorts paths, stores files as mode 0644, uses 1980-01-01 00:00:00 UTC and
|
||||
produces deterministic ZIP32 bytes;
|
||||
- excludes `node_modules`, test fixtures and source because it starts from
|
||||
`dist` plus an explicit legal/document list.
|
||||
|
||||
When used against a fixture without an application licence, the escape hatch adds
|
||||
`DEVELOPMENT-ONLY-NOT-FOR-DISTRIBUTION.txt` inside the ZIP.
|
||||
|
||||
## 4. Portal assembly smoke
|
||||
|
||||
Use the clean reference clone beside av-tools, never the user's active Portal
|
||||
worktree:
|
||||
|
||||
```sh
|
||||
npm run portal:smoke -- \
|
||||
--artifact release/av-tools-0.1.0.zip \
|
||||
--allow-known-header-gap
|
||||
```
|
||||
|
||||
The smoke harness clones the exact Portal HEAD to a temporary directory,
|
||||
copies the ZIP there, writes a temporary one-app lock derived from
|
||||
`release/toolbox.lock.example.json`, runs `npm ci`, tests, build and assembly,
|
||||
then verifies the generated catalogue/release records and copied FFmpeg assets.
|
||||
The temporary directory is removed unless `--keep-temp` is explicitly passed.
|
||||
No machine-specific path is committed and no Portal worktree is changed.
|
||||
|
||||
Without `--allow-known-header-gap`, the clean immutable Portal reference's CSP
|
||||
omission of `media-src blob:` is a failing deployment check. The override still
|
||||
reports the gap and is only for testing archive assembly. See
|
||||
`docs/PORTAL_REQUIREMENTS.md`.
|
||||
|
||||
The user's active `/mnt/DATA/git/toolbox-portal` worktree contains the narrow
|
||||
directive as a local patch, but the smoke intentionally does not consume that
|
||||
mutable worktree. Until the patch exists in a reviewed immutable Portal
|
||||
revision, retain the clean-reference failure/override behavior above.
|
||||
|
||||
## 5. Publish only reviewed bytes
|
||||
|
||||
From a clean `v0.1.0` checkout, rerun all checks, create the ZIP once, verify
|
||||
the sidecar, assemble that exact ZIP through Portal, and publish the immutable
|
||||
ZIP, `.zip.sha256`, manifest, changelog, Corresponding Source and notices. Put
|
||||
its exact lowercase digest/version/id/target in a reviewed Portal lock. Never
|
||||
use mutable “latest” URLs or credentials in a lock.
|
||||
|
||||
The final report must state inspected commits, exact packages/build config,
|
||||
capabilities, completed tested milestones, all check results, artifact paths,
|
||||
Portal result, fixture provenance, licence status, headers, gaps, performance
|
||||
and browser limits. A UI or command builder alone is not a completed milestone.
|
||||
140
docs/SECURITY.md
Normal file
140
docs/SECURITY.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Security and privacy
|
||||
|
||||
## Trust boundary
|
||||
|
||||
Imported media, filenames, container metadata, tags, chapters, subtitles,
|
||||
attachments, codec logs, project JSON and Toolbox context are untrusted. FFmpeg
|
||||
parsing occurs in a worker but still consumes memory/CPU in the user's browser.
|
||||
The application cannot make a malicious codec safe; it limits exposure and
|
||||
keeps cleanup/recovery explicit.
|
||||
|
||||
The supported product has:
|
||||
|
||||
- no upload or processing backend;
|
||||
- no telemetry, analytics or third-party runtime script;
|
||||
- no remote-media protocol or automatic external fetch;
|
||||
- no shell/native command execution;
|
||||
- no arbitrary FFmpeg command input;
|
||||
- no persistent source copy in the current application.
|
||||
|
||||
Browser extension, operating-system, reverse-proxy and compromised dependency
|
||||
behavior remain outside this boundary.
|
||||
|
||||
## Paths and command construction
|
||||
|
||||
User names are display labels only. Virtual filesystem paths are generated from
|
||||
job ids plus sanitized, length-bounded basenames; separators, traversal,
|
||||
control characters, reserved names and collisions are handled centrally.
|
||||
Outputs must be declared in a typed plan before execution.
|
||||
|
||||
Commands are argument arrays assembled from discriminated operation/preset
|
||||
types. No shell parses them. Filters, metadata, concat lists, chapter metadata
|
||||
and subtitle values use syntax-specific escaping; one escaping routine is not
|
||||
reused across incompatible grammars. Capability requirements are checked before
|
||||
execution. Logs are line/byte bounded and local path-like names are redacted in
|
||||
exportable diagnostics.
|
||||
|
||||
## Rendering and external URLs
|
||||
|
||||
Metadata is rendered as React text, never `dangerouslySetInnerHTML`. Subtitle
|
||||
text is parsed and rendered or sent through a generated local file; it is not
|
||||
inserted as HTML. Extracted URLs are inert text. The application does not open
|
||||
links from media automatically. No remote font is fetched; ASS burn-in uses
|
||||
packaged/system-generic font configuration only when the required local
|
||||
capability is available.
|
||||
|
||||
Preview/download URLs are `blob:` values made from local bytes. They are
|
||||
revoked on replacement, project close and unmount. Browser support for a
|
||||
container is not evidence that its contents are safe or fully decodable.
|
||||
|
||||
## Resource controls
|
||||
|
||||
The administrator-adjustable defaults are product limits, not FFmpeg limits:
|
||||
|
||||
| Limit | Default |
|
||||
| --------------------------------- | ----------------: |
|
||||
| soft input warning | 256 MiB |
|
||||
| hard single input | 1536 MiB |
|
||||
| hard total input | 1536 MiB |
|
||||
| soft output estimate | 512 MiB |
|
||||
| hard output estimate | 1024 MiB |
|
||||
| concurrent FFmpeg jobs | 1 |
|
||||
| scheduled operations in memory | 8 |
|
||||
| timeline clips | 100 |
|
||||
| streams per input | 64 |
|
||||
| image dimension / pixel count | 8192 / 67,108,864 |
|
||||
| generated outputs / contact cells | 255 / 500 |
|
||||
| job runtime | 4 hours |
|
||||
|
||||
Before allocation, the application estimates source/output bytes, duration,
|
||||
resolution, frame/pixel work, passes, retained results and output count.
|
||||
Estimates are labelled as estimates. Hard-limit violations do not start; soft
|
||||
violations explain resolution/duration/output-count alternatives. The
|
||||
255-output limit keeps a whole managed batch saveable in the 256-entry ZIP
|
||||
limit with an optional report. Memory-constrained devices receive stronger
|
||||
warnings only when a reliable browser signal exists.
|
||||
|
||||
WASM itself has a documented 2 GiB input ceiling; practical browser limits can
|
||||
be much lower, especially when input and output coexist. Archive/export and
|
||||
cache operations also enforce entry, total-size and path limits.
|
||||
|
||||
## Workers and cancellation
|
||||
|
||||
Wrapper, core, pthread and waveform workers are same-origin or short-lived
|
||||
`blob:` workers created by trusted application code. OPFS caching currently
|
||||
uses asynchronous browser file handles rather than a separate storage worker.
|
||||
MT requires shared memory and cross-origin isolation. ST loads when isolation
|
||||
is absent. Cancelling FFmpeg terminates and discards its worker/core; a new
|
||||
instance is required before another job. Stale generations cannot commit a
|
||||
result.
|
||||
|
||||
## Content Security Policy
|
||||
|
||||
Tested deployment policy target:
|
||||
|
||||
```text
|
||||
default-src 'self';
|
||||
script-src 'self' 'wasm-unsafe-eval';
|
||||
worker-src 'self' blob:;
|
||||
connect-src 'self';
|
||||
img-src 'self' blob: data:;
|
||||
media-src 'self' blob:;
|
||||
font-src 'self';
|
||||
style-src 'self';
|
||||
object-src 'none';
|
||||
frame-src 'none';
|
||||
frame-ancestors 'none';
|
||||
base-uri 'none';
|
||||
form-action 'none';
|
||||
```
|
||||
|
||||
Do not replace `'wasm-unsafe-eval'` with broad `'unsafe-eval'`. All core assets
|
||||
are same-origin. `media-src blob:` is required for result/source previews.
|
||||
The exact headers, immutable Portal reference gap and active-worktree local
|
||||
patch are in `docs/PORTAL_REQUIREMENTS.md`.
|
||||
|
||||
## Cross-origin isolation
|
||||
|
||||
MT needs secure HTTPS (localhost during development is acceptable),
|
||||
`SharedArrayBuffer`, and:
|
||||
|
||||
```text
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Resource-Policy: same-origin
|
||||
```
|
||||
|
||||
Isolation affects every resource in the page. Do not weaken COEP or allow
|
||||
cross-origin dependencies merely to make MT load. The safe fallback is ST.
|
||||
|
||||
## Dependency and vulnerability response
|
||||
|
||||
Exact runtime versions and core digests are recorded in `SOURCE.md`. Run
|
||||
`npm audit`, tests, real core smoke tests and legal review before an update.
|
||||
Core upgrades change a large native attack surface and require new capability
|
||||
snapshots, fixtures, checksums and browser tests.
|
||||
|
||||
Report vulnerabilities privately to the repository owner through the Gitea
|
||||
instance when possible. Do not attach sensitive media, credentials or an
|
||||
unredacted diagnostic log to a public issue. Include the app/core version,
|
||||
browser, mode, smallest synthetic reproduction, impact and cleanup result.
|
||||
93
docs/STORAGE.md
Normal file
93
docs/STORAGE.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Storage and cleanup
|
||||
|
||||
av-tools separates source bytes, engine files, derivatives and project
|
||||
metadata. The default policy is deliberately non-persistent for source media.
|
||||
|
||||
## Object URLs
|
||||
|
||||
Browser preview and result playback use `URL.createObjectURL(File|Blob)`.
|
||||
Object URLs reveal bytes only to the current browser context but retain the
|
||||
underlying object until revoked. Each owner revokes its previous URL when a
|
||||
source/result changes and all URLs on project close/unmount. A preview failure
|
||||
does not trigger an upload or external fallback.
|
||||
|
||||
## FFmpeg virtual filesystem
|
||||
|
||||
Every execution receives a generated job directory. Inputs are copied to the
|
||||
in-memory filesystem or mounted read-only through WORKERFS where browser/core
|
||||
support is verified. Outputs are always in generated paths. The runner reads
|
||||
only declared outputs, then deletes output/input/control files and unmounts.
|
||||
|
||||
WORKERFS can avoid an initial JavaScript byte copy; FFmpeg decoding and output
|
||||
still consume WASM/browser memory. Cancelling terminates the engine, so cleanup
|
||||
also discards that entire virtual filesystem.
|
||||
|
||||
Each probe/export records its actual input mode and byte accounting. A
|
||||
successful WORKERFS mount reports zero copied input bytes; if mounting fails,
|
||||
the bounded compatibility fallback reports the exact bytes copied into engine
|
||||
memory. This is runtime evidence rather than a speculative preflight claim.
|
||||
|
||||
## IndexedDB
|
||||
|
||||
The mounted application currently stores validated user presets in IndexedDB.
|
||||
The codebase also contains a durable job-metadata store, but the application
|
||||
queue described in the UI is deliberately in memory and does not instantiate
|
||||
that store. Project documents are validated, versioned JSON that the user
|
||||
explicitly saves and loads; they are not currently auto-saved to IndexedDB.
|
||||
Media source bytes are not written there by default.
|
||||
|
||||
Clearing site data removes these records. Private browsing and quota eviction
|
||||
may remove them earlier; the app must treat storage as recoverable cache, not
|
||||
the only copy of an important user preset.
|
||||
|
||||
## Origin-private file system
|
||||
|
||||
OPFS is optional for bounded preview proxies, waveform peaks, thumbnails and
|
||||
other regenerable derivatives. The current implementation uses asynchronous
|
||||
origin-private file handles and falls back to a 128 MiB least-recently-used
|
||||
in-memory cache if OPFS is unavailable or fails. A derivative key includes a
|
||||
source fingerprint, operation, relevant settings and a cache-schema version;
|
||||
waveform keys additionally identify the selected stream and analysis settings.
|
||||
|
||||
Preview-proxy settings are part of that identity. The current UI validates a
|
||||
1–300 second maximum duration and a 480, 720, 960, 1280 or 1920 pixel maximum
|
||||
width. A matching local derivative is reused without FFmpeg execution; changing
|
||||
the source or either setting produces a different cache lookup. A cache miss or
|
||||
cache failure falls back to generation and never mutates/persists the source.
|
||||
|
||||
The application works without OPFS. The storage panel reports the browser's
|
||||
origin-wide quota estimate when available and offers per-entry or global cache
|
||||
deletion. The current OPFS layer relies on browser quota enforcement rather
|
||||
than pre-reserving space; a failed cache write switches subsequent cache work
|
||||
to the bounded in-memory fallback. OPFS writes close their
|
||||
`FileSystemWritableFileStream` on success and abort it on error.
|
||||
|
||||
OPFS is private to the current **origin**. Moving an app from one hostname,
|
||||
scheme or port to another does not move cached data. Toolbox and standalone
|
||||
deployments at different origins have different caches even if their files are
|
||||
identical.
|
||||
|
||||
## Source persistence policy
|
||||
|
||||
Normal import retains the user-selected `File` only in the active page. Saved
|
||||
projects contain source descriptors/fingerprints and prompt for reattachment;
|
||||
they do not embed source bytes. If a later UI offers “keep source on this
|
||||
device,” it must be explicit, show estimated/quota usage, identify the origin,
|
||||
allow per-source deletion and never be silently enabled by a project import.
|
||||
|
||||
Downloads are explicit user actions. The File System Access API, when
|
||||
available, writes only after the user selects a target; Blob download remains
|
||||
the fallback.
|
||||
|
||||
## Cleanup matrix
|
||||
|
||||
| Resource | Normal completion | Cancellation/error | User control |
|
||||
| ------------------------ | ---------------------------------------------------- | -------------------------------------------------------- | ----------------------- |
|
||||
| source/result object URL | retained only while displayed, then revoked | revoked when owning state clears | clear source/result |
|
||||
| FFmpeg job files/mount | delete/unmount in finalization | engine termination discards FS; stale generation ignored | cancel job/reset engine |
|
||||
| result Blob | retained until replaced/cleared/download page closes | failed partial output is not published | clear result |
|
||||
| IndexedDB user presets | retained intentionally | transaction rollback | delete/reset presets |
|
||||
| OPFS derivative | retained until expiry or explicit deletion | failed write is not indexed; cache switches to memory | delete item/clear all |
|
||||
|
||||
The storage screen reports approximate usage; browser quota figures remain
|
||||
estimates and are not a durability promise.
|
||||
Reference in New Issue
Block a user