mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-29 15:22:11 +02:00
clapper-app: Add GStreamer pipeline preview
Allow to preview GStreamer pipeline while playing content. This makes it easier to check what is used underneath.
This commit is contained in:
@@ -89,6 +89,12 @@ libadwaita_dep = dependency('libadwaita-1',
|
||||
peas_dep = dependency('libpeas-2',
|
||||
required: false,
|
||||
)
|
||||
cgraph_dep = dependency('libcgraph',
|
||||
required: false,
|
||||
)
|
||||
gvc_dep = dependency('libgvc',
|
||||
required: false,
|
||||
)
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
libm = cc.find_library('m', required: false)
|
||||
|
@@ -41,6 +41,11 @@ option('enhancers-loader',
|
||||
value: 'enabled',
|
||||
description: 'Ability to load libpeas based plugins that enhance capabilities'
|
||||
)
|
||||
option('pipeline-preview',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
description: 'Ability to preview GStreamer pipeline in clapper-app'
|
||||
)
|
||||
|
||||
# Features
|
||||
option('discoverer',
|
||||
|
@@ -395,6 +395,59 @@ show_info (GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||
gtk_window_present (GTK_WINDOW (info_window));
|
||||
}
|
||||
|
||||
static void
|
||||
_show_pipeline_cb (GtkFileLauncher *launcher,
|
||||
GAsyncResult *res, gpointer user_data G_GNUC_UNUSED)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
if (!gtk_file_launcher_launch_finish (launcher, res, &error)) {
|
||||
if (error->domain != GTK_DIALOG_ERROR || error->code != GTK_DIALOG_ERROR_DISMISSED) {
|
||||
GST_ERROR ("Could not launch pipeline preview, reason: %s",
|
||||
GST_STR_NULL (error->message));
|
||||
}
|
||||
g_error_free (error);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
show_pipeline (GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||
{
|
||||
GtkApplication *gtk_app = GTK_APPLICATION (user_data);
|
||||
GtkWindow *window;
|
||||
GtkFileLauncher *launcher;
|
||||
GFile *svg_file;
|
||||
GError *error = NULL;
|
||||
|
||||
window = gtk_application_get_active_window (gtk_app);
|
||||
|
||||
while (window && !CLAPPER_APP_IS_WINDOW (window))
|
||||
window = gtk_window_get_transient_for (window);
|
||||
|
||||
if (G_UNLIKELY (window == NULL))
|
||||
return;
|
||||
|
||||
if (!(svg_file = clapper_app_utils_create_pipeline_svg_file (
|
||||
clapper_app_window_get_player (CLAPPER_APP_WINDOW (window)), &error))) {
|
||||
GST_ERROR ("Could not create pipeline graph file, reason: %s",
|
||||
GST_STR_NULL (error->message));
|
||||
g_error_free (error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
launcher = gtk_file_launcher_new (svg_file);
|
||||
g_object_unref (svg_file);
|
||||
|
||||
#if GTK_CHECK_VERSION(4,12,0)
|
||||
gtk_file_launcher_set_always_ask (launcher, TRUE);
|
||||
#endif
|
||||
|
||||
gtk_file_launcher_launch (launcher, window, NULL,
|
||||
(GAsyncReadyCallback) _show_pipeline_cb, NULL);
|
||||
g_object_unref (launcher);
|
||||
}
|
||||
|
||||
static void
|
||||
show_about (GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||
{
|
||||
@@ -712,6 +765,7 @@ clapper_app_application_constructed (GObject *object)
|
||||
{ "clear-queue", clear_queue, NULL, NULL, NULL },
|
||||
{ "new-window", new_window, NULL, NULL, NULL },
|
||||
{ "info", show_info, NULL, NULL, NULL },
|
||||
{ "pipeline", show_pipeline, NULL, NULL, NULL },
|
||||
{ "preferences", show_preferences, NULL, NULL, NULL },
|
||||
{ "about", show_about, NULL, NULL, NULL },
|
||||
};
|
||||
@@ -720,6 +774,7 @@ clapper_app_application_constructed (GObject *object)
|
||||
{ "app.add-uri", { "<Control>u", NULL, NULL }},
|
||||
{ "app.new-window", { "<Control>n", NULL, NULL }},
|
||||
{ "app.info", { "<Control>i", NULL, NULL }},
|
||||
{ "app.pipeline", { "<Control><Shift>p", NULL, NULL }},
|
||||
{ "app.preferences", { "<Control>comma", NULL, NULL }},
|
||||
{ "app.about", { "F1", NULL, NULL }},
|
||||
{ "win.toggle-fullscreen", { "F11", "f", NULL }},
|
||||
|
@@ -36,6 +36,8 @@ struct _ClapperAppInfoWindow
|
||||
GtkWidget *astreams_list;
|
||||
GtkWidget *sstreams_list;
|
||||
|
||||
GtkWidget *pipeline_button;
|
||||
|
||||
ClapperPlayer *player;
|
||||
};
|
||||
|
||||
@@ -174,6 +176,10 @@ clapper_app_info_window_init (ClapperAppInfoWindow *self)
|
||||
gtk_widget_remove_css_class (self->vstreams_list, "view");
|
||||
gtk_widget_remove_css_class (self->astreams_list, "view");
|
||||
gtk_widget_remove_css_class (self->sstreams_list, "view");
|
||||
|
||||
#ifdef HAVE_GRAPHVIZ
|
||||
gtk_widget_set_visible (self->pipeline_button, TRUE);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -256,6 +262,7 @@ clapper_app_info_window_class_init (ClapperAppInfoWindowClass *klass)
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppInfoWindow, vstreams_list);
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppInfoWindow, astreams_list);
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppInfoWindow, sstreams_list);
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppInfoWindow, pipeline_button);
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, media_duration_closure);
|
||||
gtk_widget_class_bind_template_callback (widget_class, playback_element_name_closure);
|
||||
|
@@ -23,6 +23,11 @@
|
||||
#include "clapper-app-utils.h"
|
||||
#include "clapper-app-media-item-box.h"
|
||||
|
||||
#ifdef HAVE_GRAPHVIZ
|
||||
#include <graphviz/cgraph.h>
|
||||
#include <graphviz/gvc.h>
|
||||
#endif
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
#include <windows.h>
|
||||
#ifdef HAVE_WIN_PROCESS_THREADS_API
|
||||
@@ -527,3 +532,91 @@ clapper_app_utils_make_element (const gchar *string)
|
||||
|
||||
return gst_element_factory_make (string, NULL);
|
||||
}
|
||||
|
||||
#ifdef HAVE_GRAPHVIZ
|
||||
static GFile *
|
||||
_create_tmp_subdir (const gchar *subdir)
|
||||
{
|
||||
GFile *tmp_dir;
|
||||
GError *error = NULL;
|
||||
|
||||
tmp_dir = g_file_new_build_filename (
|
||||
g_get_tmp_dir (), "." CLAPPER_APP_ID, subdir, NULL);
|
||||
|
||||
if (!g_file_make_directory_with_parents (tmp_dir, NULL, &error)) {
|
||||
if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_EXISTS) {
|
||||
GST_ERROR ("Could not create temp dir, reason: %s",
|
||||
GST_STR_NULL (error->message));
|
||||
g_clear_object (&tmp_dir); // return NULL
|
||||
}
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
return tmp_dir;
|
||||
}
|
||||
#endif
|
||||
|
||||
GFile *
|
||||
clapper_app_utils_create_pipeline_svg_file (ClapperPlayer *player, GError **error)
|
||||
{
|
||||
GFile *tmp_file = NULL;
|
||||
|
||||
#ifdef HAVE_GRAPHVIZ
|
||||
GFile *tmp_subdir;
|
||||
Agraph_t *graph;
|
||||
GVC_t *gvc;
|
||||
gchar *path, *template, *dot_data, *img_data = NULL;
|
||||
gint fd;
|
||||
guint size = 0;
|
||||
|
||||
tmp_subdir = _create_tmp_subdir ("pipelines");
|
||||
if (G_UNLIKELY (tmp_subdir == NULL))
|
||||
return NULL;
|
||||
|
||||
path = g_file_get_path (tmp_subdir);
|
||||
g_object_unref (tmp_subdir);
|
||||
|
||||
template = g_build_filename (path, "pipeline-XXXXXX.svg", NULL);
|
||||
g_free (path);
|
||||
|
||||
fd = g_mkstemp (template); // Modifies template to actual filename
|
||||
|
||||
if (G_UNLIKELY (fd == -1)) {
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"Could not open temp file for writing");
|
||||
g_free (template);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dot_data = clapper_player_make_pipeline_graph (player, GST_DEBUG_GRAPH_SHOW_ALL);
|
||||
graph = agmemread (dot_data);
|
||||
|
||||
gvc = gvContext ();
|
||||
gvLayout (gvc, graph, "dot");
|
||||
gvRenderData (gvc, graph, "svg", &img_data, &size);
|
||||
|
||||
agclose (graph);
|
||||
gvFreeContext (gvc);
|
||||
g_free (dot_data);
|
||||
|
||||
if (write (fd, img_data, size) != -1) {
|
||||
tmp_file = g_file_new_for_path (template);
|
||||
} else {
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"Could not write data to temp file");
|
||||
}
|
||||
|
||||
/* Always close the file IO */
|
||||
if (G_UNLIKELY (close (fd) == -1))
|
||||
GST_ERROR ("Could not close temp file!");
|
||||
|
||||
g_free (img_data);
|
||||
g_free (template);
|
||||
#else
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"Cannot create graph file when compiled without Graphviz");
|
||||
#endif
|
||||
|
||||
return tmp_file;
|
||||
}
|
||||
|
@@ -84,4 +84,7 @@ void clapper_app_utils_iterate_plugin_feature_ranks (GSettings *settings, Clappe
|
||||
G_GNUC_INTERNAL
|
||||
GstElement * clapper_app_utils_make_element (const gchar *string);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GFile * clapper_app_utils_create_pipeline_svg_file (ClapperPlayer *player, GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -94,6 +94,17 @@ clapperapp_c_args = [
|
||||
'-DGST_USE_UNSTABLE_API',
|
||||
]
|
||||
|
||||
pp_option = get_option('pipeline-preview')
|
||||
|
||||
if not pp_option.disabled()
|
||||
if cgraph_dep.found() and gvc_dep.found()
|
||||
clapperapp_c_args += ['-DHAVE_GRAPHVIZ']
|
||||
clapperapp_deps += [cgraph_dep, gvc_dep]
|
||||
elif pp_option.enabled()
|
||||
error('pipeline-preview option was enabled, but required dependencies were not found')
|
||||
endif
|
||||
endif
|
||||
|
||||
is_windows = ['windows'].contains(host_machine.system())
|
||||
|
||||
if is_windows
|
||||
|
@@ -232,6 +232,17 @@
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="pipeline_button">
|
||||
<property name="halign">center</property>
|
||||
<property name="label" translatable="yes">Show Pipeline</property>
|
||||
<property name="action-name">app.pipeline</property>
|
||||
<property name="visible">false</property>
|
||||
<style>
|
||||
<class name="pill"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
Reference in New Issue
Block a user