open-standards/wai/wai-web/src/video.rs
Transaction Science 0634016cf7
Some checks failed
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 18s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 9s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Failing after 1m9s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 2m53s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 2m40s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 4m5s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 4m7s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 5m11s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 8m57s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 9m37s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Has been cancelled
wai-app and Create / wai-app · does this change touch it (push) Has been cancelled
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Has been cancelled
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Has been cancelled
wai-audit collector and player integration / does this change touch it (push) Has been cancelled
wai-audit collector and player integration / collector · linux (push) Has been cancelled
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Has been cancelled
WAI operation catalogue / does this change touch it (push) Has been cancelled
WAI operation catalogue / the catalogue and every door · linux (push) Has been cancelled
wai-fleet service / does this change touch it (push) Has been cancelled
wai-fleet service / service · linux (push) Has been cancelled
wai-fleet service / service · macos (push) Has been cancelled
wai-key-proxy / does this change touch it (push) Has been cancelled
wai-key-proxy / proxy · linux (push) Has been cancelled
wai-ledger service / does this change touch it (push) Has been cancelled
wai-ledger service / service · linux (push) Has been cancelled
wai-ledger service / service · macos (push) Has been cancelled
wai-quantum for JavaScript / does this change touch it (push) Has been cancelled
wai-quantum for JavaScript / javascript · linux (push) Has been cancelled
wai-quantum studio and desktop / does this change touch it (push) Has been cancelled
wai-quantum studio and desktop / studio · browser (push) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (push) Has been cancelled
wai-quantum studio and desktop / desktop app · linux (push) Has been cancelled
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
wai-sink-android native sink library / does this change touch it (push) Has been cancelled
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Has been cancelled
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Has been cancelled
wai-sink-swift native sink package / does this change touch it (push) Has been cancelled
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Has been cancelled
WAI Studio / does this change touch it (push) Has been cancelled
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Has been cancelled
WAI Studio / Studio in Chromium and Firefox · macos (push) Has been cancelled
WAI Studio / Studio in WebKit · linux (push) Has been cancelled
wai cross-architecture byte-equality / does this change touch it (push) Has been cancelled
wai cross-architecture byte-equality / linux-aarch64 (push) Has been cancelled
wai cross-architecture byte-equality / linux-x86_64 (push) Has been cancelled
wai cross-architecture byte-equality / macos-arm64 (reference) (push) Has been cancelled
wai cross-architecture byte-equality / windows-x86_64 (push) Has been cancelled
wai-web: the integer neural capabilities with their graphs on the page's WebGPU, in its dispatch
A page's wasm decoder of wai.neural.int_hyper, wai.video.int_hyper and
wai.neural.int_synth now runs its wai-ops/1-i32 graphs on the page's
WebGPU: wai-ops-gpu's WGSL kernels, integer arithmetic only, compiled by
the browser's own WGSL compiler, after the adapter passes the arithmetic
probe and the integer-semantics suite in the page. The output is the
CPU decode's bytes; the decode on the page's CPU is the fallback, for an
absent adapter, a graph the device does not run and a device error.

wai-rs: feature ops_offload (int_ops::offload). The decoders are
synchronous and a WebGPU result arrives through a promise, so a decode's
graph runs are answered from outside it: the decode runs again from its
start with each answer in hand (a keyframe three times, repeating its
entropy decoding, never a graph), and an answer held for other inputs is
dropped. The example int_hyper_keyframe_gpu stages a full-scale keyframe
from the export tool and decodes it on each adapter both ways.

wai-web: the neural_webgpu bundle (IntNeural on the webgpu or cpu path,
decodeWih1, decodeIntSynth, decodeWiv1) and its dispatch half,
neural_leg: wai.video.int_hyper is in the report's wasm leg only
where a group decodes in no more time than it plays, from the page's own
measured times; IntNeural.route routes and chooses a report with that
leg through wai::browser_sink. browser-sink §3, §4.6 and §14.3 say so;
routed, the clip ranks by §4.7 as a learned capability.

Gate, 2026-11-09: two 640x320 keyframes and the committed vectors equal
to the CPU's bytes on Metal (the Studio), Vulkan 0x1002 and llvmpipe
(the Framework), Vulkan 0x10de and llvmpipe (Thor), and in headless
Chromium 148 on the Studio's WebGPU, both paths of the page; a float
product planted in the kernels and a changed weight caught everywhere
they ran. Playwright's WebKit 26.4 offers no navigator.gpu, headless or
headed: there it decoded every case on the CPU path to the pinned
digests. Dispatch held to the deadline and §4.7 on every path. The
measured times serve the dispatch decision only: in Chromium the
keyframe took about 0.5 s on WebGPU and 12 s on the page's CPU, so the
clip is routed on the webgpu path and not on the cpu path.

wai-ops-gpu.yml runs the offload and dispatch tests on Linux, and the
page's committed cases in Chromium on the macos runner.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 11:04:37 -05:00

104 lines
4.8 KiB
Rust

//! In-browser **byte-exact integer video** decode — zero JS ML runtime.
//!
//! The same pure-i64 Rust that decodes the `wai.video.int_motion` /
//! `wai.video.int_hyper` inter-frame codec natively compiles to wasm32 and
//! produces the IDENTICAL frames here: integer block motion compensation +
//! integer-quantized residual, no float anywhere. So a publisher can pin each
//! frame's hash and the page re-checks them client-side — *frame-state-
//! equivalence*, a whole clip you can put in a receipt.
use wasm_bindgen::prelude::*;
/// A decoded clip: its frames, each `width · height · 3` bytes of RGB, as the
/// decoder produced them. `frame(i)` hands JS one frame; `rgb` concatenates them all
/// into one new buffer on each read, so a page that paints frame by frame holds the
/// clip once.
#[wasm_bindgen]
pub struct DecodedVideo {
frames: Vec<Vec<u8>>,
width: u32,
height: u32,
}
impl DecodedVideo {
/// A clip a decoder of this crate gave.
#[cfg_attr(not(all(feature = "neural_webgpu", target_arch = "wasm32")), allow(dead_code))]
pub(crate) fn new(frames: Vec<Vec<u8>>, width: u32, height: u32) -> DecodedVideo {
DecodedVideo { frames, width, height }
}
}
#[wasm_bindgen]
impl DecodedVideo {
/// All frames, concatenated into a new buffer: frame `f` is
/// `rgb[f·w·h·3 .. (f+1)·w·h·3]`.
#[wasm_bindgen(getter)]
pub fn rgb(&self) -> Box<[u8]> {
self.frames.concat().into_boxed_slice()
}
/// Frame `index` (`width · height · 3` bytes), or an empty buffer past the last.
pub fn frame(&self, index: u32) -> Box<[u8]> {
self.frames.get(index as usize).cloned().unwrap_or_default().into_boxed_slice()
}
#[wasm_bindgen(getter, js_name = nFrames)]
pub fn n_frames(&self) -> u32 {
self.frames.len() as u32
}
#[wasm_bindgen(getter)]
pub fn width(&self) -> u32 {
self.width
}
#[wasm_bindgen(getter)]
pub fn height(&self) -> u32 {
self.height
}
}
/// Decode a `WIV1` payload (`extensions/integer-payloads.md` §3) to RGB frames in
/// the browser, byte-identical to the native reference: `capability` is the
/// envelope's, `wai.video.int_motion` (keyframe 0, no parameters) or
/// `wai.video.int_hyper` (keyframe 1, whose nested `WIH1` keyframe decodes with the
/// conditional codec's `g_s.whm`, `h_s.whm` and `entropy.json`). A refusal's message
/// starts with its code (`bad_magic` for a JSON form). No float, no ML runtime.
#[wasm_bindgen(js_name = decodeWiv1)]
pub fn decode_wiv1(
capability: &str, payload: &[u8], g_s: Option<Box<[u8]>>, h_s: Option<Box<[u8]>>, entropy: Option<Box<[u8]>>,
) -> Result<DecodedVideo, JsError> {
let (g, h, e) = (g_s.unwrap_or_default(), h_s.unwrap_or_default(), entropy.unwrap_or_default());
let set = crate::int_payload::int_hyper_set(&g, &h, &e);
let set: &[(&str, &[u8])] = if capability == "wai.video.int_hyper" { &set } else { &[] };
match crate::int_payload::decode(capability, payload, set).map_err(|e| JsError::new(&e))? {
wai::int_payload::IntDecoded::Video { frames, width, height } => Ok(DecodedVideo { frames, width, height }),
_ => Err(JsError::new("unsupported_capability: not an integer video capability")),
}
}
/// Decode the tool's JSON bitstream (`int_video_bitstream.json`) to RGB frames. For
/// debugging and conversion only: the payload `wai.video.int_motion` carries is
/// `WIV1` ([`decode_wiv1`]), and a JSON form is not one. Byte-identical to the
/// native reference; `fnv1aHex` (the `generate` binding) hashes each frame.
#[wasm_bindgen(js_name = decodeIntVideo)]
pub fn decode_int_video(bitstream_json: &[u8]) -> Result<DecodedVideo, JsError> {
let bs: serde_json::Value =
serde_json::from_slice(bitstream_json).map_err(|e| JsError::new(&e.to_string()))?;
let clip = wai::int_video::decode(&bs).map_err(JsError::new)?;
Ok(flatten(clip))
}
/// Decode the **P-frames** of a JSON bitstream on top of an already-decoded I-frame
/// (`i_frame`, RGB HWC `u8`, e.g. from `decodeIntHyperBin`) — the NEURAL-KEYFRAME
/// path (`wai.video.int_hyper`): a learned `int_hyper` keyframe + integer
/// inter-frames, the whole clip byte-exact. For debugging and conversion only: the
/// payload is `WIV1` with its keyframe nested ([`decode_wiv1`]). No float, no ML
/// runtime.
#[wasm_bindgen(js_name = decodeIntVideoPframes)]
pub fn decode_int_video_pframes(i_frame: &[u8], w: u32, h: u32, bitstream_json: &[u8]) -> Result<DecodedVideo, JsError> {
let bs: serde_json::Value =
serde_json::from_slice(bitstream_json).map_err(|e| JsError::new(&e.to_string()))?;
let clip = wai::int_video::decode_pframes(i_frame, h as usize, w as usize, &bs).map_err(JsError::new)?;
Ok(flatten(clip))
}
fn flatten(clip: wai::int_video::DecodedClip) -> DecodedVideo {
DecodedVideo { width: clip.w as u32, height: clip.h as u32, frames: clip.frames }
}