mirror of
https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio.git
synced 2024-11-22 00:50:59 +00:00
player: generate a graph from a pipeline description
This commit is contained in:
parent
01df04be60
commit
88ec98bcef
6 changed files with 322 additions and 17 deletions
56
src/app.rs
56
src/app.rs
|
@ -258,6 +258,9 @@ impl GPSApp {
|
|||
application.add_action(&gio::SimpleAction::new("open", None));
|
||||
application.set_accels_for_action("app.open", &["<primary>o"]);
|
||||
|
||||
application.add_action(&gio::SimpleAction::new("open_pipeline", None));
|
||||
application.set_accels_for_action("app.open_pipeline", &["<primary>p"]);
|
||||
|
||||
application.add_action(&gio::SimpleAction::new("save_as", None));
|
||||
application.set_accels_for_action("app.save", &["<primary>s"]);
|
||||
|
||||
|
@ -488,6 +491,22 @@ impl GPSApp {
|
|||
});
|
||||
});
|
||||
|
||||
let app_weak = self.downgrade();
|
||||
self.connect_app_menu_action("open_pipeline", move |_, _| {
|
||||
let app = upgrade_weak!(app_weak);
|
||||
GPSUI::dialog::create_input_dialog(
|
||||
"Enter pipeline description with gst-launch format",
|
||||
"description",
|
||||
&Settings::recent_pipeline_description(),
|
||||
&app,
|
||||
move |app, pipeline_desc| {
|
||||
app.load_pipeline(&pipeline_desc)
|
||||
.unwrap_or_else(|_| GPS_ERROR!("Unable to open file {}", pipeline_desc));
|
||||
Settings::set_recent_pipeline_description(&pipeline_desc);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let app_weak = self.downgrade();
|
||||
self.connect_app_menu_action("save_as", move |_, _| {
|
||||
let app = upgrade_weak!(app_weak);
|
||||
|
@ -631,11 +650,11 @@ impl GPSApp {
|
|||
app.connect_app_menu_action("graph.check",
|
||||
move |_,_| {
|
||||
let app = upgrade_weak!(app_weak);
|
||||
let render_parse_launch = app.player.borrow().render_gst_launch(&app.graphview.borrow());
|
||||
if app.player.borrow().create_pipeline(&render_parse_launch).is_ok() {
|
||||
GPSUI::message::display_message_dialog(&render_parse_launch,gtk::MessageType::Info, |_| {});
|
||||
let pipeline_description = app.player.borrow().pipeline_description_from_graphview(&app.graphview.borrow());
|
||||
if app.player.borrow().create_pipeline(&pipeline_description).is_ok() {
|
||||
GPSUI::message::display_message_dialog(&pipeline_description,gtk::MessageType::Info, |_| {});
|
||||
} else {
|
||||
GPSUI::message::display_error_dialog(false, &format!("Unable to render:\n\n{render_parse_launch}", ));
|
||||
GPSUI::message::display_error_dialog(false, &format!("Unable to render:\n\n{pipeline_description}"));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -752,7 +771,7 @@ impl GPSApp {
|
|||
move |_,_| {
|
||||
let app = upgrade_weak!(app_weak);
|
||||
GPS_DEBUG!("node.request-pad-input {}", node_id);
|
||||
app.create_port_with_caps(node_id, GM::PortDirection::Input, GM::PortPresence::Sometimes, input.caps().to_string())
|
||||
app.create_port_with_caps(node_id, GM::PortDirection::Input, GM::PortPresence::Sometimes, input.caps().to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
@ -908,13 +927,13 @@ impl GPSApp {
|
|||
properties
|
||||
}
|
||||
|
||||
fn create_port_with_caps(
|
||||
pub fn create_port_with_caps(
|
||||
&self,
|
||||
node_id: u32,
|
||||
direction: GM::PortDirection,
|
||||
presence: GM::PortPresence,
|
||||
caps: String,
|
||||
) {
|
||||
) -> u32 {
|
||||
let node = self.node(node_id);
|
||||
let ports = node.all_ports(direction);
|
||||
let port_name = match direction {
|
||||
|
@ -925,11 +944,27 @@ impl GPSApp {
|
|||
let graphview = self.graphview.borrow();
|
||||
let port_name = format!("{}{}", port_name, ports.len());
|
||||
let port = graphview.create_port(&port_name, direction, presence);
|
||||
let id = port.id();
|
||||
let properties: HashMap<String, String> = HashMap::from([("_caps".to_string(), caps)]);
|
||||
port.update_properties(&properties);
|
||||
if let Some(mut node) = graphview.node(node_id) {
|
||||
graphview.add_port_to_node(&mut node, port);
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
pub fn create_link(
|
||||
&self,
|
||||
node_from_id: u32,
|
||||
node_to_id: u32,
|
||||
port_from_id: u32,
|
||||
port_to_id: u32,
|
||||
active: bool,
|
||||
) {
|
||||
let graphview = self.graphview.borrow();
|
||||
let link =
|
||||
graphview.create_link(node_from_id, node_to_id, port_from_id, port_to_id, active);
|
||||
graphview.add_link(link);
|
||||
}
|
||||
|
||||
fn clear_graph(&self) {
|
||||
|
@ -954,4 +989,11 @@ impl GPSApp {
|
|||
graph_view.load_from_xml(buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_pipeline(&self, pipeline_desc: &str) -> anyhow::Result<()> {
|
||||
let player = self.player.borrow();
|
||||
let graphview = self.graphview.borrow();
|
||||
player.graphview_from_pipeline_description(&graphview, pipeline_desc);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::app::{AppState, GPSApp, GPSAppWeak};
|
||||
use crate::graphmanager::{GraphView, Node, NodeType, PortDirection, PropertyExt};
|
||||
use crate::graphmanager as GM;
|
||||
use crate::graphmanager::PropertyExt;
|
||||
|
||||
use crate::common;
|
||||
use crate::gps::ElementInfo;
|
||||
use crate::logger;
|
||||
use crate::settings;
|
||||
|
@ -165,14 +167,14 @@ impl Player {
|
|||
|
||||
pub fn start_pipeline(
|
||||
&self,
|
||||
graphview: &GraphView,
|
||||
graphview: &GM::GraphView,
|
||||
new_state: PipelineState,
|
||||
) -> anyhow::Result<PipelineState> {
|
||||
if self.state() == PipelineState::Stopped || self.state() == PipelineState::Error {
|
||||
let pipeline = self
|
||||
.create_pipeline(&self.render_gst_launch(graphview))
|
||||
.create_pipeline(&self.pipeline_description_from_graphview(graphview))
|
||||
.map_err(|err| {
|
||||
GPS_ERROR!("Unable to start a pipeline: {}", err);
|
||||
GPS_ERROR!("Unable to create a pipeline: {}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
|
@ -328,8 +330,8 @@ impl Player {
|
|||
#[allow(clippy::only_used_in_recursion)]
|
||||
fn process_gst_node(
|
||||
&self,
|
||||
graphview: &GraphView,
|
||||
node: &Node,
|
||||
graphview: &GM::GraphView,
|
||||
node: &GM::Node,
|
||||
elements: &mut HashMap<String, String>,
|
||||
mut description: String,
|
||||
) -> String {
|
||||
|
@ -344,7 +346,7 @@ impl Player {
|
|||
}
|
||||
}
|
||||
//Port properties
|
||||
let ports = node.all_ports(PortDirection::All);
|
||||
let ports = node.all_ports(GM::PortDirection::All);
|
||||
for port in ports {
|
||||
for (name, value) in port.properties().iter() {
|
||||
if !port.hidden_property(name) {
|
||||
|
@ -353,7 +355,7 @@ impl Player {
|
|||
}
|
||||
}
|
||||
|
||||
let ports = node.all_ports(PortDirection::Output);
|
||||
let ports = node.all_ports(GM::PortDirection::Output);
|
||||
let n_ports = ports.len();
|
||||
for port in ports {
|
||||
if let Some((_port_to, node_to)) = graphview.port_connected_to(port.id()) {
|
||||
|
@ -375,8 +377,8 @@ impl Player {
|
|||
description
|
||||
}
|
||||
|
||||
pub fn render_gst_launch(&self, graphview: &GraphView) -> String {
|
||||
let source_nodes = graphview.all_nodes(NodeType::Source);
|
||||
pub fn pipeline_description_from_graphview(&self, graphview: &GM::GraphView) -> String {
|
||||
let source_nodes = graphview.all_nodes(GM::NodeType::Source);
|
||||
let mut elements: HashMap<String, String> = HashMap::new();
|
||||
let mut description = String::from("");
|
||||
for source_node in source_nodes {
|
||||
|
@ -385,6 +387,140 @@ impl Player {
|
|||
}
|
||||
description
|
||||
}
|
||||
|
||||
pub fn create_links_for_element(&self, element: &gst::Element, graphview: &GM::GraphView) {
|
||||
let mut iter = element.iterate_pads();
|
||||
let node = graphview
|
||||
.node_by_unique_name(&element.name())
|
||||
.expect("node should exists");
|
||||
|
||||
loop {
|
||||
match iter.next() {
|
||||
Ok(Some(pad)) => {
|
||||
GPS_INFO!("Found pad: {}", pad.name());
|
||||
|
||||
if pad.direction() == gst::PadDirection::Src {
|
||||
let port = node
|
||||
.port_by_name(&pad.name())
|
||||
.expect("The port should exist here");
|
||||
if let Some(peer_pad) = pad.peer() {
|
||||
if let Some(peer_element) = peer_pad.parent_element() {
|
||||
let peer_node = graphview
|
||||
.node_by_unique_name(&peer_element.name())
|
||||
.expect("The node should exists here");
|
||||
let peer_port = peer_node
|
||||
.port_by_name(&peer_pad.name())
|
||||
.expect("The port should exists here");
|
||||
self.app.borrow().as_ref().unwrap().create_link(
|
||||
node.id(),
|
||||
peer_node.id(),
|
||||
port.id(),
|
||||
peer_port.id(),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(gst::IteratorError::Resync) => iter.resync(),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_pads_for_element(&self, element: &gst::Element, node: &GM::Node) {
|
||||
let mut iter = element.iterate_pads();
|
||||
loop {
|
||||
match iter.next() {
|
||||
Ok(Some(pad)) => {
|
||||
let pad_name = pad.name().to_string();
|
||||
GPS_INFO!("Found pad: {}", pad_name);
|
||||
let mut port_direction = GM::PortDirection::Input;
|
||||
if pad.direction() == gst::PadDirection::Src {
|
||||
port_direction = GM::PortDirection::Output;
|
||||
}
|
||||
let port_id = self.app.borrow().as_ref().unwrap().create_port_with_caps(
|
||||
node.id(),
|
||||
port_direction,
|
||||
GM::PortPresence::Always,
|
||||
pad.current_caps()
|
||||
.unwrap_or_else(|| pad.query_caps(None))
|
||||
.to_string(),
|
||||
);
|
||||
if let Some(port) = node.port(port_id) {
|
||||
port.set_name(&pad_name);
|
||||
}
|
||||
}
|
||||
Err(gst::IteratorError::Resync) => iter.resync(),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_properties_for_element(&self, element: &gst::Element, node: &GM::Node) {
|
||||
let properties = ElementInfo::element_properties(element)
|
||||
.unwrap_or_else(|_| panic!("Couldn't get properties for {}", node.name()));
|
||||
for (property_name, property_value) in properties {
|
||||
if property_name == "name"
|
||||
|| property_name == "parent"
|
||||
|| (property_value.flags() & glib::ParamFlags::READABLE)
|
||||
!= glib::ParamFlags::READABLE
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(value_str) = ElementInfo::element_property(element, &property_name) {
|
||||
let default_value_str =
|
||||
common::value_as_str(property_value.default_value()).unwrap_or_default();
|
||||
GPS_DEBUG!(
|
||||
"property name {} value_str '{}' default '{}'",
|
||||
property_name,
|
||||
value_str,
|
||||
default_value_str
|
||||
);
|
||||
if !value_str.is_empty() && value_str != default_value_str {
|
||||
node.add_property(&property_name, &value_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn graphview_from_pipeline_description(
|
||||
&self,
|
||||
graphview: &GM::GraphView,
|
||||
pipeline_desc: &str,
|
||||
) {
|
||||
graphview.clear();
|
||||
|
||||
if let Ok(pipeline) = self.create_pipeline(pipeline_desc) {
|
||||
let mut iter = pipeline.iterate_elements();
|
||||
let mut elements: Vec<gst::Element> = Vec::new();
|
||||
let elements = loop {
|
||||
match iter.next() {
|
||||
Ok(Some(element)) => {
|
||||
GPS_INFO!("Found element: {}", element.name());
|
||||
let element_factory_name = element.factory().unwrap().name().to_string();
|
||||
let node = graphview.create_node(
|
||||
&element_factory_name,
|
||||
ElementInfo::element_type(&element_factory_name),
|
||||
);
|
||||
node.set_unique_name(&element.name());
|
||||
graphview.add_node(node.clone());
|
||||
self.create_pads_for_element(&element, &node);
|
||||
self.create_properties_for_element(&element, &node);
|
||||
elements.push(element);
|
||||
}
|
||||
Err(gst::IteratorError::Resync) => iter.resync(),
|
||||
_ => break elements,
|
||||
}
|
||||
};
|
||||
for element in elements {
|
||||
self.create_links_for_element(&element, graphview);
|
||||
}
|
||||
} else {
|
||||
GPS_ERROR!("Unable to create a pipeline: {}", pipeline_desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PlayerInner {
|
||||
|
|
58
src/gps/test.rs
Normal file
58
src/gps/test.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
use crate::gps::player::Player;
|
||||
use crate::graphmanager as GM;
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_synced<F, R>(function: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R + Send + std::panic::UnwindSafe + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
/// No-op.
|
||||
macro_rules! skip_assert_initialized {
|
||||
() => {};
|
||||
}
|
||||
skip_assert_initialized!();
|
||||
|
||||
use futures_channel::oneshot;
|
||||
use std::panic;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
TEST_THREAD_WORKER
|
||||
.push(move || {
|
||||
tx.send(panic::catch_unwind(function))
|
||||
.unwrap_or_else(|_| panic!("Failed to return result from thread pool"));
|
||||
})
|
||||
.expect("Failed to schedule a test call");
|
||||
futures_executor::block_on(rx)
|
||||
.expect("Failed to receive result from thread pool")
|
||||
.unwrap_or_else(|e| std::panic::resume_unwind(e))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
static TEST_THREAD_WORKER: once_cell::sync::Lazy<gtk::glib::ThreadPool> =
|
||||
once_cell::sync::Lazy::new(|| {
|
||||
let pool = gtk::glib::ThreadPool::exclusive(1).unwrap();
|
||||
pool.push(move || {
|
||||
gtk::init().expect("Tests failed to initialize gtk");
|
||||
})
|
||||
.expect("Failed to schedule a test call");
|
||||
pool
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod player_test {
|
||||
use super::*;
|
||||
fn check_pipeline(pipeline_desc: &str) {
|
||||
let player = Player::new().expect("Not able to create the player");
|
||||
let graphview = GM::GraphView::new();
|
||||
player.graphview_from_pipeline_description(&graphview, pipeline_desc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pipeline_creation() {
|
||||
test_synced(|| {
|
||||
println!("coucou");
|
||||
//check_pipeline("videotestsrc ! autovideosink");
|
||||
});
|
||||
}
|
||||
}
|
|
@ -17,10 +17,12 @@ use crate::config;
|
|||
use crate::logger;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Settings {
|
||||
pub app_maximized: bool,
|
||||
pub app_width: i32,
|
||||
pub app_height: i32,
|
||||
pub recent_pipeline: String,
|
||||
|
||||
// values must be emitted before tables
|
||||
pub favorites: Vec<String>,
|
||||
|
@ -67,6 +69,17 @@ impl Settings {
|
|||
path
|
||||
}
|
||||
|
||||
pub fn set_recent_pipeline_description(pipeline: &str) {
|
||||
let mut settings = Settings::load_settings();
|
||||
settings.recent_pipeline = pipeline.to_string();
|
||||
Settings::save_settings(&settings);
|
||||
}
|
||||
|
||||
pub fn recent_pipeline_description() -> String {
|
||||
let settings = Settings::load_settings();
|
||||
settings.recent_pipeline
|
||||
}
|
||||
|
||||
pub fn add_favorite(favorite: &str) {
|
||||
let mut settings = Settings::load_settings();
|
||||
settings.favorites.sort();
|
||||
|
|
|
@ -43,3 +43,54 @@ pub fn create_dialog<F: Fn(GPSApp, gtk::Dialog) + 'static>(
|
|||
|
||||
dialog
|
||||
}
|
||||
|
||||
pub fn create_input_dialog<F: Fn(GPSApp, String) + 'static>(
|
||||
dialog_name: &str,
|
||||
input_name: &str,
|
||||
default_value: &str,
|
||||
app: &GPSApp,
|
||||
f: F,
|
||||
) {
|
||||
let dialog = gtk::Dialog::with_buttons(
|
||||
Some(dialog_name),
|
||||
Some(&app.window),
|
||||
gtk::DialogFlags::MODAL,
|
||||
&[("Ok", gtk::ResponseType::Apply)],
|
||||
);
|
||||
dialog.set_default_size(600, 100);
|
||||
dialog.set_modal(true);
|
||||
|
||||
let label = gtk::Label::builder()
|
||||
.label(input_name)
|
||||
.hexpand(true)
|
||||
.valign(gtk::Align::Center)
|
||||
.halign(gtk::Align::Start)
|
||||
.margin_start(4)
|
||||
.build();
|
||||
|
||||
let entry = gtk::Entry::builder()
|
||||
.width_request(400)
|
||||
.valign(gtk::Align::Center)
|
||||
.build();
|
||||
entry.set_text(default_value);
|
||||
|
||||
let content_area = dialog.content_area();
|
||||
content_area.set_orientation(gtk::Orientation::Horizontal);
|
||||
content_area.set_vexpand(true);
|
||||
content_area.set_margin_start(10);
|
||||
content_area.set_margin_end(10);
|
||||
content_area.set_margin_top(10);
|
||||
content_area.set_margin_bottom(10);
|
||||
content_area.append(&label);
|
||||
content_area.append(&entry);
|
||||
let app_weak = app.downgrade();
|
||||
dialog.connect_response(glib::clone!(@weak entry => move |dialog, response_type| {
|
||||
let app = upgrade_weak!(app_weak);
|
||||
if response_type == gtk::ResponseType::Apply {
|
||||
f(app, entry.text().to_string());
|
||||
}
|
||||
dialog.close()
|
||||
}));
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
<attribute name="action">app.open</attribute>
|
||||
<attribute name="accel"><primary>n</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes" comments="Primary menu entry that opens a pipeline">_Open pipeline</attribute>
|
||||
<attribute name="action">app.open_pipeline</attribute>
|
||||
<attribute name="accel"><primary>p</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes" comments="Primary menu entry that saves the graph">_Save As</attribute>
|
||||
<attribute name="action">app.save_as</attribute>
|
||||
|
|
Loading…
Reference in a new issue