tools: Don't depend on pbutils
[gupnp:gupnp-dlna.git] / tools / gupnp-dlna-info.c
1 /* GUPnPDLNA
2  * gupnp-dlna-info.c
3  *
4  * Copyright (C) 2010 Nokia Corporation
5  * Copyright (C) 2010 Collabora Multimedia
6  *
7  * Authors: Parthasarathi Susarla <partha.susarla@collabora.co.uk>
8  *
9  * Based on 'gst-discoverer.c' by
10  * Edward Hervey <edward.hervey@collabora.co.uk>
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the
24  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25  * Boston, MA 02111-1307, USA.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <glib.h>
36 #include <glib-object.h>
37 #include <gio/gio.h>
38
39 #include <gst/gst.h>
40 #include <gst/profile/gstprofile.h>
41 #include <gst/discoverer/gstdiscoverer.h>
42
43 #include <libgupnp-dlna/gupnp-dlna-load.h>
44 #include <libgupnp-dlna/gupnp-dlna-profile.h>
45 #include <libgupnp-dlna/gupnp-dlna-discoverer.h>
46 #include <libgupnp-dlna/gupnp-dlna-information.h>
47
48
49 static gboolean async = FALSE;
50 static gboolean verbose = FALSE;
51 static gint timeout = 10;
52
53 static const gchar *_gst_stream_type_to_string[] = {
54         "CONTAINER",
55         "AUDIO",
56         "VIDEO",
57         "IMAGE",
58         "UNKNOWN",
59 };
60
61
62 typedef struct
63 {
64         GUPnPDLNADiscoverer *dc;
65         int argc;
66         char **argv;
67 } PrivStruct;
68
69 /*
70  * The following functions are from gst-discoverer.c (gst-convenience/tools)
71  */
72 #define my_g_string_append_printf(str, format, ...)                     \
73         g_string_append_printf (str, "%*s" format, 2*depth, " ", ##__VA_ARGS__)
74
75 static gchar *
76 gst_stream_audio_information_to_string (GstStreamAudioInformation * info,
77                                         gint depth)
78 {
79         GString *s;
80         gchar *tmp;
81         int len = 400;
82
83         g_return_val_if_fail (info != NULL, NULL);
84
85         s = g_string_sized_new (len);
86
87         my_g_string_append_printf (s, "Codec:\n");
88         tmp = gst_caps_to_string (info->parent.caps);
89         my_g_string_append_printf (s, "  %s\n", tmp);
90         g_free (tmp);
91
92         my_g_string_append_printf (s, "Additional info:\n");
93         if (info->parent.misc) {
94                 tmp = gst_structure_to_string (info->parent.misc);
95                 my_g_string_append_printf (s, "  %s\n", tmp);
96                 g_free (tmp);
97         } else {
98                 my_g_string_append_printf (s, "  None\n");
99         }
100
101         my_g_string_append_printf (s, "Channels: %u\n", info->channels);
102         my_g_string_append_printf (s, "Sample rate: %u\n", info->sample_rate);
103         my_g_string_append_printf (s, "Depth: %u\n", info->depth);
104
105         my_g_string_append_printf (s, "Bitrate: %u\n", info->bitrate);
106         my_g_string_append_printf (s, "Max bitrate: %u\n", info->max_bitrate);
107         my_g_string_append_printf (s, "VBR: %s\n",
108                                    info->is_vbr ? "true" : "false");
109
110         my_g_string_append_printf (s, "Tags:\n");
111         if (info->parent.tags) {
112                 tmp = gst_structure_to_string ((GstStructure *) info->parent.tags);
113                 my_g_string_append_printf (s, "  %s\n", tmp);
114                 g_free (tmp);
115         } else {
116                 my_g_string_append_printf (s, "  None\n");
117         }
118
119         return g_string_free (s, FALSE);
120 }
121
122 static const gchar *_gst_video_format_to_string[] = {
123         "UNKNOWN",
124         "I420",
125         "YV12",
126         "YUY2",
127         "UYVY",
128         "AYUV",
129         "RGBx",
130         "BGRx",
131         "xRGB",
132         "xBGR",
133         "RGBA",
134         "BGRA",
135         "ARGB",
136         "ABGR",
137         "RGB",
138         "BGR",
139         "Y41B",
140         "Y42B",
141         "YVYU",
142         "Y444",
143         "v210",
144         "v216"
145 };
146
147 static gchar *
148 gst_stream_video_information_to_string (GstStreamVideoInformation * info,
149                                         gint depth)
150 {
151         GString *s;
152         gchar *tmp;
153         int len = 500, n, d;
154
155         g_return_val_if_fail (info != NULL, NULL);
156
157         s = g_string_sized_new (len);
158
159         my_g_string_append_printf (s, "Codec:\n");
160         tmp = gst_caps_to_string (info->parent.caps);
161         my_g_string_append_printf (s, "  %s\n", tmp);
162         g_free (tmp);
163
164         my_g_string_append_printf (s, "Additional info:\n");
165         if (info->parent.misc) {
166                 tmp = gst_structure_to_string (info->parent.misc);
167                 my_g_string_append_printf (s, "  %s\n", tmp);
168                 g_free (tmp);
169         } else {
170                 my_g_string_append_printf (s, "  None\n");
171         }
172
173         my_g_string_append_printf (s, "Width: %u\n", info->width);
174         my_g_string_append_printf (s, "Height: %u\n", info->height);
175         my_g_string_append_printf (s, "Depth: %u\n", info->depth);
176
177         n = gst_value_get_fraction_numerator (&info->frame_rate);
178         d = gst_value_get_fraction_denominator (&info->frame_rate);
179         my_g_string_append_printf (s, "Frame rate: %u/%u\n", n, d);
180
181         n = gst_value_get_fraction_numerator (&info->pixel_aspect_ratio);
182         d = gst_value_get_fraction_denominator (&info->pixel_aspect_ratio);
183         my_g_string_append_printf (s, "Pixel aspect ratio: %u/%u\n", n, d);
184
185         my_g_string_append_printf (s, "Format: %s\n",
186                                    _gst_video_format_to_string[info->format]);
187
188         my_g_string_append_printf (s, "Interlaced: %s\n",
189                                    info->interlaced ? "true" : "false");
190
191         my_g_string_append_printf (s, "Tags:\n");
192         if (info->parent.tags) {
193                 tmp = gst_structure_to_string ((GstStructure *) info->parent.tags);
194                 my_g_string_append_printf (s, "  %s\n", tmp);
195                 g_free (tmp);
196         } else {
197                 my_g_string_append_printf (s, "  None\n");
198         }
199
200
201         return g_string_free (s, FALSE);
202 }
203
204 static void
205 print_stream_info (GstStreamInformation * info, void *depth)
206 {
207         gchar *desc = NULL;
208
209         if (info->caps) {
210                 desc = gst_caps_to_string (info->caps);
211         }
212
213         g_print ("%*s%s: %s\n", 2 * GPOINTER_TO_INT (depth), " ",
214                  _gst_stream_type_to_string[info->streamtype], desc);
215
216         if (desc) {
217                 g_free (desc);
218                 desc = NULL;
219         }
220
221         switch (info->streamtype) {
222         case GST_STREAM_AUDIO:
223                 desc = gst_stream_audio_information_to_string (
224                                         (GstStreamAudioInformation *) info,
225                                         GPOINTER_TO_INT (depth) + 1);
226                 break;
227
228         case GST_STREAM_VIDEO:
229         case GST_STREAM_IMAGE:
230                 desc = gst_stream_video_information_to_string (
231                                          (GstStreamVideoInformation *) info,
232                                          GPOINTER_TO_INT (depth) + 1);
233                 break;
234
235         default:
236                 break;
237         }
238
239         if (desc) {
240                 g_print ("%s", desc);
241                 g_free (desc);
242         }
243 }
244
245 static void
246 print_topology (GstStreamInformation * info, gint depth)
247 {
248         if (!info)
249                 return;
250
251         print_stream_info (info, GINT_TO_POINTER (depth));
252
253         if (info->next)
254                 print_topology (info->next, depth + 1);
255         else if (info->streamtype == GST_STREAM_CONTAINER) {
256                 GstStreamContainerInformation *cont =
257                         (GstStreamContainerInformation *) info;
258                 GList *tmp;
259
260                 for (tmp = cont->streams; tmp; tmp = tmp->next) {
261                         GstStreamInformation *tmpinf =
262                                 (GstStreamInformation *) tmp->data;
263                         print_topology (tmpinf, depth + 1);
264                 }
265         }
266 }
267
268 static void
269 print_duration (const GstDiscovererInformation * info, gint tab)
270 {
271         g_print ("%*s%" GST_TIME_FORMAT "\n", tab + 1, " ",
272                  GST_TIME_ARGS (info->duration));
273 }
274
275 static void
276 print_gst_info (const GstDiscovererInformation *info, GError *err)
277 {
278         /* Get done with the error parsing first */
279         if (info->result & GST_DISCOVERER_URI_INVALID)
280                 g_print ("URI is not valid\n");
281         else if (info->result & GST_DISCOVERER_TIMEOUT)
282                 g_print ("Timeout !\n");
283         if (info->result & GST_DISCOVERER_ERROR) {
284                 g_print ("An error while discovering the file\n");
285                 g_print (" %s\n", err->message);
286                 if (info->result & GST_DISCOVERER_MISSING_PLUGINS) {
287                         gchar *tmp = gst_structure_to_string (info->misc);
288                         g_print (" (%s)\n", tmp);
289                         g_free (tmp);
290                 }
291
292                 return;
293         }
294
295         /* Print Useful information */
296         if (verbose) {
297                 if (!(info->result &
298                       (GST_DISCOVERER_ERROR | GST_DISCOVERER_TIMEOUT))) {
299                         g_print ("\nTopology:\n");
300                         print_topology (info->stream_info, 1);
301                         g_print ("\nDuration:\n");
302                         print_duration (info, 1);
303                 }
304         }
305 }
306
307 static void
308 print_dlna_info (GUPnPDLNAInformation *dlna, GError *err)
309 {
310         g_print ("\nURI: %s\n", gupnp_dlna_information_get_info (dlna)->uri);
311         g_print ("Profile Name: %s\n", gupnp_dlna_information_get_name (dlna));
312         g_print ("Profile MIME: %s\n", gupnp_dlna_information_get_mime (dlna));
313
314         print_gst_info (gupnp_dlna_information_get_info(dlna), err);
315
316         g_print ("\n");
317         return;
318 }
319
320 static void
321 discoverer_done (GUPnPDLNADiscoverer *discover,
322                  GUPnPDLNAInformation *dlna,
323                  GError *err)
324 {
325         print_dlna_info (dlna, err);
326         return;
327 }
328
329 static void
330 discoverer_ready (GUPnPDLNADiscoverer *dc, GMainLoop *ml)
331 {
332         g_main_loop_quit (ml);
333 }
334
335 static void
336 process_file (GUPnPDLNADiscoverer *discover, const gchar *filename)
337 {
338         GError *err = NULL;
339         GDir *dir;
340         gchar *uri, *path;
341         GUPnPDLNAInformation *dlna;
342
343         if(!gst_uri_is_valid (filename)) {
344                 if((dir = g_dir_open (filename, 0, NULL))) {
345                         const gchar *entry;
346
347                         while ((entry = g_dir_read_name (dir))) {
348                                 gchar *path;
349                                 path = g_strconcat (filename,
350                                                     G_DIR_SEPARATOR_S,
351                                                     entry,
352                                                     NULL);
353                                 process_file (discover, path);
354                                 g_free(path);
355                         }
356
357                         g_dir_close (dir);
358                         return;
359                 }
360
361                 if (!g_path_is_absolute (filename)) {
362                         gchar *cur_dir;
363
364                         cur_dir = g_get_current_dir ();
365                         path = g_build_filename (cur_dir, filename, NULL);
366                         g_free (cur_dir);
367                 } else {
368                         path = g_strdup (filename);
369                 }
370
371                 uri = g_filename_to_uri (path, NULL, &err);
372                 g_free (path);
373                 path = NULL;
374
375                 if (err) {
376                         g_warning ("Couldn't convert filename to URI: %s\n",
377                                    err->message);
378                         g_error_free (err);
379                         err = NULL;
380                         return;
381                 }
382         } else {
383                 uri = g_strdup (filename);
384         }
385
386         if (async == FALSE) {
387                 dlna = gupnp_dlna_discoverer_discover_uri_sync (discover,
388                                                                 uri,
389                                                                 &err);
390                 if (err) {
391                         /* Report error to user, and free error */
392                         fprintf (stderr,
393                                  "Unable to read file: %s\n",
394                                  err->message);
395                         g_error_free (err);
396                         err = NULL;
397                 } else {
398                         print_dlna_info (dlna, err);
399                 }
400         } else {
401                 gupnp_dlna_discoverer_discover_uri (discover, uri);
402         }
403
404         g_free (uri);
405 }
406
407
408 static gboolean
409 async_idle_loop (PrivStruct * ps)
410 {
411         gint i;
412
413         for (i = 1; i < ps->argc; i++)
414                 process_file (ps->dc, ps->argv[i]);
415
416         return FALSE;
417 }
418
419 /* Main */
420 int
421 main (int argc, char **argv)
422 {
423         gint i;
424         GUPnPDLNADiscoverer *discover;
425         gboolean relaxed_mode = FALSE;
426         gboolean extended_mode = FALSE;
427         GError *err = NULL;
428
429         GOptionEntry options[] = {
430                 {"timeout", 't', 0, G_OPTION_ARG_INT, &timeout,
431                  "Specify timeout (in seconds, defaults to 10)", "T"},
432                 {"async", 'a', 0, G_OPTION_ARG_NONE, &async,
433                  "Run asynchronously", NULL},
434                 {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
435                  "Print lot more information", NULL},
436                 {"relaxed mode", 'r', 0, G_OPTION_ARG_NONE, &relaxed_mode,
437                  "Enable Relaxed mode", NULL},
438                 {"extended mode", 'e', 0, G_OPTION_ARG_NONE, &extended_mode,
439                  "Enable extended mode", NULL},
440                 {NULL}
441         };
442
443         GOptionContext *ctx;
444
445
446         g_thread_init(NULL);
447
448         ctx = g_option_context_new (" - program to extract DLNA and related metadata");
449         g_option_context_add_main_entries (ctx, options, NULL);
450         g_option_context_add_group (ctx, gst_init_get_option_group ());
451
452         if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
453
454                 g_print ("Error initializing: %s\n", err->message);
455                 exit (1);
456         }
457
458         g_option_context_free (ctx);
459
460         if (argc < 2) {
461                 g_print ("usage:%s <files>\n", argv[0]);
462                 return -1;
463         }
464
465         gst_init(&argc, &argv);
466
467
468         discover = gupnp_dlna_discoverer_new ((GstClockTime)
469                                               (timeout * GST_SECOND),
470                                               relaxed_mode,
471                                               extended_mode);
472
473         if (async == FALSE) {
474                 for ( i = 1 ; i < argc ; i++ ) {
475                         process_file (discover, argv[i]);
476                 }
477         } else {
478                 PrivStruct *ps = g_new0 (PrivStruct, 1);
479                 GMainLoop *ml = g_main_loop_new (NULL, FALSE);
480
481                 ps->dc = discover;
482                 ps->argc = argc;
483                 ps->argv = argv;
484
485                 g_idle_add ((GSourceFunc) async_idle_loop, ps);
486
487                 g_signal_connect (discover, "done",
488                                   (GCallback) discoverer_done, 0);
489                 g_signal_connect (discover, "ready",
490                                   (GCallback) discoverer_ready, ml);
491
492                 gupnp_dlna_discoverer_start (discover);
493
494                 g_main_loop_run (ml);
495
496                 gupnp_dlna_discoverer_stop (discover);
497
498         }
499         g_object_unref (discover);
500         return 0;
501 }