f8404162d4a7ef9c46c2911894c8034152c6ff87
[platform/upstream/gstreamer.git] / subprojects / gstreamer / docs / gst-hotdoc-plugins-scanner.c
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 <glib/gi18n.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 *
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 *
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
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
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
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
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
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
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
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
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 *
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
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 *
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
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
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
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   if (!element)
827     g_error ("Couldn't not make `%s`", GST_OBJECT_NAME (feature));
828
829   g_string_append_printf (json,
830       "\"%s\": {"
831       "\"rank\":\"%s\",",
832       GST_OBJECT_NAME (feature),
833       get_rank_name (s, gst_plugin_feature_get_rank (feature)));
834
835   _add_factory_details (json, GST_ELEMENT_FACTORY (feature));
836   _add_object_details (json, other_types, seen_other_types, G_OBJECT (element),
837       G_OBJECT_TYPE (element), G_OBJECT_TYPE (element));
838
839   _add_element_pad_templates (json, other_types, seen_other_types, element,
840       GST_ELEMENT_FACTORY (feature));
841
842   g_string_append (json, "}");
843 }
844
845 int
846 main (int argc, char *argv[])
847 {
848   gchar *libfile;
849   GError *error = NULL;
850   GString *json;
851   GString *other_types;
852   GHashTable *seen_other_types;
853   GstPlugin *plugin;
854   gboolean f = TRUE;
855   GList *features, *tmp;
856   gint i;
857   gboolean first = TRUE;
858   GError *err = NULL;
859
860   g_assert (argc >= 3);
861
862   setlocale (LC_ALL, "");
863   setlocale (LC_NUMERIC, "C");
864
865 #ifdef ENABLE_NLS
866   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
867   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
868   textdomain (GETTEXT_PACKAGE);
869 #endif
870
871   gst_init (NULL, NULL);
872
873   json = g_string_new ("{");
874   for (i = 2; i < argc; i++) {
875     gchar *basename, **splitext, *filename;
876     libfile = argv[i];
877     plugin = gst_plugin_load_file (libfile, &error);
878     if (!plugin) {
879       g_printerr ("%s could not be loaded as a GstPlugin: %s", libfile,
880           error->message ? error->message : "no known reasons");
881       g_clear_error (&error);
882
883       continue;
884     }
885
886     other_types = g_string_new ("");
887     seen_other_types = g_hash_table_new (g_str_hash, g_str_equal);
888
889     basename = g_filename_display_basename (libfile);
890     splitext = g_strsplit (basename, ".", 2);
891     filename =
892         g_str_has_prefix (splitext[0], "lib") ? &splitext[0][3] : splitext[0];
893     g_string_append_printf (json,
894         "%s\"%s\": {"
895         "\"description\":\"%s\","
896         "\"filename\":\"%s\","
897         "\"source\":\"%s\","
898         "\"package\":\"%s\","
899         "\"license\":\"%s\","
900         "\"url\":\"%s\","
901         "\"elements\":{",
902         first ? "" : ",",
903         gst_plugin_get_name (plugin),
904         gst_plugin_get_description (plugin),
905         filename,
906         gst_plugin_get_source (plugin),
907         gst_plugin_get_package (plugin),
908         gst_plugin_get_license (plugin), gst_plugin_get_origin (plugin));
909     g_free (basename);
910     g_strfreev (splitext);
911     first = FALSE;
912
913     features =
914         gst_registry_get_feature_list_by_plugin (gst_registry_get (),
915         gst_plugin_get_name (plugin));
916
917     f = TRUE;
918     for (tmp = features; tmp; tmp = tmp->next) {
919       GstPluginFeature *feature = tmp->data;
920       if (GST_IS_ELEMENT_FACTORY (feature)) {
921         GstElementFactory *factory = GST_ELEMENT_FACTORY (feature);
922         if (gst_element_factory_get_skip_documentation (factory))
923           continue;
924
925         if (!f)
926           g_string_append_printf (json, ",");
927         _add_element_details (json, other_types, seen_other_types, feature);
928         f = FALSE;
929       }
930     }
931
932     g_string_append (json, "}, \"tracers\": {");
933     gst_plugin_feature_list_free (features);
934
935     f = TRUE;
936     features =
937         gst_registry_get_feature_list_by_plugin (gst_registry_get (),
938         gst_plugin_get_name (plugin));
939     for (tmp = features; tmp; tmp = tmp->next) {
940       GstPluginFeature *feature = tmp->data;
941
942       if (GST_IS_TRACER_FACTORY (feature)) {
943         if (!f)
944           g_string_append_printf (json, ",");
945         g_string_append_printf (json, "\"%s\": {}", GST_OBJECT_NAME (feature));
946         f = FALSE;
947       }
948     }
949     g_string_append_printf (json, "}, \"other-types\": {%s}}",
950         other_types->str);
951     gst_plugin_feature_list_free (features);
952
953     g_hash_table_unref (seen_other_types);
954     g_string_free (other_types, TRUE);
955   }
956
957   g_string_append_c (json, '}');
958   if (!g_file_set_contents (argv[1], json->str, -1, &err)) {
959     g_printerr ("Could not set json to %s: %s", argv[1], err->message);
960     g_clear_error (&err);
961
962     return -1;
963   }
964   g_string_free (json, TRUE);
965
966   return 0;
967 }