LiveOut held every object's path, hex SHA-256 and first piece for the whole session. It now holds: - no listing record per file. Each file's listing line is appended to a hidden .LISTING.lines beside the package as the file is written. At the end the lines are read back, sorted into LISTING, and the lines file is removed; that is the one moment it holds a line per file. - no set of written objects. An object already written is known by its file on disk. - for the abandon path, only the objects the unsealed pieces published, as a piece number and a 32-byte content hash. Those of a window are dropped when the window is sealed. packaging §9.2 and the CLI help now say exactly what remains: - what no window has sealed yet: those pieces' entry fields and their objects' hashes, which with --window 0 means every piece's until the end; - the frames of the piece being cut; - eight bytes per piece for the plan. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1171 lines
77 KiB
Markdown
1171 lines
77 KiB
Markdown
# WAI Extension: Packaging
|
||
|
||
> Status: Draft. A packager turns sources into WAI objects under a declared policy;
|
||
> this extension fixes what a verifier recomputes. This revision defines the policy,
|
||
> its digest and what it may ask a packager to emit (§3), how a source is cut into
|
||
> pieces (§4), the objects a packager writes, refinements and stills among them, and a
|
||
> piece's ladder (§5), the stage index (§6), the floor (§7), what each class is offered
|
||
> over HTTP and MoQ and how its delivery is simulated (§8), the package receipt and live
|
||
> windows (§9), and the package and its verification (§11). Reference impl: `wai-pack` —
|
||
> `policy`, `capcheck`, `source`, `scenecut`, `classify`, `plan`, `encode`, `png`,
|
||
> `floor`, `cmaf`, `catalog`, `pack`, `live`, `delivery`, `verify`; in `wai-rs`, the
|
||
> encoder `int_video_enc`, the refinement encoder `staged::wir1`, the stage index
|
||
> `stage_index`, the receipt `package` and the delivery simulator `stage_measure::sim`;
|
||
> the corpus [`pack-conformance/`](../pack-conformance/). Keywords **MUST**, **MUST NOT**,
|
||
> **SHOULD**, **MAY** are RFC 2119/8174.
|
||
|
||
## 1. Scope
|
||
|
||
A **packager** turns one or more sources into WAI objects, stage indexes, catalogs
|
||
and a package receipt, under a declared **policy**. This extension fixes what a
|
||
package contains and what a verifier recomputes. It fixes an encoder's choices only
|
||
where it names them.
|
||
|
||
## 2. Terms
|
||
|
||
- **Component**: one media stream of a package (for example video, or audio).
|
||
- **Piece**: a span of a component that decodes with no reference outside itself.
|
||
- **Stage**: one object of a piece in staged order: the base is layer 0, refinement
|
||
*k* is layer *k* ([staged-delivery](staged-delivery.md)).
|
||
- **Rung**: one entry of the policy's ladder: a capability a component's pieces are
|
||
coded in, with its role (base, refinement or floor) and parameters.
|
||
- **Class**: a delivery profile the policy names.
|
||
- **Moment**: a declared span of presentation time with tags.
|
||
- **Floor**: a rendition in a standard codec, for sinks that lack the primary.
|
||
- **Timebase** of a component: `num / den` units per second. For video one unit is
|
||
one frame; for audio, one sample.
|
||
|
||
## 3. Policy
|
||
|
||
**3.1** A package is made under exactly one policy, a TOML 1.0 document of schema
|
||
`wai.pack.policy/1`, in UTF-8 with no byte-order mark.
|
||
|
||
**3.2** The **effective policy** is the policy with every default written in (§3.3)
|
||
and every override the packager applies (such as a command line forcing
|
||
`royalty_free_only`). An override only ever forces `royalty_free_only` on; it never
|
||
turns off what the policy asks for. Its JSON form is §3.3's members, with an optional member that
|
||
has no default omitted when absent, and arrays of tables in document order. Its
|
||
**policy digest** is
|
||
|
||
BLAKE3("wai:pack-policy\x01" ‖ JCS(effective policy as JSON))
|
||
|
||
where JCS is RFC 8785. Two spellings of one policy (member order, inline or standard
|
||
tables, dotted keys, string or integer notation, comments, an omitted default or the
|
||
default written out) therefore have one digest; the order of an array of tables is
|
||
part of the policy.
|
||
|
||
A packager **MUST** refuse with `policy-toml`, before anything else, a policy whose
|
||
bytes are not UTF-8, that begins with a byte-order mark, that is not TOML 1.0 (an
|
||
integer outside TOML's 64-bit range included), or that nests arrays and tables more
|
||
than 32 deep: the document's own table is at depth 1, and an array or a table
|
||
directly inside one at depth *k* is at depth *k* + 1. Then, before it reads any key, it
|
||
**MUST** refuse one holding anywhere a float (`policy-float`), a date or time
|
||
(`policy-datetime`), a negative integer (`policy-negative`), or an integer above
|
||
`2^53 − 1` (`policy-integer-range`), visiting members in byte order of their keys and
|
||
arrays in order.
|
||
|
||
**3.3** *Schema.* Within each table, a key this section does not define is refused
|
||
first (`policy-unknown-key`, the first in byte order), then each member in the order
|
||
listed: a required member that is absent (`policy-missing-key`), a member of another
|
||
type (`policy-type`), a value outside its range or vocabulary (`policy-value`). An
|
||
absent table reads as empty.
|
||
|
||
| Member | Type | Default | Range |
|
||
|---|---|---|---|
|
||
| `schema` | string | required | `wai.pack.policy/1` (else `policy-schema`) |
|
||
| `asset_id` | string | required | 1 to 255 bytes |
|
||
| `royalty_free_only` | boolean | `false` | |
|
||
| `lead` | string | required | 1 to 255 bytes |
|
||
| `component` | array of tables | required | |
|
||
| `component.id` | string | required | 1 to 255 bytes |
|
||
| `component.media` | string | required | `video`, `image`, `audio`, `world`, `feed`, `avatar`, `film` |
|
||
| `component.source` | string | absent | |
|
||
| `component.timebase` | table | required | members `num`, `den`, each 1 to `2^32 − 1`, required |
|
||
| `segmentation.target_ms` | integer | 2000 | |
|
||
| `segmentation.min_ms` | integer | 1000 | |
|
||
| `segmentation.max_ms` | integer | 4000 | |
|
||
| `segmentation.scene_cut` | string | `wai.pack.scenecut/1` | `wai.pack.scenecut/1` |
|
||
| `segmentation.cut_abs_permille` | integer | 300 | 1 to 1000 |
|
||
| `segmentation.cut_rel_permille` | integer | 3000 | 0 to 1000000 |
|
||
| `segmentation.cut_window` | integer | 8 | 3 to 64 (a window of fewer than 3 scores never applies `cut_rel_permille`, §4.3) |
|
||
| `segmentation.lookahead_frames` | integer | 6 | 0 to 64 |
|
||
| `segmentation.closed` | boolean | `true` | |
|
||
| `ladder` | array of tables | empty | |
|
||
| `ladder.id` | string | required | 1 to 255 bytes |
|
||
| `ladder.component` | string | required | 1 to 255 bytes |
|
||
| `ladder.capability` | string | required | |
|
||
| `ladder.role` | string | required | `base`, `refinement`, `floor` |
|
||
| `ladder.refines` | string | absent | |
|
||
| `ladder.params` | table | empty | by capability, below |
|
||
| `floor.supplied_listing` | string | absent | 64 lowercase hexadecimal digits |
|
||
| `floor.extra_keyframes` | boolean | `false` | |
|
||
| `live.layer_lag_ms` | array of integers | one 0 per layer | §3.4 |
|
||
| `class` | array of tables | empty | |
|
||
| `class.id` | string | required | 1 to 255 bytes |
|
||
| `class.max_layer` | integer | 0 | 0 to 255 |
|
||
| `class.max_kbps` | integer | absent | 1 to `2^32 − 1` |
|
||
| `class.floor_only` | boolean | `false` | |
|
||
| `moment` | array of tables | empty | |
|
||
| `moment.id` | string | required | 1 to 255 bytes |
|
||
| `moment.from_ms`, `moment.to_ms` | integer | required | `to_ms` above `from_ms` |
|
||
| `moment.tags` | array of strings | empty | |
|
||
| `rule` | array of tables | empty | |
|
||
| `rule.id` | string | required | 1 to 255 bytes |
|
||
| `rule.when.piece_kind` | array of strings | absent | `natural`, `static`, `synthetic`, `motion_high` |
|
||
| `rule.when.moment_tag` | array of strings | absent | |
|
||
| `rule.then.ladder` | array of strings | absent | |
|
||
| `rule.then.max_layer` | integer | absent | 0 to 255 |
|
||
| `rule.then.deadline_ms` | integer | absent | 1 to `2^32 − 1` |
|
||
| `defaults.deadline_ms` | integer | 1500 | 1 to `2^32 − 1` |
|
||
| `classify.static_permille` | integer | 5 | 0 to 1000 |
|
||
| `classify.static_mad_milli` | integer | 300 | |
|
||
| `classify.synthetic_bins` | integer | 12 | 0 to 64 |
|
||
| `classify.motion_high_milli` | integer | 9000 | |
|
||
|
||
A rung's `params` are those of its capability; every other capability takes none.
|
||
|
||
| Capability | Parameter | Default | Range |
|
||
|---|---|---|---|
|
||
| `wai.video.int_motion` | `block` | 16 | 4, 8, 16, 32 |
|
||
| | `search` | 7 | 0 to 32 |
|
||
| | `mv_precision` | 2 | 1, 2 |
|
||
| | `q_candidates` | `[8]` | a non-empty array, each 1 to 255 |
|
||
| | `max_sse_px_milli` | 0 | |
|
||
| `wai.video.int_refine` | `q` | required | 1 to 255 |
|
||
| `wai.video.av1`, `wai.video.av1.lossless` | `source` | `supplied` | `supplied`, `encode` |
|
||
| `wai.audio.int_refine` | `to_rate`, `q` | required | 1 to 384000; 1 to 65535 |
|
||
| `wai.audio.flac` | `source` | `encode` | `encode`, `supplied` |
|
||
|
||
`wai.audio.int_layered` is reserved for the layered integer audio base: it is not
|
||
registered yet, and its parameters are defined when it is.
|
||
|
||
**3.4** *Constraints.* A policy that passes §3.3 is checked in this order, and the
|
||
first that fails is the refusal:
|
||
|
||
1. 1 to 8 components (`policy-too-many-components`); at most 64 rungs
|
||
(`policy-too-many-rungs`), 32 classes (`policy-too-many-classes`), 32 rules
|
||
(`policy-too-many-rules`) and 32 moments (`policy-too-many-moments`).
|
||
2. Ids unique among components, rungs, classes, moments and rules taken together, so
|
||
a reference never names two things (`policy-duplicate-id`).
|
||
3. `lead` names a component (`policy-lead`).
|
||
4. `segmentation.closed` is true: every piece is closed, §4.1 (`policy-open-gop`).
|
||
5. `1 ≤ min_ms ≤ target_ms ≤ max_ms ≤ 600000`, and `max_ms` is at most `2^24` units
|
||
on every component's timebase (§4.4) (`policy-piece-bounds`).
|
||
6. Every rung names a component (`policy-ladder-component`).
|
||
7. A rung carries `refines` exactly when its role is `refinement`
|
||
(`policy-refines-role`); `refines` names a rung (`policy-dangling-refines`); no
|
||
chain of `refines` returns to a rung it passed (`policy-refines-cycle`); every
|
||
chain stays in its component and ends at a base (`policy-refines-base`); no rung is
|
||
refined twice (`policy-refines-branch`).
|
||
8. Every component has a base rung (`policy-no-base`).
|
||
9. A rung's **layer** is 0 for a base or a floor, and one more than the rung it
|
||
refines for a refinement; the policy has one more layer than its highest rung
|
||
layer. No class's and no rule's `max_layer` exceeds the highest
|
||
(`policy-class-layer`).
|
||
10. A rule's `then.ladder` names known rungs, all of one component, each once:
|
||
exactly one base, every refinement over a rung it names (so its layers are a chain
|
||
from that base), and every floor rung of the component (every piece has a floor,
|
||
§7.1) (`policy-rule-ladder`).
|
||
11. `live.layer_lag_ms` has one entry per layer, the first 0, the entries
|
||
non-decreasing (`policy-layer-lag`).
|
||
12. The deadline values (`defaults.deadline_ms` and every rule's `then.deadline_ms`)
|
||
number at most 4 (`policy-too-many-deadlines`), and for every refinement layer
|
||
*k* and every deadline value *d*, `d − layer_lag_ms[k] ≥ 1`
|
||
(`policy-deadline-unreachable`).
|
||
|
||
Rules fold in document order: every matching rule applies, and a later `then` member
|
||
overrides an earlier one.
|
||
|
||
**3.5** A package receipt carries the policy digest. A verifier holding the effective
|
||
policy recomputes it; it cannot check that the packager followed the policy, except
|
||
by packing again.
|
||
|
||
**3.6** *What a packager may emit.* A packager **MUST** refuse a policy with a rung it
|
||
may not emit, reading SPEC §5's registry, never the policy. For each rung in ladder
|
||
order the first rule that holds is its refusal, and a policy's refusal is its first
|
||
rung's:
|
||
|
||
1. SPEC §5 does not register the capability: `capability-unknown:<cap>`.
|
||
2. SPEC §5 registers it as a record or a derivation operation, which names no payload
|
||
format: `capability-not-payload:<cap>`.
|
||
3. It is a candidate, whose payload format no revision has pinned (SPEC §5,
|
||
*Candidate capabilities*): `capability-candidate:<cap>`.
|
||
4. A base or a floor whose capability is not standalone media, or a refinement whose
|
||
capability is not refinement-only: `capability-role:<cap>`.
|
||
5. Under `royalty_free_only`, a capability the registry gives no licence class:
|
||
`license-unclassified:<cap>`; one of any class other than royalty-free:
|
||
`license-refused:<cap>:<label>`, with the class's SPEC §5 label. The class is the
|
||
registry's, "licensed royalty-free by its standards body"; it is not an opinion on
|
||
freedom to operate, and a package states nothing about patents.
|
||
|
||
## 4. Pieces
|
||
|
||
**4.1** Every piece **MUST** be closed: its first frame, sample or tick is coded with
|
||
no reference outside the piece, and no later object of the package references into
|
||
it.
|
||
|
||
**4.2** Piece boundaries are a pure function of the source frames and the effective
|
||
policy. Where the policy names `wai.pack.scenecut/1`, the boundaries **MUST** be those
|
||
§4.3 computes. The list of piece starts, in timebase units, is the **plan**, and its
|
||
digest is
|
||
|
||
BLAKE3("wai:pack-plan\x01" ‖ u64 LE n ‖ n × u64 LE start)
|
||
|
||
**4.3** `wai.pack.scenecut/1`. All arithmetic is integer; `>>` is a shift and every
|
||
division rounds down. For each RGB8 frame *f* of `w × h` pixels:
|
||
|
||
1. Luma `Y = (77R + 150G + 29B + 128) >> 8`, and the histogram `H_f` of 64 bins,
|
||
`Y >> 2`.
|
||
2. `D(a, b) = (Σ_i |H_a[i] − H_b[i]|) · 1000 / (2wh)`: the share of pixels, in
|
||
thousandths, whose bin differs. The score of frame *f* ≥ 1 is `s_f = D(f, f − 1)`.
|
||
3. The current piece starts at frame *p*, and `len = f − p`. Its window `W_f` is the
|
||
scores `s_g` for `p < g < f`, the last `cut_window` of them; `m_f` is their lower
|
||
median (sorted ascending, the element at index `(|W_f| − 1) / 2`).
|
||
4. **Hard cut** at *f*: `len ≥ min`, `s_f ≥ cut_abs`, and either `|W_f| < 3` or
|
||
`s_f · 1000 ≥ cut_rel · m_f`.
|
||
5. **Soft cut** at *f*: `len ≥ target` and `2 · s_f ≥ cut_abs`.
|
||
6. **Return clause.** A cut at *f* is rejected when `D(f + k, f − 1) < cut_abs` for
|
||
some *k* in `1..=min(L, last − f)`: the content came back within *L* frames, so *f*
|
||
starts a flash.
|
||
7. **Revisit clause.** A cut at *f* is rejected when `D(f, f − 1 − j) < cut_abs` for
|
||
some *j* in `1..=min(L, f − 1)`: *f* returns to content seen within *L* frames, so it
|
||
ends a flash. Frames before *p* count.
|
||
8. A hard or soft cut that clauses 6 and 7 do not reject starts a piece at *f*.
|
||
Otherwise, when `len ≥ max`, a piece starts at *f*.
|
||
|
||
Frame 0 starts the first piece. *L* is `lookahead_frames`, *last* the index of the
|
||
source's last frame, `cut_abs` and `cut_rel` the policy's permille values, and `min`,
|
||
`target` and `max` the policy's milliseconds on the component's timebase (§4.4). A
|
||
decision about frame *f* reads no frame after `f + L`, so a live segmenter decides it
|
||
once frame `f + L` has arrived, and a live and an on-demand segmenter with equal *L*
|
||
produce equal boundaries. A piece that follows a cut is flagged a scene cut; one
|
||
started at `max`, forced.
|
||
|
||
**4.4** A piece's duration is an integer number of timebase units, and lies between
|
||
`min` and `max` except the last piece of a source, which may be shorter than `min`.
|
||
A duration in milliseconds converts to timebase units as
|
||
`floor(ms · num / (1000 · den))`; a result of 0 becomes 1, and one above `2^64 − 1`
|
||
becomes `2^64 − 1`.
|
||
|
||
A piece is at most ten minutes long (`max_ms` at most 600000) and at most `2^24` units
|
||
on its component's timebase (§3.4 item 5). A verifier takes a piece whole: it decodes
|
||
every frame of a base, holds a still's one frame for every frame of its piece and hashes
|
||
each (§5.7, §6.5), and reads one floor unit per frame (§7.2). So the longest piece a
|
||
policy allows is the most work a verifier does for one piece, and a package states it.
|
||
Ten minutes is far past any piece a delivery cuts (the default `max_ms` is four
|
||
seconds); `2^24` units bounds that work on any timebase, since a timebase may have up to
|
||
`2^32 − 1` units a second.
|
||
|
||
**4.5** *Components.* Every component of a package shares one presentation timeline
|
||
that starts at 0. The policy names one **lead** component; every other component cuts
|
||
its pieces at the lead's boundary instants, each converted to its own timebase as
|
||
`floor(start_lead · num_c · den_lead / (den_c · num_lead))` (above `2^64 − 1`,
|
||
`2^64 − 1`). A sink presents piece *p* of every component from that start.
|
||
|
||
**4.6** *Classification.* Each video piece has a kind, which rules match
|
||
(`rule.when.piece_kind`). Over the piece's consecutive frame pairs, with `Y` the luma
|
||
of §4.3:
|
||
|
||
- `mad_milli = Σ |Y_f − Y_{f−1}| · 1000 / (wh · (n − 1))`, and 0 when the piece has
|
||
one frame (*n* = 1);
|
||
- `changed_permille` = the pixels whose luma differs, `· 1000 / (wh · (n − 1))`, and 0
|
||
when *n* = 1;
|
||
- `occupancy` = the non-empty histogram bins of the piece's first frame.
|
||
|
||
The kind is the first that holds of: **static** (`mad_milli ≤ static_mad_milli` and
|
||
`changed_permille ≤ static_permille`); **synthetic** (`occupancy ≤ synthetic_bins`);
|
||
**motion_high** (`mad_milli ≥ motion_high_milli`); **natural**.
|
||
|
||
**4.7** *Sources.*
|
||
|
||
- **Y4M**: 8-bit 4:2:0 (`C420jpeg`, `C420paldv`, `C420mpeg2`, `C420`, or no `C`
|
||
parameter) or 4:4:4 (`C444`), progressive. A header that is not `YUV4MPEG2` with a
|
||
width and a height is refused `y4m-header`; an interlaced stream or frame,
|
||
`y4m-interlaced`; another colour space, `y4m-colorspace`; a `FRAME` line that sets
|
||
the width, height or colour space, `y4m-size-change`; a frame that ends early or a
|
||
line that is not `FRAME`, `y4m-truncated`. Each pixel converts to RGB8 by BT.709
|
||
limited range, with 4:2:0 chroma taken at `(y >> 1, x >> 1)`:
|
||
- `R = clip((298(Y−16) + 459(Cr−128) + 128) >> 8)`
|
||
- `G = clip((298(Y−16) − 55(Cb−128) − 136(Cr−128) + 128) >> 8)`
|
||
- `B = clip((298(Y−16) + 541(Cb−128) + 128) >> 8)`
|
||
|
||
where `>>` is the arithmetic shift and `clip` clamps to 0..=255.
|
||
- **WAV**: RIFF/WAVE with a PCM `fmt ` chunk (format tag 1) of 16-bit samples, block
|
||
alignment `2 · channels`. Anything else is refused `wav-format`; a chunk that ends
|
||
early, `wav-truncated`.
|
||
|
||
## 5. Objects
|
||
|
||
**5.1** *One envelope.* Each object is exactly one `WAI1` or `WAI2` envelope and
|
||
nothing after it ([http-delivery](http-delivery.md) §5.1).
|
||
|
||
**5.2** *Position independence.* An object's bytes **MUST NOT** depend on its position
|
||
in a package or on the package it belongs to: no member of its manifest names a piece,
|
||
a time, an asset, a class or a component, so two pieces with identical content are one
|
||
object. The reference packager writes a base's manifest as SPEC §3 orders it, with
|
||
`media` the component's media, `intent` `replicate`, `model_requirement` the rung's
|
||
capability with no `fallback` member, `conditioning.kind` the capability's last
|
||
dot-separated segment (`int_motion`, `av1`), and `target` `{"frames": n, "h": h, "w": w}`
|
||
(the piece's frame count and dimensions, for a still too, whose one frame is held for
|
||
the piece, §5.7; for a floor, its sequence header's maximum frame height and width); a
|
||
floor's `derived_from` (§7.2) comes after `conditioning`.
|
||
|
||
**5.3** *Name.* An object is named by its content hash ([jwp-receipts](jwp-receipts.md)
|
||
§2.1), `BLAKE3("moq-jwp:object\x01" ‖ envelope)`, as `objects/<64 lowercase hex>.wai`:
|
||
the same value is its stage-index entry (§6), its receipt leaf, its MoQ content hash and
|
||
its HTTP strong `ETag` ([http-delivery](http-delivery.md) §5.2).
|
||
|
||
**5.4** *Refinements.* A refinement object carries `refines`
|
||
([staged-delivery](staged-delivery.md) §3): `base` the content hash of its piece's base,
|
||
`layer` its layer, and as producers in `prev` only objects of the layers below it, the
|
||
object its entry refines (§6.2) among them.
|
||
|
||
The reference packager writes, for each refinement rung of a piece's ladder (§5.7), one
|
||
object of `wai.video.int_refine` over the piece's `wai.video.int_motion` base, layer by
|
||
layer. Its manifest is the one `wai stage refine` writes, as SPEC §3 orders it: `media`
|
||
the component's, `intent` `replicate`, the capability with no `fallback`,
|
||
`conditioning.kind` `wir1`, `refines` with `base` the base's content hash, `prev` the
|
||
content hash of the object of the layer below (the base for layer 1) as a string, and
|
||
`layer`, and `target` null. Its payload is a `WIR1` (staged-delivery Appendix A) of unit
|
||
kind RGB8, one unit per frame of the piece, the identity predictor and one table,
|
||
written by `wai-rs`'s refinement encoder (`staged::wir1::encode`) at the rung's step
|
||
`q`:
|
||
|
||
- each sample's residual is `rdiv(source − below, q)`, the quotient rounded half away
|
||
from zero, where *below* is the stage the layer refines as a sink reconstructs it;
|
||
no frame predicts another;
|
||
- the table's `offset` is the least residual and it spans every residual (where they
|
||
span more than 4095, the 4095 around the lower median), fitted by §5.5's rule, the
|
||
escape at 1; a residual past it is coded through the escape;
|
||
- one rANS stream per frame.
|
||
|
||
The stage the layer reconstructs, `clamp(below + r·q, 0, 255)`, is what the next layer
|
||
refines. The packer applies each layer to the decoded base with the reference sink
|
||
(staged-delivery §4) before it keeps it, and the sink's output must be the encoder's.
|
||
|
||
**5.5** *Integer video.* An object of `wai.video.int_motion` carries a `WIV1` payload
|
||
with keyframe 0, as SPEC §5 pins it ([integer-payloads](integer-payloads.md) §3); this
|
||
extension defines no other layout for it, and its decode is that section's. The
|
||
reference packager writes these with one encoder (`wai-rs` `int_video_enc`) at the step
|
||
§5.6 chooses. The encoder's choices
|
||
make a payload a function of the source frames, the rung's `block`, `search` and
|
||
`mv_precision` (§3.3), and the step `q`:
|
||
|
||
- **Motion search** (frames 1 on): blocks in raster order; for each, every vector
|
||
`(dy, dx)` with both components in `−R..=R`, `R = search · mv_precision` (at most
|
||
127), `dy` outer and `dx` inner, each ascending. Its cost is the sum, over the
|
||
block's pixels inside the frame and the three channels, of `|source − prediction|`,
|
||
where the prediction is the decode's (integer-payloads §3) from the previous frame
|
||
as decoded. The least cost wins; a tie goes to the smaller `|dy| + |dx|`, then to the
|
||
earlier vector.
|
||
- **Residual**: each frame, each pixel in raster order, each of R, G, B: the
|
||
prediction (128 for frame 0) is subtracted from the source and divided by `q`,
|
||
rounding halves away from zero; the frame as decoded is `clamp(prediction + r·q, 0,
|
||
255)`, and the next frame predicts from it.
|
||
- **Table**: one residual table for the clip, with `offset` the least residual, `top`
|
||
the greatest, and `h_i` the count of residual `offset + i`, for
|
||
`n = min(top − offset + 1, 4095)` symbols. With `R = 2^16 − n − 1` and `H = Σ h_i`, symbol *i* gets frequency
|
||
`1 + ⌊R·h_i / H⌋`; the `R − Σ⌊R·h_i / H⌋` left over go one each to the symbols with
|
||
the largest `R·h_i mod H`, the lower symbol first on a tie; the escape gets 1. A
|
||
residual past the table's span is coded through the bypass escape
|
||
(integer-payloads §2).
|
||
- A clip of one frame is written with `block` 1 and `mv_precision` 1, as the format
|
||
requires.
|
||
|
||
The encoder also states the SSE of its reconstruction against the source: the sum of
|
||
squared differences over every sample. It codes in two passes (the first finds the
|
||
vectors, the reconstruction and the residuals' counts; the second derives each
|
||
residual again and codes it last first), so beyond its input it holds the
|
||
reconstruction, the vectors, the stream and the residual table: at most
|
||
`n·h·w·3 + 24n + 12·v + 3·(2·n·h·w·3 + 16) + T` bytes for `n` frames and `v` motion
|
||
vectors (`int_video_enc::working_bytes`). `T` is the widest table's, of `S = 2·255 + 1`
|
||
symbols: its fit, `16·(S + 1) + 2·8·S + 4·(S + 2)` bytes (a frequency for each symbol
|
||
and the escape, an index for each symbol and the sort's scratch, the table), and the
|
||
table's three copies and the payload's header, `3·4·(S + 2) + 64`. For a clip past the
|
||
format's caps, which the encoder refuses before it allocates, the figure is `2^64 − 1`.
|
||
|
||
**5.6** *Choosing the step.* For each piece, the base encode of a `wai.video.int_motion`
|
||
rung chooses its step from `q_candidates`, read as a set (a value listed twice is one
|
||
candidate). It encodes the piece at each candidate, from the largest down, and chooses
|
||
the first whose `floor(SSE · 1000 / (3·w·h·n)) ≤ max_sse_px_milli`, where `SSE` is the
|
||
encoder's (§5.5) and *n* the piece's frames: the largest step that meets the bound, so
|
||
candidates that meet it alike are decided by their step. Where no candidate meets it,
|
||
it chooses the smallest. The function the reference packager's base encode calls is
|
||
`wai_pack::encode::choose_q`.
|
||
|
||
**5.7** *A piece's ladder, and stills.* A piece is coded in the rungs of its **ladder**:
|
||
those the matching rules' `then.ladder` names (§3.4 folds them: the last that sets it),
|
||
where they are rungs of the piece's component; otherwise the component's default ladder,
|
||
its first base rung in ladder order, the chain of refinement rungs over it, and its floor
|
||
rungs. A rule's ladder therefore chooses a piece's representation: its base, how many
|
||
layers refine it, and nothing else (§3.4 item 10). A piece has one entry per rung of its
|
||
ladder: its base, each refinement in layer order, then each floor (§6.2), and a
|
||
verifier holds an index to that (§11.3).
|
||
|
||
A base of `wai.image.png` is a **still**: the piece's first frame, held for the piece. A
|
||
sink presents that frame for each of the piece's frames, so its prefix is the frame
|
||
repeated and its digest the clip digest of the repeats (§6.5), its `sse` is over the
|
||
repeats against the piece's frames (§6.6), and its manifest's `target` names the piece's
|
||
frames (§5.2). The reference packager writes the frame as a PNG every byte of which is a
|
||
pure function of the frame, so an independent implementation writes the same file:
|
||
|
||
- the signature, `IHDR` (the width, the height, bit depth 8, colour type 2, compression
|
||
0, filter 0, interlace 0), one `IDAT` holding the whole compressed stream, and `IEND`;
|
||
each chunk a big-endian length, its type, its data and the CRC-32 of type and data;
|
||
- every row filtered `Sub` (filter byte 1): each byte less the byte three before it in
|
||
the row, modulo 256, the first pixel's bytes less 0;
|
||
- a zlib stream (RFC 1950) of `0x78 0x01`, one final deflate block (RFC 1951) of the
|
||
fixed Huffman codes, and the Adler-32 of the filtered rows;
|
||
- the block's symbols, from the first byte of the filtered rows: where at least three
|
||
bytes remain and an earlier position within 32768 bytes starts with the same three
|
||
bytes, the latest such position is the match, as long as the two runs agree (at most
|
||
258 bytes), written as that length and the distance to it; otherwise one literal.
|
||
Every position a symbol covers that has three bytes left becomes the latest position
|
||
of its three bytes. The block ends with symbol 256, and the last byte is padded with
|
||
zero bits.
|
||
|
||
A verifier reads a still as a PNG of 8-bit RGB without interlace, by PNG's chunk rules:
|
||
every chunk's type four letters and its CRC checked; `IHDR` first; an optional `PLTE`
|
||
(a suggested palette) before the image data; the `IDAT`s consecutive; ancillary chunks
|
||
read past; any other critical chunk refused; `IEND` last, empty, and nothing after it.
|
||
It inflates the zlib stream as the zlib library's inflate does, so two verifiers agree
|
||
on every stream: the header's check bits, method 8, a window of at most 32 KiB and no
|
||
preset dictionary; a stored block's length and its complement agree; at most 286
|
||
literal/length and 30 distance codes, a complete code-length code, a repeat with a
|
||
length before it and within the code lengths, and a code for the end of the block; no
|
||
Huffman code over-subscribed, and none incomplete but a literal/length or distance code
|
||
whose longest code is one bit (a lone one-bit code, or none); a code with no symbol, or
|
||
a distance past the bytes written, refused where it is read; the Adler-32, and nothing
|
||
after it. The rows are exactly `1 + 3·width` bytes each, under the five filters. A still
|
||
a verifier does not read is `prefix-digest` (§11.3).
|
||
|
||
## 6. Stage index
|
||
|
||
**6.1** A package carries, per component, one stage index, the file `index-<id>.wsix`
|
||
(`WSIX`), per window: one window for on-demand content, a chain of windows for live
|
||
(§9.2). A window holds whole pieces. Integers are little-endian.
|
||
|
||
**6.2** *Layout.* A 160-byte header, then `n_pieces` piece records of 32 bytes,
|
||
`n_entries` entry records of 128 bytes, `n_classes` class records of 16 bytes, the class
|
||
plan, and the string table. Nothing follows the string table. Reserved and padding
|
||
bytes are zero.
|
||
|
||
| Offset | Size | Header field |
|
||
|---|---|---|
|
||
| 0 | 4 | `"WSIX"` |
|
||
| 4 | 2 | version, 1 |
|
||
| 6 | 2 | `media_class`: 0 video, 1 image, 2 audio, 3 world, 4 feed, 5 avatar, 6 film |
|
||
| 8 | 4 | `tb_num` |
|
||
| 12 | 4 | `tb_den` |
|
||
| 16 | 4 | `n_pieces` |
|
||
| 20 | 4 | `n_entries` |
|
||
| 24 | 4 | `n_strings` |
|
||
| 28 | 4 | `n_classes` |
|
||
| 32 | 32 | `policy_digest` (§3.2) |
|
||
| 64 | 32 | `prev_index_digest`: the previous window's index digest; zero for on-demand content and a first window |
|
||
| 96 | 8 | `first_piece`: the absolute number of the window's first piece |
|
||
| 104 | 8 | `first_entry`: the absolute ordinal of the window's first entry across the chain |
|
||
| 112 | 32 | `plan_digest` (§4.2) of the pieces' starts |
|
||
| 144 | 2 | `component`: the component's id, as a string index |
|
||
| 146 | 14 | reserved |
|
||
|
||
| Offset | Size | Piece record field |
|
||
|---|---|---|
|
||
| 0 | 8 | `start`, in timebase units |
|
||
| 8 | 4 | `duration`, in timebase units |
|
||
| 12 | 4 | `first_entry`, window-relative |
|
||
| 16 | 2 | `n_entries` |
|
||
| 18 | 1 | `kind`: 0 natural, 1 static, 2 synthetic, 3 motion_high (§4.6), 4 world segment, 5 audio |
|
||
| 19 | 1 | flags: bit 0 scene cut, bit 1 forced at `max` (§4.3), bit 2 started by a cue, bit 3 aligned to the lead (§4.5) |
|
||
| 20 | 4 | `rules`: bit *i* set when rule *i* matched the piece (§8.1) |
|
||
| 24 | 4 | `moments`: bit *i* set when the piece lies in moment *i* (§8.1) |
|
||
| 28 | 4 | reserved |
|
||
|
||
| Offset | Size | Entry record field |
|
||
|---|---|---|
|
||
| 0 | 32 | `content_hash` (§5.3) |
|
||
| 32 | 8 | `byte_len`: the whole envelope's length |
|
||
| 40 | 8 | `piece`: the absolute piece number |
|
||
| 48 | 4 | `refines`: the window-relative index of the entry this one refines; `0xFFFFFFFF` for none |
|
||
| 52 | 2 | `capability`, as a string index |
|
||
| 54 | 1 | `role`: 0 base, 1 refinement, 2 floor, 3 snapshot, 4 ops, 5 receipt, 6 asset |
|
||
| 55 | 1 | `layer` |
|
||
| 56 | 1 | `prefix_tier`: 0 none, 1 entropy-consistency, 2 decode-equivalence (§6.4) |
|
||
| 57 | 1 | flags: bit 0 deferrable, bit 1 duplicate (an earlier entry of the index has the content hash), bit 2 the manifest carries `derived_from` |
|
||
| 58 | 2 | reserved |
|
||
| 60 | 4 | `deadline_ms`: 0 for none (§8.6) |
|
||
| 64 | 32 | `prefix_digest` (§6.5) |
|
||
| 96 | 8 | `sse` (§6.6); `2^64 − 1` when not computed |
|
||
| 104 | 4 | `manifest_len`: the envelope's `man_len` |
|
||
| 108 | 4 | reserved |
|
||
| 112 | 8 | `payload_len`: the envelope's payload bytes (for a `WAI2`, every component's) |
|
||
| 120 | 8 | reserved |
|
||
|
||
`byte_len`, `manifest_len` and `payload_len` are the envelope's own lengths; for an
|
||
object whose payload format WAI checks they are its counted bytes
|
||
([staged-measurement](staged-measurement.md) §3), so a verifier also requires each such
|
||
payload to parse as its format, after the other object rules of §6.3
|
||
(`object-lengths`).
|
||
|
||
A class record is `u16` name (a string index), `u8 max_layer`, `u8` flags (bit 0
|
||
floor only), `u32 max_kbps` (0 for none) and 8 reserved bytes, in the policy's class
|
||
order. The class plan is `n_classes` rows of `n_pieces` bytes, each the highest layer
|
||
the class lists for the piece or `0xFF` for the floor only (§8.1), zero-padded to a
|
||
multiple of 8. The string table is `n_strings` strings, each a `u16` length and that
|
||
many bytes of UTF-8, at least one, zero-padded to a multiple of 8. It is the component's
|
||
id, then the class ids in policy order, then each capability at its first use, and a
|
||
package's verifier holds an index to that (§11.3).
|
||
|
||
index_digest = BLAKE3("wai:pack-stage-index\x01" ‖ file)
|
||
leaf_i = BLAKE3("wai:pack-stage\x01" ‖ entry record i)
|
||
stage_root = the jwp-receipts §2.2 Merkle root over the leaves
|
||
|
||
A reader **MUST** refuse a file that is not this layout, the first that applies, in
|
||
this order:
|
||
|
||
1. its first four bytes are not `WSIX`, a file of fewer included (`index-magic`);
|
||
2. a file of fewer than six bytes (`index-truncated`), or a version other than 1
|
||
(`index-version`);
|
||
3. a file shorter than the header, or than the tables its counts give: the piece, entry
|
||
and class records, the class plan with its padding, each string's length and bytes,
|
||
and the string table's padding, every size taken from its count before anything is
|
||
allocated (`index-truncated`);
|
||
4. a reserved or padding byte that is not zero (`index-reserved`);
|
||
5. a media class with no meaning (`index-media-class`); then, entry by entry, a role,
|
||
then a tier, with none (`index-role`, `index-tier`);
|
||
6. a string that is empty or not UTF-8, then a string index past the table
|
||
(`index-string`);
|
||
7. bytes after the string table's padding (`index-trailing`).
|
||
|
||
**6.3** *Rules.* An index lists every WAI object of its component, in piece order.
|
||
Files that are not WAI envelopes (CMAF initialization segments and fragments, FLAC
|
||
files) **MUST NOT** appear in it. A verifier **MUST** refuse an index that breaks one
|
||
of these, checked in this order:
|
||
|
||
1. A timebase of zero (`index-timebase`). An index with no previous window
|
||
(`prev_index_digest` zero: on-demand content, or a chain's first window) whose
|
||
`first_piece` or `first_entry` is not 0, or whose first piece does not start at 0, the
|
||
start of the presentation timeline (§4.5) (`window-start`). Then piece by piece: a
|
||
piece that lasts no time or does not start where the one before it ended
|
||
(`piece-order`); whose
|
||
`first_entry` is not the number of entries before it, or that has none
|
||
(`piece-entries`); of a kind or with a flag that has no meaning (`piece-kind`). Then
|
||
pieces whose entries are not the index's entries (`piece-entries`).
|
||
2. Entry by entry, in order: an entry that does not name its piece's absolute number
|
||
(`piece-entries`); a flag with no meaning (`entry-flags`); within its piece, a role
|
||
before one that comes earlier in the order base, refinement, floor, any other, or a
|
||
refinement at a lower layer than the refinement before it (`entry-order`); an entry
|
||
that is not a refinement above layer 0, a refinement at layer 0, or `refines` on an
|
||
entry that is not a refinement or missing from one (`layer-role`); a
|
||
refinement whose `refines` is outside the window (`refines-outside-window`), is not
|
||
an earlier entry of its own piece (`refines-mismatch`) or is not one layer below it
|
||
(`layer-gap`); its tier (§6.4: `tier-entropy-passthrough`, `tier-weakest`, then
|
||
`tier-mismatch`); a non-zero `prefix_digest` with a tier other than
|
||
decode-equivalence (`digest-without-tier`); a deadline on an entry that is not a
|
||
refinement (`deadline-role`); a floor without flag bit 2 (`floor-derived-from`); a
|
||
content hash an earlier entry has at another length (`dup-length`); the duplicate
|
||
flag other than set exactly when an earlier entry has the content hash (`dup-flag`).
|
||
3. Class by class: a flag with no meaning (`class-flags`); a plan value other than
|
||
`0xFF` for a floor-only class, or other than `0xFF` or at most both the class's
|
||
`max_layer` and the piece's highest layer otherwise (`class-plan`).
|
||
|
||
With the objects in hand, a verifier **MUST** also refuse, entry by entry: bytes of
|
||
another length (`object-length`) or that do not hash to the entry's content hash
|
||
(`object-hash`); bytes that are not one `WAI1` or `WAI2` envelope and nothing else
|
||
(`non-envelope-entry`); a `man_len` or a payload length other than the entry's
|
||
(`object-lengths`); a capability other than the entry's (`object-capability`; for a
|
||
`WAI2`, its first entry's); a refinement whose object is not a `WAI1` whose `refines`
|
||
names its piece's base, its layer and producers of the layers below among which the
|
||
entry it refines (§5.4, `object-refines`); and `derived_from` (SPEC §7.1) present
|
||
otherwise than bit 2 says, or not well formed (`object-derived-from`). A member whose
|
||
value is `null` is absent; for a `WAI2`, `derived_from` is present when any entry
|
||
carries it, and well formed when every entry that carries it is. A manifest that does
|
||
not parse, or names a member twice, is not an envelope (`non-envelope-entry`).
|
||
|
||
**6.4** *Tier.* The tier a capability's own decode holds is decode-equivalence for a
|
||
capability whose registry class reconstructs exactly (`StandardDefined`, and
|
||
`NeuralIntegerExact`, whose pin a sink verifies before it decodes),
|
||
entropy-consistency for `NeuralEntropyExact`, and none otherwise (SPEC §5, §7).
|
||
|
||
- An entry at layer 0 states its capability's tier, except a floor, which states none
|
||
(§7.4).
|
||
- A prefix with one or more refinements holds decode-equivalence only if every object
|
||
along it does, and none otherwise ([derived-tracks](derived-tracks.md) §3, rules 1
|
||
and 2): a refinement's tier is decode-equivalence exactly when the entry it refines
|
||
states decode-equivalence and its own capability's tier is decode-equivalence.
|
||
- An entry with `layer` > 0 **MUST NOT** state entropy-consistency
|
||
(`tier-entropy-passthrough`); one with `layer` > 0 that states decode-equivalence over
|
||
a prefix that does not hold it is refused `tier-weakest`; any entry stating another
|
||
tier than the one this section gives, `tier-mismatch`.
|
||
|
||
**6.5** `prefix_digest` is the SHA-256 decoded-output digest (SPEC §7, *Digests*) of the
|
||
prefix the entry completes when `prefix_tier` is decode-equivalence, and all zero
|
||
otherwise: for video, the clip digest over RGB8 frames (for a still, of its frame held
|
||
for the piece's frames, §5.7); for an image, the RGB8 frame digest; for audio, the digest of the s16le interleaved samples; for world, feed, avatar
|
||
and film entries, zero (their state hashes live in their receipts). A verifier decodes
|
||
the prefix and compares.
|
||
|
||
**6.6** `sse` is the integer sum, over every output sample of the prefix, of the
|
||
squared difference from the source sample the packager coded (RGB8 channels, or s16
|
||
samples). It is the producer's statement: a verifier holding the source recomputes it,
|
||
and one without the source cannot. A floor's `sse` is `2^64 − 1`.
|
||
|
||
## 7. Floor and `derived_from`
|
||
|
||
**7.1** Every piece of a video, image or audio component **MUST** have a floor rendition
|
||
in a capability of SPEC §5's mandatory floor or recommended set; a packager refuses a
|
||
video component with no floor rung (`floor-missing`). A film or world package SHOULD
|
||
have one. This revision packs a supplied AV1 floor (`wai.video.av1` or
|
||
`wai.video.av1.lossless`, `source = "supplied"`).
|
||
|
||
**7.2** *Floor objects.* Each piece's floor is one object of the floor rung's capability
|
||
(§5.2), whose manifest carries `derived_from` (SPEC §7.1, *Derived renditions*) naming
|
||
the piece's base: `motif` the base's content hash, `motif_capability` its capability,
|
||
`renderer` the floor's maker, and `settings` `BLAKE3("wai:render-settings\x01" ‖
|
||
JCS(settings))` (§3.6). For a supplied floor, `renderer` is `supplied@<the supplied
|
||
listing's SHA-256>` and the settings are `{"source": "supplied", "supplied_listing":
|
||
"<that SHA-256>"}`, so neither names a position. This profile fixes these members: they
|
||
are not the producer's statement here (SPEC §7.1), and a verifier holds every floor
|
||
object to them, to its framing header and to its target (§11.3). An AV1 floor object's payload is the
|
||
length-prefixed framing the reference sink decodes (`wai-rs` `codecs::video`): `n`,
|
||
`h`, `w`, `fps_num`, `fps_den` as `u32` little-endian (the piece's temporal units, the
|
||
sequence header's maximum frame height and width, the component's timebase `num` and
|
||
`den`), then each temporal unit as a `u32` little-endian length and its bytes as the
|
||
stream holds them.
|
||
|
||
**7.3** *A supplied floor* is a directory holding, per video component, `<id>.obu`: an
|
||
AV1 stream in the low-overhead OBU format (every OBU with its size field), one temporal
|
||
unit per source frame. Its listing (SPEC §3.1) of every file is pinned by the policy's
|
||
`floor.supplied_listing`. A packager **MUST** refuse, before it writes anything:
|
||
|
||
- a directory whose listing does not hash to `floor.supplied_listing`
|
||
(`floor-listing-mismatch`), and a component with no stream in it
|
||
(`floor-stream-missing`);
|
||
- a stream that does not split into temporal units each holding a frame, whose first
|
||
unit holds no sequence header, with a sequence header whose bytes differ from the
|
||
first's, or with a unit that starts at a shown key frame (below) without a sequence
|
||
header (`floor-av1-malformed`);
|
||
- another number of temporal units than source frames (`floor-frame-count`);
|
||
- a unit that does not start at a shown key frame at a piece's first frame, or one that
|
||
does elsewhere unless `floor.extra_keyframes` is true (`floor-keyframe-mismatch`). A
|
||
unit starts at a shown key frame when its first frame header has `frame_type`
|
||
`KEY_FRAME` and `show_frame` 1, or the sequence uses the reduced still-picture header;
|
||
a unit that shows an existing frame does not.
|
||
|
||
The plan a supplied floor is made against is the **keyframe plan**, the file
|
||
`plan-<id>.txt`: each piece's first frame in decimal, one per line, each line ended by a
|
||
line feed. Its pieces' starts are the plan of §4.2, and `wai-pack plan` writes it from the
|
||
source and the policy before any floor is encoded.
|
||
|
||
**7.4** The floor is presentation. Its output is never compared with the primary's, and
|
||
no tier or `replicate` claim covers it.
|
||
|
||
**7.5** A floor produced by an encoder that is not a pure function of its input (§11)
|
||
**MUST** name that encoder and its version in `derived_from.renderer`.
|
||
|
||
**7.6** *CMAF.* The floor of a video component is also written as CMAF files, which no
|
||
stage index lists: `floor/<id>/init.mp4`, and one fragment per piece,
|
||
`floor/<id>/<SHA-256 of the fragment>.m4s`. Box layouts are ISO/IEC 14496-12's, with the
|
||
AV1 binding's sample entry; every integer is big-endian and every time 0:
|
||
|
||
- `init.mp4`: `ftyp` (major brand `cmfc`, minor version 0, compatible `cmfc`, `iso6`,
|
||
`av01`), then `moov` holding `mvhd` (version 0, timescale `tb_num`, rate 1, volume 1,
|
||
the unity matrix, next track 2), `trak` (`tkhd` version 0 with flags 3, track 1,
|
||
layer, alternate group and volume 0, the unity matrix, the width and height in 16.16; `mdia`: `mdhd` version 0, timescale
|
||
`tb_num`, language `und`; `hdlr` `vide` named `video` with its terminating zero byte;
|
||
`minf`: `vmhd` with flags 1,
|
||
`dinf` with one self-contained `url `, and `stbl`: `stsd` with one `av01` sample entry
|
||
of the width and height, data reference 1, resolution 72 dpi, frame count 1, an empty
|
||
compressor name, depth 24 and pre-defined −1, holding `av1C`, then empty `stts`,
|
||
`stsc`, `stsz` and `stco`), and `mvex` with `trex` (track 1, sample description 1).
|
||
`av1C` is marker and version 1, then the sequence header's profile, level, tier,
|
||
bit depth, monochrome and chroma fields, no initial presentation delay, then the
|
||
sequence header OBU as the stream holds it. The width and height are the sequence
|
||
header's maximum frame width and height.
|
||
- A fragment: `moof` holding `mfhd` (sequence number piece + 1) and `traf` (`tfhd` with
|
||
default-base-is-moof, track 1; `tfdt` version 1, the piece's start × `tb_den`; `trun`
|
||
version 0 with data offset, sample duration, size and flags, the offset pointing at
|
||
the `mdat` payload), then `mdat`. Each sample is a temporal unit without its temporal
|
||
delimiter, lasting `tb_den`; its flags are `0x02000000` at a shown key frame and
|
||
`0x01010000` otherwise.
|
||
|
||
The codecs string of the floor is the AV1 binding's short form,
|
||
`av01.<profile>.<level, two digits><M or H>.<bit depth, two digits>`.
|
||
|
||
## 8. Classes, catalogs and delivery
|
||
|
||
**8.1** *What a class lists.* The stages listed for a class are a pure function of the
|
||
class, the piece and the effective policy, so every viewer of a class receives the same
|
||
catalog bytes.
|
||
|
||
- A piece that starts at unit `start` and lasts `duration` units spans the
|
||
milliseconds from `s_ms` up to `e_ms`, the floors of `start · den · 1000 / num` and of
|
||
`(start + duration) · den · 1000 / num`; it lies in moment *i* when `s_ms < to_ms` and
|
||
`e_ms > from_ms`. Rule *i* matches it when each condition the rule states holds: its
|
||
kind is in `when.piece_kind`, and a tag of a moment it lies in is in
|
||
`when.moment_tag`. Matching rules fold in document order (§3.4).
|
||
- A floor-only class lists the floor (`0xFF`). Otherwise the class lists
|
||
`min(max_layer, the rules' then.max_layer, the piece's highest layer)`; under
|
||
`max_kbps`, while the listed prefix needs more than `max_kbps`, that is
|
||
`ceil(8 · bytes · num / (1000 · duration · den))` kilobits per second over the bytes of
|
||
the listed bases and refinements, the top layer is dropped, and a base that alone
|
||
needs more makes the piece floor-only for the class.
|
||
|
||
**8.2** A catalog **MUST NOT** list a refinement without the stages below it.
|
||
|
||
**8.3** *HTTP.* A package is offered in [http-delivery](http-delivery.md)'s Mode N
|
||
(§11.1 there): the classical presentation is the floor alone, and WAI objects are
|
||
offered in playlists of their own.
|
||
|
||
- A WAI object is none of the media segment formats of RFC 8216 §3. A WAI playlist is
|
||
offered only to a sink that recognizes its codecs value from its own capability list,
|
||
and a packager **MUST** write a classical master that names nothing of WAI.
|
||
- A browser's media-capabilities query answers only for the browser's own decoders,
|
||
and **MUST NOT** be used to decide whether a `wai1.*` value can be decoded
|
||
([http-delivery](http-delivery.md) §4.3).
|
||
- Each class's WAI playlist **MUST** name its class and its stage view.
|
||
- A variant lists no bases without a capability in common ([http-delivery](http-delivery.md)
|
||
§4.2, `codecs_no_common_capability`). A class's HTTP view lists a piece only where the
|
||
piece's base is of the component's default base capability (§5.7, its first base rung's),
|
||
and plays the floor for any other piece, as for a piece the class takes as floor only: a
|
||
still (§5.7) among motion bases is carried over HTTP by the floor. The class's stage view
|
||
(§8.8) and its MoQ tracks (§8.4) list it.
|
||
Durations: a piece of *d* units lasts `floor(d · den · 1000 / num)` milliseconds,
|
||
written `S.mmm` (whole seconds, a full stop, three digits). A target duration is the
|
||
largest `ceil(d · den / num)`. A rate is `ceil(8 · bytes · num / (d · den))` bits per
|
||
second; a variant's `BANDWIDTH` is the largest over the pieces it lists and
|
||
`AVERAGE-BANDWIDTH` the rate of all their bytes over all their durations. A floor
|
||
variant's bytes per piece are its fragment's; a WAI variant's are the listed bases' and
|
||
refinements', over the pieces its HTTP view lists (above). Every playlist
|
||
line ends with a line feed. For component *c*, of frame size `W×H`, whose floor has
|
||
codecs string *F* and maximum frame size `FW×FH`:
|
||
|
||
- `hls/classic/master.m3u8`: `#EXTM3U`, `#EXT-X-VERSION:7`,
|
||
`#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-STREAM-INF:BANDWIDTH=<b>,AVERAGE-BANDWIDTH=<a>,CODECS="F",RESOLUTION=FWxFH`,
|
||
`../classic/floor-<c>.m3u8`: a URI that names the same playlist from every class's
|
||
directory, so each class's master differs from this one only by the session-data
|
||
line, as [http-delivery](http-delivery.md) §11.1 and §11.8 require.
|
||
- `hls/classic/floor-<c>.m3u8`: `#EXTM3U`, `#EXT-X-VERSION:7`,
|
||
`#EXT-X-TARGETDURATION:<t>`, `#EXT-X-MEDIA-SEQUENCE:0`, `#EXT-X-PLAYLIST-TYPE:VOD`,
|
||
`#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-MAP:URI="../../floor/<c>/init.mp4"`, then per
|
||
piece `#EXTINF:<S.mmm>,` and `../../floor/<c>/<sha256>.m4s`, then `#EXT-X-ENDLIST`.
|
||
- `hls/<class>/master.m3u8`: the classical master, byte for byte, with, when the class
|
||
lists a WAI object over HTTP, `#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"`
|
||
after `#EXT-X-INDEPENDENT-SEGMENTS`, and no other change.
|
||
- `hls/<class>/wai.m3u8`, when the class's HTTP view lists a WAI object: `#EXTM3U`,
|
||
`#EXT-X-VERSION:7`, `#EXT-X-INDEPENDENT-SEGMENTS`,
|
||
`#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.class",VALUE="<class>"`,
|
||
`#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.stages",URI="../../stages/<class>.json"`,
|
||
`#EXT-X-STREAM-INF:BANDWIDTH=<b>,AVERAGE-BANDWIDTH=<a>,CODECS="<union>",X-WAI-FLOOR="<floor>",RESOLUTION=WxH`,
|
||
`wai-<c>.m3u8`. The union and the floor are [http-delivery](http-delivery.md) §4.2's
|
||
over the objects the class lists.
|
||
- `hls/<class>/wai-<c>.m3u8`: the floor playlist's lines without `#EXT-X-MAP`, adjacent
|
||
to it ([http-delivery](http-delivery.md) §11.2); per piece, `#EXT-X-GAP` when the
|
||
class's HTTP view lists nothing of it (a floor-only piece, or one whose base is not of
|
||
the default base capability), `#EXTINF:<S.mmm>,`, each refinement the class lists,
|
||
in entry order, as `#WAI-LAYER:N=<layer>,URI="../../objects/<hash>.wai#n=<byte_len>"`,
|
||
then its base as `../../objects/<hash>.wai#n=<byte_len>`: a layer line follows its
|
||
segment's `EXTINF` ([http-delivery](http-delivery.md) §10.4).
|
||
|
||
Every HLS file above and every MPD below is a rendering of
|
||
[http-delivery](http-delivery.md)'s (Appendix G for a media playlist): the reference
|
||
packager writes them with the binding's one HLS and one DASH renderer.
|
||
|
||
DASH, the same as two MPDs ([http-delivery](http-delivery.md) §11.1), each
|
||
`<?xml version="1.0" encoding="UTF-8"?>`, then
|
||
`<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:full:2011" type="static" mediaPresentationDuration="PT<S.mmm>S" minBufferTime="PT<t>S">`
|
||
over the component's whole duration and its target duration, `<Period id="p0" start="PT0S">`,
|
||
one `AdaptationSet`, `</Period>` and `</MPD>`, indented by two spaces a level:
|
||
|
||
- `dash/classic.mpd`: `<AdaptationSet id="1" contentType="video" mimeType="video/mp4" codecs="F" width="FW" height="FH" segmentAlignment="true" startWithSAP="1">`
|
||
holding `<Representation id="<c>-floor" bandwidth="<b>">` with
|
||
`<SegmentList timescale="<num>">`: `<Initialization sourceURL="../floor/<c>/init.mp4"/>`,
|
||
a `SegmentTimeline` of one `<S t="<(start − first start)·den>" d="<d·den>"/>` per
|
||
piece, and one `<SegmentURL media="../floor/<c>/<sha256>.m4s"/>` per piece.
|
||
- `dash/<class>.mpd`, when the class's HTTP view lists a WAI object:
|
||
`<AdaptationSet id="1" contentType="video" mimeType="application/wai" codecs="<union>" width="W" height="H">`
|
||
holding `<EssentialProperty schemeIdUri="tag:transaction.science,2026:wai/http-delivery"/>`,
|
||
`<SupplementalProperty>`s of schemes `tag:transaction.science,2026:wai/floor` (value the
|
||
floor), `tag:transaction.science,2026:wai/class` (the class) and
|
||
`tag:transaction.science,2026:wai/stages` (`../stages/<class>.json`), then
|
||
per layer *k* up to the highest the class lists, a `Representation` with attributes
|
||
`id="<c>-l<k>"`, for *k* > 0 `dependencyId="<c>-l<k−1>"`, and `bandwidth` the peak rate of its own
|
||
layer's objects over the pieces it lists, whose
|
||
`SegmentList` has a timeline entry and a `SegmentURL media="../objects/<hash>.wai"`
|
||
for each piece the class lists to layer *k* (a gap where it does not).
|
||
|
||
The media type `application/wai` is proposed, not registered
|
||
([http-delivery](http-delivery.md) §3). This revision writes the catalogs of a package
|
||
of one component; how the variants and groups of several components are written is
|
||
defined with the packing of several components.
|
||
|
||
**8.4** *MoQ.* `moq/catalog.json` is RFC 8785 canonical JSON and a line feed:
|
||
`{"tracks": [...], "version": 1}`. A component's bases and refinements are a stage set of
|
||
[moq-streaming-format](moq-streaming-format.md) §4.1, one track per (layer, deadline),
|
||
by layer, then by deadline ascending, each written as that section's entry: namespace
|
||
the policy's `asset_id`; name `<c>/l<k>`, or `<c>/l<k>@<deadline>` for a refinement
|
||
whose deadline is not `defaults.deadline_ms`; `packaging` `"wai"`; `isLive` false;
|
||
`role` `video`; `renderGroup` 1; `wai_staged` with `set` the component's id, `layer` *k*
|
||
and `lead_ms` null (an on-demand package has no live schedule); `wai_capabilities` its
|
||
entries' capabilities by first appearance (a still among them on the base track, §5.7);
|
||
`wai_determinism` the weakest tier of its entries, absent when that is none; and
|
||
receipts declared: `wai_receipts` true, `wai_receipt_alias` the first eight bytes of
|
||
`BLAKE3("wai:pack-receipt-alias\x01" ‖ namespace ‖ 0x00 ‖ name)` as a little-endian
|
||
integer, less than `2^53` (so two packages published under one key keep apart chains),
|
||
and `wai_receipts_track` `<name>/receipts`. A track `depends` on every track that
|
||
carries an entry below one of its own entries, down the chain to the base, in track
|
||
order: a track of each layer below it, none at or above it, and never one the catalog
|
||
does not hold. A sink receives every track a track depends on (moq-streaming-format
|
||
§4.1), so a dependency is all of them. A refinement track carries
|
||
`wai_delivery_timeout_ms` (§8.6), a catalog field this extension defines under the `wai_`
|
||
prefix of moq-streaming-format §4. The base track has `altGroup` 1 when the component has
|
||
a floor: the floor is a track `<c>/floor` of `packaging` `"cmaf"`, in the same namespace,
|
||
with `altGroup` 1, `renderGroup` 1, `codec`, `width` and `height`, and a sink that
|
||
selects the floor subscribes to no layer track. The receipts tracks follow, one per media
|
||
track in its order, each `{"isLive": false, "name": "<name>/receipts", "namespace",
|
||
"packaging": "wai-receipts"}`. The catalog passes moq-streaming-format §4.1's check, and a
|
||
packager writes no other. A package holds no receipt: the publisher signs each track's
|
||
groups (a group a piece) on that track's chain as it publishes them (jwp-receipts §2.4.1;
|
||
§8.9 simulates it).
|
||
|
||
**8.5** *Priority.* The base track's publisher priority is 64 and layer *k*'s
|
||
`64 + 32·k`: a refinement track's value is strictly greater than that of every track it
|
||
depends on, so it is delivered after them (MOQT: a lower value is delivered first), and
|
||
moq-streaming-format §4.1's rule holds; a receipts track's is its media track's. The
|
||
priority is MOQT's (`DEFAULT_PUBLISHER_PRIORITY`), set by the publisher, not a catalog
|
||
field.
|
||
|
||
**8.6** *Deadline.* An entry's `deadline_ms` is the longest a refinement may arrive
|
||
after the base of its piece was published and still be presented; the rules' or
|
||
`defaults.deadline_ms`. A refinement track carries DELIVERY_TIMEOUT
|
||
`deadline_ms − live.layer_lag_ms[k]` (§3.4 has refused a policy where that is below 1).
|
||
Entries of one layer with different deadlines travel on different tracks. A relay may
|
||
drop a late refinement (MOQT §5.2), so a refinement never delays the base.
|
||
|
||
**8.7** *Presentation.* A sink presents piece *p* at the highest prefix whose objects
|
||
all arrived and verified by *p*'s presentation instant, and **MUST NOT** delay the base
|
||
to wait for a refinement. What the receipts of a dropped object say is
|
||
[jwp-receipts](jwp-receipts.md)' to define, not this extension's.
|
||
|
||
**8.8** *Stage views.* `stages/<class>.json` is RFC 8785 canonical JSON and a line feed:
|
||
`{"class": "<class>", "components": {"<c>": {"media_playlist":
|
||
"../hls/<class>/wai-<c>.m3u8", "pieces": [...]}}, "policy_digest": "<hex>"}`, with
|
||
`media_playlist` only when the class's HTTP view lists a WAI object (§8.3), and each piece
|
||
`{"dur", "p", "start", "stages": [...]}` with `"floor_only": true` when the class takes
|
||
it as floor only (and `stages` empty), and each listed stage `{"bytes", "h", "layer"}`,
|
||
a refinement's with `"refines"` (the content hash of the entry it refines) and
|
||
`"deadline_ms"`.
|
||
|
||
**8.9** *Delivery, simulated.* How a package's objects reach a subscriber over a link is
|
||
simulated by staged-measurement's delivery simulator ([staged-measurement](staged-measurement.md)
|
||
§10, Appendix B); its figures are labelled simulated, with their plan, and are never a
|
||
measurement of a network. The reference packager's `delivery` states the model and the
|
||
plan, and reads the outcome:
|
||
|
||
- **Model.** Each object a class lists of each piece (§8.1; a floor-only piece gives none),
|
||
on the component's frames as units (a frame of a whole number of microseconds): base the
|
||
piece, its layer, units the piece's frames, bytes the whole envelope, and its capture
|
||
time the instant it is published. A live packer (§9.2) publishes a piece's base when the
|
||
frame that decides the piece's end has arrived (the next piece's first frame plus
|
||
`lookahead_frames`, or the source's last frame, §4.3; frame *f* arriving at
|
||
`f · frame_us`), plus the encoder's delay, and layer *k* `layer_lag_ms[k]` after its
|
||
base. Each refinement takes its track's delivery timeout, `deadline_ms −
|
||
layer_lag_ms[k]` milliseconds (§8.6), as its own (staged-measurement Appendix B).
|
||
- **Plan.** Live; the link's round trip, rate and chunk; strict priority, which is §8.5's
|
||
order; each (piece, layer) a subgroup of its own; no base timeout; the subscriber's
|
||
layer cap (moq-streaming-format §4.1, *Capping*); one decode worker, bases first, each
|
||
decode 1 µs (the simulation is of the link); playout from the start-up, and a refinement
|
||
that arrives after its piece's instant discarded (§8.7).
|
||
- **Outcome.** Per piece, at its first frame's presentation instant (the start-up plus its
|
||
start): the highest layer presented by then, or none where its base arrived later; its
|
||
base's arrival; the layers dropped or abandoned. A class that lists nothing of any piece
|
||
sends nothing.
|
||
|
||
Under §8.5's priorities a refinement delays a base by at most one chunk and one decode
|
||
(staged-measurement §10.4). The publisher seals each track's group with a group receipt on
|
||
the track's chain (`wai_receipt_alias`, §8.4); a subscriber accepts a delivered object by
|
||
its Merkle proof, and holds a dropped one by inclusion alone, never as delivered
|
||
([jwp-receipts](jwp-receipts.md) §4.4).
|
||
|
||
## 9. Package receipt
|
||
|
||
**9.1** A version-2 package receipt binds the asset id; the policy digest; its parent
|
||
kind and parent receipt hash; per component its id, media class, index digest and stage
|
||
root; the entry, distinct-object, referenced-byte and stored-byte counts, which a
|
||
verifier recomputes from the indexes; the packager `builtin:<crate>@<version>`; the
|
||
energy figure with its class, or the unmetered form; the grant; and the signer. Its
|
||
signing payload, big-endian:
|
||
|
||
labelled_domain("wai:pack-receipt\x01", label)
|
||
u32 len ‖ asset_id ‖ policy_digest[32]
|
||
u8 parent_kind (0 none, 1 live-window, 2 re-pack) ‖ [parent_receipt_hash[32] if not 0]
|
||
u32 n ‖ n × ( u32 len ‖ id ‖ u8 media_class ‖ index_digest[32] ‖ stage_root[32] )
|
||
u64 total_entries ‖ u64 unique_objects ‖ u64 total_bytes ‖ u64 stored_bytes ‖ u64 work_units
|
||
u32 len ‖ packager
|
||
u64 joules_micro ‖ write_label(label)
|
||
grant_hash[32] ‖ u32 len ‖ capability ‖ u64 joule_ceiling_micro ‖ u8 0 | u8 1 ‖ u64 funds_ceiling
|
||
signer_pubkey[32] ‖ u32 len ‖ signer_id
|
||
|
||
receipt_hash = BLAKE3("wai:video-package-receipt-id\x01" ‖ payload ‖ sig)
|
||
|
||
`labelled_domain` and `write_label` are [energy-measurement](energy-measurement.md)
|
||
§2.8's: unmetered, the domain as written and no label; metered, its final byte `\x02`
|
||
and the class's signed encoding. `total_entries` counts every entry; `unique_objects`
|
||
the distinct content hashes over every component; `total_bytes` the entries'
|
||
`byte_len`; `stored_bytes` the `byte_len` of each content hash's first entry;
|
||
`work_units` equals `total_entries`. A receipt whose content hash comes at two lengths
|
||
has no counts. The signature is Ed25519 by `signer_pubkey`, verified as SPEC §7
|
||
*Signatures* requires; a signature or a key that rule refuses is `receipt-signature`.
|
||
|
||
Its JSON form, `package.receipt.json`, is RFC 8785 canonical, hexadecimal lowercase,
|
||
integers as numbers no greater than `2^53 − 1` (a packager refuses to seal anything
|
||
else, `receipt-json`): `asset_id`; `components`, each `{"id", "index":
|
||
"index-<id>.wsix", "index_digest", "media_class", "stage_root"}`; `grant`
|
||
`{"capability", "funds_ceiling", "grant_hash", "joule_ceiling_micro"}` with `null` for
|
||
no ceiling (a joule ceiling of `2^64 − 1`); `joules_micro`; `kind` `"package"`;
|
||
`packager`; `parent_kind` (`none`, `live-window` or `re-pack`); `parent_receipt_hash`
|
||
(`null` for none); `policy_digest`; `receipt_hash`; `sig`; `signer_id`;
|
||
`signer_pubkey`; `stored_bytes`; `total_bytes`; `total_entries`; `unique_objects`;
|
||
`version` 2; `work_units`; `energy_provenance`, the class's label (energy-measurement
|
||
§2.7) or `null` when unmetered; and the class's evidence members exactly as its class
|
||
carries them. Every member is present, with `null` where it has no value
|
||
(`parent_receipt_hash`, `energy_provenance`, a grant's ceilings) and nowhere else.
|
||
|
||
A reader refuses, before anything else, JSON that is not RFC 8259 or that names a member
|
||
twice in one object, at any depth (`receipt-json`). It then reads `"version"`: absent or
|
||
1 is a version-1 receipt (§9.3), 2 a version-2 one, anything else `receipt-version`. It
|
||
then refuses (`receipt-json`) a member it does not define or one missing, `null` where
|
||
the member has a value, a value of another form (a number that is not an integer of its
|
||
range, `-0` among them; a string that is not Unicode scalar values), evidence other than
|
||
exactly what the class carries, a class label energy-measurement §2.7 refuses, a parent
|
||
kind and hash that disagree, an `index` other than `index-<id>.wsix`, and a
|
||
`receipt_hash` that is not the receipt's. A reader does not require the canonical form;
|
||
a writer writes it, and a package's `package.receipt.json` must be it (§11.3).
|
||
|
||
A verifier of a receipt against its indexes, in its components' order, refuses, the
|
||
first that applies: no component, or another number of indexes than components, a live
|
||
window's receipt included (`receipt-index-count`); an index that breaks §6 (its
|
||
reason); one that does not hash to its `index_digest` (`receipt-index-digest`), has
|
||
another stage root (`receipt-stage-root`), names another component or media class
|
||
(`receipt-component`) or another policy digest (`receipt-policy-digest`); two components
|
||
of one id (`receipt-component`); other counts, or a content hash at two lengths in any of the
|
||
indexes (`receipt-counts`); a parent kind that does not
|
||
match the indexes' `prev_index_digest`, which is zero in every index for none and a
|
||
re-pack and non-zero in every one for a live window (`receipt-parent-kind`); an energy
|
||
figure and class that do not cohere, or a legacy label (`receipt-energy`); a signature
|
||
that does not verify (`receipt-signature`).
|
||
|
||
**9.2** Live windows chain by the parent link with parent kind `live-window`. A re-pack
|
||
of an earlier package **MAY** name it with parent kind `re-pack`.
|
||
|
||
The reference live packer (`wai-pack live`, `wai_pack::live`) takes a source a frame at a
|
||
time, cuts it with the segmenter of §4.3, which decides a frame once `lookahead_frames`
|
||
more have arrived, and codes each piece as it closes with the coder the on-demand packer
|
||
uses (§5.4–§5.7). So a live and an on-demand pack of one source under one policy write
|
||
the same plan and the same bases and refinements, byte for byte. It writes no floor: a
|
||
supplied floor is made from the whole source (§7.3), so it is the on-demand package's.
|
||
Its pieces are gathered into windows: each a stage index of its pieces' bases and
|
||
refinements (`first_piece` and `first_entry` continuing the chain, `prev_index_digest`
|
||
the previous window's index digest, zero for the first) sealed by a version-2 receipt
|
||
whose parent is the previous window's (`live-window`; none for the first). A piece's
|
||
objects are published when the frame that decides its end has arrived: its base then,
|
||
and layer *k* `layer_lag_ms[k]` after it (§8.6, §8.9).
|
||
|
||
It writes as it goes: each object when it is published, and each window's index and
|
||
then its receipt when the window is sealed, every file whole (written beside its place
|
||
and renamed into it); the plan file and `LISTING` at the end of the source. It keeps no
|
||
record per file of the session: each file's listing line goes to a hidden file beside
|
||
the package as the file is written, and is sorted into `LISTING` at the end (the one
|
||
moment it holds a line per file); an object already written is known by its file; an
|
||
object's bytes are not held once written. What it holds is what no window has sealed
|
||
yet: those pieces' entry fields and the content hash of each object they published
|
||
first. So with windows of `n` pieces that is at most `n` pieces', and with one window
|
||
over the whole source (the default) every piece's until the end. Besides those it holds
|
||
the frames of the piece being cut and each piece's first frame, eight bytes a piece, for
|
||
the plan file. A source that fails before its end (a short frame, a frame of another
|
||
size) leaves every sealed window and the objects it lists, removes the objects only
|
||
unsealed pieces published, writes no plan file or `LISTING`, and is an error that names
|
||
what was kept.
|
||
|
||
**9.3** A version-1 receipt (`wai.video.package`'s segment map) keeps its bytes and
|
||
still verifies. Its JSON form is RFC 8785 canonical with `version` 1: `asset_id`,
|
||
`energy_provenance` (`null` when unmetered) with its evidence, `grant`, `joules_micro`,
|
||
`kind` `"package"`, `parent_receipt_hash`, `receipt_hash`, `segment_map_root`,
|
||
`segments` (each `{"byte_len", "content_hash", "duration_ms", "index"}`), `sig`,
|
||
`signer_id`, `signer_pubkey`, `stored_bytes`, `total_bytes`, `total_segments`,
|
||
`unique_segments`, `work_segment_ops`. A reader reads one without `version` as version 1,
|
||
and, because version-1 receipts were issued before this form, keeps them verifying:
|
||
`energy_provenance` may be absent (read as unmetered), an evidence member written `null`
|
||
is absent, and an on-chip counter's filtering, absent, reads as unknown. It refuses a
|
||
member named twice as version 2's reader does.
|
||
Its signing payload, big-endian, is
|
||
|
||
"wai:video-package-receipt\x01"
|
||
u32 len ‖ asset_id ‖ segment_map_root[32]
|
||
u64 total_segments ‖ u64 unique_segments ‖ u64 total_bytes ‖ u64 stored_bytes ‖ u64 work_segment_ops
|
||
u64 joules_micro ‖ class (u8 0 when unmetered, else the class's signed encoding)
|
||
the grant as §9.1 writes it
|
||
u8 0 | u8 1 ‖ parent_receipt_hash[32]
|
||
u64 n ‖ n × BLAKE3("wai:video-package-seg\x01" ‖ u64 index ‖ content_hash[32] ‖ u64 duration_ms ‖ u64 byte_len)
|
||
signer_pubkey[32] ‖ u32 len ‖ signer_id
|
||
|
||
`segment_map_root` is the jwp-receipts §2.2 Merkle root over those segment leaves, the
|
||
counts are the segments' (the bytes of one copy per content hash for `stored_bytes`), and
|
||
`receipt_hash` is version 2's.
|
||
Both versions share the identity domain, so the figure profile
|
||
`wai:video-package-receipt-id` ([energy-measurement](energy-measurement.md) §6.1)
|
||
covers both; a version-2 receipt's figure is read only with the indexes it verifies
|
||
against.
|
||
|
||
**9.4** Where the energy figure is not the unmetered form, the packager **MUST** publish
|
||
a `measurement` claim about it ([jwp-receipts](jwp-receipts.md) §2.4.8,
|
||
[energy-measurement](energy-measurement.md) §6) as
|
||
`claims/<receipt_hash>.measurement.json`: subject the receipt, figure profile
|
||
`wai:video-package-receipt-id`, signed with the package key and checked against the
|
||
receipt before it is written. A verifier refuses a metered package without it
|
||
(`measurement-claim-missing`) or with one that does not verify and check against the
|
||
receipt (`measurement-claim-invalid`).
|
||
|
||
## 10. Royalty-free packaging
|
||
|
||
A packager run with `royalty_free_only = true` **MUST** refuse to emit any object whose
|
||
capability the registry does not classify as royalty-free, including one it does not
|
||
classify at all (§3.6, rule 5). A package receipt says nothing about patents.
|
||
`royalty_free_only` records the registry class the packager read, "licensed
|
||
royalty-free by its standards body"; it is not a freedom-to-operate opinion.
|
||
|
||
## 11. Package, re-pack equivalence and verification
|
||
|
||
**11.1** *The package* is a directory of these files and no other file or directory
|
||
(every directory below it holds one of them):
|
||
|
||
policy.toml policy.json plan-<c>.txt index-<c>.wsix package.receipt.json
|
||
objects/<content hash>.wai …
|
||
floor/<c>/init.mp4 floor/<c>/<sha256>.m4s …
|
||
hls/classic/… hls/<class>/… dash/… moq/catalog.json stages/<class>.json
|
||
claims/<receipt_hash>.measurement.json (a metered package)
|
||
LISTING
|
||
|
||
`policy.toml` is the policy as given; `policy.json` its effective canonical JSON
|
||
(§3.2). `LISTING` is the listing (SPEC §3.1) of every other file, and its SHA-256 is the
|
||
**pack digest**. A component or class id that cannot be one path segment of a listing,
|
||
two class ids equal once `A`–`Z` are mapped to `a`–`z`, and a class named `classic` in
|
||
any case (ids name paths, and paths fold case) are refused (`pack-id`).
|
||
|
||
**11.2** *Re-pack equivalence.* Given the same source bytes, effective policy, supplied
|
||
floor, signing key and packager identity, a conforming reference packager **MUST**
|
||
produce byte-identical package files on every machine, except the energy figure and
|
||
measurement claim of a metered package and floor objects §7.5 covers. A verifier
|
||
**MAY** pack again and compare listings. A packager whose identity differs is not held
|
||
to this.
|
||
|
||
**11.3** *Verification.* A verifier of a package, without its sources, applies these
|
||
checks in this order and refuses at the first that fails; a check that reads files a
|
||
failed check names is not applied:
|
||
|
||
1. Every file below the package's directory, read strictly — a name beginning with `.`
|
||
and a `*.sha256` file included, as a listing (SPEC §3.1) would not skip them — is a
|
||
regular file whose path a listing can carry; a symbolic link, a directory that
|
||
cannot be read or holds nothing, or anything else is refused (`unexpected-file`).
|
||
2. `LISTING` is present (`listing-missing`) and is the listing of every other file
|
||
(`listing-mismatch`).
|
||
3. `package.receipt.json` reads as a version-2 receipt (§9.1: `receipt-json`,
|
||
`receipt-version`) and is that receipt's canonical form, byte for byte
|
||
(`receipt-json`).
|
||
4. `policy.json` hashes to the receipt's policy digest (`policy-digest-mismatch`);
|
||
`policy.toml` is a policy (its own reason, §3.2–§3.4); `policy.json` is its
|
||
effective canonical JSON, `royalty_free_only` forced on where `policy.json` says
|
||
`true` (`policy-json-mismatch`; an override never turns it off, §3.2, so a
|
||
`policy.json` that says `false` over a `policy.toml` that says `true` is refused
|
||
here); its ids can name files (`pack-id`, §11.1); and under that effective policy it
|
||
asks for nothing a packager may not emit (§3.6's reason; under `royalty_free_only`, a
|
||
patent-pool floor is `license-refused`).
|
||
5. The receipt's components are the policy's, by id, in order, each of the policy's
|
||
media (`policy-component-mismatch`), so a package is never a receipt over fewer
|
||
components than its policy has; each index is present (`index-missing`); the receipt
|
||
verifies against them (§9.1, the index rules of §6 among its reasons).
|
||
6. Per component, in order:
|
||
- the index against the policy (`index-policy-mismatch`): its timebase and media
|
||
class are the component's; its string table is the component's id, the class ids
|
||
in policy order, then each capability at its first use; its class records are the
|
||
policy's classes, in order, with their `max_layer`, floor-only flag and `max_kbps`;
|
||
every piece's kind is one a video piece has; its rules and moments are §8.1's from
|
||
its kind and span; its entries are the rungs of its ladder (§5.7), by role,
|
||
capability and layer: its base, each refinement in layer order, each floor; each
|
||
refinement's deadline is its plan's (§8.6); and its class plan is §8.1's from its
|
||
entries;
|
||
- the plan file holds the index's starts and its digest is the header's
|
||
(`plan-mismatch`);
|
||
- every entry's object is present (`object-missing`) and keeps the object rules of
|
||
§6.3, its counted bytes included;
|
||
- before any frame is decoded, each piece's floor object, where the policy has a floor
|
||
rung and the object's payload reads as §7.2's framing, holds one unit per frame of
|
||
its piece (`floor-object-mismatch`);
|
||
- every base and refinement, in entry order: a base decodes by its capability (a
|
||
still is a PNG §5.7 reads, its one frame held for the piece), or it is
|
||
`prefix-digest`; its manifest is the one §5.2 states for its piece's frames and its
|
||
frame size (`object-manifest`); a refinement's manifest is §5.4's
|
||
(`object-manifest`), and it applies in layer order, to every frame of the stage
|
||
below it, by a staged-delivery §4 sink under the reference tools' cost limit and
|
||
chain budget, or it is `prefix-digest`; a base that does not decode, or a layer that
|
||
does not apply, ends the check;
|
||
- then every decode-equivalent prefix decodes to its `prefix_digest`
|
||
(`prefix-digest`);
|
||
- when all of that holds, the floor: per piece, one floor object of the floor rung's
|
||
capability whose renderer, settings, framing header, unit count and target are
|
||
§7.2's (`floor-object-mismatch`) and which presents the piece's base
|
||
(`floor-motif-mismatch`); its units each one temporal unit holding a frame, the
|
||
first of the first piece carrying a sequence header, every sequence header the
|
||
first's bytes, every shown key frame carrying one (`floor-av1-malformed`); each
|
||
piece opening on a shown key frame, and no other unit being one unless the policy
|
||
allows extra key frames (`floor-keyframe-mismatch`); and the CMAF files are the
|
||
ones its temporal units give (§7.6, `floor-cmaf-mismatch`);
|
||
- then the catalogs are the ones the index, the objects and the floor give, and an
|
||
index no catalog can be written for (a start past `2^53 − 1`) is refused
|
||
(`catalog-mismatch`).
|
||
7. A metered package's measurement claim (§9.4: `measurement-claim-missing`,
|
||
`measurement-claim-invalid`); an object file no entry names (`object-unlisted`); a
|
||
catalog file past the ones given (`catalog-unexpected`); any other file
|
||
(`unexpected-file`).
|
||
|
||
**11.4** *What this revision packs.* The reference packager packs one video component:
|
||
bases of `wai.video.int_motion` (§5.5, §5.6) and stills of `wai.image.png` (§5.7);
|
||
refinements of `wai.video.int_refine` over an `int_motion` base (§5.4); each piece by its
|
||
ladder, a rule's ladder subset included (§5.7); and a supplied AV1 floor (§7.3). Its live
|
||
packer packs the same pieces in windows, without a floor (§9.2). It refuses by name,
|
||
before writing anything, what it does not pack yet: another medium
|
||
(`pack-media-unsupported`), a floor to encode (`pack-floor-encode-unsupported`), and a rung
|
||
of a capability it does not encode, or a refinement over a base it does not refine
|
||
(`pack-capability-unsupported:<cap>`, every one naming the capability of the rung it does
|
||
not pack); and a component with no source (`pack-source-missing`) or whose source's frame
|
||
rate is not its timebase (`pack-source-rate`). Where its own work fails it
|
||
refuses by name too, and writes nothing: an encode the encoder refuses, such as a piece
|
||
past the samples it holds, or an object it wrote that does not count (`pack-encode`); a
|
||
piece past `2^32 − 1` units (`pack-piece-length`); catalogs it cannot write, the case
|
||
§11.3 refuses as `catalog-mismatch` (`pack-catalog`); a meter that was asked for and
|
||
gives no figure, a span too short to reach a microjoule or a figure past `2^64 − 1`
|
||
(`pack-meter`, never an unmetered package in its place); a measurement claim it cannot
|
||
write (`pack-measurement-claim`); and a listing it cannot write (`pack-listing`). It
|
||
writes a package only into a directory that is new or holds nothing, and refuses any
|
||
other before it packs (`pack-out-not-empty`): written among another package's files, it
|
||
would leave behind the ones it does not write.
|
||
|
||
## 12. Reasons
|
||
|
||
| Reason | Where |
|
||
|---|---|
|
||
| `policy-toml` (not UTF-8, a byte-order mark, not TOML 1.0, nesting past 32), `policy-float`, `policy-datetime`, `policy-negative`, `policy-integer-range` | §3.2 |
|
||
| `policy-unknown-key`, `policy-missing-key`, `policy-type`, `policy-value`, `policy-schema` | §3.3 |
|
||
| `policy-too-many-components`, `policy-too-many-rungs`, `policy-too-many-classes`, `policy-too-many-rules`, `policy-too-many-moments`, `policy-duplicate-id`, `policy-lead`, `policy-open-gop`, `policy-piece-bounds`, `policy-ladder-component`, `policy-refines-role`, `policy-dangling-refines`, `policy-refines-cycle`, `policy-refines-base`, `policy-refines-branch`, `policy-no-base`, `policy-class-layer`, `policy-rule-ladder`, `policy-layer-lag`, `policy-too-many-deadlines`, `policy-deadline-unreachable` | §3.4 |
|
||
| `capability-unknown`, `capability-not-payload`, `capability-candidate`, `capability-role`, `license-unclassified`, `license-refused`, each naming the capability (and the licence label) | §3.6 |
|
||
| `y4m-header`, `y4m-interlaced`, `y4m-colorspace`, `y4m-size-change`, `y4m-truncated`, `wav-format`, `wav-truncated` | §4.7 |
|
||
| `index-magic`, `index-version`, `index-truncated`, `index-reserved`, `index-media-class`, `index-role`, `index-tier`, `index-string`, `index-trailing` | §6.2 |
|
||
| `index-timebase`, `window-start`, `piece-order`, `piece-entries`, `piece-kind`, `entry-flags`, `entry-order`, `layer-role`, `refines-outside-window`, `refines-mismatch`, `layer-gap`, `tier-entropy-passthrough`, `tier-weakest`, `tier-mismatch`, `digest-without-tier`, `deadline-role`, `floor-derived-from`, `dup-length`, `dup-flag`, `class-plan`, `class-flags` | §6.3, §6.4 |
|
||
| `object-length`, `object-hash`, `non-envelope-entry`, `object-lengths`, `object-capability`, `object-refines`, `object-derived-from` | §6.3 |
|
||
| `floor-missing`, `floor-listing-mismatch`, `floor-stream-missing`, `floor-av1-malformed`, `floor-frame-count`, `floor-keyframe-mismatch` | §7.1, §7.3 |
|
||
| `floor-object-mismatch`, `floor-motif-mismatch`, `floor-av1-malformed`, `floor-keyframe-mismatch`, `floor-cmaf-mismatch`, against a package | §11.3 |
|
||
| `receipt-json`, `receipt-version`, `receipt-index-count`, `receipt-index-digest`, `receipt-stage-root`, `receipt-component`, `receipt-policy-digest`, `receipt-counts`, `receipt-parent-kind`, `receipt-energy`, `receipt-signature` | §9.1 |
|
||
| `measurement-claim-missing`, `measurement-claim-invalid` | §9.4 |
|
||
| `unexpected-file`, `listing-missing`, `listing-mismatch`, `policy-digest-mismatch`, `policy-json-mismatch`, `policy-component-mismatch`, `index-missing`, `index-policy-mismatch`, `plan-mismatch`, `object-missing`, `object-manifest`, `prefix-digest`, `catalog-mismatch`, `catalog-unexpected`, `object-unlisted` | §11.3 |
|
||
| `pack-id`, `pack-source-missing`, `pack-source-rate`, `pack-media-unsupported`, `pack-floor-encode-unsupported`, `pack-capability-unsupported` (naming the capability) | §11.1, §11.4 |
|
||
| `pack-encode`, `pack-piece-length`, `pack-catalog`, `pack-meter`, `pack-measurement-claim`, `pack-listing`, `pack-out-not-empty` | §11.4 |
|