• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #ifdef HAVE_CONFIG_H
2 #  include "config.h"
3 #endif
4 
5 #include <stdlib.h>
6 #include <string.h>
7 #include <locale.h>
8 #include <glib/gprintf.h>
9 #include <gst/gst.h>
10 #include <gio/gio.h>
11 #include "gst/gst-i18n-app.h"
12 
13 static GRegex *cleanup_caps_field = NULL;
14 static void _add_object_details (GString * json, GString * other_types,
15     GHashTable * seen_other_types, GObject * object, GType gtype,
16     GType inst_type);
17 
18 static gchar *
json_strescape(const gchar * str)19 json_strescape (const gchar * str)
20 {
21   const gchar *p;
22   const gchar *end;
23   GString *output;
24   gsize len;
25 
26   if (!str)
27     return g_strdup ("NULL");
28 
29   len = strlen (str);
30   end = str + len;
31   output = g_string_sized_new (len);
32 
33   for (p = str; p < end; p++) {
34     if (*p == '\\' || *p == '"') {
35       g_string_append_c (output, '\\');
36       g_string_append_c (output, *p);
37     } else if (*p == '%') {
38       g_string_append_c (output, '%');
39       g_string_append_c (output, *p);
40     } else if ((*p > 0 && *p < 0x1f) || *p == 0x7f) {
41       switch (*p) {
42         case '\b':
43           g_string_append (output, "\\b");
44           break;
45         case '\f':
46           g_string_append (output, "\\f");
47           break;
48         case '\n':
49           g_string_append (output, "\\n");
50           break;
51         case '\r':
52           g_string_append (output, "\\r");
53           break;
54         case '\t':
55           g_string_append (output, "\\t");
56           break;
57         default:
58           g_string_append_printf (output, "\\u00%02x", (guint) * p);
59           break;
60       }
61     } else {
62       g_string_append_c (output, *p);
63     }
64   }
65 
66   return g_string_free (output, FALSE);
67 }
68 
69 static gchar *
flags_to_string(GFlagsValue * values,guint flags)70 flags_to_string (GFlagsValue * values, guint flags)
71 {
72   GString *s = NULL;
73   guint flags_left, i;
74 
75   /* first look for an exact match and count the number of values */
76   for (i = 0; values[i].value_name != NULL; ++i) {
77     if (values[i].value == flags)
78       return g_strdup (values[i].value_nick);
79   }
80 
81   s = g_string_new (NULL);
82 
83   /* we assume the values are sorted from lowest to highest value */
84   flags_left = flags;
85   while (i > 0) {
86     --i;
87     if (values[i].value != 0
88         && (flags_left & values[i].value) == values[i].value) {
89       if (s->len > 0)
90         g_string_append_c (s, '+');
91       g_string_append (s, values[i].value_nick);
92       flags_left -= values[i].value;
93       if (flags_left == 0)
94         break;
95     }
96   }
97 
98   if (s->len == 0)
99     g_string_assign (s, "(none)");
100 
101   return g_string_free (s, FALSE);
102 }
103 
104 static void
_serialize_flags_default(GString * json,GType gtype,GValue * value)105 _serialize_flags_default (GString * json, GType gtype, GValue * value)
106 {
107   GFlagsValue *values = G_FLAGS_CLASS (g_type_class_ref (gtype))->values;
108   gchar *cur;
109 
110   cur = flags_to_string (values, g_value_get_flags (value));
111   g_string_append_printf (json, ",\"default\": \"%s\"", cur);
112   g_free (cur);
113 }
114 
115 static void
_serialize_flags(GString * json,GType gtype)116 _serialize_flags (GString * json, GType gtype)
117 {
118   GFlagsValue *values = G_FLAGS_CLASS (g_type_class_ref (gtype))->values;
119 
120   g_string_append_printf (json, "%s\"%s\": { "
121       "\"kind\": \"flags\"," "\"values\": [", json->len ? "," : "",
122       g_type_name (gtype));
123 
124   while (values[0].value_name) {
125     gchar *value_name = json_strescape (values[0].value_name);
126     gchar *value_nick = json_strescape (values[0].value_nick);
127 
128     g_string_append_printf (json, "{\"name\": \"%s\","
129         "\"value\": \"0x%08x\","
130         "\"desc\": \"%s\"}", value_nick, values[0].value, value_name);
131     ++values;
132 
133     if (values[0].value_name)
134       g_string_append_c (json, ',');
135 
136     g_free (value_name);
137     g_free (value_nick);
138   }
139 
140   g_string_append (json, "]}");
141 }
142 
143 static void
_serialize_enum_default(GString * json,GType gtype,GValue * value)144 _serialize_enum_default (GString * json, GType gtype, GValue * value)
145 {
146   GEnumValue *values;
147   guint j = 0;
148   gint enum_value;
149   gchar *value_nick = g_strdup ("");
150 
151   values = G_ENUM_CLASS (g_type_class_ref (gtype))->values;
152 
153   enum_value = g_value_get_enum (value);
154   while (values[j].value_name) {
155     if (values[j].value == enum_value) {
156       g_free (value_nick);
157       value_nick = json_strescape (values[j].value_nick);
158       break;
159     }
160 
161     j++;
162   }
163   g_string_append_printf (json, ",\"default\": \"%s (%d)\"", value_nick,
164       enum_value);;
165   g_free (value_nick);
166 }
167 
168 static void
_serialize_enum(GString * json,GType gtype,GstPluginAPIFlags api_flags)169 _serialize_enum (GString * json, GType gtype, GstPluginAPIFlags api_flags)
170 {
171   GEnumValue *values;
172   guint j = 0;
173 
174   values = G_ENUM_CLASS (g_type_class_ref (gtype))->values;
175 
176   g_string_append_printf (json, "%s\"%s\": { "
177       "\"kind\": \"enum\"", json->len ? "," : "", g_type_name (gtype));
178 
179   if (api_flags & GST_PLUGIN_API_FLAG_IGNORE_ENUM_MEMBERS) {
180     g_string_append (json, ",\"ignore-enum-members\": true}");
181   } else {
182     g_string_append (json, ",\"values\": [");
183 
184     while (values[j].value_name) {
185       gchar *value_name = json_strescape (values[j].value_name);
186       gchar *value_nick = json_strescape (values[j].value_nick);
187 
188       g_string_append_printf (json, "{\"name\": \"%s\","
189           "\"value\": \"%d\","
190           "\"desc\": \"%s\"}", value_nick, values[j].value, value_name);
191       j++;
192       if (values[j].value_name)
193         g_string_append_c (json, ',');
194 
195       g_free (value_name);
196       g_free (value_nick);
197     }
198 
199     g_string_append (json, "]}");
200   }
201 }
202 
203 /* @inst_type is used when serializing base classes in the hierarchy:
204  * we don't instantiate the base class, which may very well be abstract,
205  * but instantiate the final type (@inst_type), and use @type to determine
206  * what properties / signals / etc.. we are actually interested in.
207  */
208 static void
_serialize_object(GString * json,GHashTable * seen_other_types,GType gtype,GType inst_type)209 _serialize_object (GString * json, GHashTable * seen_other_types, GType gtype,
210     GType inst_type)
211 {
212   GObject *tmpobj;
213   GString *other_types = NULL;
214 
215   g_string_append_printf (json, "%s\"%s\": { "
216       "\"kind\": \"%s\"", json->len ? "," : "", g_type_name (gtype),
217       G_TYPE_IS_INTERFACE (gtype) ? "interface" : "object");
218 
219   other_types = g_string_new ("");
220   g_string_append_c (json, ',');
221   tmpobj = g_object_new (inst_type, NULL);
222   _add_object_details (json, other_types, seen_other_types, tmpobj, gtype,
223       inst_type);
224   gst_object_unref (tmpobj);
225 
226   g_string_append_c (json, '}');
227 
228   if (other_types && other_types->len) {
229     g_string_append_printf (json, ",%s", other_types->str);
230   }
231   g_string_free (other_types, TRUE);
232 }
233 
234 static void
_add_signals(GString * json,GString * other_types,GHashTable * seen_other_types,GObject * object,GType type)235 _add_signals (GString * json, GString * other_types,
236     GHashTable * seen_other_types, GObject * object, GType type)
237 {
238   gboolean opened = FALSE;
239   guint *signals = NULL;
240   guint nsignals;
241   gint i = 0, j;
242   GstPluginAPIFlags api_flags;
243 
244   signals = g_signal_list_ids (type, &nsignals);
245   for (i = 0; i < nsignals; i++) {
246     GSignalQuery query = { 0, };
247 
248     g_signal_query (signals[i], &query);
249     g_string_append_printf (json,
250         "%s\"%s\" : {", opened ? "," : ",\"signals\": {", query.signal_name);
251 
252     opened = TRUE;
253 
254     g_string_append (json, "\"args\": [");
255     for (j = 0; j < query.n_params; j++) {
256       gchar *arg_name = g_strdup_printf ("arg%u", j);
257       if (j) {
258         g_string_append_c (json, ',');
259       }
260 
261       g_string_append_printf (json, "{ \"name\": \"%s\","
262           "\"type\": \"%s\" }", arg_name, g_type_name (query.param_types[j]));
263 
264       if (!g_hash_table_contains (seen_other_types,
265               g_type_name (query.param_types[j])) &&
266           gst_type_is_plugin_api (query.param_types[j], &api_flags)) {
267         g_hash_table_insert (seen_other_types,
268             (gpointer) g_type_name (query.param_types[j]), NULL);
269 
270         if (g_type_is_a (query.param_types[j], G_TYPE_ENUM)) {
271           _serialize_enum (other_types, query.param_types[j], api_flags);
272         } else if (g_type_is_a (query.param_types[j], G_TYPE_FLAGS)) {
273           _serialize_flags (other_types, query.param_types[j]);
274         } else if (g_type_is_a (query.param_types[j], G_TYPE_OBJECT)) {
275           _serialize_object (other_types, seen_other_types,
276               query.param_types[j], query.param_types[j]);
277         }
278       }
279     }
280     g_string_append_c (json, ']');
281 
282     if (g_type_name (query.return_type) &&
283         !g_hash_table_contains (seen_other_types,
284             g_type_name (query.return_type)) &&
285         gst_type_is_plugin_api (query.return_type, &api_flags)) {
286       g_hash_table_insert (seen_other_types,
287           (gpointer) g_type_name (query.return_type), NULL);
288       if (g_type_is_a (query.return_type, G_TYPE_ENUM)) {
289         _serialize_enum (other_types, query.return_type, api_flags);
290       } else if (g_type_is_a (query.return_type, G_TYPE_FLAGS)) {
291         _serialize_flags (other_types, query.return_type);
292       } else if (g_type_is_a (query.return_type, G_TYPE_OBJECT)) {
293         _serialize_object (other_types, seen_other_types, query.return_type,
294             query.return_type);
295       }
296     }
297 
298     g_string_append_printf (json,
299         ",\"return-type\": \"%s\"", g_type_name (query.return_type));
300 
301     if (query.signal_flags & G_SIGNAL_RUN_FIRST)
302       g_string_append (json, ",\"when\": \"first\"");
303     else if (query.signal_flags & G_SIGNAL_RUN_LAST)
304       g_string_append (json, ",\"when\": \"last\"");
305     else if (query.signal_flags & G_SIGNAL_RUN_CLEANUP)
306       g_string_append (json, ",\"when\": \"cleanup\"");
307 
308     if (query.signal_flags & G_SIGNAL_NO_RECURSE)
309       g_string_append (json, ",\"no-recurse\": true");
310 
311     if (query.signal_flags & G_SIGNAL_DETAILED)
312       g_string_append (json, ",\"detailed\": true");
313 
314     if (query.signal_flags & G_SIGNAL_ACTION)
315       g_string_append (json, ",\"action\": true");
316 
317     if (query.signal_flags & G_SIGNAL_NO_HOOKS)
318       g_string_append (json, ",\"no-hooks\": true");
319 
320     g_string_append_c (json, '}');
321 
322     opened = TRUE;
323   }
324   g_free (signals);
325 
326   if (opened)
327     g_string_append (json, "}");
328 }
329 
330 static void
_add_properties(GString * json,GString * other_types,GHashTable * seen_other_types,GObject * object,GObjectClass * klass,GType type)331 _add_properties (GString * json, GString * other_types,
332     GHashTable * seen_other_types, GObject * object, GObjectClass * klass,
333     GType type)
334 {
335   gchar *tmpstr;
336   guint i, n_props;
337   gboolean opened = FALSE;
338   GParamSpec **specs, *spec;
339   GstPluginAPIFlags api_flags;
340 
341   specs = g_object_class_list_properties (klass, &n_props);
342 
343   for (i = 0; i < n_props; i++) {
344     GValue value = { 0, };
345     const gchar *mutable_str = NULL;
346     spec = specs[i];
347 
348     if (spec->owner_type != type)
349       continue;
350 
351     g_value_init (&value, spec->value_type);
352     if (object && ! !(spec->flags & G_PARAM_READABLE) &&
353         !(spec->flags & GST_PARAM_DOC_SHOW_DEFAULT)) {
354       g_object_get_property (G_OBJECT (object), spec->name, &value);
355     } else {
356       /* if we can't read the property value, assume it's set to the default
357        * (which might not be entirely true for sub-classes, but that's an
358        * unlikely corner-case anyway) */
359       g_param_value_set_default (spec, &value);
360     }
361 
362     if (!opened)
363       g_string_append (json, ",\"properties\": {");
364 
365     if ((spec->flags & GST_PARAM_MUTABLE_PLAYING)) {
366       mutable_str = "\"playing\"";
367     } else if ((spec->flags & GST_PARAM_MUTABLE_PAUSED)) {
368       mutable_str = "\"paused\"";
369     } else if ((spec->flags & GST_PARAM_MUTABLE_READY)) {
370       mutable_str = "\"ready\"";
371     } else {
372       mutable_str = "\"null\"";
373     }
374 
375     tmpstr = json_strescape (g_param_spec_get_blurb (spec));
376     g_string_append_printf (json,
377         "%s"
378         "\"%s\": {"
379         "\"construct-only\": %s,"
380         "\"construct\": %s,"
381         "\"readable\": %s,"
382         "\"writable\": %s,"
383         "\"blurb\": \"%s\","
384         "\"controllable\": %s,"
385         "\"conditionally-available\": %s,"
386         "\"mutable\": %s,"
387         "\"type\": \"%s\"",
388         opened ? "," : "",
389         spec->name,
390         spec->flags & G_PARAM_CONSTRUCT_ONLY ? "true" : "false",
391         spec->flags & G_PARAM_CONSTRUCT ? "true" : "false",
392         spec->flags & G_PARAM_READABLE ? "true" : "false",
393         spec->flags & G_PARAM_WRITABLE ? "true" : "false", tmpstr,
394         spec->flags & GST_PARAM_CONTROLLABLE ? "true" : "false",
395         spec->flags & GST_PARAM_CONDITIONALLY_AVAILABLE ? "true" : "false",
396         mutable_str, g_type_name (G_PARAM_SPEC_VALUE_TYPE (spec)));
397     g_free (tmpstr);
398 
399     if (!g_hash_table_contains (seen_other_types,
400             g_type_name (spec->value_type))
401         && gst_type_is_plugin_api (spec->value_type, &api_flags)) {
402       g_hash_table_insert (seen_other_types,
403           (gpointer) g_type_name (spec->value_type), NULL);
404       if (G_IS_PARAM_SPEC_ENUM (spec)) {
405         _serialize_enum (other_types, spec->value_type, api_flags);
406       } else if (G_IS_PARAM_SPEC_FLAGS (spec)) {
407         _serialize_flags (other_types, spec->value_type);
408       } else if (G_IS_PARAM_SPEC_OBJECT (spec)) {
409         _serialize_object (other_types, seen_other_types, spec->value_type,
410             spec->value_type);
411       }
412     }
413 
414     switch (G_VALUE_TYPE (&value)) {
415       case G_TYPE_STRING:
416       {
417         const char *string_val = g_value_get_string (&value);
418         gchar *tmpstr = json_strescape (string_val);
419 
420         g_string_append_printf (json, ",\"default\": \"%s\"", tmpstr);;
421         g_free (tmpstr);
422         break;
423       }
424       case G_TYPE_BOOLEAN:
425       {
426         gboolean bool_val = g_value_get_boolean (&value);
427 
428         g_string_append_printf (json, ",\"default\": \"%s\"",
429             bool_val ? "true" : "false");
430         break;
431       }
432       case G_TYPE_ULONG:
433       {
434         GParamSpecULong *pulong = G_PARAM_SPEC_ULONG (spec);
435 
436         g_string_append_printf (json,
437             ",\"default\": \"%lu\""
438             ",\"min\": \"%lu\""
439             ",\"max\": \"%lu\"",
440             g_value_get_ulong (&value), pulong->minimum, pulong->maximum);
441 
442         GST_ERROR_OBJECT (object,
443             "property '%s' of type ulong: consider changing to " "uint/uint64",
444             g_param_spec_get_name (spec));
445         break;
446       }
447       case G_TYPE_LONG:
448       {
449         GParamSpecLong *plong = G_PARAM_SPEC_LONG (spec);
450 
451         g_string_append_printf (json,
452             ",\"default\": \"%ld\""
453             ",\"min\": \"%ld\""
454             ",\"max\": \"%ld\"",
455             g_value_get_long (&value), plong->minimum, plong->maximum);
456 
457         GST_ERROR_OBJECT (object,
458             "property '%s' of type long: consider changing to " "int/int64",
459             g_param_spec_get_name (spec));
460         break;
461       }
462       case G_TYPE_UINT:
463       {
464         GParamSpecUInt *puint = G_PARAM_SPEC_UINT (spec);
465 
466         g_string_append_printf (json,
467             ",\"default\": \"%d\""
468             ",\"min\": \"%d\""
469             ",\"max\": \"%d\"",
470             g_value_get_uint (&value), puint->minimum, puint->maximum);
471         break;
472       }
473       case G_TYPE_INT:
474       {
475         GParamSpecInt *pint = G_PARAM_SPEC_INT (spec);
476 
477         g_string_append_printf (json,
478             ",\"default\": \"%d\""
479             ",\"min\": \"%d\""
480             ",\"max\": \"%d\"",
481             g_value_get_int (&value), pint->minimum, pint->maximum);
482         break;
483       }
484       case G_TYPE_UINT64:
485       {
486         GParamSpecUInt64 *puint64 = G_PARAM_SPEC_UINT64 (spec);
487 
488         g_string_append_printf (json,
489             ",\"default\": \"%" G_GUINT64_FORMAT
490             "\",\"min\": \"%" G_GUINT64_FORMAT
491             "\",\"max\": \"%" G_GUINT64_FORMAT "\"",
492             g_value_get_uint64 (&value), puint64->minimum, puint64->maximum);
493         break;
494       }
495       case G_TYPE_INT64:
496       {
497         GParamSpecInt64 *pint64 = G_PARAM_SPEC_INT64 (spec);
498 
499         g_string_append_printf (json,
500             ",\"default\": \"%" G_GUINT64_FORMAT
501             "\",\"min\": \"%" G_GINT64_FORMAT
502             "\",\"max\": \"%" G_GINT64_FORMAT "\"",
503             g_value_get_int64 (&value), pint64->minimum, pint64->maximum);
504         break;
505       }
506       case G_TYPE_FLOAT:
507       {
508         GParamSpecFloat *pfloat = G_PARAM_SPEC_FLOAT (spec);
509 
510         g_string_append_printf (json,
511             ",\"default\": \"%g\""
512             ",\"min\": \"%g\""
513             ",\"max\": \"%g\"",
514             g_value_get_float (&value), pfloat->minimum, pfloat->maximum);
515         break;
516       }
517       case G_TYPE_DOUBLE:
518       {
519         GParamSpecDouble *pdouble = G_PARAM_SPEC_DOUBLE (spec);
520 
521         g_string_append_printf (json,
522             ",\"default\": \"%g\""
523             ",\"min\": \"%g\""
524             ",\"max\": \"%g\"",
525             g_value_get_double (&value), pdouble->minimum, pdouble->maximum);
526         break;
527       }
528       case G_TYPE_CHAR:
529       case G_TYPE_UCHAR:
530         GST_ERROR_OBJECT (object,
531             "property '%s' of type char: consider changing to " "int/string",
532             g_param_spec_get_name (spec));
533         /* fall through */
534       default:
535         if (spec->value_type == GST_TYPE_CAPS) {
536           const GstCaps *caps = gst_value_get_caps (&value);
537 
538           if (caps) {
539             gchar *capsstr = gst_caps_to_string (caps);
540             gchar *tmpcapsstr = json_strescape (capsstr);
541 
542             g_string_append_printf (json, ",\"default\": \"%s\"", tmpcapsstr);
543             g_free (capsstr);
544             g_free (tmpcapsstr);
545           }
546         } else if (G_IS_PARAM_SPEC_BOXED (spec)) {
547           if (spec->value_type == GST_TYPE_STRUCTURE) {
548             const GstStructure *s = gst_value_get_structure (&value);
549             if (s) {
550               gchar *str = gst_structure_to_string (s);
551               gchar *tmpstr = json_strescape (str);
552 
553               g_string_append_printf (json, ",\"default\": \"%s\"", tmpstr);
554               g_free (str);
555               g_free (tmpstr);
556             }
557           }
558         } else if (GST_IS_PARAM_SPEC_FRACTION (spec)) {
559           GstParamSpecFraction *pfraction = GST_PARAM_SPEC_FRACTION (spec);
560 
561           g_string_append_printf (json,
562               ",\"default\": \"%d/%d\""
563               ",\"min\": \"%d/%d\""
564               ",\"max\": \"%d/%d\"",
565               gst_value_get_fraction_numerator (&value),
566               gst_value_get_fraction_denominator (&value),
567               pfraction->min_num, pfraction->min_den,
568               pfraction->max_num, pfraction->max_den);
569         } else if (G_IS_PARAM_SPEC_ENUM (spec)) {
570           _serialize_enum_default (json, spec->value_type, &value);
571         } else if (G_IS_PARAM_SPEC_FLAGS (spec)) {
572           _serialize_flags_default (json, spec->value_type, &value);
573         }
574         break;
575     }
576 
577     g_string_append_c (json, '}');
578 
579 
580     opened = TRUE;
581   }
582 
583   if (opened)
584     g_string_append (json, "}");
585 
586 }
587 
588 static gboolean
print_field(GQuark field,const GValue * value,GString * jcaps)589 print_field (GQuark field, const GValue * value, GString * jcaps)
590 {
591   gchar *tmp, *str = gst_value_serialize (value);
592 
593   if (!g_strcmp0 (g_quark_to_string (field), "format") ||
594       !g_strcmp0 (g_quark_to_string (field), "rate")) {
595     if (!cleanup_caps_field)
596       cleanup_caps_field = g_regex_new ("\\(string\\)|\\(rate\\)", 0, 0, NULL);
597 
598     tmp = str;
599     str = g_regex_replace (cleanup_caps_field, str, -1, 0, "", 0, NULL);;
600     g_free (tmp);
601   }
602 
603   g_string_append_printf (jcaps, "%15s: %s\n", g_quark_to_string (field), str);
604   g_free (str);
605   return TRUE;
606 }
607 
608 static gchar *
_build_caps(const GstCaps * caps)609 _build_caps (const GstCaps * caps)
610 {
611   guint i;
612   gchar *res;
613   GString *jcaps = g_string_new (NULL);
614 
615   if (gst_caps_is_any (caps)) {
616     g_string_append (jcaps, "ANY");
617     return g_string_free (jcaps, FALSE);
618   }
619 
620   if (gst_caps_is_empty (caps)) {
621     g_string_append (jcaps, "EMPTY");
622     return g_string_free (jcaps, FALSE);
623   }
624 
625   for (i = 0; i < gst_caps_get_size (caps); i++) {
626     GstStructure *structure = gst_caps_get_structure (caps, i);
627     GstCapsFeatures *features = gst_caps_get_features (caps, i);
628 
629     if (features && (gst_caps_features_is_any (features) ||
630             !gst_caps_features_is_equal (features,
631                 GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY))) {
632       gchar *features_string = gst_caps_features_to_string (features);
633 
634       g_string_append_printf (jcaps, "%s%s(%s):\n",
635           i ? "\n" : "", gst_structure_get_name (structure), features_string);
636       g_free (features_string);
637     } else {
638       g_string_append_printf (jcaps, "%s:\n",
639           gst_structure_get_name (structure));
640     }
641     gst_structure_foreach (structure, (GstStructureForeachFunc) print_field,
642         jcaps);
643   }
644 
645   res = json_strescape (jcaps->str);
646   g_string_free (jcaps, TRUE);
647 
648   return res;
649 }
650 
651 static void
_add_element_pad_templates(GString * json,GString * other_types,GHashTable * seen_other_types,GstElement * element,GstElementFactory * factory)652 _add_element_pad_templates (GString * json, GString * other_types,
653     GHashTable * seen_other_types, GstElement * element,
654     GstElementFactory * factory)
655 {
656   gboolean opened = FALSE;
657   const GList *pads;
658   GstStaticPadTemplate *padtemplate;
659   GRegex *re = g_regex_new ("%", 0, 0, NULL);
660   GstPluginAPIFlags api_flags;
661 
662   pads = gst_element_factory_get_static_pad_templates (factory);
663   while (pads) {
664     GstCaps *documentation_caps;
665     gchar *name, *caps;
666     GType pad_type;
667     GstPadTemplate *tmpl;
668     padtemplate = (GstStaticPadTemplate *) (pads->data);
669     pads = g_list_next (pads);
670 
671     tmpl = gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (element),
672         padtemplate->name_template);
673 
674     name = g_regex_replace (re, padtemplate->name_template,
675         -1, 0, "%%", 0, NULL);;
676     documentation_caps = gst_pad_template_get_documentation_caps (tmpl);
677     caps = _build_caps (documentation_caps);
678     gst_caps_replace (&documentation_caps, NULL);
679     g_string_append_printf (json, "%s"
680         "\"%s\": {"
681         "\"caps\": \"%s\","
682         "\"direction\": \"%s\","
683         "\"presence\": \"%s\"",
684         opened ? "," : ",\"pad-templates\": {",
685         name, caps,
686         padtemplate->direction ==
687         GST_PAD_SRC ? "src" : padtemplate->direction ==
688         GST_PAD_SINK ? "sink" : "unknown",
689         padtemplate->presence ==
690         GST_PAD_ALWAYS ? "always" : padtemplate->presence ==
691         GST_PAD_SOMETIMES ? "sometimes" : padtemplate->presence ==
692         GST_PAD_REQUEST ? "request" : "unknown");
693     opened = TRUE;
694     g_free (name);
695 
696     pad_type = GST_PAD_TEMPLATE_GTYPE (tmpl);
697     if (pad_type != G_TYPE_NONE && pad_type != GST_TYPE_PAD) {
698       g_string_append_printf (json, ", \"type\": \"%s\"",
699           g_type_name (pad_type));
700 
701       if (!g_hash_table_contains (seen_other_types, g_type_name (pad_type))
702           && gst_type_is_plugin_api (pad_type, &api_flags)) {
703         g_hash_table_insert (seen_other_types,
704             (gpointer) g_type_name (pad_type), NULL);
705         _serialize_object (other_types, seen_other_types, pad_type, pad_type);
706       }
707     }
708     g_string_append_c (json, '}');
709   }
710   if (opened)
711     g_string_append_c (json, '}');
712 
713   g_regex_unref (re);
714 }
715 
716 static const char *
get_rank_name(char * s,gint rank)717 get_rank_name (char *s, gint rank)
718 {
719   static const int ranks[4] = {
720     GST_RANK_NONE, GST_RANK_MARGINAL, GST_RANK_SECONDARY, GST_RANK_PRIMARY
721   };
722   static const char *rank_names[4] = { "none", "marginal", "secondary",
723     "primary"
724   };
725   int i;
726   int best_i;
727 
728   best_i = 0;
729   for (i = 0; i < 4; i++) {
730     if (rank == ranks[i])
731       return rank_names[i];
732     if (abs (rank - ranks[i]) < abs (rank - ranks[best_i])) {
733       best_i = i;
734     }
735   }
736 
737   sprintf (s, "%s %c %d", rank_names[best_i],
738       (rank - ranks[best_i] > 0) ? '+' : '-', abs (ranks[best_i] - rank));
739 
740   return s;
741 }
742 
743 static void
_add_factory_details(GString * json,GstElementFactory * factory)744 _add_factory_details (GString * json, GstElementFactory * factory)
745 {
746   gchar **keys, **k;
747   gboolean f = TRUE;
748 
749   keys = gst_element_factory_get_metadata_keys (factory);
750   if (keys != NULL) {
751     for (k = keys; *k != NULL; ++k) {
752       gchar *val;
753       gchar *key = *k;
754 
755       val = json_strescape (gst_element_factory_get_metadata (factory, key));
756       g_string_append_printf (json, "%s\"%s\": \"%s\"", f ? "" : ",", key, val);
757       f = FALSE;
758       g_free (val);
759     }
760     g_strfreev (keys);
761     g_string_append (json, ",");
762   }
763 }
764 
765 static void
_add_object_details(GString * json,GString * other_types,GHashTable * seen_other_types,GObject * object,GType type,GType inst_type)766 _add_object_details (GString * json, GString * other_types,
767     GHashTable * seen_other_types, GObject * object, GType type,
768     GType inst_type)
769 {
770   GType *interfaces;
771   guint n_interfaces;
772   GType ptype = type;
773 
774   g_string_append (json, "\"hierarchy\": [");
775 
776   for (;; ptype = g_type_parent (ptype)) {
777     g_string_append_printf (json, "\"%s\"%c", g_type_name (ptype),
778         ((ptype == G_TYPE_OBJECT || ptype == G_TYPE_INTERFACE) ? ' ' : ','));
779 
780     if (!g_hash_table_contains (seen_other_types, g_type_name (ptype))
781         && gst_type_is_plugin_api (ptype, NULL)) {
782       g_hash_table_insert (seen_other_types, (gpointer) g_type_name (ptype),
783           NULL);
784       _serialize_object (other_types, seen_other_types, ptype, inst_type);
785     }
786 
787     if (ptype == G_TYPE_OBJECT || ptype == G_TYPE_INTERFACE)
788       break;
789   }
790   g_string_append (json, "]");
791 
792   interfaces = g_type_interfaces (type, &n_interfaces);
793   if (n_interfaces) {
794     GType *iface;
795 
796     g_string_append (json, ",\"interfaces\": [");
797     for (iface = interfaces; *iface; iface++, n_interfaces--) {
798       g_string_append_printf (json, "\"%s\"%c", g_type_name (*iface),
799           n_interfaces > 1 ? ',' : ' ');
800 
801       if (!g_hash_table_contains (seen_other_types, g_type_name (*iface))
802           && gst_type_is_plugin_api (*iface, NULL)) {
803         g_hash_table_insert (seen_other_types, (gpointer) g_type_name (*iface),
804             NULL);
805         _serialize_object (other_types, seen_other_types, *iface, inst_type);
806       }
807     }
808 
809     g_string_append (json, "]");
810     g_free (interfaces);
811   }
812 
813   _add_properties (json, other_types, seen_other_types, object,
814       G_OBJECT_GET_CLASS (object), type);
815   _add_signals (json, other_types, seen_other_types, object, type);
816 }
817 
818 static void
_add_element_details(GString * json,GString * other_types,GHashTable * seen_other_types,GstPluginFeature * feature)819 _add_element_details (GString * json, GString * other_types,
820     GHashTable * seen_other_types, GstPluginFeature * feature)
821 {
822   GstElement *element =
823       gst_element_factory_create (GST_ELEMENT_FACTORY (feature), NULL);
824   char s[20];
825 
826   g_assert (element);
827 
828   g_string_append_printf (json,
829       "\"%s\": {"
830       "\"rank\":\"%s\",",
831       GST_OBJECT_NAME (feature),
832       get_rank_name (s, gst_plugin_feature_get_rank (feature)));
833 
834   _add_factory_details (json, GST_ELEMENT_FACTORY (feature));
835   _add_object_details (json, other_types, seen_other_types, G_OBJECT (element),
836       G_OBJECT_TYPE (element), G_OBJECT_TYPE (element));
837 
838   _add_element_pad_templates (json, other_types, seen_other_types, element,
839       GST_ELEMENT_FACTORY (feature));
840 
841   g_string_append (json, "}");
842 }
843 
844 int
main(int argc,char * argv[])845 main (int argc, char *argv[])
846 {
847   gchar *libfile;
848   GError *error = NULL;
849   GString *json;
850   GString *other_types;
851   GHashTable *seen_other_types;
852   GstPlugin *plugin;
853   gboolean f = TRUE;
854   GList *features, *tmp;
855   gint i;
856   gboolean first = TRUE;
857   GError *err = NULL;
858 
859   g_assert (argc >= 3);
860 
861   setlocale (LC_ALL, "");
862   setlocale (LC_NUMERIC, "C");
863 
864 #ifdef ENABLE_NLS
865   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
866   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
867   textdomain (GETTEXT_PACKAGE);
868 #endif
869 
870   gst_init (NULL, NULL);
871 
872   json = g_string_new ("{");
873   for (i = 2; i < argc; i++) {
874     gchar *basename, **splitext, *filename;
875     libfile = argv[i];
876     plugin = gst_plugin_load_file (libfile, &error);
877     if (!plugin) {
878       g_printerr ("%s could not be loaded as a GstPlugin: %s", libfile,
879           error->message ? error->message : "no known reasons");
880       g_clear_error (&error);
881 
882       continue;
883     }
884 
885     other_types = g_string_new ("");
886     seen_other_types = g_hash_table_new (g_str_hash, g_str_equal);
887 
888     basename = g_filename_display_basename (libfile);
889     splitext = g_strsplit (basename, ".", 2);
890     filename =
891         g_str_has_prefix (splitext[0], "lib") ? &splitext[0][3] : splitext[0];
892     g_string_append_printf (json,
893         "%s\"%s\": {"
894         "\"description\":\"%s\","
895         "\"filename\":\"%s\","
896         "\"source\":\"%s\","
897         "\"package\":\"%s\","
898         "\"license\":\"%s\","
899         "\"url\":\"%s\","
900         "\"elements\":{",
901         first ? "" : ",",
902         gst_plugin_get_name (plugin),
903         gst_plugin_get_description (plugin),
904         filename,
905         gst_plugin_get_source (plugin),
906         gst_plugin_get_package (plugin),
907         gst_plugin_get_license (plugin), gst_plugin_get_origin (plugin));
908     g_free (basename);
909     g_strfreev (splitext);
910     first = FALSE;
911 
912     features =
913         gst_registry_get_feature_list_by_plugin (gst_registry_get (),
914         gst_plugin_get_name (plugin));
915 
916     f = TRUE;
917     for (tmp = features; tmp; tmp = tmp->next) {
918       GstPluginFeature *feature = tmp->data;
919       if (GST_IS_ELEMENT_FACTORY (feature)) {
920         GstElementFactory *factory = GST_ELEMENT_FACTORY (feature);
921         if (gst_element_factory_get_skip_documentation (factory))
922           continue;
923 
924         if (!f)
925           g_string_append_printf (json, ",");
926         _add_element_details (json, other_types, seen_other_types, feature);
927         f = FALSE;
928       }
929     }
930 
931     g_string_append (json, "}, \"tracers\": {");
932     gst_plugin_feature_list_free (features);
933 
934     f = TRUE;
935     features =
936         gst_registry_get_feature_list_by_plugin (gst_registry_get (),
937         gst_plugin_get_name (plugin));
938     for (tmp = features; tmp; tmp = tmp->next) {
939       GstPluginFeature *feature = tmp->data;
940 
941       if (GST_IS_TRACER_FACTORY (feature)) {
942         if (!f)
943           g_string_append_printf (json, ",");
944         g_string_append_printf (json, "\"%s\": {}", GST_OBJECT_NAME (feature));
945         f = FALSE;
946       }
947     }
948     g_string_append_printf (json, "}, \"other-types\": {%s}}",
949         other_types->str);
950     gst_plugin_feature_list_free (features);
951 
952     g_hash_table_unref (seen_other_types);
953     g_string_free (other_types, TRUE);
954   }
955 
956   g_string_append_c (json, '}');
957   if (!g_file_set_contents (argv[1], json->str, -1, &err)) {
958     g_printerr ("Could not set json to %s: %s", argv[1], err->message);
959     g_clear_error (&err);
960 
961     return -1;
962   }
963   g_string_free (json, TRUE);
964 
965   return 0;
966 }
967