1use super::descriptor_body;
8use crate::error::{Error, Result};
9use crate::text::LangCode;
10use dvb_common::{Parse, Serialize};
11
12pub const TAG: u8 = 0x59;
14const HEADER_LEN: usize = 2;
15const ENTRY_LEN: usize = 8;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22#[non_exhaustive]
23pub enum SubtitlingType {
24 EbuTeletextSubtitles,
26 AssociatedEbuTeletext,
28 VbiData,
30 DvbSubtitlesNormal,
32 DvbSubtitlesNormal4x3,
34 DvbSubtitlesNormal16x9,
36 DvbSubtitlesNormal2p21x1,
38 DvbSubtitlesNormalHd,
40 DvbSubtitlesNormalUhd,
43 DvbSubtitlesHardOfHearing,
46 DvbSubtitlesHardOfHearing4x3,
49 DvbSubtitlesHardOfHearing16x9,
52 DvbSubtitlesHardOfHearing2p21x1,
55 DvbSubtitlesHardOfHearingHd,
58 DvbSubtitlesHardOfHearingUhd,
61 OpenSignLanguage,
63 ClosedSignLanguage,
65 Reserved(u8),
67}
68
69impl SubtitlingType {
70 #[must_use]
71 pub fn from_u8(v: u8) -> Self {
74 match v {
75 0x01 => Self::EbuTeletextSubtitles,
76 0x02 => Self::AssociatedEbuTeletext,
77 0x03 => Self::VbiData,
78 0x10 => Self::DvbSubtitlesNormal,
79 0x11 => Self::DvbSubtitlesNormal4x3,
80 0x12 => Self::DvbSubtitlesNormal16x9,
81 0x13 => Self::DvbSubtitlesNormal2p21x1,
82 0x14 => Self::DvbSubtitlesNormalHd,
83 0x16 => Self::DvbSubtitlesNormalUhd,
84 0x20 => Self::DvbSubtitlesHardOfHearing,
85 0x21 => Self::DvbSubtitlesHardOfHearing4x3,
86 0x22 => Self::DvbSubtitlesHardOfHearing16x9,
87 0x23 => Self::DvbSubtitlesHardOfHearing2p21x1,
88 0x24 => Self::DvbSubtitlesHardOfHearingHd,
89 0x26 => Self::DvbSubtitlesHardOfHearingUhd,
90 0x30 => Self::OpenSignLanguage,
91 0x31 => Self::ClosedSignLanguage,
92 v => Self::Reserved(v),
93 }
94 }
95
96 #[must_use]
97 pub fn to_u8(self) -> u8 {
99 match self {
100 Self::EbuTeletextSubtitles => 0x01,
101 Self::AssociatedEbuTeletext => 0x02,
102 Self::VbiData => 0x03,
103 Self::DvbSubtitlesNormal => 0x10,
104 Self::DvbSubtitlesNormal4x3 => 0x11,
105 Self::DvbSubtitlesNormal16x9 => 0x12,
106 Self::DvbSubtitlesNormal2p21x1 => 0x13,
107 Self::DvbSubtitlesNormalHd => 0x14,
108 Self::DvbSubtitlesNormalUhd => 0x16,
109 Self::DvbSubtitlesHardOfHearing => 0x20,
110 Self::DvbSubtitlesHardOfHearing4x3 => 0x21,
111 Self::DvbSubtitlesHardOfHearing16x9 => 0x22,
112 Self::DvbSubtitlesHardOfHearing2p21x1 => 0x23,
113 Self::DvbSubtitlesHardOfHearingHd => 0x24,
114 Self::DvbSubtitlesHardOfHearingUhd => 0x26,
115 Self::OpenSignLanguage => 0x30,
116 Self::ClosedSignLanguage => 0x31,
117 Self::Reserved(v) => v,
118 }
119 }
120
121 #[must_use]
122 pub fn name(self) -> &'static str {
124 match self {
125 Self::EbuTeletextSubtitles => "EBU teletext subtitles",
126 Self::AssociatedEbuTeletext => "associated EBU teletext",
127 Self::VbiData => "VBI data",
128 Self::DvbSubtitlesNormal => "DVB subtitles (normal), no aspect ratio critical",
129 Self::DvbSubtitlesNormal4x3 => "DVB subtitles (normal), 4:3",
130 Self::DvbSubtitlesNormal16x9 => "DVB subtitles (normal), 16:9",
131 Self::DvbSubtitlesNormal2p21x1 => "DVB subtitles (normal), 2.21:1",
132 Self::DvbSubtitlesNormalHd => "DVB subtitles (normal), HD",
133 Self::DvbSubtitlesNormalUhd => "DVB subtitles (normal), UHD",
134 Self::DvbSubtitlesHardOfHearing => {
135 "DVB subtitles (hard of hearing), no aspect ratio critical"
136 }
137 Self::DvbSubtitlesHardOfHearing4x3 => "DVB subtitles (hard of hearing), 4:3",
138 Self::DvbSubtitlesHardOfHearing16x9 => "DVB subtitles (hard of hearing), 16:9",
139 Self::DvbSubtitlesHardOfHearing2p21x1 => "DVB subtitles (hard of hearing), 2.21:1",
140 Self::DvbSubtitlesHardOfHearingHd => "DVB subtitles (hard of hearing), HD",
141 Self::DvbSubtitlesHardOfHearingUhd => "DVB subtitles (hard of hearing), UHD",
142 Self::OpenSignLanguage => "open (in-vision) sign language interpretation",
143 Self::ClosedSignLanguage => "closed sign language interpretation",
144 Self::Reserved(_) => "reserved",
145 }
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize))]
152pub struct SubtitlingEntry {
153 pub language_code: LangCode,
155 pub subtitling_type: SubtitlingType,
157 pub composition_page_id: u16,
159 pub ancillary_page_id: u16,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize))]
166pub struct SubtitlingDescriptor {
167 pub entries: Vec<SubtitlingEntry>,
169}
170
171impl<'a> Parse<'a> for SubtitlingDescriptor {
172 type Error = crate::error::Error;
173 fn parse(bytes: &'a [u8]) -> Result<Self> {
174 let body = descriptor_body(
175 bytes,
176 TAG,
177 "SubtitlingDescriptor",
178 "unexpected tag for subtitling_descriptor",
179 )?;
180 if body.len() % ENTRY_LEN != 0 {
181 return Err(Error::InvalidDescriptor {
182 tag: TAG,
183 reason: "subtitling_descriptor length must be a multiple of 8",
184 });
185 }
186 let mut entries = Vec::with_capacity(body.len() / ENTRY_LEN);
187 for chunk in body.chunks_exact(ENTRY_LEN) {
188 entries.push(SubtitlingEntry {
189 language_code: LangCode([chunk[0], chunk[1], chunk[2]]),
190 subtitling_type: SubtitlingType::from_u8(chunk[3]),
191 composition_page_id: u16::from_be_bytes([chunk[4], chunk[5]]),
192 ancillary_page_id: u16::from_be_bytes([chunk[6], chunk[7]]),
193 });
194 }
195 Ok(Self { entries })
196 }
197}
198
199impl Serialize for SubtitlingDescriptor {
200 type Error = crate::error::Error;
201 fn serialized_len(&self) -> usize {
202 HEADER_LEN + self.entries.len() * ENTRY_LEN
203 }
204
205 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
206 let len = self.serialized_len();
207 if buf.len() < len {
208 return Err(Error::OutputBufferTooSmall {
209 need: len,
210 have: buf.len(),
211 });
212 }
213 buf[0] = TAG;
214 buf[1] = (self.entries.len() * ENTRY_LEN) as u8;
215 let mut pos = HEADER_LEN;
216 for e in &self.entries {
217 buf[pos..pos + 3].copy_from_slice(&e.language_code.0);
218 buf[pos + 3] = e.subtitling_type.to_u8();
219 buf[pos + 4..pos + 6].copy_from_slice(&e.composition_page_id.to_be_bytes());
220 buf[pos + 6..pos + 8].copy_from_slice(&e.ancillary_page_id.to_be_bytes());
221 pos += ENTRY_LEN;
222 }
223 Ok(len)
224 }
225}
226impl<'a> crate::traits::DescriptorDef<'a> for SubtitlingDescriptor {
227 const TAG: u8 = TAG;
228 const NAME: &'static str = "SUBTITLING";
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn parse_single_entry() {
237 let bytes = [TAG, 8, b'e', b'n', b'g', 0x10, 0x00, 0x01, 0x00, 0x02];
238 let d = SubtitlingDescriptor::parse(&bytes).unwrap();
239 assert_eq!(d.entries.len(), 1);
240 assert_eq!(d.entries[0].language_code, LangCode(*b"eng"));
241 assert_eq!(
242 d.entries[0].subtitling_type,
243 SubtitlingType::DvbSubtitlesNormal
244 );
245 assert_eq!(d.entries[0].composition_page_id, 1);
246 assert_eq!(d.entries[0].ancillary_page_id, 2);
247 }
248
249 #[test]
250 fn parse_rejects_wrong_tag() {
251 assert!(matches!(
252 SubtitlingDescriptor::parse(&[0x5A, 0]).unwrap_err(),
253 Error::InvalidDescriptor { tag: 0x5A, .. }
254 ));
255 }
256
257 #[test]
258 fn parse_rejects_length_not_multiple_of_8() {
259 let bytes = [TAG, 7, 0, 0, 0, 0, 0, 0, 0];
260 assert!(matches!(
261 SubtitlingDescriptor::parse(&bytes).unwrap_err(),
262 Error::InvalidDescriptor { .. }
263 ));
264 }
265
266 #[test]
267 fn serialize_round_trip() {
268 let d = SubtitlingDescriptor {
269 entries: vec![
270 SubtitlingEntry {
271 language_code: LangCode(*b"fra"),
272 subtitling_type: SubtitlingType::DvbSubtitlesNormal,
273 composition_page_id: 0x1234,
274 ancillary_page_id: 0x5678,
275 },
276 SubtitlingEntry {
277 language_code: LangCode(*b"deu"),
278 subtitling_type: SubtitlingType::DvbSubtitlesHardOfHearing,
279 composition_page_id: 0,
280 ancillary_page_id: 0,
281 },
282 ],
283 };
284 let mut buf = vec![0u8; d.serialized_len()];
285 d.serialize_into(&mut buf).unwrap();
286 assert_eq!(SubtitlingDescriptor::parse(&buf).unwrap(), d);
287 }
288
289 #[test]
290 fn empty_descriptor_valid() {
291 let d = SubtitlingDescriptor::parse(&[TAG, 0]).unwrap();
292 assert_eq!(d.entries.len(), 0);
293 }
294
295 #[test]
296 fn subtitling_type_full_range_round_trip() {
297 for b in 0..=0xFF_u8 {
298 let st = SubtitlingType::from_u8(b);
299 assert_eq!(st.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
300 }
301 }
302
303 #[test]
304 fn subtitling_type_name_for_known() {
305 assert_eq!(
306 SubtitlingType::EbuTeletextSubtitles.name(),
307 "EBU teletext subtitles"
308 );
309 assert_eq!(
310 SubtitlingType::DvbSubtitlesNormal.name(),
311 "DVB subtitles (normal), no aspect ratio critical"
312 );
313 assert_eq!(
314 SubtitlingType::DvbSubtitlesNormalUhd.name(),
315 "DVB subtitles (normal), UHD"
316 );
317 assert_eq!(
318 SubtitlingType::DvbSubtitlesHardOfHearingUhd.name(),
319 "DVB subtitles (hard of hearing), UHD"
320 );
321 assert_eq!(
322 SubtitlingType::OpenSignLanguage.name(),
323 "open (in-vision) sign language interpretation"
324 );
325 assert_eq!(
326 SubtitlingType::ClosedSignLanguage.name(),
327 "closed sign language interpretation"
328 );
329 assert_eq!(SubtitlingType::Reserved(0x50).name(), "reserved");
330 }
331}