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 tables: feed packets in,
4//! get back PAT/PMT/SDT/EIT/… structs whose text fields decode to UTF-8 and
5//! whose descriptor loops walk into typed descriptors. Every layout is cited to
6//! the ETSI spec and round-trip tested; the same types serialize back to bytes.
7//!
8//! # 30-second quickstart
9//!
10//! Build a [`demux::SiDemux`], feed it TS packets, match on [`tables::AnyTable`],
11//! walk the descriptor loop with [`descriptors::DescriptorLoop::iter`], and print
12//! decoded text via [`text::DvbText::decode`]:
13//!
14//! ```
15//! use dvb_si::demux::SiDemux;
16//! use dvb_si::descriptors::AnyDescriptor;
17//! use dvb_si::tables::AnyTable;
18//!
19//! let mut demux = SiDemux::builder().build();
20//!
21//! // In real code, `packet` is each aligned 188-byte packet from your TS source
22//! // (file, UDP, tuner). Here we hand-build one PAT packet to keep the doctest
23//! // self-contained — see `examples/si_dump.rs` for the file-reading loop.
24//! # let packet = build_pat_packet();
25//! for event in demux.feed(&packet) {
26//! match event.table() {
27//! Ok(AnyTable::Sdt(sdt)) => {
28//! for service in &sdt.services {
29//! for item in service.descriptors.iter().flatten() {
30//! if let AnyDescriptor::Service(svc) = item {
31//! // DvbText decodes EN 300 468 Annex A → UTF-8.
32//! println!("service: {}", svc.service_name.decode());
33//! }
34//! }
35//! }
36//! }
37//! Ok(AnyTable::Pat(pat)) => {
38//! println!("PAT v{} on {}", event.version().unwrap_or(0), event.pid());
39//! assert_eq!(pat.entries.len(), 1);
40//! }
41//! Ok(other) => { let _ = other; }
42//! Err(_) => {} // malformed section
43//! }
44//! }
45//!
46//! # // Minimal PAT-in-a-TS-packet builder used by the doctest above.
47//! # fn build_pat_packet() -> [u8; 188] {
48//! # use dvb_common::Serialize;
49//! # use dvb_si::tables::pat::{Pat, PatEntry};
50//! # let pat = Pat {
51//! # transport_stream_id: 1, version_number: 0, current_next_indicator: true,
52//! # section_number: 0, last_section_number: 0,
53//! # entries: vec![PatEntry { program_number: 1, pid: 0x0100 }],
54//! # };
55//! # let mut section = vec![0u8; pat.serialized_len()];
56//! # pat.serialize_into(&mut section).unwrap();
57//! # let mut pkt = [0xFFu8; 188];
58//! # pkt[0] = 0x47; pkt[1] = 0x40; pkt[2] = 0x00; pkt[3] = 0x10; pkt[4] = 0x00;
59//! # pkt[5..5 + section.len()].copy_from_slice(§ion);
60//! # pkt
61//! # }
62//! ```
63//!
64//! # Layer map
65//!
66//! ```text
67//! TS packets ─▶ demux::SiDemux ─▶ SectionEvent
68//! │ .table()
69//! ▼
70//! tables::AnyTable (PAT, PMT, SDT, EIT, …)
71//! │ table.<loop field> : &[u8]
72//! ▼
73//! descriptors::parse_loop ─▶ AnyDescriptor
74//! │ field : DvbText / LangCode
75//! ▼
76//! text::DvbText::decode() ─▶ UTF-8 String
77//! ```
78//!
79//! Each layer is independently usable: a caller who already has complete section
80//! bytes can skip [`demux`] and call [`tables::AnyTable::parse`] directly; a
81//! caller with a bare descriptor loop can call [`descriptors::parse_loop`] on it.
82//!
83//! # Features
84//!
85//! | Feature | Default | Enables |
86//! |---|---|---|
87//! | `chrono` | on | MJD + BCD time fields decode to `chrono::DateTime<Utc>` (EIT `start_time()`, TDT/TOT). Off → raw bytes. |
88//! | `ts` | on | [`demux::SiDemux`], [`ts::SectionReassembler`], TS packet parsing. Off → bring your own complete section bytes. |
89//! | `serde` | on | `Serialize` on every table/descriptor; [`text::DvbText`] serializes as its **decoded** UTF-8 string (camelCase JSON). |
90//!
91//! ```toml
92//! dvb-si = { version = "2.0", default-features = false } # tight, no_std-ish build
93//! ```
94//!
95//! # Entry points
96//!
97//! - [`demux::SiDemux`] — PID-filtered, version-gated section pump (feature `ts`).
98//! - [`tables::AnyTable`] / [`descriptors::AnyDescriptor`] — trait-driven dispatch
99//! on table_id / descriptor_tag; [`descriptors::parse_loop`] walks a loop lazily.
100//! - [`descriptors::DescriptorRegistry`] — register private descriptors at runtime.
101//! - [`text::DvbText`] / [`text::LangCode`] — decoded-on-demand Annex A text.
102//! - [`Parse`](dvb_common::Parse) / [`Serialize`](dvb_common::Serialize) — the two
103//! symmetric contracts every table and descriptor implements.
104//! - [`tables`] — PAT, PMT, CAT, TSDT, NIT, BAT, SDT, EIT, TDT, TOT, RST, DIT, SIT,
105//! ST, SAT, AIT, DSM-CC section, UNT, INT, RCT, CIT, RNT, Container, MPE
106//! datagram, MPE-FEC, MPE-IFEC, protection message, downloadable font info —
107//! every allocated table_id in EN 300 468 V1.19.1 Table 2.
108//! - [`descriptors`] — every DVB descriptor (tags 0x40..0x7F) plus MPEG-2 descriptors.
109//! - [`carousel`] — DSM-CC data-carousel messages (DSI/DII/DDB) + module
110//! reassembly on top of the [`tables::dsmcc`] section framing.
111//! - [`pid::well_known`] — reserved DVB/MPEG-2 PIDs.
112//! - [`table_id::TableId`] — typed table_id enum.
113//! - [`descriptor_tag::DescriptorTag`] — typed descriptor_tag enum.
114//!
115//! See the crate README and `docs/` for the structured spec reference, and
116//! `MIGRATION-2.0.md` for the 1.x → 2.0 upgrade guide.
117
118#![warn(missing_docs)]
119
120pub mod carousel;
121pub mod descriptor_tag;
122pub mod descriptors;
123pub mod error;
124pub mod pid;
125pub mod section;
126pub mod table_id;
127pub mod tables;
128pub mod text;
129pub mod traits;
130
131#[cfg(feature = "ts")]
132pub mod demux;
133#[cfg(feature = "ts")]
134pub mod ts;
135
136pub use descriptor_tag::DescriptorTag;
137pub use error::{Error, Result};
138pub use table_id::TableId;