app: port support update

Connect the port to the element pad
capabilities.
Can now create a port only if the element
supports the request pad
Can only delete a port which is a "sometimes"
port/pad.

Introduce GPS module.
This commit is contained in:
Stéphane Cerveau 2022-01-18 16:03:34 +01:00
parent 4bae12c011
commit 0f0f9d6fc0
11 changed files with 422 additions and 263 deletions

28
Cargo.lock generated
View file

@ -287,9 +287,9 @@ dependencies = [
[[package]]
name = "gdk4"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1023950739cd692fbf559507cad031bf2b0369d68466121f8a1316bc5ae29f0"
checksum = "228384db1fd6539f51152c97e59fbf29b6293988ee89bc915ebff229eb5cda58"
dependencies = [
"bitflags 1.3.2",
"cairo-rs",
@ -303,9 +303,9 @@ dependencies = [
[[package]]
name = "gdk4-sys"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "401b6be19db1e0ad2978fde7b46989b82f7595affa09cb56fe6f1302bc4f19e3"
checksum = "48a39e34abe35ee2cf54a1e29dd983accecd113ad30bdead5050418fa92f2a1b"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@ -510,9 +510,9 @@ dependencies = [
[[package]]
name = "gsk4"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd7c79a864b8c4606d39fad3fd872688ea20ba8e833a9f38e905a458a29a8c"
checksum = "cc7de6ca62027aea1d3ab5d4a6bb2db24a296f56473d8a9a4d7ee33042745ff3"
dependencies = [
"bitflags 1.3.2",
"cairo-rs",
@ -526,9 +526,9 @@ dependencies = [
[[package]]
name = "gsk4-sys"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d25ee96f7d1bdb6fa9945425aea95ce60efedf60c994eceb671fd93ad11d541"
checksum = "e31d21d7ce02ba261bb24c50c4ab238a10b41a2c97c32afffae29471b7cca69b"
dependencies = [
"cairo-sys-rs",
"gdk4-sys",
@ -594,9 +594,9 @@ dependencies = [
[[package]]
name = "gtk4"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6fda2b49a25eefc1116f15d06585d333c21fc3ce499ab0a86641fac2fbf40ac"
checksum = "8622c50f55cbe83e78335cf95850ccefa99e8eac3335ef7d17473312402d7e89"
dependencies = [
"bitflags 1.3.2",
"cairo-rs",
@ -617,9 +617,9 @@ dependencies = [
[[package]]
name = "gtk4-macros"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b17860e493ada8a40577f0e92bce8249a97c831e39d7811ef21de6281d67f86"
checksum = "c666e9d310431da43cb9d24995b247e5e24d11972d19bcd8b40e1663d25d0f51"
dependencies = [
"anyhow",
"proc-macro-crate",
@ -631,9 +631,9 @@ dependencies = [
[[package]]
name = "gtk4-sys"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc1047ea7a0ff2947fbfb703eecfcca6b58912171037a4c55b1577015f079d5"
checksum = "1544614a8d44f38719a2fdfd608d644d5f385beab33e5852688f48a983a2b583"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",

View file

@ -27,12 +27,13 @@
### Graphview
- [ ] create a crate for graphview/node/port
- [ ] Remove a port from a node
- [x] Remove a port from a node if possible
- [ ] Implement graphview unit test
- [x] add a css class for pad (presence always or sometimes)
### app
- [ ] check that a node accept to create a port on request (input/output)
- [x] check that a node accept to create a port on request (input/output)
- [ ] Control the connection between element
- [x] unable to connect in and in out and out
- [ ] unable to connect element with incompatible caps.
@ -45,7 +46,7 @@
- [ ] Implement a command line parser to graph
- [ ] Render the parse launch line in a message box
- [ ] Prevent to create a pad in an element without the template
- [ ] Check the pîpeline live
- [ ] Check the pipeline validity
- [ ] Implement pipeline unit test
- [x] Save node position in XML
- [x] Autosave the graph

View file

@ -35,13 +35,13 @@ use std::ops;
use std::rc::{Rc, Weak};
use crate::about;
use crate::gps::{ElementInfo, PadInfo, Pipeline, PipelineState};
use crate::logger;
use crate::pipeline::{Pipeline, PipelineState};
use crate::plugindialogs;
use crate::settings::Settings;
use crate::{GPS_DEBUG, GPS_ERROR, GPS_TRACE, GPS_WARN};
use crate::graphmanager::{GraphView, Node, PortDirection};
use crate::graphmanager::{GraphView, Node, PortDirection, PortPresence};
#[derive(Debug)]
pub struct GPSAppInner {
@ -192,7 +192,7 @@ impl GPSApp {
application.add_action(&gio::SimpleAction::new("graph.add-plugin", None));
application.add_action(&gio::SimpleAction::new("port.delete-link", None));
application.add_action(&gio::SimpleAction::new("port.delete", None));
application.add_action(&gio::SimpleAction::new("node.add-to-favorite", None));
application.add_action(&gio::SimpleAction::new("node.delete", None));
@ -224,6 +224,28 @@ impl GPSApp {
pop_menu
}
fn app_menu_action(&self, action_name: &str) -> SimpleAction {
let application = gio::Application::default()
.expect("No default application")
.downcast::<gtk::Application>()
.expect("Unable to downcast default application");
application
.lookup_action(action_name)
.unwrap_or_else(|| panic!("Unable to find action {}", action_name))
.dynamic_cast::<SimpleAction>()
.expect("Unable to dynamic cast to SimpleAction")
}
fn disconnect_app_menu_action(&self, action_name: &str) {
let action = self.app_menu_action(action_name);
if let Some(signal_handler_id) = self.menu_signal_handlers.borrow_mut().remove(action_name)
{
action.disconnect(signal_handler_id);
}
}
fn connect_app_menu_action<
F: Fn(&SimpleAction, std::option::Option<&glib::Variant>) + 'static,
>(
@ -231,20 +253,8 @@ impl GPSApp {
action_name: &str,
f: F,
) {
let application = gio::Application::default()
.expect("No default application")
.downcast::<gtk::Application>()
.expect("Unable to downcast default application");
let action = application
.lookup_action(action_name)
.unwrap_or_else(|| panic!("Unable to find action {}", action_name))
.dynamic_cast::<SimpleAction>()
.expect("Unable to dynamic cast to SimpleAction");
if let Some(signal_handler_id) = self.menu_signal_handlers.borrow_mut().remove(action_name)
{
action.disconnect(signal_handler_id);
}
let action = self.app_menu_action(action_name);
self.disconnect_app_menu_action(action_name);
let signal_handler_id = action.connect_activate(f);
self.menu_signal_handlers
.borrow_mut()
@ -459,7 +469,7 @@ impl GPSApp {
}
pub fn display_plugin_list(app: &GPSApp) {
let elements = Pipeline::elements_list().expect("Unable to obtain element's list");
let elements = ElementInfo::elements_list().expect("Unable to obtain element's list");
plugindialogs::display_plugin_list(app, &elements);
}
@ -660,9 +670,18 @@ impl GPSApp {
.object("port_menu")
.expect("Couldn't get menu model for port");
pop_menu.set_menu_model(Some(&menu));
app.connect_app_menu_action("port.delete-link", move |_, _| {
GPS_DEBUG!("port.delete-link port {} node {}", port_id, node_id);
});
if app.graphview.borrow().can_remove_port(node_id, port_id) {
let app_weak = app.downgrade();
app.connect_app_menu_action("port.delete", move |_, _| {
let app = upgrade_weak!(app_weak);
GPS_DEBUG!("port.delete-link port {} node {}", port_id, node_id);
app.graphview.borrow().remove_port(node_id, port_id);
});
} else {
app.disconnect_app_menu_action("port.delete");
}
pop_menu.show();
None
},
@ -679,6 +698,7 @@ impl GPSApp {
let app = upgrade_weak!(app_weak, None);
let node_id = values[1].get::<u32>().expect("node id args[1]");
let node = app.graphview.borrow().node(node_id).expect("Unable to find node with this ID");
let point = values[2].get::<graphene::Point>().expect("point in args[2]");
let pop_menu = app.app_pop_menu_at_position(&*app.graphview.borrow(), point.to_vec2().x() as f64, point.to_vec2().y() as f64);
@ -708,28 +728,33 @@ impl GPSApp {
}
);
let app_weak = app.downgrade();
app.connect_app_menu_action("node.request-pad-input",
move |_,_| {
let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.request-pad-input {}", node_id);
let mut node = app.graphview.borrow().node(node_id).unwrap();
let port_id = app.graphview.borrow().next_port_id();
node.add_port(port_id, "in", PortDirection::Input);
}
);
if ElementInfo::element_supports_new_pad_request(&node.name(), PortDirection::Input) {
let app_weak = app.downgrade();
app.connect_app_menu_action("node.request-pad-input",
move |_,_| {
let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.request-pad-input {}", node_id);
let port_id = app.graphview.borrow().next_port_id();
app.graphview.borrow().add_port(node_id, port_id, "in", PortDirection::Input, PortPresence::Sometimes);
}
);
} else {
app.disconnect_app_menu_action("node.request-pad-input");
}
if ElementInfo::element_supports_new_pad_request(&node.name(), PortDirection::Output) {
let app_weak = app.downgrade();
app.connect_app_menu_action("node.request-pad-output",
move |_,_| {
let app = upgrade_weak!(app_weak);
GPS_DEBUG!("node.request-pad-output {}", node_id);
let mut node = app.graphview.borrow_mut().node(node_id).unwrap();
let port_id = app.graphview.borrow_mut().next_port_id();
node.add_port(port_id, "out", PortDirection::Output);
app.graphview.borrow().add_port(node_id, port_id, "out", PortDirection::Output, PortPresence::Sometimes);
}
);
} else {
app.disconnect_app_menu_action("node.request-pad-output");
}
let app_weak = app.downgrade();
app.connect_app_menu_action("node.properties",
@ -771,8 +796,8 @@ impl GPSApp {
pub fn add_new_element(&self, element_name: &str) {
let graph_view = self.graphview.borrow();
let node_id = graph_view.next_node_id();
let pads = Pipeline::pads(element_name, false);
if Pipeline::element_is_uri_src_handler(element_name) {
let (inputs, outputs) = PadInfo::pads(element_name, false);
if ElementInfo::element_is_uri_src_handler(element_name) {
GPSApp::get_file_from_dialog(self, false, move |app, filename| {
GPS_DEBUG!("Open file {}", filename);
let node = app.graphview.borrow().node(node_id).unwrap();
@ -783,9 +808,13 @@ impl GPSApp {
}
graph_view.add_node_with_port(
node_id,
Node::new(node_id, element_name, Pipeline::element_type(element_name)),
pads.0,
pads.1,
Node::new(
node_id,
element_name,
ElementInfo::element_type(element_name),
),
inputs.len() as u32,
outputs.len() as u32,
);
}

View file

@ -31,8 +31,8 @@
<menu id="port_menu">
<section>
<item>
<attribute name="label" translatable="yes" comments="port menu entry delete the link">_Delete link</attribute>
<attribute name="action">app.port.delete-link</attribute>
<attribute name="label" translatable="yes" comments="port menu entry delete the link">_Delete</attribute>
<attribute name="action">app.port.delete</attribute>
</item>
</section>
</menu>

222
src/gps/element.rs Normal file
View file

@ -0,0 +1,222 @@
// element.rs
//
// Copyright 2022 Stéphane Cerveau <scerveau@collabora.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
use crate::gps::PadInfo;
use crate::graphmanager::{NodeType, PortDirection, PortPresence};
use crate::logger;
use crate::GPS_INFO;
use gst::glib;
use gst::prelude::*;
use gstreamer as gst;
use std::collections::HashMap;
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ElementInfo {
pub name: Option<String>,
plugin_name: Option<String>,
rank: i32,
}
impl Default for ElementInfo {
fn default() -> ElementInfo {
ElementInfo {
name: None,
plugin_name: None,
rank: -1,
}
}
}
impl ElementInfo {
pub fn elements_list() -> anyhow::Result<Vec<ElementInfo>> {
let registry = gst::Registry::get();
let mut elements: Vec<ElementInfo> = Vec::new();
let plugins = gst::Registry::plugin_list(&registry);
for plugin in plugins {
let plugin_name = gst::Plugin::plugin_name(&plugin);
let features = gst::Registry::feature_list_by_plugin(&registry, &plugin_name);
for feature in features {
let mut element = ElementInfo::default();
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
let feature = factory.upcast::<gst::PluginFeature>();
element.name = Some(gst::PluginFeature::name(&feature).as_str().to_owned());
element.plugin_name =
Some(gst::Plugin::plugin_name(&plugin).as_str().to_owned());
elements.push(element);
}
}
}
elements.sort();
Ok(elements)
}
pub fn element_feature(element_name: &str) -> Option<gst::PluginFeature> {
let registry = gst::Registry::get();
gst::Registry::find_feature(&registry, element_name, gst::ElementFactory::static_type())
}
pub fn element_description(element_name: &str) -> anyhow::Result<String> {
let mut desc = String::from("");
let feature = ElementInfo::element_feature(element_name)
.ok_or_else(|| glib::bool_error!("Failed get element feature"))?;
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
desc.push_str("<b>Factory details:</b>\n");
desc.push_str("<b>Name:</b>");
desc.push_str(&factory.name());
desc.push('\n');
let element_keys = factory.metadata_keys();
for key in element_keys {
let val = factory.metadata(&key);
if let Some(val) = val {
desc.push_str("<b>");
desc.push_str(&key);
desc.push_str("</b>:");
desc.push_str(&gtk::glib::markup_escape_text(&val).to_string());
desc.push('\n');
}
}
let feature = factory.upcast::<gst::PluginFeature>();
let plugin = gst::PluginFeature::plugin(&feature);
if let Some(plugin) = plugin {
desc.push('\n');
desc.push_str("<b>Plugin details:</b>");
desc.push('\n');
desc.push_str("<b>Name:");
desc.push_str("</b>");
desc.push_str(gst::Plugin::plugin_name(&plugin).as_str());
desc.push('\n');
desc.push_str("<b>Description:");
desc.push_str("</b>");
desc.push_str(&gtk::glib::markup_escape_text(&plugin.description()).to_string());
desc.push('\n');
desc.push_str("<b>Filename:");
desc.push_str("</b>");
desc.push_str(
&gtk::glib::markup_escape_text(
&plugin.filename().unwrap().as_path().display().to_string(),
)
.to_string(),
);
desc.push('\n');
desc.push_str("<b>Version:");
desc.push_str("</b>");
desc.push_str(&gtk::glib::markup_escape_text(&plugin.version()).to_string());
desc.push('\n');
}
}
Ok(desc)
}
pub fn element_type(element_name: &str) -> NodeType {
let (inputs, outputs) = PadInfo::pads(element_name, true);
let mut element_type = NodeType::Source;
if !inputs.is_empty() {
if !outputs.is_empty() {
element_type = NodeType::Transform;
} else {
element_type = NodeType::Sink;
}
} else if !outputs.is_empty() {
element_type = NodeType::Source;
}
element_type
}
fn value_as_str(v: &glib::Value) -> Option<String> {
match v.type_() {
glib::Type::I8 => Some(str_some_value!(v, i8).to_string()),
glib::Type::U8 => Some(str_some_value!(v, u8).to_string()),
glib::Type::BOOL => Some(str_some_value!(v, bool).to_string()),
glib::Type::I32 => Some(str_some_value!(v, i32).to_string()),
glib::Type::U32 => Some(str_some_value!(v, u32).to_string()),
glib::Type::I64 => Some(str_some_value!(v, i64).to_string()),
glib::Type::U64 => Some(str_some_value!(v, u64).to_string()),
glib::Type::F32 => Some(str_some_value!(v, f32).to_string()),
glib::Type::F64 => Some(str_some_value!(v, f64).to_string()),
glib::Type::STRING => str_opt_value!(v, String),
_ => None,
}
}
pub fn element_properties(element_name: &str) -> anyhow::Result<HashMap<String, String>> {
let mut properties_list = HashMap::new();
let feature = ElementInfo::element_feature(element_name).expect("Unable to get feature");
let factory = feature
.downcast::<gst::ElementFactory>()
.expect("Unable to get the factory from the feature");
let element = factory.create(None)?;
let params = element.class().list_properties();
for param in params {
GPS_INFO!("Property_name {}", param.name());
if (param.flags() & glib::ParamFlags::READABLE) == glib::ParamFlags::READABLE
|| (param.flags() & glib::ParamFlags::READWRITE) == glib::ParamFlags::READWRITE
{
let value = ElementInfo::value_as_str(&element.property(param.name()).unwrap())
.unwrap_or_else(|| String::from(""));
properties_list.insert(String::from(param.name()), value);
} else if let Some(value) = ElementInfo::value_as_str(param.default_value()) {
properties_list.insert(String::from(param.name()), value);
} else {
GPS_INFO!("Unable to add property_name {}", param.name());
}
}
Ok(properties_list)
}
pub fn element_is_uri_src_handler(element_name: &str) -> bool {
let feature = ElementInfo::element_feature(element_name).expect("Unable to get feature");
let factory = feature
.downcast::<gst::ElementFactory>()
.expect("Unable to get the factory from the feature");
let element = factory
.create(None)
.expect("Unable to create an element from the feature");
match element.dynamic_cast::<gst::URIHandler>() {
Ok(uri_handler) => uri_handler.uri_type() == gst::URIType::Src,
Err(_e) => false,
}
}
pub fn element_supports_new_pad_request(element_name: &str, direction: PortDirection) -> bool {
let (inputs, outputs) = PadInfo::pads(element_name, true);
if direction == PortDirection::Input {
for input in inputs {
if input.presence() == PortPresence::Sometimes {
return true;
}
}
} else if direction == PortDirection::Output {
for output in outputs {
if output.presence() == PortPresence::Sometimes {
return true;
}
}
} else {
GPS_ERROR!("Port direction unknown");
}
false
}
}

7
src/gps/mod.rs Normal file
View file

@ -0,0 +1,7 @@
mod element;
mod pad;
mod pipeline;
pub use element::ElementInfo;
pub use pad::PadInfo;
pub use pipeline::{Pipeline, PipelineState};

97
src/gps/pad.rs Normal file
View file

@ -0,0 +1,97 @@
// pad.rs
//
// Copyright 2022 Stéphane Cerveau <scerveau@collabora.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
use crate::logger;
use crate::GPS_INFO;
use crate::gps::ElementInfo;
use crate::graphmanager::{PortDirection, PortPresence};
use gst::prelude::*;
use gstreamer as gst;
#[derive(Debug, PartialOrd, PartialEq)]
pub struct PadInfo {
name: Option<String>,
element_name: Option<String>,
direction: PortDirection,
presence: PortPresence,
}
impl Default for PadInfo {
fn default() -> PadInfo {
PadInfo {
name: None,
element_name: None,
direction: PortDirection::Unknown,
presence: PortPresence::Unknown,
}
}
}
impl PadInfo {
pub fn presence(&self) -> PortPresence {
self.presence
}
fn pad_to_port_presence(presence: gst::PadPresence) -> PortPresence {
match presence {
gst::PadPresence::Always => PortPresence::Always,
gst::PadPresence::Sometimes => PortPresence::Sometimes,
gst::PadPresence::Request => PortPresence::Sometimes,
_ => PortPresence::Unknown,
}
}
pub fn pads(element_name: &str, include_on_request: bool) -> (Vec<PadInfo>, Vec<PadInfo>) {
let feature = ElementInfo::element_feature(element_name).expect("Unable to get feature");
let mut input = vec![];
let mut output = vec![];
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
if factory.num_pad_templates() > 0 {
let pads = factory.static_pad_templates();
for pad in pads {
GPS_INFO!("Found a pad name {}", pad.name_template());
if pad.presence() == gst::PadPresence::Always
|| (include_on_request
&& (pad.presence() == gst::PadPresence::Request
|| pad.presence() == gst::PadPresence::Sometimes))
{
if pad.direction() == gst::PadDirection::Src {
output.push(PadInfo {
name: Some(pad.name_template().to_string()),
element_name: Some(element_name.to_string()),
direction: PortDirection::Output,
presence: PadInfo::pad_to_port_presence(pad.presence()),
});
} else if pad.direction() == gst::PadDirection::Sink {
input.push(PadInfo {
name: Some(pad.name_template().to_string()),
element_name: Some(element_name.to_string()),
direction: PortDirection::Input,
presence: PadInfo::pad_to_port_presence(pad.presence()),
});
}
}
}
}
}
(input, output)
}
}

View file

@ -30,23 +30,6 @@ use std::fmt;
use std::ops;
use std::rc::{Rc, Weak};
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ElementInfo {
pub name: Option<String>,
plugin_name: Option<String>,
rank: i32,
}
impl Default for ElementInfo {
fn default() -> ElementInfo {
ElementInfo {
name: None,
plugin_name: None,
rank: -1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PipelineState {
Playing,
@ -197,188 +180,6 @@ impl Pipeline {
};
}
pub fn elements_list() -> anyhow::Result<Vec<ElementInfo>> {
let registry = gst::Registry::get();
let mut elements: Vec<ElementInfo> = Vec::new();
let plugins = gst::Registry::plugin_list(&registry);
for plugin in plugins {
let plugin_name = gst::Plugin::plugin_name(&plugin);
let features = gst::Registry::feature_list_by_plugin(&registry, &plugin_name);
for feature in features {
let mut element = ElementInfo::default();
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
let feature = factory.upcast::<gst::PluginFeature>();
element.name = Some(gst::PluginFeature::name(&feature).as_str().to_owned());
element.plugin_name =
Some(gst::Plugin::plugin_name(&plugin).as_str().to_owned());
elements.push(element);
}
}
}
elements.sort();
Ok(elements)
}
fn element_feature(element_name: &str) -> Option<gst::PluginFeature> {
let registry = gst::Registry::get();
gst::Registry::find_feature(&registry, element_name, gst::ElementFactory::static_type())
}
pub fn element_description(element_name: &str) -> anyhow::Result<String> {
let mut desc = String::from("");
let feature = Pipeline::element_feature(element_name)
.ok_or_else(|| glib::bool_error!("Failed get element feature"))?;
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
desc.push_str("<b>Factory details:</b>\n");
desc.push_str("<b>Name:</b>");
desc.push_str(&factory.name());
desc.push('\n');
let element_keys = factory.metadata_keys();
for key in element_keys {
let val = factory.metadata(&key);
if let Some(val) = val {
desc.push_str("<b>");
desc.push_str(&key);
desc.push_str("</b>:");
desc.push_str(&gtk::glib::markup_escape_text(&val).to_string());
desc.push('\n');
}
}
let feature = factory.upcast::<gst::PluginFeature>();
let plugin = gst::PluginFeature::plugin(&feature);
if let Some(plugin) = plugin {
desc.push('\n');
desc.push_str("<b>Plugin details:</b>");
desc.push('\n');
desc.push_str("<b>Name:");
desc.push_str("</b>");
desc.push_str(gst::Plugin::plugin_name(&plugin).as_str());
desc.push('\n');
desc.push_str("<b>Description:");
desc.push_str("</b>");
desc.push_str(&gtk::glib::markup_escape_text(&plugin.description()).to_string());
desc.push('\n');
desc.push_str("<b>Filename:");
desc.push_str("</b>");
desc.push_str(
&gtk::glib::markup_escape_text(
&plugin.filename().unwrap().as_path().display().to_string(),
)
.to_string(),
);
desc.push('\n');
desc.push_str("<b>Version:");
desc.push_str("</b>");
desc.push_str(&gtk::glib::markup_escape_text(&plugin.version()).to_string());
desc.push('\n');
}
}
Ok(desc)
}
pub fn pads(element_name: &str, include_on_request: bool) -> (u32, u32) {
let feature = Pipeline::element_feature(element_name).expect("Unable to get feature");
let mut input = 0;
let mut output = 0;
if let Ok(factory) = feature.downcast::<gst::ElementFactory>() {
if factory.num_pad_templates() > 0 {
let pads = factory.static_pad_templates();
for pad in pads {
if pad.presence() == gst::PadPresence::Always
|| (include_on_request
&& (pad.presence() == gst::PadPresence::Request
|| pad.presence() == gst::PadPresence::Sometimes))
{
if pad.direction() == gst::PadDirection::Src {
output += 1;
} else if pad.direction() == gst::PadDirection::Sink {
input += 1;
}
}
}
}
}
(input, output)
}
pub fn element_type(element_name: &str) -> NodeType {
let pads = Pipeline::pads(element_name, true);
let mut element_type = NodeType::Source;
if pads.0 > 0 {
if pads.1 > 0 {
element_type = NodeType::Transform;
} else {
element_type = NodeType::Sink;
}
} else if pads.1 > 0 {
element_type = NodeType::Source;
}
element_type
}
fn value_as_str(v: &glib::Value) -> Option<String> {
match v.type_() {
glib::Type::I8 => Some(str_some_value!(v, i8).to_string()),
glib::Type::U8 => Some(str_some_value!(v, u8).to_string()),
glib::Type::BOOL => Some(str_some_value!(v, bool).to_string()),
glib::Type::I32 => Some(str_some_value!(v, i32).to_string()),
glib::Type::U32 => Some(str_some_value!(v, u32).to_string()),
glib::Type::I64 => Some(str_some_value!(v, i64).to_string()),
glib::Type::U64 => Some(str_some_value!(v, u64).to_string()),
glib::Type::F32 => Some(str_some_value!(v, f32).to_string()),
glib::Type::F64 => Some(str_some_value!(v, f64).to_string()),
glib::Type::STRING => str_opt_value!(v, String),
_ => None,
}
}
pub fn element_properties(element_name: &str) -> anyhow::Result<HashMap<String, String>> {
let mut properties_list = HashMap::new();
let feature = Pipeline::element_feature(element_name).expect("Unable to get feature");
let factory = feature
.downcast::<gst::ElementFactory>()
.expect("Unable to get the factory from the feature");
let element = factory.create(None)?;
let params = element.class().list_properties();
for param in params {
GPS_INFO!("Property_name {}", param.name());
if (param.flags() & glib::ParamFlags::READABLE) == glib::ParamFlags::READABLE
|| (param.flags() & glib::ParamFlags::READWRITE) == glib::ParamFlags::READWRITE
{
let value = Pipeline::value_as_str(&element.property(param.name()).unwrap())
.unwrap_or_else(|| String::from(""));
properties_list.insert(String::from(param.name()), value);
} else if let Some(value) = Pipeline::value_as_str(param.default_value()) {
properties_list.insert(String::from(param.name()), value);
} else {
GPS_INFO!("Unable to add property_name {}", param.name());
}
}
Ok(properties_list)
}
pub fn element_is_uri_src_handler(element_name: &str) -> bool {
let feature = Pipeline::element_feature(element_name).expect("Unable to get feature");
let factory = feature
.downcast::<gst::ElementFactory>()
.expect("Unable to get the factory from the feature");
let element = factory
.create(None)
.expect("Unable to create an element from the feature");
match element.dynamic_cast::<gst::URIHandler>() {
Ok(uri_handler) => uri_handler.uri_type() == gst::URIType::Src,
Err(_e) => false,
}
}
// Render graph methods
fn process_gst_node(
&self,

View file

@ -26,7 +26,7 @@ mod config;
mod graphmanager;
#[macro_use]
mod logger;
mod pipeline;
mod gps;
mod plugindialogs;
mod settings;
use gtk::prelude::*;

View file

@ -28,7 +28,10 @@ rust_sources = files(
'logger.rs',
'macros.rs',
'main.rs',
'pipeline.rs',
'gps/pipeline.rs',
'gps/element.rs',
'gps/pad.rs',
'gps/mod.rs',
'plugindialogs.rs',
'settings.rs',

View file

@ -17,9 +17,8 @@
//
// SPDX-License-Identifier: GPL-3.0-only
use crate::app::GPSApp;
use crate::gps::ElementInfo;
use crate::logger;
use crate::pipeline::ElementInfo;
use crate::pipeline::Pipeline;
use gtk::glib;
use gtk::prelude::*;
use gtk::TextBuffer;
@ -93,7 +92,7 @@ pub fn display_plugin_list(app: &GPSApp, elements: &[ElementInfo]) {
if let Some((model, iter)) = selection.selected() {
let element_name = model
.get::<String>(&iter, 1);
let description = Pipeline::element_description(&element_name).expect("Unable to get element description from GStreamer");
let description = ElementInfo::element_description(&element_name).expect("Unable to get element description from GStreamer");
text_buffer.set_text("");
text_buffer.insert_markup(&mut text_buffer.end_iter(), &description);
}
@ -133,7 +132,7 @@ pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32)
.expect("Couldn't get box-plugin-properties");
let update_properties: Rc<RefCell<HashMap<String, String>>> =
Rc::new(RefCell::new(HashMap::new()));
let properties = Pipeline::element_properties(element_name).unwrap();
let properties = ElementInfo::element_properties(element_name).unwrap();
for (name, value) in properties {
let entry_box = Box::new(gtk::Orientation::Horizontal, 6);
let label = Label::new(Some(&name));