1#![deny(missing_debug_implementations)]
8#![deny(missing_docs)]
9
10pub use cea708_types;
17
18mod parser;
19mod svc;
20mod writer;
21
22#[macro_use]
23extern crate log;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
27pub enum ParserError {
28 #[error("The length of the data ({actual}) does not match the advertised expected ({expected}) length")]
30 LengthMismatch {
31 expected: usize,
33 actual: usize,
35 },
36 #[error("Some magic byte/s do not have the correct value")]
38 WrongMagic,
39 #[error("The framerate specified is not known by this implementation")]
41 UnknownFramerate,
42 #[error("Some fixed bits did not have the correct value")]
44 InvalidFixedBits,
45 #[error("CEA-608 compatibility bytes were found after CEA-708 bytes")]
47 Cea608AfterCea708,
48 #[error("The computed checksum value does not match the stored checksum value")]
50 ChecksumFailed,
51 #[error("The sequence count differs between the header and the footer")]
54 SequenceCountMismatch,
55 #[error("The service descriptor has different values")]
57 ServiceNumberMismatch,
58 #[error("The service number is not valid")]
60 InvalidServiceNumber,
61 #[error("The service descriptor contains a different set of flags to the CDP")]
63 ServiceFlagsMismatched,
64}
65
66impl From<cea708_types::ParserError> for ParserError {
67 fn from(value: cea708_types::ParserError) -> Self {
68 match value {
69 cea708_types::ParserError::Cea608AfterCea708 { byte_pos: _ } => {
70 ParserError::Cea608AfterCea708
71 }
72 cea708_types::ParserError::LengthMismatch { expected, actual } => {
73 ParserError::LengthMismatch { expected, actual }
74 }
75 }
76 }
77}
78
79pub use cea708_types::WriterError;
80
81static FRAMERATES: [Framerate; 8] = [
82 Framerate {
83 id: 0x1,
84 numer: 24000,
85 denom: 1001,
86 },
87 Framerate {
88 id: 0x2,
89 numer: 24,
90 denom: 1,
91 },
92 Framerate {
93 id: 0x3,
94 numer: 25,
95 denom: 1,
96 },
97 Framerate {
98 id: 0x4,
99 numer: 30000,
100 denom: 1001,
101 },
102 Framerate {
103 id: 0x5,
104 numer: 30,
105 denom: 1,
106 },
107 Framerate {
108 id: 0x6,
109 numer: 50,
110 denom: 1,
111 },
112 Framerate {
113 id: 0x7,
114 numer: 60000,
115 denom: 1001,
116 },
117 Framerate {
118 id: 0x8,
119 numer: 60,
120 denom: 1,
121 },
122];
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub struct Framerate {
127 id: u8,
128 numer: u32,
129 denom: u32,
130}
131
132impl Framerate {
134 pub fn from_id(id: u8) -> Option<Framerate> {
148 FRAMERATES.iter().find(|f| f.id == id).copied()
149 }
150
151 pub fn id(&self) -> u8 {
153 self.id
154 }
155
156 pub fn numer(&self) -> u32 {
158 self.numer
159 }
160
161 pub fn denom(&self) -> u32 {
163 self.denom
164 }
165}
166
167pub(crate) struct Flags {
169 time_code: bool,
170 cc_data: bool,
171 svc_info: bool,
172 svc_info_start: bool,
173 svc_info_change: bool,
174 svc_info_complete: bool,
175 caption_service_active: bool,
176 _reserved: bool,
177}
178
179impl Flags {
180 const TIME_CODE_PRESENT: u8 = 0x80;
181 const CC_DATA_PRESENT: u8 = 0x40;
182 const SVC_INFO_PRESENT: u8 = 0x20;
183 const SVC_INFO_START: u8 = 0x10;
184 const SVC_INFO_CHANGE: u8 = 0x08;
185 const SVC_INFO_COMPLETE: u8 = 0x04;
186 const CAPTION_SERVICE_ACTIVE: u8 = 0x02;
187}
188
189impl From<u8> for Flags {
190 fn from(value: u8) -> Self {
191 Self {
192 time_code: (value & Self::TIME_CODE_PRESENT) > 0,
193 cc_data: (value & Self::CC_DATA_PRESENT) > 0,
194 svc_info: (value & Self::SVC_INFO_PRESENT) > 0,
195 svc_info_start: (value & Self::SVC_INFO_START) > 0,
196 svc_info_change: (value & Self::SVC_INFO_CHANGE) > 0,
197 svc_info_complete: (value & Self::SVC_INFO_COMPLETE) > 0,
198 caption_service_active: (value & Self::CAPTION_SERVICE_ACTIVE) > 0,
199 _reserved: (value & 0x01) > 0,
200 }
201 }
202}
203
204impl From<Flags> for u8 {
205 fn from(value: Flags) -> Self {
206 let mut ret = 0x1;
207 if value.time_code {
208 ret |= Flags::TIME_CODE_PRESENT;
209 }
210 if value.cc_data {
211 ret |= Flags::CC_DATA_PRESENT;
212 }
213 if value.svc_info {
214 ret |= Flags::SVC_INFO_PRESENT;
215 }
216 if value.svc_info_start {
217 ret |= Flags::SVC_INFO_START;
218 }
219 if value.svc_info_change {
220 ret |= Flags::SVC_INFO_CHANGE;
221 }
222 if value.svc_info_complete {
223 ret |= Flags::SVC_INFO_COMPLETE;
224 }
225 if value.caption_service_active {
226 ret |= Flags::CAPTION_SERVICE_ACTIVE;
227 }
228 ret
229 }
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234pub struct TimeCode {
235 hours: u8,
236 minutes: u8,
237 seconds: u8,
238 frames: u8,
239 field: bool,
240 drop_frame: bool,
241}
242
243impl TimeCode {
244 pub fn new(
259 hours: u8,
260 minutes: u8,
261 seconds: u8,
262 frames: u8,
263 field: bool,
264 drop_frame: bool,
265 ) -> Self {
266 Self {
267 hours,
268 minutes,
269 seconds,
270 frames,
271 field,
272 drop_frame,
273 }
274 }
275
276 pub fn hours(&self) -> u8 {
278 self.hours
279 }
280
281 pub fn minutes(&self) -> u8 {
283 self.minutes
284 }
285
286 pub fn seconds(&self) -> u8 {
288 self.seconds
289 }
290
291 pub fn frames(&self) -> u8 {
293 self.frames
294 }
295
296 pub fn field(&self) -> bool {
298 self.field
299 }
300
301 pub fn drop_frame(&self) -> bool {
303 self.drop_frame
304 }
305}
306
307pub use parser::CDPParser;
308pub use svc::{DigitalServiceEntry, FieldOrService, ServiceEntry, ServiceInfo};
309pub use writer::CDPWriter;
310
311#[cfg(test)]
312pub(crate) mod tests {
313 use super::*;
314 use cea708_types::{tables, Cea608};
315 use std::sync::OnceLock;
316
317 #[derive(Debug)]
318 pub(crate) struct ServiceData<'a> {
319 pub service_no: u8,
320 pub codes: &'a [tables::Code],
321 }
322
323 #[derive(Debug)]
324 pub(crate) struct CCPacketData<'a> {
325 pub sequence_no: u8,
326 pub services: &'a [ServiceData<'a>],
327 }
328
329 #[derive(Debug)]
330 pub(crate) struct CDPPacketData<'a> {
331 pub data: &'a [u8],
332 pub sequence_count: u16,
333 pub time_code: Option<TimeCode>,
334 pub packets: &'a [CCPacketData<'a>],
335 pub cea608: &'a [Cea608],
336 }
337
338 #[derive(Debug)]
339 pub(crate) struct TestCCData<'a> {
340 pub framerate: Framerate,
341 pub cdp_data: &'a [CDPPacketData<'a>],
342 }
343
344 pub fn test_init_log() {
345 static TRACING: OnceLock<()> = OnceLock::new();
346 TRACING.get_or_init(|| {
347 env_logger::init();
348 });
349 }
350}