mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-12-22 18:16:28 +00:00
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:
parent
cc43935036
commit
fea85ff9c8
11 changed files with 471 additions and 785 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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![];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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],
|
||||
|
|
Loading…
Reference in a new issue