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