use crate::style::{Alignment, Color, Position};
use crate::{Subtitle, SubtitleError, SubtitleResult, SubtitleStyle};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cea608Mode {
PopOn,
RollUp2,
RollUp3,
RollUp4,
PaintOn,
}
pub struct Cea608Decoder {
mode: Cea608Mode,
buffer: String,
display: String,
row: u8,
column: u8,
style: Cea608Style,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cea608Style {
White,
Green,
Blue,
Cyan,
Red,
Yellow,
Magenta,
Italic,
}
impl Cea608Style {
#[must_use]
pub const fn color(&self) -> Color {
match self {
Self::White | Self::Italic => Color::white(),
Self::Green => Color::rgb(0, 255, 0),
Self::Blue => Color::rgb(0, 0, 255),
Self::Cyan => Color::rgb(0, 255, 255),
Self::Red => Color::rgb(255, 0, 0),
Self::Yellow => Color::rgb(255, 255, 0),
Self::Magenta => Color::rgb(255, 0, 255),
}
}
}
impl Default for Cea608Decoder {
fn default() -> Self {
Self::new()
}
}
impl Cea608Decoder {
#[must_use]
pub fn new() -> Self {
Self {
mode: Cea608Mode::PopOn,
buffer: String::new(),
display: String::new(),
row: 15,
column: 0,
style: Cea608Style::White,
}
}
pub fn decode_pair(&mut self, byte1: u8, byte2: u8) -> SubtitleResult<Option<Subtitle>> {
let b1 = byte1 & 0x7F;
let b2 = byte2 & 0x7F;
if (0x10..=0x1F).contains(&b1) {
return self.decode_control(b1, b2);
}
if b1 == 0x11 || b1 == 0x12 || b1 == 0x13 {
return self.decode_special(b1, b2);
}
if (0x20..=0x7F).contains(&b1) {
self.add_char(b1 as char);
}
if (0x20..=0x7F).contains(&b2) {
self.add_char(b2 as char);
}
Ok(None)
}
fn decode_control(&mut self, b1: u8, b2: u8) -> SubtitleResult<Option<Subtitle>> {
match (b1, b2) {
(0x14, 0x20) => {
self.mode = Cea608Mode::PopOn;
}
(0x14, 0x21) => {
self.buffer.pop();
}
(0x14, 0x2F) => {
return Ok(self.end_of_caption());
}
(0x14, 0x2D) => {
self.buffer.push('\n');
}
(0x14, 0x2C) => {
self.display.clear();
}
(0x14, 0x25) => {
self.mode = Cea608Mode::RollUp2;
}
(0x14, 0x26) => {
self.mode = Cea608Mode::RollUp3;
}
(0x14, 0x27) => {
self.mode = Cea608Mode::RollUp4;
}
_ => {
}
}
Ok(None)
}
fn decode_special(&mut self, _b1: u8, _b2: u8) -> SubtitleResult<Option<Subtitle>> {
Ok(None)
}
fn add_char(&mut self, c: char) {
self.buffer.push(c);
self.column += 1;
}
fn end_of_caption(&mut self) -> Option<Subtitle> {
if self.buffer.is_empty() {
return None;
}
std::mem::swap(&mut self.buffer, &mut self.display);
self.buffer.clear();
let mut subtitle = Subtitle::new(0, 3000, self.display.clone());
let mut style = SubtitleStyle::default();
style.primary_color = self.style.color();
style.position = Position::bottom_center();
subtitle.style = Some(style);
Some(subtitle)
}
pub fn reset(&mut self) {
self.buffer.clear();
self.display.clear();
self.row = 15;
self.column = 0;
self.style = Cea608Style::White;
}
#[must_use]
pub fn display(&self) -> &str {
&self.display
}
}
pub struct Cea708Decoder {
current_window: u8,
windows: [String; 8],
}
impl Default for Cea708Decoder {
fn default() -> Self {
Self::new()
}
}
impl Cea708Decoder {
#[must_use]
pub fn new() -> Self {
Self {
current_window: 0,
windows: Default::default(),
}
}
pub fn decode_service_block(&mut self, _data: &[u8]) -> SubtitleResult<Option<Vec<Subtitle>>> {
Ok(None)
}
pub fn reset(&mut self) {
self.current_window = 0;
for window in &mut self.windows {
window.clear();
}
}
#[must_use]
pub fn window_text(&self, window: u8) -> &str {
self.windows
.get(window as usize)
.map(String::as_str)
.unwrap_or("")
}
}
pub fn extract_cea608_from_user_data(user_data: &[u8]) -> SubtitleResult<Vec<(u8, u8)>> {
let mut pairs = Vec::new();
let mut i = 0;
while i + 2 < user_data.len() {
if user_data[i] == 0xCC {
let byte1 = user_data[i + 1];
let byte2 = user_data[i + 2];
pairs.push((byte1, byte2));
i += 3;
} else {
i += 1;
}
}
Ok(pairs)
}
pub fn extract_cea708_from_user_data(user_data: &[u8]) -> SubtitleResult<Vec<u8>> {
Ok(user_data.to_vec())
}