gst-plugins-rs/video/closedcaption/src/cea708utils.rs
Matthew Waters 55b4de779c tttocea708: add support for writing 608 compatibility bytes
608 compatibility bytes are generated using the same functionality as
tttocea608.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1406>
2024-03-28 13:46:28 +11:00

276 lines
7.7 KiB
Rust

// Copyright (C) 2023 Matthew Waters <matthew@centricular.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
use cea708_types::{tables::*, Service};
use gst::glib;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use crate::cea608utils::TextStyle;
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
gst::DebugCategory::new(
"cea708utils",
gst::DebugColorFlags::empty(),
Some("CEA-708 Utilities"),
)
});
#[derive(
Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum,
)]
#[repr(u32)]
#[enum_type(name = "GstTtToCea708Mode")]
pub enum Cea708Mode {
PopOn,
PaintOn,
RollUp,
}
pub fn textstyle_foreground_color(style: TextStyle) -> Color {
match style {
TextStyle::Red => Color {
r: ColorValue::Full,
g: ColorValue::None,
b: ColorValue::None,
},
TextStyle::Green => Color {
r: ColorValue::None,
g: ColorValue::Full,
b: ColorValue::None,
},
TextStyle::Blue => Color {
r: ColorValue::None,
g: ColorValue::None,
b: ColorValue::Full,
},
TextStyle::Cyan => Color {
r: ColorValue::None,
g: ColorValue::Full,
b: ColorValue::Full,
},
TextStyle::Yellow => Color {
r: ColorValue::Full,
g: ColorValue::Full,
b: ColorValue::None,
},
TextStyle::Magenta => Color {
r: ColorValue::Full,
g: ColorValue::None,
b: ColorValue::Full,
},
TextStyle::White | TextStyle::ItalicWhite => Color {
r: ColorValue::Full,
g: ColorValue::Full,
b: ColorValue::Full,
},
}
}
pub fn textstyle_to_pen_color(style: TextStyle) -> SetPenColorArgs {
let black = Color {
r: ColorValue::None,
g: ColorValue::None,
b: ColorValue::None,
};
SetPenColorArgs {
foreground_color: textstyle_foreground_color(style),
foreground_opacity: Opacity::Solid,
background_color: black,
background_opacity: Opacity::Solid,
edge_color: black,
}
}
#[derive(Debug)]
pub(crate) struct Cea708ServiceWriter {
codes: Vec<Code>,
service_no: u8,
active_window: WindowBits,
hidden_window: WindowBits,
}
impl Cea708ServiceWriter {
pub fn new(service_no: u8) -> Self {
Self {
codes: vec![],
service_no,
active_window: WindowBits::ZERO,
hidden_window: WindowBits::ONE,
}
}
pub fn take_service(&mut self, available_bytes: usize) -> Option<Service> {
if self.codes.is_empty() {
return None;
}
gst::trace!(CAT, "New service block {}", self.service_no);
let mut service = Service::new(self.service_no);
let mut i = 0;
for code in self.codes.iter() {
if code.byte_len() > service.free_space() {
gst::trace!(CAT, "service is full");
break;
}
if service.len() + code.byte_len() > available_bytes {
gst::trace!(CAT, "packet is full");
break;
}
gst::trace!(CAT, "Adding code {code:?} to service");
match service.push_code(code) {
Ok(_) => i += 1,
Err(_) => break,
}
}
if i == 0 {
return None;
}
self.codes = self.codes.split_off(i);
Some(service)
}
pub fn popon_preamble(&mut self) {
gst::trace!(CAT, "popon_preamble");
let window = match self.hidden_window {
// switch up the newly defined window
WindowBits::ZERO => 0,
WindowBits::ONE => 1,
_ => unreachable!(),
};
let args = DefineWindowArgs::new(
window,
0,
Anchor::BottomMiddle,
false,
70,
105,
14,
31,
true,
true,
false,
1,
1,
);
gst::trace!(CAT, "active window {:?}", self.active_window);
let codes = [
Code::DeleteWindows(!self.active_window),
Code::DefineWindow(args),
];
self.push_codes(&codes)
}
pub fn clear_current_window(&mut self) {
gst::trace!(CAT, "clear_current_window {:?}", self.active_window);
self.push_codes(&[Code::ClearWindows(self.active_window)])
}
pub fn clear_hidden_window(&mut self) {
gst::trace!(CAT, "clear_hidden_window");
self.push_codes(&[Code::ClearWindows(self.hidden_window)])
}
pub fn end_of_caption(&mut self) {
gst::trace!(CAT, "end_of_caption");
self.push_codes(&[Code::ToggleWindows(self.active_window | self.hidden_window)]);
std::mem::swap(&mut self.active_window, &mut self.hidden_window);
gst::trace!(CAT, "active window {:?}", self.active_window);
}
pub fn paint_on_preamble(&mut self) {
gst::trace!(CAT, "paint_on_preamble");
let window = match self.active_window {
WindowBits::ZERO => 0,
WindowBits::ONE => 1,
_ => unreachable!(),
};
self.push_codes(&[
// FIXME: assumes positioning in a 16:9 ratio
Code::DefineWindow(DefineWindowArgs::new(
window,
0,
Anchor::BottomMiddle,
false,
70,
105,
14,
31,
true,
true,
true,
1,
1,
)),
])
}
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;
gst::trace!(
CAT,
"rollup_preamble base {base_row} count {rollup_count}, anchor-v {anchor_vertical}"
);
let codes = [
Code::DeleteWindows(!WindowBits::ZERO),
Code::DefineWindow(DefineWindowArgs::new(
0,
0,
Anchor::BottomMiddle,
true,
anchor_vertical,
50,
rollup_count - 1,
31,
true,
true,
true,
1,
1,
)),
Code::SetPenLocation(SetPenLocationArgs::new(rollup_count - 1, 0)),
];
self.active_window = WindowBits::ZERO;
self.hidden_window = WindowBits::ONE;
self.push_codes(&codes)
}
pub fn write_char(&mut self, c: char) {
if let Some(code) = Code::from_char(c) {
self.push_codes(&[code])
}
}
pub fn push_codes(&mut self, codes: &[Code]) {
gst::log!(CAT, "pushing codes: {codes:?}");
self.codes.extend(codes.iter().cloned());
}
pub fn etx(&mut self) {
self.push_codes(&[Code::ETX])
}
pub fn carriage_return(&mut self) {
self.push_codes(&[Code::CR])
}
pub fn set_pen_attributes(&mut self, args: SetPenAttributesArgs) {
self.push_codes(&[Code::SetPenAttributes(args)])
}
pub fn set_pen_location(&mut self, args: SetPenLocationArgs) {
self.push_codes(&[Code::SetPenLocation(args)])
}
pub fn set_pen_color(&mut self, args: SetPenColorArgs) {
self.push_codes(&[Code::SetPenColor(args)])
}
}