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 super::descriptor_body;
8use crate::error::{Error, Result};
9use crate::text::LangCode;
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))]
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))]
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        let body = descriptor_body(
44            bytes,
45            TAG,
46            "SubtitlingDescriptor",
47            "unexpected tag for subtitling_descriptor",
48        )?;
49        if body.len() % ENTRY_LEN != 0 {
50            return Err(Error::InvalidDescriptor {
51                tag: TAG,
52                reason: "subtitling_descriptor length must be a multiple of 8",
53            });
54        }
55        let mut entries = Vec::with_capacity(body.len() / ENTRY_LEN);
56        for chunk in body.chunks_exact(ENTRY_LEN) {
57            entries.push(SubtitlingEntry {
58                language_code: LangCode([chunk[0], chunk[1], chunk[2]]),
59                subtitling_type: chunk[3],
60                composition_page_id: u16::from_be_bytes([chunk[4], chunk[5]]),
61                ancillary_page_id: u16::from_be_bytes([chunk[6], chunk[7]]),
62            });
63        }
64        Ok(Self { entries })
65    }
66}
67
68impl Serialize for SubtitlingDescriptor {
69    type Error = crate::error::Error;
70    fn serialized_len(&self) -> usize {
71        HEADER_LEN + self.entries.len() * ENTRY_LEN
72    }
73
74    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
75        let len = self.serialized_len();
76        if buf.len() < len {
77            return Err(Error::OutputBufferTooSmall {
78                need: len,
79                have: buf.len(),
80            });
81        }
82        buf[0] = TAG;
83        buf[1] = (self.entries.len() * ENTRY_LEN) as u8;
84        let mut pos = HEADER_LEN;
85        for e in &self.entries {
86            buf[pos..pos + 3].copy_from_slice(&e.language_code.0);
87            buf[pos + 3] = e.subtitling_type;
88            buf[pos + 4..pos + 6].copy_from_slice(&e.composition_page_id.to_be_bytes());
89            buf[pos + 6..pos + 8].copy_from_slice(&e.ancillary_page_id.to_be_bytes());
90            pos += ENTRY_LEN;
91        }
92        Ok(len)
93    }
94}
95impl<'a> crate::traits::DescriptorDef<'a> for SubtitlingDescriptor {
96    const TAG: u8 = TAG;
97    const NAME: &'static str = "SUBTITLING";
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn parse_single_entry() {
106        let bytes = [TAG, 8, b'e', b'n', b'g', 0x10, 0x00, 0x01, 0x00, 0x02];
107        let d = SubtitlingDescriptor::parse(&bytes).unwrap();
108        assert_eq!(d.entries.len(), 1);
109        assert_eq!(d.entries[0].language_code, LangCode(*b"eng"));
110        assert_eq!(d.entries[0].subtitling_type, 0x10);
111        assert_eq!(d.entries[0].composition_page_id, 1);
112        assert_eq!(d.entries[0].ancillary_page_id, 2);
113    }
114
115    #[test]
116    fn parse_rejects_wrong_tag() {
117        assert!(matches!(
118            SubtitlingDescriptor::parse(&[0x5A, 0]).unwrap_err(),
119            Error::InvalidDescriptor { tag: 0x5A, .. }
120        ));
121    }
122
123    #[test]
124    fn parse_rejects_length_not_multiple_of_8() {
125        let bytes = [TAG, 7, 0, 0, 0, 0, 0, 0, 0];
126        assert!(matches!(
127            SubtitlingDescriptor::parse(&bytes).unwrap_err(),
128            Error::InvalidDescriptor { .. }
129        ));
130    }
131
132    #[test]
133    fn serialize_round_trip() {
134        let d = SubtitlingDescriptor {
135            entries: vec![
136                SubtitlingEntry {
137                    language_code: LangCode(*b"fra"),
138                    subtitling_type: 0x10,
139                    composition_page_id: 0x1234,
140                    ancillary_page_id: 0x5678,
141                },
142                SubtitlingEntry {
143                    language_code: LangCode(*b"deu"),
144                    subtitling_type: 0x20,
145                    composition_page_id: 0,
146                    ancillary_page_id: 0,
147                },
148            ],
149        };
150        let mut buf = vec![0u8; d.serialized_len()];
151        d.serialize_into(&mut buf).unwrap();
152        assert_eq!(SubtitlingDescriptor::parse(&buf).unwrap(), d);
153    }
154
155    #[test]
156    fn empty_descriptor_valid() {
157        let d = SubtitlingDescriptor::parse(&[TAG, 0]).unwrap();
158        assert_eq!(d.entries.len(), 0);
159    }
160}