dvb_si/descriptors/
subtitling.rs1use 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, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20pub struct SubtitlingEntry {
21 pub language_code: LangCode,
23 pub subtitling_type: u8,
26 pub composition_page_id: u16,
28 pub ancillary_page_id: u16,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize))]
35pub struct SubtitlingDescriptor {
36 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}