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 `examples/si_dump.rs` for the file-reading loop.
26//! # let packet = build_pat_packet();
27//! for event in demux.feed(&packet) {
28//!     match event.table_section() {
29//!         Ok(AnyTableSection::SdtSection(sdt)) => {
30//!             for service in &sdt.services {
31//!                 for item in service.descriptors.iter().flatten() {
32//!                     if let AnyDescriptor::Service(svc) = item {
33//!                         // DvbText decodes EN 300 468 Annex A → UTF-8.
34//!                         println!("service: {}", svc.service_name.decode());
35//!                     }
36//!                 }
37//!             }
38//!         }
39//!         Ok(AnyTableSection::PatSection(pat)) => {
40//!             println!("PAT v{} on {}", event.version().unwrap_or(0), event.pid());
41//!             assert_eq!(pat.entries.len(), 1);
42//!         }
43//!         Ok(other) => { let _ = other; }
44//!         Err(_) => {} // malformed section
45//!     }
46//! }
47//!
48//! # // Minimal PAT-in-a-TS-packet builder used by the doctest above.
49//! # fn build_pat_packet() -> [u8; 188] {
50//! #     use dvb_common::Serialize;
51//! #     use dvb_si::tables::pat::{PatSection, PatEntry};
52//! #     const PMT_PID: u16 = 0x0100;
53//! #     let pat = PatSection {
54//! #         transport_stream_id: 1, version_number: 0, current_next_indicator: true,
55//! #         section_number: 0, last_section_number: 0,
56//! #         entries: vec![PatEntry { program_number: 1, pid: PMT_PID }],
57//! #     };
58//! #     let mut section = vec![0u8; pat.serialized_len()];
59//! #     pat.serialize_into(&mut section).unwrap();
60//! #     const TS_SYNC_BYTE: u8 = 0x47;
61//! #     const PAYLOAD_UNIT_START_INDICATOR: u8 = 0x40;
62//! #     const PID_LOW_BYTE: u8 = 0x00;
63//! #     const PAYLOAD_ONLY: u8 = 0x10;
64//! #     const POINTER_FIELD_START: u8 = 0x00;
65//! #     const STUFFING_BYTE: u8 = 0xFF;
66//! #     let mut pkt = [STUFFING_BYTE; 188];
67//! #     pkt[0] = TS_SYNC_BYTE;
68//! #     pkt[1] = PAYLOAD_UNIT_START_INDICATOR;
69//! #     pkt[2] = PID_LOW_BYTE;
70//! #     pkt[3] = PAYLOAD_ONLY;
71//! #     pkt[4] = POINTER_FIELD_START;
72//! #     pkt[5..5 + section.len()].copy_from_slice(&section);
73//! #     pkt
74//! # }
75//! ```
76//!
77//! # Layer map
78//!
79//! ```text
80//! TS packets ─▶ demux::SiDemux ─▶ SectionEvent
81//!                                    │ .table_section()
82//!                                    ▼
83//!                              tables::AnyTableSection  (PatSection, SdtSection, …)
84//!                                    │ section.<loop field> : DescriptorLoop
85//!                                    ▼
86//!                          descriptors::parse_loop ─▶ AnyDescriptor
87//!                                    │ field : DvbText / LangCode
88//!                                    ▼
89//!                              text::DvbText::decode() ─▶ UTF-8 String
90//!
91//! SectionEvent.bytes() ─▶ collect::SectionSetCollector ─▶ CompleteSectionSet
92//!                                                        │ .table::<T>()
93//!                                                        ├ .nit() / .bat() / .sdt() / .eit()
94//!                                                        ▼
95//!                                                  complete logical tables
96//! ```
97//!
98//! Each layer is independently usable: a caller who already has complete section
99//! bytes can skip [`demux`] and call [`tables::AnyTableSection::parse`] directly; a
100//! caller with a bare descriptor loop can call [`descriptors::parse_loop`] on it.
101//! Use [`collect`] when a table spans multiple sections.
102//!
103//! # Features
104//!
105//! | Feature | Default | Enables |
106//! |---|---|---|
107//! | `chrono` | on | MJD + BCD time fields decode to `chrono::DateTime<Utc>` (EIT `start_time()`, TDT/TOT). Off → raw bytes. |
108//! | `ts` | on | [`demux::SiDemux`], [`ts::SectionReassembler`], TS packet parsing. Off → bring your own complete section bytes. |
109//! | `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). |
110//! | `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. |
111//!
112//! ```toml
113//! dvb-si = { version = "4.0", default-features = false }  # tight, no_std-ish build
114//! ```
115//!
116//! # Entry points
117//!
118//! - [`demux::SiDemux`] — PID-filtered, version-gated section pump (feature `ts`).
119//! - [`tables::AnyTableSection`] / [`descriptors::AnyDescriptor`] — trait-driven
120//!   dispatch on table_id / descriptor_tag; [`descriptors::parse_loop`] walks a
121//!   loop lazily.
122//! - [`collect`] — generic multi-section collection plus complete NIT/BAT/SDT/EIT
123//!   views with typed descriptor loops.
124//! - [`descriptors::DescriptorRegistry`] — register private descriptors at runtime.
125//! - [`text::DvbText`] / [`text::LangCode`] — decoded-on-demand Annex A text.
126//! - [`Parse`](dvb_common::Parse) / [`Serialize`](dvb_common::Serialize) — the two
127//!   symmetric contracts every table and descriptor implements.
128//! - [`tables`] — `*Section` parsers for PAT, PMT, CAT, TSDT, NIT, BAT, SDT,
129//!   EIT, TDT, TOT, RST, DIT, SIT, ST, SAT, AIT, DSM-CC section, UNT, INT, RCT,
130//!   CIT, RNT, Container, MPE datagram, MPE-FEC, MPE-IFEC, protection message,
131//!   downloadable font info — every allocated table_id in EN 300 468 V1.19.1
132//!   Table 2.
133//! - [`descriptors`] — every DVB descriptor (tags 0x40..0x7F) plus MPEG-2 descriptors.
134//! - [`carousel`] — DSM-CC data-carousel messages (DSI/DII/DDB) + module
135//!   reassembly on top of the [`tables::dsmcc`] section framing.
136//! - [`pid::well_known`] — reserved DVB/MPEG-2 PIDs.
137//! - [`table_id::TableId`] — typed table_id enum.
138//! - [`descriptor_tag::DescriptorTag`] — typed descriptor_tag enum.
139//!
140//! See the crate README and `docs/` for the structured spec reference.
141//! `MIGRATION-4.0.md` covers the 3.x → 4.0 API break.
142
143#![warn(missing_docs)]
144
145pub mod carousel;
146pub mod collect;
147pub mod descriptor_tag;
148pub mod descriptors;
149pub mod error;
150pub mod pid;
151pub mod section;
152pub mod table_id;
153pub mod tables;
154pub mod text;
155pub mod traits;
156
157#[cfg(feature = "yoke")]
158pub mod owned;
159
160#[cfg(feature = "ts")]
161pub mod demux;
162#[cfg(feature = "ts")]
163pub mod ts;
164
165pub use descriptor_tag::DescriptorTag;
166pub use error::{Error, Result};
167pub use table_id::TableId;