discoverer: extract helper to print common stream info
[platform/upstream/gstreamer.git] / tools / gst-discoverer.c
1 /* GStreamer
2  * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <stdlib.h>
25 #include <glib.h>
26 #include <gst/gst.h>
27 #include <gst/pbutils/pbutils.h>
28
29 static gboolean async = FALSE;
30 static gboolean show_toc = FALSE;
31 static gboolean verbose = FALSE;
32
33 typedef struct
34 {
35   GstDiscoverer *dc;
36   int argc;
37   char **argv;
38 } PrivStruct;
39
40 static void
41 my_g_string_append_printf (GString * str, int depth, const gchar * format, ...)
42 {
43   va_list args;
44
45   while (depth-- > 0) {
46     g_string_append (str, "  ");
47   }
48
49   va_start (args, format);
50   g_string_append_vprintf (str, format, args);
51   va_end (args);
52 }
53
54 static void
55 gst_stream_information_to_string (GstDiscovererStreamInfo * info, GString * s,
56     gint depth)
57 {
58   gchar *tmp;
59   GstCaps *caps;
60   const GstStructure *misc;
61
62   my_g_string_append_printf (s, depth, "Codec:\n");
63   caps = gst_discoverer_stream_info_get_caps (info);
64   tmp = gst_caps_to_string (caps);
65   gst_caps_unref (caps);
66   my_g_string_append_printf (s, depth, "  %s\n", tmp);
67   g_free (tmp);
68
69   my_g_string_append_printf (s, depth, "Additional info:\n");
70   if ((misc = gst_discoverer_stream_info_get_misc (info))) {
71     tmp = gst_structure_to_string (misc);
72     my_g_string_append_printf (s, depth, "  %s\n", tmp);
73     g_free (tmp);
74   } else {
75     my_g_string_append_printf (s, depth, "  None\n");
76   }
77
78   my_g_string_append_printf (s, depth, "Stream ID: %s\n",
79       gst_discoverer_stream_info_get_stream_id (info));
80 }
81
82 static gchar *
83 gst_stream_audio_information_to_string (GstDiscovererStreamInfo * info,
84     gint depth)
85 {
86   GstDiscovererAudioInfo *audio_info;
87   GString *s;
88   gchar *tmp;
89   const gchar *ctmp;
90   int len = 400;
91   const GstTagList *tags;
92
93   g_return_val_if_fail (info != NULL, NULL);
94
95   s = g_string_sized_new (len);
96
97   gst_stream_information_to_string (info, s, depth);
98
99   audio_info = (GstDiscovererAudioInfo *) info;
100   ctmp = gst_discoverer_audio_info_get_language (audio_info);
101   my_g_string_append_printf (s, depth, "Language: %s\n",
102       ctmp ? ctmp : "<unknown>");
103   my_g_string_append_printf (s, depth, "Channels: %u\n",
104       gst_discoverer_audio_info_get_channels (audio_info));
105   my_g_string_append_printf (s, depth, "Sample rate: %u\n",
106       gst_discoverer_audio_info_get_sample_rate (audio_info));
107   my_g_string_append_printf (s, depth, "Depth: %u\n",
108       gst_discoverer_audio_info_get_depth (audio_info));
109
110   my_g_string_append_printf (s, depth, "Bitrate: %u\n",
111       gst_discoverer_audio_info_get_bitrate (audio_info));
112   my_g_string_append_printf (s, depth, "Max bitrate: %u\n",
113       gst_discoverer_audio_info_get_max_bitrate (audio_info));
114
115   my_g_string_append_printf (s, depth, "Tags:\n");
116   tags = gst_discoverer_stream_info_get_tags (info);
117   if (tags) {
118     tmp = gst_tag_list_to_string (tags);
119     my_g_string_append_printf (s, depth, "  %s\n", tmp);
120     g_free (tmp);
121   } else {
122     my_g_string_append_printf (s, depth, "  None\n");
123   }
124   if (verbose)
125     my_g_string_append_printf (s, depth, "\n");
126
127   return g_string_free (s, FALSE);
128 }
129
130 static gchar *
131 gst_stream_video_information_to_string (GstDiscovererStreamInfo * info,
132     gint depth)
133 {
134   GstDiscovererVideoInfo *video_info;
135   GString *s;
136   gchar *tmp;
137   int len = 500;
138   const GstTagList *tags;
139
140   g_return_val_if_fail (info != NULL, NULL);
141
142   s = g_string_sized_new (len);
143
144   gst_stream_information_to_string (info, s, depth);
145
146   video_info = (GstDiscovererVideoInfo *) info;
147   my_g_string_append_printf (s, depth, "Width: %u\n",
148       gst_discoverer_video_info_get_width (video_info));
149   my_g_string_append_printf (s, depth, "Height: %u\n",
150       gst_discoverer_video_info_get_height (video_info));
151   my_g_string_append_printf (s, depth, "Depth: %u\n",
152       gst_discoverer_video_info_get_depth (video_info));
153
154   my_g_string_append_printf (s, depth, "Frame rate: %u/%u\n",
155       gst_discoverer_video_info_get_framerate_num (video_info),
156       gst_discoverer_video_info_get_framerate_denom (video_info));
157
158   my_g_string_append_printf (s, depth, "Pixel aspect ratio: %u/%u\n",
159       gst_discoverer_video_info_get_par_num (video_info),
160       gst_discoverer_video_info_get_par_denom (video_info));
161
162   my_g_string_append_printf (s, depth, "Interlaced: %s\n",
163       gst_discoverer_video_info_is_interlaced (video_info) ? "true" : "false");
164
165   my_g_string_append_printf (s, depth, "Bitrate: %u\n",
166       gst_discoverer_video_info_get_bitrate (video_info));
167   my_g_string_append_printf (s, depth, "Max bitrate: %u\n",
168       gst_discoverer_video_info_get_max_bitrate (video_info));
169
170   my_g_string_append_printf (s, depth, "Tags:\n");
171   tags = gst_discoverer_stream_info_get_tags (info);
172   if (tags) {
173     tmp = gst_tag_list_to_string (tags);
174     my_g_string_append_printf (s, depth, "  %s\n", tmp);
175     g_free (tmp);
176   } else {
177     my_g_string_append_printf (s, depth, "  None\n");
178   }
179   if (verbose)
180     my_g_string_append_printf (s, depth, "\n");
181
182   return g_string_free (s, FALSE);
183 }
184
185 static gchar *
186 gst_stream_subtitle_information_to_string (GstDiscovererStreamInfo * info,
187     gint depth)
188 {
189   GstDiscovererSubtitleInfo *subtitle_info;
190   GString *s;
191   gchar *tmp;
192   const gchar *ctmp;
193   int len = 400;
194   const GstTagList *tags;
195
196   g_return_val_if_fail (info != NULL, NULL);
197
198   s = g_string_sized_new (len);
199
200   gst_stream_information_to_string (info, s, depth);
201
202   subtitle_info = (GstDiscovererSubtitleInfo *) info;
203   ctmp = gst_discoverer_subtitle_info_get_language (subtitle_info);
204   my_g_string_append_printf (s, depth, "Language: %s\n",
205       ctmp ? ctmp : "<unknown>");
206
207   my_g_string_append_printf (s, depth, "Tags:\n");
208   tags = gst_discoverer_stream_info_get_tags (info);
209   if (tags) {
210     tmp = gst_tag_list_to_string (tags);
211     my_g_string_append_printf (s, depth, "  %s\n", tmp);
212     g_free (tmp);
213   } else {
214     my_g_string_append_printf (s, depth, "  None\n");
215   }
216   if (verbose)
217     my_g_string_append_printf (s, depth, "\n");
218
219   return g_string_free (s, FALSE);
220 }
221
222 static void
223 print_stream_info (GstDiscovererStreamInfo * info, void *depth)
224 {
225   gchar *desc = NULL;
226   GstCaps *caps;
227
228   caps = gst_discoverer_stream_info_get_caps (info);
229
230   if (caps) {
231     if (gst_caps_is_fixed (caps) && !verbose)
232       desc = gst_pb_utils_get_codec_description (caps);
233     else
234       desc = gst_caps_to_string (caps);
235     gst_caps_unref (caps);
236   }
237
238   g_print ("%*s%s: %s\n", 2 * GPOINTER_TO_INT (depth), " ",
239       gst_discoverer_stream_info_get_stream_type_nick (info), desc);
240
241   if (desc) {
242     g_free (desc);
243     desc = NULL;
244   }
245
246   if (verbose) {
247     if (GST_IS_DISCOVERER_AUDIO_INFO (info))
248       desc =
249           gst_stream_audio_information_to_string (info,
250           GPOINTER_TO_INT (depth) + 1);
251     else if (GST_IS_DISCOVERER_VIDEO_INFO (info))
252       desc =
253           gst_stream_video_information_to_string (info,
254           GPOINTER_TO_INT (depth) + 1);
255     else if (GST_IS_DISCOVERER_SUBTITLE_INFO (info))
256       desc =
257           gst_stream_subtitle_information_to_string (info,
258           GPOINTER_TO_INT (depth) + 1);
259     if (desc) {
260       g_print ("%s", desc);
261       g_free (desc);
262     }
263   }
264 }
265
266 static void
267 print_topology (GstDiscovererStreamInfo * info, gint depth)
268 {
269   GstDiscovererStreamInfo *next;
270
271   if (!info)
272     return;
273
274   print_stream_info (info, GINT_TO_POINTER (depth));
275
276   next = gst_discoverer_stream_info_get_next (info);
277   if (next) {
278     print_topology (next, depth + 1);
279     gst_discoverer_stream_info_unref (next);
280   } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
281     GList *tmp, *streams;
282
283     streams =
284         gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO
285         (info));
286     for (tmp = streams; tmp; tmp = tmp->next) {
287       GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
288       print_topology (tmpinf, depth + 1);
289     }
290     gst_discoverer_stream_info_list_free (streams);
291   }
292 }
293
294 static void
295 print_tag_foreach (const GstTagList * tags, const gchar * tag,
296     gpointer user_data)
297 {
298   GValue val = { 0, };
299   gchar *str;
300   gint depth = GPOINTER_TO_INT (user_data);
301
302   gst_tag_list_copy_value (&val, tags, tag);
303
304   if (G_VALUE_HOLDS_STRING (&val))
305     str = g_value_dup_string (&val);
306   else
307     str = gst_value_serialize (&val);
308
309   g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);
310   g_free (str);
311
312   g_value_unset (&val);
313 }
314
315 #define MAX_INDENT 40
316
317 static void
318 print_toc_entry (gpointer data, gpointer user_data)
319 {
320   GstTocEntry *entry = (GstTocEntry *) data;
321   gint depth = GPOINTER_TO_INT (user_data);
322   guint indent = MIN (GPOINTER_TO_UINT (user_data), MAX_INDENT);
323   GstTagList *tags;
324   GList *subentries;
325   gint64 start, stop;
326
327   gst_toc_entry_get_start_stop_times (entry, &start, &stop);
328   g_print ("%*s%s: start: %" GST_TIME_FORMAT " stop: %" GST_TIME_FORMAT "\n",
329       depth, " ",
330       gst_toc_entry_type_get_nick (gst_toc_entry_get_entry_type (entry)),
331       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
332   indent += 2;
333
334   /* print tags */
335   tags = gst_toc_entry_get_tags (entry);
336   if (tags) {
337     g_print ("%*sTags:\n", 2 * depth, " ");
338     gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (indent));
339   }
340
341   /* loop over sub-toc entries */
342   subentries = gst_toc_entry_get_sub_entries (entry);
343   g_list_foreach (subentries, print_toc_entry, GUINT_TO_POINTER (indent));
344 }
345
346 static void
347 print_properties (GstDiscovererInfo * info, gint tab)
348 {
349   const GstTagList *tags;
350   const GstToc *toc;
351
352   g_print ("%*sDuration: %" GST_TIME_FORMAT "\n", tab + 1, " ",
353       GST_TIME_ARGS (gst_discoverer_info_get_duration (info)));
354   g_print ("%*sSeekable: %s\n", tab + 1, " ",
355       (gst_discoverer_info_get_seekable (info) ? "yes" : "no"));
356   if ((tags = gst_discoverer_info_get_tags (info))) {
357     g_print ("%*sTags: \n", tab + 1, " ");
358     gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (tab + 2));
359   }
360   if (show_toc && (toc = gst_discoverer_info_get_toc (info))) {
361     GList *entries;
362
363     g_print ("%*sTOC: \n", tab + 1, " ");
364     entries = gst_toc_get_entries (toc);
365     g_list_foreach (entries, print_toc_entry, GUINT_TO_POINTER (tab + 5));
366   }
367 }
368
369 static void
370 print_info (GstDiscovererInfo * info, GError * err)
371 {
372   GstDiscovererResult result = gst_discoverer_info_get_result (info);
373   GstDiscovererStreamInfo *sinfo;
374
375   g_print ("Done discovering %s\n", gst_discoverer_info_get_uri (info));
376   switch (result) {
377     case GST_DISCOVERER_OK:
378     {
379       break;
380     }
381     case GST_DISCOVERER_URI_INVALID:
382     {
383       g_print ("URI is not valid\n");
384       break;
385     }
386     case GST_DISCOVERER_ERROR:
387     {
388       g_print ("An error was encountered while discovering the file\n");
389       g_print (" %s\n", err->message);
390       break;
391     }
392     case GST_DISCOVERER_TIMEOUT:
393     {
394       g_print ("Analyzing URI timed out\n");
395       break;
396     }
397     case GST_DISCOVERER_BUSY:
398     {
399       g_print ("Discoverer was busy\n");
400       break;
401     }
402     case GST_DISCOVERER_MISSING_PLUGINS:
403     {
404       g_print ("Missing plugins\n");
405       if (verbose) {
406         gchar *tmp =
407             gst_structure_to_string (gst_discoverer_info_get_misc (info));
408         g_print (" (%s)\n", tmp);
409         g_free (tmp);
410       }
411       break;
412     }
413   }
414
415   if ((sinfo = gst_discoverer_info_get_stream_info (info))) {
416     g_print ("\nTopology:\n");
417     print_topology (sinfo, 1);
418     g_print ("\nProperties:\n");
419     print_properties (info, 1);
420     gst_discoverer_stream_info_unref (sinfo);
421   }
422
423   g_print ("\n");
424 }
425
426 static void
427 process_file (GstDiscoverer * dc, const gchar * filename)
428 {
429   GError *err = NULL;
430   GDir *dir;
431   gchar *uri, *path;
432   GstDiscovererInfo *info;
433
434   if (!gst_uri_is_valid (filename)) {
435     /* Recurse into directories */
436     if ((dir = g_dir_open (filename, 0, NULL))) {
437       const gchar *entry;
438
439       while ((entry = g_dir_read_name (dir))) {
440         gchar *path;
441         path = g_strconcat (filename, G_DIR_SEPARATOR_S, entry, NULL);
442         process_file (dc, path);
443         g_free (path);
444       }
445
446       g_dir_close (dir);
447       return;
448     }
449
450     if (!g_path_is_absolute (filename)) {
451       gchar *cur_dir;
452
453       cur_dir = g_get_current_dir ();
454       path = g_build_filename (cur_dir, filename, NULL);
455       g_free (cur_dir);
456     } else {
457       path = g_strdup (filename);
458     }
459
460     uri = g_filename_to_uri (path, NULL, &err);
461     g_free (path);
462     path = NULL;
463
464     if (err) {
465       g_warning ("Couldn't convert filename to URI: %s\n", err->message);
466       g_error_free (err);
467       return;
468     }
469   } else {
470     uri = g_strdup (filename);
471   }
472
473   if (async == FALSE) {
474     g_print ("Analyzing %s\n", uri);
475     info = gst_discoverer_discover_uri (dc, uri, &err);
476     print_info (info, err);
477     if (err)
478       g_error_free (err);
479     gst_discoverer_info_unref (info);
480   } else {
481     gst_discoverer_discover_uri_async (dc, uri);
482   }
483
484   g_free (uri);
485 }
486
487 static void
488 _new_discovered_uri (GstDiscoverer * dc, GstDiscovererInfo * info, GError * err)
489 {
490   print_info (info, err);
491 }
492
493 static gboolean
494 _run_async (PrivStruct * ps)
495 {
496   gint i;
497
498   for (i = 1; i < ps->argc; i++)
499     process_file (ps->dc, ps->argv[i]);
500
501   return FALSE;
502 }
503
504 static void
505 _discoverer_finished (GstDiscoverer * dc, GMainLoop * ml)
506 {
507   g_main_loop_quit (ml);
508 }
509
510 int
511 main (int argc, char **argv)
512 {
513   GError *err = NULL;
514   GstDiscoverer *dc;
515   gint timeout = 10;
516   GOptionEntry options[] = {
517     {"async", 'a', 0, G_OPTION_ARG_NONE, &async,
518         "Run asynchronously", NULL},
519     {"timeout", 't', 0, G_OPTION_ARG_INT, &timeout,
520         "Specify timeout (in seconds, default 10)", "T"},
521     /* {"elem", 'e', 0, G_OPTION_ARG_NONE, &elem_seek, */
522     /*     "Seek on elements instead of pads", NULL}, */
523     {"toc", 'c', 0, G_OPTION_ARG_NONE, &show_toc,
524         "Output TOC (chapters and editions)", NULL},
525     {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
526         "Verbose properties", NULL},
527     {NULL}
528   };
529   GOptionContext *ctx;
530
531   ctx =
532       g_option_context_new
533       ("- discover files synchronously with GstDiscoverer");
534   g_option_context_add_main_entries (ctx, options, NULL);
535   g_option_context_add_group (ctx, gst_init_get_option_group ());
536
537   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
538     g_print ("Error initializing: %s\n", err->message);
539     exit (1);
540   }
541
542   g_option_context_free (ctx);
543
544   if (argc < 2) {
545     g_print ("usage: %s <uris>\n", argv[0]);
546     exit (-1);
547   }
548
549   dc = gst_discoverer_new (timeout * GST_SECOND, &err);
550   if (G_UNLIKELY (dc == NULL)) {
551     g_print ("Error initializing: %s\n", err->message);
552     exit (1);
553   }
554
555   if (async == FALSE) {
556     gint i;
557     for (i = 1; i < argc; i++)
558       process_file (dc, argv[i]);
559   } else {
560     PrivStruct *ps = g_new0 (PrivStruct, 1);
561     GMainLoop *ml = g_main_loop_new (NULL, FALSE);
562
563     ps->dc = dc;
564     ps->argc = argc;
565     ps->argv = argv;
566
567     /* adding uris will be started when the mainloop runs */
568     g_idle_add ((GSourceFunc) _run_async, ps);
569
570     /* connect signals */
571     g_signal_connect (dc, "discovered", G_CALLBACK (_new_discovered_uri), NULL);
572     g_signal_connect (dc, "finished", G_CALLBACK (_discoverer_finished), ml);
573
574     gst_discoverer_start (dc);
575     /* run mainloop */
576     g_main_loop_run (ml);
577
578     gst_discoverer_stop (dc);
579     g_free (ps);
580     g_main_loop_unref (ml);
581   }
582   g_object_unref (dc);
583
584   return 0;
585 }