use std::collections::VecDeque;
use tables::{Channel, Code, Field, MidRow, PreambleAddressCode};
#[macro_use]
extern crate log;
pub mod tables;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum ParserError {
#[error("Invalid parity")]
InvalidParity,
#[error("Length of the data ({actual}) does not match the expected length ({expected})")]
LengthMismatch {
expected: usize,
actual: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum WriterError {
#[error("Writing would overflow by {0} bytes")]
WouldOverflow(usize),
#[error("Read only resource")]
ReadOnly,
}
impl From<tables::CodeError> for ParserError {
fn from(err: tables::CodeError) -> Self {
match err {
tables::CodeError::LengthMismatch { expected, actual } => {
ParserError::LengthMismatch { expected, actual }
}
tables::CodeError::InvalidParity => ParserError::InvalidParity,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Mode {
PopOn,
PaintOn,
RollUp2,
RollUp3,
RollUp4,
}
impl Mode {
pub fn is_rollup(&self) -> bool {
matches!(self, Self::RollUp2 | Self::RollUp3 | Self::RollUp4)
}
pub fn rollup_rows(&self) -> Option<u8> {
match self {
Self::RollUp2 => Some(2),
Self::RollUp3 => Some(3),
Self::RollUp4 => Some(4),
_ => None,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Text {
pub needs_backspace: bool,
pub char1: Option<char>,
pub char2: Option<char>,
pub channel: Channel,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Cea608 {
Text(Text),
NewMode(Channel, Mode),
EraseDisplay(Channel),
EraseNonDisplay(Channel),
CarriageReturn(Channel),
Backspace(Channel),
EndOfCaption(Channel),
TabOffset(Channel, u8),
DeleteToEndOfRow(Channel),
Preamble(Channel, PreambleAddressCode),
MidRowChange(Channel, MidRow),
}
impl Cea608 {
pub fn channel(&self) -> Channel {
match self {
Self::Text(text) => text.channel,
Self::NewMode(chan, _) => *chan,
Self::EraseDisplay(chan) => *chan,
Self::EraseNonDisplay(chan) => *chan,
Self::CarriageReturn(chan) => *chan,
Self::Backspace(chan) => *chan,
Self::EndOfCaption(chan) => *chan,
Self::TabOffset(chan, _) => *chan,
Self::Preamble(chan, _) => *chan,
Self::MidRowChange(chan, _) => *chan,
Self::DeleteToEndOfRow(chan) => *chan,
}
}
}
#[derive(Debug, Default)]
pub struct Cea608State {
last_data: Option<[u8; 2]>,
last_channel: Option<Channel>,
last_received_field: Option<Field>,
}
impl Cea608State {
pub fn decode(&mut self, data: [u8; 2]) -> Result<Option<Cea608>, ParserError> {
trace!("decoding {data:x?}, last data {:x?}", self.last_data);
let code = Code::from_data(data)?;
if Some(data) == self.last_data {
if let Code::Control(_control) = code[0] {
debug!("Skipping duplicate");
return Ok(None);
}
}
self.last_data = Some(data);
match code {
[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) => {
Cea608::Preamble(channel, preamble)
}
tables::Control::EraseDisplayedMemory => Cea608::EraseDisplay(channel),
tables::Control::EraseNonDisplayedMemory => Cea608::EraseNonDisplay(channel),
tables::Control::CarriageReturn => Cea608::CarriageReturn(channel),
tables::Control::Backspace => Cea608::Backspace(channel),
tables::Control::EndOfCaption => Cea608::EndOfCaption(channel),
tables::Control::RollUp2 => Cea608::NewMode(channel, Mode::RollUp2),
tables::Control::RollUp3 => Cea608::NewMode(channel, Mode::RollUp3),
tables::Control::RollUp4 => Cea608::NewMode(channel, Mode::RollUp4),
tables::Control::ResumeDirectionCaptioning => {
Cea608::NewMode(channel, Mode::PaintOn)
}
tables::Control::ResumeCaptionLoading => Cea608::NewMode(channel, Mode::PopOn),
tables::Control::TabOffset1 => Cea608::TabOffset(channel, 1),
tables::Control::TabOffset2 => Cea608::TabOffset(channel, 2),
tables::Control::TabOffset3 => Cea608::TabOffset(channel, 3),
tables::Control::DeleteToEndOfRow => Cea608::DeleteToEndOfRow(channel),
_ => {
if let Some(char) = code[0].char() {
Cea608::Text(Text {
needs_backspace: code[0].needs_backspace(),
char1: Some(char),
char2: None,
channel,
})
} else {
return Ok(None);
}
}
}))
}
_ => {
let Some(channel) = self.last_channel else {
return Ok(None);
};
let char1 = code[0].char();
let char2 = code[1].char();
if char1.is_some() || char2.is_some() {
Ok(Some(Cea608::Text(Text {
needs_backspace: false,
char1,
char2,
channel,
})))
} else {
Ok(None)
}
}
}
}
pub fn last_received_field(&self) -> Option<Field> {
self.last_received_field
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
#[derive(Debug, Default)]
pub struct Cea608Writer {
pending: VecDeque<Code>,
pending_code: Option<Code>,
}
impl Cea608Writer {
pub fn push(&mut self, code: Code) {
self.pending.push_front(code)
}
pub fn pop(&mut self) -> [u8; 2] {
let mut ret = [0x80; 2];
let mut prev = None::<Code>;
if let Some(code) = self.pending_code.take() {
code.write_into(&mut ret);
return ret;
}
while let Some(code) = self.pending.pop_back() {
if let Some(prev) = prev {
if code.byte_len() == 1 {
let mut data = [0; 2];
prev.write_into(&mut ret);
code.write_into(&mut data);
ret[1] = data[0];
return ret;
} else if code.needs_backspace() {
self.pending_code = Some(code);
let mut data = [0; 2];
prev.write_into(&mut ret);
Code::Space.write_into(&mut data);
ret[1] = data[0];
return ret;
} else {
self.pending_code = Some(code);
prev.write_into(&mut ret);
return ret;
}
} else if code.needs_backspace() {
self.pending_code = Some(code);
Code::Space.write_into(&mut ret);
return ret;
} else if code.byte_len() == 1 {
prev = Some(code);
} else {
code.write_into(&mut ret);
return ret;
}
}
if let Some(prev) = prev {
prev.write_into(&mut ret);
}
ret
}
pub fn n_codes(&self) -> usize {
self.pending.len() + if self.pending_code.is_some() { 1 } else { 0 }
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Id {
CC1,
CC2,
CC3,
CC4,
}
impl Id {
pub fn field(&self) -> Field {
match self {
Self::CC1 | Self::CC2 => Field::ONE,
Self::CC3 | Self::CC4 => Field::TWO,
}
}
pub fn channel(&self) -> Channel {
match self {
Self::CC1 | Self::CC3 => Channel::ONE,
Self::CC2 | Self::CC4 => Channel::TWO,
}
}
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,
}
}
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;
use super::*;
use crate::tests::*;
#[test]
fn state_duplicate_control() {
test_init_log();
let mut data = vec![];
Code::Control(ControlCode::new(
Field::ONE,
Channel::ONE,
tables::Control::EraseDisplayedMemory,
))
.write(&mut data)
.unwrap();
let mut state = Cea608State::default();
assert_eq!(
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]
fn state_text_after_control() {
test_init_log();
let mut state = Cea608State::default();
let mut data = vec![];
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();
assert_eq!(
Ok(Some(Cea608::Text(Text {
needs_backspace: false,
char1: Some('A'),
char2: None,
channel: Channel::ONE,
}))),
state.decode([data[0], 0x80])
);
assert_eq!(state.last_received_field(), Some(Field::ONE));
let mut data = vec![];
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();
assert_eq!(
Ok(Some(Cea608::Text(Text {
needs_backspace: false,
char1: Some('A'),
char2: None,
channel: Channel::TWO,
}))),
state.decode([data[0], 0x80])
);
}
#[test]
fn writer_padding() {
test_init_log();
let mut writer = Cea608Writer::default();
assert_eq!(writer.pop(), [0x80, 0x80]);
}
#[test]
fn writer_single_byte_code() {
test_init_log();
let mut writer = Cea608Writer::default();
writer.push(Code::LatinLowerA);
assert_eq!(writer.pop(), [0x61, 0x80]);
assert_eq!(writer.pop(), [0x80, 0x80]);
}
#[test]
fn writer_two_single_byte_codes() {
test_init_log();
let mut writer = Cea608Writer::default();
writer.push(Code::LatinLowerA);
writer.push(Code::LatinLowerB);
assert_eq!(writer.pop(), [0x61, 0x62]);
assert_eq!(writer.pop(), [0x80, 0x80]);
}
#[test]
fn writer_single_byte_and_control() {
test_init_log();
let mut writer = Cea608Writer::default();
writer.push(Code::LatinLowerA);
writer.push(Code::Control(ControlCode::new(
Field::ONE,
Channel::ONE,
tables::Control::DegreeSign,
)));
assert_eq!(writer.pop(), [0x61, 0x80]);
assert_eq!(writer.pop(), [0x91, 0x31]);
assert_eq!(writer.pop(), [0x80, 0x80]);
}
#[test]
fn writer_single_byte_and_control_needing_backspace() {
test_init_log();
let mut writer = Cea608Writer::default();
writer.push(Code::LatinLowerA);
writer.push(Code::Control(ControlCode::new(
Field::ONE,
Channel::ONE,
tables::Control::Tilde,
)));
assert_eq!(writer.pop(), [0x61, 0x20]);
assert_eq!(writer.pop(), [0x13, 0x2f]);
assert_eq!(writer.pop(), [0x80, 0x80]);
}
#[test]
fn writer_control_needing_backspace() {
test_init_log();
let mut writer = Cea608Writer::default();
writer.push(Code::Control(ControlCode::new(
Field::ONE,
Channel::ONE,
tables::Control::Tilde,
)));
assert_eq!(writer.pop(), [0x20, 0x80]);
assert_eq!(writer.pop(), [0x13, 0x2f]);
assert_eq!(writer.pop(), [0x80, 0x80]);
}
#[test]
fn writer_control() {
test_init_log();
let mut writer = Cea608Writer::default();
writer.push(Code::Control(ControlCode::new(
Field::ONE,
Channel::ONE,
tables::Control::DegreeSign,
)));
assert_eq!(writer.pop(), [0x91, 0x31]);
assert_eq!(writer.pop(), [0x80, 0x80]);
}
}
#[cfg(test)]
pub(crate) mod tests {
use once_cell::sync::Lazy;
static TRACING: Lazy<()> = Lazy::new(|| {
env_logger::init();
});
pub fn test_init_log() {
Lazy::force(&TRACING);
}
}