// This example demonstrates the use of GStreamer's ToC API. This API is used // to manage a table of contents contained in the handled media stream. // Chapters within a matroska file would be an example of a scenario for using // this API. Elements that can parse ToCs from a stream (such as matroskademux) // notify all elements in the pipeline when they encountered a ToC. // For this, the example operates the following pipeline: // /-{queue} - {fakesink} // {filesrc} - {decodebin} - {queue} - {fakesink} // \- ... use std::env; use gst::prelude::*; #[path = "../examples-common.rs"] mod examples_common; fn example_main() { gst::init().unwrap(); let args: Vec<_> = env::args().collect(); let uri: &str = if args.len() == 2 { args[1].as_ref() } else { println!("Usage: toc file_path"); std::process::exit(-1) }; let pipeline = gst::Pipeline::default(); let src = gst::ElementFactory::make("filesrc") .property("location", uri) .build() .unwrap(); let decodebin = gst::ElementFactory::make("decodebin").build().unwrap(); pipeline.add_many(&[&src, &decodebin]).unwrap(); gst::Element::link_many(&[&src, &decodebin]).unwrap(); // Need to move a new reference into the closure. // !!ATTENTION!!: // It might seem appealing to use pipeline.clone() here, because that greatly // simplifies the code within the callback. What this actually dose, however, is creating // a memory leak. The clone of a pipeline is a new strong reference on the pipeline. // Storing this strong reference of the pipeline within the callback (we are moving it in!), // which is in turn stored in another strong reference on the pipeline is creating a // reference cycle. // DO NOT USE pipeline.clone() TO USE THE PIPELINE WITHIN A CALLBACK let pipeline_weak = pipeline.downgrade(); // Connect to decodebin's pad-added signal, that is emitted whenever it found another stream // from the input file and found a way to decode it to its raw format. decodebin.connect_pad_added(move |_, src_pad| { // Here we temporarily retrieve a strong reference on the pipeline from the weak one // we moved into this callback. let pipeline = match pipeline_weak.upgrade() { Some(pipeline) => pipeline, None => return, }; // In this example, we are only interested about parsing the ToC, so // we simply pipe every encountered stream into a fakesink, essentially // throwing away the data. let queue = gst::ElementFactory::make("queue").build().unwrap(); let sink = gst::ElementFactory::make("fakesink").build().unwrap(); let elements = &[&queue, &sink]; pipeline.add_many(elements).unwrap(); gst::Element::link_many(elements).unwrap(); for e in elements { e.sync_state_with_parent().unwrap(); } let sink_pad = queue.static_pad("sink").unwrap(); src_pad .link(&sink_pad) .expect("Unable to link src pad to sink pad"); }); pipeline .set_state(gst::State::Paused) .expect("Unable to set the pipeline to the `Paused` state"); let bus = pipeline.bus().unwrap(); // Instead of using a main loop (like GLib's), we manually iterate over // GStreamer's bus messages in this example. We don't need any special // functionality like timeouts or GLib socket notifications, so this is sufficient. // The bus is manually operated by repeatedly calling timed_pop on the bus with // the desired timeout for when to stop waiting for new messages. (None = Wait forever) for msg in bus.iter_timed(gst::ClockTime::NONE) { use gst::MessageView; match msg.view() { MessageView::Eos(_) | MessageView::AsyncDone(_) => break, MessageView::Error(err) => { println!( "Error from {:?}: {} ({:?})", err.src().map(|s| s.path_string()), err.error(), err.debug() ); break; } MessageView::Toc(msg_toc) => { // Some element found a ToC in the current media stream and told // us by posting a message to GStreamer's bus. let (toc, updated) = msg_toc.toc(); println!("\nReceived toc: {:?} - updated: {}", toc.scope(), updated); // Get a list of tags that are ToC specific. if let Some(tags) = toc.tags() { println!("- tags: {tags}"); } // ToCs do not have a fixed structure. Depending on the format that // they were parsed from, they might have different tree-like structures, // so applications that want to support ToCs (for example in the form // of jumping between chapters in a video) have to try parsing and // interpreting the ToC manually. // In this example, we simply want to print the ToC structure, so // we iterate everything and don't try to interpret anything. for toc_entry in toc.entries() { // Every entry in a ToC has its own type. One type could for // example be Chapter. println!("\t{:?} - {}", toc_entry.entry_type(), toc_entry.uid()); // Every ToC entry can have a set of timestamps (start, stop). if let Some((start, stop)) = toc_entry.start_stop_times() { println!("\t- start: {start}, stop: {stop}"); } // Every ToC entry can have tags to it. if let Some(tags) = toc_entry.tags() { println!("\t- tags: {tags}"); } // Every ToC entry can have a set of child entries. // With this structure, you can create trees of arbitrary depth. for toc_sub_entry in toc_entry.sub_entries() { println!( "\n\t\t{:?} - {}", toc_sub_entry.entry_type(), toc_sub_entry.uid() ); if let Some((start, stop)) = toc_sub_entry.start_stop_times() { println!("\t\t- start: {start}, stop: {stop}"); } if let Some(tags) = toc_sub_entry.tags() { println!("\t\t- tags: {tags}"); } } } } _ => (), } } pipeline .set_state(gst::State::Null) .expect("Unable to set the pipeline to the `Null` state"); } fn main() { // tutorials_common::run is only required to set up the application environment on macOS // (but not necessary in normal Cocoa applications where this is set up automatically) examples_common::run(example_main); }