cdp_types/
lib.rs

1// Copyright (C) 2023 Matthew Waters <matthew@centricular.com>
2//
3// Licensed under the MIT license <LICENSE-MIT> or
4// http://opensource.org/licenses/MIT>, at your option. This file may not be
5// copied, modified, or distributed except according to those terms.
6
7#![deny(missing_debug_implementations)]
8#![deny(missing_docs)]
9
10//! # cdp-types
11//!
12//! Provides the necessary infrastructure to read and write CDP (Caption Distribution Packet)
13//!
14//! The reference for this implementation is the `SMPTE 334-2-2007` specification.
15
16pub use cea708_types;
17
18mod parser;
19mod svc;
20mod writer;
21
22#[macro_use]
23extern crate log;
24
25/// Various possible errors when parsing data
26#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
27pub enum ParserError {
28    /// The length of the data does not match the length in the data
29    #[error("The length of the data ({actual}) does not match the advertised expected ({expected}) length")]
30    LengthMismatch {
31        /// Expected minimum size of the data
32        expected: usize,
33        /// The actual length of the data
34        actual: usize,
35    },
36    /// Some magic byte/s do not have the correct value
37    #[error("Some magic byte/s do not have the correct value")]
38    WrongMagic,
39    /// Unrecognied framerate value
40    #[error("The framerate specified is not known by this implementation")]
41    UnknownFramerate,
42    /// Some 'fixed' bits did not have the correct value
43    #[error("Some fixed bits did not have the correct value")]
44    InvalidFixedBits,
45    /// CEA-608 bytes were found after CEA-708 bytes
46    #[error("CEA-608 compatibility bytes were found after CEA-708 bytes")]
47    Cea608AfterCea708,
48    /// Failed to validate the checksum
49    #[error("The computed checksum value does not match the stored checksum value")]
50    ChecksumFailed,
51    /// Sequence count differs between the header and the footer.  Usually indicates this packet was
52    /// spliced together incorrectly.
53    #[error("The sequence count differs between the header and the footer")]
54    SequenceCountMismatch,
55    /// The service information contains conflicting service numbers.
56    #[error("The service descriptor has different values")]
57    ServiceNumberMismatch,
58    /// The service number is not valid.
59    #[error("The service number is not valid")]
60    InvalidServiceNumber,
61    /// The service descriptor contains a different set of flags to the CDP.
62    #[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/// A framerate as found in a CDP.
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub struct Framerate {
127    id: u8,
128    numer: u32,
129    denom: u32,
130}
131
132/// A CDP framerate.
133impl Framerate {
134    /// Create a [`Framerate`] from an identifier as found in a CDP.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// # use cdp_types::Framerate;
140    /// let frame = Framerate::from_id(0x8).unwrap();
141    /// assert_eq!(frame.id(), 0x8);
142    /// assert_eq!(frame.numer(), 60);
143    /// assert_eq!(frame.denom(), 1);
144    ///
145    /// assert!(Framerate::from_id(0x0).is_none());
146    /// ```
147    pub fn from_id(id: u8) -> Option<Framerate> {
148        FRAMERATES.iter().find(|f| f.id == id).copied()
149    }
150
151    /// The identifier for this [`Framerate`] in a CDP.
152    pub fn id(&self) -> u8 {
153        self.id
154    }
155
156    /// The numerator component of this [`Framerate`]
157    pub fn numer(&self) -> u32 {
158        self.numer
159    }
160
161    /// The denominator component of this [`Framerate`]
162    pub fn denom(&self) -> u32 {
163        self.denom
164    }
165}
166
167/// A set of flags available in a CDP.
168pub(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/// A time code as available in a CDP.
233#[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    /// Construct a new [`TimeCode`] value.
245    ///
246    /// # Examples
247    ///
248    /// ```
249    /// # use cdp_types::TimeCode;
250    /// let tc = TimeCode::new(1, 2, 3, 4, true, false);
251    /// assert_eq!(tc.hours(), 1);
252    /// assert_eq!(tc.minutes(), 2);
253    /// assert_eq!(tc.seconds(), 3);
254    /// assert_eq!(tc.frames(), 4);
255    /// assert!(tc.field());
256    /// assert!(!tc.drop_frame());
257    /// ```
258    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    /// The hour value of this [`TimeCode`].
277    pub fn hours(&self) -> u8 {
278        self.hours
279    }
280
281    /// The minute value of this [`TimeCode`].
282    pub fn minutes(&self) -> u8 {
283        self.minutes
284    }
285
286    /// The second value of this [`TimeCode`].
287    pub fn seconds(&self) -> u8 {
288        self.seconds
289    }
290
291    /// The frame value of this [`TimeCode`].
292    pub fn frames(&self) -> u8 {
293        self.frames
294    }
295
296    /// The field value of this [`TimeCode`].
297    pub fn field(&self) -> bool {
298        self.field
299    }
300
301    /// The drop frame value of this [`TimeCode`].
302    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}