//! 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>, 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>, 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>, h_s: Option>, entropy: Option>, ) -> Result { 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 { 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 { 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 } }