diff options
author | Matthew Waters <matthew@centricular.com> | 2023-03-01 05:04:44 +0300 |
---|---|---|
committer | Matthew Waters <matthew@centricular.com> | 2023-04-05 05:18:15 +0300 |
commit | 9a5e5db2717cb9699d0beac1b9eede1339787b72 (patch) | |
tree | 8d5cceeac4200e942aad62ffac0497eda1b51de2 /video | |
parent | f17622a1e1de8a4f8818e0cbfe5bc864b5d6a340 (diff) |
closedcaption: move 608 utility functions to a separate file
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1112>
Diffstat (limited to 'video')
-rw-r--r-- | video/closedcaption/src/cea608tojson/imp.rs | 443 | ||||
-rw-r--r-- | video/closedcaption/src/cea608utils.rs | 359 | ||||
-rw-r--r-- | video/closedcaption/src/lib.rs | 3 | ||||
-rw-r--r-- | video/closedcaption/src/transcriberbin/imp.rs | 2 | ||||
-rw-r--r-- | video/closedcaption/src/tttocea608/imp.rs | 3 | ||||
-rw-r--r-- | video/closedcaption/src/tttojson/imp.rs | 3 | ||||
-rw-r--r-- | video/closedcaption/src/ttutils.rs | 42 |
7 files changed, 500 insertions, 355 deletions
diff --git a/video/closedcaption/src/cea608tojson/imp.rs b/video/closedcaption/src/cea608tojson/imp.rs index cf90b6f08..c513dff3a 100644 --- a/video/closedcaption/src/cea608tojson/imp.rs +++ b/video/closedcaption/src/cea608tojson/imp.rs @@ -29,8 +29,8 @@ use gst::glib; use gst::prelude::*; use gst::subclass::prelude::*; -use crate::ffi; -use crate::ttutils::{Cea608Mode, Chunk, Line, Lines, TextStyle}; +use crate::cea608utils::*; +use crate::ttutils::{Chunk, Line, Lines}; use atomic_refcell::AtomicRefCell; @@ -195,7 +195,6 @@ impl Default for Settings { struct State { mode: Option<Cea608Mode>, - last_cc_data: Option<u16>, rows: BTreeMap<u32, Row>, first_pts: Option<gst::ClockTime>, current_pts: Option<gst::ClockTime>, @@ -205,13 +204,13 @@ struct State { cursor: Cursor, pending_lines: Option<TimestampedLines>, settings: Settings, + cea608_state: Cea608StateTracker, } impl Default for State { fn default() -> Self { State { mode: None, - last_cc_data: None, rows: BTreeMap::new(), first_pts: gst::ClockTime::NONE, current_pts: gst::ClockTime::NONE, @@ -226,6 +225,7 @@ impl Default for State { }, pending_lines: None, settings: Settings::default(), + cea608_state: Cea608StateTracker::default(), } } } @@ -246,152 +246,6 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| { ) }); -fn is_basicna(cc_data: u16) -> bool { - 0x0000 != (0x6000 & cc_data) -} - -fn is_preamble(cc_data: u16) -> bool { - 0x1040 == (0x7040 & cc_data) -} - -fn is_midrowchange(cc_data: u16) -> bool { - 0x1120 == (0x7770 & cc_data) -} - -fn is_specialna(cc_data: u16) -> bool { - 0x1130 == (0x7770 & cc_data) -} - -fn is_xds(cc_data: u16) -> bool { - 0x0000 == (0x7070 & cc_data) && 0x0000 != (0x0F0F & cc_data) -} - -fn is_westeu(cc_data: u16) -> bool { - 0x1220 == (0x7660 & cc_data) -} - -fn is_control(cc_data: u16) -> bool { - 0x1420 == (0x7670 & cc_data) || 0x1720 == (0x7770 & cc_data) -} - -fn parse_control(cc_data: u16) -> (ffi::eia608_control_t, i32) { - unsafe { - let mut chan = 0; - let cmd = ffi::eia608_parse_control(cc_data, &mut chan); - - (cmd, chan) - } -} - -#[derive(Debug)] -struct Preamble { - row: i32, - col: i32, - style: TextStyle, - chan: i32, - underline: i32, -} - -fn parse_preamble(cc_data: u16) -> Preamble { - unsafe { - let mut row = 0; - let mut col = 0; - let mut style = 0; - let mut chan = 0; - let mut underline = 0; - - ffi::eia608_parse_preamble( - cc_data, - &mut row, - &mut col, - &mut style, - &mut chan, - &mut underline, - ); - - Preamble { - row, - col, - style: style.into(), - chan, - underline, - } - } -} - -struct MidrowChange { - chan: i32, - style: TextStyle, - underline: bool, -} - -fn parse_midrowchange(cc_data: u16) -> MidrowChange { - unsafe { - let mut chan = 0; - let mut style = 0; - let mut underline = 0; - - ffi::eia608_parse_midrowchange(cc_data, &mut chan, &mut style, &mut underline); - - MidrowChange { - chan, - style: style.into(), - underline: underline > 0, - } - } -} - -fn eia608_to_utf8(cc_data: u16) -> (Option<char>, Option<char>, i32) { - unsafe { - let mut chan = 0; - let mut char1 = [0u8; 5usize]; - let mut char2 = [0u8; 5usize]; - - let n_chars = ffi::eia608_to_utf8( - cc_data, - &mut chan, - char1.as_mut_ptr() as *mut _, - char2.as_mut_ptr() as *mut _, - ); - - let char1 = if n_chars > 0 { - Some( - std::ffi::CStr::from_bytes_with_nul_unchecked(&char1) - .to_string_lossy() - .chars() - .next() - .unwrap(), - ) - } else { - None - }; - - let char2 = if n_chars > 1 { - Some( - std::ffi::CStr::from_bytes_with_nul_unchecked(&char2) - .to_string_lossy() - .chars() - .next() - .unwrap(), - ) - } else { - None - }; - - (char1, char2, chan) - } -} - -fn eia608_to_text(cc_data: u16) -> String { - unsafe { - let bufsz = ffi::eia608_to_text(std::ptr::null_mut(), 0, cc_data); - let mut data = Vec::with_capacity((bufsz + 1) as usize); - ffi::eia608_to_text(data.as_ptr() as *mut _, (bufsz + 1) as usize, cc_data); - data.set_len(bufsz as usize); - String::from_utf8_unchecked(data) - } -} - fn dump( imp: &Cea608ToJson, cc_data: u16, @@ -492,6 +346,7 @@ impl State { } self.rows.clear(); + self.cea608_state.flush(); } else { for row in self.rows.values() { if !row.is_empty() { @@ -544,9 +399,11 @@ impl State { } } - fn decode_preamble(&mut self, imp: &Cea608ToJson, cc_data: u16) -> Option<TimestampedLines> { - let preamble = parse_preamble(cc_data); - + fn handle_preamble( + &mut self, + imp: &Cea608ToJson, + preamble: Preamble, + ) -> Option<TimestampedLines> { if preamble.chan != 0 { return None; } @@ -630,140 +487,17 @@ impl State { } } - fn decode_control(&mut self, imp: &Cea608ToJson, cc_data: u16) -> Option<TimestampedLines> { - let (cmd, chan) = parse_control(cc_data); - - gst::log!(CAT, imp: imp, "Command for CC {}", chan); - - if chan != 0 { - return None; - } - - match cmd { - ffi::eia608_control_t_eia608_control_resume_direct_captioning => { - return self.update_mode(imp, Cea608Mode::PaintOn); - } - ffi::eia608_control_t_eia608_control_erase_display_memory => { - return match self.mode { - Some(Cea608Mode::PopOn) => { - self.clear = Some(true); - self.drain_pending(imp) - } - _ => { - let ret = self.drain(imp, true); - self.clear = Some(true); - ret - } - }; - } - ffi::eia608_control_t_eia608_control_roll_up_2 => { - return self.update_mode(imp, Cea608Mode::RollUp2); - } - ffi::eia608_control_t_eia608_control_roll_up_3 => { - return self.update_mode(imp, Cea608Mode::RollUp3); - } - ffi::eia608_control_t_eia608_control_roll_up_4 => { - return self.update_mode(imp, Cea608Mode::RollUp4); - } - ffi::eia608_control_t_eia608_control_carriage_return => { - gst::log!(CAT, imp: imp, "carriage return"); - - if let Some(mode) = self.mode { - // https://www.law.cornell.edu/cfr/text/47/79.101 (f)(2)(i) (f)(3)(i) - if mode.is_rollup() { - let ret = if self.settings.unbuffered { - let offset = match mode { - Cea608Mode::RollUp2 => 1, - Cea608Mode::RollUp3 => 2, - Cea608Mode::RollUp4 => 3, - _ => unreachable!(), - }; - - let top_row = self.cursor.row.saturating_sub(offset); - - // https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(iii) - self.rows.remove(&top_row); - - for row in top_row + 1..self.cursor.row + 1 { - if let Some(mut row) = self.rows.remove(&row) { - row.row -= 1; - self.rows.insert(row.row, row); - } - } - - self.rows.insert(self.cursor.row, Row::new(self.cursor.row)); - self.drain(imp, false) - } else { - let ret = self.drain(imp, true); - self.carriage_return = Some(true); - ret - }; - - return ret; - } - } - } - ffi::eia608_control_t_eia608_control_backspace => { - if let Some(row) = self.rows.get_mut(&self.cursor.row) { - row.pop(&mut self.cursor); - } - } - ffi::eia608_control_t_eia608_control_resume_caption_loading => { - return self.update_mode(imp, Cea608Mode::PopOn); - } - ffi::eia608_control_t_eia608_control_erase_non_displayed_memory => { - if self.mode == Some(Cea608Mode::PopOn) { - self.rows.clear(); - } - } - ffi::eia608_control_t_eia608_control_end_of_caption => { - // https://www.law.cornell.edu/cfr/text/47/79.101 (f)(2) - self.update_mode(imp, Cea608Mode::PopOn); - self.first_pts = self.current_pts; - let ret = if self.settings.unbuffered { - self.drain(imp, true) - } else { - let ret = self.drain_pending(imp); - self.pending_lines = self.drain(imp, true); - ret - }; - return ret; - } - ffi::eia608_control_t_eia608_tab_offset_0 - | ffi::eia608_control_t_eia608_tab_offset_1 - | ffi::eia608_control_t_eia608_tab_offset_2 - | ffi::eia608_control_t_eia608_tab_offset_3 => { - self.cursor.col += (cmd - ffi::eia608_control_t_eia608_tab_offset_0) as usize; - // C.13 Right Margin Limitation - self.cursor.col = std::cmp::min(self.cursor.col, 31); - } - // TODO - ffi::eia608_control_t_eia608_control_alarm_off - | ffi::eia608_control_t_eia608_control_delete_to_end_of_row => {} - ffi::eia608_control_t_eia608_control_alarm_on - | ffi::eia608_control_t_eia608_control_text_restart - | ffi::eia608_control_t_eia608_control_text_resume_text_display => {} - _ => { - gst::warning!(CAT, imp: imp, "Unknown command {}!", cmd); - } - } - - None - } - - fn decode_text(&mut self, imp: &Cea608ToJson, cc_data: u16) { - let (char1, char2, chan) = eia608_to_utf8(cc_data); - - if chan != 0 { + fn handle_text(&mut self, imp: &Cea608ToJson, text: Cea608Text) { + if text.chan != 0 { return; } if let Some(row) = self.rows.get_mut(&self.cursor.row) { - if is_westeu(cc_data) { + if text.code_space == CodeSpace::WestEU { row.pop(&mut self.cursor); } - if (char1.is_some() || char2.is_some()) && self.first_pts.is_none() { + if (text.char1.is_some() || text.char2.is_some()) && self.first_pts.is_none() { if let Some(mode) = self.mode { if mode.is_rollup() || mode == Cea608Mode::PaintOn { self.first_pts = self.current_pts; @@ -771,11 +505,11 @@ impl State { } } - if let Some(c) = char1 { + if let Some(c) = text.char1 { row.push(&mut self.cursor, c); } - if let Some(c) = char2 { + if let Some(c) = text.char2 { row.push(&mut self.cursor, c); } } else { @@ -783,10 +517,8 @@ impl State { } } - fn decode_midrowchange(&mut self, cc_data: u16) { + fn handle_midrowchange(&mut self, midrowchange: MidRowChange) { if let Some(row) = self.rows.get_mut(&self.cursor.row) { - let midrowchange = parse_midrowchange(cc_data); - if midrowchange.chan == 0 { row.push_midrow(&mut self.cursor, midrowchange.style, midrowchange.underline); } @@ -800,36 +532,127 @@ impl State { duration: Option<gst::ClockTime>, cc_data: u16, ) -> Option<TimestampedLines> { - if (is_specialna(cc_data) || is_control(cc_data)) && Some(cc_data) == self.last_cc_data { - gst::log!(CAT, imp: imp, "Skipping duplicate"); - return None; - } + self.cea608_state.push_cc_data(cc_data); - self.last_cc_data = Some(cc_data); - self.current_pts = pts; - self.current_duration = duration; + while let Some(cea608) = self.cea608_state.pop() { + if matches!(cea608, Cea608::Duplicate) { + gst::log!(CAT, imp: imp, "Skipping duplicate"); + return None; + } - if is_xds(cc_data) { - gst::log!(CAT, imp: imp, "XDS, ignoring"); - } else if is_control(cc_data) { - gst::log!(CAT, imp: imp, "control!"); - return self.decode_control(imp, cc_data); - } else if is_basicna(cc_data) || is_specialna(cc_data) || is_westeu(cc_data) { - if let Some(mode) = self.mode { - self.mode?; - gst::log!(CAT, imp: imp, "text"); - self.decode_text(imp, cc_data); + self.current_pts = pts; + self.current_duration = duration; - if mode.is_rollup() && self.settings.unbuffered { - return self.drain(imp, false); + match cea608 { + Cea608::Duplicate => unreachable!(), + Cea608::EraseDisplay(chan) => { + if chan == 0 { + return match self.mode { + Some(Cea608Mode::PopOn) => { + self.clear = Some(true); + self.drain_pending(imp) + } + _ => { + let ret = self.drain(imp, true); + self.clear = Some(true); + ret + } + }; + } + } + Cea608::NewMode(chan, mode) => { + if chan == 0 { + return self.update_mode(imp, mode); + } + } + Cea608::CarriageReturn(chan) => { + if chan == 0 { + gst::log!(CAT, imp: imp, "carriage return"); + + if let Some(mode) = self.mode { + // https://www.law.cornell.edu/cfr/text/47/79.101 (f)(2)(i) (f)(3)(i) + if mode.is_rollup() { + let ret = if self.settings.unbuffered { + let offset = match mode { + Cea608Mode::RollUp2 => 1, + Cea608Mode::RollUp3 => 2, + Cea608Mode::RollUp4 => 3, + _ => unreachable!(), + }; + + let top_row = self.cursor.row.saturating_sub(offset); + + // https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(iii) + self.rows.remove(&top_row); + + for row in top_row + 1..self.cursor.row + 1 { + if let Some(mut row) = self.rows.remove(&row) { + row.row -= 1; + self.rows.insert(row.row, row); + } + } + + self.rows.insert(self.cursor.row, Row::new(self.cursor.row)); + self.drain(imp, false) + } else { + let ret = self.drain(imp, true); + self.carriage_return = Some(true); + ret + }; + + return ret; + } + } + } + } + Cea608::Backspace(chan) => { + if chan == 0 { + if let Some(row) = self.rows.get_mut(&self.cursor.row) { + row.pop(&mut self.cursor); + } + } + } + Cea608::EraseNonDisplay(chan) => { + if chan == 0 && self.mode == Some(Cea608Mode::PopOn) { + self.rows.clear(); + } + } + Cea608::EndOfCaption(chan) => { + if chan == 0 { + // https://www.law.cornell.edu/cfr/text/47/79.101 (f)(2) + self.update_mode(imp, Cea608Mode::PopOn); + self.first_pts = self.current_pts; + let ret = if self.settings.unbuffered { + self.drain(imp, true) + } else { + let ret = self.drain_pending(imp); + self.pending_lines = self.drain(imp, true); + ret + }; + return ret; + } + } + Cea608::TabOffset(chan, count) => { + if chan == 0 { + self.cursor.col += count as usize; + // C.13 Right Margin Limitation + self.cursor.col = std::cmp::min(self.cursor.col, 31); + } + } + Cea608::Text(text) => { + if let Some(mode) = self.mode { + self.mode?; + gst::log!(CAT, imp: imp, "text"); + self.handle_text(imp, text); + + if mode.is_rollup() && self.settings.unbuffered { + return self.drain(imp, false); + } + } } + Cea608::Preamble(preamble) => return self.handle_preamble(imp, preamble), + Cea608::MidRowChange(change) => self.handle_midrowchange(change), } - } else if is_preamble(cc_data) { - gst::log!(CAT, imp: imp, "preamble"); - return self.decode_preamble(imp, cc_data); - } else if is_midrowchange(cc_data) { - gst::log!(CAT, imp: imp, "midrowchange"); - self.decode_midrowchange(cc_data); } None } diff --git a/video/closedcaption/src/cea608utils.rs b/video/closedcaption/src/cea608utils.rs new file mode 100644 index 000000000..c2415b905 --- /dev/null +++ b/video/closedcaption/src/cea608utils.rs @@ -0,0 +1,359 @@ +// Copyright (C) 2023 Matthew Waters <matthew@centricular.com> +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// <https://mozilla.org/MPL/2.0/>. +// +// SPDX-License-Identifier: MPL-2.0 + +use gst::glib; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +use crate::ffi; + +static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| { + gst::DebugCategory::new( + "cea608utils", + gst::DebugColorFlags::empty(), + Some("CEA-608 utilities"), + ) +}); + +#[derive( + Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum, +)] +#[repr(u32)] +#[enum_type(name = "GstTtToCea608Mode")] +pub enum Cea608Mode { + PopOn, + PaintOn, + RollUp2, + RollUp3, + RollUp4, +} + +#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)] +pub enum TextStyle { + White, + Green, + Blue, + Cyan, + Red, + Yellow, + Magenta, + ItalicWhite, +} + +impl From<u32> for TextStyle { + fn from(val: u32) -> Self { + match val { + 0 => TextStyle::White, + 1 => TextStyle::Green, + 2 => TextStyle::Blue, + 3 => TextStyle::Cyan, + 4 => TextStyle::Red, + 5 => TextStyle::Yellow, + 6 => TextStyle::Magenta, + 7 => TextStyle::ItalicWhite, + _ => TextStyle::White, + } + } +} + +pub(crate) fn is_basicna(cc_data: u16) -> bool { + 0x0000 != (0x6000 & cc_data) +} + +pub(crate) fn is_preamble(cc_data: u16) -> bool { + 0x1040 == (0x7040 & cc_data) +} + +pub(crate) fn is_midrowchange(cc_data: u16) -> bool { + 0x1120 == (0x7770 & cc_data) +} + +pub(crate) fn is_specialna(cc_data: u16) -> bool { + 0x1130 == (0x7770 & cc_data) +} + +pub(crate) fn is_xds(cc_data: u16) -> bool { + 0x0000 == (0x7070 & cc_data) && 0x0000 != (0x0F0F & cc_data) +} + +pub(crate) fn is_westeu(cc_data: u16) -> bool { + 0x1220 == (0x7660 & cc_data) +} + +pub(crate) fn is_control(cc_data: u16) -> bool { + 0x1420 == (0x7670 & cc_data) || 0x1720 == (0x7770 & cc_data) +} + +pub(crate) fn parse_control(cc_data: u16) -> (ffi::eia608_control_t, i32) { + unsafe { + let mut chan = 0; + let cmd = ffi::eia608_parse_control(cc_data, &mut chan); + + (cmd, chan) + } +} + +#[derive(Debug)] +pub(crate) struct Preamble { + pub row: i32, + pub col: i32, + pub style: TextStyle, + pub chan: i32, + pub underline: i32, +} + +pub(crate) fn parse_preamble(cc_data: u16) -> Preamble { + unsafe { + let mut row = 0; + let mut col = 0; + let mut style = 0; + let mut chan = 0; + let mut underline = 0; + + ffi::eia608_parse_preamble( + cc_data, + &mut row, + &mut col, + &mut style, + &mut chan, + &mut underline, + ); + + Preamble { + row, + col, + style: style.into(), + chan, + underline, + } + } +} + +#[derive(Debug)] +pub(crate) struct MidRowChange { + pub chan: i32, + pub style: TextStyle, + pub underline: bool, +} + +pub(crate) fn parse_midrowchange(cc_data: u16) -> MidRowChange { + unsafe { + let mut chan = 0; + let mut style = 0; + let mut underline = 0; + + ffi::eia608_parse_midrowchange(cc_data, &mut chan, &mut style, &mut underline); + + MidRowChange { + chan, + style: style.into(), + underline: underline > 0, + } + } +} + +pub(crate) fn eia608_to_utf8(cc_data: u16) -> (Option<char>, Option<char>, i32) { + unsafe { + let mut chan = 0; + let mut char1 = [0u8; 5usize]; + let mut char2 = [0u8; 5usize]; + + let n_chars = ffi::eia608_to_utf8( + cc_data, + &mut chan, + char1.as_mut_ptr() as *mut _, + char2.as_mut_ptr() as *mut _, + ); + + let char1 = if n_chars > 0 { + Some( + std::ffi::CStr::from_bytes_with_nul_unchecked(&char1) + .to_string_lossy() + .chars() + .next() + .unwrap(), + ) + } else { + None + }; + + let char2 = if n_chars > 1 { + Some( + std::ffi::CStr::from_bytes_with_nul_unchecked(&char2) + .to_string_lossy() + .chars() + .next() + .unwrap(), + ) + } else { + None + }; + + (char1, char2, chan) + } +} + +pub(crate) fn eia608_to_text(cc_data: u16) -> String { + unsafe { + let bufsz = ffi::eia608_to_text(std::ptr::null_mut(), 0, cc_data); + let mut data = Vec::with_capacity((bufsz + 1) as usize); + ffi::eia608_to_text(data.as_ptr() as *mut _, (bufsz + 1) as usize, cc_data); + data.set_len(bufsz as usize); + String::from_utf8_unchecked(data) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum CodeSpace { + BasicNA, + SpecialNA, + WestEU, +} + +impl CodeSpace { + fn from_cc_data(cc_data: u16) -> Self { + if is_basicna(cc_data) { + Self::BasicNA + } else if is_specialna(cc_data) { + Self::SpecialNA + } else if is_westeu(cc_data) { + Self::WestEU + } else { + unreachable!() + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Cea608Text { + pub char1: Option<char>, + pub char2: Option<char>, + pub code_space: CodeSpace, + pub chan: i32, +} + +#[derive(Debug)] +pub(crate) enum Cea608 { + Duplicate, + NewMode(i32, Cea608Mode), + EraseDisplay(i32), + EraseNonDisplay(i32), + CarriageReturn(i32), + Backspace(i32), + EndOfCaption(i32), + TabOffset(i32, u32), + Text(Cea608Text), + Preamble(Preamble), + MidRowChange(MidRowChange), +} + +fn decode_control(cc_data: u16) -> Option<Cea608> { + let (cmd, chan) = parse_control(cc_data); + + gst::log!(CAT, "Command for CC {}", chan); + + match cmd { + ffi::eia608_control_t_eia608_control_resume_direct_captioning => { + return Some(Cea608::NewMode(chan, Cea608Mode::PaintOn)); + } + ffi::eia608_control_t_eia608_control_erase_display_memory => { + return Some(Cea608::EraseDisplay(chan)); + } + ffi::eia608_control_t_eia608_control_roll_up_2 => { + return Some(Cea608::NewMode(chan, Cea608Mode::RollUp2)); + } + ffi::eia608_control_t_eia608_control_roll_up_3 => { + return Some(Cea608::NewMode(chan, Cea608Mode::RollUp3)); + } + ffi::eia608_control_t_eia608_control_roll_up_4 => { + return Some(Cea608::NewMode(chan, Cea608Mode::RollUp4)); + } + ffi::eia608_control_t_eia608_control_carriage_return => { + return Some(Cea608::CarriageReturn(chan)); + } + ffi::eia608_control_t_eia608_control_backspace => { + return Some(Cea608::Backspace(chan)); + } + ffi::eia608_control_t_eia608_control_resume_caption_loading => { + return Some(Cea608::NewMode(chan, Cea608Mode::PopOn)); + } + ffi::eia608_control_t_eia608_control_erase_non_displayed_memory => { + return Some(Cea608::EraseNonDisplay(chan)); + } + ffi::eia608_control_t_eia608_control_end_of_caption => { + return Some(Cea608::EndOfCaption(chan)); + } + ffi::eia608_control_t_eia608_tab_offset_0 + | ffi::eia608_control_t_eia608_tab_offset_1 + | ffi::eia608_control_t_eia608_tab_offset_2 + | ffi::eia608_control_t_eia608_tab_offset_3 => { + return Some(Cea608::TabOffset( + chan, + cmd - ffi::eia608_control_t_eia608_tab_offset_0, + )); + } + // TODO + ffi::eia608_control_t_eia608_control_alarm_off + | ffi::eia608_control_t_eia608_control_delete_to_end_of_row => {} + ffi::eia608_control_t_eia608_control_alarm_on + | ffi::eia608_control_t_eia608_control_text_restart + | ffi::eia608_control_t_eia608_control_text_resume_text_display => {} + _ => { + return None; + } + } + None +} + +#[derive(Debug, Default)] +pub(crate) struct Cea608StateTracker { + last_cc_data: Option<u16>, + pending_output: Vec<Cea608>, +} + +impl Cea608StateTracker { + pub(crate) fn push_cc_data(&mut self, cc_data: u16) { + if (is_specialna(cc_data) || is_control(cc_data)) && Some(cc_data) == self.last_cc_data { + gst::log!(CAT, "Skipping duplicate"); + self.pending_output.push(Cea608::Duplicate); + return; + } + + if is_xds(cc_data) { + gst::log!(CAT, "XDS, ignoring"); + } else if is_control(cc_data) { + if let Some(d) = decode_control(cc_data) { + self.pending_output.push(d); + } + } else if is_basicna(cc_data) || is_specialna(cc_data) || is_westeu(cc_data) { + let (char1, char2, chan) = eia608_to_utf8(cc_data); + if char1.is_some() || char2.is_some() { + self.pending_output.push(Cea608::Text(Cea608Text { + char1, + char2, + code_space: CodeSpace::from_cc_data(cc_data), + chan, + })); + } + } else if is_preamble(cc_data) { + self.pending_output + .push(Cea608::Preamble(parse_preamble(cc_data))); + } else if is_midrowchange(cc_data) { + self.pending_output + .push(Cea608::MidRowChange(parse_midrowchange(cc_data))); + } + } + + pub(crate) fn pop(&mut self) -> Option<Cea608> { + self.pending_output.pop() + } + + pub(crate) fn flush(&mut self) { + self.pending_output = vec![]; + } +} diff --git a/video/closedcaption/src/lib.rs b/video/closedcaption/src/lib.rs index 107a940b8..ec99b26e8 100644 --- a/video/closedcaption/src/lib.rs +++ b/video/closedcaption/src/lib.rs @@ -28,6 +28,7 @@ mod ccutils; mod cea608overlay; mod cea608tojson; mod cea608tott; +mod cea608utils; mod jsontovtt; mod line_reader; mod mcc_enc; @@ -42,7 +43,7 @@ mod ttutils; fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { #[cfg(feature = "doc")] - ttutils::Cea608Mode::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty()); + cea608utils::Cea608Mode::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty()); mcc_parse::register(plugin)?; mcc_enc::register(plugin)?; scc_parse::register(plugin)?; diff --git a/video/closedcaption/src/transcriberbin/imp.rs b/video/closedcaption/src/transcriberbin/imp.rs index ae67a477d..1240c137d 100644 --- a/video/closedcaption/src/transcriberbin/imp.rs +++ b/video/closedcaption/src/transcriberbin/imp.rs @@ -6,7 +6,7 @@ // // SPDX-License-Identifier: MPL-2.0 -use crate::ttutils::Cea608Mode; +use crate::cea608utils::Cea608Mode; use anyhow::{anyhow, Error}; use gst::glib; use gst::prelude::*; diff --git a/video/closedcaption/src/tttocea608/imp.rs b/video/closedcaption/src/tttocea608/imp.rs index ac186e27d..2d8b931c7 100644 --- a/video/closedcaption/src/tttocea608/imp.rs +++ b/video/closedcaption/src/tttocea608/imp.rs @@ -15,7 +15,8 @@ use once_cell::sync::Lazy; use crate::ffi; use std::sync::Mutex; -use crate::ttutils::{Cea608Mode, Chunk, Line, Lines, TextStyle}; +use crate::cea608utils::{Cea608Mode, TextStyle}; +use crate::ttutils::{Chunk, Line, Lines}; fn is_punctuation(word: &str) -> bool { word == "." || word == "," || word == "?" || word == "!" || word == ";" || word == ":" diff --git a/video/closedcaption/src/tttojson/imp.rs b/video/closedcaption/src/tttojson/imp.rs index 688213ea9..b8d4228e2 100644 --- a/video/closedcaption/src/tttojson/imp.rs +++ b/video/closedcaption/src/tttojson/imp.rs @@ -15,7 +15,8 @@ use once_cell::sync::Lazy; use std::cmp::min; use std::sync::Mutex; -use crate::ttutils::{Cea608Mode, Chunk, Line, Lines, TextStyle}; +use crate::cea608utils::*; +use crate::ttutils::{Chunk, Line, Lines}; static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| { gst::DebugCategory::new( diff --git a/video/closedcaption/src/ttutils.rs b/video/closedcaption/src/ttutils.rs index 2c6f3a55b..84bd661d1 100644 --- a/video/closedcaption/src/ttutils.rs +++ b/video/closedcaption/src/ttutils.rs @@ -6,49 +6,9 @@ // // SPDX-License-Identifier: MPL-2.0 -use gst::glib; use serde::{Deserialize, Serialize}; -#[derive( - Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum, -)] -#[repr(u32)] -#[enum_type(name = "GstTtToCea608Mode")] -pub enum Cea608Mode { - PopOn, - PaintOn, - RollUp2, - RollUp3, - RollUp4, -} - -#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)] -pub enum TextStyle { - White, - Green, - Blue, - Cyan, - Red, - Yellow, - Magenta, - ItalicWhite, -} - -impl From<u32> for TextStyle { - fn from(val: u32) -> Self { - match val { - 0 => TextStyle::White, - 1 => TextStyle::Green, - 2 => TextStyle::Blue, - 3 => TextStyle::Cyan, - 4 => TextStyle::Red, - 5 => TextStyle::Yellow, - 6 => TextStyle::Magenta, - 7 => TextStyle::ItalicWhite, - _ => TextStyle::White, - } - } -} +use crate::cea608utils::*; // TODO allow indenting chunks #[derive(Clone, Serialize, Deserialize, Debug)] |