From 819c9f4e2a380f4beaef1f106788e806462f237d Mon Sep 17 00:00:00 2001 From: Stefan Kost Date: Sat, 17 Nov 2007 16:43:12 +0000 Subject: [PATCH] Add the preset interface (Fixes #396779). Do some doc cleanups along. Original commit message from CVS: * docs/gst/gstreamer-docs.sgml: * docs/gst/gstreamer-sections.txt: * docs/gst/gstreamer.types.in: * gst/Makefile.am: * gst/gst.h: * gst/gstpreset.c: * gst/gstpreset.h: Add the preset interface (Fixes #396779). Do some doc cleanups along. --- ChangeLog | 11 + docs/gst/gstreamer-docs.sgml | 2 + docs/gst/gstreamer-sections.txt | 23 +- docs/gst/gstreamer.types.in | 17 +- gst/Makefile.am | 2 + gst/gst.h | 1 + gst/gstpreset.c | 1064 +++++++++++++++++++++++++++++++ gst/gstpreset.h | 82 +++ 8 files changed, 1195 insertions(+), 7 deletions(-) create mode 100644 gst/gstpreset.c create mode 100644 gst/gstpreset.h diff --git a/ChangeLog b/ChangeLog index 461dd68c76..7ff4bb6609 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2007-11-17 Stefan Kost + + * docs/gst/gstreamer-docs.sgml: + * docs/gst/gstreamer-sections.txt: + * docs/gst/gstreamer.types.in: + * gst/Makefile.am: + * gst/gst.h: + * gst/gstpreset.c: + * gst/gstpreset.h: + Add the preset interface (Fixes #396779). Do some doc cleanups along. + 2007-11-16 Jan Schmidt * configure.ac: diff --git a/docs/gst/gstreamer-docs.sgml b/docs/gst/gstreamer-docs.sgml index e27e3a0267..e6a0aae55e 100644 --- a/docs/gst/gstreamer-docs.sgml +++ b/docs/gst/gstreamer-docs.sgml @@ -35,6 +35,7 @@ + @@ -138,6 +139,7 @@ Windows. It is released under the GNU Library General Public License &GstPipeline; &GstPlugin; &GstPluginFeature; + &GstPreset; &GstQuery; &GstRegistry; &GstSegment; diff --git a/docs/gst/gstreamer-sections.txt b/docs/gst/gstreamer-sections.txt index 9df1ce319a..60c2201aeb 100644 --- a/docs/gst/gstreamer-sections.txt +++ b/docs/gst/gstreamer-sections.txt @@ -1599,6 +1599,28 @@ gst_rank_get_type +
+gstpreset +GstPreset +GstPreset +gst_preset_get_preset_names +gst_preset_load_preset +gst_preset_save_preset +gst_preset_rename_preset +gst_preset_delete_preset +gst_preset_set_meta +gst_preset_get_meta +gst_preset_create_preset + +GstPresetInterface +GST_PRESET +GST_IS_PRESET +GST_TYPE_PRESET +GST_PRESET_GET_INTERFACE +gst_preset_get_type +
+ +
gstquery GstQuery @@ -1608,7 +1630,6 @@ GST_QUERY_TYPE GST_QUERY_TYPE_NAME GstQueryTypeDefinition - gst_query_type_get_name gst_query_type_to_quark gst_query_type_register diff --git a/docs/gst/gstreamer.types.in b/docs/gst/gstreamer.types.in index ee1930e967..67a719357f 100644 --- a/docs/gst/gstreamer.types.in +++ b/docs/gst/gstreamer.types.in @@ -10,6 +10,7 @@ gst_bin_get_type gst_bus_get_type +gst_child_proxy_get_type gst_clock_get_type gst_element_factory_get_type gst_element_get_type @@ -17,22 +18,26 @@ gst_ghost_pad_get_type gst_implements_interface_get_type @GST_INDEX_DOC_TYPES@gst_index_factory_get_type @GST_INDEX_DOC_TYPES@gst_index_get_type -gst_registry_get_type - -% these are not GObject derived types ! -% gst_mini_object_get_type -% gst_message_get_type - gst_object_get_type gst_pad_get_type gst_pad_template_get_type gst_pipeline_get_type gst_plugin_feature_get_type +gst_preset_get_type +gst_registry_get_type +gst_system_clock_get_type gst_tag_setter_get_type gst_task_get_type gst_type_find_factory_get_type +gst_uri_handler_get_type @GST_LOADSAVE_DOC_TYPES@gst_xml_get_type +% these are not GObject derived types ! +% gst_buffer_get_type +% gst_mini_object_get_type +% gst_message_get_type +% gst_query_get_type + % base classes #include diff --git a/gst/Makefile.am b/gst/Makefile.am index 2bf7bbca7a..251e03ffbd 100644 --- a/gst/Makefile.am +++ b/gst/Makefile.am @@ -102,6 +102,7 @@ libgstreamer_@GST_MAJORMINOR@_la_SOURCES = \ gstpipeline.c \ gstplugin.c \ gstpluginfeature.c \ + gstpreset.c \ gstquark.c \ gstquery.c \ gstregistry.c \ @@ -185,6 +186,7 @@ gst_headers = \ gstpipeline.h \ gstplugin.h \ gstpluginfeature.h \ + gstpreset.h \ gstquery.h \ gstsegment.h \ gststructure.h \ diff --git a/gst/gst.h b/gst/gst.h index 93a40060c6..939649f425 100644 --- a/gst/gst.h +++ b/gst/gst.h @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include diff --git a/gst/gstpreset.c b/gst/gstpreset.c new file mode 100644 index 0000000000..6ac1bad0f3 --- /dev/null +++ b/gst/gstpreset.c @@ -0,0 +1,1064 @@ +/* GStreamer + * Copyright (C) 2006 Stefan Kost + * + * gstpreset.c: helper interface for element presets + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:gstpreset + * @short_description: helper interface for element presets + * + * This interface offers methods to query and manipulate parameter preset sets. + * A preset is a bunch of property settings, together with meta data and a name. + * The name of a preset serves as key for subsequent method calls to manipulate + * single presets. + * All instances of one type will share the list of presets. The list is created + * on demand, if presets are not used, the list is not created. + * + */ +/* @TODO: + * - we need locks to avoid two instances manipulating the preset list -> flock + * - need to add support for GstChildProxy + * - how can we support both Preferences and Presets, a flag for _get_preset_names ? + * - should there be a 'preset-list' property to get the preset list + * (and to connect a notify:: to to listen for changes) + * + * http://www.buzztard.org/index.php/Preset_handling_interface + */ + +#include "gst_private.h" + +#include "gstpreset.h" + +#include "stdlib.h" +#include +#include + +#define GST_CAT_DEFAULT preset_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +static GQuark preset_list_quark = 0; +static GQuark preset_path_quark = 0; +static GQuark preset_data_quark = 0; +static GQuark preset_meta_quark = 0; +static GQuark instance_list_quark = 0; + +/* default iface implementation */ + +/* max character per line */ +#define LINE_LEN 200 + +static gboolean +preset_get_storage (GstPreset * self, GList ** presets, + GHashTable ** preset_meta, GHashTable ** preset_data) +{ + gboolean res = FALSE; + GType type = G_TYPE_FROM_INSTANCE (self); + + g_assert (presets); + + if ((*presets = g_type_get_qdata (type, preset_list_quark))) { + GST_DEBUG ("have presets"); + res = TRUE; + } + if (preset_meta) { + if (!(*preset_meta = g_type_get_qdata (type, preset_meta_quark))) { + *preset_meta = g_hash_table_new (g_str_hash, g_str_equal); + g_type_set_qdata (type, preset_meta_quark, (gpointer) * preset_meta); + GST_DEBUG ("new meta hash"); + } + } + if (preset_data) { + if (!(*preset_data = g_type_get_qdata (type, preset_data_quark))) { + *preset_data = g_hash_table_new (g_str_hash, g_str_equal); + g_type_set_qdata (type, preset_data_quark, (gpointer) * preset_data); + GST_DEBUG ("new data hash"); + } + } + GST_INFO ("%ld:%s: presets: %p, %p, %p", type, G_OBJECT_TYPE_NAME (self), + *presets, (preset_meta ? *preset_meta : 0), + (preset_data ? *preset_data : 0)); + return (res); +} + +static const gchar * +preset_get_path (GstPreset * self) +{ + GType type = G_TYPE_FROM_INSTANCE (self); + gchar *preset_path; + + preset_path = (gchar *) g_type_get_qdata (type, preset_path_quark); + if (!preset_path) { + const gchar *element_name, *plugin_name, *file_name; + gchar *preset_dir; + GstElementFactory *factory; + GstPlugin *plugin; + + element_name = G_OBJECT_TYPE_NAME (self); + GST_INFO ("element_name: '%s'", element_name); + + factory = GST_ELEMENT_GET_CLASS (self)->elementfactory; + GST_INFO ("factory: %p", factory); + if (factory) { + plugin_name = GST_PLUGIN_FEATURE (factory)->plugin_name; + GST_INFO ("plugin_name: '%s'", plugin_name); + plugin = gst_default_registry_find_plugin (plugin_name); + GST_INFO ("plugin: %p", plugin); + file_name = gst_plugin_get_filename (plugin); + GST_INFO ("file_name: '%s'", file_name); + /* + '/home/ensonic/buzztard/lib/gstreamer-0.10/libgstsimsyn.so' + -> '/home/ensonic/buzztard/share/gstreamer-0.10/GstSimSyn.xml' + -> '$HOME/.gstreamer-0.10/presets/GstSimSyn.xml' + + '/usr/lib/gstreamer-0.10/libgstaudiofx.so' + -> '/usr/share/gstreamer-0.10/GstAudioPanorama.xml' + -> '$HOME/.gstreamer-0.10/presets/GstAudioPanorama.xml' + */ + } + + preset_dir = + g_build_filename (g_get_home_dir (), ".gstreamer-0.10", "presets", + NULL); + GST_INFO ("preset_dir: '%s'", preset_dir); + preset_path = + g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s.prs", preset_dir, + element_name); + GST_INFO ("preset_path: '%s'", preset_path); + g_mkdir_with_parents (preset_dir, 0755); + g_free (preset_dir); + + /* attach the preset path to the type */ + g_type_set_qdata (type, preset_path_quark, (gpointer) preset_path); + } + return (preset_path); +} + +static void +preset_cleanup (gpointer user_data, GObject * self) +{ + GType type = (GType) user_data; + GList *instances; + + /* remove instance from instance list (if not yet there) */ + instances = (GList *) g_type_get_qdata (type, instance_list_quark); + if (instances != NULL) { + instances = g_list_remove (instances, self); + GST_INFO ("old instanc removed"); + g_type_set_qdata (type, instance_list_quark, (gpointer) instances); + } +} + +static GList * +gst_preset_default_get_preset_names (GstPreset * self) +{ + GType type = G_TYPE_FROM_INSTANCE (self); + GList *presets; + GList *instances; + GHashTable *preset_meta, *preset_data; + gboolean found = FALSE; + + /* get the presets from the type */ + if (!preset_get_storage (self, &presets, &preset_meta, &preset_data)) { + const gchar *preset_path = preset_get_path (self); + FILE *in; + + GST_DEBUG ("probing preset file: '%s'", preset_path); + + /* read presets */ + if ((in = fopen (preset_path, "rb"))) { + const gchar *element_name = G_OBJECT_TYPE_NAME (self); + gchar line[LINE_LEN + 1], *str, *val; + gboolean parse_preset; + gchar *preset_name; + GHashTable *meta; + GHashTable *data; + GObjectClass *klass; + GParamSpec *property; + + GST_DEBUG ("loading preset file: '%s'", preset_path); + + /* read header */ + if (!fgets (line, LINE_LEN, in)) + goto eof_error; + if (strcmp (line, "GStreamer Preset\n")) { + GST_WARNING ("%s:1: file id expected", preset_path); + goto eof_error; + } + if (!fgets (line, LINE_LEN, in)) + goto eof_error; + /* @todo: what version */ + if (!fgets (line, LINE_LEN, in)) + goto eof_error; + if (strcmp (g_strchomp (line), element_name)) { + GST_WARNING ("%s:3: wrong element name", preset_path); + goto eof_error; + } + if (!fgets (line, LINE_LEN, in)) + goto eof_error; + if (*line != '\n') { + GST_WARNING ("%s:4: blank line expected", preset_path); + goto eof_error; + } + + klass = G_OBJECT_CLASS (GST_ELEMENT_GET_CLASS (self)); + + /* read preset entries */ + while (!feof (in)) { + /* read preset entry */ + fgets (line, LINE_LEN, in); + g_strchomp (line); + if (*line) { + preset_name = g_strdup (line); + GST_INFO ("%s: preset '%s'", preset_path, preset_name); + + data = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); + meta = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + /* read preset lines */ + parse_preset = TRUE; + while (parse_preset) { + fgets (line, LINE_LEN, in); + if (feof (in) || (*line == '\n')) { + GST_DEBUG ("preset done"); + parse_preset = FALSE; + break; + } + str = g_strchomp (line); + while (*str) { + if (*str == ':') { + *str = '\0'; + GST_DEBUG ("meta[%s]='%s'", line, &str[1]); + if ((val = g_hash_table_lookup (meta, line))) { + g_free (val); + g_hash_table_insert (meta, (gpointer) line, + (gpointer) g_strdup (&str[1])); + } else { + g_hash_table_insert (meta, (gpointer) g_strdup (line), + (gpointer) g_strdup (&str[1])); + } + break; + } else if (*str == '=') { + *str = '\0'; + GST_DEBUG ("data[%s]='%s'", line, &str[1]); + if ((property = g_object_class_find_property (klass, line))) { + g_hash_table_insert (data, (gpointer) property->name, + (gpointer) g_strdup (&str[1])); + } else { + GST_WARNING ("%s: Invalid property '%s'", preset_path, line); + } + break; + } + str++; + } + /* @todo: handle childproxy properties + * [child]= + */ + } + + GST_INFO ("preset: %p, %p", meta, data); + g_hash_table_insert (preset_data, (gpointer) preset_name, + (gpointer) data); + g_hash_table_insert (preset_meta, (gpointer) preset_name, + (gpointer) meta); + presets = + g_list_insert_sorted (presets, (gpointer) preset_name, + (GCompareFunc) strcmp); + } + } + + eof_error: + fclose (in); + } else { + GST_INFO ("can't open preset file: '%s'", preset_path); + } + + /* attach the preset to the type */ + g_type_set_qdata (type, preset_list_quark, (gpointer) presets); + } + + /* insert instance in instance list (if not yet there) */ + instances = (GList *) g_type_get_qdata (type, instance_list_quark); + if (instances != NULL) { + if (g_list_find (instances, self)) + found = TRUE; + } + if (!found) { + GST_INFO ("new instance added"); + /* register a weak ref, to clean up when the object gets destroyed */ + g_object_weak_ref (G_OBJECT (self), preset_cleanup, (gpointer) type); + instances = g_list_prepend (instances, self); + g_type_set_qdata (type, instance_list_quark, (gpointer) instances); + } + return (presets); +} + +static gboolean +gst_preset_default_load_preset (GstPreset * self, const gchar * name) +{ + GList *presets; + GHashTable *preset_data; + + /* get the presets from the type */ + if (preset_get_storage (self, &presets, NULL, &preset_data)) { + GList *node; + + if ((node = g_list_find_custom (presets, name, (GCompareFunc) strcmp))) { + GHashTable *data = g_hash_table_lookup (preset_data, node->data); + GParamSpec **properties, *property; + GType base, parent; + guint i, number_of_properties; + gchar *val = NULL; + + GST_DEBUG ("loading preset : '%s', data : %p (size=%d)", name, data, + g_hash_table_size (data)); + + /* preset found, now set values */ + if ((properties = + g_object_class_list_properties (G_OBJECT_CLASS + (GST_ELEMENT_GET_CLASS (self)), &number_of_properties))) { + for (i = 0; i < number_of_properties; i++) { + property = properties[i]; + /* skip non-controlable */ + if (!(property->flags & GST_PARAM_CONTROLLABLE)) + continue; + /* check if we have a settings for this property */ + if ((val = (gchar *) g_hash_table_lookup (data, property->name))) { + GST_DEBUG ("setting value '%s' for property '%s'", val, + property->name); + /* get base type */ + base = property->value_type; + while ((parent = g_type_parent (base))) { + base = parent; + } + switch (base) { + case G_TYPE_INT: + case G_TYPE_UINT: + case G_TYPE_BOOLEAN: + case G_TYPE_ENUM: + g_object_set (G_OBJECT (self), property->name, atoi (val), + NULL); + break; + case G_TYPE_LONG: + case G_TYPE_ULONG: + g_object_set (G_OBJECT (self), property->name, atol (val), + NULL); + break; + case G_TYPE_FLOAT: + g_object_set (G_OBJECT (self), property->name, + (float) g_ascii_strtod (val, NULL), NULL); + break; + case G_TYPE_DOUBLE: + g_object_set (G_OBJECT (self), property->name, + g_ascii_strtod (val, NULL), NULL); + break; + case G_TYPE_STRING: + g_object_set (G_OBJECT (self), property->name, val, NULL); + break; + default: + GST_WARNING + ("incomplete implementation for GParamSpec type '%s'", + G_PARAM_SPEC_TYPE_NAME (property)); + } + } else { + GST_INFO ("parameter '%s' not in preset", property->name); + } + } + return (TRUE); + } + } + } else { + GST_INFO ("no presets"); + } + return (FALSE); +} + +static void +preset_store_meta (gpointer key, gpointer value, gpointer user_data) +{ + if (key && value) { + fprintf ((FILE *) user_data, "%s:%s\n", (gchar *) key, (gchar *) value); + } +} + +static void +preset_store_data (gpointer key, gpointer value, gpointer user_data) +{ + if (key && value) { + fprintf ((FILE *) user_data, "%s=%s\n", (gchar *) key, (gchar *) value); + } +} + +static gboolean +gst_preset_default_save_presets_file (GstPreset * self) +{ + gboolean res = FALSE; + GList *presets; + GHashTable *preset_meta, *preset_data; + const gchar *preset_path = preset_get_path (self); + + /* get the presets from the type */ + if (preset_get_storage (self, &presets, &preset_meta, &preset_data)) { + FILE *out; + gchar *bak_file_name; + gboolean backup = TRUE; + + GST_DEBUG ("saving preset file: '%s'", preset_path); + + // create backup if possible + bak_file_name = g_strdup_printf ("%s.bak", preset_path); + if (g_file_test (bak_file_name, G_FILE_TEST_EXISTS)) { + if (g_unlink (bak_file_name)) { + backup = FALSE; + GST_INFO ("cannot remove old backup file : %s", bak_file_name); + } + } + if (backup) { + if (g_rename (preset_path, bak_file_name)) { + GST_INFO ("cannot backup file : %s -> %s", preset_path, bak_file_name); + } + } + g_free (bak_file_name); + + + /* @todo: create backup */ + + /* write presets */ + if ((out = fopen (preset_path, "wb"))) { + const gchar *element_name = G_OBJECT_TYPE_NAME (self); + gchar *preset_name; + GList *node; + GHashTable *meta, *data; + + /* write header */ + if (!(fputs ("GStreamer Preset\n", out))) + goto eof_error; + /* @todo: what version */ + if (!(fputs ("1.0\n", out))) + goto eof_error; + if (!(fputs (element_name, out))) + goto eof_error; + if (!(fputs ("\n\n", out))) + goto eof_error; + + /* write preset entries */ + for (node = presets; node; node = g_list_next (node)) { + preset_name = node->data; + /* write preset entry */ + if (!(fputs (preset_name, out))) + goto eof_error; + if (!(fputs ("\n", out))) + goto eof_error; + + /* write data */ + meta = g_hash_table_lookup (preset_meta, (gpointer) preset_name); + g_hash_table_foreach (meta, preset_store_meta, out); + data = g_hash_table_lookup (preset_data, (gpointer) preset_name); + g_hash_table_foreach (data, preset_store_data, out); + if (!(fputs ("\n", out))) + goto eof_error; + } + + res = TRUE; + eof_error: + fclose (out); + } + } else { + GST_DEBUG + ("no presets, trying to unlink possibly existing preset file: '%s'", + preset_path); + unlink (preset_path); + } + return (res); +} + +static gboolean +gst_preset_default_save_preset (GstPreset * self, const gchar * name) +{ + GType type = G_TYPE_FROM_INSTANCE (self); + GList *presets; + GHashTable *preset_meta, *preset_data; + GHashTable *meta, *data; + GParamSpec **properties, *property; + GType base, parent; + guint i, number_of_properties; + gchar *str = NULL, buffer[30 + 1]; + + /*guint flags; */ + + GST_INFO ("saving new preset: %s", name); + + /* get the presets from the type */ + preset_get_storage (self, &presets, &preset_meta, &preset_data); + + data = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); + meta = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + /* take copies of current gobject properties from self */ + if ((properties = + g_object_class_list_properties (G_OBJECT_CLASS (GST_ELEMENT_GET_CLASS + (self)), &number_of_properties))) { + for (i = 0; i < number_of_properties; i++) { + property = properties[i]; + /*flags=GPOINTER_TO_INT(g_param_spec_get_qdata(property,gst_property_meta_quark_flags)); */ + + /* skip non-controlable */ + if (!(property->flags & GST_PARAM_CONTROLLABLE)) + continue; + + /* get base type */ + base = property->value_type; + while ((parent = g_type_parent (base))) { + base = parent; + } + /* get value and serialize */ + GST_INFO (" storing property: %s (type is %s)", property->name, + g_type_name (base)); + switch (base) { + case G_TYPE_BOOLEAN: + case G_TYPE_ENUM: + case G_TYPE_INT:{ + gint val; + + g_object_get (G_OBJECT (self), property->name, &val, NULL); + str = g_strdup_printf ("%d", val); + } + break; + case G_TYPE_UINT:{ + guint val; + + g_object_get (G_OBJECT (self), property->name, &val, NULL); + str = g_strdup_printf ("%u", val); + } + break; + case G_TYPE_LONG:{ + glong val; + + g_object_get (G_OBJECT (self), property->name, &val, NULL); + str = g_strdup_printf ("%ld", val); + } + break; + case G_TYPE_ULONG:{ + gulong val; + + g_object_get (G_OBJECT (self), property->name, &val, NULL); + str = g_strdup_printf ("%lu", val); + } + break; + case G_TYPE_FLOAT:{ + gfloat val; + + g_object_get (G_OBJECT (self), property->name, &val, NULL); + g_ascii_dtostr (buffer, 30, (gdouble) val); + str = g_strdup (buffer); + } + break; + case G_TYPE_DOUBLE:{ + gdouble val; + + g_object_get (G_OBJECT (self), property->name, &val, NULL); + g_ascii_dtostr (buffer, 30, val); + str = g_strdup (buffer); + } + break; + case G_TYPE_STRING: + g_object_get (G_OBJECT (self), property->name, &str, NULL); + if (str && !*str) + str = NULL; + break; + default: + GST_WARNING ("incomplete implementation for GParamSpec type '%s'", + G_PARAM_SPEC_TYPE_NAME (property)); + } + if (str) { + g_hash_table_insert (data, (gpointer) property->name, (gpointer) str); + str = NULL; + } + + } + GST_INFO (" saved"); + } + + /* + * flock(fileno()) + * http://www.ecst.csuchico.edu/~beej/guide/ipc/flock.html + */ + g_hash_table_insert (preset_data, (gpointer) name, (gpointer) data); + g_hash_table_insert (preset_meta, (gpointer) name, (gpointer) meta); + presets = + g_list_insert_sorted (presets, (gpointer) name, (GCompareFunc) strcmp); + /* attach the preset list to the type */ + g_type_set_qdata (type, preset_list_quark, (gpointer) presets); + GST_INFO ("done"); + + return (gst_preset_default_save_presets_file (self)); +} + +static gboolean +gst_preset_default_rename_preset (GstPreset * self, const gchar * old_name, + const gchar * new_name) +{ + GType type = G_TYPE_FROM_INSTANCE (self); + GList *presets; + GHashTable *preset_meta, *preset_data; + + /* get the presets from the type */ + if (preset_get_storage (self, &presets, &preset_meta, &preset_data)) { + GList *node; + + if ((node = g_list_find_custom (presets, old_name, (GCompareFunc) strcmp))) { + GHashTable *meta, *data; + + /* readd under new name */ + presets = + g_list_insert_sorted (presets, (gpointer) new_name, + (GCompareFunc) strcmp); + + /* readd the hash entries */ + if ((meta = g_hash_table_lookup (preset_meta, node->data))) { + g_hash_table_remove (preset_meta, node->data); + g_hash_table_insert (preset_meta, (gpointer) new_name, (gpointer) meta); + } + if ((data = g_hash_table_lookup (preset_data, node->data))) { + g_hash_table_remove (preset_data, node->data); + g_hash_table_insert (preset_data, (gpointer) new_name, (gpointer) data); + } + + /* remove the old one */ + presets = g_list_delete_link (presets, node); + + GST_INFO ("preset moved '%s' -> '%s'", old_name, new_name); + g_type_set_qdata (type, preset_list_quark, (gpointer) presets); + + return (gst_preset_default_save_presets_file (self)); + } + } else { + GST_WARNING ("no presets"); + } + return (FALSE); +} + +static gboolean +gst_preset_default_delete_preset (GstPreset * self, const gchar * name) +{ + GType type = G_TYPE_FROM_INSTANCE (self); + GList *presets; + GHashTable *preset_meta, *preset_data; + + /* get the presets from the type */ + if (preset_get_storage (self, &presets, &preset_meta, &preset_data)) { + GList *node; + + if ((node = g_list_find_custom (presets, name, (GCompareFunc) strcmp))) { + GHashTable *meta, *data; + + /* free the hash entries */ + if ((meta = g_hash_table_lookup (preset_meta, node->data))) { + g_hash_table_remove (preset_meta, node->data); + g_hash_table_destroy (meta); + } + if ((data = g_hash_table_lookup (preset_data, node->data))) { + g_hash_table_remove (preset_data, node->data); + g_hash_table_destroy (data); + } + + /* remove the found one */ + presets = g_list_delete_link (presets, node); + + GST_INFO ("preset removed '%s'", name); + g_type_set_qdata (type, preset_list_quark, (gpointer) presets); + g_free ((gpointer) name); + + return (gst_preset_default_save_presets_file (self)); + } + } else { + GST_WARNING ("no presets"); + } + return (FALSE); +} + +static gboolean +gst_preset_default_set_meta (GstPreset * self, const gchar * name, + const gchar * tag, gchar * value) +{ + gboolean res = FALSE; + GList *presets; + GHashTable *preset_meta; + + /* get the presets from the type */ + if (preset_get_storage (self, &presets, &preset_meta, NULL)) { + GList *node; + + if ((node = g_list_find_custom (presets, name, (GCompareFunc) strcmp))) { + GHashTable *meta = g_hash_table_lookup (preset_meta, node->data); + gchar *old_value; + gboolean changed = FALSE; + + if ((old_value = g_hash_table_lookup (meta, tag))) { + g_free (old_value); + changed = TRUE; + } + if (value) { + if (changed) + tag = g_strdup (tag); + g_hash_table_insert (meta, (gpointer) tag, g_strdup (value)); + changed = TRUE; + } + if (changed) { + res = gst_preset_default_save_presets_file (self); + } + } + } else { + GST_WARNING ("no presets"); + } + return (res); +} + +static gboolean +gst_preset_default_get_meta (GstPreset * self, const gchar * name, + const gchar * tag, gchar ** value) +{ + gboolean res = FALSE; + GList *presets; + GHashTable *preset_meta; + + /* get the presets from the type */ + if (preset_get_storage (self, &presets, &preset_meta, NULL)) { + GList *node; + + if ((node = g_list_find_custom (presets, name, (GCompareFunc) strcmp))) { + GHashTable *meta = g_hash_table_lookup (preset_meta, node->data); + gchar *new_value; + + if ((new_value = g_hash_table_lookup (meta, tag))) { + *value = g_strdup (new_value); + res = TRUE; + } + } + } else { + GST_WARNING ("no presets"); + } + if (!res) + *value = NULL; + return (res); +} + +static void +gst_preset_default_create_preset (GstPreset * self) +{ + GParamSpec **properties, *property; + guint i, number_of_properties; + GType param_type, base_type; + + if ((properties = + g_object_class_list_properties (G_OBJECT_CLASS (GST_OBJECT_GET_CLASS + (self)), &number_of_properties))) { + gdouble rnd; + + /* @todo: what about voice properties */ + + GST_INFO ("nr of values : %d", number_of_properties); + for (i = 0; i < number_of_properties; i++) { + property = properties[i]; + + /* skip non-controlable, trigger params & voice params */ + if (!(property->flags & GST_PARAM_CONTROLLABLE)) + continue; + /* we do not want to create a setting for trigger properties, buzztard + has more flags attached to g_param_specs + else { + guint flags=0; + + if(BT_IS_PROPERTY_META(self)) { + flags=GPOINTER_TO_INT(g_param_spec_get_qdata(property,bt_property_meta_quark_flags)); + } + if(!(flags&BT_PROPERTY_META_STATE)) continue; + } + */ + + GST_INFO ("property '%s' (GType=%d)", property->name, + property->value_type); + + param_type = property->value_type; + while ((base_type = g_type_parent (param_type))) + param_type = base_type; + + rnd = ((gdouble) rand ()) / (RAND_MAX + 1.0); + switch (param_type) { + case G_TYPE_BOOLEAN:{ + g_object_set (self, property->name, (gboolean) (2.0 * rnd), NULL); + } + break; + case G_TYPE_INT:{ + const GParamSpecInt *int_property = G_PARAM_SPEC_INT (property); + + g_object_set (self, property->name, + (gint) (int_property->minimum + ((int_property->maximum - + int_property->minimum) * rnd)), NULL); + } break; + case G_TYPE_UINT:{ + const GParamSpecUInt *uint_property = G_PARAM_SPEC_UINT (property); + + g_object_set (self, property->name, + (guint) (uint_property->minimum + ((uint_property->maximum - + uint_property->minimum) * rnd)), NULL); + } break; + case G_TYPE_DOUBLE:{ + const GParamSpecDouble *double_property = + G_PARAM_SPEC_DOUBLE (property); + + g_object_set (self, property->name, + (gdouble) (double_property->minimum + ((double_property->maximum - + double_property->minimum) * rnd)), NULL); + } break; + case G_TYPE_ENUM:{ + const GParamSpecEnum *enum_property = G_PARAM_SPEC_ENUM (property); + const GEnumClass *enum_class = enum_property->enum_class; + + g_object_set (self, property->name, + (gulong) (enum_class->minimum + ((enum_class->maximum - + enum_class->minimum) * rnd)), NULL); + } break; + default: + //GST_WARNING("unhandled GType=%d:'%s'",param_type,G_VALUE_TYPE_NAME(param_type)); + GST_WARNING ("unhandled GType=%d", param_type); + } + } + } +} + +/* wrapper */ + +/** + * gst_preset_get_preset_names: + * @self: a #GObject that implements #GstPreset + * + * Get a copy of the preset list names. Free list when done. + * + * Returns: list with names + */ +GList * +gst_preset_get_preset_names (GstPreset * self) +{ + g_return_val_if_fail (GST_IS_PRESET (self), NULL); + + return (GST_PRESET_GET_INTERFACE (self)->get_preset_names (self)); +} + +/** + * gst_preset_load_preset: + * @self: a #GObject that implements #GstPreset + * @name: preset name to load + * + * Load the given preset. + * + * Returns: %TRUE for success, %FALSE if e.g. there is no preset with that @name + */ +gboolean +gst_preset_load_preset (GstPreset * self, const gchar * name) +{ + g_return_val_if_fail (GST_IS_PRESET (self), FALSE); + g_return_val_if_fail (name, FALSE); + + return (GST_PRESET_GET_INTERFACE (self)->load_preset (self, name)); +} + +/** + * gst_preset_save_preset: + * @self: a #GObject that implements #GstPreset + * @name: preset name to save + * + * Save the current preset under the given name. If there is already a preset by + * this @name it will be overwritten. + * + * Returns: %TRUE for success, %FALSE + */ +gboolean +gst_preset_save_preset (GstPreset * self, const gchar * name) +{ + g_return_val_if_fail (GST_IS_PRESET (self), FALSE); + g_return_val_if_fail (name, FALSE); + + return (GST_PRESET_GET_INTERFACE (self)->save_preset (self, name)); +} + +/** + * gst_preset_rename_preset: + * @self: a #GObject that implements #GstPreset + * @old_name: current preset name + * @new_name: new preset name + * + * Renames a preset. If there is already a preset by thr @new_name it will be + * overwritten. + * + * Returns: %TRUE for success, %FALSE if e.g. there is no preset with @old_name + */ +gboolean +gst_preset_rename_preset (GstPreset * self, const gchar * old_name, + const gchar * new_name) +{ + g_return_val_if_fail (GST_IS_PRESET (self), FALSE); + g_return_val_if_fail (old_name, FALSE); + g_return_val_if_fail (new_name, FALSE); + + return (GST_PRESET_GET_INTERFACE (self)->rename_preset (self, old_name, + new_name)); +} + +/** + * gst_preset_delete_preset: + * @self: a #GObject that implements #GstPreset + * @name: preset name to remove + * + * Delete the given preset. + * + * Returns: %TRUE for success, %FALSE if e.g. there is no preset with that @name + */ +gboolean +gst_preset_delete_preset (GstPreset * self, const gchar * name) +{ + g_return_val_if_fail (GST_IS_PRESET (self), FALSE); + g_return_val_if_fail (name, FALSE); + + return (GST_PRESET_GET_INTERFACE (self)->delete_preset (self, name)); +} + +/** + * gst_preset_set_meta: + * @self: a #GObject that implements #GstPreset + * @name: preset name + * @tag: meta data item name + * @value: new value + * + * Sets a new @value for an existing meta data item or adds a new item. Meta + * data @tag names can be something like e.g. "comment". Supplying %NULL for the + * @value will unset an existing value. + * + * Returns: %TRUE for success, %FALSE if e.g. there is no preset with that @name + */ +gboolean +gst_preset_set_meta (GstPreset * self, const gchar * name, const gchar * tag, + gchar * value) +{ + g_return_val_if_fail (GST_IS_PRESET (self), FALSE); + g_return_val_if_fail (name, FALSE); + g_return_val_if_fail (tag, FALSE); + + return GST_PRESET_GET_INTERFACE (self)->set_meta (self, name, tag, value); +} + +/** + * gst_preset_get_meta: + * @self: a #GObject that implements #GstPreset + * @name: preset name + * @tag: meta data item name + * @value: value + * + * Gets the @value for an existing meta data @tag. Meta data @tag names can be + * something like e.g. "comment". Returned values need to be released when done. + * + * Returns: %TRUE for success, %FALSE if e.g. there is no preset with that @name + * or no value for the given @tag + */ +gboolean +gst_preset_get_meta (GstPreset * self, const gchar * name, const gchar * tag, + gchar ** value) +{ + g_return_val_if_fail (GST_IS_PRESET (self), FALSE); + g_return_val_if_fail (name, FALSE); + g_return_val_if_fail (tag, FALSE); + g_return_val_if_fail (value, FALSE); + + return GST_PRESET_GET_INTERFACE (self)->get_meta (self, name, tag, value); +} + +/** + * gst_preset_create_preset: + * @self: a #GObject that implements #GstPreset + * + * Create a new randomized preset. This method is optional. If not implemented + * true randomization will be applied. + */ +void +gst_preset_create_preset (GstPreset * self) +{ + g_return_if_fail (GST_IS_PRESET (self)); + + GST_PRESET_GET_INTERFACE (self)->create_preset (self); +} + +/* class internals */ + +static void +gst_preset_class_init (GstPresetInterface * iface) +{ + iface->get_preset_names = gst_preset_default_get_preset_names; + + iface->load_preset = gst_preset_default_load_preset; + iface->save_preset = gst_preset_default_save_preset; + iface->rename_preset = gst_preset_default_rename_preset; + iface->delete_preset = gst_preset_default_delete_preset; + + iface->set_meta = gst_preset_default_set_meta; + iface->get_meta = gst_preset_default_get_meta; + + iface->create_preset = gst_preset_default_create_preset; +} + +static void +gst_preset_base_init (gpointer g_class) +{ + static gboolean initialized = FALSE; + + if (!initialized) { + /* init default implementation */ + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "preset", + GST_DEBUG_FG_WHITE | GST_DEBUG_BG_BLACK, "preset interface"); + + /* create quarks for use with g_type_{g,s}et_qdata() */ + preset_list_quark = g_quark_from_string ("GstPreset::presets"); + preset_path_quark = g_quark_from_string ("GstPreset::path"); + preset_data_quark = g_quark_from_string ("GstPreset::data"); + preset_meta_quark = g_quark_from_string ("GstPreset::meta"); + instance_list_quark = g_quark_from_string ("GstPreset::instances"); + + initialized = TRUE; + } +} + +GType +gst_preset_get_type (void) +{ + static GType type = 0; + + if (type == 0) { + const GTypeInfo info = { + sizeof (GstPresetInterface), + (GBaseInitFunc) gst_preset_base_init, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gst_preset_class_init, /* class_init */ + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL /* instance_init */ + }; + type = g_type_register_static (G_TYPE_INTERFACE, "GstPreset", &info, 0); + } + return type; +} diff --git a/gst/gstpreset.h b/gst/gstpreset.h new file mode 100644 index 0000000000..fab9babd71 --- /dev/null +++ b/gst/gstpreset.h @@ -0,0 +1,82 @@ +/* GStreamer + * Copyright (C) 2006 Stefan Kost + * + * gstpreset.h: helper interface header for element presets + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_PRESET_H__ +#define __GST_PRESET_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_PRESET (gst_preset_get_type()) +#define GST_PRESET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PRESET, GstPreset)) +#define GST_IS_PRESET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PRESET)) +#define GST_PRESET_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GST_TYPE_PRESET, GstPresetInterface)) + + +typedef struct _GstPreset GstPreset; /* dummy object */ +typedef struct _GstPresetInterface GstPresetInterface; + +struct _GstPresetInterface +{ + GTypeInterface parent; + + GList* (*get_preset_names) (GstPreset *self); + + gboolean (*load_preset) (GstPreset *self, const gchar *name); + gboolean (*save_preset) (GstPreset *self, const gchar *name); + gboolean (*rename_preset) (GstPreset *self, const gchar *old_name, const gchar *new_name); + gboolean (*delete_preset) (GstPreset *self, const gchar *name); + + gboolean (*set_meta) (GstPreset *self,const gchar *name, const gchar *tag, gchar *value); + gboolean (*get_meta) (GstPreset *self,const gchar *name, const gchar *tag, gchar **value); + + void (*create_preset) (GstPreset *self); + + /* @todo: + * + * need a presets-changed signal, to notify of changes in preset list + * + * need a way to sync class instances, we want to keep only one list for all + * instances of a type and if the list changes, we trigger the signal for all + * instance + */ +}; + +GType gst_preset_get_type(void); + +GList* gst_preset_get_preset_names (GstPreset *self); + +gboolean gst_preset_load_preset (GstPreset *self, const gchar *name); +gboolean gst_preset_save_preset (GstPreset *self, const gchar *name); +gboolean gst_preset_rename_preset (GstPreset *self, const gchar *old_name, const gchar *new_name); +gboolean gst_preset_delete_preset (GstPreset *self, const gchar *name); + +gboolean gst_preset_set_meta (GstPreset *self,const gchar *name, const gchar *tag, gchar *value); +gboolean gst_preset_get_meta (GstPreset *self,const gchar *name, const gchar *tag, gchar **value); + +void gst_preset_create_preset (GstPreset *self); + +G_END_DECLS + +#endif /* __GST_PRESET_H__ */