#ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include "gst/gst-i18n-app.h" static GRegex *cleanup_caps_field = NULL; static void _add_object_details (GString * json, GObject * object); static gboolean has_sometimes_template (GObject * object) { GstElementClass *klass; GList *l; if (!GST_IS_OBJECT (object)) return FALSE; klass = GST_ELEMENT_GET_CLASS (object); for (l = klass->padtemplates; l != NULL; l = l->next) { if (GST_PAD_TEMPLATE (l->data)->presence == GST_PAD_SOMETIMES) return TRUE; } return FALSE; } static gchar * json_strescape (const gchar * str) { const gchar *p; const gchar *end; GString *output; gsize len; if (!str) return g_strdup ("NULL"); len = strlen (str); end = str + len; output = g_string_sized_new (len); for (p = str; p < end; p++) { if (*p == '\\' || *p == '"') { g_string_append_c (output, '\\'); g_string_append_c (output, *p); } else if (*p == '%') { g_string_append_c (output, '%'); g_string_append_c (output, *p); } else if ((*p > 0 && *p < 0x1f) || *p == 0x7f) { switch (*p) { case '\b': g_string_append (output, "\\b"); break; case '\f': g_string_append (output, "\\f"); break; case '\n': g_string_append (output, "\\n"); break; case '\r': g_string_append (output, "\\r"); break; case '\t': g_string_append (output, "\\t"); break; default: g_string_append_printf (output, "\\u00%02x", (guint) * p); break; } } else { g_string_append_c (output, *p); } } return g_string_free (output, FALSE); } static gchar * flags_to_string (GFlagsValue * values, guint flags) { GString *s = NULL; guint flags_left, i; /* first look for an exact match and count the number of values */ for (i = 0; values[i].value_name != NULL; ++i) { if (values[i].value == flags) return g_strdup (values[i].value_nick); } s = g_string_new (NULL); /* we assume the values are sorted from lowest to highest value */ flags_left = flags; while (i > 0) { --i; if (values[i].value != 0 && (flags_left & values[i].value) == values[i].value) { if (s->len > 0) g_string_append_c (s, '+'); g_string_append (s, values[i].value_nick); flags_left -= values[i].value; if (flags_left == 0) break; } } if (s->len == 0) g_string_assign (s, "(none)"); return g_string_free (s, FALSE); } static void _serialize_flags (GString * json, const gchar * key_name, GType gtype, GValue * value) { GFlagsValue *values = G_FLAGS_CLASS (g_type_class_ref (gtype))->values; if (value) { gchar *cur; cur = flags_to_string (values, g_value_get_flags (value)); g_string_append_printf (json, ",\"default\": \"%s\",", cur); g_free (cur); } g_string_append_printf (json, "\"%s\": [", key_name); while (values[0].value_name) { gchar *value_name = json_strescape (values[0].value_name); gchar *value_nick = json_strescape (values[0].value_nick); g_string_append_printf (json, "{\"name\": \"%s\"," "\"value\": \"0x%08x\"," "\"desc\": \"%s\"}", value_nick, values[0].value, value_name); ++values; if (values[0].value_name) g_string_append_c (json, ','); g_free (value_name); g_free (value_nick); } g_string_append_c (json, ']'); } static void _serialize_enum (GString * json, const gchar * key_name, GType gtype, GValue * value) { GEnumValue *values; guint j = 0; gint enum_value; gchar *value_nick = g_strdup (""); values = G_ENUM_CLASS (g_type_class_ref (gtype))->values; if (value) { enum_value = g_value_get_enum (value); while (values[j].value_name) { if (values[j].value == enum_value) { g_free (value_nick); value_nick = json_strescape (values[j].value_nick); } j++; } g_string_append_printf (json, ",\"default\": \"%s (%d)\"," "\"enum\": true,", value_nick, enum_value);; g_free (value_nick); } g_string_append_printf (json, "\"%s\": [", key_name); j = 0; while (values[j].value_name) { gchar *value_name = json_strescape (values[j].value_name); gchar *value_nick = json_strescape (values[j].value_nick); g_string_append_printf (json, "{\"name\": \"%s\"," "\"value\": \"%d\"," "\"desc\": \"%s\"}", value_nick, values[j].value, value_name); j++; if (values[j].value_name) g_string_append_c (json, ','); g_free (value_name); g_free (value_nick); } g_string_append_c (json, ']'); } static void _add_signals (GString * json, GObject * object) { gboolean opened = FALSE; guint *signals; guint nsignals; gint i = 0, j, k; GSignalQuery *query = NULL; GType type; GSList *found_signals, *l; for (k = 0; k < 2; k++) { found_signals = NULL; /* For elements that have sometimes pads, also list a few useful GstElement * signals. Put these first, so element-specific ones come later. */ if (k == 0 && has_sometimes_template (object)) { query = g_new0 (GSignalQuery, 1); g_signal_query (g_signal_lookup ("pad-added", GST_TYPE_ELEMENT), query); found_signals = g_slist_append (found_signals, query); query = g_new0 (GSignalQuery, 1); g_signal_query (g_signal_lookup ("pad-removed", GST_TYPE_ELEMENT), query); found_signals = g_slist_append (found_signals, query); query = g_new0 (GSignalQuery, 1); g_signal_query (g_signal_lookup ("no-more-pads", GST_TYPE_ELEMENT), query); found_signals = g_slist_append (found_signals, query); } for (type = G_OBJECT_TYPE (object); type; type = g_type_parent (type)) { if (type == GST_TYPE_ELEMENT || type == GST_TYPE_OBJECT) break; if (type == GST_TYPE_PAD) break; if (type == GST_TYPE_BIN && G_OBJECT_TYPE (object) != GST_TYPE_BIN) continue; signals = g_signal_list_ids (type, &nsignals); for (i = 0; i < nsignals; i++) { query = g_new0 (GSignalQuery, 1); g_signal_query (signals[i], query); if ((k == 0 && !(query->signal_flags & G_SIGNAL_ACTION)) || (k == 1 && (query->signal_flags & G_SIGNAL_ACTION))) found_signals = g_slist_append (found_signals, query); else g_free (query); } g_free (signals); signals = NULL; } if (!found_signals) continue; for (l = found_signals; l; l = l->next) { query = (GSignalQuery *) l->data; g_string_append_printf (json, "%s\"%s\" : {" "\"retval\": \"%s\"," "\"args\": [", opened ? "," : ",\"signals\": {", query->signal_name, g_type_name (query->return_type)); opened = TRUE; for (j = 0; j < query->n_params; j++) { g_string_append_printf (json, "%s\"%s\"", j ? "," : "", g_type_name (query->param_types[j])); } g_string_append_c (json, ']'); if (g_type_is_a (query->return_type, G_TYPE_ENUM)) { g_string_append_c (json, ','); _serialize_enum (json, "return-values", query->return_type, NULL); } else if (g_type_is_a (query->return_type, G_TYPE_FLAGS)) { g_string_append_c (json, ','); _serialize_flags (json, "return-values", query->return_type, NULL); } g_string_append_c (json, '}'); } g_slist_foreach (found_signals, (GFunc) g_free, NULL); g_slist_free (found_signals); opened = TRUE; } if (opened) g_string_append (json, "}"); } static void _add_properties (GString * json, GObject * object, GObjectClass * klass) { gchar *tmpstr; guint i, n_props; gboolean opened = FALSE; GParamSpec **specs, *spec; specs = g_object_class_list_properties (klass, &n_props); for (i = 0; i < n_props; i++) { GValue value = { 0, }; spec = specs[i]; if (spec->owner_type == GST_TYPE_PAD || spec->owner_type == GST_TYPE_OBJECT) continue; g_value_init (&value, spec->value_type); if (object && ! !(spec->flags & G_PARAM_READABLE) && !(spec->flags & GST_PARAM_DOC_SHOW_DEFAULT)) { g_object_get_property (G_OBJECT (object), spec->name, &value); } else { /* if we can't read the property value, assume it's set to the default * (which might not be entirely true for sub-classes, but that's an * unlikely corner-case anyway) */ g_param_value_set_default (spec, &value); } if (!opened) g_string_append (json, ",\"properties\": {"); tmpstr = json_strescape (g_param_spec_get_blurb (spec)); g_string_append_printf (json, "%s" "\"%s\": {" "\"construct-only\": %s," "\"construct\": %s," "\"writable\": %s," "\"blurb\": \"%s\"," "\"type-name\": \"%s\"", opened ? "," : "", spec->name, spec->flags & G_PARAM_CONSTRUCT_ONLY ? "true" : "false", spec->flags & G_PARAM_CONSTRUCT ? "true" : "false", spec->flags & G_PARAM_WRITABLE ? "true" : "false", tmpstr, g_type_name (G_PARAM_SPEC_VALUE_TYPE (spec))); g_free (tmpstr); switch (G_VALUE_TYPE (&value)) { case G_TYPE_STRING: { const char *string_val = g_value_get_string (&value); gchar *tmpstr = json_strescape (string_val); g_string_append_printf (json, ",\"default\": \"%s\"", tmpstr);; g_free (tmpstr); break; } case G_TYPE_BOOLEAN: { gboolean bool_val = g_value_get_boolean (&value); g_string_append_printf (json, ",\"default\": \"%s\"", bool_val ? "true" : "false"); break; } case G_TYPE_ULONG: { GParamSpecULong *pulong = G_PARAM_SPEC_ULONG (spec); g_string_append_printf (json, ",\"default\": \"%lu\"" ",\"min\": \"%lu\"" ",\"max\": \"%lu\"", g_value_get_ulong (&value), pulong->minimum, pulong->maximum); GST_ERROR_OBJECT (object, "property '%s' of type ulong: consider changing to " "uint/uint64", g_param_spec_get_name (spec)); break; } case G_TYPE_LONG: { GParamSpecLong *plong = G_PARAM_SPEC_LONG (spec); g_string_append_printf (json, ",\"default\": \"%ld\"" ",\"min\": \"%ld\"" ",\"max\": \"%ld\"", g_value_get_long (&value), plong->minimum, plong->maximum); GST_ERROR_OBJECT (object, "property '%s' of type long: consider changing to " "int/int64", g_param_spec_get_name (spec)); break; } case G_TYPE_UINT: { GParamSpecUInt *puint = G_PARAM_SPEC_UINT (spec); g_string_append_printf (json, ",\"default\": \"%d\"" ",\"min\": \"%d\"" ",\"max\": \"%d\"", g_value_get_uint (&value), puint->minimum, puint->maximum); break; } case G_TYPE_INT: { GParamSpecInt *pint = G_PARAM_SPEC_INT (spec); g_string_append_printf (json, ",\"default\": \"%d\"" ",\"min\": \"%d\"" ",\"max\": \"%d\"", g_value_get_int (&value), pint->minimum, pint->maximum); break; } case G_TYPE_UINT64: { GParamSpecUInt64 *puint64 = G_PARAM_SPEC_UINT64 (spec); g_string_append_printf (json, ",\"default\": \"%" G_GUINT64_FORMAT "\",\"min\": \"%" G_GUINT64_FORMAT "\",\"max\": \"%" G_GUINT64_FORMAT "\"", g_value_get_uint64 (&value), puint64->minimum, puint64->maximum); break; } case G_TYPE_INT64: { GParamSpecInt64 *pint64 = G_PARAM_SPEC_INT64 (spec); g_string_append_printf (json, ",\"default\": \"%" G_GUINT64_FORMAT "\",\"min\": \"%" G_GINT64_FORMAT "\",\"max\": \"%" G_GINT64_FORMAT "\"", g_value_get_int64 (&value), pint64->minimum, pint64->maximum); break; } case G_TYPE_FLOAT: { GParamSpecFloat *pfloat = G_PARAM_SPEC_FLOAT (spec); g_string_append_printf (json, ",\"default\": \"%g\"" ",\"min\": \"%g\"" ",\"max\": \"%g\"", g_value_get_float (&value), pfloat->minimum, pfloat->maximum); break; } case G_TYPE_DOUBLE: { GParamSpecDouble *pdouble = G_PARAM_SPEC_DOUBLE (spec); g_string_append_printf (json, ",\"default\": \"%g\"" ",\"min\": \"%g\"" ",\"max\": \"%g\"", g_value_get_double (&value), pdouble->minimum, pdouble->maximum); break; } case G_TYPE_CHAR: case G_TYPE_UCHAR: GST_ERROR_OBJECT (object, "property '%s' of type char: consider changing to " "int/string", g_param_spec_get_name (spec)); /* fall through */ default: if (spec->value_type == GST_TYPE_CAPS) { const GstCaps *caps = gst_value_get_caps (&value); if (caps) { gchar *capsstr = gst_caps_to_string (caps); gchar *tmpcapsstr = json_strescape (capsstr); g_string_append_printf (json, ",\"default\": \"%s\"", tmpcapsstr); g_free (capsstr); g_free (tmpcapsstr); } } else if (G_IS_PARAM_SPEC_ENUM (spec)) { _serialize_enum (json, "values", spec->value_type, &value); } else if (G_IS_PARAM_SPEC_FLAGS (spec)) { _serialize_flags (json, "values", spec->value_type, &value); } else if (G_IS_PARAM_SPEC_BOXED (spec)) { if (spec->value_type == GST_TYPE_STRUCTURE) { const GstStructure *s = gst_value_get_structure (&value); if (s) { gchar *str = gst_structure_to_string (s); gchar *tmpstr = json_strescape (str); g_string_append_printf (json, ",\"default\": \"%s\"", tmpstr); g_free (str); g_free (tmpstr); } } } else if (GST_IS_PARAM_SPEC_FRACTION (spec)) { GstParamSpecFraction *pfraction = GST_PARAM_SPEC_FRACTION (spec); g_string_append_printf (json, ",\"default\": \"%d/%d\"" ",\"min\": \"%d/%d\"" ",\"max\": \"%d/%d\"", gst_value_get_fraction_numerator (&value), gst_value_get_fraction_denominator (&value), pfraction->min_num, pfraction->min_den, pfraction->max_num, pfraction->max_den); } break; } g_string_append_c (json, '}'); opened = TRUE; } if (opened) g_string_append (json, "}"); } static gboolean print_field (GQuark field, const GValue * value, GString * jcaps) { gchar *tmp, *str = gst_value_serialize (value); if (!g_strcmp0 (g_quark_to_string (field), "format") || !g_strcmp0 (g_quark_to_string (field), "rate")) { if (!cleanup_caps_field) cleanup_caps_field = g_regex_new ("\\(string\\)|\\(rate\\)", 0, 0, NULL); tmp = str; str = g_regex_replace (cleanup_caps_field, str, -1, 0, "", 0, NULL);; g_free (tmp); } g_string_append_printf (jcaps, "%15s: %s\n", g_quark_to_string (field), str); g_free (str); return TRUE; } static gchar * _build_caps (const GstCaps * caps) { guint i; gchar *res; GString *jcaps = g_string_new (NULL); if (gst_caps_is_any (caps)) { g_string_append (jcaps, "ANY"); return g_string_free (jcaps, FALSE); } if (gst_caps_is_empty (caps)) { g_string_append (jcaps, "EMPTY"); return g_string_free (jcaps, FALSE); } for (i = 0; i < gst_caps_get_size (caps); i++) { GstStructure *structure = gst_caps_get_structure (caps, i); GstCapsFeatures *features = gst_caps_get_features (caps, i); if (features && (gst_caps_features_is_any (features) || !gst_caps_features_is_equal (features, GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY))) { gchar *features_string = gst_caps_features_to_string (features); g_string_append_printf (jcaps, "%s%s(%s):\n", i ? "\n" : "", gst_structure_get_name (structure), features_string); g_free (features_string); } else { g_string_append_printf (jcaps, "%s:\n", gst_structure_get_name (structure)); } gst_structure_foreach (structure, (GstStructureForeachFunc) print_field, jcaps); } res = json_strescape (jcaps->str); g_string_free (jcaps, TRUE); return res; } static void _add_element_pad_templates (GString * json, GstElement * element, GstElementFactory * factory) { gboolean opened = FALSE; const GList *pads; GstStaticPadTemplate *padtemplate; GRegex *re = g_regex_new ("%", 0, 0, NULL); pads = gst_element_factory_get_static_pad_templates (factory); while (pads) { gchar *name, *caps; GType pad_type; GstPadTemplate *tmpl; padtemplate = (GstStaticPadTemplate *) (pads->data); pads = g_list_next (pads); name = g_regex_replace (re, padtemplate->name_template, -1, 0, "%%", 0, NULL);; caps = _build_caps (gst_static_caps_get (&padtemplate->static_caps)); g_string_append_printf (json, "%s" "\"%s\": {" "\"caps\": \"%s\"," "\"direction\": \"%s\"," "\"presence\": \"%s\"", opened ? "," : ",\"pad-templates\": {", name, caps, padtemplate->direction == GST_PAD_SRC ? "src" : padtemplate->direction == GST_PAD_SINK ? "sink" : "unknown", padtemplate->presence == GST_PAD_ALWAYS ? "always" : padtemplate->presence == GST_PAD_SOMETIMES ? "sometimes" : padtemplate->presence == GST_PAD_REQUEST ? "request" : "unknown"); opened = TRUE; g_free (name); tmpl = gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (element), padtemplate->name_template); pad_type = GST_PAD_TEMPLATE_GTYPE (tmpl); if (pad_type != G_TYPE_NONE && pad_type != GST_TYPE_PAD) { GObject *tmpobj; g_string_append (json, ",\"object-type\": {"); tmpobj = g_object_new (pad_type, NULL); _add_object_details (json, tmpobj); gst_object_unref (tmpobj); g_string_append (json, "}"); } g_string_append (json, "}"); } if (opened) g_string_append (json, "}"); g_regex_unref (re); } static const char * get_rank_name (char *s, gint rank) { static const int ranks[4] = { GST_RANK_NONE, GST_RANK_MARGINAL, GST_RANK_SECONDARY, GST_RANK_PRIMARY }; static const char *rank_names[4] = { "none", "marginal", "secondary", "primary" }; int i; int best_i; best_i = 0; for (i = 0; i < 4; i++) { if (rank == ranks[i]) return rank_names[i]; if (abs (rank - ranks[i]) < abs (rank - ranks[best_i])) { best_i = i; } } sprintf (s, "%s %c %d", rank_names[best_i], (rank - ranks[best_i] > 0) ? '+' : '-', abs (ranks[best_i] - rank)); return s; } static void _add_factory_details (GString * json, GstElementFactory * factory) { gchar **keys, **k; gboolean f = TRUE; keys = gst_element_factory_get_metadata_keys (factory); if (keys != NULL) { for (k = keys; *k != NULL; ++k) { gchar *val; gchar *key = *k; val = json_strescape (gst_element_factory_get_metadata (factory, key)); g_string_append_printf (json, "%s\"%s\": \"%s\"", f ? "" : ",", key, val); f = FALSE; g_free (val); } g_strfreev (keys); g_string_append (json, ","); } } static void _add_object_details (GString * json, GObject * object) { GType type; g_string_append (json, "\"hierarchy\": ["); for (type = G_OBJECT_TYPE (object);; type = g_type_parent (type)) { g_string_append_printf (json, "\"%s\"%c", g_type_name (type), type == G_TYPE_OBJECT ? ' ' : ','); if (type == G_TYPE_OBJECT) break; } g_string_append (json, "]"); _add_properties (json, object, G_OBJECT_GET_CLASS (object)); _add_signals (json, object); } static void _add_element_details (GString * json, GstPluginFeature * feature) { GstElement *element = gst_element_factory_create (GST_ELEMENT_FACTORY (feature), NULL); char s[20]; g_assert (element); g_string_append_printf (json, "\"%s\": {" "\"rank\":\"%s\",", GST_OBJECT_NAME (feature), get_rank_name (s, gst_plugin_feature_get_rank (feature))); _add_factory_details (json, GST_ELEMENT_FACTORY (feature)); _add_object_details (json, G_OBJECT (element)); _add_element_pad_templates (json, element, GST_ELEMENT_FACTORY (feature)); g_string_append (json, "}"); } int main (int argc, char *argv[]) { gchar *libfile; GError *error = NULL; GString *json; GstPlugin *plugin; gboolean f = TRUE; GList *features, *tmp; gint i; gboolean first = TRUE; GError *err = NULL; g_assert (argc >= 3); setlocale (LC_ALL, ""); setlocale (LC_NUMERIC, "C"); #ifdef ENABLE_NLS bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); #endif gst_init (NULL, NULL); json = g_string_new ("{"); for (i = 2; i < argc; i++) { gchar *basename, **splitext, *filename; libfile = argv[i]; plugin = gst_plugin_load_file (libfile, &error); if (!plugin) { g_printerr ("%s could not be loaded as a GstPlugin: %s", libfile, error->message ? error->message : "no known reasons"); g_clear_error (&error); continue; } basename = g_filename_display_basename (libfile); splitext = g_strsplit (basename, ".", 2); filename = g_str_has_prefix (splitext[0], "lib") ? &splitext[0][3] : splitext[0]; g_string_append_printf (json, "%s\"%s\": {" "\"description\":\"%s\"," "\"filename\":\"%s\"," "\"source\":\"%s\"," "\"package\":\"%s\"," "\"license\":\"%s\"," "\"url\":\"%s\"," "\"elements\":{", first ? "" : ",", gst_plugin_get_name (plugin), gst_plugin_get_description (plugin), filename, gst_plugin_get_source (plugin), gst_plugin_get_package (plugin), gst_plugin_get_license (plugin), gst_plugin_get_origin (plugin)); g_free (basename); g_strfreev (splitext); first = FALSE; features = gst_registry_get_feature_list_by_plugin (gst_registry_get (), gst_plugin_get_name (plugin)); f = TRUE; for (tmp = features; tmp; tmp = tmp->next) { GstPluginFeature *feature = tmp->data; if (GST_IS_ELEMENT_FACTORY (feature)) { if (!f) g_string_append_printf (json, ","); _add_element_details (json, feature); f = FALSE; } } g_string_append (json, "}, \"tracers\": {"); gst_plugin_feature_list_free (features); f = TRUE; features = gst_registry_get_feature_list_by_plugin (gst_registry_get (), gst_plugin_get_name (plugin)); for (tmp = features; tmp; tmp = tmp->next) { GstPluginFeature *feature = tmp->data; if (GST_IS_TRACER_FACTORY (feature)) { if (!f) g_string_append_printf (json, ","); g_string_append_printf (json, "\"%s\": {}", GST_OBJECT_NAME (feature)); f = FALSE; } } g_string_append (json, "}}"); gst_plugin_feature_list_free (features); } g_string_append_c (json, '}'); if (!g_file_set_contents (argv[1], json->str, -1, &err)) { g_printerr ("Could not set json to %s: %s", argv[1], err->message); g_clear_error (&err); return -1; } g_string_free (json, TRUE); return 0; }