cea608tott: use our own CEA-608 frame handling instead of libcaption

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1517>
This commit is contained in:
Matthew Waters 2024-03-25 10:43:26 +11:00
parent d8fe1c64f1
commit 2575013faa
3 changed files with 80 additions and 37 deletions

View file

@ -6,15 +6,17 @@
//
// SPDX-License-Identifier: MPL-2.0
use cea608_types::Cea608State;
use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use crate::caption_frame::{CaptionFrame, Status};
use atomic_refcell::AtomicRefCell;
use once_cell::sync::Lazy;
use crate::cea608utils::Cea608Frame;
#[derive(Copy, Clone, Debug)]
enum Format {
Srt,
@ -25,7 +27,8 @@ enum Format {
struct State {
format: Option<Format>,
wrote_header: bool,
caption_frame: CaptionFrame,
state: Cea608State,
frame: Cea608Frame,
previous_text: Option<(gst::ClockTime, String)>,
index: u64,
}
@ -35,7 +38,8 @@ impl Default for State {
State {
format: None,
wrote_header: false,
caption_frame: CaptionFrame::default(),
state: Cea608State::default(),
frame: Cea608Frame::new(),
previous_text: None,
index: 1,
}
@ -79,8 +83,6 @@ impl Cea608ToTt {
gst::FlowError::Error
})?;
let pts = (buffer_pts.nseconds() as f64) / 1_000_000_000.0;
let data = buffer.map_readable().map_err(|_| {
gst::error!(CAT, obj: pad, "Can't map buffer readable");
@ -93,39 +95,48 @@ impl Cea608ToTt {
return Ok(gst::FlowSuccess::Ok);
}
let previous_text = match state
.caption_frame
.decode((data[0] as u16) << 8 | data[1] as u16, pts)
{
Ok(Status::Ok) => return Ok(gst::FlowSuccess::Ok),
Err(_) => {
gst::error!(CAT, obj: pad, "Failed to decode closed caption packet");
return Ok(gst::FlowSuccess::Ok);
}
Ok(Status::Clear) => {
gst::debug!(CAT, obj: pad, "Clearing previous closed caption packet");
state.previous_text.take()
}
Ok(Status::Ready) => {
gst::debug!(CAT, obj: pad, "Have new closed caption packet");
let text = match state.caption_frame.to_text(false) {
Ok(text) => text,
Err(_) => {
gst::error!(CAT, obj: pad, "Failed to convert caption frame to text");
let previous_text = {
match state.state.decode([data[0], data[1]]) {
Err(e) => {
gst::error!(CAT, obj: pad, "Failed to decode closed caption packet: {e:?}");
return Ok(gst::FlowSuccess::Ok);
}
Ok(Some(cea608)) => {
gst::trace!(CAT, obj: pad, "received {:x?} cea608: {cea608:?}", [data[0], data[1]]);
if state.frame.push_code(cea608) {
let text = state.frame.get_text();
gst::trace!(CAT, obj: pad, "generated text: {text}");
if text.is_empty() {
state.previous_text.take()
} else if state.frame.mode() == Some(cea608_types::Mode::PaintOn)
|| matches!(
cea608,
cea608_types::Cea608::EraseDisplay(_)
| cea608_types::Cea608::Backspace(_)
| cea608_types::Cea608::EndOfCaption(_)
| cea608_types::Cea608::DeleteToEndOfRow(_)
)
{
// only in some specific circumstances do we want to actually change
// our generated text
state.previous_text.replace((buffer_pts, text))
} else {
return Ok(gst::FlowSuccess::Ok);
}
} else {
// no change, nothing to do
return Ok(gst::FlowSuccess::Ok);
}
};
state.previous_text.replace((buffer_pts, text))
}
Ok(None) => {
return Ok(gst::FlowSuccess::Ok);
}
}
};
let previous_text = match previous_text {
Some(previous_text) => previous_text,
None => {
gst::debug!(CAT, obj: pad, "Have no previous text");
return Ok(gst::FlowSuccess::Ok);
}
let Some(previous_text) = previous_text else {
gst::debug!(CAT, obj: pad, "Have no previous text");
return Ok(gst::FlowSuccess::Ok);
};
let duration = buffer_pts.saturating_sub(previous_text.0);
@ -317,7 +328,8 @@ impl Cea608ToTt {
}
EventView::FlushStop(..) => {
let mut state = self.state.borrow_mut();
state.caption_frame = CaptionFrame::default();
state.frame = Cea608Frame::new();
state.state = Cea608State::default();
state.previous_text = None;
}
EventView::Eos(..) => {

View file

@ -408,6 +408,37 @@ impl Cea608Frame {
pub fn iter(&self) -> impl Iterator<Item = &Cea608Line> {
self.display_lines.iter()
}
pub fn get_text(&self) -> String {
let mut text = String::new();
for (i, line) in self.iter().enumerate() {
let mut seen_non_space = false;
if i != 0 {
text.push('\r');
text.push('\n');
}
for (_i, c) in line.display_iter() {
match c {
Cea608Cell::Empty | Cea608Cell::MidRow(_) => {
if seen_non_space {
text.push(' ')
}
}
Cea608Cell::Char(c) => {
if *c != ' ' {
seen_non_space = true;
}
text.push(*c);
}
}
}
}
text
}
pub fn mode(&self) -> Option<cea608_types::Mode> {
self.mode
}
}
#[derive(Debug)]

View file

@ -43,17 +43,17 @@ fn test_parse() {
(
18_985_633_333.nseconds(),
1_234_566_667.nseconds(),
"Yes, Im supporting\r\nDonald Trump.",
"Yes, I'm supporting\r\nDonald Trump.",
),
(
20_220_200_000.nseconds(),
2_168_833_333.nseconds(),
"Im doing so as enthusiastically\r\nas I can,",
"I'm doing so as enthusiastically\r\nas I can,",
),
(
22_389_033_333.nseconds(),
2_235_566_667.nseconds(),
"even the fact I think\r\nhes a terrible human being.",
"even the fact I think\r\nhe's a terrible human being.",
),
];