Skip to main content

dvb_si/descriptors/
subtitling.rs

1//! Subtitling Descriptor — ETSI EN 300 468 §6.2.42 (tag 0x59).
2//!
3//! Carried inside PMT's ES_info loop. Enumerates DVB subtitle services:
4//! one entry per 3-char language code + subtitling_type + composition/
5//! ancillary page triple (8 bytes).
6
7use crate::error::{Error, Result};
8use crate::text::LangCode;
9use crate::traits::Descriptor;
10use dvb_common::{Parse, Serialize};
11
12/// Descriptor tag for subtitling_descriptor.
13pub const TAG: u8 = 0x59;
14const HEADER_LEN: usize = 2;
15const ENTRY_LEN: usize = 8;
16
17/// One subtitling component.
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct SubtitlingEntry {
21    /// ISO 639-2 language code.
22    pub language_code: LangCode,
23    /// subtitling_type byte (ETSI EN 300 468 §6.2.42): 0x01 = EBU teletext subtitles,
24    /// 0x10..=0x13 = DVB subtitles, etc.
25    pub subtitling_type: u8,
26    /// composition_page_id.
27    pub composition_page_id: u16,
28    /// ancillary_page_id.
29    pub ancillary_page_id: u16,
30}
31
32/// Subtitling Descriptor.
33#[derive(Debug, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35pub struct SubtitlingDescriptor {
36    /// Entries in wire order.
37    pub entries: Vec<SubtitlingEntry>,
38}
39
40impl<'a> Parse<'a> for SubtitlingDescriptor {
41    type Error = crate::error::Error;
42    fn parse(bytes: &'a [u8]) -> Result<Self> {
43        if bytes.len() < HEADER_LEN {
44            return Err(Error::BufferTooShort {
45                need: HEADER_LEN,
46                have: bytes.len(),
47                what: "SubtitlingDescriptor header",
48            });
49        }
50        if bytes[0] != TAG {
51            return Err(Error::InvalidDescriptor {
52                tag: bytes[0],
53                reason: "unexpected tag for subtitling_descriptor",
54            });
55        }
56        let length = bytes[1] as usize;
57        if bytes.len() < HEADER_LEN + length {
58            return Err(Error::BufferTooShort {
59                need: HEADER_LEN + length,
60                have: bytes.len(),
61                what: "SubtitlingDescriptor body",
62            });
63        }
64        if length % ENTRY_LEN != 0 {
65            return Err(Error::InvalidDescriptor {
66                tag: TAG,
67                reason: "subtitling_descriptor length must be a multiple of 8",
68            });
69        }
70        let body = &bytes[HEADER_LEN..HEADER_LEN + length];
71        let mut entries = Vec::with_capacity(length / ENTRY_LEN);
72        for chunk in body.chunks_exact(ENTRY_LEN) {
73            entries.push(SubtitlingEntry {
74                language_code: LangCode([chunk[0], chunk[1], chunk[2]]),
75                subtitling_type: chunk[3],
76                composition_page_id: u16::from_be_bytes([chunk[4], chunk[5]]),
77                ancillary_page_id: u16::from_be_bytes([chunk[6], chunk[7]]),
78            });
79        }
80        Ok(Self { entries })
81    }
82}
83
84impl Serialize for SubtitlingDescriptor {
85    type Error = crate::error::Error;
86    fn serialized_len(&self) -> usize {
87        HEADER_LEN + self.entries.len() * ENTRY_LEN
88    }
89
90    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
91        let len = self.serialized_len();
92        if buf.len() < len {
93            return Err(Error::OutputBufferTooSmall {
94                need: len,
95                have: buf.len(),
96            });
97        }
98        buf[0] = TAG;
99        buf[1] = (self.entries.len() * ENTRY_LEN) as u8;
100        let mut pos = HEADER_LEN;
101        for e in &self.entries {
102            buf[pos..pos + 3].copy_from_slice(&e.language_code.0);
103            buf[pos + 3] = e.subtitling_type;
104            buf[pos + 4..pos + 6].copy_from_slice(&e.composition_page_id.to_be_bytes());
105            buf[pos + 6..pos + 8].copy_from_slice(&e.ancillary_page_id.to_be_bytes());
106            pos += ENTRY_LEN;
107        }
108        Ok(len)
109    }
110}
111
112impl<'a> Descriptor<'a> for SubtitlingDescriptor {
113    const TAG: u8 = TAG;
114    fn descriptor_length(&self) -> u8 {
115        (self.entries.len() * ENTRY_LEN) as u8
116    }
117}
118
119impl<'a> crate::traits::DescriptorDef<'a> for SubtitlingDescriptor {
120    const TAG: u8 = TAG;
121    const NAME: &'static str = "SUBTITLING";
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn parse_single_entry() {
130        let bytes = [TAG, 8, b'e', b'n', b'g', 0x10, 0x00, 0x01, 0x00, 0x02];
131        let d = SubtitlingDescriptor::parse(&bytes).unwrap();
132        assert_eq!(d.entries.len(), 1);
133        assert_eq!(d.entries[0].language_code, LangCode(*b"eng"));
134        assert_eq!(d.entries[0].subtitling_type, 0x10);
135        assert_eq!(d.entries[0].composition_page_id, 1);
136        assert_eq!(d.entries[0].ancillary_page_id, 2);
137    }
138
139    #[test]
140    fn parse_rejects_wrong_tag() {
141        assert!(matches!(
142            SubtitlingDescriptor::parse(&[0x5A, 0]).unwrap_err(),
143            Error::InvalidDescriptor { tag: 0x5A, .. }
144        ));
145    }
146
147    #[test]
148    fn parse_rejects_length_not_multiple_of_8() {
149        let bytes = [TAG, 7, 0, 0, 0, 0, 0, 0, 0];
150        assert!(matches!(
151            SubtitlingDescriptor::parse(&bytes).unwrap_err(),
152            Error::InvalidDescriptor { .. }
153        ));
154    }
155
156    #[test]
157    fn serialize_round_trip() {
158        let d = SubtitlingDescriptor {
159            entries: vec![
160                SubtitlingEntry {
161                    language_code: LangCode(*b"fra"),
162                    subtitling_type: 0x10,
163                    composition_page_id: 0x1234,
164                    ancillary_page_id: 0x5678,
165                },
166                SubtitlingEntry {
167                    language_code: LangCode(*b"deu"),
168                    subtitling_type: 0x20,
169                    composition_page_id: 0,
170                    ancillary_page_id: 0,
171                },
172            ],
173        };
174        let mut buf = vec![0u8; d.serialized_len()];
175        d.serialize_into(&mut buf).unwrap();
176        assert_eq!(SubtitlingDescriptor::parse(&buf).unwrap(), d);
177    }
178
179    #[test]
180    fn empty_descriptor_valid() {
181        let d = SubtitlingDescriptor::parse(&[TAG, 0]).unwrap();
182        assert_eq!(d.entries.len(), 0);
183    }
184}