Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some examples to documentation #9

Merged
merged 7 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 77 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ pub struct Framerate {
/// A CDP framerate.
impl Framerate {
/// Create a [`Framerate`] from an identifier as found in a CDP.
///
/// # Examples
///
/// ```
/// # use cdp_types::Framerate;
/// let frame = Framerate::from_id(0x8).unwrap();
/// assert_eq!(frame.id(), 0x8);
/// assert_eq!(frame.numer(), 60);
/// assert_eq!(frame.denom(), 1);
///
/// assert!(Framerate::from_id(0x0).is_none());
/// ```
pub fn from_id(id: u8) -> Option<Framerate> {
FRAMERATES.iter().find(|f| f.id == id).copied()
}
Expand Down Expand Up @@ -224,10 +236,74 @@ pub struct TimeCode {
minutes: u8,
seconds: u8,
frames: u8,
field: u8,
field: bool,
drop_frame: bool,
}

impl TimeCode {
/// Construct a new [`TimeCode`] value.
///
/// # Examples
///
/// ```
/// # use cdp_types::TimeCode;
/// let tc = TimeCode::new(1, 2, 3, 4, true, false);
/// assert_eq!(tc.hours(), 1);
/// assert_eq!(tc.minutes(), 2);
/// assert_eq!(tc.seconds(), 3);
/// assert_eq!(tc.frames(), 4);
/// assert!(tc.field());
/// assert!(!tc.drop_frame());
/// ```
pub fn new(
hours: u8,
minutes: u8,
seconds: u8,
frames: u8,
field: bool,
drop_frame: bool,
) -> Self {
Self {
hours,
minutes,
seconds,
frames,
field,
drop_frame,
}
}

/// The hour value of this [`TimeCode`].
pub fn hours(&self) -> u8 {
self.hours
}

/// The minute value of this [`TimeCode`].
pub fn minutes(&self) -> u8 {
self.minutes
}

/// The second value of this [`TimeCode`].
pub fn seconds(&self) -> u8 {
self.seconds
}

/// The frame value of this [`TimeCode`].
pub fn frames(&self) -> u8 {
self.frames
}

/// The field value of this [`TimeCode`].
pub fn field(&self) -> bool {
self.field
}

/// The drop frame value of this [`TimeCode`].
pub fn drop_frame(&self) -> bool {
self.drop_frame
}
}

pub use parser::CDPParser;
pub use svc::{DigitalServiceEntry, FieldOrService, ServiceEntry, ServiceInfo};
pub use writer::CDPWriter;
Expand Down
124 changes: 119 additions & 5 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,76 @@
use crate::{Flags, Framerate, ParserError, ServiceInfo, TimeCode};

/// Parses CDP packets.
#[derive(Debug, Default)]
///
/// # Examples
///
/// ```
/// # use cdp_types::*;
/// # use cdp_types::cea708_types::Cea608;
/// let mut parser = CDPParser::new();
/// let data = [
/// 0x96, 0x69, // magic
/// 0x27, // cdp_len
/// 0x3f, // framerate
/// 0x80 | 0x40 | 0x20 | 0x10 | 0x04 | 0x02 | 0x01, // flags
/// 0x12, 0x34, // sequence counter
/// 0x71, // time code id
/// 0xc0 | 0x17, // hours
/// 0x80 | 0x59, // minutes
/// 0x80 | 0x57, // seconds
/// 0x80 | 0x18, // frames
/// 0x72, // cc_data id
/// 0xe0 | 0x04, // cc_count
/// 0xFC, 0x20, 0x41, // CEA608 field 1
/// 0xFD, 0x42, 0x43, // CEA608 field 2
/// 0xFF, 0x02, 0x21, // start CEA708 data
/// 0xFE, 0x41, 0x00,
/// 0x73, // svc_info id
/// 0x80 | 0x40 | 0x10 | 0x01, // reserved | start | change | complete | count
/// 0x80, // reserved | service number
/// b'e', b'n', b'g', // language
/// 0x40 | 0x3e, // is_digital | reserved | field/service
/// 0x3f, // reader | wide | reserved
/// 0xff, // reserved
/// 0x74, // cdp footer
/// 0x12, 0x34, // sequence counter
/// 0xc4, // checksum
/// ];
/// parser.parse(&data).unwrap();
///
/// assert_eq!(parser.sequence(), 0x1234);
/// assert_eq!(parser.framerate(), Framerate::from_id(0x3));
///
/// // Service information
/// let service_info = parser.service_info().unwrap();
/// assert!(service_info.is_start());
/// assert!(!service_info.is_change());
/// assert!(service_info.is_complete());
/// let entries = service_info.services();
/// assert_eq!(entries[0].language(), [b'e', b'n', b'g']);
/// let FieldOrService::Field(field) = entries[0].service() else {
/// unreachable!();
/// };
/// assert!(field);
///
/// // Time code information
/// let time_code = parser.time_code().unwrap();
/// assert_eq!(time_code.hours(), 17);
/// assert_eq!(time_code.minutes(), 59);
/// assert_eq!(time_code.seconds(), 57);
/// assert_eq!(time_code.frames(), 18);
/// assert!(time_code.field());
/// assert!(time_code.drop_frame());
///
/// // CEA-708 cc_data
/// let packet = parser.pop_packet().unwrap();
/// assert_eq!(packet.sequence_no(), 0);
///
/// // CEA-608 data
/// let cea608 = parser.cea608().unwrap();
/// assert_eq!(cea608, &[Cea608::Field1(0x20, 0x41), Cea608::Field2(0x42, 0x43)]);
/// ```
#[derive(Debug)]
pub struct CDPParser {
cc_data_parser: cea708_types::CCDataParser,
time_code: Option<TimeCode>,
Expand All @@ -16,6 +85,20 @@ pub struct CDPParser {
sequence: u16,
}

impl Default for CDPParser {
fn default() -> Self {
let mut cc_data_parser = cea708_types::CCDataParser::default();
cc_data_parser.handle_cea608();
Self {
cc_data_parser,
time_code: None,
framerate: None,
service_info: None,
sequence: 0,
}
}
}

impl CDPParser {
const MIN_PACKET_LEN: usize = 11;
const TIME_CODE_ID: u8 = 0x71;
Expand Down Expand Up @@ -88,7 +171,7 @@ impl CDPParser {
let minutes = ((data[idx] & 0x70) >> 4) * 10 + (data[idx] & 0x0f);

idx += 1;
let field = (data[idx] & 0x80) >> 7;
let field = ((data[idx] & 0x80) >> 7) > 0;
let seconds = ((data[idx] & 0x70) >> 4) * 10 + (data[idx] & 0x0f);

idx += 1;
Expand Down Expand Up @@ -288,9 +371,9 @@ mod test {
use super::*;
use crate::tests::*;
use crate::*;
use cea708_types::tables;
use cea708_types::{tables, Cea608};

static PARSE_CDP: [TestCCData; 4] = [
static PARSE_CDP: [TestCCData; 5] = [
// simple packet with cc_data and a time code
TestCCData {
framerate: FRAMERATES[2],
Expand Down Expand Up @@ -327,7 +410,7 @@ mod test {
minutes: 59,
seconds: 57,
frames: 18,
field: 1,
field: true,
drop_frame: true,
}),
packets: &[CCPacketData {
Expand Down Expand Up @@ -429,6 +512,37 @@ mod test {
cea608: &[],
}],
},
// simple packet with CEA-608 data
TestCCData {
framerate: FRAMERATES[2],
cdp_data: &[CDPPacketData {
data: &[
0x96, // magic
0x69,
0x13, // cdp_len
0x3f, // framerate
0x40 | 0x01, // flags
0x12, // sequence counter
0x34,
0x72, // cc_data id
0xe0 | 0x02, // cc_count
0xFC,
0x20,
0x41,
0xFD,
0x42,
0x80,
0x74, // cdp footer
0x12,
0x34,
0xFE, // checksum
],
sequence_count: 0x1234,
time_code: None,
packets: &[],
cea608: &[Cea608::Field1(0x20, 0x41), Cea608::Field2(0x42, 0x80)],
}],
},
];

#[test]
Expand Down
91 changes: 88 additions & 3 deletions src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,91 @@

use crate::{Flags, Framerate, ServiceInfo, TimeCode};

/// A struct for writing cc_data packets
/// A struct for writing a stream of CDPs
///
/// # Examples
///
/// ```
/// # use cdp_types::*;
/// use cdp_types::cea708_types::{Cea608, DTVCCPacket, Service, tables};
///
/// let mut writer = CDPWriter::new();
/// writer.set_sequence_count(3);
/// let mut packet = DTVCCPacket::new(0);
/// let mut service = Service::new(1);
/// service.push_code(&tables::Code::LatinCapitalA).unwrap();
/// packet.push_service(service).unwrap();
/// writer.push_packet(packet);
///
/// writer.push_cea608(Cea608::Field1(0x41, 0x80));
///
/// writer.set_time_code(Some(TimeCode::new(1, 2, 3, 4, true, false)));
///
/// let mut service_info = ServiceInfo::default();
/// service_info.set_start(true);
/// service_info.set_complete(true);
/// let entry = ServiceEntry::new([b'e', b'n', b'g'], FieldOrService::Field(true));
/// service_info.add_service(entry);
/// let entry = ServiceEntry::new(
/// [b'e', b'n', b'g'],
/// FieldOrService::Service(DigitalServiceEntry::new(1, false, true))
/// );
/// service_info.add_service(entry);
/// writer.set_service_info(Some(service_info));
///
/// let framerate = Framerate::from_id(4).unwrap();
/// let mut data = vec![];
/// writer.write(framerate, &mut data).unwrap();
///
/// let expected = [
/// 0x96, 0x69, // magic
/// 0x5e, // CDP length
/// 0x4f, // framerate
/// 0xf7, // flags
/// 0x00, 0x03, // sequence counter
/// 0x71, // time code start
/// 0xc1, // hours
/// 0x82, // minutes
/// 0x83, // seconds
/// 0x04, // frames
/// 0x72, // cc_data id
/// 0xf4, // cc_data count
/// 0xfc, 0x41, 0x80, // CEA-608 field 1
/// 0xf9, 0x80, 0x80, // CEA-608 field 2
/// 0xff, 0x02, 0x21, // CEA-708 start
/// 0xfe, 0x41, 0x00, // CEA-708 continued
/// 0xfa, 0x00, 0x00, // CEA-708 padding
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0xfa, 0x00, 0x00, // .
/// 0x73, // service info id
/// 0xd2, // start | change | complete | count
/// 0x80, // service no
/// b'e', b'n', b'g', // language
/// 0x7e, // is_digital | ignored
/// 0x3f, 0xff, // ignored | reserved
/// 0x81, // service no
/// b'e', b'n', b'g', // language
/// 0xc1, // is_digital | service no
/// 0x7f, 0xff, // easy_reader | wide_aspect_ratio | reserved
/// 0x74, // footer id
/// 0x00, 0x03, // sequence counter
/// 0xd6, // checksum
/// ];
/// assert_eq!(&data, &expected);
/// ```
#[derive(Debug)]
pub struct CDPWriter {
cc_data: cea708_types::CCDataWriter,
Expand Down Expand Up @@ -65,6 +149,7 @@ impl CDPWriter {
self.cc_data.flush();
self.time_code = None;
self.sequence_count = 0;
self.service_info = None;
}

/// Write the next CDP packet taking the next relevant CEA-608 byte pairs and
Expand Down Expand Up @@ -130,7 +215,7 @@ impl CDPWriter {
0x71,
0xc0 | ((time_code.hours / 10) << 4) | (time_code.hours % 10),
0x80 | ((time_code.minutes / 10) << 4) | (time_code.minutes % 10),
((time_code.field & 0x1) << 7)
if time_code.field { 0x80 } else { 0x00 }
| ((time_code.seconds / 10) << 4)
| (time_code.seconds % 10),
if time_code.drop_frame { 0x80 } else { 0x0 }
Expand Down Expand Up @@ -286,7 +371,7 @@ mod test {
minutes: 59,
seconds: 57,
frames: 18,
field: 1,
field: true,
drop_frame: true,
}),
packets: &[CCPacketData {
Expand Down
Loading