mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-12-19 16:46:35 +00:00
cea708overlay: support changing the safe title area
By default it is 80% of the output size as recommended by CEA-708/608. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1985>
This commit is contained in:
parent
b367b38633
commit
497b1e58bd
5 changed files with 220 additions and 47 deletions
|
@ -7984,6 +7984,34 @@
|
|||
"type": "gint",
|
||||
"writable": true
|
||||
},
|
||||
"safe-title-height": {
|
||||
"blurb": "Ratio of the video height to use as the safe area for caption display",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0.8",
|
||||
"max": "1",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "gfloat",
|
||||
"writable": true
|
||||
},
|
||||
"safe-title-width": {
|
||||
"blurb": "Ratio of the video width to use as the safe area for caption display",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0.8",
|
||||
"max": "1",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "gfloat",
|
||||
"writable": true
|
||||
},
|
||||
"service": {
|
||||
"blurb": "The service to render the caption for when available, (-1=automatic, 0=disabled)",
|
||||
"conditionally-available": false,
|
||||
|
|
|
@ -157,8 +157,6 @@ pub(crate) fn recalculate_pango_layout(
|
|||
video_height: u32,
|
||||
) -> (i32, i32) {
|
||||
let mut font_desc = pango::FontDescription::from_string("monospace");
|
||||
let video_width = video_width * 80 / 100;
|
||||
let video_height = video_height * 80 / 100;
|
||||
|
||||
let mut font_size = 1;
|
||||
loop {
|
||||
|
|
|
@ -494,6 +494,11 @@ pub struct Cea608Renderer {
|
|||
video_height: u32,
|
||||
left_alignment: i32,
|
||||
black_background: bool,
|
||||
|
||||
safe_width: f32,
|
||||
safe_height: f32,
|
||||
max_layout_width: i32,
|
||||
max_layout_height: i32,
|
||||
}
|
||||
|
||||
impl Cea608Renderer {
|
||||
|
@ -506,7 +511,8 @@ impl Cea608Renderer {
|
|||
context.set_base_dir(pango::Direction::Ltr);
|
||||
let layout = pango::Layout::new(&context);
|
||||
layout.set_alignment(pango::Alignment::Left);
|
||||
recalculate_pango_layout(&layout, video_width, video_height);
|
||||
let (max_layout_width, max_layout_height) =
|
||||
recalculate_pango_layout(&layout, video_width, video_height);
|
||||
Self {
|
||||
frame: Cea608Frame::new(),
|
||||
state: Cea608State::default(),
|
||||
|
@ -517,6 +523,10 @@ impl Cea608Renderer {
|
|||
video_height,
|
||||
left_alignment: 0,
|
||||
black_background: false,
|
||||
safe_width: 0.8,
|
||||
safe_height: 0.8,
|
||||
max_layout_width,
|
||||
max_layout_height,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -546,15 +556,31 @@ impl Cea608Renderer {
|
|||
if width != self.video_width || height != self.video_height {
|
||||
self.video_width = width;
|
||||
self.video_height = height;
|
||||
self.layout = pango::Layout::new(&self.context);
|
||||
self.layout.set_alignment(pango::Alignment::Left);
|
||||
let (max_layout_width, _max_layout_height) =
|
||||
recalculate_pango_layout(&self.layout, width, height);
|
||||
self.left_alignment = (width as i32 - max_layout_width) / 2;
|
||||
self.rectangle.take();
|
||||
self.recalculate_window_position();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_safe_title_area(&mut self, safe_width: f32, safe_height: f32) {
|
||||
if safe_width != self.safe_width || safe_height != self.safe_height {
|
||||
self.safe_width = safe_width;
|
||||
self.safe_height = safe_height;
|
||||
self.recalculate_window_position();
|
||||
}
|
||||
}
|
||||
|
||||
fn recalculate_window_position(&mut self) {
|
||||
self.layout = pango::Layout::new(&self.context);
|
||||
self.layout.set_alignment(pango::Alignment::Left);
|
||||
let width = (self.video_width as f32 * self.safe_width) as u32;
|
||||
let height = (self.video_height as f32 * self.safe_height) as u32;
|
||||
let (max_layout_width, max_layout_height) =
|
||||
recalculate_pango_layout(&self.layout, width, height);
|
||||
self.left_alignment = (self.video_width as i32 - max_layout_width) / 2;
|
||||
self.max_layout_width = max_layout_width;
|
||||
self.max_layout_height = max_layout_height;
|
||||
self.rectangle.take();
|
||||
}
|
||||
|
||||
pub fn set_black_background(&mut self, bg: bool) {
|
||||
self.black_background = bg;
|
||||
self.rectangle.take();
|
||||
|
@ -670,7 +696,6 @@ impl Cea608Renderer {
|
|||
let (_ink_rect, logical_rect) = self.layout.extents();
|
||||
let height = logical_rect.height() / pango::SCALE;
|
||||
let width = logical_rect.width() / pango::SCALE;
|
||||
gst::debug!(CAT, "overlaying size {width}x{height}, text {text}");
|
||||
|
||||
// No text actually needs rendering
|
||||
if width == 0 || height == 0 {
|
||||
|
@ -756,16 +781,24 @@ impl Cea608Renderer {
|
|||
}
|
||||
};
|
||||
|
||||
let vertical_padding = self.video_height / 10;
|
||||
let safe_height = self.video_height.mul_div_floor(80, 100).unwrap();
|
||||
let first_row_position = (safe_height as i32)
|
||||
let safe_height = (self.video_height as f32 * self.safe_height) as i32;
|
||||
let vertical_padding =
|
||||
(2 * self.video_height as i32 - safe_height - self.max_layout_height) / 2;
|
||||
let first_row_position = self
|
||||
.max_layout_height
|
||||
.mul_div_round(first_row as i32, MAX_ROW as i32 + 1)
|
||||
.unwrap();
|
||||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
"overlay size {width}x{height} at {}x{}, text {text}",
|
||||
self.left_alignment,
|
||||
first_row_position + vertical_padding
|
||||
);
|
||||
let rect = gst_video::VideoOverlayRectangle::new_raw(
|
||||
&buffer,
|
||||
self.left_alignment,
|
||||
first_row_position + vertical_padding as i32,
|
||||
first_row_position + vertical_padding,
|
||||
width as u32,
|
||||
height as u32,
|
||||
gst_video::VideoOverlayFormatFlags::PREMULTIPLIED_ALPHA,
|
||||
|
|
|
@ -28,6 +28,8 @@ static CAT: LazyLock<gst::DebugCategory> = LazyLock::new(|| {
|
|||
|
||||
const DEFAULT_CEA608_CHANNEL: i32 = -1;
|
||||
const DEFAULT_SERVICE: i32 = 1;
|
||||
const DEFAULT_SAFE_WIDTH: f32 = 0.8;
|
||||
const DEFAULT_SAFE_HEIGHT: f32 = 0.8;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Settings {
|
||||
|
@ -35,6 +37,8 @@ struct Settings {
|
|||
cea608_channel: i32,
|
||||
service: i32,
|
||||
timeout: Option<gst::ClockTime>,
|
||||
safe_width: f32,
|
||||
safe_height: f32,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
|
@ -44,6 +48,8 @@ impl Default for Settings {
|
|||
cea608_channel: DEFAULT_CEA608_CHANNEL,
|
||||
service: DEFAULT_SERVICE,
|
||||
timeout: gst::ClockTime::NONE,
|
||||
safe_width: DEFAULT_SAFE_WIDTH,
|
||||
safe_height: DEFAULT_SAFE_HEIGHT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +137,9 @@ impl Cea708Overlay {
|
|||
);
|
||||
|
||||
state.cea708_renderer.set_service_channel(state.selected);
|
||||
state
|
||||
.cea708_renderer
|
||||
.set_safe_title_area(settings.safe_width, settings.safe_height);
|
||||
settings.changed = false;
|
||||
}
|
||||
|
||||
|
@ -437,9 +446,12 @@ impl Cea708Overlay {
|
|||
true
|
||||
}
|
||||
EventView::FlushStop(..) => {
|
||||
let settings = self.settings.lock().unwrap().clone();
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.cea708_renderer = Cea708Renderer::new();
|
||||
//state.cea608_renderer.set_black_background(settings.black_background);
|
||||
state
|
||||
.cea708_renderer
|
||||
.set_safe_title_area(settings.safe_width, settings.safe_height);
|
||||
drop(state);
|
||||
|
||||
gst::Pad::event_default(pad, Some(&*self.obj()), event)
|
||||
|
@ -518,6 +530,22 @@ impl ObjectImpl for Cea708Overlay {
|
|||
.default_value(u64::MAX)
|
||||
.mutable_playing()
|
||||
.build(),
|
||||
glib::ParamSpecFloat::builder("safe-title-height")
|
||||
.nick("Safe Title Height")
|
||||
.blurb("Ratio of the video height to use as the safe area for caption display")
|
||||
.minimum(0.0)
|
||||
.maximum(1.0)
|
||||
.default_value(0.8)
|
||||
.mutable_playing()
|
||||
.build(),
|
||||
glib::ParamSpecFloat::builder("safe-title-width")
|
||||
.nick("Safe Title Width")
|
||||
.blurb("Ratio of the video width to use as the safe area for caption display")
|
||||
.minimum(0.0)
|
||||
.maximum(1.0)
|
||||
.default_value(0.8)
|
||||
.mutable_playing()
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -554,28 +582,42 @@ impl ObjectImpl for Cea708Overlay {
|
|||
_ => Some(timeout.nseconds()),
|
||||
};
|
||||
}
|
||||
"safe-title-width" => {
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
|
||||
let new = value.get().expect("type checked upstream");
|
||||
if new != settings.safe_width {
|
||||
settings.safe_width = new;
|
||||
settings.changed = true;
|
||||
}
|
||||
}
|
||||
"safe-title-height" => {
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
|
||||
let new = value.get().expect("type checked upstream");
|
||||
if new != settings.safe_height {
|
||||
settings.safe_height = new;
|
||||
settings.changed = true;
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
match pspec.name() {
|
||||
"cea608-channel" => {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
settings.cea608_channel.to_value()
|
||||
}
|
||||
"service" => {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
settings.service.to_value()
|
||||
}
|
||||
"cea608-channel" => settings.cea608_channel.to_value(),
|
||||
"service" => settings.service.to_value(),
|
||||
"timeout" => {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
if let Some(timeout) = settings.timeout {
|
||||
timeout.nseconds().to_value()
|
||||
} else {
|
||||
u64::MAX.to_value()
|
||||
}
|
||||
}
|
||||
"safe-title-width" => settings.safe_width.to_value(),
|
||||
"safe-title-height" => settings.safe_height.to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -309,6 +309,9 @@ pub struct Cea708Renderer {
|
|||
video_width: u32,
|
||||
video_height: u32,
|
||||
composition: Option<gst_video::VideoOverlayComposition>,
|
||||
|
||||
safe_width: f32,
|
||||
safe_height: f32,
|
||||
}
|
||||
|
||||
impl Cea708Renderer {
|
||||
|
@ -322,6 +325,8 @@ impl Cea708Renderer {
|
|||
video_width: 0,
|
||||
video_height: 0,
|
||||
composition: None,
|
||||
safe_width: 0.8,
|
||||
safe_height: 0.8,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,6 +342,18 @@ impl Cea708Renderer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_safe_title_area(&mut self, safe_width: f32, safe_height: f32) {
|
||||
if safe_width != self.safe_width || safe_height != self.safe_height {
|
||||
self.safe_width = safe_width;
|
||||
self.safe_height = safe_height;
|
||||
self.cea608.set_safe_title_area(safe_width, safe_height);
|
||||
if let Some(service) = self.service.as_mut() {
|
||||
service.set_safe_title_area(safe_width, safe_height);
|
||||
}
|
||||
self.composition.take();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_service_channel(&mut self, service_channel: Option<ServiceOrChannel>) {
|
||||
if self.selected != service_channel {
|
||||
self.selected = service_channel;
|
||||
|
@ -353,6 +370,7 @@ impl Cea708Renderer {
|
|||
let overlay_service = self.service.get_or_insert_with(|| {
|
||||
let mut service = ServiceState::new();
|
||||
service.set_video_size(self.video_width, self.video_height);
|
||||
service.set_safe_title_area(self.safe_width, self.safe_height);
|
||||
service
|
||||
});
|
||||
overlay_service.handle_code(code);
|
||||
|
@ -446,6 +464,8 @@ struct ServiceState {
|
|||
pango_context: pango::Context,
|
||||
video_width: u32,
|
||||
video_height: u32,
|
||||
width_ratio: f32,
|
||||
height_ratio: f32,
|
||||
}
|
||||
|
||||
impl ServiceState {
|
||||
|
@ -462,6 +482,8 @@ impl ServiceState {
|
|||
pango_context: context,
|
||||
video_width: 0,
|
||||
video_height: 0,
|
||||
width_ratio: 0.8,
|
||||
height_ratio: 0.8,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -486,23 +508,20 @@ impl ServiceState {
|
|||
let layout = pango::Layout::new(&self.pango_context);
|
||||
// XXX: May need a different alignment
|
||||
layout.set_alignment(pango::Alignment::Left);
|
||||
let mut window = Window {
|
||||
visible: args.visible,
|
||||
attrs: args.window_attributes(),
|
||||
pen_attrs: args.pen_attributes(),
|
||||
pen_color: args.pen_color(),
|
||||
define: *args,
|
||||
pen_location: SetPenLocationArgs::default(),
|
||||
lines: VecDeque::new(),
|
||||
rectangle: None,
|
||||
self.windows.push_back(Window::new(
|
||||
args.visible,
|
||||
*args,
|
||||
args.window_attributes(),
|
||||
args.pen_attributes(),
|
||||
args.pen_color(),
|
||||
layout,
|
||||
video_dims: Dimensions::default(),
|
||||
window_position: Dimensions::default(),
|
||||
window_dims: Dimensions::default(),
|
||||
max_layout_dims: Dimensions::default(),
|
||||
};
|
||||
window.set_video_size(self.video_width, self.video_height);
|
||||
self.windows.push_back(window);
|
||||
Dimensions {
|
||||
w: self.video_width,
|
||||
h: self.video_height,
|
||||
},
|
||||
self.width_ratio,
|
||||
self.height_ratio,
|
||||
));
|
||||
};
|
||||
self.current_window = args.window_id as usize;
|
||||
}
|
||||
|
@ -667,6 +686,14 @@ impl ServiceState {
|
|||
self.video_width = video_width;
|
||||
self.video_height = video_height;
|
||||
}
|
||||
|
||||
fn set_safe_title_area(&mut self, width_ratio: f32, height_ratio: f32) {
|
||||
for window in self.windows.iter_mut() {
|
||||
window.set_safe_title_area(width_ratio, height_ratio);
|
||||
}
|
||||
self.width_ratio = width_ratio;
|
||||
self.height_ratio = height_ratio;
|
||||
}
|
||||
}
|
||||
|
||||
fn color_value_as_u16(val: ColorValue) -> u16 {
|
||||
|
@ -753,6 +780,8 @@ struct Window {
|
|||
pen_location: SetPenLocationArgs,
|
||||
lines: VecDeque<WindowLine>,
|
||||
|
||||
safe_width: f32,
|
||||
safe_height: f32,
|
||||
window_position: Dimensions,
|
||||
video_dims: Dimensions,
|
||||
window_dims: Dimensions,
|
||||
|
@ -762,6 +791,39 @@ struct Window {
|
|||
}
|
||||
|
||||
impl Window {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
visible: bool,
|
||||
define: DefineWindowArgs,
|
||||
attrs: SetWindowAttributesArgs,
|
||||
pen_attrs: SetPenAttributesArgs,
|
||||
pen_color: SetPenColorArgs,
|
||||
layout: pango::Layout,
|
||||
video_dims: Dimensions,
|
||||
width_ratio: f32,
|
||||
height_ratio: f32,
|
||||
) -> Self {
|
||||
let mut ret = Self {
|
||||
visible,
|
||||
define,
|
||||
attrs,
|
||||
pen_attrs,
|
||||
pen_color,
|
||||
pen_location: SetPenLocationArgs::default(),
|
||||
lines: VecDeque::new(),
|
||||
rectangle: None,
|
||||
layout,
|
||||
video_dims: Dimensions::default(),
|
||||
window_position: Dimensions::default(),
|
||||
window_dims: Dimensions::default(),
|
||||
max_layout_dims: Dimensions::default(),
|
||||
safe_width: width_ratio,
|
||||
safe_height: height_ratio,
|
||||
};
|
||||
ret.set_video_size(video_dims.w, video_dims.h);
|
||||
ret
|
||||
}
|
||||
|
||||
fn dump(&self) {
|
||||
for line in self.lines.iter() {
|
||||
let mut string = line.no.to_string();
|
||||
|
@ -1050,8 +1112,12 @@ impl Window {
|
|||
// XXX: may need a better implementation for 'skinny' (horizontal or vertical) output
|
||||
// sizes.
|
||||
|
||||
let safe_area = Dimensions {
|
||||
w: (self.video_dims.w as f32 * self.safe_width) as u32,
|
||||
h: (self.video_dims.h as f32 * self.safe_height) as u32,
|
||||
};
|
||||
let (max_layout_width, max_layout_height) =
|
||||
recalculate_pango_layout(&self.layout, self.video_dims.w, self.video_dims.h);
|
||||
recalculate_pango_layout(&self.layout, safe_area.w, safe_area.h);
|
||||
self.max_layout_dims = Dimensions {
|
||||
w: max_layout_width as u32,
|
||||
h: max_layout_height as u32,
|
||||
|
@ -1067,12 +1133,8 @@ impl Window {
|
|||
};
|
||||
|
||||
let padding = Dimensions {
|
||||
w: self.video_dims.w / 10,
|
||||
h: self.video_dims.h / 10,
|
||||
};
|
||||
let safe_area = Dimensions {
|
||||
w: self.video_dims.w - self.video_dims.w / 5,
|
||||
h: self.video_dims.h - self.video_dims.h / 5,
|
||||
w: (self.video_dims.w - safe_area.w) / 2,
|
||||
h: (self.video_dims.h - safe_area.h) / 2,
|
||||
};
|
||||
|
||||
self.window_position = if self.define.relative_positioning {
|
||||
|
@ -1131,6 +1193,16 @@ impl Window {
|
|||
self.recalculate_window_position();
|
||||
}
|
||||
|
||||
fn set_safe_title_area(&mut self, width_ratio: f32, height_ratio: f32) {
|
||||
if self.safe_width == width_ratio && self.safe_height == height_ratio {
|
||||
return;
|
||||
}
|
||||
self.safe_width = width_ratio;
|
||||
self.safe_height = height_ratio;
|
||||
|
||||
self.recalculate_window_position();
|
||||
}
|
||||
|
||||
fn generate_rectangle(&mut self) -> Option<gst_video::VideoOverlayRectangle> {
|
||||
if !self.visible {
|
||||
return None;
|
||||
|
|
Loading…
Reference in a new issue