Some checks failed
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 12s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 22s
wai-audit collector and player integration / does this change touch it (push) Successful in 19s
wai-fleet service / does this change touch it (push) Successful in 21s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 23s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 33s
wai-sink-swift native sink package / does this change touch it (push) Successful in 23s
WAI Studio / does this change touch it (push) Successful in 18s
wai-sink-android native sink library / does this change touch it (push) Successful in 47s
wai-quantum for JavaScript / does this change touch it (push) Successful in 1m0s
wai-app and Create / wai-app · does this change touch it (push) Successful in 1m2s
wai-key-proxy / does this change touch it (push) Successful in 1m2s
WAI operation catalogue / does this change touch it (push) Successful in 1m3s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 1m9s
wai-ledger service / does this change touch it (push) Successful in 1m2s
wai-quantum studio and desktop / does this change touch it (push) Successful in 1m2s
wai-fleet service / service · macos (push) Successful in 55s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 51s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 1m25s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 1m35s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 1m48s
wai-key-proxy / proxy · linux (push) Successful in 1m41s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 2m0s
wai-ledger service / service · macos (push) Successful in 56s
wai-fleet service / service · linux (push) Successful in 2m32s
wai-ledger service / service · linux (push) Successful in 2m13s
wai-audit collector and player integration / collector · linux (push) Successful in 2m57s
wai-quantum for JavaScript / javascript · linux (push) Successful in 4m8s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (push) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (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 · Create, browser end-to-end (macos) (push) Has been cancelled
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Has been cancelled
wai-quantum energy-landscape jobs / the same bits · macos (push) Has been cancelled
wai-quantum studio and desktop / studio · browser (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 breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 5m13s
wai-quantum studio and desktop / desktop app · macos (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-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Has been cancelled
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Has been cancelled
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
WAI Studio / Studio in Chromium and Firefox · macos (push) Has been cancelled
WAI operation catalogue / the catalogue and every door · linux (push) Has been cancelled
WAI Studio / Studio in WebKit · linux (push) Has been cancelled
wai-quantum studio and desktop / desktop app · linux (push) Has been cancelled
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Has been cancelled
extensions/audience-decision.md registers two route-record documents, each signed in its canonical form under its own domain. `wai.video.audience` (`wai-audience-decision`): a decider's signed decision of whether the sessions in one region may be shown a programme over a span, under the blackouts in force. Each blackout is named by the id a session class carries (http-delivery Appendix H `blackouts`) and lists its regions in rung-price §2's region codes, with the programme shown in its place. A session aggregates its own location before it leaves the session: to the one listed code that holds it, to outside, or to unresolved (no location, or a country of which only subdivisions are listed); the aggregation decides exactly what the location would. A decision whose evidence holds anything else (coordinates, a postal code, a session's identifier) or a region finer than the aggregation gives is refused `audience_location_not_aggregated`; one whose region the blackouts neither list nor hold, `audience_region_not_in_set`. The blackouts applied (every one, unresolved), the verdict (show, withhold, or alternate when all that apply name one programme) and that programme are recomputed; a session class of the programme carries the applied set as its `blackouts` and the region's country as its `geo`, and a verifier given both states the class's hash. `wai.delivery.territory` (`wai-territory`): a publisher's signed statement of where its content may be shown (everywhere, only in some regions, everywhere but some). Its latest statement stands alone; a lapsed or contradicted latest revives none. The territory (within, outside, undetermined, or unstated with its reason) is reported beside a server key's placement, which cache-authority §4.4 computes from its own inputs and no statement changes; a statement that names servers, nodes, origins, roles or may_sign is refused `territory_scopes_serving`. cache-authority §4.3 and §4.4 are unchanged; §4.4 gains a paragraph saying so. The reference is the `audience_decision` module of wai-rs (feature `audience_decision`); the corpus is live-conformance/audience/ (13 aggregations, 67 decisions, 17 classes, 39 statements, 32 territories, 10 placements), and audience_decision_verify.py is its second implementation: 178 cases agree, 21 of 21 mutated copies fail and 34 of 34 weakened verifiers are caught. Twelve faults planted in the reference each fail its tests: the evidence's extra-member check and its finer-region check, rule 6 (a region not the blackouts'), the serving-member check and `may_sign` among its members, a placement refused beside an outside territory, a decision's and a statement's signature verified without the domain, over the text as written and under the other record's domain, an unresolved session read as outside, and a lapsed statement letting an earlier one stand. Co-Authored-By: Claude Opus 5.5 <[email protected]>
993 lines
62 KiB
Markdown
993 lines
62 KiB
Markdown
# WAI Extension: Channel Lineup and Guide
|
||
|
||
> Status: Draft. Registers the record `wai.video.lineup`: a linear channel's signed **lineup**,
|
||
> `WLU1`, which describes each programme asset of one channel schedule (its title, description,
|
||
> categories, rating, artwork by content hash, where it may be shown, and its rights holder) and
|
||
> where each programme of the guide begins, and which binds that schedule by its root. It
|
||
> defines how a channel as operators exchange it (an XMLTV schedule, Media RSS catalogues, a
|
||
> JSON form of both, and the SCTE-35 date ranges of the channel's HLS Media Playlist) is
|
||
> **imported** into a `wai.video.channel` receipt and a lineup; and the channel's **guide**, an
|
||
> XMLTV document or a now/next document for a player's guide row, rendered from a receipt and its
|
||
> lineup, naming both, so that anyone holding them recomputes the guide and compares. The
|
||
> `wai.video.channel` receipt keeps its signed bytes and travels in revenue-share's JSON carriage
|
||
> of it. No new transport. The reference implementation is the `channel_lineup` module of `wai-rs` (feature
|
||
> `channel_lineup`); the corpus is [`live-conformance/channels/`](../live-conformance/channels/),
|
||
> and its second implementation is `live-conformance/channel_lineup_verify.py`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> This revision adds the **cue tags** of a Media Playlist of the channel (§7.4): a packager that
|
||
> writes the channel's segments, MPEG-2 transport stream segments among them, marks each break of
|
||
> the receipt's schedule with `EXT-X-CUE-OUT` and the break's duration, `EXT-X-CUE-OUT-CONT` on each
|
||
> later segment of the break and `EXT-X-CUE-IN` on its return, and, for a break whose cue carries
|
||
> its SCTE-35 section, with the `EXT-X-DATERANGE` tags of RFC 8216 §4.3.2.7.1; a verifier given
|
||
> the receipt, its cues and the playlist recomputes the tags and compares (§8.4). An import, a
|
||
> lineup and a guide are unchanged.
|
||
>
|
||
> Texts read for this revision, each at its source:
|
||
>
|
||
> - the XMLTV DTD, `xmltv.dtd`: its date and time format, and the elements `tv`, `channel`,
|
||
> `display-name`, `programme`, `title`, `desc`, `category`, `icon`, `episode-num`, `rating`
|
||
> and `value`, with their attributes and the order the DTD gives a programme's children;
|
||
> - *Media RSS Specification* 1.5.1, December 2009: §4.1 (`media:group`), §4.2
|
||
> (`media:content`), §5 (where its optional elements appear), §5.2 (`media:rating`), §5.3
|
||
> (`media:title`), §5.4 (`media:description`), §5.6 (`media:thumbnails`), §5.7
|
||
> (`media:category`) and §5.13 (`media:restriction`); and the RSS 2.0 elements it extends
|
||
> (`rss`, `channel`, `item`, `title`, `description`, `category`, `guid`);
|
||
> - `draft-pantos-hls-rfc8216bis-22`: §4.2 (attribute lists, decimal-floating-point and
|
||
> hexadecimal-sequence values), §4.4.5.1 (`EXT-X-DATERANGE`) and §4.4.5.1.1 (SCTE-35 in a
|
||
> date range);
|
||
> - ANSI/SCTE 35-1 2023r2: §9.6 (Table 5, `splice_info_section()`) and §9.7.3 (Table 10,
|
||
> `splice_insert()`);
|
||
> - RFC 8216 (HTTP Live Streaming): §4.2 (attribute lists), §4.3.2.1 (`EXTINF`), §4.3.2.6
|
||
> (`EXT-X-PROGRAM-DATE-TIME`), §4.3.2.7 (`EXT-X-DATERANGE`), §4.3.2.7.1 (SCTE-35 in a date
|
||
> range), §4.3.3.1, §4.3.3.2 and §4.3.3.4 (`EXT-X-TARGETDURATION`, `EXT-X-MEDIA-SEQUENCE`,
|
||
> `EXT-X-ENDLIST`), §6.2.1 and §6.3.1 (the server's and the client's general rules), §7 (protocol
|
||
> versions) and §8.10 (its example of a date range carrying SCTE-35);
|
||
> - RFC 8259 (JSON), RFC 8785 (the JSON Canonicalization Scheme), and XML 1.0 for the subset of
|
||
> §3.3.
|
||
>
|
||
> A section number of one of these texts is that revision's.
|
||
|
||
## 1. Scope
|
||
|
||
In scope: the bounds and value forms an import reads (§3); the import of a channel's schedule,
|
||
catalogue and ad breaks into a schedule, its assets and its cues (§4); the JSON carriage a
|
||
`wai.video.channel` receipt travels in, which is revenue-share's (§5); the lineup, its rules, its signature and how it binds its
|
||
schedule (§6); the guide, in XMLTV and as a now/next document (§7), and the cue tags of a Media
|
||
Playlist of the channel (§7.4); verification of a receipt, a
|
||
lineup and a guide together (§8), and of a playlist's cue tags (§8.4); the operations that do
|
||
each, with a worked example (§9).
|
||
|
||
Out of scope: a transport; how feeds travel between operators; playout, which turns a schedule
|
||
into segments; the ad decision of a break, which [live-ads](live-ads.md) and `wai.video.ssai`
|
||
make; the revenue an asset's rights holder is owed; how a packager cuts and encodes segments,
|
||
which §7.4 takes as given; interstitial date ranges, which [stitched-presentation](stitched-presentation.md)
|
||
reads; SCTE 224 audience and blackout rules (a lineup states where an asset may be shown, and
|
||
says nothing about when; a decision under blackouts is [audience-decision](audience-decision.md)'s);
|
||
and the elements of the XMLTV DTD and of Media RSS that §4 does not name, which an import does not
|
||
read.
|
||
|
||
Relationship to the core spec: `wai.video.lineup` is a **record** ([SPEC](../SPEC.md) §5): a
|
||
signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.
|
||
A guide is not a record: it is a rendering of two records that anyone recomputes, so it carries
|
||
no signature of its own.
|
||
|
||
Relationship to `wai.video.channel` (SPEC §5): an import seals a channel receipt with
|
||
`ChannelReceipt::seal`, whose signed bytes are unchanged here, and carries it in the JSON form
|
||
[revenue-share](revenue-share.md) §4.1 defines for it; a lineup binds the receipt's
|
||
schedule by its `schedule_root`. A receipt's program-as-run (`aired`) is what a verification
|
||
sets beside a guide (§8.3).
|
||
|
||
Relationship to [live-events](live-events.md): each ad break an import reads becomes a `cue`
|
||
payload of live-events §4.3, unchanged, which a room's principal signs as a `cue` event when the
|
||
channel is delivered as a room. Relationship to [cue-injection](cue-injection.md): its injection
|
||
record (§7 there) binds a cue to the SCTE 104 message it was translated from; a cue an import
|
||
yields comes from a playlist's SCTE-35 date range, and an import makes no injection record.
|
||
|
||
## 2. Terms
|
||
|
||
- **Channel**: a linear channel, named by a **channel id**.
|
||
- **Schedule**: the entries of a `wai.video.channel` receipt, gapless and non-overlapping, each
|
||
an asset id, a start, a duration and a kind: program, ad or filler.
|
||
- **Programme**: a span of the schedule the guide lists as one item: an episode or a film, with
|
||
the ad breaks inside it. A programme's **asset** is the asset id of its program entries.
|
||
- **Asset**: what an asset id names; the lineup describes each programme asset (§6.2).
|
||
- **Catalogue**: the assets a channel's content owners describe, in Media RSS or the JSON form.
|
||
- **Break**: a span of a programme that ads fill, from a date range or the JSON form (§4.5).
|
||
- **Operator**: the party that runs the channel and signs its receipt and its lineup.
|
||
- **Rights holder**: the party an asset's lineup entry names as holding its rights.
|
||
- **Feed**: a document an import reads: an XMLTV document, a Media RSS document, the JSON form,
|
||
or a Media Playlist.
|
||
- **Guide**: an XMLTV document (§7.2) or a now/next document (§7.3) rendered from a receipt and a
|
||
lineup.
|
||
|
||
An instant is a count of milliseconds since 1970-01-01T00:00:00Z, leap seconds not counted. An
|
||
instant or a duration an import reads, a lineup states or a guide writes is at most 2^53 − 1,
|
||
the integers JSON carries exactly (RFC 8785 §3.2.2.3). A span from `a` to `b` holds the instants from `a`, included, to `b`,
|
||
excluded. Integers of the lineup are little-endian. A digest is SHA-256 (FIPS 180-4), 32 bytes.
|
||
Ed25519 is RFC 8032's, verified strictly ([SPEC](../SPEC.md) §7 *Signatures*): a key that fails
|
||
step 1 or 2 is `bad_key`, and a signature that fails a later step is `bad_signature`. Hexadecimal
|
||
written by this text is lowercase.
|
||
|
||
## 3. Reading
|
||
|
||
### 3.1 Bounds
|
||
|
||
An import refuses `import_oversized` a feed or an artwork file of more than 16 777 216 bytes, and
|
||
a feed that holds more than 10 000 programmes of the channel (XMLTV, the JSON form), more than
|
||
10 000 items (Media RSS, all its documents together), more than 10 000 assets (the JSON form) or
|
||
more than 10 000 breaks (the JSON form and the playlist together). The size of each feed is
|
||
checked before it is read, and each count as soon as its document has been read as XML, JSON or
|
||
lines, before any of its elements is read further. An XML document is read to a depth of 64
|
||
elements at most (§3.3). An import therefore reads a bounded amount for any input.
|
||
|
||
### 3.2 Text values
|
||
|
||
A **text** is a sequence of Unicode scalar values in UTF-8 with no character below U+0020 other
|
||
than U+0009 (tab) and U+000A (line feed). A **short text** is a text of 1 to 255 bytes; a
|
||
**long text** a text of 0 to 65 535 bytes. A value an import reads that must be one of these and
|
||
is not is refused `import_field_invalid`. Text is taken as the feed writes it; nothing is
|
||
normalised.
|
||
|
||
### 3.3 The XML subset
|
||
|
||
An XMLTV or Media RSS document is read over the XML subset of
|
||
[stitched-presentation](stitched-presentation.md) §3.5, with its local names and its text of an
|
||
element, and with one addition: before the root element, where that subset admits a comment, the
|
||
document **MAY** hold one document type declaration with no internal subset, which is skipped.
|
||
Such a declaration is `<!DOCTYPE`, white space, a name (an ASCII letter or `_`, then ASCII
|
||
letters, digits, `_`, `-`, `.` and `:`), and then optionally white space and either `SYSTEM`,
|
||
white space and a literal, or `PUBLIC`, white space, a literal, white space and a literal;
|
||
then optional white space and `>`. A literal is `"`, bytes other than `"`, and `"`; or `'`,
|
||
bytes other than `'`, and `'`. White space is one or more of space, tab, CR and LF. A
|
||
declaration of any other form (one with an internal subset among them), or a second one, is
|
||
outside the subset. A document outside the subset, or whose root element is not the one §4.2 or
|
||
§4.3 names, is refused `import_xml_malformed`.
|
||
|
||
An element's **first** `x` child is its first child element of local name `x`. Elements are
|
||
named by local name only, so a Media RSS element and an RSS 2.0 element of one local name
|
||
(`media:title` and `title`, for one) are both `title` children; §4.3 says which one an import
|
||
takes.
|
||
|
||
### 3.4 Times and durations
|
||
|
||
An **XMLTV time** is 14 ASCII digits `YYYYMMDDhhmmss`, optionally followed by one space and a
|
||
numeric zone: `+` or `-` and four digits `HHMM` with `HH` at most 23 and `MM` at most 59. With no
|
||
zone the time is UTC, as the DTD says. The instant is the UTC instant of the date and time, less
|
||
the zone's offset. A value is refused:
|
||
|
||
- `import_timezone_unknown` when its first 14 bytes are digits and what follows them is a space
|
||
and anything other than a numeric zone (a zone named by letters, for one);
|
||
- `import_time_malformed` otherwise when it is not of that form, when its year is below 1970, its
|
||
month not 1 to 12, its day not a day of that month in the Gregorian calendar, its hour above 23,
|
||
its minute above 59 or its second above 59, or when its instant is before 1970-01-01T00:00:00Z.
|
||
|
||
An **HLS date-time** is `YYYY-MM-DDThh:mm:ss`, optionally `.` and one or more digits, then `Z` or
|
||
a zone `+hh:mm` or `-hh:mm` with `hh` at most 23 and `mm` at most 59, the fields as above. Its
|
||
fraction is read to the millisecond, and every digit after the third **MUST** be 0. A value of
|
||
this form without a zone is refused `import_timezone_unknown`; any other value that is not of
|
||
this form, or breaks a rule above, is refused `import_time_malformed`.
|
||
|
||
A **duration** is a decimal-floating-point value (`draft-pantos-hls-rfc8216bis-22` §4.2): one or
|
||
more digits, optionally `.` and one or more digits, a number of seconds. It is read to the
|
||
millisecond, every fractional digit after the third **MUST** be 0, and the count of milliseconds
|
||
**MUST** be at most 2^53 − 1; otherwise it is refused `import_time_malformed`.
|
||
|
||
## 4. Import
|
||
|
||
### 4.1 Inputs
|
||
|
||
An import is given:
|
||
|
||
- **channel**: the channel id, a short text;
|
||
- **feeds**, one of these sets, each optionally with one Media Playlist:
|
||
- an XMLTV document, and zero or more Media RSS documents;
|
||
- one or more Media RSS documents, with no XMLTV document;
|
||
- one JSON form (§4.4), and no XMLTV or Media RSS document;
|
||
- for each Media RSS document, optionally its **owner**, the rights holder of its items: a short
|
||
text;
|
||
- **owner**: optionally, the rights holder of every asset that no other input gives one: a short
|
||
text;
|
||
- **id_system**: the XMLTV `episode-num` system that names a programme's asset (§4.2); when not
|
||
given, `wai-asset-id`, the system a guide writes (§7.2);
|
||
- **xmltv_channel**: the XMLTV channel id whose programmes are read; when not given, `channel`;
|
||
- **start**: an instant, which a schedule made of Media RSS items alone requires (§4.6);
|
||
- **artwork**: a set of files, each named by a file name (§4.6);
|
||
- **window**: optionally, the receipt's window, a span from `window_start` to `window_end` (§4.9);
|
||
- the operator's Ed25519 key and the receipt's `signer_id` (a short text), and optionally the
|
||
receipt hash of the window before this one, its `parent_receipt_hash`.
|
||
|
||
An input set that is none of the sets above, a Media RSS owner given for some of the Media RSS
|
||
documents and not for the others, a value above that is not of its form, or a schedule of Media
|
||
RSS items alone with no start, is refused `import_inputs_invalid`.
|
||
|
||
### 4.2 An XMLTV schedule
|
||
|
||
The document's root element is `tv`. Its **channel name** is the text of the first
|
||
`display-name` child of the first `channel` child of `tv` whose `id` attribute is
|
||
`xmltv_channel`, when that text is not empty; it **MUST** then be a short text. The import's
|
||
**programmes** are the `programme` children of `tv` whose `channel` attribute is
|
||
`xmltv_channel`, in document order; when there are none, the import is refused `import_empty`.
|
||
Each one is read, in document order, by these steps, and refused at the first that fails:
|
||
|
||
1. Its `start` attribute is an XMLTV time (§3.4); an absent `start` is `import_time_malformed`.
|
||
2. It has a `stop` attribute: otherwise `import_no_stop`. That attribute is an XMLTV time (§3.4),
|
||
and its instant is after the start's: otherwise `import_time_malformed`.
|
||
3. **Its asset.** It has an `episode-num` child whose `system` attribute is `id_system` (an
|
||
absent `system` is `onscreen`, the DTD's default), and the text of the first such child is a
|
||
short text: otherwise `import_asset_unknown`. That text is the programme's asset id.
|
||
4. **Its description**, each value as §3.2 says (`import_field_invalid`): its **title**, the text
|
||
of its first `title` child, a long text of 1 byte or more; its **description**, the text of
|
||
its first `desc` child, or empty when it has none, a long text; its **categories**, the text
|
||
of each `category` child, in order, each a short text, at most 16 of them; its **artwork**,
|
||
the `src` attribute of each `icon` child, in order, each a long text of 1 byte or more, at
|
||
most 8 of them (an `icon` with no `src` is refused); and its **rating**, when it has a
|
||
`rating` child: the first one's `system` attribute, or empty when it has none, which when
|
||
not empty is a short text; and the text of that child's first `value` child, a short text
|
||
(a `rating` with no `value`, or an empty one, is refused).
|
||
|
||
Every instant an XMLTV document gives is a whole second.
|
||
|
||
### 4.3 A Media RSS catalogue
|
||
|
||
The document's root element is `rss`, with a `channel` child; the first `channel` child is
|
||
read. Its **channel name** is the text of that channel's first `title` child, when not empty; it
|
||
**MUST** then be a short text. Its **items** are that channel's `item` children, in document
|
||
order. Each one is read, in document order, by these steps, and refused at the first that fails:
|
||
|
||
1. **Its asset id** is the text of its first `guid` child (RSS 2.0's `guid`), a short text:
|
||
otherwise `import_asset_unknown`. An asset id that an item read before it has, in this
|
||
document or in an earlier one, is refused `import_asset_duplicate`.
|
||
2. **Its content** is its first `content` child (`media:content`, §4.2 there), or, when it has
|
||
none, the first `content` child of its first `group` child (`media:group`, §4.1 there). Its
|
||
**content URI** is the content's `url` attribute, a long text, empty when absent or when it
|
||
has no content. Its **duration** is the content's `duration` attribute read as a duration
|
||
(§3.4; `import_time_malformed`), which **MUST** be at least 1 millisecond
|
||
(`import_time_malformed`); with no such attribute it is unknown.
|
||
3. **Its description**, each value as §3.2 says (`import_field_invalid`): its title, the text of
|
||
its first `title` child, a long text of 1 byte or more; its description, the text of its
|
||
first `description` child, or empty, a long text; its categories, the text of each `category`
|
||
child, each a short text, at most 16; its artwork, the `url` attribute of each `thumbnail`
|
||
child (`media:thumbnail`, §5.6 there), each a long text of 1 byte or more, at most 8 (a
|
||
`thumbnail` with no `url` is refused); its rating, when it has a `rating` child
|
||
(`media:rating`, §5.2 there): the first one's `scheme` attribute, or `urn:simple` when absent
|
||
(§5.2's default), which when not empty is a short text; and its text, a short text.
|
||
4. **Where it may be shown**, from its first `restriction` child whose `type` attribute is
|
||
`country` (`media:restriction`, §5.13 there): its `relationship` attribute is `allow` or
|
||
`deny`, and its text, split at white space (space, tab, CR, LF) into non-empty tokens, is
|
||
either the one token `all` or the one token `none`, or one or more tokens of two ASCII
|
||
letters each: otherwise `import_field_invalid`. Two letters name the ISO 3166-1 alpha-2 code
|
||
they spell in upper case, and the codes are taken in increasing order, each once. `allow` with `all`, `deny` with `none`, or no such child: shown
|
||
everywhere; `allow` with `none`, or `deny` with `all`: shown nowhere; `allow` with codes:
|
||
shown only in those countries; `deny` with codes: shown everywhere but those.
|
||
|
||
The item's title is RSS 2.0's `title` or Media RSS's `media:title`, whichever the item writes
|
||
first, and so for `description` and `category`. Elements that a `media:group` or a
|
||
`media:content` holds, other than the content itself, are not read.
|
||
|
||
### 4.4 The JSON form
|
||
|
||
The JSON form carries a channel's schedule, its catalogue and its breaks in one document. It is
|
||
one JSON text (RFC 8259) in UTF-8, nested at most 64 deep, whose objects each name a member once.
|
||
Its value is an object with exactly these members:
|
||
|
||
| member | value |
|
||
|---|---|
|
||
| `kind` | the string `wai-channel-feed` |
|
||
| `channel_name` | a string |
|
||
| `programmes` | an array of objects, each with exactly `start_ms` and `stop_ms` (integers) and `asset_id` (a string) |
|
||
| `assets` | an array of objects, each with exactly the members of the next table |
|
||
| `breaks` | an array of objects, each with exactly `id` (a string), `start_ms` and `duration_ms` (integers) and `scte35` (a string, or `null`) |
|
||
|
||
| asset member | value |
|
||
|---|---|
|
||
| `asset_id`, `title`, `description`, `content_uri` | strings |
|
||
| `categories` | an array of strings |
|
||
| `rating` | `null`, or an object with exactly `system` and `value`, strings |
|
||
| `artwork` | an array of objects, each with exactly `uri` and `sha256`, strings |
|
||
| `geo` | `null` (shown everywhere), or an object with exactly one member, `allow` (shown only in) or `deny` (shown everywhere but), an array of strings |
|
||
| `rights_holder` | a string, or `null` |
|
||
| `duration_ms` | an integer: the asset's duration, 0 when unknown |
|
||
|
||
An integer is written as decimal digits with no sign, fraction, exponent or leading zero, at most
|
||
2^53 − 1. A document that is not so is refused `import_json_malformed`. Then, in document order,
|
||
each value is read as §3.2 and this paragraph say (`import_field_invalid` unless said otherwise):
|
||
`channel_name` a short text; each programme's `asset_id` a short text, with `stop_ms` after
|
||
`start_ms` (otherwise `import_time_malformed`); each asset's `asset_id` a short text (one that an
|
||
asset before it has is `import_asset_duplicate`), its `title` a long text of 1 byte or more, its
|
||
`description` and `content_uri` long texts, its categories as in §4.2, its `rating`'s `system` a
|
||
short text or empty and its `value` a short text, its artwork at most 8, each `uri` a long text
|
||
of 1 byte or more and each `sha256` 64 lowercase hexadecimal digits, its `geo` codes each two
|
||
upper-case ASCII letters in strictly increasing order (a `deny` with none is refused), and its
|
||
`rights_holder` a short text when not `null`; each break as §4.5 says of a break.
|
||
|
||
### 4.5 Breaks from a Media Playlist
|
||
|
||
A Media Playlist is read as stitched-presentation §3.2 reads one: UTF-8, split at each line feed
|
||
with a CR before it dropped, its first line `#EXTM3U` (otherwise `import_playlist_malformed`).
|
||
Each line that begins `#EXT-X-DATERANGE:` is a date range tag, and the rest of the line its
|
||
attribute list, read by the rules of stitched-presentation §3.2; a tag whose list does not read,
|
||
or whose `ID` is not a quoted string, is refused `import_playlist_malformed`.
|
||
|
||
Tags are grouped by `ID`, each group in the order its first tag appears. Two tags of one group
|
||
that carry one attribute **MUST** carry the same value, a quoted string or not as both write it
|
||
(`draft-pantos-hls-rfc8216bis-22` §4.4.5.1): otherwise `import_playlist_malformed`. A group is a
|
||
**break** when one of its tags carries `SCTE35-OUT`; the other groups are not read. A break's
|
||
attributes are those of its tags together, and it is read by these steps, each refused at the
|
||
first that fails:
|
||
|
||
1. Its **id** is its `ID`, a short text (`import_field_invalid`).
|
||
2. Its `START-DATE` is a quoted string, an HLS date-time (§3.4): an absent one, or one that is
|
||
not quoted, is `import_playlist_malformed`. Its instant is the break's **start**.
|
||
3. Its `SCTE35-OUT` (and its `SCTE35-IN`, when it has one) is an unquoted hexadecimal-sequence
|
||
(§4.2 there: `0x` or `0X`, then hexadecimal digits, read here in either case) of an even
|
||
count of digits, whose bytes are a `splice_info_section()` as live-events §4.3 reads form 1:
|
||
`table_id` 0xFC, and `section_length` (the low 12 bits of its bytes 1 and 2) plus 3 its
|
||
length. Otherwise `import_scte35_malformed`. The bytes of `SCTE35-OUT` are the break's
|
||
**section**.
|
||
4. Its **duration** is its `DURATION`, unquoted, when it has one; otherwise its `END-DATE`, a
|
||
quoted HLS date-time, less its start, when it has one; otherwise its `PLANNED-DURATION`,
|
||
unquoted. A duration attribute that is quoted is `import_playlist_malformed`, and one that
|
||
does not read is refused as §3.4 says. A break with none of the three, or whose duration is 0
|
||
or above 2^32 − 1 milliseconds, or an `END-DATE` before its start, is refused
|
||
`import_break_duration`.
|
||
|
||
A break of the JSON form is its `id` (step 1); its `start_ms`; its `scte35`, when not `null`,
|
||
an even count of hexadecimal digits (either case, no `0x`) read as step 3 reads a section
|
||
(`import_scte35_malformed`); and its `duration_ms`, refused as step 4 refuses one when 0 or above
|
||
2^32 − 1. A break of the JSON form with a `null` `scte35` has no section.
|
||
|
||
### 4.6 The programmes and their assets
|
||
|
||
The programmes are, by the feeds:
|
||
|
||
- **XMLTV**: those of §4.2;
|
||
- **the JSON form**: its `programmes`, each the span from `start_ms` to `stop_ms`, with its `asset_id`; none is
|
||
`import_empty`;
|
||
- **Media RSS alone**: the items of the documents in the order given, each document's in
|
||
document order, one programme an item, back to back from `start`: each programme begins where
|
||
the one before it ends and lasts its item's duration. An item whose duration is unknown is
|
||
refused `import_duration_unknown`, and an instant above 2^53 − 1 `import_time_malformed`. No
|
||
item is `import_empty`.
|
||
|
||
The programmes are ordered by start (programmes of one start keep their order), and each next to
|
||
the one after it: a programme that ends before the next begins is refused `import_gap`, and one
|
||
that ends after the next begins `import_overlap`. Every programme's start, and the last one's
|
||
end, **MUST** be a whole second, a multiple of 1000 (`import_time_malformed`): a guide's XMLTV
|
||
times have no finer unit (§7.2).
|
||
|
||
Each distinct asset id of the programmes, in the order of the first programme that names it, gets
|
||
its lineup entry (§6.2) by these steps, refused at the first that fails:
|
||
|
||
1. **Its description.** With the JSON form, its `assets` entry, which **MUST** exist
|
||
(`import_asset_unknown`). With XMLTV, the item of that asset id when a Media RSS document has
|
||
one, otherwise the first programme that names it. With Media RSS alone, its item. The
|
||
description is title, description, categories, artwork, rating, where it may be shown,
|
||
content URI and duration; a programme gives no content URI, a duration of 0 and shown
|
||
everywhere.
|
||
2. **Its rights holder**: the JSON form's `rights_holder` when not `null`; otherwise the owner of
|
||
the Media RSS document its item came from, when given; otherwise the import's owner, when
|
||
given. None is refused `import_owner_unknown`.
|
||
3. **Its artwork.** Each artwork URI of a description that came from XMLTV or Media RSS is bound
|
||
by its content hash: its **name** is the part of the URI after its last `/`, once the first
|
||
`?` or `#` and everything after it are removed. The name **MUST** be 1 to 255 bytes of ASCII
|
||
letters, digits, `.`, `_` and `-`, other than `.` and `..`, and the artwork set **MUST** hold a
|
||
file of that name; otherwise `import_artwork_missing`. A file of more than 16 777 216 bytes is
|
||
`import_oversized`. The artwork's `sha256` is the SHA-256 of that file's bytes. The JSON form
|
||
gives its artwork's `sha256` itself.
|
||
|
||
Programmes and assets are read once each: an asset's later programmes give it nothing, and
|
||
catalogue items no programme names are read (§4.3) but enter no lineup.
|
||
|
||
### 4.7 The schedule
|
||
|
||
The breaks are those of the JSON form, then those of the playlist, ordered by start (breaks of
|
||
one start keep their order). A break whose end (its start and its duration) is after the next
|
||
break's start is refused `import_overlap`; a break that does not lie within one programme, a
|
||
programme whose start is at most its start and whose end is at least its end, is refused
|
||
`import_break_outside`.
|
||
|
||
The **schedule** is, for each programme in order: a program entry from the programme's start, or
|
||
from the end of its previous break, to the start of its next break or to its end, of the
|
||
programme's asset id, wherever that span is not empty; and an ad entry for each of its breaks,
|
||
from its start to its start plus its duration, whose asset id is the break's id. The schedule is therefore gapless
|
||
and non-overlapping, and every entry of a programme lies within it.
|
||
|
||
### 4.8 The cues
|
||
|
||
Each break, in the order of §4.7, yields one `cue` payload of live-events §4.3, unchanged:
|
||
`type` 4 (break: out and back, its duration known), `duration_ms` the break's duration, and
|
||
`form` 1 with the break's section verbatim, or `form` 0 for a break with no section. Its
|
||
`cue_id` is the section's `splice_event_id` when the section's `encrypted_packet` bit (the high
|
||
bit of byte 4) is 0, its `splice_command_type` (byte 13) is 0x05 (`splice_insert()`, SCTE 35-1
|
||
§9.7.3) and it is 18 bytes or longer: the big-endian integer of bytes 14 to 17 (SCTE 35-1 §9.6,
|
||
Table 5, and §9.7.3, Table 10). Otherwise the `cue_id` is the big-endian integer of the first 4
|
||
bytes of SHA-256 over `wai:channel-cue-id` (ASCII), 0x01 and the break's id. A cue says, as its
|
||
message does, that the break is out of the program and back.
|
||
|
||
### 4.9 What an import yields
|
||
|
||
- **The receipt**: `ChannelReceipt::seal` (SPEC §5, `wai.video.channel`) by the operator's key,
|
||
with the import's `signer_id`, `channel_id` the channel, the schedule of §4.7, the window
|
||
(when not given, from the schedule's first start to its last end), `joules_micro` 0 and no
|
||
class (the import meters nothing; energy-measurement §2), the grant reference with no ceilings
|
||
(`grant_hash` 32 zero bytes, `capability` `video.channel`, `joule_ceiling_micro` 2^64 − 1, no
|
||
`funds_ceiling`), and `parent_receipt_hash` as given. A window that is empty, or that does not
|
||
lie within the schedule (its start before the schedule's first start, or its end after its
|
||
last end), is refused `import_window_outside`.
|
||
- **The lineup** (§6): the channel id, the channel name (the first of these that gives one: the
|
||
XMLTV document, the first Media RSS document, the JSON form; when none does, the channel id), the receipt's
|
||
`schedule_root`, the schedule's first start and last end, each programme's start, and the
|
||
assets of §4.6 in the order of their asset ids' bytes, signed by the operator's key.
|
||
- **The cues** of §4.8, each with its break's id and start.
|
||
|
||
### 4.10 The order of refusal
|
||
|
||
An import is refused for the first step that fails, in this order: §4.1; then each feed, the
|
||
XMLTV document, the Media RSS documents in the order given, the JSON form and then the playlist,
|
||
each first by its size (§3.1), then as XML, JSON or lines, then by its counts (§3.1), then by
|
||
its own steps (§4.2 to §4.5), element by element in document order; then §4.6, §4.7 and §4.9.
|
||
An import that is refused yields nothing.
|
||
|
||
## 5. The channel receipt's JSON carriage
|
||
|
||
A `wai.video.channel` receipt keeps its signed bytes, and this text defines no form of its own for
|
||
it: an import writes a receipt, and a verifier reads one, in the JSON carriage of
|
||
[revenue-share](revenue-share.md) §4.1, the one JSON form of a channel receipt. A file that holds
|
||
a receipt holds one JSON text (RFC 8259) in UTF-8 of that carriage. A receipt **reads** when it
|
||
is such a text and reads as revenue-share §4.1 says; its entries' leaves, its schedule root, its
|
||
signing payload, its signature and its **identity** (its receipt hash) are revenue-share §4.2's;
|
||
its **rules** are rules 1 to 5 of revenue-share §4.3, and its signature that section's rule 6. A
|
||
receipt **verifies** when its signature verifies and its rules hold.
|
||
|
||
## 6. The lineup
|
||
|
||
### 6.1 Layout
|
||
|
||
| size | field | meaning |
|
||
|---|---|---|
|
||
| 4 | `magic` | `WLU1` (ASCII) |
|
||
| 1 | `version` | 1 in this revision |
|
||
| short | `channel_id` | the channel: a short text |
|
||
| short | `channel_name` | its name, as a guide shows it: a short text |
|
||
| 32 | `schedule_root` | the root of the schedule it describes (§5) |
|
||
| 8 | `schedule_start_ms` | u64: the schedule's first start |
|
||
| 8 | `schedule_end_ms` | u64: the schedule's last end |
|
||
| 4 | `p` | u32: the programmes' count, 1 to 10 000 |
|
||
| 8 × `p` | `programmes` | each programme's start (u64), in order |
|
||
| 4 | `a` | u32: the assets' count, 1 to 10 000 |
|
||
| … | `assets` | `a` assets, §6.2, in the order of their asset ids' bytes |
|
||
| 32 | `signer` | the operator's Ed25519 key |
|
||
| 64 | `sig` | §6.3 |
|
||
|
||
A **short** field is a u8 length and that many bytes; a **long** field a u16 length and that
|
||
many bytes. Programme *k* runs from its start to the start of programme *k* + 1, the last one to
|
||
`schedule_end_ms`.
|
||
|
||
### 6.2 An asset
|
||
|
||
| size | field | meaning |
|
||
|---|---|---|
|
||
| short | `asset_id` | the schedule's asset id: a short text |
|
||
| long | `title` | a long text of 1 byte or more |
|
||
| long | `description` | a long text |
|
||
| 1 | `c` | the categories' count, 0 to 16 |
|
||
| short × `c` | `categories` | each a short text |
|
||
| short | `rating_system` | the rating's system, or no bytes |
|
||
| short | `rating_value` | the rating, or no bytes for none |
|
||
| 1 | `k` | the artworks' count, 0 to 8 |
|
||
| … | `artwork` | `k` artworks, each 32 bytes (the SHA-256 of the image's bytes) and a long field (its URI, a long text of 1 byte or more) |
|
||
| 1 | `geo` | where it may be shown: 0 everywhere; 1 only in the countries listed; 2 everywhere but them |
|
||
| 1 | `g` | the countries' count |
|
||
| 2 × `g` | `countries` | each an ISO 3166-1 alpha-2 code, two of `A`–`Z`, in strictly increasing order |
|
||
| short | `rights_holder` | the party that holds its rights, as the operator names it: a short text |
|
||
| long | `content_uri` | the asset's media, or no bytes |
|
||
| 8 | `duration_ms` | u64: the asset's duration, 0 when unknown |
|
||
|
||
A rights holder is a name, not a key: a contract between the operator and the rights holder
|
||
(a share contract of the revenue it earns, for one) names its parties itself, and a lineup states
|
||
what the operator published.
|
||
|
||
### 6.3 Digest and signature
|
||
|
||
The **digest** of a lineup is SHA-256 over its bytes from `magic` through `signer`. The
|
||
**signature** is Ed25519 by `signer` over `wai:channel-lineup` (ASCII) and 0x01, followed by the
|
||
bytes the digest covers.
|
||
|
||
### 6.4 Reading and rules
|
||
|
||
A reader reads exactly the bytes it is given as one lineup, in the order of §6.1, and refuses it
|
||
`lineup_malformed` when the bytes end before a field the layout requires or go on after `sig`,
|
||
or when:
|
||
|
||
1. its magic is not `WLU1` or its version not 1;
|
||
2. `channel_id` or `channel_name` is not a short text;
|
||
3. `schedule_start_ms` is not below `schedule_end_ms`, either is not a multiple of 1000, or
|
||
`schedule_end_ms` is above 2^53 − 1;
|
||
4. `p` is 0 or above 10 000; or the programmes' starts are not strictly increasing, the first is
|
||
not `schedule_start_ms`, the last is not below `schedule_end_ms`, or one is not a multiple of
|
||
1000;
|
||
5. `a` is 0 or above 10 000, or the asset ids are not in strictly increasing order of their
|
||
bytes;
|
||
6. an asset breaks §6.2: an `asset_id`, a category or `rights_holder` that is not a short text;
|
||
a `title` empty or not a text; a `description`, an artwork's URI or `content_uri` not a text;
|
||
an empty artwork URI; `c` above 16 or `k` above 8; a `rating_system` that is not empty or a
|
||
short text, or one that is not empty beside an empty `rating_value`; a `rating_value` that is
|
||
not empty or a short text; `geo` above 2, `geo` 0 with `g` above 0, `geo` 2 with `g` 0, or
|
||
countries not two of `A`–`Z` each in strictly increasing order; or `duration_ms` above
|
||
2^53 − 1.
|
||
|
||
### 6.5 A lineup and its schedule
|
||
|
||
A lineup **describes** a receipt's schedule when:
|
||
|
||
1. its `channel_id` is the receipt's;
|
||
2. its `schedule_root` is the receipt's, and its `schedule_start_ms` and `schedule_end_ms` are
|
||
the start of the receipt's first entry and the end of its last;
|
||
3. each programme's start is the start of an entry;
|
||
4. each programme holds at least one program entry, and every program entry it holds (every
|
||
entry that starts in its span) has one asset id, the programme's asset;
|
||
5. the asset ids of its assets are exactly the programmes' assets.
|
||
|
||
A programme's ad and filler entries are its breaks; a guide lists the programme over them.
|
||
|
||
## 7. The guide
|
||
|
||
### 7.1 The programmes of a range
|
||
|
||
A guide is rendered from a receipt and a lineup that describes its schedule. The programmes of a
|
||
range from `from` to `to`, with `from` below `to`, are the lineup's programmes whose span meets it (a
|
||
programme that begins before `to` and ends after `from`), in order, each with its start, its
|
||
end, its asset's id and its asset's entry (§6.2).
|
||
|
||
### 7.2 The XMLTV guide
|
||
|
||
The XMLTV guide of a range is these bytes, in UTF-8, each line ending with a line feed:
|
||
|
||
```
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<tv source-data-url="urn:wai:guide:ROOT:LINEUP:FROM:TO">
|
||
<channel id="CHANNEL">
|
||
<display-name>NAME</display-name>
|
||
</channel>
|
||
<programme start="START" stop="STOP" channel="CHANNEL">
|
||
<title>TITLE</title>
|
||
<desc>DESCRIPTION</desc>
|
||
<category>CATEGORY</category>
|
||
<icon src="URI"/>
|
||
<episode-num system="wai-asset-id">ASSET</episode-num>
|
||
<rating system="SYSTEM">
|
||
<value>VALUE</value>
|
||
</rating>
|
||
</programme>
|
||
</tv>
|
||
```
|
||
|
||
- `ROOT` is the receipt's `schedule_root` and `LINEUP` the lineup's digest, each 64 lowercase
|
||
hexadecimal digits, and `FROM` and `TO` the range's ends in decimal, with no leading zero.
|
||
`urn:wai:guide:` names a guide's sources; a guide names them in its `source-data-url`, an
|
||
attribute the DTD gives `tv` for where the data came from.
|
||
- `CHANNEL` is the lineup's `channel_id` and `NAME` its `channel_name`.
|
||
- There is one `programme` element for each programme of the range, in order. `START` and `STOP`
|
||
are its start and its end as XMLTV times (§3.4) in UTC with the zone written: `YYYYMMDDhhmmss
|
||
+0000`.
|
||
- Of a programme's children, in the order the DTD gives them: one `title`; one `desc` only when
|
||
the description is not empty; one `category` for each category, in order; one `icon` for each
|
||
artwork, in order, its `src` the URI; one `episode-num`, whose text is the asset id; and one
|
||
`rating` only when `rating_value` is not empty, with a `system` attribute only when
|
||
`rating_system` is not empty.
|
||
- In text, `&`, `<` and `>` are written `&`, `<` and `>`; in an attribute value,
|
||
those and `"`, tab and line feed are written `&`, `<`, `>`, `"`, `	` and
|
||
` `. Every other character is written as itself.
|
||
|
||
The guide's `episode-num` system is `wai-asset-id`, the import's default `id_system` (§4.1), so
|
||
a guide imports back into the programmes it lists.
|
||
|
||
### 7.3 The now/next document
|
||
|
||
The now/next document of an instant `at` is the JCS serialisation (RFC 8785) of this object,
|
||
followed by one line feed:
|
||
|
||
| member | value |
|
||
|---|---|
|
||
| `kind` | `wai-now-next` |
|
||
| `channel`, `channel_name` | the lineup's `channel_id` and `channel_name` |
|
||
| `schedule_root` | the receipt's, 64 lowercase hexadecimal digits |
|
||
| `lineup` | the lineup's digest, 64 lowercase hexadecimal digits |
|
||
| `at_ms` | `at` |
|
||
| `now` | the programme whose span holds `at`, or `null` |
|
||
| `next` | when `now` is a programme, the one after it, or `null` for the last; when `at` is before the schedule's first start, the first programme; otherwise `null` |
|
||
|
||
A programme is an object with the members `start_ms`, `stop_ms` and `asset_id`; `title`,
|
||
`description` and `content_uri`; `categories`, an array of strings; `rating`, `null` or
|
||
`{"system", "value"}`; `artwork`, an array of `{"sha256", "uri"}`; `geo`, `null` for everywhere,
|
||
`{"allow": [...]}` or `{"deny": [...]}`; `rights_holder`; and `duration_ms`.
|
||
|
||
### 7.4 The Media Playlist's cue tags
|
||
|
||
A receipt also gives the ad markers of a Media Playlist of the channel (RFC 8216), so that a
|
||
packager that writes the channel's segments, MPEG-2 transport stream segments among them, marks
|
||
each break where the signed schedule places it, and anyone holding
|
||
the receipt and its cues recomputes the markers and compares (§8.4). Each break is marked in two
|
||
forms:
|
||
|
||
- the tags `EXT-X-CUE-OUT`, `EXT-X-CUE-OUT-CONT` and `EXT-X-CUE-IN`, in a form in common use, which
|
||
no published standard defines and which this section fixes; and
|
||
- for a break whose cue carries its section, the `EXT-X-DATERANGE` tags of RFC 8216 §4.3.2.7.1,
|
||
which carry the SCTE-35 section (ANSI/SCTE 35-1 2023r2 §9.6) in `SCTE35-OUT`.
|
||
|
||
A client ignores a tag it does not recognise (RFC 8216 §6.3.1), so a client that reads one form
|
||
plays a playlist that carries both.
|
||
|
||
**The breaks** are the receipt's `ad` entries of a duration above 0, in schedule order. A
|
||
break's **id** is its entry's asset id, its **start** the entry's start, its **duration** the
|
||
entry's duration, and its **end** its start plus its duration. Each break has its cue, the one an
|
||
import wrote for it (§4.8, the `cues.json` of §9); its **section** is the cue's message when the
|
||
cue is of form 1, and it has none when the cue is of form 0. The receipt signs each break's id,
|
||
start and duration, which are all the cue tags are written from. It does not sign the section a
|
||
date range carries: that is the cue's, which §8.4 holds to its break's id and duration and no
|
||
further.
|
||
|
||
**The window** is what a Media Playlist lists: its media sequence number (RFC 8216 §4.3.3.2); the
|
||
instant its first segment begins; its segments in order, each a URI and a duration, each beginning
|
||
where the one before it ends; and whether it is **ended** (`EXT-X-ENDLIST`, RFC 8216 §4.3.3.4). A
|
||
segment's URI is 1 to 2 048 bytes of ASCII from `!` to `~`, the first of which is not `#`, and its
|
||
duration 1 to 2^32 − 1 milliseconds; the media sequence number is at most 2^53 − 1, and the last
|
||
segment ends before 253 402 300 800 000, the first instant of the year 10000. A break in the window
|
||
starts at the latest where the window ends, so every date below has four digits of year. A window
|
||
that is not so is refused `playlist_malformed`.
|
||
|
||
A segment **starts in** a break when it starts at or after the break's start and before its end.
|
||
A break's **last segment before it** is the segment that ends at its start, and its **return** the
|
||
segment that starts at its end. A break **is in** the window when the window holds its last
|
||
segment before it, a segment that starts in it, or its return. The tags are refused:
|
||
|
||
- `cue_unaligned`, at the first segment of the window, in order, inside which a break's start or
|
||
end lies (after the segment's start and before its end): a marker applies from a segment's
|
||
start, and the splice the schedule places would fall inside the segment;
|
||
- then `cue_unwritable`, at the first break, among the breaks, that is in the window, has a
|
||
section and has an id holding a double quote, a line feed or a carriage return, which no
|
||
quoted-string can hold (RFC 8216 §4.2).
|
||
|
||
Otherwise the **playlist** of the window is these lines, each ended by a line feed:
|
||
|
||
```
|
||
#EXTM3U
|
||
#EXT-X-VERSION:3
|
||
#EXT-X-TARGETDURATION:TARGET
|
||
#EXT-X-MEDIA-SEQUENCE:SEQUENCE
|
||
#EXT-X-PROGRAM-DATE-TIME:FIRST
|
||
```
|
||
|
||
then, for each segment of the window in order, its cue lines (below), `#EXTINF:DURATION,` and its
|
||
URI; and then `#EXT-X-ENDLIST` when the window is ended.
|
||
|
||
- `TARGET` is the longest segment's duration in seconds, rounded up, so that each `EXTINF` rounded
|
||
to the nearest integer is at most it (RFC 8216 §4.3.3.1); `SEQUENCE` the media sequence number in
|
||
decimal, with no leading zero; and `FIRST` the first segment's start as a date.
|
||
- A **date** is an instant written `YYYY-MM-DDTHH:MM:SS.mmmZ`, in UTC, always three digits after the
|
||
point (RFC 8216 §4.3.2.6); a **span** is a count of milliseconds written as seconds with three
|
||
digits after the point (`90.000`). A segment's `DURATION` is its duration as a span: a decimal
|
||
duration, which needs `EXT-X-VERSION` 3 (RFC 8216 §4.3.2.1, §7).
|
||
|
||
A segment's **cue lines** are, in this order:
|
||
|
||
1. The date range tags written at it, break by break in schedule order, of each break that has a
|
||
section: its **out tag** at the first segment of the window that puts it in the window, and its
|
||
**in tag** at its return, when the window holds it:
|
||
|
||
```
|
||
#EXT-X-DATERANGE:ID="ID",START-DATE="START",PLANNED-DURATION=SPAN,SCTE35-OUT=0xSECTION
|
||
#EXT-X-DATERANGE:ID="ID",START-DATE="START",DURATION=SPAN
|
||
```
|
||
|
||
`ID` is the break's id, `@` and its start in decimal; `START` its start as a date; `SPAN` its
|
||
duration as a span; and `SECTION` its section in uppercase hexadecimal, a hexadecimal-sequence
|
||
(RFC 8216 §4.2).
|
||
2. `#EXT-X-CUE-IN`, when a break ends at the segment's start.
|
||
3. `#EXT-X-CUE-OUT:SPAN`, `SPAN` the break's duration, when a break starts at the segment's start;
|
||
otherwise `#EXT-X-CUE-OUT-CONT:ElapsedTime=ELAPSED,Duration=SPAN`, when the segment starts in a
|
||
break, `ELAPSED` the time from the break's start to the segment's as a span.
|
||
|
||
So:
|
||
|
||
- **A date range announces its splice and is kept while it maps a segment.** RFC 8216 §4.3.2.7.1
|
||
places an out section in `SCTE35-OUT`, with a `START-DATE` that is the splice's and a
|
||
`PLANNED-DURATION`, and has a tag announcing a splice added with the last segment before it; once
|
||
the end is established, another tag of the same `ID` signals it. RFC 8216 §6.2.1 keeps a date range
|
||
while any of its dates maps to a segment the playlist holds. Two tags of one `ID` agree on every
|
||
attribute both carry (RFC 8216 §4.3.2.7), here `ID` and `START-DATE`, which that section requires
|
||
of every date range. The in tag carries no `SCTE35-IN`: an import keeps the out section alone
|
||
(§4.5 step 3).
|
||
- **An `ID` names one break.** A channel's schedule may repeat a break's id on another day; its start
|
||
makes the `ID` unique in any playlist, as RFC 8216 §4.3.2.7.1 requires of an id that may recur.
|
||
- **The cue form is a function of each segment.** A segment's `EXT-X-CUE` lines depend on its own
|
||
start and the schedule alone, so it carries the same ones in every window that holds it, and a
|
||
player that joins inside a break reads, on the first segment it is given, how far into the break
|
||
it is and how long the break is. The date range tags depend on the window, as RFC 8216 places them.
|
||
- **A break with no section has no date range**: RFC 8216 §4.3.2.7.1 maps a section, and there is
|
||
none to map. Its cue form is written all the same.
|
||
- **Every playlist states its program date-time** (`FIRST`), which RFC 8216 §4.3.2.7 requires of a
|
||
playlist that carries a date range, on the receipt's UTC timeline.
|
||
- **Nothing is spliced.** The segments are the channel's own, the program and the ad entries
|
||
alike; the tags say where a downstream inserter replaces the ad entries' segments. No
|
||
`EXT-X-DISCONTINUITY` is written, since the window's segments run on one timeline.
|
||
|
||
For brk-1 of the corpus's sample (§11), 90 seconds from 18:12:00 UTC, in ten-second segments, the
|
||
last segment before it, its first and its return carry:
|
||
|
||
```
|
||
#EXT-X-DATERANGE:ID="brk-1@1793297520000",START-DATE="2026-10-29T18:12:00.000Z",PLANNED-DURATION=90.000,SCTE35-OUT=0xFC30…
|
||
#EXTINF:10.000,
|
||
seg-71.ts
|
||
#EXT-X-CUE-OUT:90.000
|
||
#EXTINF:10.000,
|
||
seg-72.ts
|
||
#EXT-X-CUE-OUT-CONT:ElapsedTime=10.000,Duration=90.000
|
||
#EXTINF:10.000,
|
||
seg-73.ts
|
||
…
|
||
#EXT-X-DATERANGE:ID="brk-1@1793297520000",START-DATE="2026-10-29T18:12:00.000Z",DURATION=90.000
|
||
#EXT-X-CUE-IN
|
||
#EXTINF:10.000,
|
||
seg-81.ts
|
||
```
|
||
|
||
## 8. Verification
|
||
|
||
### 8.1 Inputs
|
||
|
||
A verifier is given a receipt in its JSON carriage (§5), a lineup's bytes, and optionally a
|
||
guide's bytes: an XMLTV guide when its first byte is `<`, a now/next document when it is `{`.
|
||
|
||
### 8.2 Steps
|
||
|
||
A verification gets one verdict, **verified** or the reason of the first step it fails:
|
||
|
||
1. **The receipt** reads (§5): `channel_receipt_malformed`.
|
||
2. Its signature verifies under `signer_pubkey` (§5): `bad_key`, `bad_signature`.
|
||
3. Its rules hold (§5): `channel_receipt_invalid`.
|
||
4. **The lineup** reads (§6.4): `lineup_malformed`.
|
||
5. Its signature verifies under `signer` (§6.3): `bad_key`, `bad_signature`.
|
||
6. Its `signer` is the receipt's `signer_pubkey`: one operator signs both. Otherwise
|
||
`lineup_signer_mismatch`.
|
||
7. It describes the receipt's schedule (§6.5): `lineup_schedule_mismatch`.
|
||
8. **The guide**, when one is given, reads: `guide_malformed`. An XMLTV guide reads when it is in
|
||
the subset of §3.3 with the root `tv`; its `source-data-url` is `urn:wai:guide:`, two runs of
|
||
64 lowercase hexadecimal digits and two decimal integers with no leading zero, the first below
|
||
the second, separated by `:`; and each of its `programme` children has `start` and `stop`
|
||
attributes that are XMLTV times (§3.4) and an `episode-num` child of system `wai-asset-id`,
|
||
the text of the first of which is its asset id. A now/next document reads when it is one JSON
|
||
object whose members are those of §7.3, with `at_ms` an integer of at most 2^53 − 1,
|
||
`schedule_root` and `lineup` strings, and `now` and `next` each `null` or an object whose `start_ms` and `stop_ms` are
|
||
integers and whose `asset_id` is a string.
|
||
9. It names the receipt's `schedule_root` and the lineup's digest: `guide_source_mismatch`.
|
||
10. Each of its programmes is the lineup's: for an XMLTV guide, each programme has the start, the
|
||
end and the asset of one of the programmes of the guide's range (§7.1); for a now/next
|
||
document, `now` and `next` have those of the programmes §7.3 names for its `at_ms`, and are
|
||
`null` where those are. Otherwise `guide_entry_unscheduled`, at the first programme, in the
|
||
guide's order, that is not (its index among the guide's `programme` elements; for a now/next
|
||
document, `now` 0 and `next` 1).
|
||
11. Its bytes are the guide of §7.2 for its range, or the document of §7.3 for its `at_ms`:
|
||
`guide_mismatch`.
|
||
|
||
Step 10 catches a guide that places a programme where the schedule does not; step 11 every other
|
||
difference: a title, a rating or a picture the lineup does not state, a programme left out.
|
||
|
||
### 8.3 What a verification states
|
||
|
||
- its verdict, and the **part** it is about: `receipt` for steps 1 to 3, `lineup` for steps 4 to
|
||
7, `guide` for steps 8 to 11, none when verified;
|
||
- the receipt's identity, when it reads; and the lineup's digest, when it reads;
|
||
- for a guide that reads: its format (`xmltv` or `now-next`), its range or its `at_ms`, and its
|
||
programmes, each with its start, its end, its asset id and whether it **aired**: whether an
|
||
entry the receipt's `aired` lists meets its span. A guide shows what the schedule placed; the
|
||
receipt states which of it was on air in its window;
|
||
- for `guide_entry_unscheduled`, the index of step 10.
|
||
|
||
### 8.4 A playlist's cue tags
|
||
|
||
A verifier given a receipt in its JSON carriage (§5), a cues document and a Media Playlist's bytes
|
||
gets one verdict, **verified** or the reason of the first step it fails:
|
||
|
||
1. The receipt reads, as §8.2 step 1: `channel_receipt_malformed`.
|
||
2. Its signature verifies, as §8.2 step 2: `bad_key`, `bad_signature`.
|
||
3. Its rules hold, as §8.2 step 3: `channel_receipt_invalid`.
|
||
4. **The cues** read: one JSON text (RFC 8259) in UTF-8, nested at most 64 deep, whose objects each
|
||
name a member once, an array of objects of exactly `break` (a string), `start_ms` (an integer of
|
||
at most 2^53 − 1, written as §4.4 writes one) and `payload` (an even count of lowercase
|
||
hexadecimal digits), as `channel.import` writes `cues.json` (§9). Otherwise `cues_malformed`.
|
||
5. They are the breaks' cues (§7.4): there are as many as there are breaks, and the cue at each index
|
||
is its break's: its `break` the break's id and its `start_ms` the break's start, and its payload
|
||
the one §4.8 yields for a break of that id and duration and of the section the payload carries,
|
||
when it carries a message of form 1 that reads as §4.5 step 3 reads a section, or of none, when it
|
||
is of form 0. Otherwise `cues_mismatch`, at the first index that is not, a cue missing or added at
|
||
the first index past the shorter list. A break whose duration is above 2^32 − 1 milliseconds has no
|
||
cue of §4.8, and its index fails.
|
||
6. **The playlist** reads: UTF-8 text whose lines, split at each line feed, the last ended by one,
|
||
begin with `#EXTM3U`; with exactly one line `#EXT-X-MEDIA-SEQUENCE:` and a decimal integer with no
|
||
leading zero, and a line `#EXT-X-PROGRAM-DATE-TIME:` and an HLS date-time (§3.4) before its first
|
||
segment; whose segments are each a line `#EXTINF:`, a duration (§3.4) and `,`, followed by a line,
|
||
the segment's URI, at least one of them. It is ended when one of its lines is `#EXT-X-ENDLIST`. Its
|
||
**window** is that media sequence number, the first such date-time's instant as its first
|
||
segment's start, its segments in order and whether it is ended, which **MUST** hold to §7.4's
|
||
bounds. Otherwise `playlist_malformed`. No other line is read here: step 8 compares every line.
|
||
7. Its window's tags can be written (§7.4): `cue_unaligned`, at the segment's index; `cue_unwritable`,
|
||
at the break's index among the breaks.
|
||
8. Its bytes are the playlist of §7.4 of its window: `playlist_mismatch`.
|
||
|
||
Step 8 catches a marker the schedule does not place, at a segment or in a date range: a break
|
||
another duration, another start or another section than its cue's, a tag left out, added or moved.
|
||
|
||
A verification states its verdict and the **part** it is about (`receipt` for steps 1 to 3, `cues`
|
||
for steps 4 and 5, `playlist` for steps 6 to 8, none when verified); the receipt's identity, when it
|
||
reads; for a playlist that reads, its window (its media sequence number, its first segment's start,
|
||
how many segments it holds and whether it is ended) and the breaks in it, each with its id, its
|
||
start, its duration and whether it has a date range; and the index of step 5 or step 7.
|
||
|
||
## 9. Operations and a worked example
|
||
|
||
Three operations of the WAI command line carry this text (the operation catalogue,
|
||
`wai/catalogue/ops.json`), each with the toolchain's agent rules: `--json` for the envelope,
|
||
`--dry-run` on an operation that writes, and `--yes` before a file is replaced.
|
||
|
||
- `channel.import` (`wai channel import`): feeds in; the receipt (`channel.json`, its JSON
|
||
carriage), the lineup (`lineup.wlu`) and the cues (`cues.json`) out, into a directory. A
|
||
refusal's code is the error's code, and its exit status 1.
|
||
- `channel.guide` (`wai channel guide`): a receipt and a lineup in; an XMLTV guide of a range
|
||
(the whole schedule unless `--from` and `--to` say otherwise), or with `--now-next --at` a
|
||
now/next document, out.
|
||
- `channel.verify` (`wai channel verify`): a receipt, a lineup and optionally a guide in; what
|
||
§8.3 states out, exit status 0 when verified and 1 otherwise.
|
||
|
||
The corpus's sample channel (§11) is imported, guided and verified so:
|
||
|
||
```
|
||
wai channel import --channel sample-1 --xmltv sample.xmltv.xml \
|
||
--mrss sample-a.mrss.xml --mrss-owner owner-a --mrss sample-b.mrss.xml --mrss-owner owner-b \
|
||
--owner owner-c --playlist sample.m3u8 --artwork . --key operator.key --signer-id sample-operator \
|
||
--window-start 1793296800000 --window-end 1793300400000 --out-dir out
|
||
wai channel guide --receipt out/channel.json --lineup out/lineup.wlu --out guide.xml
|
||
wai channel guide --receipt out/channel.json --lineup out/lineup.wlu --now-next --at 1793299545000 --out now-next.json
|
||
wai channel verify --receipt out/channel.json --lineup out/lineup.wlu --guide guide.xml
|
||
```
|
||
|
||
The test `tests/channel_lineup.rs` runs these commands on the corpus's files and requires the
|
||
corpus's receipt, lineup, cues and guides from them, byte for byte.
|
||
|
||
## 10. Reasons
|
||
|
||
The codes this extension adds, each with where it is raised:
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `import_inputs_invalid` | §4.1 |
|
||
| `import_oversized` | §3.1, §4.6 step 3 |
|
||
| `import_xml_malformed` | §3.3 |
|
||
| `import_json_malformed` | §4.4 |
|
||
| `import_playlist_malformed` | §4.5 |
|
||
| `import_time_malformed` | §3.4, §4.2, §4.3, §4.4, §4.6 |
|
||
| `import_timezone_unknown` | §3.4 |
|
||
| `import_no_stop` | §4.2 step 2 |
|
||
| `import_empty` | §4.2, §4.6 |
|
||
| `import_asset_unknown` | §4.2 step 3, §4.3 step 1, §4.6 step 1 |
|
||
| `import_asset_duplicate` | §4.3 step 1, §4.4 |
|
||
| `import_field_invalid` | §3.2, §4.2 to §4.5 |
|
||
| `import_scte35_malformed` | §4.5 step 3 |
|
||
| `import_break_duration` | §4.5 step 4 |
|
||
| `import_duration_unknown` | §4.6 |
|
||
| `import_gap` | §4.6 |
|
||
| `import_overlap` | §4.6, §4.7 |
|
||
| `import_owner_unknown` | §4.6 step 2 |
|
||
| `import_artwork_missing` | §4.6 step 3 |
|
||
| `import_break_outside` | §4.7 |
|
||
| `import_window_outside` | §4.9 |
|
||
| `channel_receipt_malformed` | §8.2 step 1 |
|
||
| `channel_receipt_invalid` | §8.2 step 3 |
|
||
| `lineup_malformed` | §6.4, §8.2 step 4 |
|
||
| `lineup_signer_mismatch` | §8.2 step 6 |
|
||
| `lineup_schedule_mismatch` | §8.2 step 7 |
|
||
| `guide_malformed` | §8.2 step 8 |
|
||
| `guide_source_mismatch` | §8.2 step 9 |
|
||
| `guide_entry_unscheduled` | §8.2 step 10 |
|
||
| `guide_mismatch` | §8.2 step 11 |
|
||
| `cues_malformed` | §8.4 step 4 |
|
||
| `cues_mismatch` | §8.4 step 5 |
|
||
| `playlist_malformed` | §7.4, §8.4 step 6 |
|
||
| `cue_unaligned` | §7.4, §8.4 step 7 |
|
||
| `cue_unwritable` | §7.4, §8.4 step 7 |
|
||
| `playlist_mismatch` | §8.4 step 8 |
|
||
|
||
A verification also gives live-events' `bad_key` and `bad_signature` (§8.2 steps 2 and 5, §8.4
|
||
step 2).
|
||
|
||
## 11. Conformance
|
||
|
||
[`live-conformance/channels/`](../live-conformance/channels/) holds the corpus, written from fixed
|
||
seeds by `wai_channel_lineup_vectors gen`: `vectors.json`, which holds the operator's key and
|
||
each vector's inputs and outcome; the feeds of a sample channel (an XMLTV document with a second
|
||
channel's programmes beside it, two Media RSS documents of two content owners, a Media Playlist
|
||
with SCTE-35 date ranges, and the JSON form of the same channel); the artwork files; the receipt
|
||
and the guides the sample yields. Every document is a fixture written for its case, of
|
||
programmes, owners and pictures made for it. The vectors:
|
||
|
||
- **imports**: the sample from XMLTV, Media RSS and the playlist; the same channel from the JSON
|
||
form, which yields the same receipt and lineup byte for byte; a channel made of one owner's
|
||
Media RSS items alone, back to back; and a control of each refusal of §4, each the sample with
|
||
one change (an edit the vector states, which a verifier applies to its copy);
|
||
- **verifications**: the sample's receipt and lineup alone, with its XMLTV guide, with a guide of
|
||
part of the schedule, and with now/next documents before, inside and after the schedule; and a
|
||
control of each step of §8.2 (a receipt that does not read, a flipped signature, a key of small
|
||
order, a program-as-run the window does not give, a lineup that does not read, one another key
|
||
signed, one for each rule of §6.5 that it alone breaks, a guide that does not read, one naming
|
||
another schedule, one that places a programme the schedule does not, and one whose title the
|
||
lineup does not state);
|
||
- **playlists**: windows of the sample's Media Playlist and the playlist each renders (§7.4): brk-1
|
||
from before its last segment before it to after its return; a live window that begins inside
|
||
brk-2, whose section is a `time_signal()`; brk-3, which begins half a second past a whole second,
|
||
in six-second segments, the window ended; a window that begins at a return; one no break is in;
|
||
and brk-1 from the JSON form with no section, which yields the same receipt and other cues, and
|
||
no date range. And a control of each refusal of §7.4: segments cut off a break's start, a segment
|
||
across a return, a break named with a double quote, and a segment of no duration;
|
||
- **playlist windows at their bounds**: brk-1 in segments of 6.006 seconds, whose `EXTINF` and
|
||
`ElapsedTime` state milliseconds and whose `TARGETDURATION` is 7, 6.006 rounded up; a segment URI
|
||
of 2 048 bytes; a segment of 2^32 − 1 milliseconds; a media sequence number of 2^53 − 1; and, of
|
||
a channel scheduled at the end of the year 9999, a window that ends at 9999-12-31T23:59:59.999Z,
|
||
where a break starts, its date range dated in the year 9999. And a control of each bound: brk-1 named with a line feed, and
|
||
with a carriage return in a receipt the operator signed (`cue_unwritable`); that window with a
|
||
segment of a millisecond after it, which ends at 10000-01-01T00:00:00Z; a window of no segment; a
|
||
segment URI of no byte, of 2 049 bytes, holding a space, holding a letter outside ASCII and
|
||
beginning with `#`; a segment of 2^32 milliseconds; and a media sequence number of 2^53
|
||
(`playlist_malformed`);
|
||
- **playlist verifications**: the playlists of the first six windows, the one of 6.006-second
|
||
segments and the one of the year 9999, each verified against its receipt and its cues (§8.4); and a
|
||
control of each step: a receipt that does not read, a flipped signature, a
|
||
program-as-run its window does not give, cues that do not read, a cue a millisecond longer than its
|
||
break, one whose section names another `splice_event_id`, one a second late, one left out, another
|
||
receipt's, a playlist with no media sequence number, one that ends at 10000-01-01T00:00:00Z, one
|
||
whose segment URI begins with `#`, one whose segments move off a break's start,
|
||
a break named with a double quote, and playlists that state another duration, leave out an
|
||
`EXT-X-CUE-OUT-CONT`, return a segment early, carry another section, start a date range a second
|
||
late, and leave out a date range's end.
|
||
|
||
A conforming implementation gives every import its outcome (the receipt, the lineup and the cues,
|
||
or the refusal), every verification its verdict and facts, every playlist window its playlist or
|
||
its refusal, and every playlist verification its verdict and facts, as the corpus states.
|
||
`wai_channel_lineup_vectors verify` and the test `tests/channel_lineup.rs` check that every file
|
||
regenerates byte for byte, that `wai-rs` gives every vector its outcome, that §10 lists exactly
|
||
the codes the reference adds, and that the corpus gives every reason of §10 and both of
|
||
live-events'. `live-conformance/channel_lineup_verify.py` is the second implementation, written
|
||
from this text: it shares no code with `wai-rs`; it verifies Ed25519 with WAI's strict rule
|
||
(`claim-conformance/_ed25519.py`), BLAKE3 with `claim-conformance/_blake3.py`, and reads a channel
|
||
receipt's carriage, its signed bytes, its identity and its rules as the revenue-share corpus's
|
||
second implementation reads them (§5); it reads XML, JSON and playlists itself, and renders and
|
||
reads a Media Playlist's cue tags itself (§7.4, §8.4); and it mutates copies of the corpus and requires
|
||
every copy to fail, and drops each of its own checks in turn and requires the corpus to catch the
|
||
weakened verifier.
|
||
|
||
## 12. Security and privacy considerations
|
||
|
||
- **What a lineup proves.** That the operator stated this description of each programme asset of
|
||
one schedule, and where its guide's programmes begin. It does not prove that a rights holder
|
||
agreed: a rights holder's name in a lineup is the operator's statement, and a party paid by it
|
||
checks a contract it signed, not the lineup.
|
||
- **What a guide proves.** Nothing by itself: it is recomputed from a receipt and a lineup and
|
||
compared, so a guide that verifies says exactly what the two signed records say, and a guide a
|
||
third party altered is refused. A guide is a schedule published ahead of air; which of it was on
|
||
air in a window is the receipt's program-as-run (§8.3), and a guide covering a span the receipt's
|
||
window does not is checked against the schedule alone.
|
||
- **Artwork.** A lineup binds each picture by its SHA-256, so a player that fetches the URI and
|
||
checks the hash shows the picture the operator stated, wherever it was fetched from.
|
||
- **Bounded reading.** An import reads bounded documents (§3.1), XML to a bounded depth with no
|
||
document type declaration that defines anything (§3.3), so no entity is ever expanded, and
|
||
integers it reads are checked against their bounds before any arithmetic.
|
||
- **What the cue tags prove.** A playlist that verifies marks each break where the receipt's
|
||
signed schedule places it, with its signed duration, and every segment of it; one that moves a
|
||
break, shortens it or leaves a segment of it unmarked is refused. The section a date range carries
|
||
is the import's (§4.8): the receipt does not sign it, and a verifier holds it only to its break's
|
||
id and duration, and through the cue id to a `splice_insert()`'s `splice_event_id`. A party that
|
||
splices on a section's own fields holds the playlist it was imported from, or the signed `cue`
|
||
events of a room that carried it (live-events §3.2, §4.3), which sign the section itself.
|
||
- **Availability.** Where an asset may be shown is the operator's statement of the catalogue's
|
||
restriction; a player enforces it, or a delivery that a `wai.video.manifest` describes does.
|
||
A lineup holds no viewer's data.
|