Skip to content

Commit

Permalink
add support for both field 1 and 2 data
Browse files Browse the repository at this point in the history
  • Loading branch information
ystreet committed Apr 4, 2024
1 parent 789e279 commit 4ac1882
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 13 deletions.
97 changes: 88 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

use std::collections::VecDeque;

use tables::{Channel, Code, MidRow, PreambleAddressCode};
use tables::{Channel, Code, Field, MidRow, PreambleAddressCode};

#[macro_use]
extern crate log;
Expand Down Expand Up @@ -147,10 +147,13 @@ impl Cea608 {
/// Helper struct that has two purposes:
/// 1. Tracks the previous data for control code de-duplication
/// 2. Adds the last received channel to non control codes.
///
/// This object only keeps data for a single [`Field`]
#[derive(Debug, Default)]
pub struct Cea608State {
last_data: Option<[u8; 2]>,
last_channel: Option<Channel>,
last_received_field: Option<Field>,
}

impl Cea608State {
Expand All @@ -173,6 +176,9 @@ impl Cea608State {
[Code::Control(control_code), _] => {
let channel = control_code.channel();
self.last_channel = Some(channel);
if let Some(field) = control_code.field() {
self.last_received_field = Some(field);
}
Ok(Some(match control_code.code() {
tables::Control::MidRow(midrow) => Cea608::MidRowChange(channel, midrow),
tables::Control::PreambleAddress(preamble) => {
Expand Down Expand Up @@ -229,6 +235,12 @@ impl Cea608State {
}
}

/// The [`Field`] that some specific [`tables::Control`] codes referenced. Can be used to detect field
/// reversal of the incoming data.
pub fn last_received_field(&self) -> Option<Field> {
self.last_received_field
}

/// Reset the state to that of an initially constructed object.
pub fn reset(&mut self) {
*self = Self::default();
Expand Down Expand Up @@ -307,6 +319,55 @@ impl Cea608Writer {
}
}

/// A CEA-608 caption identifier unique within a CEA-608 stream
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Id {
CC1,
CC2,
CC3,
CC4,
// TODO: add Text1/2
}

impl Id {
/// The [`Field`] that this [`Id`] is contained within
pub fn field(&self) -> Field {
match self {
Self::CC1 | Self::CC2 => Field::ONE,
Self::CC3 | Self::CC4 => Field::TWO,
}
}

/// The caption [`Channel`] that this [`Id`] references
pub fn channel(&self) -> Channel {
match self {
Self::CC1 | Self::CC3 => Channel::ONE,
Self::CC2 | Self::CC4 => Channel::TWO,
}
}

/// Construct an [`Id`] from a [`Field`] and [`Channel`]
pub fn from_caption_field_channel(field: Field, channel: Channel) -> Self {
match (field, channel) {
(Field::ONE, Channel::ONE) => Self::CC1,
(Field::ONE, Channel::TWO) => Self::CC2,
(Field::TWO, Channel::ONE) => Self::CC3,
(Field::TWO, Channel::TWO) => Self::CC4,
}
}

/// Construct an [`Id`] from its integer value in the range [1, 4]
pub fn from_value(value: i8) -> Self {
match value {
1 => Self::CC1,
2 => Self::CC2,
3 => Self::CC3,
4 => Self::CC4,
_ => unreachable!(),
}
}
}

#[cfg(test)]
mod test {
use self::tables::ControlCode;
Expand All @@ -319,17 +380,20 @@ mod test {
test_init_log();
let mut data = vec![];
Code::Control(ControlCode::new(
Channel(true),
Field::ONE,
Channel::ONE,
tables::Control::EraseDisplayedMemory,
))
.write(&mut data)
.unwrap();
let mut state = Cea608State::default();
assert_eq!(
Ok(Some(Cea608::EraseDisplay(Channel(true)))),
Ok(Some(Cea608::EraseDisplay(Channel::ONE))),
state.decode([data[0], data[1]])
);
assert_eq!(state.last_received_field(), Some(Field::ONE));
assert_eq!(Ok(None), state.decode([data[0], data[1]]));
assert_eq!(state.last_received_field(), Some(Field::ONE));
}

#[test]
Expand All @@ -338,13 +402,18 @@ mod test {
let mut state = Cea608State::default();

let mut data = vec![];
Code::Control(ControlCode::new(Channel::ONE, tables::Control::RollUp2))
.write(&mut data)
.unwrap();
Code::Control(ControlCode::new(
Field::ONE,
Channel::ONE,
tables::Control::RollUp2,
))
.write(&mut data)
.unwrap();
assert_eq!(
Ok(Some(Cea608::NewMode(Channel::ONE, Mode::RollUp2))),
state.decode([data[0], data[1]])
);
assert_eq!(state.last_received_field(), Some(Field::ONE));

let mut data = vec![];
Code::LatinCapitalA.write(&mut data).unwrap();
Expand All @@ -357,15 +426,21 @@ mod test {
}))),
state.decode([data[0], 0x80])
);
assert_eq!(state.last_received_field(), Some(Field::ONE));

let mut data = vec![];
Code::Control(ControlCode::new(Channel::TWO, tables::Control::RollUp2))
.write(&mut data)
.unwrap();
Code::Control(ControlCode::new(
Field::TWO,
Channel::TWO,
tables::Control::RollUp2,
))
.write(&mut data)
.unwrap();
assert_eq!(
Ok(Some(Cea608::NewMode(Channel::TWO, Mode::RollUp2))),
state.decode([data[0], data[1]])
);
assert_eq!(state.last_received_field(), Some(Field::TWO));

let mut data = vec![];
Code::LatinCapitalA.write(&mut data).unwrap();
Expand Down Expand Up @@ -412,6 +487,7 @@ mod test {
let mut writer = Cea608Writer::default();
writer.push(Code::LatinLowerA);
writer.push(Code::Control(ControlCode::new(
Field::ONE,
Channel::ONE,
tables::Control::DegreeSign,
)));
Expand All @@ -426,6 +502,7 @@ mod test {
let mut writer = Cea608Writer::default();
writer.push(Code::LatinLowerA);
writer.push(Code::Control(ControlCode::new(
Field::ONE,
Channel::ONE,
tables::Control::Tilde,
)));
Expand All @@ -439,6 +516,7 @@ mod test {
test_init_log();
let mut writer = Cea608Writer::default();
writer.push(Code::Control(ControlCode::new(
Field::ONE,
Channel::ONE,
tables::Control::Tilde,
)));
Expand All @@ -452,6 +530,7 @@ mod test {
test_init_log();
let mut writer = Cea608Writer::default();
writer.push(Code::Control(ControlCode::new(
Field::ONE,
Channel::ONE,
tables::Control::DegreeSign,
)));
Expand Down
99 changes: 95 additions & 4 deletions src/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,33 @@ impl Channel {
}
}

/// The field that the control code references
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Field(pub(crate) bool);

impl Field {
/// Field 1
pub const ONE: Field = Field(true);
/// Field 2
pub const TWO: Field = Field(false);

/// The numerical identifier of this field
pub fn id(&self) -> u8 {
if self.0 {
1
} else {
2
}
}
}

/// A control code
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
// must be ordered the same as the byte values
// These codes start with 0x11 (channel 1, odd-parity: 0x91) or 0x19 (channel 2, odd-parity: 0x19)
pub struct ControlCode {
/// The field
pub field: Option<Field>,
/// The channel
pub channel: Channel,
/// The control code
Expand All @@ -55,15 +77,24 @@ pub struct ControlCode {

impl ControlCode {
/// Construct a new [`ControlCode`]
pub fn new(channel: Channel, control: Control) -> Self {
Self { channel, control }
pub fn new(field: Field, channel: Channel, control: Control) -> Self {
Self {
field: Some(field),
channel,
control,
}
}

/// The [`Channel`] for this [`ControlCode`]
pub fn channel(&self) -> Channel {
self.channel
}

/// The [`Field`] for this [`ControlCode`]
pub fn field(&self) -> Option<Field> {
self.field
}

/// The [`Control`] code for this [`ControlCode`]
pub fn code(&self) -> Control {
self.control
Expand Down Expand Up @@ -91,6 +122,9 @@ impl ControlCode {
}
}
}
if (0x20..=0x2f).contains(&data[1]) && data[0] == 0x14 && self.field == Some(Field::TWO) {
data[0] |= 0x01;
}
if self.channel == Channel::TWO {
data[0] |= 0x08;
}
Expand Down Expand Up @@ -860,10 +894,24 @@ fn check_odd_parity(byte: u8) -> bool {
fn parse_control_code(data: [u8; 2]) -> ControlCode {
let channel = data[0] & 0x08;
let underline = data[1] & 0x1 != 0;
let mut byte0 = data[0] & !0x08;
let field = if (0x20..=0x2f).contains(&data[1]) {
match data[0] & !0x08 {
0x14 => Some(Field::ONE),
0x15 => {
byte0 &= !0x01;
Some(Field::TWO)
}
_ => None,
}
} else {
None
};

ControlCode {
field,
channel: Channel(channel == 0),
control: match (data[0] & !0x08, data[1]) {
control: match (byte0, data[1]) {
(0x11, 0x20 | 0x21) => Control::MidRow(MidRow {
color: MidRowColor::Color(Color::White),
underline,
Expand Down Expand Up @@ -898,7 +946,7 @@ fn parse_control_code(data: [u8; 2]) -> ControlCode {
}),
(0x10..=0x19, 0x20..=0x3f) => {
let idx = CONTROL_MAP_TABLE
.binary_search_by_key(&[data[0] & !0x08, data[1]], |control_map| {
.binary_search_by_key(&[byte0, data[1]], |control_map| {
control_map.cea608_bytes
});
idx.map(|idx| CONTROL_MAP_TABLE[idx].control)
Expand Down Expand Up @@ -1130,6 +1178,7 @@ impl Code {
CONTROL_MAP_TABLE.iter().find_map(|control_map| {
if code_map.utf8 == Some(c) {
Some(Code::Control(ControlCode {
field: None,
channel,
control: control_map.control,
}))
Expand All @@ -1144,6 +1193,7 @@ impl Code {
/// Whether or not this code requires there to have a backspace prepended for correct display
pub fn needs_backspace(&self) -> bool {
let Code::Control(ControlCode {
field: _,
channel: _,
control,
}) = self
Expand Down Expand Up @@ -1317,6 +1367,7 @@ mod test {
for ty in tys {
for channel in [Channel::ONE, Channel::TWO] {
let preamble = Code::Control(ControlCode {
field: None,
channel,
control: Control::PreambleAddress(PreambleAddressCode {
row,
Expand Down Expand Up @@ -1353,6 +1404,7 @@ mod test {
for color in colors {
for channel in [Channel::ONE, Channel::TWO] {
let midrow = Code::Control(ControlCode {
field: None,
channel,
control: Control::MidRow(MidRow { underline, color }),
});
Expand All @@ -1366,4 +1418,43 @@ mod test {
}
}
}

#[test]
fn field2_control_to_from_bytes() {
let codes = [
Control::ResumeCaptionLoading,
Control::Backspace,
Control::AlarmOff,
Control::AlarmOn,
Control::DeleteToEndOfRow,
Control::RollUp2,
Control::RollUp3,
Control::RollUp4,
Control::FlashOn,
Control::ResumeDirectionCaptioning,
Control::TextRestart,
Control::ResumeTextDisplay,
Control::EraseDisplayedMemory,
Control::CarriageReturn,
Control::EraseNonDisplayedMemory,
Control::EndOfCaption,
];
for control in codes {
for field in [Field::ONE, Field::TWO] {
for channel in [Channel::ONE, Channel::TWO] {
let control = Code::Control(ControlCode {
field: Some(field),
channel,
control,
});
debug!("{control:?}");
let mut data = vec![];
control.write(&mut data).unwrap();
debug!("{data:x?}");
let parsed = Code::from_data([data[0], data[1]]).unwrap();
assert_eq!(control, parsed[0]);
}
}
}
}
}

0 comments on commit 4ac1882

Please sign in to comment.