mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-22 19:41:00 +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",
|
"image",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cea608-types"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3cf75f36b48b2e9701d278a3ab9081abd1b2881d913ad7b3b0e4d9c6e8c59f86"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger 0.10.2",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cea708-types"
|
name = "cea708-types"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -2272,6 +2284,7 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"cairo-rs",
|
"cairo-rs",
|
||||||
"cc",
|
"cc",
|
||||||
|
"cea608-types",
|
||||||
"cea708-types",
|
"cea708-types",
|
||||||
"chrono",
|
"chrono",
|
||||||
"either",
|
"either",
|
||||||
|
@ -2287,6 +2300,7 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"smallvec",
|
||||||
"uuid",
|
"uuid",
|
||||||
"winnow 0.6.5",
|
"winnow 0.6.5",
|
||||||
]
|
]
|
||||||
|
|
|
@ -20,12 +20,14 @@ pangocairo.workspace = true
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { version = "1.0", features = ["raw_value"] }
|
serde_json = { version = "1.0", features = ["raw_value"] }
|
||||||
cea708-types = "0.3"
|
cea708-types = "0.3.1"
|
||||||
|
cea608-types = "0.1.1"
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
gst = { workspace = true, features = ["v1_16"]}
|
gst = { workspace = true, features = ["v1_16"]}
|
||||||
gst-base = { workspace = true, features = ["v1_18"]}
|
gst-base = { workspace = true, features = ["v1_18"]}
|
||||||
gst-video = { workspace = true, features = ["v1_16"]}
|
gst-video = { workspace = true, features = ["v1_16"]}
|
||||||
winnow = "0.6"
|
winnow = "0.6"
|
||||||
|
smallvec = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1"
|
pretty_assertions = "1"
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use cea608_types::tables::{Channel, PreambleAddressCode};
|
||||||
use cea708_types::{tables::*, DTVCCPacket};
|
use cea708_types::{tables::*, DTVCCPacket};
|
||||||
use cea708_types::{CCDataWriter, Framerate};
|
use cea708_types::{CCDataWriter, Framerate};
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
|
@ -15,7 +16,11 @@ use gst::subclass::prelude::*;
|
||||||
use atomic_refcell::AtomicRefCell;
|
use atomic_refcell::AtomicRefCell;
|
||||||
|
|
||||||
use crate::cea608utils::*;
|
use crate::cea608utils::*;
|
||||||
use crate::cea708utils::{textstyle_foreground_color, textstyle_to_pen_color, Cea708ServiceWriter};
|
use crate::cea708utils::{
|
||||||
|
cea608_color_to_foreground_color, textstyle_to_pen_color, Cea708ServiceWriter,
|
||||||
|
};
|
||||||
|
|
||||||
|
use cea608_types::{Cea608, Cea608State as Cea608StateTracker};
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
@ -56,7 +61,7 @@ impl Default for Cea608ServiceState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
mode: None,
|
mode: None,
|
||||||
base_row: 15,
|
base_row: 14,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,8 +121,8 @@ impl Cea708ServiceState {
|
||||||
self.pen_attributes.italics = false;
|
self.pen_attributes.italics = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_text(&mut self, text: Cea608Text) {
|
fn handle_text(&mut self, text: cea608_types::Text) {
|
||||||
if text.code_space == CodeSpace::WestEU {
|
if text.needs_backspace {
|
||||||
self.writer.push_codes(&[Code::BS]);
|
self.writer.push_codes(&[Code::BS]);
|
||||||
}
|
}
|
||||||
if let Some(c) = text.char1 {
|
if let Some(c) = text.char1 {
|
||||||
|
@ -136,18 +141,18 @@ impl Cea708ServiceState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_preamble(&mut self, preamble: Preamble) {
|
fn handle_preamble(&mut self, preamble: PreambleAddressCode) {
|
||||||
let mut need_pen_location = false;
|
let mut need_pen_location = false;
|
||||||
// TODO: may need a better algorithm then compressing the top four rows
|
// TODO: may need a better algorithm than compressing the top four rows
|
||||||
let new_row = std::cmp::max(0, preamble.row) as u8;
|
let new_row = std::cmp::max(0, preamble.row());
|
||||||
if self.pen_location.row != new_row {
|
if self.pen_location.row != new_row {
|
||||||
need_pen_location = true;
|
need_pen_location = true;
|
||||||
self.pen_location.row = new_row;
|
self.pen_location.row = new_row;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.pen_location.column != preamble.col as u8 {
|
if self.pen_location.column != preamble.column() {
|
||||||
need_pen_location = true;
|
need_pen_location = true;
|
||||||
self.pen_location.column = preamble.col as u8;
|
self.pen_location.column = preamble.column();
|
||||||
}
|
}
|
||||||
|
|
||||||
if need_pen_location {
|
if need_pen_location {
|
||||||
|
@ -155,42 +160,44 @@ impl Cea708ServiceState {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut need_pen_attributes = false;
|
let mut need_pen_attributes = false;
|
||||||
if self.pen_attributes.italics != preamble.style.is_italics() {
|
if self.pen_attributes.italics != preamble.italics() {
|
||||||
need_pen_attributes = true;
|
need_pen_attributes = true;
|
||||||
self.pen_attributes.italics = preamble.style.is_italics();
|
self.pen_attributes.italics = preamble.italics();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.pen_attributes.underline != (preamble.underline > 0) {
|
if self.pen_attributes.underline != preamble.underline() {
|
||||||
need_pen_attributes = true;
|
need_pen_attributes = true;
|
||||||
self.pen_attributes.underline = preamble.underline > 0;
|
self.pen_attributes.underline = preamble.underline();
|
||||||
}
|
}
|
||||||
|
|
||||||
if need_pen_attributes {
|
if need_pen_attributes {
|
||||||
self.writer.set_pen_attributes(self.pen_attributes);
|
self.writer.set_pen_attributes(self.pen_attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.pen_color.foreground_color != textstyle_foreground_color(preamble.style) {
|
if self.pen_color.foreground_color != cea608_color_to_foreground_color(preamble.color()) {
|
||||||
self.pen_color.foreground_color = textstyle_foreground_color(preamble.style);
|
self.pen_color.foreground_color = cea608_color_to_foreground_color(preamble.color());
|
||||||
self.writer.set_pen_color(self.pen_color);
|
self.writer.set_pen_color(self.pen_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_midrowchange(&mut self, midrowchange: MidRowChange) {
|
fn handle_midrowchange(&mut self, midrowchange: cea608_types::tables::MidRow) {
|
||||||
self.writer.write_char(' ');
|
self.writer.write_char(' ');
|
||||||
if self.pen_color.foreground_color != textstyle_foreground_color(midrowchange.style) {
|
if let Some(color) = midrowchange.color() {
|
||||||
self.pen_color.foreground_color = textstyle_foreground_color(midrowchange.style);
|
if self.pen_color.foreground_color != cea608_color_to_foreground_color(color) {
|
||||||
self.writer.set_pen_color(self.pen_color);
|
self.pen_color.foreground_color = cea608_color_to_foreground_color(color);
|
||||||
|
self.writer.set_pen_color(self.pen_color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut need_pen_attributes = false;
|
let mut need_pen_attributes = false;
|
||||||
if self.pen_attributes.italics != midrowchange.style.is_italics() {
|
if self.pen_attributes.italics != midrowchange.italics() {
|
||||||
need_pen_attributes = true;
|
need_pen_attributes = true;
|
||||||
self.pen_attributes.italics = midrowchange.style.is_italics();
|
self.pen_attributes.italics = midrowchange.italics();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.pen_attributes.underline != midrowchange.underline {
|
if self.pen_attributes.underline != midrowchange.underline() {
|
||||||
need_pen_attributes = true;
|
need_pen_attributes = true;
|
||||||
self.pen_attributes.underline = midrowchange.underline;
|
self.pen_attributes.underline = midrowchange.underline();
|
||||||
}
|
}
|
||||||
|
|
||||||
if need_pen_attributes {
|
if need_pen_attributes {
|
||||||
|
@ -287,12 +294,12 @@ enum BufferOrEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
fn field_channel_to_index(&self, field: u8, channel: i32) -> usize {
|
fn field_channel_to_index(&self, field: u8, channel: Channel) -> usize {
|
||||||
match (field, channel) {
|
match (field, channel) {
|
||||||
(0, 0 | 2) => 0,
|
(0, Channel::ONE) => 0,
|
||||||
(0, 1 | 3) => 2,
|
(0, Channel::TWO) => 2,
|
||||||
(1, 0 | 2) => 1,
|
(1, Channel::ONE) => 1,
|
||||||
(1, 1 | 3) => 3,
|
(1, Channel::TWO) => 3,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,12 +307,12 @@ impl State {
|
||||||
fn service_state_from_608_field_channel(
|
fn service_state_from_608_field_channel(
|
||||||
&mut self,
|
&mut self,
|
||||||
field: u8,
|
field: u8,
|
||||||
channel: i32,
|
channel: Channel,
|
||||||
) -> &mut Cea708ServiceState {
|
) -> &mut Cea708ServiceState {
|
||||||
&mut self.cea708.service_state[self.field_channel_to_index(field, channel)]
|
&mut self.cea708.service_state[self.field_channel_to_index(field, channel)]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_mode(&mut self, field: u8, channel: i32, cea608_mode: Cea608Mode) {
|
fn new_mode(&mut self, field: u8, channel: Channel, cea608_mode: Cea608Mode) {
|
||||||
let idx = self.field_channel_to_index(field, channel);
|
let idx = self.field_channel_to_index(field, channel);
|
||||||
if let Some(old_mode) = self.cea608.service[idx].mode {
|
if let Some(old_mode) = self.cea608.service[idx].mode {
|
||||||
if cea608_mode.is_rollup()
|
if cea608_mode.is_rollup()
|
||||||
|
@ -345,27 +352,20 @@ impl State {
|
||||||
self.cea708.service_state[idx].new_mode(cea608_mode, base_row);
|
self.cea708.service_state[idx].new_mode(cea608_mode, base_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_cc_data(&mut self, imp: &Cea608ToCea708, field: u8, cc_data: u16) {
|
fn handle_cc_data(&mut self, imp: &Cea608ToCea708, field: u8, cc_data: [u8; 2]) {
|
||||||
self.cea608.tracker[field as usize].push_cc_data(cc_data);
|
if let Ok(Some(cea608)) = self.cea608.tracker[field as usize].decode(cc_data) {
|
||||||
|
|
||||||
let mut channel = None;
|
|
||||||
if let Some(cea608) = self.cea608.tracker[field as usize].pop() {
|
|
||||||
gst::trace!(
|
gst::trace!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: imp,
|
imp: imp,
|
||||||
"have field:{field} channel:{} {cea608:?}",
|
"have field:{field} channel:{:?} {cea608:?}",
|
||||||
cea608.channel()
|
cea608.channel()
|
||||||
);
|
);
|
||||||
if !matches!(cea608, Cea608::Duplicate) {
|
|
||||||
channel = Some(cea608.channel());
|
|
||||||
}
|
|
||||||
match cea608 {
|
match cea608 {
|
||||||
Cea608::Duplicate => (),
|
|
||||||
Cea608::NewMode(chan, new_mode) => {
|
Cea608::NewMode(chan, new_mode) => {
|
||||||
self.new_mode(field, chan, new_mode);
|
self.new_mode(field, chan, new_mode.into());
|
||||||
}
|
}
|
||||||
Cea608::Text(text) => {
|
Cea608::Text(text) => {
|
||||||
let state = self.service_state_from_608_field_channel(field, text.chan);
|
let state = self.service_state_from_608_field_channel(field, text.channel);
|
||||||
state.handle_text(text);
|
state.handle_text(text);
|
||||||
}
|
}
|
||||||
Cea608::EndOfCaption(chan) => {
|
Cea608::EndOfCaption(chan) => {
|
||||||
|
@ -373,8 +373,8 @@ impl State {
|
||||||
state.writer.end_of_caption();
|
state.writer.end_of_caption();
|
||||||
state.writer.etx();
|
state.writer.etx();
|
||||||
}
|
}
|
||||||
Cea608::Preamble(mut preamble) => {
|
Cea608::Preamble(chan, mut preamble) => {
|
||||||
let idx = self.field_channel_to_index(field, preamble.chan);
|
let idx = self.field_channel_to_index(field, chan);
|
||||||
let rollup_count = self.cea608.service[idx]
|
let rollup_count = self.cea608.service[idx]
|
||||||
.mode
|
.mode
|
||||||
.map(|mode| {
|
.map(|mode| {
|
||||||
|
@ -388,21 +388,23 @@ impl State {
|
||||||
if rollup_count > 0 {
|
if rollup_count > 0 {
|
||||||
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(ii)
|
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(ii)
|
||||||
let old_base_row = self.cea608.service[idx].base_row;
|
let old_base_row = self.cea608.service[idx].base_row;
|
||||||
self.cea608.service[idx].base_row = preamble.row as u8;
|
self.cea608.service[idx].base_row = preamble.row();
|
||||||
let state = self.service_state_from_608_field_channel(field, preamble.chan);
|
let state = self.service_state_from_608_field_channel(field, chan);
|
||||||
if old_base_row != preamble.row as u8 {
|
if old_base_row != preamble.row() {
|
||||||
state
|
state.writer.rollup_preamble(rollup_count, preamble.row());
|
||||||
.writer
|
|
||||||
.rollup_preamble(rollup_count, preamble.row as u8);
|
|
||||||
}
|
}
|
||||||
state.pen_location.row = rollup_count - 1;
|
state.pen_location.row = rollup_count - 1;
|
||||||
preamble.row = rollup_count as i32 - 1;
|
preamble = PreambleAddressCode::new(
|
||||||
|
rollup_count - 1,
|
||||||
|
preamble.underline(),
|
||||||
|
preamble.code(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let state = self.service_state_from_608_field_channel(field, preamble.chan);
|
let state = self.service_state_from_608_field_channel(field, chan);
|
||||||
state.handle_preamble(preamble);
|
state.handle_preamble(preamble);
|
||||||
}
|
}
|
||||||
Cea608::MidRowChange(midrowchange) => {
|
Cea608::MidRowChange(chan, midrowchange) => {
|
||||||
let state = self.service_state_from_608_field_channel(field, midrowchange.chan);
|
let state = self.service_state_from_608_field_channel(field, chan);
|
||||||
state.handle_midrowchange(midrowchange);
|
state.handle_midrowchange(midrowchange);
|
||||||
}
|
}
|
||||||
Cea608::Backspace(chan) => {
|
Cea608::Backspace(chan) => {
|
||||||
|
@ -432,24 +434,23 @@ impl State {
|
||||||
Cea608::TabOffset(chan, count) => {
|
Cea608::TabOffset(chan, count) => {
|
||||||
let state = self.service_state_from_608_field_channel(field, chan);
|
let state = self.service_state_from_608_field_channel(field, chan);
|
||||||
state.pen_location.column =
|
state.pen_location.column =
|
||||||
std::cmp::min(state.pen_location.column + count as u8, 32);
|
std::cmp::min(state.pen_location.column + count, 32);
|
||||||
}
|
}
|
||||||
|
Cea608::DeleteToEndOfRow(_chan) => (),
|
||||||
}
|
}
|
||||||
if let Some(channel) = channel {
|
let idx = self.field_channel_to_index(field, cea608.channel());
|
||||||
let idx = self.field_channel_to_index(field, channel);
|
if let Some(
|
||||||
if let Some(
|
Cea608Mode::RollUp2
|
||||||
Cea608Mode::RollUp2
|
| Cea608Mode::RollUp3
|
||||||
| Cea608Mode::RollUp3
|
| Cea608Mode::RollUp4
|
||||||
| Cea608Mode::RollUp4
|
| Cea608Mode::PaintOn,
|
||||||
| Cea608Mode::PaintOn,
|
) = self.cea608.service[idx].mode
|
||||||
) = self.cea608.service[idx].mode
|
{
|
||||||
{
|
// FIXME: actually track state for when things have changed
|
||||||
// FIXME: actually track state for when things have changed
|
// and we need to send ETX
|
||||||
// and we need to send ETX
|
self.cea708.service_state[idx]
|
||||||
self.cea708.service_state[idx]
|
.writer
|
||||||
.writer
|
.push_codes(&[cea708_types::tables::Code::ETX]);
|
||||||
.push_codes(&[cea708_types::tables::Code::ETX]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -529,9 +530,8 @@ impl Cea608ToCea708 {
|
||||||
return Ok(gst::FlowSuccess::Ok);
|
return Ok(gst::FlowSuccess::Ok);
|
||||||
}
|
}
|
||||||
for triple in data.chunks_exact(3) {
|
for triple in data.chunks_exact(3) {
|
||||||
let cc_data = (triple[1] as u16) << 8 | triple[2] as u16;
|
|
||||||
let field = (triple[0] & 0x80) >> 7;
|
let field = (triple[0] & 0x80) >> 7;
|
||||||
state.handle_cc_data(self, field, cc_data);
|
state.handle_cc_data(self, field, [triple[1], triple[2]]);
|
||||||
}
|
}
|
||||||
data.to_vec()
|
data.to_vec()
|
||||||
}
|
}
|
||||||
|
@ -559,8 +559,7 @@ impl Cea608ToCea708 {
|
||||||
};
|
};
|
||||||
let mut s334_1a_data = Vec::with_capacity(data.len() / 2 * 3);
|
let mut s334_1a_data = Vec::with_capacity(data.len() / 2 * 3);
|
||||||
for pair in data.chunks_exact(2) {
|
for pair in data.chunks_exact(2) {
|
||||||
let cc_data = (pair[0] as u16) << 8 | pair[1] as u16;
|
state.handle_cc_data(self, field, [pair[0], pair[1]]);
|
||||||
state.handle_cc_data(self, field, cc_data);
|
|
||||||
if field == 0 {
|
if field == 0 {
|
||||||
s334_1a_data.push(0x00);
|
s334_1a_data.push(0x00);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -25,10 +25,14 @@
|
||||||
// * The Chunk object could have an "indent" field, that would get translated
|
// * The Chunk object could have an "indent" field, that would get translated
|
||||||
// to tab offsets for small bandwidth savings
|
// to tab offsets for small bandwidth savings
|
||||||
|
|
||||||
|
use cea608_types::tables::Channel;
|
||||||
|
use cea608_types::tables::MidRow;
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
|
|
||||||
|
use cea608_types::{Cea608, Cea608State as Cea608StateTracker};
|
||||||
|
|
||||||
use crate::cea608utils::*;
|
use crate::cea608utils::*;
|
||||||
use crate::ttutils::{Chunk, Line, Lines};
|
use crate::ttutils::{Chunk, Line, Lines};
|
||||||
|
|
||||||
|
@ -248,21 +252,24 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
|
|
||||||
fn dump(
|
fn dump(
|
||||||
imp: &Cea608ToJson,
|
imp: &Cea608ToJson,
|
||||||
cc_data: u16,
|
cc_data: [u8; 2],
|
||||||
pts: impl Into<Option<gst::ClockTime>>,
|
pts: impl Into<Option<gst::ClockTime>>,
|
||||||
duration: impl Into<Option<gst::ClockTime>>,
|
duration: impl Into<Option<gst::ClockTime>>,
|
||||||
) {
|
) {
|
||||||
let pts = pts.into();
|
let pts = pts.into();
|
||||||
let end = pts.opt_add(duration.into());
|
let end = pts.opt_add(duration.into());
|
||||||
|
|
||||||
if cc_data != 0x8080 {
|
if cc_data != [0x80, 0x80] {
|
||||||
|
let Ok(code) = cea608_types::tables::Code::from_data(cc_data) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: imp,
|
imp: imp,
|
||||||
"{} -> {}: {}",
|
"{} -> {}: {:?}",
|
||||||
pts.display(),
|
pts.display(),
|
||||||
end.display(),
|
end.display(),
|
||||||
eia608_to_text(cc_data)
|
code
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
gst::trace!(
|
gst::trace!(
|
||||||
|
@ -346,7 +353,7 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rows.clear();
|
self.rows.clear();
|
||||||
self.cea608_state.flush();
|
self.cea608_state.reset();
|
||||||
} else {
|
} else {
|
||||||
for row in self.rows.values() {
|
for row in self.rows.values() {
|
||||||
if !row.is_empty() {
|
if !row.is_empty() {
|
||||||
|
@ -402,22 +409,18 @@ impl State {
|
||||||
fn handle_preamble(
|
fn handle_preamble(
|
||||||
&mut self,
|
&mut self,
|
||||||
imp: &Cea608ToJson,
|
imp: &Cea608ToJson,
|
||||||
preamble: Preamble,
|
preamble: cea608_types::tables::PreambleAddressCode,
|
||||||
) -> Option<TimestampedLines> {
|
) -> Option<TimestampedLines> {
|
||||||
if preamble.chan != 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
gst::log!(CAT, imp: imp, "preamble: {:?}", preamble);
|
gst::log!(CAT, imp: imp, "preamble: {:?}", preamble);
|
||||||
|
|
||||||
let drain_roll_up = self.cursor.row != preamble.row as u32;
|
let drain_roll_up = self.cursor.row != preamble.row() as u32;
|
||||||
|
|
||||||
// In unbuffered mode, we output the whole roll-up window
|
// In unbuffered mode, we output the whole roll-up window
|
||||||
// and need to move it when the preamble relocates it
|
// and need to move it when the preamble relocates it
|
||||||
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(ii)
|
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(ii)
|
||||||
if self.settings.unbuffered {
|
if self.settings.unbuffered {
|
||||||
if let Some(mode) = self.mode {
|
if let Some(mode) = self.mode {
|
||||||
if mode.is_rollup() && self.cursor.row != preamble.row as u32 {
|
if mode.is_rollup() && self.cursor.row != preamble.row() as u32 {
|
||||||
let offset = match mode {
|
let offset = match mode {
|
||||||
Cea608Mode::RollUp2 => 1,
|
Cea608Mode::RollUp2 => 1,
|
||||||
Cea608Mode::RollUp3 => 2,
|
Cea608Mode::RollUp3 => 2,
|
||||||
|
@ -426,7 +429,7 @@ impl State {
|
||||||
};
|
};
|
||||||
|
|
||||||
let current_top_row = self.cursor.row.saturating_sub(offset);
|
let current_top_row = self.cursor.row.saturating_sub(offset);
|
||||||
let new_row_offset = preamble.row - self.cursor.row as i32;
|
let new_row_offset = preamble.row() as i32 - self.cursor.row as i32;
|
||||||
|
|
||||||
for row in current_top_row..self.cursor.row {
|
for row in current_top_row..self.cursor.row {
|
||||||
if let Some(mut row) = self.rows.remove(&row) {
|
if let Some(mut row) = self.rows.remove(&row) {
|
||||||
|
@ -441,10 +444,22 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cursor.row = preamble.row as u32;
|
self.cursor.row = preamble.row() as u32;
|
||||||
self.cursor.col = preamble.col as usize;
|
self.cursor.col = preamble.column() as usize;
|
||||||
self.cursor.style = preamble.style;
|
self.cursor.underline = preamble.underline();
|
||||||
self.cursor.underline = preamble.underline != 0;
|
self.cursor.style = if preamble.italics() {
|
||||||
|
TextStyle::ItalicWhite
|
||||||
|
} else {
|
||||||
|
match preamble.color() {
|
||||||
|
cea608_types::tables::Color::White => TextStyle::White,
|
||||||
|
cea608_types::tables::Color::Green => TextStyle::Green,
|
||||||
|
cea608_types::tables::Color::Blue => TextStyle::Blue,
|
||||||
|
cea608_types::tables::Color::Cyan => TextStyle::Cyan,
|
||||||
|
cea608_types::tables::Color::Red => TextStyle::Red,
|
||||||
|
cea608_types::tables::Color::Yellow => TextStyle::Yellow,
|
||||||
|
cea608_types::tables::Color::Magenta => TextStyle::Magenta,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(mode) = self.mode {
|
if let Some(mode) = self.mode {
|
||||||
match mode {
|
match mode {
|
||||||
|
@ -487,13 +502,9 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_text(&mut self, imp: &Cea608ToJson, text: Cea608Text) {
|
fn handle_text(&mut self, imp: &Cea608ToJson, text: cea608_types::Text) {
|
||||||
if text.chan != 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(row) = self.rows.get_mut(&self.cursor.row) {
|
if let Some(row) = self.rows.get_mut(&self.cursor.row) {
|
||||||
if text.code_space == CodeSpace::WestEU {
|
if text.needs_backspace {
|
||||||
row.pop(&mut self.cursor);
|
row.pop(&mut self.cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,11 +528,13 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_midrowchange(&mut self, midrowchange: MidRowChange) {
|
fn handle_midrowchange(&mut self, midrowchange: MidRow) {
|
||||||
if let Some(row) = self.rows.get_mut(&self.cursor.row) {
|
if let Some(row) = self.rows.get_mut(&self.cursor.row) {
|
||||||
if midrowchange.chan == 0 {
|
row.push_midrow(
|
||||||
row.push_midrow(&mut self.cursor, midrowchange.style, midrowchange.underline);
|
&mut self.cursor,
|
||||||
}
|
midrowchange.into(),
|
||||||
|
midrowchange.underline(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,129 +543,115 @@ impl State {
|
||||||
imp: &Cea608ToJson,
|
imp: &Cea608ToJson,
|
||||||
pts: Option<gst::ClockTime>,
|
pts: Option<gst::ClockTime>,
|
||||||
duration: Option<gst::ClockTime>,
|
duration: Option<gst::ClockTime>,
|
||||||
cc_data: u16,
|
cc_data: [u8; 2],
|
||||||
) -> Option<TimestampedLines> {
|
) -> Option<TimestampedLines> {
|
||||||
self.cea608_state.push_cc_data(cc_data);
|
let Ok(Some(cea608)) = self.cea608_state.decode(cc_data) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
while let Some(cea608) = self.cea608_state.pop() {
|
self.current_pts = pts;
|
||||||
if matches!(cea608, Cea608::Duplicate) {
|
self.current_duration = duration;
|
||||||
gst::log!(CAT, imp: imp, "Skipping duplicate");
|
|
||||||
return None;
|
if cea608.channel() != Channel::ONE {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match cea608 {
|
||||||
|
Cea608::EraseDisplay(_chan) => {
|
||||||
|
return match self.mode {
|
||||||
|
Some(Cea608Mode::PopOn) => {
|
||||||
|
self.clear = Some(true);
|
||||||
|
self.drain_pending(imp)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let ret = self.drain(imp, true);
|
||||||
|
self.clear = Some(true);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Cea608::NewMode(_chan, mode) => return self.update_mode(imp, mode.into()),
|
||||||
|
Cea608::CarriageReturn(_chan) => {
|
||||||
|
gst::log!(CAT, imp: imp, "carriage return");
|
||||||
|
|
||||||
self.current_pts = pts;
|
if let Some(mode) = self.mode {
|
||||||
self.current_duration = duration;
|
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(2)(i) (f)(3)(i)
|
||||||
|
if mode.is_rollup() {
|
||||||
match cea608 {
|
|
||||||
Cea608::Duplicate => unreachable!(),
|
|
||||||
Cea608::EraseDisplay(chan) => {
|
|
||||||
if chan == 0 {
|
|
||||||
return match self.mode {
|
|
||||||
Some(Cea608Mode::PopOn) => {
|
|
||||||
self.clear = Some(true);
|
|
||||||
self.drain_pending(imp)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let ret = self.drain(imp, true);
|
|
||||||
self.clear = Some(true);
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Cea608::NewMode(chan, mode) => {
|
|
||||||
if chan == 0 {
|
|
||||||
return self.update_mode(imp, mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Cea608::CarriageReturn(chan) => {
|
|
||||||
if chan == 0 {
|
|
||||||
gst::log!(CAT, imp: imp, "carriage return");
|
|
||||||
|
|
||||||
if let Some(mode) = self.mode {
|
|
||||||
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(2)(i) (f)(3)(i)
|
|
||||||
if mode.is_rollup() {
|
|
||||||
let ret = if self.settings.unbuffered {
|
|
||||||
let offset = match mode {
|
|
||||||
Cea608Mode::RollUp2 => 1,
|
|
||||||
Cea608Mode::RollUp3 => 2,
|
|
||||||
Cea608Mode::RollUp4 => 3,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let top_row = self.cursor.row.saturating_sub(offset);
|
|
||||||
|
|
||||||
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(iii)
|
|
||||||
self.rows.remove(&top_row);
|
|
||||||
|
|
||||||
for row in top_row + 1..self.cursor.row + 1 {
|
|
||||||
if let Some(mut row) = self.rows.remove(&row) {
|
|
||||||
row.row -= 1;
|
|
||||||
self.rows.insert(row.row, row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.rows.insert(self.cursor.row, Row::new(self.cursor.row));
|
|
||||||
self.drain(imp, false)
|
|
||||||
} else {
|
|
||||||
let ret = self.drain(imp, true);
|
|
||||||
self.carriage_return = Some(true);
|
|
||||||
ret
|
|
||||||
};
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Cea608::Backspace(chan) => {
|
|
||||||
if chan == 0 {
|
|
||||||
if let Some(row) = self.rows.get_mut(&self.cursor.row) {
|
|
||||||
row.pop(&mut self.cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Cea608::EraseNonDisplay(chan) => {
|
|
||||||
if chan == 0 && self.mode == Some(Cea608Mode::PopOn) {
|
|
||||||
self.rows.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Cea608::EndOfCaption(chan) => {
|
|
||||||
if chan == 0 {
|
|
||||||
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(2)
|
|
||||||
self.update_mode(imp, Cea608Mode::PopOn);
|
|
||||||
self.first_pts = self.current_pts;
|
|
||||||
let ret = if self.settings.unbuffered {
|
let ret = if self.settings.unbuffered {
|
||||||
self.drain(imp, true)
|
let offset = match mode {
|
||||||
|
Cea608Mode::RollUp2 => 1,
|
||||||
|
Cea608Mode::RollUp3 => 2,
|
||||||
|
Cea608Mode::RollUp4 => 3,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let top_row = self.cursor.row.saturating_sub(offset);
|
||||||
|
|
||||||
|
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(1)(iii)
|
||||||
|
self.rows.remove(&top_row);
|
||||||
|
|
||||||
|
for row in top_row + 1..self.cursor.row + 1 {
|
||||||
|
if let Some(mut row) = self.rows.remove(&row) {
|
||||||
|
row.row -= 1;
|
||||||
|
self.rows.insert(row.row, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rows.insert(self.cursor.row, Row::new(self.cursor.row));
|
||||||
|
self.drain(imp, false)
|
||||||
} else {
|
} else {
|
||||||
let ret = self.drain_pending(imp);
|
let ret = self.drain(imp, true);
|
||||||
self.pending_lines = self.drain(imp, true);
|
self.carriage_return = Some(true);
|
||||||
ret
|
ret
|
||||||
};
|
};
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Cea608::TabOffset(chan, count) => {
|
|
||||||
if chan == 0 {
|
|
||||||
self.cursor.col += count as usize;
|
|
||||||
// C.13 Right Margin Limitation
|
|
||||||
self.cursor.col = std::cmp::min(self.cursor.col, 31);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Cea608::Text(text) => {
|
|
||||||
if let Some(mode) = self.mode {
|
|
||||||
self.mode?;
|
|
||||||
gst::log!(CAT, imp: imp, "text");
|
|
||||||
self.handle_text(imp, text);
|
|
||||||
|
|
||||||
if mode.is_rollup() && self.settings.unbuffered {
|
|
||||||
return self.drain(imp, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Cea608::Preamble(preamble) => return self.handle_preamble(imp, preamble),
|
|
||||||
Cea608::MidRowChange(change) => self.handle_midrowchange(change),
|
|
||||||
}
|
}
|
||||||
|
Cea608::Backspace(_chan) => {
|
||||||
|
if let Some(row) = self.rows.get_mut(&self.cursor.row) {
|
||||||
|
row.pop(&mut self.cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Cea608::EraseNonDisplay(_chan) => {
|
||||||
|
if self.mode == Some(Cea608Mode::PopOn) {
|
||||||
|
self.rows.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Cea608::EndOfCaption(_chan) => {
|
||||||
|
// https://www.law.cornell.edu/cfr/text/47/79.101 (f)(2)
|
||||||
|
self.update_mode(imp, Cea608Mode::PopOn);
|
||||||
|
self.first_pts = self.current_pts;
|
||||||
|
let ret = if self.settings.unbuffered {
|
||||||
|
self.drain(imp, true)
|
||||||
|
} else {
|
||||||
|
let ret = self.drain_pending(imp);
|
||||||
|
self.pending_lines = self.drain(imp, true);
|
||||||
|
ret
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
Cea608::TabOffset(_chan, count) => {
|
||||||
|
self.cursor.col += count as usize;
|
||||||
|
// C.13 Right Margin Limitation
|
||||||
|
self.cursor.col = std::cmp::min(self.cursor.col, 31);
|
||||||
|
}
|
||||||
|
Cea608::Text(text) => {
|
||||||
|
if let Some(mode) = self.mode {
|
||||||
|
self.mode?;
|
||||||
|
gst::log!(CAT, imp: imp, "text");
|
||||||
|
self.handle_text(imp, text);
|
||||||
|
|
||||||
|
if mode.is_rollup() && self.settings.unbuffered {
|
||||||
|
return self.drain(imp, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: implement
|
||||||
|
Cea608::DeleteToEndOfRow(_chan) => (),
|
||||||
|
Cea608::Preamble(_chan, preamble) => return self.handle_preamble(imp, preamble),
|
||||||
|
Cea608::MidRowChange(_chan, change) => self.handle_midrowchange(change),
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -717,7 +716,7 @@ impl Cea608ToJson {
|
||||||
return Ok(gst::FlowSuccess::Ok);
|
return Ok(gst::FlowSuccess::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
let cc_data = (data[0] as u16) << 8 | data[1] as u16;
|
let cc_data = [data[0], data[1]];
|
||||||
|
|
||||||
dump(self, cc_data, pts, duration);
|
dump(self, cc_data, pts, duration);
|
||||||
|
|
||||||
|
|
|
@ -7,19 +7,8 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::ffi;
|
|
||||||
|
|
||||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|
||||||
gst::DebugCategory::new(
|
|
||||||
"cea608utils",
|
|
||||||
gst::DebugColorFlags::empty(),
|
|
||||||
Some("CEA-608 utilities"),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum,
|
Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum,
|
||||||
)]
|
)]
|
||||||
|
@ -33,6 +22,18 @@ pub enum Cea608Mode {
|
||||||
RollUp4,
|
RollUp4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<cea608_types::Mode> for Cea608Mode {
|
||||||
|
fn from(value: cea608_types::Mode) -> Self {
|
||||||
|
match value {
|
||||||
|
cea608_types::Mode::PopOn => Self::PopOn,
|
||||||
|
cea608_types::Mode::PaintOn => Self::PaintOn,
|
||||||
|
cea608_types::Mode::RollUp2 => Self::RollUp2,
|
||||||
|
cea608_types::Mode::RollUp3 => Self::RollUp3,
|
||||||
|
cea608_types::Mode::RollUp4 => Self::RollUp4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum TextStyle {
|
pub enum TextStyle {
|
||||||
#[default]
|
#[default]
|
||||||
|
@ -50,6 +51,53 @@ impl TextStyle {
|
||||||
pub fn is_italics(&self) -> bool {
|
pub fn is_italics(&self) -> bool {
|
||||||
*self == TextStyle::ItalicWhite
|
*self == TextStyle::ItalicWhite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_cea608_color(self) -> Option<cea608_types::tables::Color> {
|
||||||
|
match self {
|
||||||
|
Self::White => Some(cea608_types::tables::Color::White),
|
||||||
|
Self::Green => Some(cea608_types::tables::Color::Green),
|
||||||
|
Self::Blue => Some(cea608_types::tables::Color::Blue),
|
||||||
|
Self::Cyan => Some(cea608_types::tables::Color::Cyan),
|
||||||
|
Self::Red => Some(cea608_types::tables::Color::Red),
|
||||||
|
Self::Yellow => Some(cea608_types::tables::Color::Yellow),
|
||||||
|
Self::Magenta => Some(cea608_types::tables::Color::Magenta),
|
||||||
|
Self::ItalicWhite => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<cea608_types::tables::PreambleAddressCode> for TextStyle {
|
||||||
|
fn from(value: cea608_types::tables::PreambleAddressCode) -> Self {
|
||||||
|
value.color().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<cea608_types::tables::MidRow> for TextStyle {
|
||||||
|
fn from(value: cea608_types::tables::MidRow) -> Self {
|
||||||
|
if value.italics() {
|
||||||
|
Self::ItalicWhite
|
||||||
|
} else {
|
||||||
|
// XXX: MidRow may not change color
|
||||||
|
value
|
||||||
|
.color()
|
||||||
|
.unwrap_or(cea608_types::tables::Color::White)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<cea608_types::tables::Color> for TextStyle {
|
||||||
|
fn from(value: cea608_types::tables::Color) -> Self {
|
||||||
|
match value {
|
||||||
|
cea608_types::tables::Color::White => Self::White,
|
||||||
|
cea608_types::tables::Color::Green => Self::Green,
|
||||||
|
cea608_types::tables::Color::Blue => Self::Blue,
|
||||||
|
cea608_types::tables::Color::Cyan => Self::Cyan,
|
||||||
|
cea608_types::tables::Color::Red => Self::Red,
|
||||||
|
cea608_types::tables::Color::Yellow => Self::Yellow,
|
||||||
|
cea608_types::tables::Color::Magenta => Self::Magenta,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u32> for TextStyle {
|
impl From<u32> for TextStyle {
|
||||||
|
@ -67,329 +115,3 @@ impl From<u32> for TextStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_basicna(cc_data: u16) -> bool {
|
|
||||||
0x0000 != (0x6000 & cc_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_preamble(cc_data: u16) -> bool {
|
|
||||||
0x1040 == (0x7040 & cc_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_midrowchange(cc_data: u16) -> bool {
|
|
||||||
0x1120 == (0x7770 & cc_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_specialna(cc_data: u16) -> bool {
|
|
||||||
0x1130 == (0x7770 & cc_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_xds(cc_data: u16) -> bool {
|
|
||||||
0x0000 == (0x7070 & cc_data) && 0x0000 != (0x0F0F & cc_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_westeu(cc_data: u16) -> bool {
|
|
||||||
0x1220 == (0x7660 & cc_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_control(cc_data: u16) -> bool {
|
|
||||||
0x1420 == (0x7670 & cc_data) || 0x1720 == (0x7770 & cc_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse_control(cc_data: u16) -> (ffi::eia608_control_t, i32) {
|
|
||||||
unsafe {
|
|
||||||
let mut chan = 0;
|
|
||||||
let cmd = ffi::eia608_parse_control(cc_data, &mut chan);
|
|
||||||
|
|
||||||
(cmd, chan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct Preamble {
|
|
||||||
pub row: i32,
|
|
||||||
pub col: i32,
|
|
||||||
pub style: TextStyle,
|
|
||||||
pub chan: i32,
|
|
||||||
pub underline: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse_preamble(cc_data: u16) -> Preamble {
|
|
||||||
unsafe {
|
|
||||||
let mut row = 0;
|
|
||||||
let mut col = 0;
|
|
||||||
let mut style = 0;
|
|
||||||
let mut chan = 0;
|
|
||||||
let mut underline = 0;
|
|
||||||
|
|
||||||
ffi::eia608_parse_preamble(
|
|
||||||
cc_data,
|
|
||||||
&mut row,
|
|
||||||
&mut col,
|
|
||||||
&mut style,
|
|
||||||
&mut chan,
|
|
||||||
&mut underline,
|
|
||||||
);
|
|
||||||
|
|
||||||
Preamble {
|
|
||||||
row,
|
|
||||||
col,
|
|
||||||
style: style.into(),
|
|
||||||
chan,
|
|
||||||
underline,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct MidRowChange {
|
|
||||||
pub chan: i32,
|
|
||||||
pub style: TextStyle,
|
|
||||||
pub underline: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse_midrowchange(cc_data: u16) -> MidRowChange {
|
|
||||||
unsafe {
|
|
||||||
let mut chan = 0;
|
|
||||||
let mut style = 0;
|
|
||||||
let mut underline = 0;
|
|
||||||
|
|
||||||
ffi::eia608_parse_midrowchange(cc_data, &mut chan, &mut style, &mut underline);
|
|
||||||
|
|
||||||
MidRowChange {
|
|
||||||
chan,
|
|
||||||
style: style.into(),
|
|
||||||
underline: underline > 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn eia608_to_utf8(cc_data: u16) -> (Option<char>, Option<char>, i32) {
|
|
||||||
unsafe {
|
|
||||||
let mut chan = 0;
|
|
||||||
let mut char1 = [0u8; 5usize];
|
|
||||||
let mut char2 = [0u8; 5usize];
|
|
||||||
|
|
||||||
let n_chars = ffi::eia608_to_utf8(
|
|
||||||
cc_data,
|
|
||||||
&mut chan,
|
|
||||||
char1.as_mut_ptr() as *mut _,
|
|
||||||
char2.as_mut_ptr() as *mut _,
|
|
||||||
);
|
|
||||||
|
|
||||||
let char1 = if n_chars > 0 {
|
|
||||||
Some(
|
|
||||||
std::ffi::CStr::from_bytes_with_nul_unchecked(&char1)
|
|
||||||
.to_string_lossy()
|
|
||||||
.chars()
|
|
||||||
.next()
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let char2 = if n_chars > 1 {
|
|
||||||
Some(
|
|
||||||
std::ffi::CStr::from_bytes_with_nul_unchecked(&char2)
|
|
||||||
.to_string_lossy()
|
|
||||||
.chars()
|
|
||||||
.next()
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
(char1, char2, chan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn eia608_to_text(cc_data: u16) -> String {
|
|
||||||
unsafe {
|
|
||||||
let bufsz = ffi::eia608_to_text(std::ptr::null_mut(), 0, cc_data);
|
|
||||||
let mut data = Vec::with_capacity((bufsz + 1) as usize);
|
|
||||||
ffi::eia608_to_text(data.as_ptr() as *mut _, (bufsz + 1) as usize, cc_data);
|
|
||||||
data.set_len(bufsz as usize);
|
|
||||||
String::from_utf8_unchecked(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub(crate) enum CodeSpace {
|
|
||||||
BasicNA,
|
|
||||||
SpecialNA,
|
|
||||||
WestEU,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CodeSpace {
|
|
||||||
fn from_cc_data(cc_data: u16) -> Self {
|
|
||||||
if is_basicna(cc_data) {
|
|
||||||
Self::BasicNA
|
|
||||||
} else if is_specialna(cc_data) {
|
|
||||||
Self::SpecialNA
|
|
||||||
} else if is_westeu(cc_data) {
|
|
||||||
Self::WestEU
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(crate) struct Cea608Text {
|
|
||||||
pub char1: Option<char>,
|
|
||||||
pub char2: Option<char>,
|
|
||||||
pub code_space: CodeSpace,
|
|
||||||
pub chan: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) enum Cea608 {
|
|
||||||
Duplicate,
|
|
||||||
NewMode(i32, Cea608Mode),
|
|
||||||
EraseDisplay(i32),
|
|
||||||
EraseNonDisplay(i32),
|
|
||||||
CarriageReturn(i32),
|
|
||||||
Backspace(i32),
|
|
||||||
EndOfCaption(i32),
|
|
||||||
TabOffset(i32, u32),
|
|
||||||
Text(Cea608Text),
|
|
||||||
Preamble(Preamble),
|
|
||||||
MidRowChange(MidRowChange),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cea608 {
|
|
||||||
pub fn channel(&self) -> i32 {
|
|
||||||
match self {
|
|
||||||
Self::Duplicate => 0,
|
|
||||||
Self::NewMode(chan, _) => *chan,
|
|
||||||
Self::EraseDisplay(chan) => *chan,
|
|
||||||
Self::EraseNonDisplay(chan) => *chan,
|
|
||||||
Self::CarriageReturn(chan) => *chan,
|
|
||||||
Self::Backspace(chan) => *chan,
|
|
||||||
Self::EndOfCaption(chan) => *chan,
|
|
||||||
Self::TabOffset(chan, _) => *chan,
|
|
||||||
Self::Text(text) => text.chan,
|
|
||||||
Self::Preamble(preamble) => preamble.chan,
|
|
||||||
Self::MidRowChange(midrowchange) => midrowchange.chan,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_control(cc_data: u16) -> Option<Cea608> {
|
|
||||||
let (cmd, chan) = parse_control(cc_data);
|
|
||||||
|
|
||||||
gst::log!(CAT, "Command for CC {}", chan);
|
|
||||||
|
|
||||||
match cmd {
|
|
||||||
ffi::eia608_control_t_eia608_control_resume_direct_captioning => {
|
|
||||||
return Some(Cea608::NewMode(chan, Cea608Mode::PaintOn));
|
|
||||||
}
|
|
||||||
ffi::eia608_control_t_eia608_control_erase_display_memory => {
|
|
||||||
return Some(Cea608::EraseDisplay(chan));
|
|
||||||
}
|
|
||||||
ffi::eia608_control_t_eia608_control_roll_up_2 => {
|
|
||||||
return Some(Cea608::NewMode(chan, Cea608Mode::RollUp2));
|
|
||||||
}
|
|
||||||
ffi::eia608_control_t_eia608_control_roll_up_3 => {
|
|
||||||
return Some(Cea608::NewMode(chan, Cea608Mode::RollUp3));
|
|
||||||
}
|
|
||||||
ffi::eia608_control_t_eia608_control_roll_up_4 => {
|
|
||||||
return Some(Cea608::NewMode(chan, Cea608Mode::RollUp4));
|
|
||||||
}
|
|
||||||
ffi::eia608_control_t_eia608_control_carriage_return => {
|
|
||||||
return Some(Cea608::CarriageReturn(chan));
|
|
||||||
}
|
|
||||||
ffi::eia608_control_t_eia608_control_backspace => {
|
|
||||||
return Some(Cea608::Backspace(chan));
|
|
||||||
}
|
|
||||||
ffi::eia608_control_t_eia608_control_resume_caption_loading => {
|
|
||||||
return Some(Cea608::NewMode(chan, Cea608Mode::PopOn));
|
|
||||||
}
|
|
||||||
ffi::eia608_control_t_eia608_control_erase_non_displayed_memory => {
|
|
||||||
return Some(Cea608::EraseNonDisplay(chan));
|
|
||||||
}
|
|
||||||
ffi::eia608_control_t_eia608_control_end_of_caption => {
|
|
||||||
return Some(Cea608::EndOfCaption(chan));
|
|
||||||
}
|
|
||||||
ffi::eia608_control_t_eia608_tab_offset_0
|
|
||||||
| ffi::eia608_control_t_eia608_tab_offset_1
|
|
||||||
| ffi::eia608_control_t_eia608_tab_offset_2
|
|
||||||
| ffi::eia608_control_t_eia608_tab_offset_3 => {
|
|
||||||
return Some(Cea608::TabOffset(
|
|
||||||
chan,
|
|
||||||
cmd - ffi::eia608_control_t_eia608_tab_offset_0,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// TODO
|
|
||||||
ffi::eia608_control_t_eia608_control_alarm_off
|
|
||||||
| ffi::eia608_control_t_eia608_control_delete_to_end_of_row => {}
|
|
||||||
ffi::eia608_control_t_eia608_control_alarm_on
|
|
||||||
| ffi::eia608_control_t_eia608_control_text_restart
|
|
||||||
| ffi::eia608_control_t_eia608_control_text_resume_text_display => {}
|
|
||||||
_ => {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub(crate) struct Cea608StateTracker {
|
|
||||||
last_cc_data: Option<u16>,
|
|
||||||
last_channel: i32,
|
|
||||||
pending_output: Vec<Cea608>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cea608StateTracker {
|
|
||||||
pub(crate) fn push_cc_data(&mut self, cc_data: u16) {
|
|
||||||
if (is_specialna(cc_data) || is_control(cc_data)) && Some(cc_data) == self.last_cc_data {
|
|
||||||
gst::log!(CAT, "Skipping duplicate");
|
|
||||||
self.pending_output.push(Cea608::Duplicate);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_xds(cc_data) {
|
|
||||||
gst::log!(CAT, "XDS, ignoring");
|
|
||||||
} else if is_control(cc_data) {
|
|
||||||
if let Some(d) = decode_control(cc_data) {
|
|
||||||
self.last_channel = d.channel();
|
|
||||||
self.pending_output.push(d);
|
|
||||||
}
|
|
||||||
} else if is_basicna(cc_data) || is_specialna(cc_data) || is_westeu(cc_data) {
|
|
||||||
let (char1, char2, chan) = eia608_to_utf8(cc_data);
|
|
||||||
if char1.is_some() || char2.is_some() {
|
|
||||||
let chan = if is_basicna(cc_data) {
|
|
||||||
/* basicna doesn't include a channel indicator so we track the last generated
|
|
||||||
* one */
|
|
||||||
self.last_channel
|
|
||||||
} else {
|
|
||||||
chan
|
|
||||||
};
|
|
||||||
self.pending_output.push(Cea608::Text(Cea608Text {
|
|
||||||
char1,
|
|
||||||
char2,
|
|
||||||
code_space: CodeSpace::from_cc_data(cc_data),
|
|
||||||
chan,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else if is_preamble(cc_data) {
|
|
||||||
let preamble = parse_preamble(cc_data);
|
|
||||||
self.last_channel = preamble.chan;
|
|
||||||
self.pending_output.push(Cea608::Preamble(preamble));
|
|
||||||
} else if is_midrowchange(cc_data) {
|
|
||||||
let midrowchange = parse_midrowchange(cc_data);
|
|
||||||
self.last_channel = midrowchange.chan;
|
|
||||||
self.pending_output.push(Cea608::MidRowChange(midrowchange));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn pop(&mut self) -> Option<Cea608> {
|
|
||||||
self.pending_output.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn flush(&mut self) {
|
|
||||||
self.pending_output = vec![];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -33,39 +33,55 @@ pub enum Cea708Mode {
|
||||||
RollUp,
|
RollUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn textstyle_foreground_color(style: TextStyle) -> Color {
|
pub fn textstyle_foreground_color(style: TextStyle) -> cea708_types::tables::Color {
|
||||||
match style {
|
let color = match style {
|
||||||
TextStyle::Red => Color {
|
TextStyle::Red => cea608_types::tables::Color::Red,
|
||||||
|
TextStyle::Blue => cea608_types::tables::Color::Blue,
|
||||||
|
TextStyle::Cyan => cea608_types::tables::Color::Cyan,
|
||||||
|
TextStyle::White => cea608_types::tables::Color::White,
|
||||||
|
TextStyle::Green => cea608_types::tables::Color::Green,
|
||||||
|
TextStyle::Yellow => cea608_types::tables::Color::Yellow,
|
||||||
|
TextStyle::Magenta => cea608_types::tables::Color::Magenta,
|
||||||
|
TextStyle::ItalicWhite => cea608_types::tables::Color::White,
|
||||||
|
};
|
||||||
|
cea608_color_to_foreground_color(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cea608_color_to_foreground_color(
|
||||||
|
color: cea608_types::tables::Color,
|
||||||
|
) -> cea708_types::tables::Color {
|
||||||
|
match color {
|
||||||
|
cea608_types::tables::Color::Red => cea708_types::tables::Color {
|
||||||
r: ColorValue::Full,
|
r: ColorValue::Full,
|
||||||
g: ColorValue::None,
|
g: ColorValue::None,
|
||||||
b: ColorValue::None,
|
b: ColorValue::None,
|
||||||
},
|
},
|
||||||
TextStyle::Green => Color {
|
cea608_types::tables::Color::Green => cea708_types::tables::Color {
|
||||||
r: ColorValue::None,
|
r: ColorValue::None,
|
||||||
g: ColorValue::Full,
|
g: ColorValue::Full,
|
||||||
b: ColorValue::None,
|
b: ColorValue::None,
|
||||||
},
|
},
|
||||||
TextStyle::Blue => Color {
|
cea608_types::tables::Color::Blue => cea708_types::tables::Color {
|
||||||
r: ColorValue::None,
|
r: ColorValue::None,
|
||||||
g: ColorValue::None,
|
g: ColorValue::None,
|
||||||
b: ColorValue::Full,
|
b: ColorValue::Full,
|
||||||
},
|
},
|
||||||
TextStyle::Cyan => Color {
|
cea608_types::tables::Color::Cyan => cea708_types::tables::Color {
|
||||||
r: ColorValue::None,
|
r: ColorValue::None,
|
||||||
g: ColorValue::Full,
|
g: ColorValue::Full,
|
||||||
b: ColorValue::Full,
|
b: ColorValue::Full,
|
||||||
},
|
},
|
||||||
TextStyle::Yellow => Color {
|
cea608_types::tables::Color::Yellow => cea708_types::tables::Color {
|
||||||
r: ColorValue::Full,
|
r: ColorValue::Full,
|
||||||
g: ColorValue::Full,
|
g: ColorValue::Full,
|
||||||
b: ColorValue::None,
|
b: ColorValue::None,
|
||||||
},
|
},
|
||||||
TextStyle::Magenta => Color {
|
cea608_types::tables::Color::Magenta => cea708_types::tables::Color {
|
||||||
r: ColorValue::Full,
|
r: ColorValue::Full,
|
||||||
g: ColorValue::None,
|
g: ColorValue::None,
|
||||||
b: ColorValue::Full,
|
b: ColorValue::Full,
|
||||||
},
|
},
|
||||||
TextStyle::White | TextStyle::ItalicWhite => Color {
|
cea608_types::tables::Color::White => cea708_types::tables::Color {
|
||||||
r: ColorValue::Full,
|
r: ColorValue::Full,
|
||||||
g: ColorValue::Full,
|
g: ColorValue::Full,
|
||||||
b: ColorValue::Full,
|
b: ColorValue::Full,
|
||||||
|
@ -213,7 +229,7 @@ impl Cea708ServiceWriter {
|
||||||
|
|
||||||
pub fn rollup_preamble(&mut self, rollup_count: u8, base_row: u8) {
|
pub fn rollup_preamble(&mut self, rollup_count: u8, base_row: u8) {
|
||||||
let base_row = std::cmp::max(rollup_count, base_row);
|
let base_row = std::cmp::max(rollup_count, base_row);
|
||||||
let anchor_vertical = (base_row as u32 * 100 / 15) as u8;
|
let anchor_vertical = (base_row as u32 * 100 / 14) as u8;
|
||||||
gst::trace!(
|
gst::trace!(
|
||||||
CAT,
|
CAT,
|
||||||
"rollup_preamble base {base_row} count {rollup_count}, anchor-v {anchor_vertical}"
|
"rollup_preamble base {base_row} count {rollup_count}, anchor-v {anchor_vertical}"
|
||||||
|
|
|
@ -16,7 +16,6 @@ use std::sync::Mutex;
|
||||||
|
|
||||||
use crate::cea608utils::Cea608Mode;
|
use crate::cea608utils::Cea608Mode;
|
||||||
use crate::cea608utils::TextStyle;
|
use crate::cea608utils::TextStyle;
|
||||||
use crate::ffi;
|
|
||||||
use crate::ttutils::Chunk;
|
use crate::ttutils::Chunk;
|
||||||
use crate::ttutils::Line;
|
use crate::ttutils::Line;
|
||||||
use crate::ttutils::Lines;
|
use crate::ttutils::Lines;
|
||||||
|
@ -75,40 +74,30 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
fn eia608_to_text(cc_data: u16) -> String {
|
|
||||||
unsafe {
|
|
||||||
let bufsz = ffi::eia608_to_text(std::ptr::null_mut(), 0, cc_data);
|
|
||||||
let mut data = Vec::with_capacity((bufsz + 1) as usize);
|
|
||||||
ffi::eia608_to_text(data.as_ptr() as *mut _, (bufsz + 1) as usize, cc_data);
|
|
||||||
data.set_len(bufsz as usize);
|
|
||||||
String::from_utf8_unchecked(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cc_data_buffer(
|
fn cc_data_buffer(
|
||||||
imp: &TtToCea608,
|
imp: &TtToCea608,
|
||||||
cc_data: u16,
|
cc_data: [u8; 2],
|
||||||
pts: gst::ClockTime,
|
pts: gst::ClockTime,
|
||||||
duration: gst::ClockTime,
|
duration: gst::ClockTime,
|
||||||
) -> gst::Buffer {
|
) -> gst::Buffer {
|
||||||
let mut ret = gst::Buffer::with_size(2).unwrap();
|
let mut ret = gst::Buffer::with_size(2).unwrap();
|
||||||
let buf_mut = ret.get_mut().unwrap();
|
let buf_mut = ret.get_mut().unwrap();
|
||||||
let data = cc_data.to_be_bytes();
|
|
||||||
|
|
||||||
if cc_data != 0x8080 {
|
if cc_data != [0x80, 0x80] {
|
||||||
|
let code = cea608_types::tables::Code::from_data(cc_data);
|
||||||
gst::log!(
|
gst::log!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: imp,
|
imp: imp,
|
||||||
"{} -> {}: {}",
|
"{} -> {}: {:?}",
|
||||||
pts,
|
pts,
|
||||||
pts + duration,
|
pts + duration,
|
||||||
eia608_to_text(cc_data)
|
code
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
gst::trace!(CAT, imp: imp, "{} -> {}: padding", pts, pts + duration);
|
gst::trace!(CAT, imp: imp, "{} -> {}: padding", pts, pts + duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
buf_mut.copy_from_slice(0, &data).unwrap();
|
buf_mut.copy_from_slice(0, &cc_data).unwrap();
|
||||||
buf_mut.set_pts(pts);
|
buf_mut.set_pts(pts);
|
||||||
buf_mut.set_duration(duration);
|
buf_mut.set_duration(duration);
|
||||||
|
|
||||||
|
@ -301,6 +290,7 @@ impl TtToCea608 {
|
||||||
let framerate = s.get::<gst::Fraction>("framerate").unwrap();
|
let framerate = s.get::<gst::Fraction>("framerate").unwrap();
|
||||||
state.framerate = framerate;
|
state.framerate = framerate;
|
||||||
state.translator.set_framerate(framerate);
|
state.translator.set_framerate(framerate);
|
||||||
|
state.translator.set_caption_id(cea608_types::Id::CC1);
|
||||||
|
|
||||||
let upstream_caps = e.caps();
|
let upstream_caps = e.caps();
|
||||||
let s = upstream_caps.structure(0).unwrap();
|
let s = upstream_caps.structure(0).unwrap();
|
||||||
|
@ -362,7 +352,9 @@ impl TtToCea608 {
|
||||||
*state = State::default();
|
*state = State::default();
|
||||||
state.translator.set_mode(settings.mode);
|
state.translator.set_mode(settings.mode);
|
||||||
state.translator.set_framerate(framerate);
|
state.translator.set_framerate(framerate);
|
||||||
state.translator.set_origin_column(settings.origin_column);
|
state
|
||||||
|
.translator
|
||||||
|
.set_origin_column(settings.origin_column as u8);
|
||||||
state
|
state
|
||||||
.translator
|
.translator
|
||||||
.set_roll_up_timeout(settings.roll_up_timeout);
|
.set_roll_up_timeout(settings.roll_up_timeout);
|
||||||
|
@ -606,7 +598,9 @@ impl ElementImpl for TtToCea608 {
|
||||||
*state = State::default();
|
*state = State::default();
|
||||||
state.force_clear = false;
|
state.force_clear = false;
|
||||||
state.translator.set_mode(settings.mode);
|
state.translator.set_mode(settings.mode);
|
||||||
state.translator.set_origin_column(settings.origin_column);
|
state
|
||||||
|
.translator
|
||||||
|
.set_origin_column(settings.origin_column as u8);
|
||||||
state.translator.set_framerate(framerate);
|
state.translator.set_framerate(framerate);
|
||||||
state
|
state
|
||||||
.translator
|
.translator
|
||||||
|
|
|
@ -10,9 +10,7 @@ use gst::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use crate::ffi;
|
use crate::cea608utils::{Cea608Mode, TextStyle};
|
||||||
|
|
||||||
use crate::cea608utils::{is_basicna, is_specialna, is_westeu, Cea608Mode, TextStyle};
|
|
||||||
use crate::ttutils::{Chunk, Lines};
|
use crate::ttutils::{Chunk, Lines};
|
||||||
|
|
||||||
pub const DEFAULT_FPS_N: i32 = 30;
|
pub const DEFAULT_FPS_N: i32 = 30;
|
||||||
|
@ -26,65 +24,24 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
static SPACE: Lazy<u16> = Lazy::new(|| eia608_from_utf8_1(&[0x20, 0, 0, 0, 0]));
|
|
||||||
|
|
||||||
fn is_punctuation(word: &str) -> bool {
|
fn is_punctuation(word: &str) -> bool {
|
||||||
word == "." || word == "," || word == "?" || word == "!" || word == ";" || word == ":"
|
word == "." || word == "," || word == "?" || word == "!" || word == ";" || word == ":"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
fn peek_word_length(chars: std::iter::Peekable<std::str::Chars>) -> u8 {
|
||||||
fn eia608_from_utf8_1(c: &[u8; 5]) -> u16 {
|
chars.take_while(|c| !c.is_ascii_whitespace()).count() as u8
|
||||||
assert!(c[4] == 0);
|
|
||||||
unsafe { ffi::eia608_from_utf8_1(c.as_ptr() as *const _, 0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eia608_row_column_preamble(row: i32, col: i32, underline: bool) -> u16 {
|
|
||||||
unsafe {
|
|
||||||
/* Hardcoded chan */
|
|
||||||
ffi::eia608_row_column_pramble(row, col, 0, underline as i32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eia608_row_style_preamble(row: i32, style: u32, underline: bool) -> u16 {
|
|
||||||
unsafe {
|
|
||||||
/* Hardcoded chan */
|
|
||||||
ffi::eia608_row_style_pramble(row, 0, style, underline as i32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eia608_midrow_change(style: u32, underline: bool) -> u16 {
|
|
||||||
unsafe {
|
|
||||||
/* Hardcoded chan and underline */
|
|
||||||
ffi::eia608_midrow_change(0, style, underline as i32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eia608_control_command(cmd: ffi::eia608_control_t) -> u16 {
|
|
||||||
unsafe { ffi::eia608_control_command(cmd, 0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eia608_from_basicna(bna1: u16, bna2: u16) -> u16 {
|
|
||||||
unsafe { ffi::eia608_from_basicna(bna1, bna2) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn erase_non_displayed_memory() -> u16 {
|
|
||||||
eia608_control_command(ffi::eia608_control_t_eia608_control_erase_non_displayed_memory)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn peek_word_length(chars: std::iter::Peekable<std::str::Chars>) -> u32 {
|
|
||||||
chars.take_while(|c| !c.is_ascii_whitespace()).count() as u32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TimedCea608 {
|
pub struct TimedCea608 {
|
||||||
pub cea608: u16,
|
pub cea608: [u8; 2],
|
||||||
pub frame_no: u64,
|
pub frame_no: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TextToCea608 {
|
pub struct TextToCea608 {
|
||||||
// settings
|
// settings
|
||||||
origin_column: u32,
|
origin_column: u8,
|
||||||
framerate: gst::Fraction,
|
framerate: gst::Fraction,
|
||||||
roll_up_timeout: Option<gst::ClockTime>,
|
roll_up_timeout: Option<gst::ClockTime>,
|
||||||
// state
|
// state
|
||||||
|
@ -96,6 +53,8 @@ pub struct TextToCea608 {
|
||||||
underline: bool,
|
underline: bool,
|
||||||
column: u8,
|
column: u8,
|
||||||
mode: Cea608Mode,
|
mode: Cea608Mode,
|
||||||
|
writer: cea608_types::Cea608Writer,
|
||||||
|
caption_id: cea608_types::Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextToCea608 {
|
impl Default for TextToCea608 {
|
||||||
|
@ -112,6 +71,8 @@ impl Default for TextToCea608 {
|
||||||
style: TextStyle::White,
|
style: TextStyle::White,
|
||||||
underline: false,
|
underline: false,
|
||||||
mode: Cea608Mode::PopOn,
|
mode: Cea608Mode::PopOn,
|
||||||
|
writer: cea608_types::Cea608Writer::default(),
|
||||||
|
caption_id: cea608_types::Id::CC1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,10 +82,14 @@ impl TextToCea608 {
|
||||||
self.mode = mode;
|
self.mode = mode;
|
||||||
if self.mode != Cea608Mode::PopOn {
|
if self.mode != Cea608Mode::PopOn {
|
||||||
self.send_roll_up_preamble = true;
|
self.send_roll_up_preamble = true;
|
||||||
self.column = self.origin_column as u8;
|
self.column = self.origin_column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_caption_id(&mut self, id: cea608_types::Id) {
|
||||||
|
self.caption_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_framerate(&mut self, framerate: gst::Fraction) {
|
pub fn set_framerate(&mut self, framerate: gst::Fraction) {
|
||||||
self.framerate = framerate;
|
self.framerate = framerate;
|
||||||
}
|
}
|
||||||
|
@ -137,7 +102,7 @@ impl TextToCea608 {
|
||||||
self.roll_up_timeout = timeout;
|
self.roll_up_timeout = timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_origin_column(&mut self, origin_column: u32) {
|
pub fn set_origin_column(&mut self, origin_column: u8) {
|
||||||
self.origin_column = origin_column
|
self.origin_column = origin_column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +141,7 @@ impl TextToCea608 {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cc_data(&mut self, cc_data: u16) {
|
fn cc_data(&mut self, cc_data: [u8; 2]) {
|
||||||
self.check_erase_display();
|
self.check_erase_display();
|
||||||
|
|
||||||
self.output_frames.push_back(TimedCea608 {
|
self.output_frames.push_back(TimedCea608 {
|
||||||
|
@ -190,101 +155,102 @@ impl TextToCea608 {
|
||||||
fn pad(&mut self, frame_no: u64) {
|
fn pad(&mut self, frame_no: u64) {
|
||||||
while self.last_frame_no < frame_no {
|
while self.last_frame_no < frame_no {
|
||||||
if !self.check_erase_display() {
|
if !self.check_erase_display() {
|
||||||
self.cc_data(0x8080);
|
self.cc_data([0x80, 0x80]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn control_code(&mut self, control: cea608_types::tables::Control) {
|
||||||
|
let code = cea608_types::tables::ControlCode::new(
|
||||||
|
self.caption_id.field(),
|
||||||
|
self.caption_id.channel(),
|
||||||
|
control,
|
||||||
|
);
|
||||||
|
let mut vec = smallvec::SmallVec::<[u8; 2]>::new();
|
||||||
|
let _ = cea608_types::tables::Code::Control(code).write(&mut vec);
|
||||||
|
self.cc_data([vec[0], vec[1]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn erase_non_displayed_memory(&mut self) {
|
||||||
|
self.control_code(cea608_types::tables::Control::EraseNonDisplayedMemory);
|
||||||
|
}
|
||||||
|
|
||||||
fn resume_caption_loading(&mut self) {
|
fn resume_caption_loading(&mut self) {
|
||||||
self.cc_data(eia608_control_command(
|
self.control_code(cea608_types::tables::Control::ResumeCaptionLoading);
|
||||||
ffi::eia608_control_t_eia608_control_resume_caption_loading,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resume_direct_captioning(&mut self) {
|
fn resume_direct_captioning(&mut self) {
|
||||||
self.cc_data(eia608_control_command(
|
self.control_code(cea608_types::tables::Control::ResumeDirectionCaptioning);
|
||||||
ffi::eia608_control_t_eia608_control_resume_direct_captioning,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_to_end_of_row(&mut self) {
|
fn delete_to_end_of_row(&mut self) {
|
||||||
self.cc_data(eia608_control_command(
|
self.control_code(cea608_types::tables::Control::DeleteToEndOfRow);
|
||||||
ffi::eia608_control_t_eia608_control_delete_to_end_of_row,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn roll_up_2(&mut self) {
|
fn roll_up_2(&mut self) {
|
||||||
self.cc_data(eia608_control_command(
|
self.control_code(cea608_types::tables::Control::RollUp2);
|
||||||
ffi::eia608_control_t_eia608_control_roll_up_2,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn roll_up_3(&mut self) {
|
fn roll_up_3(&mut self) {
|
||||||
self.cc_data(eia608_control_command(
|
self.control_code(cea608_types::tables::Control::RollUp3);
|
||||||
ffi::eia608_control_t_eia608_control_roll_up_3,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn roll_up_4(&mut self) {
|
fn roll_up_4(&mut self) {
|
||||||
self.cc_data(eia608_control_command(
|
self.control_code(cea608_types::tables::Control::RollUp4);
|
||||||
ffi::eia608_control_t_eia608_control_roll_up_4,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn carriage_return(&mut self) {
|
fn carriage_return(&mut self) {
|
||||||
self.cc_data(eia608_control_command(
|
self.control_code(cea608_types::tables::Control::CarriageReturn);
|
||||||
ffi::eia608_control_t_eia608_control_carriage_return,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_of_caption(&mut self) {
|
fn end_of_caption(&mut self) {
|
||||||
self.cc_data(eia608_control_command(
|
self.control_code(cea608_types::tables::Control::EndOfCaption);
|
||||||
ffi::eia608_control_t_eia608_control_end_of_caption,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_offset(&mut self, offset: u32) {
|
fn tab_offset(&mut self, offset: u8) {
|
||||||
match offset {
|
let Some(offset) = cea608_types::tables::Control::tab_offset(offset) else {
|
||||||
0 => (),
|
return;
|
||||||
1 => self.cc_data(eia608_control_command(
|
};
|
||||||
ffi::eia608_control_t_eia608_tab_offset_1,
|
self.control_code(offset);
|
||||||
)),
|
|
||||||
2 => self.cc_data(eia608_control_command(
|
|
||||||
ffi::eia608_control_t_eia608_tab_offset_2,
|
|
||||||
)),
|
|
||||||
3 => self.cc_data(eia608_control_command(
|
|
||||||
ffi::eia608_control_t_eia608_tab_offset_3,
|
|
||||||
)),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preamble_indent(&mut self, row: i32, col: i32, underline: bool) {
|
fn preamble_indent(&mut self, row: u8, col: u8, underline: bool) {
|
||||||
self.cc_data(eia608_row_column_preamble(row, col, underline))
|
let Some(preamble) = cea608_types::tables::PreambleType::from_indent(col) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.control_code(cea608_types::tables::Control::PreambleAddress(
|
||||||
|
cea608_types::tables::PreambleAddressCode::new(row, underline, preamble),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preamble_style(&mut self, row: i32, style: u32, underline: bool) {
|
fn preamble_style(&mut self, row: u8, style: TextStyle, underline: bool) {
|
||||||
self.cc_data(eia608_row_style_preamble(row, style, underline))
|
let preamble = if style.is_italics() {
|
||||||
|
cea608_types::tables::PreambleType::WhiteItalics
|
||||||
|
} else {
|
||||||
|
cea608_types::tables::PreambleType::Color(style.to_cea608_color().unwrap())
|
||||||
|
};
|
||||||
|
self.control_code(cea608_types::tables::Control::PreambleAddress(
|
||||||
|
cea608_types::tables::PreambleAddressCode::new(row, underline, preamble),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn midrow_change(&mut self, style: u32, underline: bool) {
|
fn midrow_change(&mut self, style: TextStyle, underline: bool) {
|
||||||
self.cc_data(eia608_midrow_change(style, underline))
|
let midrow = if style.is_italics() {
|
||||||
}
|
cea608_types::tables::MidRow::new_italics(underline)
|
||||||
|
} else {
|
||||||
fn bna(&mut self, bna1: u16, bna2: u16) {
|
cea608_types::tables::MidRow::new_color(style.to_cea608_color().unwrap(), underline)
|
||||||
self.cc_data(eia608_from_basicna(bna1, bna2))
|
};
|
||||||
|
self.control_code(cea608_types::tables::Control::MidRow(midrow));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn erase_display_memory(&mut self) {
|
fn erase_display_memory(&mut self) {
|
||||||
self.erase_display_frame_no = None;
|
self.erase_display_frame_no = None;
|
||||||
self.cc_data(eia608_control_command(
|
self.control_code(cea608_types::tables::Control::EraseDisplayedMemory);
|
||||||
ffi::eia608_control_t_eia608_control_erase_display_memory,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_chunk(&mut self, chunk: &Chunk, col: u32) -> bool {
|
fn open_chunk(&mut self, chunk: &Chunk, col: u8) -> bool {
|
||||||
if (chunk.style != self.style || chunk.underline != self.underline) && col < 31 {
|
if (chunk.style != self.style || chunk.underline != self.underline) && col < 31 {
|
||||||
self.midrow_change(chunk.style as u32, chunk.underline);
|
self.midrow_change(chunk.style, chunk.underline);
|
||||||
self.style = chunk.style;
|
self.style = chunk.style;
|
||||||
self.underline = chunk.underline;
|
self.underline = chunk.underline;
|
||||||
true
|
true
|
||||||
|
@ -297,8 +263,8 @@ impl TextToCea608 {
|
||||||
fn open_line(
|
fn open_line(
|
||||||
&mut self,
|
&mut self,
|
||||||
chunk: &Chunk,
|
chunk: &Chunk,
|
||||||
col: &mut u32,
|
col: &mut u8,
|
||||||
row: i32,
|
row: u8,
|
||||||
carriage_return: Option<bool>,
|
carriage_return: Option<bool>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut ret = true;
|
let mut ret = true;
|
||||||
|
@ -332,7 +298,7 @@ impl TextToCea608 {
|
||||||
}
|
}
|
||||||
|
|
||||||
if chunk.style != TextStyle::White && indent == 0 {
|
if chunk.style != TextStyle::White && indent == 0 {
|
||||||
self.preamble_style(row, chunk.style as u32, chunk.underline);
|
self.preamble_style(row, chunk.style, chunk.underline);
|
||||||
self.style = chunk.style;
|
self.style = chunk.style;
|
||||||
} else {
|
} else {
|
||||||
if chunk.style != TextStyle::White {
|
if chunk.style != TextStyle::White {
|
||||||
|
@ -346,7 +312,7 @@ impl TextToCea608 {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.style = TextStyle::White;
|
self.style = TextStyle::White;
|
||||||
self.preamble_indent(row, (indent * 4) as i32, chunk.underline);
|
self.preamble_indent(row, indent * 4, chunk.underline);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mode == Cea608Mode::PaintOn {
|
if self.mode == Cea608Mode::PaintOn {
|
||||||
|
@ -393,7 +359,7 @@ impl TextToCea608 {
|
||||||
let mut col = if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
|
let mut col = if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
self.column as u32
|
self.column
|
||||||
};
|
};
|
||||||
|
|
||||||
self.pad(frame_no);
|
self.pad(frame_no);
|
||||||
|
@ -430,19 +396,17 @@ impl TextToCea608 {
|
||||||
if !lines.lines.is_empty() {
|
if !lines.lines.is_empty() {
|
||||||
if self.mode == Cea608Mode::PopOn {
|
if self.mode == Cea608Mode::PopOn {
|
||||||
self.resume_caption_loading();
|
self.resume_caption_loading();
|
||||||
self.cc_data(erase_non_displayed_memory());
|
self.erase_non_displayed_memory();
|
||||||
} else if self.mode == Cea608Mode::PaintOn {
|
} else if self.mode == Cea608Mode::PaintOn {
|
||||||
self.resume_direct_captioning();
|
self.resume_direct_captioning();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut prev_char = 0;
|
|
||||||
|
|
||||||
for line in &lines.lines {
|
for line in &lines.lines {
|
||||||
gst::log!(CAT, "Processing {:?}", line);
|
gst::log!(CAT, "Processing {:?}", line);
|
||||||
|
|
||||||
if let Some(line_row) = line.row {
|
if let Some(line_row) = line.row {
|
||||||
row = line_row;
|
row = line_row as u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
if row > 14 {
|
if row > 14 {
|
||||||
|
@ -454,21 +418,20 @@ impl TextToCea608 {
|
||||||
if self.mode != Cea608Mode::PopOn && self.mode != Cea608Mode::PaintOn {
|
if self.mode != Cea608Mode::PopOn && self.mode != Cea608Mode::PaintOn {
|
||||||
self.send_roll_up_preamble = true;
|
self.send_roll_up_preamble = true;
|
||||||
}
|
}
|
||||||
col = line_column;
|
col = line_column as u8;
|
||||||
} else if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
|
} else if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
|
||||||
col = origin_column;
|
col = origin_column;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (j, chunk) in line.chunks.iter().enumerate() {
|
for (j, chunk) in line.chunks.iter().enumerate() {
|
||||||
let mut prepend_space = true;
|
let mut prepend_space = true;
|
||||||
if prev_char != 0 {
|
while self.writer.n_codes() > 0 {
|
||||||
self.cc_data(prev_char);
|
let data = self.writer.pop();
|
||||||
prev_char = 0;
|
self.cc_data(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if j == 0 {
|
if j == 0 {
|
||||||
prepend_space =
|
prepend_space = self.open_line(chunk, &mut col, row, line.carriage_return);
|
||||||
self.open_line(chunk, &mut col, row as i32, line.carriage_return);
|
|
||||||
} else if self.open_chunk(chunk, col) {
|
} else if self.open_chunk(chunk, col) {
|
||||||
prepend_space = false;
|
prepend_space = false;
|
||||||
col += 1;
|
col += 1;
|
||||||
|
@ -495,40 +458,22 @@ impl TextToCea608 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut encoded = [0; 5];
|
let code = cea608_types::tables::Code::from_char(
|
||||||
c.encode_utf8(&mut encoded);
|
c,
|
||||||
let mut cc_data = eia608_from_utf8_1(&encoded);
|
cea608_types::tables::Channel::ONE,
|
||||||
|
)
|
||||||
if cc_data == 0 {
|
.unwrap_or_else(|| {
|
||||||
gst::warning!(CAT, "Not translating UTF8: {}", c);
|
gst::warning!(CAT, "Not translating UTF8: {}", c);
|
||||||
cc_data = *SPACE;
|
cea608_types::tables::Code::Space
|
||||||
}
|
});
|
||||||
|
|
||||||
if is_basicna(prev_char) {
|
self.writer.push(code);
|
||||||
if is_basicna(cc_data) {
|
|
||||||
self.bna(prev_char, cc_data);
|
if let cea608_types::tables::Code::Control(_) = code {
|
||||||
} else if is_westeu(cc_data) {
|
while self.writer.n_codes() > 0 {
|
||||||
// extended characters overwrite the previous character,
|
let data = self.writer.pop();
|
||||||
// so insert a dummy char then write the extended char
|
self.cc_data(data);
|
||||||
self.bna(prev_char, *SPACE);
|
|
||||||
self.cc_data(cc_data);
|
|
||||||
} else {
|
|
||||||
self.cc_data(prev_char);
|
|
||||||
self.cc_data(cc_data);
|
|
||||||
}
|
}
|
||||||
prev_char = 0;
|
|
||||||
} else if is_westeu(cc_data) {
|
|
||||||
// extended characters overwrite the previous character,
|
|
||||||
// so insert a dummy char then write the extended char
|
|
||||||
self.cc_data(*SPACE);
|
|
||||||
self.cc_data(cc_data);
|
|
||||||
} else if is_basicna(cc_data) {
|
|
||||||
prev_char = cc_data;
|
|
||||||
} else {
|
|
||||||
self.cc_data(cc_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_specialna(cc_data) {
|
|
||||||
// adapted from libcaption's generation code:
|
// adapted from libcaption's generation code:
|
||||||
// specialna are treated as control characters. Duplicated control characters are discarded
|
// specialna are treated as control characters. Duplicated control characters are discarded
|
||||||
// So we write a resume after a specialna as a noop control command to break repetition detection
|
// So we write a resume after a specialna as a noop control command to break repetition detection
|
||||||
|
@ -560,12 +505,12 @@ impl TextToCea608 {
|
||||||
if (next_word_length <= 32 - origin_column && col + next_word_length > 31)
|
if (next_word_length <= 32 - origin_column && col + next_word_length > 31)
|
||||||
|| col > 31
|
|| col > 31
|
||||||
{
|
{
|
||||||
if prev_char != 0 {
|
while self.writer.n_codes() > 0 {
|
||||||
self.cc_data(prev_char);
|
let data = self.writer.pop();
|
||||||
prev_char = 0;
|
self.cc_data(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.open_line(chunk, &mut col, row as i32, Some(true));
|
self.open_line(chunk, &mut col, row, Some(true));
|
||||||
}
|
}
|
||||||
} else if col > 31 {
|
} else if col > 31 {
|
||||||
if chars.peek().is_some() {
|
if chars.peek().is_some() {
|
||||||
|
@ -577,17 +522,18 @@ impl TextToCea608 {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
|
if self.mode == Cea608Mode::PopOn || self.mode == Cea608Mode::PaintOn {
|
||||||
if prev_char != 0 {
|
while self.writer.n_codes() > 0 {
|
||||||
self.cc_data(prev_char);
|
let data = self.writer.pop();
|
||||||
prev_char = 0;
|
self.cc_data(data);
|
||||||
}
|
}
|
||||||
row += 1;
|
row += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lines.lines.is_empty() {
|
if !lines.lines.is_empty() {
|
||||||
if prev_char != 0 {
|
while self.writer.n_codes() > 0 {
|
||||||
self.cc_data(prev_char);
|
let data = self.writer.pop();
|
||||||
|
self.cc_data(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mode == Cea608Mode::PopOn {
|
if self.mode == Cea608Mode::PopOn {
|
||||||
|
@ -597,7 +543,7 @@ impl TextToCea608 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.column = col as u8;
|
self.column = col;
|
||||||
|
|
||||||
if self.mode == Cea608Mode::PopOn {
|
if self.mode == Cea608Mode::PopOn {
|
||||||
self.erase_display_frame_no =
|
self.erase_display_frame_no =
|
||||||
|
|
|
@ -360,7 +360,13 @@ impl TtToCea708 {
|
||||||
.translator
|
.translator
|
||||||
.set_roll_up_timeout(settings.roll_up_timeout);
|
.set_roll_up_timeout(settings.roll_up_timeout);
|
||||||
state.translator.set_roll_up_count(settings.roll_up_rows);
|
state.translator.set_roll_up_count(settings.roll_up_rows);
|
||||||
state.translator.set_cea608_channel(settings.cea608_channel);
|
state
|
||||||
|
.translator
|
||||||
|
.set_cea608_channel(if settings.cea608_channel < 1 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(cea608_types::Id::from_value(settings.cea608_channel as i8))
|
||||||
|
});
|
||||||
state.translator.set_service_no(settings.service_no);
|
state.translator.set_service_no(settings.service_no);
|
||||||
state.translator.flush();
|
state.translator.flush();
|
||||||
|
|
||||||
|
@ -526,7 +532,13 @@ impl ObjectImpl for TtToCea708 {
|
||||||
let mut settings = self.settings.lock().unwrap();
|
let mut settings = self.settings.lock().unwrap();
|
||||||
let channel = value.get::<u32>().expect("type checked upstream") as u8;
|
let channel = value.get::<u32>().expect("type checked upstream") as u8;
|
||||||
settings.cea608_channel = channel;
|
settings.cea608_channel = channel;
|
||||||
state.translator.set_cea608_channel(channel);
|
state
|
||||||
|
.translator
|
||||||
|
.set_cea608_channel(if settings.cea608_channel < 1 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(cea608_types::Id::from_value(settings.cea608_channel as i8))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
"roll-up-rows" => {
|
"roll-up-rows" => {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
|
@ -657,7 +669,13 @@ impl ElementImpl for TtToCea708 {
|
||||||
.translator
|
.translator
|
||||||
.set_roll_up_timeout(settings.roll_up_timeout);
|
.set_roll_up_timeout(settings.roll_up_timeout);
|
||||||
state.translator.set_column(settings.origin_column as u8);
|
state.translator.set_column(settings.origin_column as u8);
|
||||||
state.translator.set_cea608_channel(settings.cea608_channel);
|
state
|
||||||
|
.translator
|
||||||
|
.set_cea608_channel(if settings.cea608_channel < 1 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(cea608_types::Id::from_value(settings.cea608_channel as i8))
|
||||||
|
});
|
||||||
state.translator.set_service_no(settings.service_no);
|
state.translator.set_service_no(settings.service_no);
|
||||||
state.translator.flush();
|
state.translator.flush();
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ pub struct TextToCea708 {
|
||||||
mode: Cea708Mode,
|
mode: Cea708Mode,
|
||||||
roll_up_count: u8,
|
roll_up_count: u8,
|
||||||
service_no: u8,
|
service_no: u8,
|
||||||
cea608_channel: u8,
|
cea608_channel: Option<cea608_types::Id>,
|
||||||
origin_column: u32,
|
origin_column: u32,
|
||||||
roll_up_timeout: Option<gst::ClockTime>,
|
roll_up_timeout: Option<gst::ClockTime>,
|
||||||
framerate: gst::Fraction,
|
framerate: gst::Fraction,
|
||||||
|
@ -78,7 +78,7 @@ impl Default for TextToCea708 {
|
||||||
mode: Cea708Mode::RollUp,
|
mode: Cea708Mode::RollUp,
|
||||||
roll_up_count: 2,
|
roll_up_count: 2,
|
||||||
service_no: 1,
|
service_no: 1,
|
||||||
cea608_channel: 1,
|
cea608_channel: Some(cea608_types::Id::CC1),
|
||||||
origin_column: 0,
|
origin_column: 0,
|
||||||
framerate: gst::Fraction::new(DEFAULT_FPS_N, DEFAULT_FPS_D),
|
framerate: gst::Fraction::new(DEFAULT_FPS_N, DEFAULT_FPS_D),
|
||||||
roll_up_timeout: None,
|
roll_up_timeout: None,
|
||||||
|
@ -117,7 +117,7 @@ impl TextToCea708 {
|
||||||
|
|
||||||
pub fn set_origin_column(&mut self, origin_column: u32) {
|
pub fn set_origin_column(&mut self, origin_column: u32) {
|
||||||
self.origin_column = origin_column;
|
self.origin_column = origin_column;
|
||||||
self.cea608.set_origin_column(origin_column);
|
self.cea608.set_origin_column(origin_column as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_column(&mut self, column: u8) {
|
pub fn set_column(&mut self, column: u8) {
|
||||||
|
@ -151,10 +151,12 @@ impl TextToCea708 {
|
||||||
self.framerate
|
self.framerate
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cea608_channel(&mut self, channel: u8) {
|
pub fn set_cea608_channel(&mut self, channel: Option<cea608_types::Id>) {
|
||||||
assert!((0..=4).contains(&channel));
|
|
||||||
if self.cea608_channel != channel {
|
if self.cea608_channel != channel {
|
||||||
self.cea608.flush();
|
self.cea608.flush();
|
||||||
|
if let Some(id) = channel {
|
||||||
|
self.cea608.set_caption_id(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.cea608_channel = channel;
|
self.cea608_channel = channel;
|
||||||
}
|
}
|
||||||
|
@ -271,23 +273,19 @@ impl TextToCea708 {
|
||||||
}
|
}
|
||||||
gst::trace!(CAT, "push packet to writer");
|
gst::trace!(CAT, "push packet to writer");
|
||||||
self.cc_data_writer.push_packet(packet);
|
self.cc_data_writer.push_packet(packet);
|
||||||
if self.cea608_channel > 0 {
|
if let Some(cea608_id) = self.cea608_channel {
|
||||||
let tcea608 = self.cea608.pop_output().unwrap_or(TimedCea608 {
|
let tcea608 = self.cea608.pop_output().unwrap_or(TimedCea608 {
|
||||||
cea608: 0x8080,
|
cea608: [0x80, 0x80],
|
||||||
frame_no: self.last_frame_no,
|
frame_no: self.last_frame_no,
|
||||||
});
|
});
|
||||||
let (byte0, byte1) = (
|
let (byte0, byte1) = (tcea608.cea608[0], tcea608.cea608[1]);
|
||||||
((tcea608.cea608 & 0xff00) >> 8) as u8,
|
match cea608_id.field() {
|
||||||
(tcea608.cea608 & 0xff) as u8,
|
cea608_types::tables::Field::ONE => self
|
||||||
);
|
|
||||||
match self.cea608_channel {
|
|
||||||
1 | 2 => self
|
|
||||||
.cc_data_writer
|
.cc_data_writer
|
||||||
.push_cea608(cea708_types::Cea608::Field1(byte0, byte1)),
|
.push_cea608(cea708_types::Cea608::Field1(byte0, byte1)),
|
||||||
3 | 4 => self
|
cea608_types::tables::Field::TWO => self
|
||||||
.cc_data_writer
|
.cc_data_writer
|
||||||
.push_cea608(cea708_types::Cea608::Field2(byte0, byte1)),
|
.push_cea608(cea708_types::Cea608::Field2(byte0, byte1)),
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,7 +329,7 @@ impl TextToCea708 {
|
||||||
let frame_no = frame_no.max(self.last_frame_no);
|
let frame_no = frame_no.max(self.last_frame_no);
|
||||||
let end_frame_no = end_frame_no.max(frame_no);
|
let end_frame_no = end_frame_no.max(frame_no);
|
||||||
|
|
||||||
if self.cea608_channel > 0 {
|
if self.cea608_channel.is_some() {
|
||||||
self.cea608.generate(frame_no, end_frame_no, lines.clone());
|
self.cea608.generate(frame_no, end_frame_no, lines.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,29 +133,7 @@ fn test_rollup() {
|
||||||
],
|
],
|
||||||
), // RU3 -> DeleteWindows(!0), DefineWindow(0...), SetPenLocation(bottom-row)
|
), // RU3 -> DeleteWindows(!0), DefineWindow(0...), SetPenLocation(bottom-row)
|
||||||
([0x94, 0xAD], vec![Code::CR, Code::ETX]), // CR -> CR
|
([0x94, 0xAD], vec![Code::CR, Code::ETX]), // CR -> CR
|
||||||
(
|
([0x94, 0x70], vec![Code::ETX]), // PAC to bottom left -> (pen already there) -> nothing to do
|
||||||
[0x94, 0x70],
|
|
||||||
vec![
|
|
||||||
Code::DeleteWindows(!WindowBits::ZERO),
|
|
||||||
Code::DefineWindow(DefineWindowArgs::new(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
Anchor::BottomMiddle,
|
|
||||||
true,
|
|
||||||
93,
|
|
||||||
50,
|
|
||||||
2,
|
|
||||||
31,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
)),
|
|
||||||
Code::SetPenLocation(SetPenLocationArgs::new(2, 0)),
|
|
||||||
Code::ETX,
|
|
||||||
],
|
|
||||||
), // PAC to bottom left -> SetPenLocation(...)
|
|
||||||
(
|
(
|
||||||
[0xA8, 0x43],
|
[0xA8, 0x43],
|
||||||
vec![Code::LeftParenthesis, Code::LatinCapitalC, Code::ETX],
|
vec![Code::LeftParenthesis, Code::LatinCapitalC, Code::ETX],
|
||||||
|
@ -169,7 +147,7 @@ fn test_rollup() {
|
||||||
0,
|
0,
|
||||||
Anchor::BottomMiddle,
|
Anchor::BottomMiddle,
|
||||||
true,
|
true,
|
||||||
93,
|
100,
|
||||||
50,
|
50,
|
||||||
2,
|
2,
|
||||||
31,
|
31,
|
||||||
|
@ -183,7 +161,7 @@ fn test_rollup() {
|
||||||
Code::ETX,
|
Code::ETX,
|
||||||
],
|
],
|
||||||
), // RU3
|
), // RU3
|
||||||
([0x94, 0xAD], vec![Code::CR, Code::ETX]), // CR -> CR
|
([0x94, 0xAD], vec![Code::CR, Code::ETX]), // CR -> CR
|
||||||
([0x94, 0x70], vec![Code::ETX]), // PAC to bottom left -> SetPenLocation(...)
|
([0x94, 0x70], vec![Code::ETX]), // PAC to bottom left -> SetPenLocation(...)
|
||||||
(
|
(
|
||||||
[0xF2, 0xEF],
|
[0xF2, 0xEF],
|
||||||
|
|
Loading…
Reference in a new issue