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",
]
[[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]]
name = "cea708-types"
version = "0.3.1"
@ -2272,6 +2284,7 @@ dependencies = [
"byteorder",
"cairo-rs",
"cc",
"cea608-types",
"cea708-types",
"chrono",
"either",
@ -2287,6 +2300,7 @@ dependencies = [
"rand",
"serde",
"serde_json",
"smallvec",
"uuid",
"winnow 0.6.5",
]

View file

@ -20,12 +20,14 @@ pangocairo.workspace = true
byteorder = "1"
serde = { version = "1.0", features = ["derive"] }
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
gst = { workspace = true, features = ["v1_16"]}
gst-base = { workspace = true, features = ["v1_18"]}
gst-video = { workspace = true, features = ["v1_16"]}
winnow = "0.6"
smallvec = "1"
[dev-dependencies]
pretty_assertions = "1"

View file

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

View file

@ -25,10 +25,14 @@
// * The Chunk object could have an "indent" field, that would get translated
// to tab offsets for small bandwidth savings
use cea608_types::tables::Channel;
use cea608_types::tables::MidRow;
use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use cea608_types::{Cea608, Cea608State as Cea608StateTracker};
use crate::cea608utils::*;
use crate::ttutils::{Chunk, Line, Lines};
@ -248,21 +252,24 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
fn dump(
imp: &Cea608ToJson,
cc_data: u16,
cc_data: [u8; 2],
pts: impl Into<Option<gst::ClockTime>>,
duration: impl Into<Option<gst::ClockTime>>,
) {
let pts = pts.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!(
CAT,
imp: imp,
"{} -> {}: {}",
"{} -> {}: {:?}",
pts.display(),
end.display(),
eia608_to_text(cc_data)
code
);
} else {
gst::trace!(
@ -346,7 +353,7 @@ impl State {
}
self.rows.clear();
self.cea608_state.flush();
self.cea608_state.reset();
} else {
for row in self.rows.values() {
if !row.is_empty() {
@ -402,22 +409,18 @@ impl State {
fn handle_preamble(
&mut self,
imp: &Cea608ToJson,
preamble: Preamble,
preamble: cea608_types::tables::PreambleAddressCode,
) -> Option<TimestampedLines> {
if preamble.chan != 0 {
return None;
}
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
// and need to move it when the preamble relocates it
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(ii)
if self.settings.unbuffered {
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 {
Cea608Mode::RollUp2 => 1,
Cea608Mode::RollUp3 => 2,
@ -426,7 +429,7 @@ impl State {
};
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 {
if let Some(mut row) = self.rows.remove(&row) {
@ -441,10 +444,22 @@ impl State {
}
}
self.cursor.row = preamble.row as u32;
self.cursor.col = preamble.col as usize;
self.cursor.style = preamble.style;
self.cursor.underline = preamble.underline != 0;
self.cursor.row = preamble.row() as u32;
self.cursor.col = preamble.column() as usize;
self.cursor.underline = preamble.underline();
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 {
match mode {
@ -487,13 +502,9 @@ impl State {
}
}
fn handle_text(&mut self, imp: &Cea608ToJson, text: Cea608Text) {
if text.chan != 0 {
return;
}
fn handle_text(&mut self, imp: &Cea608ToJson, text: cea608_types::Text) {
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);
}
@ -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 midrowchange.chan == 0 {
row.push_midrow(&mut self.cursor, midrowchange.style, midrowchange.underline);
}
row.push_midrow(
&mut self.cursor,
midrowchange.into(),
midrowchange.underline(),
);
}
}
@ -530,129 +543,115 @@ impl State {
imp: &Cea608ToJson,
pts: Option<gst::ClockTime>,
duration: Option<gst::ClockTime>,
cc_data: u16,
cc_data: [u8; 2],
) -> 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() {
if matches!(cea608, Cea608::Duplicate) {
gst::log!(CAT, imp: imp, "Skipping duplicate");
return None;
self.current_pts = pts;
self.current_duration = duration;
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;
self.current_duration = duration;
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;
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 {
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 {
let ret = self.drain_pending(imp);
self.pending_lines = self.drain(imp, true);
let ret = self.drain(imp, true);
self.carriage_return = Some(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),
}
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
}
@ -717,7 +716,7 @@ impl Cea608ToJson {
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);

View file

@ -7,19 +7,8 @@
// 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,
)]
@ -33,6 +22,18 @@ pub enum Cea608Mode {
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)]
pub enum TextStyle {
#[default]
@ -50,6 +51,53 @@ impl TextStyle {
pub fn is_italics(&self) -> bool {
*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 {
@ -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,
}
pub fn textstyle_foreground_color(style: TextStyle) -> Color {
match style {
TextStyle::Red => Color {
pub fn textstyle_foreground_color(style: TextStyle) -> cea708_types::tables::Color {
let color = match style {
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,
g: ColorValue::None,
b: ColorValue::None,
},
TextStyle::Green => Color {
cea608_types::tables::Color::Green => cea708_types::tables::Color {
r: ColorValue::None,
g: ColorValue::Full,
b: ColorValue::None,
},
TextStyle::Blue => Color {
cea608_types::tables::Color::Blue => cea708_types::tables::Color {
r: ColorValue::None,
g: ColorValue::None,
b: ColorValue::Full,
},
TextStyle::Cyan => Color {
cea608_types::tables::Color::Cyan => cea708_types::tables::Color {
r: ColorValue::None,
g: ColorValue::Full,
b: ColorValue::Full,
},
TextStyle::Yellow => Color {
cea608_types::tables::Color::Yellow => cea708_types::tables::Color {
r: ColorValue::Full,
g: ColorValue::Full,
b: ColorValue::None,
},
TextStyle::Magenta => Color {
cea608_types::tables::Color::Magenta => cea708_types::tables::Color {
r: ColorValue::Full,
g: ColorValue::None,
b: ColorValue::Full,
},
TextStyle::White | TextStyle::ItalicWhite => Color {
cea608_types::tables::Color::White => cea708_types::tables::Color {
r: ColorValue::Full,
g: ColorValue::Full,
b: ColorValue::Full,
@ -213,7 +229,7 @@ impl Cea708ServiceWriter {
pub fn rollup_preamble(&mut self, rollup_count: u8, base_row: u8) {
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!(
CAT,
"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::TextStyle;
use crate::ffi;
use crate::ttutils::Chunk;
use crate::ttutils::Line;
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(
imp: &TtToCea608,
cc_data: u16,
cc_data: [u8; 2],
pts: gst::ClockTime,
duration: gst::ClockTime,
) -> gst::Buffer {
let mut ret = gst::Buffer::with_size(2).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!(
CAT,
imp: imp,
"{} -> {}: {}",
"{} -> {}: {:?}",
pts,
pts + duration,
eia608_to_text(cc_data)
code
);
} else {
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_duration(duration);
@ -301,6 +290,7 @@ impl TtToCea608 {
let framerate = s.get::<gst::Fraction>("framerate").unwrap();
state.framerate = framerate;
state.translator.set_framerate(framerate);
state.translator.set_caption_id(cea608_types::Id::CC1);
let upstream_caps = e.caps();
let s = upstream_caps.structure(0).unwrap();
@ -362,7 +352,9 @@ impl TtToCea608 {
*state = State::default();
state.translator.set_mode(settings.mode);
state.translator.set_framerate(framerate);
state.translator.set_origin_column(settings.origin_column);
state
.translator
.set_origin_column(settings.origin_column as u8);
state
.translator
.set_roll_up_timeout(settings.roll_up_timeout);
@ -606,7 +598,9 @@ impl ElementImpl for TtToCea608 {
*state = State::default();
state.force_clear = false;
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

View file

@ -10,9 +10,7 @@ use gst::prelude::*;
use once_cell::sync::Lazy;
use std::collections::VecDeque;
use crate::ffi;
use crate::cea608utils::{is_basicna, is_specialna, is_westeu, Cea608Mode, TextStyle};
use crate::cea608utils::{Cea608Mode, TextStyle};
use crate::ttutils::{Chunk, Lines};
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 {
word == "." || word == "," || word == "?" || word == "!" || word == ";" || word == ":"
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn eia608_from_utf8_1(c: &[u8; 5]) -> u16 {
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
fn peek_word_length(chars: std::iter::Peekable<std::str::Chars>) -> u8 {
chars.take_while(|c| !c.is_ascii_whitespace()).count() as u8
}
#[derive(Debug)]
pub struct TimedCea608 {
pub cea608: u16,
pub cea608: [u8; 2],
pub frame_no: u64,
}
#[derive(Debug)]
pub struct TextToCea608 {
// settings
origin_column: u32,
origin_column: u8,
framerate: gst::Fraction,
roll_up_timeout: Option<gst::ClockTime>,
// state
@ -96,6 +53,8 @@ pub struct TextToCea608 {
underline: bool,
column: u8,
mode: Cea608Mode,
writer: cea608_types::Cea608Writer,
caption_id: cea608_types::Id,
}
impl Default for TextToCea608 {
@ -112,6 +71,8 @@ impl Default for TextToCea608 {
style: TextStyle::White,
underline: false,
mode: Cea608Mode::PopOn,
writer: cea608_types::Cea608Writer::default(),
caption_id: cea608_types::Id::CC1,
}
}
}
@ -121,10 +82,14 @@ impl TextToCea608 {
self.mode = mode;
if self.mode != Cea608Mode::PopOn {
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) {
self.framerate = framerate;
}
@ -137,7 +102,7 @@ impl TextToCea608 {
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
}
@ -176,7 +141,7 @@ impl TextToCea608 {
false
}
fn cc_data(&mut self, cc_data: u16) {
fn cc_data(&mut self, cc_data: [u8; 2]) {
self.check_erase_display();
self.output_frames.push_back(TimedCea608 {
@ -190,101 +155,102 @@ impl TextToCea608 {
fn pad(&mut self, frame_no: u64) {
while self.last_frame_no < frame_no {
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) {
self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_control_resume_caption_loading,
))
self.control_code(cea608_types::tables::Control::ResumeCaptionLoading);
}
fn resume_direct_captioning(&mut self) {
self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_control_resume_direct_captioning,
))
self.control_code(cea608_types::tables::Control::ResumeDirectionCaptioning);
}
fn delete_to_end_of_row(&mut self) {
self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_control_delete_to_end_of_row,
))
self.control_code(cea608_types::tables::Control::DeleteToEndOfRow);
}
fn roll_up_2(&mut self) {
self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_control_roll_up_2,
))
self.control_code(cea608_types::tables::Control::RollUp2);
}
fn roll_up_3(&mut self) {
self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_control_roll_up_3,
))
self.control_code(cea608_types::tables::Control::RollUp3);
}
fn roll_up_4(&mut self) {
self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_control_roll_up_4,
))
self.control_code(cea608_types::tables::Control::RollUp4);
}
fn carriage_return(&mut self) {
self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_control_carriage_return,
))
self.control_code(cea608_types::tables::Control::CarriageReturn);
}
fn end_of_caption(&mut self) {
self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_control_end_of_caption,
))
self.control_code(cea608_types::tables::Control::EndOfCaption);
}
fn tab_offset(&mut self, offset: u32) {
match offset {
0 => (),
1 => self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_tab_offset_1,
)),
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 tab_offset(&mut self, offset: u8) {
let Some(offset) = cea608_types::tables::Control::tab_offset(offset) else {
return;
};
self.control_code(offset);
}
fn preamble_indent(&mut self, row: i32, col: i32, underline: bool) {
self.cc_data(eia608_row_column_preamble(row, col, underline))
fn preamble_indent(&mut self, row: u8, col: u8, underline: bool) {
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) {
self.cc_data(eia608_row_style_preamble(row, style, underline))
fn preamble_style(&mut self, row: u8, style: TextStyle, underline: bool) {
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) {
self.cc_data(eia608_midrow_change(style, underline))
}
fn bna(&mut self, bna1: u16, bna2: u16) {
self.cc_data(eia608_from_basicna(bna1, bna2))
fn midrow_change(&mut self, style: TextStyle, underline: bool) {
let midrow = if style.is_italics() {
cea608_types::tables::MidRow::new_italics(underline)
} else {
cea608_types::tables::MidRow::new_color(style.to_cea608_color().unwrap(), underline)
};
self.control_code(cea608_types::tables::Control::MidRow(midrow));
}
fn erase_display_memory(&mut self) {
self.erase_display_frame_no = None;
self.cc_data(eia608_control_command(
ffi::eia608_control_t_eia608_control_erase_display_memory,
))
self.control_code(cea608_types::tables::Control::EraseDisplayedMemory);
}
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 {
self.midrow_change(chunk.style as u32, chunk.underline);
self.midrow_change(chunk.style, chunk.underline);
self.style = chunk.style;
self.underline = chunk.underline;
true
@ -297,8 +263,8 @@ impl TextToCea608 {
fn open_line(
&mut self,
chunk: &Chunk,
col: &mut u32,
row: i32,
col: &mut u8,
row: u8,
carriage_return: Option<bool>,
) -> bool {
let mut ret = true;
@ -332,7 +298,7 @@ impl TextToCea608 {
}
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;
} else {
if chunk.style != TextStyle::White {
@ -346,7 +312,7 @@ impl TextToCea608 {
}
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 {
@ -393,7 +359,7 @@ impl TextToCea608 {
let mut col = if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
0
} else {
self.column as u32
self.column
};
self.pad(frame_no);
@ -430,19 +396,17 @@ impl TextToCea608 {
if !lines.lines.is_empty() {
if self.mode == Cea608Mode::PopOn {
self.resume_caption_loading();
self.cc_data(erase_non_displayed_memory());
self.erase_non_displayed_memory();
} else if self.mode == Cea608Mode::PaintOn {
self.resume_direct_captioning();
}
}
let mut prev_char = 0;
for line in &lines.lines {
gst::log!(CAT, "Processing {:?}", line);
if let Some(line_row) = line.row {
row = line_row;
row = line_row as u8;
}
if row > 14 {
@ -454,21 +418,20 @@ impl TextToCea608 {
if self.mode != Cea608Mode::PopOn && self.mode != Cea608Mode::PaintOn {
self.send_roll_up_preamble = true;
}
col = line_column;
col = line_column as u8;
} else if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
col = origin_column;
}
for (j, chunk) in line.chunks.iter().enumerate() {
let mut prepend_space = true;
if prev_char != 0 {
self.cc_data(prev_char);
prev_char = 0;
while self.writer.n_codes() > 0 {
let data = self.writer.pop();
self.cc_data(data);
}
if j == 0 {
prepend_space =
self.open_line(chunk, &mut col, row as i32, line.carriage_return);
prepend_space = self.open_line(chunk, &mut col, row, line.carriage_return);
} else if self.open_chunk(chunk, col) {
prepend_space = false;
col += 1;
@ -495,40 +458,22 @@ impl TextToCea608 {
continue;
}
let mut encoded = [0; 5];
c.encode_utf8(&mut encoded);
let mut cc_data = eia608_from_utf8_1(&encoded);
if cc_data == 0 {
let code = cea608_types::tables::Code::from_char(
c,
cea608_types::tables::Channel::ONE,
)
.unwrap_or_else(|| {
gst::warning!(CAT, "Not translating UTF8: {}", c);
cc_data = *SPACE;
}
cea608_types::tables::Code::Space
});
if is_basicna(prev_char) {
if is_basicna(cc_data) {
self.bna(prev_char, cc_data);
} else if is_westeu(cc_data) {
// extended characters overwrite the previous character,
// so insert a dummy char then write the extended char
self.bna(prev_char, *SPACE);
self.cc_data(cc_data);
} else {
self.cc_data(prev_char);
self.cc_data(cc_data);
self.writer.push(code);
if let cea608_types::tables::Code::Control(_) = code {
while self.writer.n_codes() > 0 {
let data = self.writer.pop();
self.cc_data(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:
// 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
@ -560,12 +505,12 @@ impl TextToCea608 {
if (next_word_length <= 32 - origin_column && col + next_word_length > 31)
|| col > 31
{
if prev_char != 0 {
self.cc_data(prev_char);
prev_char = 0;
while self.writer.n_codes() > 0 {
let data = self.writer.pop();
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 {
if chars.peek().is_some() {
@ -577,17 +522,18 @@ impl TextToCea608 {
}
if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
if prev_char != 0 {
self.cc_data(prev_char);
prev_char = 0;
while self.writer.n_codes() > 0 {
let data = self.writer.pop();
self.cc_data(data);
}
row += 1;
}
}
if !lines.lines.is_empty() {
if prev_char != 0 {
self.cc_data(prev_char);
while self.writer.n_codes() > 0 {
let data = self.writer.pop();
self.cc_data(data);
}
if self.mode == Cea608Mode::PopOn {
@ -597,7 +543,7 @@ impl TextToCea608 {
}
}
self.column = col as u8;
self.column = col;
if self.mode == Cea608Mode::PopOn {
self.erase_display_frame_no =

View file

@ -360,7 +360,13 @@ impl TtToCea708 {
.translator
.set_roll_up_timeout(settings.roll_up_timeout);
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.flush();
@ -526,7 +532,13 @@ impl ObjectImpl for TtToCea708 {
let mut settings = self.settings.lock().unwrap();
let channel = value.get::<u32>().expect("type checked upstream") as u8;
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" => {
let mut state = self.state.lock().unwrap();
@ -657,7 +669,13 @@ impl ElementImpl for TtToCea708 {
.translator
.set_roll_up_timeout(settings.roll_up_timeout);
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.flush();
}

View file

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

View file

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