gst-discoverer.c 16.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/* GStreamer
 * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
16 17
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
18 19 20 21 22 23
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

24 25
#include <locale.h>

26 27 28 29 30
#include <stdlib.h>
#include <glib.h>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>

31 32
#define MAX_INDENT 40

33 34 35 36
/* *INDENT-OFF* */
static void my_g_string_append_printf (GString * str, int depth, const gchar * format, ...) G_GNUC_PRINTF (3, 4);
/* *INDENT-ON* */

37
static gboolean async = FALSE;
38
static gboolean show_toc = FALSE;
39 40 41 42 43 44 45 46 47
static gboolean verbose = FALSE;

typedef struct
{
  GstDiscoverer *dc;
  int argc;
  char **argv;
} PrivStruct;

48 49 50 51 52 53 54 55 56 57 58 59 60
static void
my_g_string_append_printf (GString * str, int depth, const gchar * format, ...)
{
  va_list args;

  while (depth-- > 0) {
    g_string_append (str, "  ");
  }

  va_start (args, format);
  g_string_append_vprintf (str, format, args);
  va_end (args);
}
61

62 63
static void
gst_stream_information_to_string (GstDiscovererStreamInfo * info, GString * s,
64
    guint depth)
65 66 67
{
  gchar *tmp;
  GstCaps *caps;
68
  const GstStructure *misc;
69

70
  my_g_string_append_printf (s, depth, "Codec:\n");
71 72 73
  caps = gst_discoverer_stream_info_get_caps (info);
  tmp = gst_caps_to_string (caps);
  gst_caps_unref (caps);
74
  my_g_string_append_printf (s, depth, "  %s\n", tmp);
75 76
  g_free (tmp);

77
  my_g_string_append_printf (s, depth, "Additional info:\n");
78 79
  if ((misc = gst_discoverer_stream_info_get_misc (info))) {
    tmp = gst_structure_to_string (misc);
80
    my_g_string_append_printf (s, depth, "  %s\n", tmp);
81 82
    g_free (tmp);
  } else {
83
    my_g_string_append_printf (s, depth, "  None\n");
84 85
  }

86 87
  my_g_string_append_printf (s, depth, "Stream ID: %s\n",
      gst_discoverer_stream_info_get_stream_id (info));
88 89
}

90 91 92 93 94 95 96 97
static void
print_tag_foreach (const GstTagList * tags, const gchar * tag,
    gpointer user_data)
{
  GValue val = { 0, };
  gchar *str;
  guint depth = GPOINTER_TO_UINT (user_data);

98 99
  if (!gst_tag_list_copy_value (&val, tags, tag))
    return;
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146

  if (G_VALUE_HOLDS_STRING (&val)) {
    str = g_value_dup_string (&val);
  } else if (G_VALUE_TYPE (&val) == GST_TYPE_SAMPLE) {
    GstSample *sample = gst_value_get_sample (&val);
    GstBuffer *img = gst_sample_get_buffer (sample);
    GstCaps *caps = gst_sample_get_caps (sample);

    if (img) {
      if (caps) {
        gchar *caps_str;

        caps_str = gst_caps_to_string (caps);
        str = g_strdup_printf ("buffer of %" G_GSIZE_FORMAT " bytes, "
            "type: %s", gst_buffer_get_size (img), caps_str);
        g_free (caps_str);
      } else {
        str = g_strdup_printf ("buffer of %" G_GSIZE_FORMAT " bytes",
            gst_buffer_get_size (img));
      }
    } else {
      str = g_strdup ("NULL buffer");
    }
  } else {
    str = gst_value_serialize (&val);
  }

  g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);
  g_free (str);

  g_value_unset (&val);
}

static void
print_tags_topology (guint depth, const GstTagList * tags)
{
  g_print ("%*sTags:\n", 2 * depth, " ");
  if (tags) {
    gst_tag_list_foreach (tags, print_tag_foreach,
        GUINT_TO_POINTER (depth + 1));
  } else {
    g_print ("%*sNone\n", 2 * (depth + 1), " ");
  }
  if (verbose)
    g_print ("%*s\n", 2 * depth, " ");
}

147 148
static gchar *
gst_stream_audio_information_to_string (GstDiscovererStreamInfo * info,
149
    guint depth)
150 151 152 153 154 155 156 157 158 159 160 161
{
  GstDiscovererAudioInfo *audio_info;
  GString *s;
  const gchar *ctmp;
  int len = 400;
  const GstTagList *tags;

  g_return_val_if_fail (info != NULL, NULL);

  s = g_string_sized_new (len);

  gst_stream_information_to_string (info, s, depth);
162

163
  audio_info = (GstDiscovererAudioInfo *) info;
164
  ctmp = gst_discoverer_audio_info_get_language (audio_info);
165 166 167
  my_g_string_append_printf (s, depth, "Language: %s\n",
      ctmp ? ctmp : "<unknown>");
  my_g_string_append_printf (s, depth, "Channels: %u\n",
168
      gst_discoverer_audio_info_get_channels (audio_info));
169
  my_g_string_append_printf (s, depth, "Sample rate: %u\n",
170
      gst_discoverer_audio_info_get_sample_rate (audio_info));
171
  my_g_string_append_printf (s, depth, "Depth: %u\n",
172
      gst_discoverer_audio_info_get_depth (audio_info));
173

174
  my_g_string_append_printf (s, depth, "Bitrate: %u\n",
175
      gst_discoverer_audio_info_get_bitrate (audio_info));
176
  my_g_string_append_printf (s, depth, "Max bitrate: %u\n",
177
      gst_discoverer_audio_info_get_max_bitrate (audio_info));
178 179

  tags = gst_discoverer_stream_info_get_tags (info);
180
  print_tags_topology (depth, tags);
181 182 183 184 185 186

  return g_string_free (s, FALSE);
}

static gchar *
gst_stream_video_information_to_string (GstDiscovererStreamInfo * info,
187
    guint depth)
188
{
189
  GstDiscovererVideoInfo *video_info;
190 191 192 193 194 195 196 197
  GString *s;
  int len = 500;
  const GstTagList *tags;

  g_return_val_if_fail (info != NULL, NULL);

  s = g_string_sized_new (len);

198
  gst_stream_information_to_string (info, s, depth);
199

200
  video_info = (GstDiscovererVideoInfo *) info;
201
  my_g_string_append_printf (s, depth, "Width: %u\n",
202
      gst_discoverer_video_info_get_width (video_info));
203
  my_g_string_append_printf (s, depth, "Height: %u\n",
204
      gst_discoverer_video_info_get_height (video_info));
205
  my_g_string_append_printf (s, depth, "Depth: %u\n",
206
      gst_discoverer_video_info_get_depth (video_info));
207

208
  my_g_string_append_printf (s, depth, "Frame rate: %u/%u\n",
209 210
      gst_discoverer_video_info_get_framerate_num (video_info),
      gst_discoverer_video_info_get_framerate_denom (video_info));
211

212
  my_g_string_append_printf (s, depth, "Pixel aspect ratio: %u/%u\n",
213 214
      gst_discoverer_video_info_get_par_num (video_info),
      gst_discoverer_video_info_get_par_denom (video_info));
215

216
  my_g_string_append_printf (s, depth, "Interlaced: %s\n",
217
      gst_discoverer_video_info_is_interlaced (video_info) ? "true" : "false");
218

219
  my_g_string_append_printf (s, depth, "Bitrate: %u\n",
220
      gst_discoverer_video_info_get_bitrate (video_info));
221
  my_g_string_append_printf (s, depth, "Max bitrate: %u\n",
222
      gst_discoverer_video_info_get_max_bitrate (video_info));
223 224

  tags = gst_discoverer_stream_info_get_tags (info);
225
  print_tags_topology (depth, tags);
226 227 228 229

  return g_string_free (s, FALSE);
}

230 231
static gchar *
gst_stream_subtitle_information_to_string (GstDiscovererStreamInfo * info,
232
    guint depth)
233 234 235 236 237 238 239 240 241 242 243
{
  GstDiscovererSubtitleInfo *subtitle_info;
  GString *s;
  const gchar *ctmp;
  int len = 400;
  const GstTagList *tags;

  g_return_val_if_fail (info != NULL, NULL);

  s = g_string_sized_new (len);

244
  gst_stream_information_to_string (info, s, depth);
245

246 247
  subtitle_info = (GstDiscovererSubtitleInfo *) info;
  ctmp = gst_discoverer_subtitle_info_get_language (subtitle_info);
248 249
  my_g_string_append_printf (s, depth, "Language: %s\n",
      ctmp ? ctmp : "<unknown>");
250 251

  tags = gst_discoverer_stream_info_get_tags (info);
252
  print_tags_topology (depth, tags);
253 254 255 256

  return g_string_free (s, FALSE);
}

257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
static void
print_stream_info (GstDiscovererStreamInfo * info, void *depth)
{
  gchar *desc = NULL;
  GstCaps *caps;

  caps = gst_discoverer_stream_info_get_caps (info);

  if (caps) {
    if (gst_caps_is_fixed (caps) && !verbose)
      desc = gst_pb_utils_get_codec_description (caps);
    else
      desc = gst_caps_to_string (caps);
    gst_caps_unref (caps);
  }

  g_print ("%*s%s: %s\n", 2 * GPOINTER_TO_INT (depth), " ",
      gst_discoverer_stream_info_get_stream_type_nick (info), desc);

  if (desc) {
    g_free (desc);
    desc = NULL;
  }

  if (verbose) {
    if (GST_IS_DISCOVERER_AUDIO_INFO (info))
      desc =
          gst_stream_audio_information_to_string (info,
          GPOINTER_TO_INT (depth) + 1);
    else if (GST_IS_DISCOVERER_VIDEO_INFO (info))
      desc =
          gst_stream_video_information_to_string (info,
          GPOINTER_TO_INT (depth) + 1);
290 291 292 293
    else if (GST_IS_DISCOVERER_SUBTITLE_INFO (info))
      desc =
          gst_stream_subtitle_information_to_string (info,
          GPOINTER_TO_INT (depth) + 1);
Stefan Kost's avatar
Stefan Kost committed
294 295 296 297
    if (desc) {
      g_print ("%s", desc);
      g_free (desc);
    }
298 299 300 301
  }
}

static void
302
print_topology (GstDiscovererStreamInfo * info, guint depth)
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
{
  GstDiscovererStreamInfo *next;

  if (!info)
    return;

  print_stream_info (info, GINT_TO_POINTER (depth));

  next = gst_discoverer_stream_info_get_next (info);
  if (next) {
    print_topology (next, depth + 1);
    gst_discoverer_stream_info_unref (next);
  } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
    GList *tmp, *streams;

318 319 320
    streams =
        gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO
        (info));
321 322 323 324 325 326 327 328
    for (tmp = streams; tmp; tmp = tmp->next) {
      GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
      print_topology (tmpinf, depth + 1);
    }
    gst_discoverer_stream_info_list_free (streams);
  }
}

329 330 331 332
static void
print_toc_entry (gpointer data, gpointer user_data)
{
  GstTocEntry *entry = (GstTocEntry *) data;
333
  guint depth = GPOINTER_TO_UINT (user_data);
334
  guint indent = MIN (GPOINTER_TO_UINT (user_data), MAX_INDENT);
335 336
  GstTagList *tags;
  GList *subentries;
337 338
  gint64 start, stop;

339
  gst_toc_entry_get_start_stop_times (entry, &start, &stop);
340
  g_print ("%*s%s: start: %" GST_TIME_FORMAT " stop: %" GST_TIME_FORMAT "\n",
341 342
      depth, " ",
      gst_toc_entry_type_get_nick (gst_toc_entry_get_entry_type (entry)),
343 344 345 346
      GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
  indent += 2;

  /* print tags */
347 348
  tags = gst_toc_entry_get_tags (entry);
  if (tags) {
349
    g_print ("%*sTags:\n", 2 * depth, " ");
350 351
    gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (indent));
  }
352 353

  /* loop over sub-toc entries */
354 355
  subentries = gst_toc_entry_get_sub_entries (entry);
  g_list_foreach (subentries, print_toc_entry, GUINT_TO_POINTER (indent));
356 357
}

358
static void
Stefan Kost's avatar
Stefan Kost committed
359
print_properties (GstDiscovererInfo * info, gint tab)
360
{
361
  const GstTagList *tags;
362
  const GstToc *toc;
363

Stefan Kost's avatar
Stefan Kost committed
364
  g_print ("%*sDuration: %" GST_TIME_FORMAT "\n", tab + 1, " ",
365
      GST_TIME_ARGS (gst_discoverer_info_get_duration (info)));
Stefan Kost's avatar
Stefan Kost committed
366 367
  g_print ("%*sSeekable: %s\n", tab + 1, " ",
      (gst_discoverer_info_get_seekable (info) ? "yes" : "no"));
368 369
  if ((tags = gst_discoverer_info_get_tags (info))) {
    g_print ("%*sTags: \n", tab + 1, " ");
370
    gst_tag_list_foreach (tags, print_tag_foreach, GUINT_TO_POINTER (tab + 2));
371
  }
372
  if (show_toc && (toc = gst_discoverer_info_get_toc (info))) {
373 374
    GList *entries;

375
    g_print ("%*sTOC: \n", tab + 1, " ");
376 377
    entries = gst_toc_get_entries (toc);
    g_list_foreach (entries, print_toc_entry, GUINT_TO_POINTER (tab + 5));
378
  }
379 380 381 382 383 384
}

static void
print_info (GstDiscovererInfo * info, GError * err)
{
  GstDiscovererResult result = gst_discoverer_info_get_result (info);
385
  GstDiscovererStreamInfo *sinfo;
386 387

  g_print ("Done discovering %s\n", gst_discoverer_info_get_uri (info));
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
  switch (result) {
    case GST_DISCOVERER_OK:
    {
      break;
    }
    case GST_DISCOVERER_URI_INVALID:
    {
      g_print ("URI is not valid\n");
      break;
    }
    case GST_DISCOVERER_ERROR:
    {
      g_print ("An error was encountered while discovering the file\n");
      g_print (" %s\n", err->message);
      break;
    }
    case GST_DISCOVERER_TIMEOUT:
    {
      g_print ("Analyzing URI timed out\n");
      break;
    }
    case GST_DISCOVERER_BUSY:
    {
      g_print ("Discoverer was busy\n");
      break;
    }
    case GST_DISCOVERER_MISSING_PLUGINS:
    {
      g_print ("Missing plugins\n");
      if (verbose) {
418 419 420 421 422 423 424 425 426
        gint i = 0;
        const gchar **installer_details =
            gst_discoverer_info_get_missing_elements_installer_details (info);

        while (installer_details[i]) {
          g_print (" (%s)\n", installer_details[i]);

          i++;
        }
427 428
      }
      break;
429 430 431
    }
  }

432 433 434
  if ((sinfo = gst_discoverer_info_get_stream_info (info))) {
    g_print ("\nTopology:\n");
    print_topology (sinfo, 1);
Stefan Kost's avatar
Stefan Kost committed
435 436
    g_print ("\nProperties:\n");
    print_properties (info, 1);
437 438 439
    gst_discoverer_stream_info_unref (sinfo);
  }

440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
  g_print ("\n");
}

static void
process_file (GstDiscoverer * dc, const gchar * filename)
{
  GError *err = NULL;
  GDir *dir;
  gchar *uri, *path;
  GstDiscovererInfo *info;

  if (!gst_uri_is_valid (filename)) {
    /* Recurse into directories */
    if ((dir = g_dir_open (filename, 0, NULL))) {
      const gchar *entry;

      while ((entry = g_dir_read_name (dir))) {
        gchar *path;
        path = g_strconcat (filename, G_DIR_SEPARATOR_S, entry, NULL);
        process_file (dc, path);
        g_free (path);
      }

      g_dir_close (dir);
      return;
    }

    if (!g_path_is_absolute (filename)) {
      gchar *cur_dir;

      cur_dir = g_get_current_dir ();
      path = g_build_filename (cur_dir, filename, NULL);
      g_free (cur_dir);
    } else {
      path = g_strdup (filename);
    }

    uri = g_filename_to_uri (path, NULL, &err);
    g_free (path);
    path = NULL;

    if (err) {
      g_warning ("Couldn't convert filename to URI: %s\n", err->message);
      g_error_free (err);
      return;
    }
  } else {
    uri = g_strdup (filename);
  }

490
  if (!async) {
491 492 493
    g_print ("Analyzing %s\n", uri);
    info = gst_discoverer_discover_uri (dc, uri, &err);
    print_info (info, err);
494 495
    if (err)
      g_error_free (err);
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
    gst_discoverer_info_unref (info);
  } else {
    gst_discoverer_discover_uri_async (dc, uri);
  }

  g_free (uri);
}

static void
_new_discovered_uri (GstDiscoverer * dc, GstDiscovererInfo * info, GError * err)
{
  print_info (info, err);
}

static gboolean
_run_async (PrivStruct * ps)
{
  gint i;

  for (i = 1; i < ps->argc; i++)
    process_file (ps->dc, ps->argv[i]);

  return FALSE;
}

static void
522
_discoverer_finished (GstDiscoverer * dc, GMainLoop * ml)
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
{
  g_main_loop_quit (ml);
}

int
main (int argc, char **argv)
{
  GError *err = NULL;
  GstDiscoverer *dc;
  gint timeout = 10;
  GOptionEntry options[] = {
    {"async", 'a', 0, G_OPTION_ARG_NONE, &async,
        "Run asynchronously", NULL},
    {"timeout", 't', 0, G_OPTION_ARG_INT, &timeout,
        "Specify timeout (in seconds, default 10)", "T"},
    /* {"elem", 'e', 0, G_OPTION_ARG_NONE, &elem_seek, */
    /*     "Seek on elements instead of pads", NULL}, */
540 541
    {"toc", 'c', 0, G_OPTION_ARG_NONE, &show_toc,
        "Output TOC (chapters and editions)", NULL},
542 543 544 545 546 547
    {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
        "Verbose properties", NULL},
    {NULL}
  };
  GOptionContext *ctx;

548 549
  setlocale (LC_ALL, "");

550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
  ctx =
      g_option_context_new
      ("- discover files synchronously with GstDiscoverer");
  g_option_context_add_main_entries (ctx, options, NULL);
  g_option_context_add_group (ctx, gst_init_get_option_group ());

  if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
    g_print ("Error initializing: %s\n", err->message);
    exit (1);
  }

  g_option_context_free (ctx);

  if (argc < 2) {
    g_print ("usage: %s <uris>\n", argv[0]);
    exit (-1);
  }

  dc = gst_discoverer_new (timeout * GST_SECOND, &err);
  if (G_UNLIKELY (dc == NULL)) {
    g_print ("Error initializing: %s\n", err->message);
    exit (1);
  }

574
  if (!async) {
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
    gint i;
    for (i = 1; i < argc; i++)
      process_file (dc, argv[i]);
  } else {
    PrivStruct *ps = g_new0 (PrivStruct, 1);
    GMainLoop *ml = g_main_loop_new (NULL, FALSE);

    ps->dc = dc;
    ps->argc = argc;
    ps->argv = argv;

    /* adding uris will be started when the mainloop runs */
    g_idle_add ((GSourceFunc) _run_async, ps);

    /* connect signals */
    g_signal_connect (dc, "discovered", G_CALLBACK (_new_discovered_uri), NULL);
591
    g_signal_connect (dc, "finished", G_CALLBACK (_discoverer_finished), ml);
592 593 594 595 596 597 598

    gst_discoverer_start (dc);
    /* run mainloop */
    g_main_loop_run (ml);

    gst_discoverer_stop (dc);
    g_free (ps);
599
    g_main_loop_unref (ml);
600 601 602 603 604
  }
  g_object_unref (dc);

  return 0;
}