Skip to main content

dvb_si/
lib.rs

1//! ETSI EN 300 468 v1.19.1 DVB Service Information parser and builder.
2//!
3//! `dvb-si` turns a raw MPEG-TS into typed, decoded DVB sections and complete
4//! logical tables: feed packets in, get back PAT/PMT/SDT/EIT/… section structs
5//! whose text fields decode to UTF-8 and
6//! whose descriptor loops walk into typed descriptors. Every layout is cited to
7//! the ETSI spec and round-trip tested; the same types serialize back to bytes.
8//!
9//! # 30-second quickstart
10//!
11//! Build a [`demux::SiDemux`], feed it TS packets, match on
12//! [`tables::AnyTableSection`], walk the descriptor loop with
13//! [`descriptors::DescriptorLoop::iter`], and print decoded text via
14//! [`text::DvbText::decode`]:
15//!
16//! ```
17//! use dvb_si::demux::SiDemux;
18//! use dvb_si::descriptors::AnyDescriptor;
19//! use dvb_si::tables::AnyTableSection;
20//!
21//! let mut demux = SiDemux::builder().build();
22//!
23//! // In real code, `packet` is each aligned 188-byte packet from your TS source
24//! // (file, UDP, tuner). Here we hand-build one PAT packet to keep the doctest
25//! // self-contained — see `dvb-tools dump` (in the `dvb-tools` crate) for the
26//! // file-reading loop.
27//! # let packet = build_pat_packet();
28//! for event in demux.feed(&packet) {
29//!     match event.table_section() {
30//!         Ok(AnyTableSection::SdtSection(sdt)) => {
31//!             for service in &sdt.services {
32//!                 for item in service.descriptors.iter().flatten() {
33//!                     if let AnyDescriptor::Service(svc) = item {
34//!                         // DvbText decodes EN 300 468 Annex A → UTF-8.
35//!                         println!("service: {}", svc.service_name.decode());
36//!                     }
37//!                 }
38//!             }
39//!         }
40//!         Ok(AnyTableSection::PatSection(pat)) => {
41//!             println!("PAT v{} on {}", event.version().unwrap_or(0), event.pid());
42//!             assert_eq!(pat.entries.len(), 1);
43//!         }
44//!         Ok(other) => { let _ = other; }
45//!         Err(_) => {} // malformed section
46//!     }
47//! }
48//!
49//! # // Minimal PAT-in-a-TS-packet builder used by the doctest above.
50//! # fn build_pat_packet() -> [u8; 188] {
51//! #     use dvb_common::Serialize;
52//! #     use dvb_si::tables::pat::{PatSection, PatEntry};
53//! #     const PMT_PID: u16 = 0x0100;
54//! #     let pat = PatSection {
55//! #         transport_stream_id: 1, version_number: 0, current_next_indicator: true,
56//! #         section_number: 0, last_section_number: 0,
57//! #         entries: vec![PatEntry { program_number: 1, pid: PMT_PID }],
58//! #     };
59//! #     let mut section = vec![0u8; pat.serialized_len()];
60//! #     pat.serialize_into(&mut section).unwrap();
61//! #     const TS_SYNC_BYTE: u8 = 0x47;
62//! #     const PAYLOAD_UNIT_START_INDICATOR: u8 = 0x40;
63//! #     const PID_LOW_BYTE: u8 = 0x00;
64//! #     const PAYLOAD_ONLY: u8 = 0x10;
65//! #     const POINTER_FIELD_START: u8 = 0x00;
66//! #     const STUFFING_BYTE: u8 = 0xFF;
67//! #     let mut pkt = [STUFFING_BYTE; 188];
68//! #     pkt[0] = TS_SYNC_BYTE;
69//! #     pkt[1] = PAYLOAD_UNIT_START_INDICATOR;
70//! #     pkt[2] = PID_LOW_BYTE;
71//! #     pkt[3] = PAYLOAD_ONLY;
72//! #     pkt[4] = POINTER_FIELD_START;
73//! #     pkt[5..5 + section.len()].copy_from_slice(&section);
74//! #     pkt
75//! # }
76//! ```
77//!
78//! # Layer map
79//!
80//! ```text
81//! TS packets ─▶ demux::SiDemux ─▶ SectionEvent
82//!                                    │ .table_section()
83//!                                    ▼
84//!                              tables::AnyTableSection  (PatSection, SdtSection, …)
85//!                                    │ section.<loop field> : DescriptorLoop
86//!                                    ▼
87//!                          descriptors::parse_loop ─▶ AnyDescriptor
88//!                                    │ field : DvbText / LangCode
89//!                                    ▼
90//!                              text::DvbText::decode() ─▶ UTF-8 String
91//!
92//! SectionEvent.bytes() ─▶ collect::SectionSetCollector ─▶ CompleteSectionSet
93//!                                                        │ .table::<T>()
94//!                                                        ├ .nit() / .bat() / .sdt() / .eit()
95//!                                                        ▼
96//!                                                  complete logical tables
97//! ```
98//!
99//! Each layer is independently usable: a caller who already has complete section
100//! bytes can skip [`demux`] and call [`tables::AnyTableSection::parse`] directly; a
101//! caller with a bare descriptor loop can call [`descriptors::parse_loop`] on it.
102//! Use [`collect`] when a table spans multiple sections.
103//!
104//! # RFU policy
105//!
106//! DVB reserved-bit fields carry a semantic distinction:
107//! - `reserved_future_use` bits are **emitted as 1** (the DVB convention that
108//!   future equipment sees a "1" for unimplemented bits).
109//! - `reserved_zero_future_use` bits are **emitted as 0**.
110//!
111//! Parsers **accept** any value (no rejection on non-zero RFU) — unlike
112//! `dvb-t2mi`, which validates RFU bits. This crate prioritises forward
113//! compatibility with future broadcast streams.
114//!
115//! # Features
116//!
117//! | Feature | Default | Enables |
118//! |---|---|---|
119//! | `chrono` | on | MJD + BCD time fields decode to `chrono::DateTime<Utc>` (EIT `start_time()`, TDT/TOT). Off → raw bytes. |
120//! | `ts` | on | [`demux::SiDemux`], [`ts::SectionReassembler`], TS packet parsing. Off → bring your own complete section bytes. |
121//! | `serde` | on | **Serialize-only** — for display/export (JSON via serde_json); parsing FROM JSON is deliberately unsupported, re-parse from wire bytes. `Serialize` on every table/descriptor; [`text::DvbText`] serializes as its **decoded** UTF-8 string (camelCase JSON). |
122//! | `yoke` | off | `yoke::Yokeable` on every zero-copy view type + the `owned::Owned` wrapper — own a parsed view past the input buffer's borrow (store/cache/send across threads) without re-parsing or a mirror type. |
123//!
124//! ```toml
125//! dvb-si = { version = "4.0", default-features = false }  # tight, no_std-ish build
126//! ```
127//!
128//! # Entry points
129//!
130//! - [`demux::SiDemux`] — PID-filtered, version-gated section pump (feature `ts`).
131//! - [`tables::AnyTableSection`] / [`descriptors::AnyDescriptor`] — trait-driven
132//!   dispatch on table_id / descriptor_tag; [`descriptors::parse_loop`] walks a
133//!   loop lazily.
134//! - [`collect`] — generic multi-section collection plus complete NIT/BAT/SDT/EIT
135//!   views with typed descriptor loops.
136//! - [`descriptors::DescriptorRegistry`] — register private descriptors at runtime.
137//! - [`descriptors::ExtensionRegistry`] — register private tag-extension
138//!   sub-descriptors at runtime.
139//! - [`tables::TableRegistry`] — register private table_ids at runtime.
140//! - [`text::DvbText`] / [`text::LangCode`] — decoded-on-demand Annex A text.
141//! - [`Parse`](dvb_common::Parse) / [`Serialize`](dvb_common::Serialize) — the two
142//!   symmetric contracts every table and descriptor implements.
143//! - [`tables`] — `*Section` parsers for PAT, PMT, CAT, TSDT, NIT, BAT, SDT,
144//!   EIT, TDT, TOT, RST, DIT, SIT, ST, SAT, AIT, DSM-CC section, UNT, INT, RCT,
145//!   CIT, RNT, Container, MPE datagram, MPE-FEC, MPE-IFEC, protection message,
146//!   downloadable font info — every allocated table_id in EN 300 468 V1.19.1
147//!   Table 2.
148//! - [`descriptors`] — every DVB descriptor (tags 0x40..0x7F) plus MPEG-2 descriptors.
149//! - [`carousel`] — DSM-CC data-carousel messages (DSI/DII/DDB) + module
150//!   reassembly on top of the [`tables::dsmcc`] section framing.
151//! - [`pid::well_known`] — reserved DVB/MPEG-2 PIDs.
152//! - [`table_id::TableId`] — typed table_id enum.
153//! - [`descriptor_tag::DescriptorTag`] — typed descriptor_tag enum.
154//!
155//! See the crate README and `docs/` for the structured spec reference.
156//! `MIGRATION-4.0.md` covers the 3.x → 4.0 API break.
157
158#![warn(missing_docs)]
159
160pub mod carousel;
161pub mod collect;
162pub mod compatibility;
163pub mod descriptor_tag;
164pub mod descriptors;
165#[cfg(feature = "chrono")]
166pub mod epg;
167pub mod error;
168pub mod pid;
169mod registry_names;
170pub mod section;
171pub mod table_id;
172pub mod tables;
173pub mod text;
174pub mod traits;
175
176#[cfg(feature = "yoke")]
177pub mod owned;
178
179#[cfg(feature = "ts")]
180pub mod demux;
181#[cfg(feature = "ts")]
182pub mod mux;
183#[cfg(feature = "ts")]
184pub mod resync;
185#[cfg(feature = "ts")]
186pub mod ts;
187
188pub use descriptor_tag::DescriptorTag;
189pub use error::{Error, Result};
190pub use table_id::TableId;