1#![deny(missing_debug_implementations)]
8#![deny(missing_docs)]
9
10use std::collections::VecDeque;
17
18use tables::{Channel, Code, Field, MidRow, PreambleAddressCode};
19
20#[macro_use]
21extern crate log;
22
23pub mod tables;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
27pub enum ParserError {
28 #[error("Invalid parity")]
30 InvalidParity,
31 #[error("Length of the data ({actual}) does not match the expected length ({expected})")]
33 LengthMismatch {
34 expected: usize,
36 actual: usize,
38 },
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
43pub enum WriterError {
44 #[error("Writing would overflow by {0} bytes")]
46 WouldOverflow(usize),
47 #[error("Read only resource")]
49 ReadOnly,
50}
51
52impl From<tables::CodeError> for ParserError {
53 fn from(err: tables::CodeError) -> Self {
54 match err {
55 tables::CodeError::LengthMismatch { expected, actual } => {
56 ParserError::LengthMismatch { expected, actual }
57 }
58 tables::CodeError::InvalidParity => ParserError::InvalidParity,
59 }
60 }
61}
62
63#[derive(Debug, Copy, Clone, PartialEq, Eq)]
65pub enum Mode {
66 PopOn,
69 PaintOn,
71 RollUp2,
74 RollUp3,
77 RollUp4,
80}
81
82impl Mode {
83 pub fn is_rollup(&self) -> bool {
85 matches!(self, Self::RollUp2 | Self::RollUp3 | Self::RollUp4)
86 }
87
88 pub fn rollup_rows(&self) -> Option<u8> {
90 match self {
91 Self::RollUp2 => Some(2),
92 Self::RollUp3 => Some(3),
93 Self::RollUp4 => Some(4),
94 _ => None,
95 }
96 }
97}
98
99#[derive(Debug, Copy, Clone, PartialEq, Eq)]
101pub struct Text {
102 pub needs_backspace: bool,
104 pub char1: Option<char>,
106 pub char2: Option<char>,
108 pub channel: Channel,
110}
111
112#[derive(Debug, Copy, Clone, PartialEq, Eq)]
114pub enum Cea608 {
115 Text(Text),
117 NewMode(Channel, Mode),
119 EraseDisplay(Channel),
121 EraseNonDisplay(Channel),
123 CarriageReturn(Channel),
125 Backspace(Channel),
127 EndOfCaption(Channel),
130 TabOffset(Channel, u8),
132 DeleteToEndOfRow(Channel),
134 Preamble(Channel, PreambleAddressCode),
136 MidRowChange(Channel, MidRow),
138}
139
140impl Cea608 {
141 pub fn channel(&self) -> Channel {
143 match self {
144 Self::Text(text) => text.channel,
145 Self::NewMode(chan, _) => *chan,
146 Self::EraseDisplay(chan) => *chan,
147 Self::EraseNonDisplay(chan) => *chan,
148 Self::CarriageReturn(chan) => *chan,
149 Self::Backspace(chan) => *chan,
150 Self::EndOfCaption(chan) => *chan,
151 Self::TabOffset(chan, _) => *chan,
152 Self::Preamble(chan, _) => *chan,
153 Self::MidRowChange(chan, _) => *chan,
154 Self::DeleteToEndOfRow(chan) => *chan,
155 }
156 }
157
158 pub fn into_code(&self, field: Field) -> [Code; 2] {
160 match self {
161 Self::Text(text) => {
162 let mut ret = [Code::NUL, Code::NUL];
163 if let Some(char1) = text.char1 {
164 ret[0] = Code::from_char(char1, text.channel).unwrap();
165 }
166 if let Some(char2) = text.char2 {
167 ret[1] = Code::from_char(char2, text.channel).unwrap();
168 }
169 ret
170 }
171 Self::NewMode(chan, mode) => [
172 Code::Control(tables::ControlCode {
173 field: Some(field),
174 channel: *chan,
175 control: match mode {
176 Mode::RollUp2 => tables::Control::RollUp2,
177 Mode::RollUp3 => tables::Control::RollUp3,
178 Mode::RollUp4 => tables::Control::RollUp4,
179 Mode::PaintOn => tables::Control::ResumeDirectionCaptioning,
180 Mode::PopOn => tables::Control::ResumeCaptionLoading,
181 },
182 }),
183 Code::NUL,
184 ],
185 Self::EraseDisplay(chan) => [
186 Code::Control(tables::ControlCode {
187 field: Some(field),
188 channel: *chan,
189 control: tables::Control::EraseDisplayedMemory,
190 }),
191 Code::NUL,
192 ],
193 Self::EraseNonDisplay(chan) => [
194 Code::Control(tables::ControlCode {
195 field: Some(field),
196 channel: *chan,
197 control: tables::Control::EraseNonDisplayedMemory,
198 }),
199 Code::NUL,
200 ],
201 Self::CarriageReturn(chan) => [
202 Code::Control(tables::ControlCode {
203 field: Some(field),
204 channel: *chan,
205 control: tables::Control::CarriageReturn,
206 }),
207 Code::NUL,
208 ],
209 Self::Backspace(chan) => [
210 Code::Control(tables::ControlCode {
211 field: Some(field),
212 channel: *chan,
213 control: tables::Control::Backspace,
214 }),
215 Code::NUL,
216 ],
217 Self::EndOfCaption(chan) => [
218 Code::Control(tables::ControlCode {
219 field: Some(field),
220 channel: *chan,
221 control: tables::Control::EndOfCaption,
222 }),
223 Code::NUL,
224 ],
225 Self::TabOffset(chan, count) => [
226 Code::Control(tables::ControlCode {
227 field: Some(field),
228 channel: *chan,
229 control: match count {
230 1 => tables::Control::TabOffset1,
231 2 => tables::Control::TabOffset2,
232 3 => tables::Control::TabOffset3,
233 _ => unreachable!(),
234 },
235 }),
236 Code::NUL,
237 ],
238 Self::Preamble(chan, preamble) => [
239 Code::Control(tables::ControlCode {
240 field: Some(field),
241 channel: *chan,
242 control: tables::Control::PreambleAddress(*preamble),
243 }),
244 Code::NUL,
245 ],
246 Self::MidRowChange(chan, midrow) => [
247 Code::Control(tables::ControlCode {
248 field: Some(field),
249 channel: *chan,
250 control: tables::Control::MidRow(*midrow),
251 }),
252 Code::NUL,
253 ],
254 Self::DeleteToEndOfRow(chan) => [
255 Code::Control(tables::ControlCode {
256 field: Some(field),
257 channel: *chan,
258 control: tables::Control::DeleteToEndOfRow,
259 }),
260 Code::NUL,
261 ],
262 }
263 }
264}
265
266#[derive(Debug, Default)]
272pub struct Cea608State {
273 last_data: Option<[u8; 2]>,
274 last_channel: Option<Channel>,
275 last_received_field: Option<Field>,
276}
277
278impl Cea608State {
279 pub fn decode(&mut self, data: [u8; 2]) -> Result<Option<Cea608>, ParserError> {
281 trace!("decoding {data:x?}, last data {:x?}", self.last_data);
282 let code = Code::from_data(data)?;
283
284 if Some(data) == self.last_data {
285 if let Code::Control(_control) = code[0] {
286 debug!("Skipping duplicate");
287 return Ok(None);
288 }
289 }
290 self.last_data = Some(data);
291 trace!("decoded into codes {code:x?}");
292
293 match code {
296 [Code::Control(control_code), _] => {
297 let channel = control_code.channel();
298 self.last_channel = Some(channel);
299 if let Some(field) = control_code.field() {
300 self.last_received_field = Some(field);
301 }
302 Ok(Some(match control_code.code() {
303 tables::Control::MidRow(midrow) => Cea608::MidRowChange(channel, midrow),
304 tables::Control::PreambleAddress(preamble) => {
305 Cea608::Preamble(channel, preamble)
306 }
307 tables::Control::EraseDisplayedMemory => Cea608::EraseDisplay(channel),
308 tables::Control::EraseNonDisplayedMemory => Cea608::EraseNonDisplay(channel),
309 tables::Control::CarriageReturn => Cea608::CarriageReturn(channel),
310 tables::Control::Backspace => Cea608::Backspace(channel),
311 tables::Control::EndOfCaption => Cea608::EndOfCaption(channel),
312 tables::Control::RollUp2 => Cea608::NewMode(channel, Mode::RollUp2),
313 tables::Control::RollUp3 => Cea608::NewMode(channel, Mode::RollUp3),
314 tables::Control::RollUp4 => Cea608::NewMode(channel, Mode::RollUp4),
315 tables::Control::ResumeDirectionCaptioning => {
316 Cea608::NewMode(channel, Mode::PaintOn)
317 }
318 tables::Control::ResumeCaptionLoading => Cea608::NewMode(channel, Mode::PopOn),
319 tables::Control::TabOffset1 => Cea608::TabOffset(channel, 1),
320 tables::Control::TabOffset2 => Cea608::TabOffset(channel, 2),
321 tables::Control::TabOffset3 => Cea608::TabOffset(channel, 3),
322 tables::Control::DeleteToEndOfRow => Cea608::DeleteToEndOfRow(channel),
323 _ => {
325 if let Some(char) = code[0].char() {
326 Cea608::Text(Text {
327 needs_backspace: code[0].needs_backspace(),
328 char1: Some(char),
329 char2: None,
330 channel,
331 })
332 } else {
333 return Ok(None);
334 }
335 }
336 }))
337 }
338 _ => {
339 let Some(channel) = self.last_channel else {
340 return Ok(None);
341 };
342 let char1 = code[0].char();
343 let char2 = code[1].char();
344 if char1.is_some() || char2.is_some() {
345 Ok(Some(Cea608::Text(Text {
346 needs_backspace: false,
347 char1,
348 char2,
349 channel,
350 })))
351 } else {
352 Ok(None)
353 }
354 }
355 }
356 }
357
358 pub fn last_received_field(&self) -> Option<Field> {
361 self.last_received_field
362 }
363
364 pub fn reset(&mut self) {
366 *self = Self::default();
367 }
368}
369
370#[derive(Debug, Default)]
372pub struct Cea608Writer {
373 pending: VecDeque<Code>,
374 pending_code: Option<Code>,
375}
376
377impl Cea608Writer {
378 pub fn push(&mut self, code: Code) {
380 if code == Code::NUL {
381 return;
382 }
383 self.pending.push_front(code)
384 }
385
386 pub fn pop(&mut self) -> [u8; 2] {
388 let mut ret = [0x80; 2];
389 let mut prev = None::<Code>;
390
391 if let Some(code) = self.pending_code.take() {
392 trace!("returning pending code {code:?}");
393 code.write_into(&mut ret);
394 return ret;
395 }
396
397 while let Some(code) = self.pending.pop_back() {
398 if let Some(prev) = prev {
399 trace!("have prev {prev:?}");
400 if code.byte_len() == 1 {
401 let mut data = [0; 2];
402 prev.write_into(&mut ret);
403 code.write_into(&mut data);
404 ret[1] = data[0];
405 trace!("have 1 byte code {code:?}, returning {ret:x?}");
406 return ret;
407 } else if code.needs_backspace() {
408 self.pending_code = Some(code);
409 let mut data = [0; 2];
410 prev.write_into(&mut ret);
411 Code::Space.write_into(&mut data);
412 ret[1] = data[0];
413 trace!("have backspace needing code {code:?} stored as pending, pushing space with previous code {prev:?}");
414 return ret;
415 } else {
416 self.pending_code = Some(code);
417 prev.write_into(&mut ret);
418 trace!("have two byte code {code:?} stored as pending, pushing space");
419 return ret;
420 }
421 } else if code.needs_backspace() {
422 self.pending_code = Some(code);
424 Code::Space.write_into(&mut ret);
425 trace!("have backspace needing code {code:?} stored as pending, pushing space");
426 return ret;
427 } else if code.byte_len() == 1 {
428 prev = Some(code);
429 } else {
430 trace!("have standalone 2 byte code {code:?}");
431 code.write_into(&mut ret);
432 return ret;
433 }
434 }
435 if let Some(prev) = prev {
436 trace!("have no more pending codes, writing prev {prev:?}");
437 prev.write_into(&mut ret);
438 }
439 ret
440 }
441
442 pub fn n_codes(&self) -> usize {
444 self.pending.len() + if self.pending_code.is_some() { 1 } else { 0 }
445 }
446
447 pub fn reset(&mut self) {
449 *self = Self::default();
450 }
451}
452
453#[derive(Debug, Copy, Clone, PartialEq, Eq)]
455pub enum Id {
456 CC1,
458 CC2,
460 CC3,
462 CC4,
464 }
466
467impl Id {
468 pub fn field(&self) -> Field {
470 match self {
471 Self::CC1 | Self::CC2 => Field::ONE,
472 Self::CC3 | Self::CC4 => Field::TWO,
473 }
474 }
475
476 pub fn channel(&self) -> Channel {
478 match self {
479 Self::CC1 | Self::CC3 => Channel::ONE,
480 Self::CC2 | Self::CC4 => Channel::TWO,
481 }
482 }
483
484 pub fn from_caption_field_channel(field: Field, channel: Channel) -> Self {
486 match (field, channel) {
487 (Field::ONE, Channel::ONE) => Self::CC1,
488 (Field::ONE, Channel::TWO) => Self::CC2,
489 (Field::TWO, Channel::ONE) => Self::CC3,
490 (Field::TWO, Channel::TWO) => Self::CC4,
491 }
492 }
493
494 pub fn from_value(value: i8) -> Self {
496 match value {
497 1 => Self::CC1,
498 2 => Self::CC2,
499 3 => Self::CC3,
500 4 => Self::CC4,
501 _ => unreachable!(),
502 }
503 }
504}
505
506#[cfg(test)]
507mod test {
508 use self::tables::ControlCode;
509
510 use super::*;
511 use crate::tests::*;
512
513 #[test]
514 fn state_duplicate_control() {
515 test_init_log();
516 let mut data = vec![];
517 Code::Control(ControlCode::new(
518 Field::ONE,
519 Channel::ONE,
520 tables::Control::EraseDisplayedMemory,
521 ))
522 .write(&mut data)
523 .unwrap();
524 let mut state = Cea608State::default();
525 assert_eq!(
526 Ok(Some(Cea608::EraseDisplay(Channel::ONE))),
527 state.decode([data[0], data[1]])
528 );
529 assert_eq!(state.last_received_field(), Some(Field::ONE));
530 assert_eq!(Ok(None), state.decode([data[0], data[1]]));
531 assert_eq!(state.last_received_field(), Some(Field::ONE));
532 }
533
534 #[test]
535 fn state_text_after_control() {
536 test_init_log();
537 let mut state = Cea608State::default();
538
539 let mut data = vec![];
540 Code::Control(ControlCode::new(
541 Field::ONE,
542 Channel::ONE,
543 tables::Control::RollUp2,
544 ))
545 .write(&mut data)
546 .unwrap();
547 assert_eq!(
548 Ok(Some(Cea608::NewMode(Channel::ONE, Mode::RollUp2))),
549 state.decode([data[0], data[1]])
550 );
551 assert_eq!(state.last_received_field(), Some(Field::ONE));
552
553 let mut data = vec![];
554 Code::LatinCapitalA.write(&mut data).unwrap();
555 assert_eq!(
556 Ok(Some(Cea608::Text(Text {
557 needs_backspace: false,
558 char1: Some('A'),
559 char2: None,
560 channel: Channel::ONE,
561 }))),
562 state.decode([data[0], 0x80])
563 );
564 assert_eq!(state.last_received_field(), Some(Field::ONE));
565
566 let mut data = vec![];
567 Code::Control(ControlCode::new(
568 Field::TWO,
569 Channel::TWO,
570 tables::Control::RollUp2,
571 ))
572 .write(&mut data)
573 .unwrap();
574 assert_eq!(
575 Ok(Some(Cea608::NewMode(Channel::TWO, Mode::RollUp2))),
576 state.decode([data[0], data[1]])
577 );
578 assert_eq!(state.last_received_field(), Some(Field::TWO));
579
580 let mut data = vec![];
581 Code::LatinCapitalA.write(&mut data).unwrap();
582 assert_eq!(
583 Ok(Some(Cea608::Text(Text {
584 needs_backspace: false,
585 char1: Some('A'),
586 char2: None,
587 channel: Channel::TWO,
588 }))),
589 state.decode([data[0], 0x80])
590 );
591 }
592
593 #[test]
594 fn writer_padding() {
595 test_init_log();
596 let mut writer = Cea608Writer::default();
597 assert_eq!(writer.pop(), [0x80, 0x80]);
598 }
599
600 #[test]
601 fn writer_single_byte_code() {
602 test_init_log();
603 let mut writer = Cea608Writer::default();
604 writer.push(Code::LatinLowerA);
605 assert_eq!(writer.pop(), [0x61, 0x80]);
606 assert_eq!(writer.pop(), [0x80, 0x80]);
607 }
608
609 #[test]
610 fn writer_two_single_byte_codes() {
611 test_init_log();
612 let mut writer = Cea608Writer::default();
613 writer.push(Code::LatinLowerA);
614 writer.push(Code::LatinLowerB);
615 assert_eq!(writer.pop(), [0x61, 0x62]);
616 assert_eq!(writer.pop(), [0x80, 0x80]);
617 }
618
619 #[test]
620 fn writer_single_byte_and_control() {
621 test_init_log();
622 let mut writer = Cea608Writer::default();
623 writer.push(Code::LatinLowerA);
624 writer.push(Code::Control(ControlCode::new(
625 Field::ONE,
626 Channel::ONE,
627 tables::Control::DegreeSign,
628 )));
629 assert_eq!(writer.pop(), [0x61, 0x80]);
630 assert_eq!(writer.pop(), [0x91, 0x31]);
631 assert_eq!(writer.pop(), [0x80, 0x80]);
632 }
633
634 #[test]
635 fn writer_single_byte_and_control_needing_backspace() {
636 test_init_log();
637 let mut writer = Cea608Writer::default();
638 writer.push(Code::LatinLowerA);
639 writer.push(Code::Control(ControlCode::new(
640 Field::ONE,
641 Channel::ONE,
642 tables::Control::Tilde,
643 )));
644 assert_eq!(writer.pop(), [0x61, 0x20]);
645 assert_eq!(writer.pop(), [0x13, 0x2f]);
646 assert_eq!(writer.pop(), [0x80, 0x80]);
647 }
648
649 #[test]
650 fn writer_control_needing_backspace() {
651 test_init_log();
652 let mut writer = Cea608Writer::default();
653 writer.push(Code::Control(ControlCode::new(
654 Field::ONE,
655 Channel::ONE,
656 tables::Control::Tilde,
657 )));
658 assert_eq!(writer.pop(), [0x20, 0x80]);
659 assert_eq!(writer.pop(), [0x13, 0x2f]);
660 assert_eq!(writer.pop(), [0x80, 0x80]);
661 }
662
663 #[test]
664 fn writer_control() {
665 test_init_log();
666 let mut writer = Cea608Writer::default();
667 writer.push(Code::Control(ControlCode::new(
668 Field::ONE,
669 Channel::ONE,
670 tables::Control::DegreeSign,
671 )));
672 assert_eq!(writer.pop(), [0x91, 0x31]);
673 assert_eq!(writer.pop(), [0x80, 0x80]);
674 }
675
676 #[test]
677 fn state_into_writer() {
678 test_init_log();
679 let stream = [[0x20, 0x80], [0x13, 0x2f], [0x80, 0x80]];
680 let mut state = Cea608State::default();
681 let mut writer = Cea608Writer::default();
682 for pair in stream {
683 let Some(cea608) = state.decode(pair).unwrap() else {
684 continue;
685 };
686 for code in cea608.into_code(Field::ONE) {
687 writer.push(code);
688 }
689 }
690 assert_eq!(writer.pop(), [0x20, 0x80]);
691 assert_eq!(writer.pop(), [0x13, 0x2f]);
692 assert_eq!(writer.pop(), [0x80, 0x80]);
693 }
694
695 #[test]
696 fn cea608_to_from_code() {
697 test_init_log();
698
699 let controls = [
700 tables::Control::ResumeCaptionLoading,
701 tables::Control::RollUp2,
702 tables::Control::RollUp3,
703 tables::Control::RollUp4,
704 tables::Control::EndOfCaption,
705 tables::Control::ResumeDirectionCaptioning,
706 tables::Control::tab_offset(1).unwrap(),
707 tables::Control::tab_offset(2).unwrap(),
708 tables::Control::tab_offset(3).unwrap(),
709 tables::Control::PreambleAddress(PreambleAddressCode::new(
710 3,
711 true,
712 tables::PreambleType::Indent4,
713 )),
714 tables::Control::EraseDisplayedMemory,
715 tables::Control::EraseNonDisplayedMemory,
716 tables::Control::CarriageReturn,
717 tables::Control::Backspace,
718 tables::Control::DeleteToEndOfRow,
719 ];
720 let controls_with_preceding_overwritten_char = [tables::Control::MidRow(
721 MidRow::new_color(tables::Color::Green, true),
722 )];
723
724 let codes = [Code::PercentSign, Code::LatinLowerA];
725
726 let mut writer = Cea608Writer::default();
727
728 let mut state = Cea608State::default();
729
730 for field in [Field::ONE, Field::TWO] {
731 for channel in [Channel::ONE, Channel::TWO] {
732 for control in controls {
733 let code = Code::Control(ControlCode {
734 field: Some(field),
735 channel,
736 control,
737 });
738 writer.push(code);
739 let cea608 = state.decode(writer.pop()).unwrap().unwrap();
740 assert_eq!(cea608.into_code(field)[0], code);
741 }
742 for control in controls_with_preceding_overwritten_char {
743 let code = Code::Control(ControlCode {
744 field: Some(field),
745 channel,
746 control,
747 });
748 writer.push(code);
749 writer.pop(); let cea608 = state.decode(writer.pop()).unwrap().unwrap();
751 assert_eq!(cea608.into_code(field)[0], code);
752 }
753 }
754 }
755
756 for code in codes {
757 debug!("pushing {code:?}");
758 writer.push(code);
759 let data = writer.pop();
760 let cea608 = state.decode(data).unwrap().unwrap();
761 assert_eq!(cea608.into_code(Field::ONE)[0], code);
762 }
763 }
764
765 #[test]
766 fn writer_ignore_padding() {
767 test_init_log();
768
769 let mut writer = Cea608Writer::default();
770 writer.push(Code::NUL);
771 writer.push(Code::LatinLowerA);
772 assert_eq!(writer.pop(), [0x61, 0x80]);
773 }
774}
775
776#[cfg(test)]
777pub(crate) mod tests {
778 use std::sync::OnceLock;
779
780 static TRACING: OnceLock<()> = OnceLock::new();
781
782 pub fn test_init_log() {
783 TRACING.get_or_init(|| {
784 env_logger::init();
785 });
786 }
787}