Video extension 1.0.0

Video extension 1.0.0

Video extension 1.0.0

Function

Independent kind=wasm official extension for asynchronous local-file video work.

Install only when needed. It does not depend on the image or SQLite extensions.

FFmpeg and ffprobe must be installed on the host's PATH; no FFmpeg executable or

codec library is included or downloaded by this package. The tested backend is

FFmpeg 8.1.1 on Windows x64. The WASI module builds independently. Linux and macOS

are intended host targets, but their end-to-end behavior has not been verified.

This extension requires a BT build with the optional bts_host.process_request

import added alongside this extension; previously released BT 1.1.4 binaries lack

that import despite satisfying the manifest's minimum version number.

Syntax, parameters and return values

All names use snake_case. Every processing method returns a VideoJob immediately;

decoding and encoding execute in bounded native background processes. info() is

also asynchronous. Pass {} for default option objects.

video is the sole global entry point; use names such as clip or source for

the returned object so that a local variable does not shadow the entry function.

CallParameters and behaviorReturn
video(path, options)Existing media filename; open options below. Only validates filesystem metadata.Video
clip.job(id)Positive integer from job.id(); requires a live source for the same normalized project-relative path, project and live worker.Existing VideoJob alias
clip.info()Probe duration, container and stream metadata.VideoJob
clip.transcode(output, options)Encode MP4 or WebM; encoding options below.VideoJob
clip.trim(output, start, duration, options)Start in seconds, 0..86400; duration in seconds, 0.001..86400; range must fit the source.VideoJob
clip.concat(paths, output, options)Array of 1..7 additional project-relative filenames; this source is first.VideoJob
clip.frame(output, time)One PNG/JPEG frame; seconds, 0 inclusive to source duration exclusive.VideoJob
clip.resize(output, width, height, options)Even integer dimensions, 2..8192 each, at most 16,777,216 pixels.VideoJob
clip.extract_audio(output)First audio stream to WAV, FLAC or M4A.VideoJob
clip.replace_audio(audio, output, options)Replace audio from an existing media file; retain video duration.VideoJob
clip.close()Release the source descriptor; existing jobs retain their own source snapshot.true
job.status()Poll once and advance preflight/encoding stages. Never waits for media processing.Status object
job.cancel()Cancel unfinished work and return its snapshot; completed output survives.Status object
job.id()Identifier for restoring the job in subsequent requests.Positive integer
job.close()Cancel unfinished work and release the handle; all aliases become invalid and restoring a closed ID fails.true

Top-level path parameters use BT path_read/path_write roles, so @/ refers to

the project root and source-relative paths follow ordinary BT resolution. Items

inside concat(paths, ...) are project-relative, without @/. All paths must

remain inside the project. URL syntax, parent traversal, percent signs, symbolic

links and control characters are rejected. Output parent directories must exist.

Output files must not exist: jobs reserve them with create_new and never replace

caller-owned files. Files are exchanged with image using their paths; no media

bytes, Base64 strings or cross-extension object handles are transferred.

Open options

FieldTypeRequiredDefaultValid rangeMeaning
timeout_msintNo600001..300000One deadline for the whole job, including all probes, encoding and time between polling calls.

Encoding options

FieldTypeRequiredDefaultValid rangeMeaning
qualityintNo52..31MPEG-4 q:v; VP9 CRF is twice this number (4..62). Lower means higher quality; not a percentage or size guarantee.
fpsnumberNoNone1..120, finiteForce constant output frame rate. Omitted: preserve source timing except concat, which uses the first source's average frame rate.
audioboolNotruetrue/falseRetain first audio stream; concat requires audio in every input when true. False produces video-only output. replace_audio always includes the replacement audio.

Unknown option fields and unsupported combinations fail with English errors.

Status object

FieldTypeRequiredDefaultValues/rangeMeaning
statestringYesprobingprobing, running, succeeded, failed, cancelled, timed_outJob lifecycle.
elapsed_msintYes0NonnegativeWall time since job creation.
processed_secondsnumberYes0NonnegativeLatest encoded output timeline position from FFmpeg progress; remains zero during probing. Not a percentage.
errorstringYesEmpty stringBounded by host stderr (1 MiB tail)English failure details; empty on success.
resultobject or emptyYesemptyMetadata or output objectPresent only after successful completion. Missing stream fields remain absent; external JSON null remains BT null.

An output result contains:

FieldTypeRequiredDefaultRangeMeaning
pathstringYesNoneProject-relative filenameCompleted output file.
size_bytesintYesNone1 to less than 2 GiBEncoded file size.

An info() result contains:

FieldTypeRequiredDefaultRangeMeaning
durationnumberYesNone0.001..86400 secondsContainer duration.
formatstringYesNoneFFprobe format identifierContainer name; may contain aliases separated by commas.
size_bytesintYes0 if unreported0..32 GiBInput size reported by ffprobe.
streamsarray of objectsYesNone1..16 streamsStream descriptors below.

Stream objects expose only these ffprobe fields; unavailable fields are absent.

Rational/time strings retain backend precision without lossy normalization.

FieldTypeRequiredDefaultRangeMeaning
indexintBackend dependentNoneNonnegativeContainer stream index.
codec_typestringBackend dependentNonevideo, audio, other FFprobe typesMedia kind.
codec_namestringBackend dependentNoneBackend codec identifierDecoder codec.
width, heightintVideo onlyNone1..8192; product <=16,777,216Encoded frame dimensions before display rotation.
avg_frame_ratestringVideo onlyNoneRational; >0 and <=240 fpsAverage source frame rate.
sample_ratestringAudio onlyNoneInteger text 8000..192000Audio sample rate in Hz.
channelsintAudio onlyNone1..8Source channel count.
durationstringBackend dependentNoneSeconds textPer-stream duration.
start_timestringBackend dependentNoneSeconds textPer-stream starting timestamp.

Formats and timeline rules

Inputs are local MP4/MOV/MKV/WebM/AVI video or WAV/MP3/M4A/FLAC/OGG audio. An explicit

demuxer is selected from the filename; disguised playlists, network protocols and

device inputs are not supported. Actual decoder availability depends on the host

FFmpeg installation. Unsupported codecs fail through job.status().error.

Output suffixEncoding
.mp4Native MPEG-4 Part 2 video + AAC audio, faststart; not H.264.
.webmlibvpx-vp9 video + libopus audio; these encoders must be present.
.pngOne lossless RGB frame.
.jpg, .jpegOne lossy JPEG frame using FFmpeg's encoder defaults.
.wavPCM signed 16-bit audio.
.flacFLAC audio.
.m4aAAC audio.

Video output is 8-bit YUV 4:2:0 without alpha; audio is normalized to stereo 48 kHz.

Metadata, chapters, subtitles, attachments and additional audio/video tracks are

discarded. This version does not perform HDR tone mapping or preserve color

metadata. Odd source dimensions are rounded down to even dimensions; resize uses

the exact requested dimensions, and can alter aspect ratio. No GPU is required.

Trim seeks by seconds and re-encodes, so it is frame-accurate rather than limited

to keyframes; endpoints are quantized to decoded frame/audio sample boundaries.

Frame extraction selects the first decoded frame at/after the requested time.

Concat resets each segment's timestamps, scales to the first input's even geometry,

normalizes pixel aspect ratio/frame rate and resamples audio. With audio enabled,

the shorter stream in a segment can be padded by the concat filter; total duration

can differ from the sum by frame/audio rounding. Use audio:false when any segment

lacks audio. The sum of input durations must not exceed 86400 seconds.

Replacement audio begins at time zero, pads silence when short and is cut to the

video's duration when long. Existing video is re-encoded so container/codec

combinations remain deterministic. Millisecond-exact duration is not promised.

Jobs, Web requests and resource bounds

Call status() periodically (for example every 50–250 ms). Polling advances one

stage at a time: each input is probed, validated, then encoded. If polling stops,

the active native process still finishes or times out; the next stage waits for

another status call. There is no blocking wait() API. For Web handlers, create

a job, return job.id(), and restore it with source.job(id) in later short requests.

Reopen the same source path first; a different source or a closed source is rejected.

Path identity ignores redundant . and separators, but preserves case. Restoration

does not copy the job or start another process. Closing the source leaves the job

alive; closing any job alias invalidates every alias. The reopened source's timeout

does not change the original job's deadline. For example, in a later request:

Never run a sleep/poll loop inside a Web request. IDs are **not authentication

tokens**; applications must authorize ownership before accepting an ID from clients.

IDs expire when closed, on service shutdown or after the shared worker is evicted.

The worker retains at most 64 sources and 32 jobs. Close completed jobs to reclaim

their slots. The process host permits 4 active processes per worker, 32 handles per

worker, and 32 active processes across the host; overload is rejected rather than

queued without a bound. The shared call queue is limited to 32, with a 2-second

call timeout and 5-minute idle TTL. All operations have a maximum 5-minute deadline.

The host retains only the latest 1 MiB of stdout and stderr for each process;

truncated probe JSON is rejected. Media payloads never enter WASM.

Input files are limited to 32 GiB, 24 hours, 16 streams, 8192 pixels per axis,

16,777,216 pixels per frame, 240 fps, and 8 audio channels/192 kHz. Probing uses

5 MB / 5 seconds analysis limits. FFmpeg uses one decoding thread per input,

two encoding threads, one filter thread, one complex-filter thread, a 64 MiB

maximum individual allocation and a 128-packet muxing queue. These constrain

work but are not a hard total RSS quota for FFmpeg; decoder internals and

multiple input streams can retain several frames. Output is capped at 2 GiB;

reaching that cap is a failed job and its file is removed.

Cancel/close requests return promptly; the native worker kills and waits for the

exact child process, closes pipes and removes incomplete owned files afterward.

Cleanup can therefore be observed shortly after cancellation. Completed files

remain after close. Extension/service shutdown also cancels owned native work.

Filesystem permission checks and declared paths are enforced by the host. Granting

process nevertheless permits native execution outside the WASI sandbox: it is

a trusted-extension capability, not an OS sandbox for arbitrary downloaded code.

Example (CLI)

Use clip.frame('@/frame.png', 0.5), poll to success, then open @/frame.png

with the independently installed image extension for further editing.

Build and verification

From the extension/video directory, build.ps1 -BtExe <path-to-current-bt.exe> builds with the

locked WASI dependencies, stages only runtime files and licenses under target/,

creates video-1.0.0.bts, then checks it. The .bts is independently versioned.

verify.ps1 -BtExe <path> -Package <package> creates an independent project under

target/, installs the package and runs smoke.bt against generated test media,

then independently decodes and probes its outputs. The scripts require ffmpeg

and ffprobe on PATH. They do not commit, publish, download codecs or upload files.

Native tests cover all processing methods, container/codec outputs, geometry,

trim/concat/replacement durations, PNG/JPEG signatures, full output decode,

invalid paths/options/metadata, existing-output protection, cancel while encoding,

timeouts, same-source ID restoration, mismatched sources, invalid/missing IDs,

closed sources, shared job aliases and repeated release/reuse. These run

the actual native host protocol, not mocks. Real installed-BT package checks are

recorded in the workspace docs/ acceptance record. Installed-BT verification

requires the explicit VIDEO_SMOKE_PASS marker and checks five error scenarios;

a zero CLI exit code alone does not establish that a script succeeded.

Licenses and backend provenance

Source: Copyright 2026 Lifeng Yan, MIT OR Apache-2.0. LICENSE-MIT,

LICENSE-APACHE, COPYRIGHT and THIRD_PARTY_LICENSES.txt accompany the package.

The Rust JSON dependency is pinned to serde_json 1.0.151 (MIT OR Apache-2.0,

Rust 1.71 minimum) and all transitive versions/checksums are locked in Cargo.lock.

See serde_json's official manifest.

FFmpeg is a separately installed program, invoked with argument arrays without a

shell. FFmpeg's official legal page documents the

LGPL/GPL distinction based on build configuration. The tested Gyan 8.1.1 Windows

build enables GPL and version3; it is not redistributed here. Anyone distributing

a backend must independently satisfy that exact build's notices and source

obligations. Supported arguments follow the official

FFmpeg CLI and

FFprobe CLI documentation.