gst-plugins-rs/video/closedcaption/examples/passthrough-notify.rs
Mathieu Duponchelle 635a83a8d7 transcriberbin: notify passthrough at the appropriate time
We want to enable passthrough internally, and only notify that
internally it has been enabled once the transcriber has been unlinked.

This way applications connected to the notify handler can synchronously
update the properties and attempt to disable passthrough again.

Doing so properly requires a refactoring of the transition to the
passthrough state, with the currently set passthrough mode maintained
separately from the target passthrough state.

This commit also finishes the work left incomplete in
17d7997137 by moving the passthrough
property to the sink pad class, making each transcriber passthrough
state independent from the others.

Also adds an example to demonstrate the behavior

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1856>
2024-11-05 12:23:54 +00:00

184 lines
5.5 KiB
Rust

// This example creates a pipeline that will decode a file, pipe its audio
// and video streams through transcriberbin, and display the result
// with closed captions overlaid.
//
// At first the (AWS) transcriber will not have its access keys set, which
// means it should automatically go to passthrough=true.
//
// At this point we set the access keys and disable passthrough again.
//
// The expected result is for the terminal to display
// "Access key set, disabling passthrough" and for closed captions to be
// overlaid over the video.
use anyhow::Error;
use clap::Parser;
use gst::glib;
use gst::prelude::*;
#[derive(Debug, Default, Clone, clap::Parser)]
struct Args {
#[clap(long, help = "URI to transcribe")]
pub uri: String,
#[clap(long, help = "Access key ID")]
pub access_key_id: String,
#[clap(long, help = "Secret access key")]
pub secret_access_key: String,
}
fn link_video_stream(
pipeline: &gst::Pipeline,
transcriberbin: &gst::Element,
pad: &gst::Pad,
) -> Result<(), Error> {
let conv = gst::ElementFactory::make("videoconvert").build()?;
pipeline.add(&conv)?;
conv.sync_state_with_parent()?;
pad.link(&conv.static_pad("sink").unwrap())?;
conv.link_pads(None, transcriberbin, Some("sink_video"))?;
Ok(())
}
fn link_audio_stream(
pipeline: &gst::Pipeline,
transcriberbin: &gst::Element,
pad: &gst::Pad,
) -> Result<(), Error> {
let conv = gst::ElementFactory::make("audioconvert").build()?;
pipeline.add(&conv)?;
conv.sync_state_with_parent()?;
pad.link(&conv.static_pad("sink").unwrap())?;
conv.link_pads(None, transcriberbin, Some("sink_audio"))?;
Ok(())
}
fn main() -> Result<(), Error> {
let args = Args::parse();
gst::init()?;
let pipeline = gst::Pipeline::builder().build();
let uridecodebin = gst::ElementFactory::make("uridecodebin")
.property("uri", &args.uri)
.build()?;
let transcriberbin = gst::ElementFactory::make("transcriberbin").build()?;
let asink = gst::ElementFactory::make("fakesink").build()?;
let overlay = gst::ElementFactory::make("cea608overlay").build()?;
let vconv = gst::ElementFactory::make("videoconvert").build()?;
let vsink = gst::ElementFactory::make("autovideosink").build()?;
uridecodebin.connect_pad_added(glib::clone!(
#[weak]
pipeline,
#[weak]
transcriberbin,
move |_element, pad| {
if pad
.current_caps()
.map(|c| c.structure(0).unwrap().name().starts_with("video/"))
.unwrap_or(false)
{
link_video_stream(&pipeline, &transcriberbin, pad)
.expect("Failed to link video stream");
} else {
link_audio_stream(&pipeline, &transcriberbin, pad)
.expect("Failed to link audio stream");
}
}
));
transcriberbin
.static_pad("sink_audio")
.unwrap()
.connect_closure(
"notify::passthrough",
false,
glib::closure!(
#[strong]
args,
move |pad: &gst::Pad, _pspec: &gst::glib::ParamSpec| {
let passthrough = pad.property::<bool>("passthrough");
if passthrough {
let transcriber = pad.property::<gst::Element>("transcriber");
transcriber.set_property("access-key", &args.access_key_id);
transcriber.set_property("secret-access-key", &args.secret_access_key);
eprintln!(
"Access key set, disabling passthrough, transcriber state: {:?}",
transcriber.state(gst::ClockTime::NONE)
);
pad.set_property("passthrough", false);
}
}
),
);
pipeline.add_many([
&uridecodebin,
&transcriberbin,
&asink,
&overlay,
&vconv,
&vsink,
])?;
transcriberbin.link_pads(Some("src_audio"), &asink, None)?;
transcriberbin.link_pads(Some("src_video"), &overlay, None)?;
gst::Element::link_many([&overlay, &vconv, &vsink])?;
pipeline.set_state(gst::State::Playing)?;
let bus = pipeline.bus().expect("Pipeline should have a bus");
for msg in bus.iter_timed(gst::ClockTime::NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Eos(..) => {
println!("EOS");
break;
}
MessageView::StateChanged(sc) => {
if msg.src() == Some(pipeline.upcast_ref()) {
pipeline.debug_to_dot_file(
gst::DebugGraphDetails::all(),
format!("{}-{:?}-{:?}", pipeline.name(), sc.old(), sc.current()),
);
}
}
MessageView::Error(err) => {
pipeline.debug_to_dot_file(gst::DebugGraphDetails::ALL, "error");
pipeline.set_state(gst::State::Null)?;
eprintln!(
"Got error from {}: {} ({})",
msg.src()
.map(|s| String::from(s.path_string()))
.unwrap_or_else(|| "None".into()),
err.error(),
err.debug().unwrap_or_else(|| "".into()),
);
break;
}
_ => (),
}
}
pipeline.set_state(gst::State::Null)?;
Ok(())
}