closedcaption: use cea608-types for parsing 608 captions instead of libcaption

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1517>
This commit is contained in:
Matthew Waters 2024-03-14 13:41:15 +11:00
parent cc43935036
commit fea85ff9c8
11 changed files with 471 additions and 785 deletions

14
Cargo.lock generated
View file

@ -1002,6 +1002,18 @@ dependencies = [
"image", "image",
] ]
[[package]]
name = "cea608-types"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf75f36b48b2e9701d278a3ab9081abd1b2881d913ad7b3b0e4d9c6e8c59f86"
dependencies = [
"env_logger 0.10.2",
"log",
"once_cell",
"thiserror",
]
[[package]] [[package]]
name = "cea708-types" name = "cea708-types"
version = "0.3.1" version = "0.3.1"
@ -2272,6 +2284,7 @@ dependencies = [
"byteorder", "byteorder",
"cairo-rs", "cairo-rs",
"cc", "cc",
"cea608-types",
"cea708-types", "cea708-types",
"chrono", "chrono",
"either", "either",
@ -2287,6 +2300,7 @@ dependencies = [
"rand", "rand",
"serde", "serde",
"serde_json", "serde_json",
"smallvec",
"uuid", "uuid",
"winnow 0.6.5", "winnow 0.6.5",
] ]

View file

@ -20,12 +20,14 @@ pangocairo.workspace = true
byteorder = "1" byteorder = "1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["raw_value"] } serde_json = { version = "1.0", features = ["raw_value"] }
cea708-types = "0.3" cea708-types = "0.3.1"
cea608-types = "0.1.1"
once_cell.workspace = true once_cell.workspace = true
gst = { workspace = true, features = ["v1_16"]} gst = { workspace = true, features = ["v1_16"]}
gst-base = { workspace = true, features = ["v1_18"]} gst-base = { workspace = true, features = ["v1_18"]}
gst-video = { workspace = true, features = ["v1_16"]} gst-video = { workspace = true, features = ["v1_16"]}
winnow = "0.6" winnow = "0.6"
smallvec = "1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1" pretty_assertions = "1"

View file

@ -6,6 +6,7 @@
// //
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use cea608_types::tables::{Channel, PreambleAddressCode};
use cea708_types::{tables::*, DTVCCPacket}; use cea708_types::{tables::*, DTVCCPacket};
use cea708_types::{CCDataWriter, Framerate}; use cea708_types::{CCDataWriter, Framerate};
use gst::glib; use gst::glib;
@ -15,7 +16,11 @@ use gst::subclass::prelude::*;
use atomic_refcell::AtomicRefCell; use atomic_refcell::AtomicRefCell;
use crate::cea608utils::*; use crate::cea608utils::*;
use crate::cea708utils::{textstyle_foreground_color, textstyle_to_pen_color, Cea708ServiceWriter}; use crate::cea708utils::{
cea608_color_to_foreground_color, textstyle_to_pen_color, Cea708ServiceWriter,
};
use cea608_types::{Cea608, Cea608State as Cea608StateTracker};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -56,7 +61,7 @@ impl Default for Cea608ServiceState {
fn default() -> Self { fn default() -> Self {
Self { Self {
mode: None, mode: None,
base_row: 15, base_row: 14,
} }
} }
} }
@ -116,8 +121,8 @@ impl Cea708ServiceState {
self.pen_attributes.italics = false; self.pen_attributes.italics = false;
} }
fn handle_text(&mut self, text: Cea608Text) { fn handle_text(&mut self, text: cea608_types::Text) {
if text.code_space == CodeSpace::WestEU { if text.needs_backspace {
self.writer.push_codes(&[Code::BS]); self.writer.push_codes(&[Code::BS]);
} }
if let Some(c) = text.char1 { if let Some(c) = text.char1 {
@ -136,18 +141,18 @@ impl Cea708ServiceState {
} }
} }
fn handle_preamble(&mut self, preamble: Preamble) { fn handle_preamble(&mut self, preamble: PreambleAddressCode) {
let mut need_pen_location = false; let mut need_pen_location = false;
// TODO: may need a better algorithm then compressing the top four rows // TODO: may need a better algorithm than compressing the top four rows
let new_row = std::cmp::max(0, preamble.row) as u8; let new_row = std::cmp::max(0, preamble.row());
if self.pen_location.row != new_row { if self.pen_location.row != new_row {
need_pen_location = true; need_pen_location = true;
self.pen_location.row = new_row; self.pen_location.row = new_row;
} }
if self.pen_location.column != preamble.col as u8 { if self.pen_location.column != preamble.column() {
need_pen_location = true; need_pen_location = true;
self.pen_location.column = preamble.col as u8; self.pen_location.column = preamble.column();
} }
if need_pen_location { if need_pen_location {
@ -155,42 +160,44 @@ impl Cea708ServiceState {
} }
let mut need_pen_attributes = false; let mut need_pen_attributes = false;
if self.pen_attributes.italics != preamble.style.is_italics() { if self.pen_attributes.italics != preamble.italics() {
need_pen_attributes = true; need_pen_attributes = true;
self.pen_attributes.italics = preamble.style.is_italics(); self.pen_attributes.italics = preamble.italics();
} }
if self.pen_attributes.underline != (preamble.underline > 0) { if self.pen_attributes.underline != preamble.underline() {
need_pen_attributes = true; need_pen_attributes = true;
self.pen_attributes.underline = preamble.underline > 0; self.pen_attributes.underline = preamble.underline();
} }
if need_pen_attributes { if need_pen_attributes {
self.writer.set_pen_attributes(self.pen_attributes); self.writer.set_pen_attributes(self.pen_attributes);
} }
if self.pen_color.foreground_color != textstyle_foreground_color(preamble.style) { if self.pen_color.foreground_color != cea608_color_to_foreground_color(preamble.color()) {
self.pen_color.foreground_color = textstyle_foreground_color(preamble.style); self.pen_color.foreground_color = cea608_color_to_foreground_color(preamble.color());
self.writer.set_pen_color(self.pen_color); self.writer.set_pen_color(self.pen_color);
} }
} }
fn handle_midrowchange(&mut self, midrowchange: MidRowChange) { fn handle_midrowchange(&mut self, midrowchange: cea608_types::tables::MidRow) {
self.writer.write_char(' '); self.writer.write_char(' ');
if self.pen_color.foreground_color != textstyle_foreground_color(midrowchange.style) { if let Some(color) = midrowchange.color() {
self.pen_color.foreground_color = textstyle_foreground_color(midrowchange.style); if self.pen_color.foreground_color != cea608_color_to_foreground_color(color) {
self.writer.set_pen_color(self.pen_color); self.pen_color.foreground_color = cea608_color_to_foreground_color(color);
self.writer.set_pen_color(self.pen_color);
}
} }
let mut need_pen_attributes = false; let mut need_pen_attributes = false;
if self.pen_attributes.italics != midrowchange.style.is_italics() { if self.pen_attributes.italics != midrowchange.italics() {
need_pen_attributes = true; need_pen_attributes = true;
self.pen_attributes.italics = midrowchange.style.is_italics(); self.pen_attributes.italics = midrowchange.italics();
} }
if self.pen_attributes.underline != midrowchange.underline { if self.pen_attributes.underline != midrowchange.underline() {
need_pen_attributes = true; need_pen_attributes = true;
self.pen_attributes.underline = midrowchange.underline; self.pen_attributes.underline = midrowchange.underline();
} }
if need_pen_attributes { if need_pen_attributes {
@ -287,12 +294,12 @@ enum BufferOrEvent {
} }
impl State { impl State {
fn field_channel_to_index(&self, field: u8, channel: i32) -> usize { fn field_channel_to_index(&self, field: u8, channel: Channel) -> usize {
match (field, channel) { match (field, channel) {
(0, 0 | 2) => 0, (0, Channel::ONE) => 0,
(0, 1 | 3) => 2, (0, Channel::TWO) => 2,
(1, 0 | 2) => 1, (1, Channel::ONE) => 1,
(1, 1 | 3) => 3, (1, Channel::TWO) => 3,
_ => unreachable!(), _ => unreachable!(),
} }
} }
@ -300,12 +307,12 @@ impl State {
fn service_state_from_608_field_channel( fn service_state_from_608_field_channel(
&mut self, &mut self,
field: u8, field: u8,
channel: i32, channel: Channel,
) -> &mut Cea708ServiceState { ) -> &mut Cea708ServiceState {
&mut self.cea708.service_state[self.field_channel_to_index(field, channel)] &mut self.cea708.service_state[self.field_channel_to_index(field, channel)]
} }
fn new_mode(&mut self, field: u8, channel: i32, cea608_mode: Cea608Mode) { fn new_mode(&mut self, field: u8, channel: Channel, cea608_mode: Cea608Mode) {
let idx = self.field_channel_to_index(field, channel); let idx = self.field_channel_to_index(field, channel);
if let Some(old_mode) = self.cea608.service[idx].mode { if let Some(old_mode) = self.cea608.service[idx].mode {
if cea608_mode.is_rollup() if cea608_mode.is_rollup()
@ -345,27 +352,20 @@ impl State {
self.cea708.service_state[idx].new_mode(cea608_mode, base_row); self.cea708.service_state[idx].new_mode(cea608_mode, base_row);
} }
fn handle_cc_data(&mut self, imp: &Cea608ToCea708, field: u8, cc_data: u16) { fn handle_cc_data(&mut self, imp: &Cea608ToCea708, field: u8, cc_data: [u8; 2]) {
self.cea608.tracker[field as usize].push_cc_data(cc_data); if let Ok(Some(cea608)) = self.cea608.tracker[field as usize].decode(cc_data) {
let mut channel = None;
if let Some(cea608) = self.cea608.tracker[field as usize].pop() {
gst::trace!( gst::trace!(
CAT, CAT,
imp: imp, imp: imp,
"have field:{field} channel:{} {cea608:?}", "have field:{field} channel:{:?} {cea608:?}",
cea608.channel() cea608.channel()
); );
if !matches!(cea608, Cea608::Duplicate) {
channel = Some(cea608.channel());
}
match cea608 { match cea608 {
Cea608::Duplicate => (),
Cea608::NewMode(chan, new_mode) => { Cea608::NewMode(chan, new_mode) => {
self.new_mode(field, chan, new_mode); self.new_mode(field, chan, new_mode.into());
} }
Cea608::Text(text) => { Cea608::Text(text) => {
let state = self.service_state_from_608_field_channel(field, text.chan); let state = self.service_state_from_608_field_channel(field, text.channel);
state.handle_text(text); state.handle_text(text);
} }
Cea608::EndOfCaption(chan) => { Cea608::EndOfCaption(chan) => {
@ -373,8 +373,8 @@ impl State {
state.writer.end_of_caption(); state.writer.end_of_caption();
state.writer.etx(); state.writer.etx();
} }
Cea608::Preamble(mut preamble) => { Cea608::Preamble(chan, mut preamble) => {
let idx = self.field_channel_to_index(field, preamble.chan); let idx = self.field_channel_to_index(field, chan);
let rollup_count = self.cea608.service[idx] let rollup_count = self.cea608.service[idx]
.mode .mode
.map(|mode| { .map(|mode| {
@ -388,21 +388,23 @@ impl State {
if rollup_count > 0 { if rollup_count > 0 {
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(ii) // https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(ii)
let old_base_row = self.cea608.service[idx].base_row; let old_base_row = self.cea608.service[idx].base_row;
self.cea608.service[idx].base_row = preamble.row as u8; self.cea608.service[idx].base_row = preamble.row();
let state = self.service_state_from_608_field_channel(field, preamble.chan); let state = self.service_state_from_608_field_channel(field, chan);
if old_base_row != preamble.row as u8 { if old_base_row != preamble.row() {
state state.writer.rollup_preamble(rollup_count, preamble.row());
.writer
.rollup_preamble(rollup_count, preamble.row as u8);
} }
state.pen_location.row = rollup_count - 1; state.pen_location.row = rollup_count - 1;
preamble.row = rollup_count as i32 - 1; preamble = PreambleAddressCode::new(
rollup_count - 1,
preamble.underline(),
preamble.code(),
);
} }
let state = self.service_state_from_608_field_channel(field, preamble.chan); let state = self.service_state_from_608_field_channel(field, chan);
state.handle_preamble(preamble); state.handle_preamble(preamble);
} }
Cea608::MidRowChange(midrowchange) => { Cea608::MidRowChange(chan, midrowchange) => {
let state = self.service_state_from_608_field_channel(field, midrowchange.chan); let state = self.service_state_from_608_field_channel(field, chan);
state.handle_midrowchange(midrowchange); state.handle_midrowchange(midrowchange);
} }
Cea608::Backspace(chan) => { Cea608::Backspace(chan) => {
@ -432,24 +434,23 @@ impl State {
Cea608::TabOffset(chan, count) => { Cea608::TabOffset(chan, count) => {
let state = self.service_state_from_608_field_channel(field, chan); let state = self.service_state_from_608_field_channel(field, chan);
state.pen_location.column = state.pen_location.column =
std::cmp::min(state.pen_location.column + count as u8, 32); std::cmp::min(state.pen_location.column + count, 32);
} }
Cea608::DeleteToEndOfRow(_chan) => (),
} }
if let Some(channel) = channel { let idx = self.field_channel_to_index(field, cea608.channel());
let idx = self.field_channel_to_index(field, channel); if let Some(
if let Some( Cea608Mode::RollUp2
Cea608Mode::RollUp2 | Cea608Mode::RollUp3
| Cea608Mode::RollUp3 | Cea608Mode::RollUp4
| Cea608Mode::RollUp4 | Cea608Mode::PaintOn,
| Cea608Mode::PaintOn, ) = self.cea608.service[idx].mode
) = self.cea608.service[idx].mode {
{ // FIXME: actually track state for when things have changed
// FIXME: actually track state for when things have changed // and we need to send ETX
// and we need to send ETX self.cea708.service_state[idx]
self.cea708.service_state[idx] .writer
.writer .push_codes(&[cea708_types::tables::Code::ETX]);
.push_codes(&[cea708_types::tables::Code::ETX]);
}
} }
} }
} }
@ -529,9 +530,8 @@ impl Cea608ToCea708 {
return Ok(gst::FlowSuccess::Ok); return Ok(gst::FlowSuccess::Ok);
} }
for triple in data.chunks_exact(3) { for triple in data.chunks_exact(3) {
let cc_data = (triple[1] as u16) << 8 | triple[2] as u16;
let field = (triple[0] & 0x80) >> 7; let field = (triple[0] & 0x80) >> 7;
state.handle_cc_data(self, field, cc_data); state.handle_cc_data(self, field, [triple[1], triple[2]]);
} }
data.to_vec() data.to_vec()
} }
@ -559,8 +559,7 @@ impl Cea608ToCea708 {
}; };
let mut s334_1a_data = Vec::with_capacity(data.len() / 2 * 3); let mut s334_1a_data = Vec::with_capacity(data.len() / 2 * 3);
for pair in data.chunks_exact(2) { for pair in data.chunks_exact(2) {
let cc_data = (pair[0] as u16) << 8 | pair[1] as u16; state.handle_cc_data(self, field, [pair[0], pair[1]]);
state.handle_cc_data(self, field, cc_data);
if field == 0 { if field == 0 {
s334_1a_data.push(0x00); s334_1a_data.push(0x00);
} else { } else {

View file

@ -25,10 +25,14 @@
// * The Chunk object could have an "indent" field, that would get translated // * The Chunk object could have an "indent" field, that would get translated
// to tab offsets for small bandwidth savings // to tab offsets for small bandwidth savings
use cea608_types::tables::Channel;
use cea608_types::tables::MidRow;
use gst::glib; use gst::glib;
use gst::prelude::*; use gst::prelude::*;
use gst::subclass::prelude::*; use gst::subclass::prelude::*;
use cea608_types::{Cea608, Cea608State as Cea608StateTracker};
use crate::cea608utils::*; use crate::cea608utils::*;
use crate::ttutils::{Chunk, Line, Lines}; use crate::ttutils::{Chunk, Line, Lines};
@ -248,21 +252,24 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
fn dump( fn dump(
imp: &Cea608ToJson, imp: &Cea608ToJson,
cc_data: u16, cc_data: [u8; 2],
pts: impl Into<Option<gst::ClockTime>>, pts: impl Into<Option<gst::ClockTime>>,
duration: impl Into<Option<gst::ClockTime>>, duration: impl Into<Option<gst::ClockTime>>,
) { ) {
let pts = pts.into(); let pts = pts.into();
let end = pts.opt_add(duration.into()); let end = pts.opt_add(duration.into());
if cc_data != 0x8080 { if cc_data != [0x80, 0x80] {
let Ok(code) = cea608_types::tables::Code::from_data(cc_data) else {
return;
};
gst::debug!( gst::debug!(
CAT, CAT,
imp: imp, imp: imp,
"{} -> {}: {}", "{} -> {}: {:?}",
pts.display(), pts.display(),
end.display(), end.display(),
eia608_to_text(cc_data) code
); );
} else { } else {
gst::trace!( gst::trace!(
@ -346,7 +353,7 @@ impl State {
} }
self.rows.clear(); self.rows.clear();
self.cea608_state.flush(); self.cea608_state.reset();
} else { } else {
for row in self.rows.values() { for row in self.rows.values() {
if !row.is_empty() { if !row.is_empty() {
@ -402,22 +409,18 @@ impl State {
fn handle_preamble( fn handle_preamble(
&mut self, &mut self,
imp: &Cea608ToJson, imp: &Cea608ToJson,
preamble: Preamble, preamble: cea608_types::tables::PreambleAddressCode,
) -> Option<TimestampedLines> { ) -> Option<TimestampedLines> {
if preamble.chan != 0 {
return None;
}
gst::log!(CAT, imp: imp, "preamble: {:?}", preamble); gst::log!(CAT, imp: imp, "preamble: {:?}", preamble);
let drain_roll_up = self.cursor.row != preamble.row as u32; let drain_roll_up = self.cursor.row != preamble.row() as u32;
// In unbuffered mode, we output the whole roll-up window // In unbuffered mode, we output the whole roll-up window
// and need to move it when the preamble relocates it // and need to move it when the preamble relocates it
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(ii) // https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(ii)
if self.settings.unbuffered { if self.settings.unbuffered {
if let Some(mode) = self.mode { if let Some(mode) = self.mode {
if mode.is_rollup() && self.cursor.row != preamble.row as u32 { if mode.is_rollup() && self.cursor.row != preamble.row() as u32 {
let offset = match mode { let offset = match mode {
Cea608Mode::RollUp2 => 1, Cea608Mode::RollUp2 => 1,
Cea608Mode::RollUp3 => 2, Cea608Mode::RollUp3 => 2,
@ -426,7 +429,7 @@ impl State {
}; };
let current_top_row = self.cursor.row.saturating_sub(offset); let current_top_row = self.cursor.row.saturating_sub(offset);
let new_row_offset = preamble.row - self.cursor.row as i32; let new_row_offset = preamble.row() as i32 - self.cursor.row as i32;
for row in current_top_row..self.cursor.row { for row in current_top_row..self.cursor.row {
if let Some(mut row) = self.rows.remove(&row) { if let Some(mut row) = self.rows.remove(&row) {
@ -441,10 +444,22 @@ impl State {
} }
} }
self.cursor.row = preamble.row as u32; self.cursor.row = preamble.row() as u32;
self.cursor.col = preamble.col as usize; self.cursor.col = preamble.column() as usize;
self.cursor.style = preamble.style; self.cursor.underline = preamble.underline();
self.cursor.underline = preamble.underline != 0; self.cursor.style = if preamble.italics() {
TextStyle::ItalicWhite
} else {
match preamble.color() {
cea608_types::tables::Color::White => TextStyle::White,
cea608_types::tables::Color::Green => TextStyle::Green,
cea608_types::tables::Color::Blue => TextStyle::Blue,
cea608_types::tables::Color::Cyan => TextStyle::Cyan,
cea608_types::tables::Color::Red => TextStyle::Red,
cea608_types::tables::Color::Yellow => TextStyle::Yellow,
cea608_types::tables::Color::Magenta => TextStyle::Magenta,
}
};
if let Some(mode) = self.mode { if let Some(mode) = self.mode {
match mode { match mode {
@ -487,13 +502,9 @@ impl State {
} }
} }
fn handle_text(&mut self, imp: &Cea608ToJson, text: Cea608Text) { fn handle_text(&mut self, imp: &Cea608ToJson, text: cea608_types::Text) {
if text.chan != 0 {
return;
}
if let Some(row) = self.rows.get_mut(&self.cursor.row) { if let Some(row) = self.rows.get_mut(&self.cursor.row) {
if text.code_space == CodeSpace::WestEU { if text.needs_backspace {
row.pop(&mut self.cursor); row.pop(&mut self.cursor);
} }
@ -517,11 +528,13 @@ impl State {
} }
} }
fn handle_midrowchange(&mut self, midrowchange: MidRowChange) { fn handle_midrowchange(&mut self, midrowchange: MidRow) {
if let Some(row) = self.rows.get_mut(&self.cursor.row) { if let Some(row) = self.rows.get_mut(&self.cursor.row) {
if midrowchange.chan == 0 { row.push_midrow(
row.push_midrow(&mut self.cursor, midrowchange.style, midrowchange.underline); &mut self.cursor,
} midrowchange.into(),
midrowchange.underline(),
);
} }
} }
@ -530,129 +543,115 @@ impl State {
imp: &Cea608ToJson, imp: &Cea608ToJson,
pts: Option<gst::ClockTime>, pts: Option<gst::ClockTime>,
duration: Option<gst::ClockTime>, duration: Option<gst::ClockTime>,
cc_data: u16, cc_data: [u8; 2],
) -> Option<TimestampedLines> { ) -> Option<TimestampedLines> {
self.cea608_state.push_cc_data(cc_data); let Ok(Some(cea608)) = self.cea608_state.decode(cc_data) else {
return None;
};
while let Some(cea608) = self.cea608_state.pop() { self.current_pts = pts;
if matches!(cea608, Cea608::Duplicate) { self.current_duration = duration;
gst::log!(CAT, imp: imp, "Skipping duplicate");
return None; if cea608.channel() != Channel::ONE {
return None;
}
match cea608 {
Cea608::EraseDisplay(_chan) => {
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) => return self.update_mode(imp, mode.into()),
Cea608::CarriageReturn(_chan) => {
gst::log!(CAT, imp: imp, "carriage return");
self.current_pts = pts; if let Some(mode) = self.mode {
self.current_duration = duration; // https://www.law.cornell.edu/cfr/text/47/79.101 (f)(2)(i) (f)(3)(i)
if mode.is_rollup() {
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 { let ret = if self.settings.unbuffered {
self.drain(imp, true) 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 { } else {
let ret = self.drain_pending(imp); let ret = self.drain(imp, true);
self.pending_lines = self.drain(imp, true); self.carriage_return = Some(true);
ret ret
}; };
return 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),
} }
Cea608::Backspace(_chan) => {
if let Some(row) = self.rows.get_mut(&self.cursor.row) {
row.pop(&mut self.cursor);
}
}
Cea608::EraseNonDisplay(_chan) => {
if self.mode == Some(Cea608Mode::PopOn) {
self.rows.clear();
}
}
Cea608::EndOfCaption(_chan) => {
// 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) => {
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);
}
}
}
// TODO: implement
Cea608::DeleteToEndOfRow(_chan) => (),
Cea608::Preamble(_chan, preamble) => return self.handle_preamble(imp, preamble),
Cea608::MidRowChange(_chan, change) => self.handle_midrowchange(change),
} }
None None
} }
@ -717,7 +716,7 @@ impl Cea608ToJson {
return Ok(gst::FlowSuccess::Ok); return Ok(gst::FlowSuccess::Ok);
} }
let cc_data = (data[0] as u16) << 8 | data[1] as u16; let cc_data = [data[0], data[1]];
dump(self, cc_data, pts, duration); dump(self, cc_data, pts, duration);

View file

@ -7,19 +7,8 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use gst::glib; use gst::glib;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize}; 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( #[derive(
Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum, Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum,
)] )]
@ -33,6 +22,18 @@ pub enum Cea608Mode {
RollUp4, RollUp4,
} }
impl From<cea608_types::Mode> for Cea608Mode {
fn from(value: cea608_types::Mode) -> Self {
match value {
cea608_types::Mode::PopOn => Self::PopOn,
cea608_types::Mode::PaintOn => Self::PaintOn,
cea608_types::Mode::RollUp2 => Self::RollUp2,
cea608_types::Mode::RollUp3 => Self::RollUp3,
cea608_types::Mode::RollUp4 => Self::RollUp4,
}
}
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
pub enum TextStyle { pub enum TextStyle {
#[default] #[default]
@ -50,6 +51,53 @@ impl TextStyle {
pub fn is_italics(&self) -> bool { pub fn is_italics(&self) -> bool {
*self == TextStyle::ItalicWhite *self == TextStyle::ItalicWhite
} }
pub fn to_cea608_color(self) -> Option<cea608_types::tables::Color> {
match self {
Self::White => Some(cea608_types::tables::Color::White),
Self::Green => Some(cea608_types::tables::Color::Green),
Self::Blue => Some(cea608_types::tables::Color::Blue),
Self::Cyan => Some(cea608_types::tables::Color::Cyan),
Self::Red => Some(cea608_types::tables::Color::Red),
Self::Yellow => Some(cea608_types::tables::Color::Yellow),
Self::Magenta => Some(cea608_types::tables::Color::Magenta),
Self::ItalicWhite => None,
}
}
}
impl From<cea608_types::tables::PreambleAddressCode> for TextStyle {
fn from(value: cea608_types::tables::PreambleAddressCode) -> Self {
value.color().into()
}
}
impl From<cea608_types::tables::MidRow> for TextStyle {
fn from(value: cea608_types::tables::MidRow) -> Self {
if value.italics() {
Self::ItalicWhite
} else {
// XXX: MidRow may not change color
value
.color()
.unwrap_or(cea608_types::tables::Color::White)
.into()
}
}
}
impl From<cea608_types::tables::Color> for TextStyle {
fn from(value: cea608_types::tables::Color) -> Self {
match value {
cea608_types::tables::Color::White => Self::White,
cea608_types::tables::Color::Green => Self::Green,
cea608_types::tables::Color::Blue => Self::Blue,
cea608_types::tables::Color::Cyan => Self::Cyan,
cea608_types::tables::Color::Red => Self::Red,
cea608_types::tables::Color::Yellow => Self::Yellow,
cea608_types::tables::Color::Magenta => Self::Magenta,
}
}
} }
impl From<u32> for TextStyle { impl From<u32> for TextStyle {
@ -67,329 +115,3 @@ impl From<u32> for TextStyle {
} }
} }
} }
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),
}
impl Cea608 {
pub fn channel(&self) -> i32 {
match self {
Self::Duplicate => 0,
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::Text(text) => text.chan,
Self::Preamble(preamble) => preamble.chan,
Self::MidRowChange(midrowchange) => midrowchange.chan,
}
}
}
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>,
last_channel: i32,
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.last_channel = d.channel();
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() {
let chan = if is_basicna(cc_data) {
/* basicna doesn't include a channel indicator so we track the last generated
* one */
self.last_channel
} else {
chan
};
self.pending_output.push(Cea608::Text(Cea608Text {
char1,
char2,
code_space: CodeSpace::from_cc_data(cc_data),
chan,
}));
}
} else if is_preamble(cc_data) {
let preamble = parse_preamble(cc_data);
self.last_channel = preamble.chan;
self.pending_output.push(Cea608::Preamble(preamble));
} else if is_midrowchange(cc_data) {
let midrowchange = parse_midrowchange(cc_data);
self.last_channel = midrowchange.chan;
self.pending_output.push(Cea608::MidRowChange(midrowchange));
}
}
pub(crate) fn pop(&mut self) -> Option<Cea608> {
self.pending_output.pop()
}
pub(crate) fn flush(&mut self) {
self.pending_output = vec![];
}
}

View file

@ -33,39 +33,55 @@ pub enum Cea708Mode {
RollUp, RollUp,
} }
pub fn textstyle_foreground_color(style: TextStyle) -> Color { pub fn textstyle_foreground_color(style: TextStyle) -> cea708_types::tables::Color {
match style { let color = match style {
TextStyle::Red => Color { TextStyle::Red => cea608_types::tables::Color::Red,
TextStyle::Blue => cea608_types::tables::Color::Blue,
TextStyle::Cyan => cea608_types::tables::Color::Cyan,
TextStyle::White => cea608_types::tables::Color::White,
TextStyle::Green => cea608_types::tables::Color::Green,
TextStyle::Yellow => cea608_types::tables::Color::Yellow,
TextStyle::Magenta => cea608_types::tables::Color::Magenta,
TextStyle::ItalicWhite => cea608_types::tables::Color::White,
};
cea608_color_to_foreground_color(color)
}
pub fn cea608_color_to_foreground_color(
color: cea608_types::tables::Color,
) -> cea708_types::tables::Color {
match color {
cea608_types::tables::Color::Red => cea708_types::tables::Color {
r: ColorValue::Full, r: ColorValue::Full,
g: ColorValue::None, g: ColorValue::None,
b: ColorValue::None, b: ColorValue::None,
}, },
TextStyle::Green => Color { cea608_types::tables::Color::Green => cea708_types::tables::Color {
r: ColorValue::None, r: ColorValue::None,
g: ColorValue::Full, g: ColorValue::Full,
b: ColorValue::None, b: ColorValue::None,
}, },
TextStyle::Blue => Color { cea608_types::tables::Color::Blue => cea708_types::tables::Color {
r: ColorValue::None, r: ColorValue::None,
g: ColorValue::None, g: ColorValue::None,
b: ColorValue::Full, b: ColorValue::Full,
}, },
TextStyle::Cyan => Color { cea608_types::tables::Color::Cyan => cea708_types::tables::Color {
r: ColorValue::None, r: ColorValue::None,
g: ColorValue::Full, g: ColorValue::Full,
b: ColorValue::Full, b: ColorValue::Full,
}, },
TextStyle::Yellow => Color { cea608_types::tables::Color::Yellow => cea708_types::tables::Color {
r: ColorValue::Full, r: ColorValue::Full,
g: ColorValue::Full, g: ColorValue::Full,
b: ColorValue::None, b: ColorValue::None,
}, },
TextStyle::Magenta => Color { cea608_types::tables::Color::Magenta => cea708_types::tables::Color {
r: ColorValue::Full, r: ColorValue::Full,
g: ColorValue::None, g: ColorValue::None,
b: ColorValue::Full, b: ColorValue::Full,
}, },
TextStyle::White | TextStyle::ItalicWhite => Color { cea608_types::tables::Color::White => cea708_types::tables::Color {
r: ColorValue::Full, r: ColorValue::Full,
g: ColorValue::Full, g: ColorValue::Full,
b: ColorValue::Full, b: ColorValue::Full,
@ -213,7 +229,7 @@ impl Cea708ServiceWriter {
pub fn rollup_preamble(&mut self, rollup_count: u8, base_row: u8) { pub fn rollup_preamble(&mut self, rollup_count: u8, base_row: u8) {
let base_row = std::cmp::max(rollup_count, base_row); let base_row = std::cmp::max(rollup_count, base_row);
let anchor_vertical = (base_row as u32 * 100 / 15) as u8; let anchor_vertical = (base_row as u32 * 100 / 14) as u8;
gst::trace!( gst::trace!(
CAT, CAT,
"rollup_preamble base {base_row} count {rollup_count}, anchor-v {anchor_vertical}" "rollup_preamble base {base_row} count {rollup_count}, anchor-v {anchor_vertical}"

View file

@ -16,7 +16,6 @@ use std::sync::Mutex;
use crate::cea608utils::Cea608Mode; use crate::cea608utils::Cea608Mode;
use crate::cea608utils::TextStyle; use crate::cea608utils::TextStyle;
use crate::ffi;
use crate::ttutils::Chunk; use crate::ttutils::Chunk;
use crate::ttutils::Line; use crate::ttutils::Line;
use crate::ttutils::Lines; use crate::ttutils::Lines;
@ -75,40 +74,30 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
) )
}); });
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 cc_data_buffer( fn cc_data_buffer(
imp: &TtToCea608, imp: &TtToCea608,
cc_data: u16, cc_data: [u8; 2],
pts: gst::ClockTime, pts: gst::ClockTime,
duration: gst::ClockTime, duration: gst::ClockTime,
) -> gst::Buffer { ) -> gst::Buffer {
let mut ret = gst::Buffer::with_size(2).unwrap(); let mut ret = gst::Buffer::with_size(2).unwrap();
let buf_mut = ret.get_mut().unwrap(); let buf_mut = ret.get_mut().unwrap();
let data = cc_data.to_be_bytes();
if cc_data != 0x8080 { if cc_data != [0x80, 0x80] {
let code = cea608_types::tables::Code::from_data(cc_data);
gst::log!( gst::log!(
CAT, CAT,
imp: imp, imp: imp,
"{} -> {}: {}", "{} -> {}: {:?}",
pts, pts,
pts + duration, pts + duration,
eia608_to_text(cc_data) code
); );
} else { } else {
gst::trace!(CAT, imp: imp, "{} -> {}: padding", pts, pts + duration); gst::trace!(CAT, imp: imp, "{} -> {}: padding", pts, pts + duration);
} }
buf_mut.copy_from_slice(0, &data).unwrap(); buf_mut.copy_from_slice(0, &cc_data).unwrap();
buf_mut.set_pts(pts); buf_mut.set_pts(pts);
buf_mut.set_duration(duration); buf_mut.set_duration(duration);
@ -301,6 +290,7 @@ impl TtToCea608 {
let framerate = s.get::<gst::Fraction>("framerate").unwrap(); let framerate = s.get::<gst::Fraction>("framerate").unwrap();
state.framerate = framerate; state.framerate = framerate;
state.translator.set_framerate(framerate); state.translator.set_framerate(framerate);
state.translator.set_caption_id(cea608_types::Id::CC1);
let upstream_caps = e.caps(); let upstream_caps = e.caps();
let s = upstream_caps.structure(0).unwrap(); let s = upstream_caps.structure(0).unwrap();
@ -362,7 +352,9 @@ impl TtToCea608 {
*state = State::default(); *state = State::default();
state.translator.set_mode(settings.mode); state.translator.set_mode(settings.mode);
state.translator.set_framerate(framerate); state.translator.set_framerate(framerate);
state.translator.set_origin_column(settings.origin_column); state
.translator
.set_origin_column(settings.origin_column as u8);
state state
.translator .translator
.set_roll_up_timeout(settings.roll_up_timeout); .set_roll_up_timeout(settings.roll_up_timeout);
@ -606,7 +598,9 @@ impl ElementImpl for TtToCea608 {
*state = State::default(); *state = State::default();
state.force_clear = false; state.force_clear = false;
state.translator.set_mode(settings.mode); state.translator.set_mode(settings.mode);
state.translator.set_origin_column(settings.origin_column); state
.translator
.set_origin_column(settings.origin_column as u8);
state.translator.set_framerate(framerate); state.translator.set_framerate(framerate);
state state
.translator .translator

View file

@ -10,9 +10,7 @@ use gst::prelude::*;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::collections::VecDeque; use std::collections::VecDeque;
use crate::ffi; use crate::cea608utils::{Cea608Mode, TextStyle};
use crate::cea608utils::{is_basicna, is_specialna, is_westeu, Cea608Mode, TextStyle};
use crate::ttutils::{Chunk, Lines}; use crate::ttutils::{Chunk, Lines};
pub const DEFAULT_FPS_N: i32 = 30; pub const DEFAULT_FPS_N: i32 = 30;
@ -26,65 +24,24 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
) )
}); });
static SPACE: Lazy<u16> = Lazy::new(|| eia608_from_utf8_1(&[0x20, 0, 0, 0, 0]));
fn is_punctuation(word: &str) -> bool { fn is_punctuation(word: &str) -> bool {
word == "." || word == "," || word == "?" || word == "!" || word == ";" || word == ":" word == "." || word == "," || word == "?" || word == "!" || word == ";" || word == ":"
} }
#[allow(clippy::trivially_copy_pass_by_ref)] fn peek_word_length(chars: std::iter::Peekable<std::str::Chars>) -> u8 {
fn eia608_from_utf8_1(c: &[u8; 5]) -> u16 { chars.take_while(|c| !c.is_ascii_whitespace()).count() as u8
assert!(c[4] == 0);
unsafe { ffi::eia608_from_utf8_1(c.as_ptr() as *const _, 0) }
}
fn eia608_row_column_preamble(row: i32, col: i32, underline: bool) -> u16 {
unsafe {
/* Hardcoded chan */
ffi::eia608_row_column_pramble(row, col, 0, underline as i32)
}
}
fn eia608_row_style_preamble(row: i32, style: u32, underline: bool) -> u16 {
unsafe {
/* Hardcoded chan */
ffi::eia608_row_style_pramble(row, 0, style, underline as i32)
}
}
fn eia608_midrow_change(style: u32, underline: bool) -> u16 {
unsafe {
/* Hardcoded chan and underline */
ffi::eia608_midrow_change(0, style, underline as i32)
}
}
fn eia608_control_command(cmd: ffi::eia608_control_t) -> u16 {
unsafe { ffi::eia608_control_command(cmd, 0) }
}
fn eia608_from_basicna(bna1: u16, bna2: u16) -> u16 {
unsafe { ffi::eia608_from_basicna(bna1, bna2) }
}
fn erase_non_displayed_memory() -> u16 {
eia608_control_command(ffi::eia608_control_t_eia608_control_erase_non_displayed_memory)
}
fn peek_word_length(chars: std::iter::Peekable<std::str::Chars>) -> u32 {
chars.take_while(|c| !c.is_ascii_whitespace()).count() as u32
} }
#[derive(Debug)] #[derive(Debug)]
pub struct TimedCea608 { pub struct TimedCea608 {
pub cea608: u16, pub cea608: [u8; 2],
pub frame_no: u64, pub frame_no: u64,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct TextToCea608 { pub struct TextToCea608 {
// settings // settings
origin_column: u32, origin_column: u8,
framerate: gst::Fraction, framerate: gst::Fraction,
roll_up_timeout: Option<gst::ClockTime>, roll_up_timeout: Option<gst::ClockTime>,
// state // state
@ -96,6 +53,8 @@ pub struct TextToCea608 {
underline: bool, underline: bool,
column: u8, column: u8,
mode: Cea608Mode, mode: Cea608Mode,
writer: cea608_types::Cea608Writer,
caption_id: cea608_types::Id,
} }
impl Default for TextToCea608 { impl Default for TextToCea608 {
@ -112,6 +71,8 @@ impl Default for TextToCea608 {
style: TextStyle::White, style: TextStyle::White,
underline: false, underline: false,
mode: Cea608Mode::PopOn, mode: Cea608Mode::PopOn,
writer: cea608_types::Cea608Writer::default(),
caption_id: cea608_types::Id::CC1,
} }
} }
} }
@ -121,10 +82,14 @@ impl TextToCea608 {
self.mode = mode; self.mode = mode;
if self.mode != Cea608Mode::PopOn { if self.mode != Cea608Mode::PopOn {
self.send_roll_up_preamble = true; self.send_roll_up_preamble = true;
self.column = self.origin_column as u8; self.column = self.origin_column;
} }
} }
pub fn set_caption_id(&mut self, id: cea608_types::Id) {
self.caption_id = id;
}
pub fn set_framerate(&mut self, framerate: gst::Fraction) { pub fn set_framerate(&mut self, framerate: gst::Fraction) {
self.framerate = framerate; self.framerate = framerate;
} }
@ -137,7 +102,7 @@ impl TextToCea608 {
self.roll_up_timeout = timeout; self.roll_up_timeout = timeout;
} }
pub fn set_origin_column(&mut self, origin_column: u32) { pub fn set_origin_column(&mut self, origin_column: u8) {
self.origin_column = origin_column self.origin_column = origin_column
} }
@ -176,7 +141,7 @@ impl TextToCea608 {
false false
} }
fn cc_data(&mut self, cc_data: u16) { fn cc_data(&mut self, cc_data: [u8; 2]) {
self.check_erase_display(); self.check_erase_display();
self.output_frames.push_back(TimedCea608 { self.output_frames.push_back(TimedCea608 {
@ -190,101 +155,102 @@ impl TextToCea608 {
fn pad(&mut self, frame_no: u64) { fn pad(&mut self, frame_no: u64) {
while self.last_frame_no < frame_no { while self.last_frame_no < frame_no {
if !self.check_erase_display() { if !self.check_erase_display() {
self.cc_data(0x8080); self.cc_data([0x80, 0x80]);
} }
} }
} }
fn control_code(&mut self, control: cea608_types::tables::Control) {
let code = cea608_types::tables::ControlCode::new(
self.caption_id.field(),
self.caption_id.channel(),
control,
);
let mut vec = smallvec::SmallVec::<[u8; 2]>::new();
let _ = cea608_types::tables::Code::Control(code).write(&mut vec);
self.cc_data([vec[0], vec[1]]);
}
fn erase_non_displayed_memory(&mut self) {
self.control_code(cea608_types::tables::Control::EraseNonDisplayedMemory);
}
fn resume_caption_loading(&mut self) { fn resume_caption_loading(&mut self) {
self.cc_data(eia608_control_command( self.control_code(cea608_types::tables::Control::ResumeCaptionLoading);
ffi::eia608_control_t_eia608_control_resume_caption_loading,
))
} }
fn resume_direct_captioning(&mut self) { fn resume_direct_captioning(&mut self) {
self.cc_data(eia608_control_command( self.control_code(cea608_types::tables::Control::ResumeDirectionCaptioning);
ffi::eia608_control_t_eia608_control_resume_direct_captioning,
))
} }
fn delete_to_end_of_row(&mut self) { fn delete_to_end_of_row(&mut self) {
self.cc_data(eia608_control_command( self.control_code(cea608_types::tables::Control::DeleteToEndOfRow);
ffi::eia608_control_t_eia608_control_delete_to_end_of_row,
))
} }
fn roll_up_2(&mut self) { fn roll_up_2(&mut self) {
self.cc_data(eia608_control_command( self.control_code(cea608_types::tables::Control::RollUp2);
ffi::eia608_control_t_eia608_control_roll_up_2,
))
} }
fn roll_up_3(&mut self) { fn roll_up_3(&mut self) {
self.cc_data(eia608_control_command( self.control_code(cea608_types::tables::Control::RollUp3);
ffi::eia608_control_t_eia608_control_roll_up_3,
))
} }
fn roll_up_4(&mut self) { fn roll_up_4(&mut self) {
self.cc_data(eia608_control_command( self.control_code(cea608_types::tables::Control::RollUp4);
ffi::eia608_control_t_eia608_control_roll_up_4,
))
} }
fn carriage_return(&mut self) { fn carriage_return(&mut self) {
self.cc_data(eia608_control_command( self.control_code(cea608_types::tables::Control::CarriageReturn);
ffi::eia608_control_t_eia608_control_carriage_return,
))
} }
fn end_of_caption(&mut self) { fn end_of_caption(&mut self) {
self.cc_data(eia608_control_command( self.control_code(cea608_types::tables::Control::EndOfCaption);
ffi::eia608_control_t_eia608_control_end_of_caption,
))
} }
fn tab_offset(&mut self, offset: u32) { fn tab_offset(&mut self, offset: u8) {
match offset { let Some(offset) = cea608_types::tables::Control::tab_offset(offset) else {
0 => (), return;
1 => self.cc_data(eia608_control_command( };
ffi::eia608_control_t_eia608_tab_offset_1, self.control_code(offset);
)),
2 => self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_tab_offset_2,
)),
3 => self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_tab_offset_3,
)),
_ => unreachable!(),
}
} }
fn preamble_indent(&mut self, row: i32, col: i32, underline: bool) { fn preamble_indent(&mut self, row: u8, col: u8, underline: bool) {
self.cc_data(eia608_row_column_preamble(row, col, underline)) let Some(preamble) = cea608_types::tables::PreambleType::from_indent(col) else {
return;
};
self.control_code(cea608_types::tables::Control::PreambleAddress(
cea608_types::tables::PreambleAddressCode::new(row, underline, preamble),
));
} }
fn preamble_style(&mut self, row: i32, style: u32, underline: bool) { fn preamble_style(&mut self, row: u8, style: TextStyle, underline: bool) {
self.cc_data(eia608_row_style_preamble(row, style, underline)) let preamble = if style.is_italics() {
cea608_types::tables::PreambleType::WhiteItalics
} else {
cea608_types::tables::PreambleType::Color(style.to_cea608_color().unwrap())
};
self.control_code(cea608_types::tables::Control::PreambleAddress(
cea608_types::tables::PreambleAddressCode::new(row, underline, preamble),
));
} }
fn midrow_change(&mut self, style: u32, underline: bool) { fn midrow_change(&mut self, style: TextStyle, underline: bool) {
self.cc_data(eia608_midrow_change(style, underline)) let midrow = if style.is_italics() {
} cea608_types::tables::MidRow::new_italics(underline)
} else {
fn bna(&mut self, bna1: u16, bna2: u16) { cea608_types::tables::MidRow::new_color(style.to_cea608_color().unwrap(), underline)
self.cc_data(eia608_from_basicna(bna1, bna2)) };
self.control_code(cea608_types::tables::Control::MidRow(midrow));
} }
fn erase_display_memory(&mut self) { fn erase_display_memory(&mut self) {
self.erase_display_frame_no = None; self.erase_display_frame_no = None;
self.cc_data(eia608_control_command( self.control_code(cea608_types::tables::Control::EraseDisplayedMemory);
ffi::eia608_control_t_eia608_control_erase_display_memory,
))
} }
fn open_chunk(&mut self, chunk: &Chunk, col: u32) -> bool { fn open_chunk(&mut self, chunk: &Chunk, col: u8) -> bool {
if (chunk.style != self.style || chunk.underline != self.underline) && col < 31 { if (chunk.style != self.style || chunk.underline != self.underline) && col < 31 {
self.midrow_change(chunk.style as u32, chunk.underline); self.midrow_change(chunk.style, chunk.underline);
self.style = chunk.style; self.style = chunk.style;
self.underline = chunk.underline; self.underline = chunk.underline;
true true
@ -297,8 +263,8 @@ impl TextToCea608 {
fn open_line( fn open_line(
&mut self, &mut self,
chunk: &Chunk, chunk: &Chunk,
col: &mut u32, col: &mut u8,
row: i32, row: u8,
carriage_return: Option<bool>, carriage_return: Option<bool>,
) -> bool { ) -> bool {
let mut ret = true; let mut ret = true;
@ -332,7 +298,7 @@ impl TextToCea608 {
} }
if chunk.style != TextStyle::White && indent == 0 { if chunk.style != TextStyle::White && indent == 0 {
self.preamble_style(row, chunk.style as u32, chunk.underline); self.preamble_style(row, chunk.style, chunk.underline);
self.style = chunk.style; self.style = chunk.style;
} else { } else {
if chunk.style != TextStyle::White { if chunk.style != TextStyle::White {
@ -346,7 +312,7 @@ impl TextToCea608 {
} }
self.style = TextStyle::White; self.style = TextStyle::White;
self.preamble_indent(row, (indent * 4) as i32, chunk.underline); self.preamble_indent(row, indent * 4, chunk.underline);
} }
if self.mode == Cea608Mode::PaintOn { if self.mode == Cea608Mode::PaintOn {
@ -393,7 +359,7 @@ impl TextToCea608 {
let mut col = if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn { let mut col = if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
0 0
} else { } else {
self.column as u32 self.column
}; };
self.pad(frame_no); self.pad(frame_no);
@ -430,19 +396,17 @@ impl TextToCea608 {
if !lines.lines.is_empty() { if !lines.lines.is_empty() {
if self.mode == Cea608Mode::PopOn { if self.mode == Cea608Mode::PopOn {
self.resume_caption_loading(); self.resume_caption_loading();
self.cc_data(erase_non_displayed_memory()); self.erase_non_displayed_memory();
} else if self.mode == Cea608Mode::PaintOn { } else if self.mode == Cea608Mode::PaintOn {
self.resume_direct_captioning(); self.resume_direct_captioning();
} }
} }
let mut prev_char = 0;
for line in &lines.lines { for line in &lines.lines {
gst::log!(CAT, "Processing {:?}", line); gst::log!(CAT, "Processing {:?}", line);
if let Some(line_row) = line.row { if let Some(line_row) = line.row {
row = line_row; row = line_row as u8;
} }
if row > 14 { if row > 14 {
@ -454,21 +418,20 @@ impl TextToCea608 {
if self.mode != Cea608Mode::PopOn && self.mode != Cea608Mode::PaintOn { if self.mode != Cea608Mode::PopOn && self.mode != Cea608Mode::PaintOn {
self.send_roll_up_preamble = true; self.send_roll_up_preamble = true;
} }
col = line_column; col = line_column as u8;
} else if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn { } else if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
col = origin_column; col = origin_column;
} }
for (j, chunk) in line.chunks.iter().enumerate() { for (j, chunk) in line.chunks.iter().enumerate() {
let mut prepend_space = true; let mut prepend_space = true;
if prev_char != 0 { while self.writer.n_codes() > 0 {
self.cc_data(prev_char); let data = self.writer.pop();
prev_char = 0; self.cc_data(data);
} }
if j == 0 { if j == 0 {
prepend_space = prepend_space = self.open_line(chunk, &mut col, row, line.carriage_return);
self.open_line(chunk, &mut col, row as i32, line.carriage_return);
} else if self.open_chunk(chunk, col) { } else if self.open_chunk(chunk, col) {
prepend_space = false; prepend_space = false;
col += 1; col += 1;
@ -495,40 +458,22 @@ impl TextToCea608 {
continue; continue;
} }
let mut encoded = [0; 5]; let code = cea608_types::tables::Code::from_char(
c.encode_utf8(&mut encoded); c,
let mut cc_data = eia608_from_utf8_1(&encoded); cea608_types::tables::Channel::ONE,
)
if cc_data == 0 { .unwrap_or_else(|| {
gst::warning!(CAT, "Not translating UTF8: {}", c); gst::warning!(CAT, "Not translating UTF8: {}", c);
cc_data = *SPACE; cea608_types::tables::Code::Space
} });
if is_basicna(prev_char) { self.writer.push(code);
if is_basicna(cc_data) {
self.bna(prev_char, cc_data); if let cea608_types::tables::Code::Control(_) = code {
} else if is_westeu(cc_data) { while self.writer.n_codes() > 0 {
// extended characters overwrite the previous character, let data = self.writer.pop();
// so insert a dummy char then write the extended char self.cc_data(data);
self.bna(prev_char, *SPACE);
self.cc_data(cc_data);
} else {
self.cc_data(prev_char);
self.cc_data(cc_data);
} }
prev_char = 0;
} else if is_westeu(cc_data) {
// extended characters overwrite the previous character,
// so insert a dummy char then write the extended char
self.cc_data(*SPACE);
self.cc_data(cc_data);
} else if is_basicna(cc_data) {
prev_char = cc_data;
} else {
self.cc_data(cc_data);
}
if is_specialna(cc_data) {
// adapted from libcaption's generation code: // adapted from libcaption's generation code:
// specialna are treated as control characters. Duplicated control characters are discarded // specialna are treated as control characters. Duplicated control characters are discarded
// So we write a resume after a specialna as a noop control command to break repetition detection // So we write a resume after a specialna as a noop control command to break repetition detection
@ -560,12 +505,12 @@ impl TextToCea608 {
if (next_word_length <= 32 - origin_column && col + next_word_length > 31) if (next_word_length <= 32 - origin_column && col + next_word_length > 31)
|| col > 31 || col > 31
{ {
if prev_char != 0 { while self.writer.n_codes() > 0 {
self.cc_data(prev_char); let data = self.writer.pop();
prev_char = 0; self.cc_data(data);
} }
self.open_line(chunk, &mut col, row as i32, Some(true)); self.open_line(chunk, &mut col, row, Some(true));
} }
} else if col > 31 { } else if col > 31 {
if chars.peek().is_some() { if chars.peek().is_some() {
@ -577,17 +522,18 @@ impl TextToCea608 {
} }
if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn { if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
if prev_char != 0 { while self.writer.n_codes() > 0 {
self.cc_data(prev_char); let data = self.writer.pop();
prev_char = 0; self.cc_data(data);
} }
row += 1; row += 1;
} }
} }
if !lines.lines.is_empty() { if !lines.lines.is_empty() {
if prev_char != 0 { while self.writer.n_codes() > 0 {
self.cc_data(prev_char); let data = self.writer.pop();
self.cc_data(data);
} }
if self.mode == Cea608Mode::PopOn { if self.mode == Cea608Mode::PopOn {
@ -597,7 +543,7 @@ impl TextToCea608 {
} }
} }
self.column = col as u8; self.column = col;
if self.mode == Cea608Mode::PopOn { if self.mode == Cea608Mode::PopOn {
self.erase_display_frame_no = self.erase_display_frame_no =

View file

@ -360,7 +360,13 @@ impl TtToCea708 {
.translator .translator
.set_roll_up_timeout(settings.roll_up_timeout); .set_roll_up_timeout(settings.roll_up_timeout);
state.translator.set_roll_up_count(settings.roll_up_rows); state.translator.set_roll_up_count(settings.roll_up_rows);
state.translator.set_cea608_channel(settings.cea608_channel); state
.translator
.set_cea608_channel(if settings.cea608_channel < 1 {
None
} else {
Some(cea608_types::Id::from_value(settings.cea608_channel as i8))
});
state.translator.set_service_no(settings.service_no); state.translator.set_service_no(settings.service_no);
state.translator.flush(); state.translator.flush();
@ -526,7 +532,13 @@ impl ObjectImpl for TtToCea708 {
let mut settings = self.settings.lock().unwrap(); let mut settings = self.settings.lock().unwrap();
let channel = value.get::<u32>().expect("type checked upstream") as u8; let channel = value.get::<u32>().expect("type checked upstream") as u8;
settings.cea608_channel = channel; settings.cea608_channel = channel;
state.translator.set_cea608_channel(channel); state
.translator
.set_cea608_channel(if settings.cea608_channel < 1 {
None
} else {
Some(cea608_types::Id::from_value(settings.cea608_channel as i8))
});
} }
"roll-up-rows" => { "roll-up-rows" => {
let mut state = self.state.lock().unwrap(); let mut state = self.state.lock().unwrap();
@ -657,7 +669,13 @@ impl ElementImpl for TtToCea708 {
.translator .translator
.set_roll_up_timeout(settings.roll_up_timeout); .set_roll_up_timeout(settings.roll_up_timeout);
state.translator.set_column(settings.origin_column as u8); state.translator.set_column(settings.origin_column as u8);
state.translator.set_cea608_channel(settings.cea608_channel); state
.translator
.set_cea608_channel(if settings.cea608_channel < 1 {
None
} else {
Some(cea608_types::Id::from_value(settings.cea608_channel as i8))
});
state.translator.set_service_no(settings.service_no); state.translator.set_service_no(settings.service_no);
state.translator.flush(); state.translator.flush();
} }

View file

@ -53,7 +53,7 @@ pub struct TextToCea708 {
mode: Cea708Mode, mode: Cea708Mode,
roll_up_count: u8, roll_up_count: u8,
service_no: u8, service_no: u8,
cea608_channel: u8, cea608_channel: Option<cea608_types::Id>,
origin_column: u32, origin_column: u32,
roll_up_timeout: Option<gst::ClockTime>, roll_up_timeout: Option<gst::ClockTime>,
framerate: gst::Fraction, framerate: gst::Fraction,
@ -78,7 +78,7 @@ impl Default for TextToCea708 {
mode: Cea708Mode::RollUp, mode: Cea708Mode::RollUp,
roll_up_count: 2, roll_up_count: 2,
service_no: 1, service_no: 1,
cea608_channel: 1, cea608_channel: Some(cea608_types::Id::CC1),
origin_column: 0, origin_column: 0,
framerate: gst::Fraction::new(DEFAULT_FPS_N, DEFAULT_FPS_D), framerate: gst::Fraction::new(DEFAULT_FPS_N, DEFAULT_FPS_D),
roll_up_timeout: None, roll_up_timeout: None,
@ -117,7 +117,7 @@ impl TextToCea708 {
pub fn set_origin_column(&mut self, origin_column: u32) { pub fn set_origin_column(&mut self, origin_column: u32) {
self.origin_column = origin_column; self.origin_column = origin_column;
self.cea608.set_origin_column(origin_column); self.cea608.set_origin_column(origin_column as u8);
} }
pub fn set_column(&mut self, column: u8) { pub fn set_column(&mut self, column: u8) {
@ -151,10 +151,12 @@ impl TextToCea708 {
self.framerate self.framerate
} }
pub fn set_cea608_channel(&mut self, channel: u8) { pub fn set_cea608_channel(&mut self, channel: Option<cea608_types::Id>) {
assert!((0..=4).contains(&channel));
if self.cea608_channel != channel { if self.cea608_channel != channel {
self.cea608.flush(); self.cea608.flush();
if let Some(id) = channel {
self.cea608.set_caption_id(id);
}
} }
self.cea608_channel = channel; self.cea608_channel = channel;
} }
@ -271,23 +273,19 @@ impl TextToCea708 {
} }
gst::trace!(CAT, "push packet to writer"); gst::trace!(CAT, "push packet to writer");
self.cc_data_writer.push_packet(packet); self.cc_data_writer.push_packet(packet);
if self.cea608_channel > 0 { if let Some(cea608_id) = self.cea608_channel {
let tcea608 = self.cea608.pop_output().unwrap_or(TimedCea608 { let tcea608 = self.cea608.pop_output().unwrap_or(TimedCea608 {
cea608: 0x8080, cea608: [0x80, 0x80],
frame_no: self.last_frame_no, frame_no: self.last_frame_no,
}); });
let (byte0, byte1) = ( let (byte0, byte1) = (tcea608.cea608[0], tcea608.cea608[1]);
((tcea608.cea608 & 0xff00) >> 8) as u8, match cea608_id.field() {
(tcea608.cea608 & 0xff) as u8, cea608_types::tables::Field::ONE => self
);
match self.cea608_channel {
1 | 2 => self
.cc_data_writer .cc_data_writer
.push_cea608(cea708_types::Cea608::Field1(byte0, byte1)), .push_cea608(cea708_types::Cea608::Field1(byte0, byte1)),
3 | 4 => self cea608_types::tables::Field::TWO => self
.cc_data_writer .cc_data_writer
.push_cea608(cea708_types::Cea608::Field2(byte0, byte1)), .push_cea608(cea708_types::Cea608::Field2(byte0, byte1)),
_ => (),
} }
} }
@ -331,7 +329,7 @@ impl TextToCea708 {
let frame_no = frame_no.max(self.last_frame_no); let frame_no = frame_no.max(self.last_frame_no);
let end_frame_no = end_frame_no.max(frame_no); let end_frame_no = end_frame_no.max(frame_no);
if self.cea608_channel > 0 { if self.cea608_channel.is_some() {
self.cea608.generate(frame_no, end_frame_no, lines.clone()); self.cea608.generate(frame_no, end_frame_no, lines.clone());
} }

View file

@ -133,29 +133,7 @@ fn test_rollup() {
], ],
), // RU3 -> DeleteWindows(!0), DefineWindow(0...), SetPenLocation(bottom-row) ), // RU3 -> DeleteWindows(!0), DefineWindow(0...), SetPenLocation(bottom-row)
([0x94, 0xAD], vec![Code::CR, Code::ETX]), // CR -> CR ([0x94, 0xAD], vec![Code::CR, Code::ETX]), // CR -> CR
( ([0x94, 0x70], vec![Code::ETX]), // PAC to bottom left -> (pen already there) -> nothing to do
[0x94, 0x70],
vec![
Code::DeleteWindows(!WindowBits::ZERO),
Code::DefineWindow(DefineWindowArgs::new(
0,
0,
Anchor::BottomMiddle,
true,
93,
50,
2,
31,
true,
true,
true,
1,
1,
)),
Code::SetPenLocation(SetPenLocationArgs::new(2, 0)),
Code::ETX,
],
), // PAC to bottom left -> SetPenLocation(...)
( (
[0xA8, 0x43], [0xA8, 0x43],
vec![Code::LeftParenthesis, Code::LatinCapitalC, Code::ETX], vec![Code::LeftParenthesis, Code::LatinCapitalC, Code::ETX],
@ -169,7 +147,7 @@ fn test_rollup() {
0, 0,
Anchor::BottomMiddle, Anchor::BottomMiddle,
true, true,
93, 100,
50, 50,
2, 2,
31, 31,
@ -183,7 +161,7 @@ fn test_rollup() {
Code::ETX, Code::ETX,
], ],
), // RU3 ), // RU3
([0x94, 0xAD], vec![Code::CR, Code::ETX]), // CR -> CR ([0x94, 0xAD], vec![Code::CR, Code::ETX]), // CR -> CR
([0x94, 0x70], vec![Code::ETX]), // PAC to bottom left -> SetPenLocation(...) ([0x94, 0x70], vec![Code::ETX]), // PAC to bottom left -> SetPenLocation(...)
( (
[0xF2, 0xEF], [0xF2, 0xEF],