/* GStreamer
 * Copyright (C) 2010 Collabora Multimedia
 *               2010 Nokia Corporation
 *
 * 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
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

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

#include "pbutils.h"
#include "pbutils-private.h"

static GstDiscovererStreamInfo
    * gst_discoverer_info_copy_int (GstDiscovererStreamInfo * info,
    GHashTable * stream_map);

static GstDiscovererContainerInfo
    * gst_stream_container_info_copy_int (GstDiscovererContainerInfo * ptr,
    GHashTable * stream_map);

static GstDiscovererAudioInfo
    * gst_discoverer_audio_info_copy_int (GstDiscovererAudioInfo * ptr);

static GstDiscovererVideoInfo
    * gst_discoverer_video_info_copy_int (GstDiscovererVideoInfo * ptr);

static GstDiscovererSubtitleInfo
    * gst_discoverer_subtitle_info_copy_int (GstDiscovererSubtitleInfo * ptr);

/* Per-stream information */

G_DEFINE_TYPE (GstDiscovererStreamInfo, gst_discoverer_stream_info,
    G_TYPE_OBJECT);

static void
gst_discoverer_stream_info_init (GstDiscovererStreamInfo * info)
{
  info->stream_number = -1;
}

static void
gst_discoverer_stream_info_finalize (GObject * object)
{
  GstDiscovererStreamInfo *info = (GstDiscovererStreamInfo *) object;

  if (info->next)
    g_object_unref ((GObject *) info->next);

  if (info->caps)
    gst_caps_unref (info->caps);

  if (info->tags)
    gst_tag_list_unref (info->tags);

  if (info->toc)
    gst_toc_unref (info->toc);

  g_free (info->stream_id);

  if (info->misc)
    gst_structure_free (info->misc);

  G_OBJECT_CLASS (gst_discoverer_stream_info_parent_class)->finalize (object);
}

static void
gst_discoverer_stream_info_class_init (GObjectClass * klass)
{
  klass->finalize = gst_discoverer_stream_info_finalize;
}

static GstDiscovererStreamInfo *
gst_discoverer_stream_info_new (void)
{
  return (GstDiscovererStreamInfo *)
      g_object_new (GST_TYPE_DISCOVERER_STREAM_INFO, NULL);
}

static GstDiscovererStreamInfo *
gst_discoverer_info_copy_int (GstDiscovererStreamInfo * info,
    GHashTable * stream_map)
{
  GstDiscovererStreamInfo *ret;
  GType ltyp;

  g_return_val_if_fail (info != NULL, NULL);

  ltyp = G_TYPE_FROM_INSTANCE (info);

  if (ltyp == GST_TYPE_DISCOVERER_CONTAINER_INFO) {
    ret = (GstDiscovererStreamInfo *)
        gst_stream_container_info_copy_int (
        (GstDiscovererContainerInfo *) info, stream_map);
  } else if (ltyp == GST_TYPE_DISCOVERER_AUDIO_INFO) {
    ret = (GstDiscovererStreamInfo *)
        gst_discoverer_audio_info_copy_int ((GstDiscovererAudioInfo *) info);

  } else if (ltyp == GST_TYPE_DISCOVERER_VIDEO_INFO) {
    ret = (GstDiscovererStreamInfo *)
        gst_discoverer_video_info_copy_int ((GstDiscovererVideoInfo *) info);

  } else if (ltyp == GST_TYPE_DISCOVERER_SUBTITLE_INFO) {
    ret = (GstDiscovererStreamInfo *)
        gst_discoverer_subtitle_info_copy_int ((GstDiscovererSubtitleInfo *)
        info);

  } else
    ret = gst_discoverer_stream_info_new ();

  if (info->next) {
    ret->next = gst_discoverer_info_copy_int (info->next, stream_map);
    ret->next->previous = ret;
  }

  if (info->caps)
    ret->caps = gst_caps_copy (info->caps);

  if (info->tags)
    ret->tags = gst_tag_list_copy (info->tags);

  if (info->toc)
    ret->toc = gst_toc_ref (info->toc);

  if (info->stream_id)
    ret->stream_id = g_strdup (info->stream_id);

  if (info->misc)
    ret->misc = gst_structure_copy (info->misc);

  if (stream_map)
    g_hash_table_insert (stream_map, info, ret);

  ret->stream_number = info->stream_number;

  return ret;
}

/* Container information */
G_DEFINE_TYPE (GstDiscovererContainerInfo, gst_discoverer_container_info,
    GST_TYPE_DISCOVERER_STREAM_INFO);

static void
gst_discoverer_container_info_init (GstDiscovererContainerInfo * info)
{
  /* Nothing to initialize */
}

static GstDiscovererContainerInfo *
gst_discoverer_container_info_new (void)
{
  return (GstDiscovererContainerInfo *)
      g_object_new (GST_TYPE_DISCOVERER_CONTAINER_INFO, NULL);
}

static void
gst_discoverer_container_info_finalize (GObject * object)
{
  GstDiscovererContainerInfo *info = (GstDiscovererContainerInfo *) object;
  GList *tmp;

  for (tmp = ((GstDiscovererContainerInfo *) info)->streams; tmp;
      tmp = tmp->next)
    g_object_unref ((GObject *) tmp->data);

  gst_discoverer_stream_info_list_free (info->streams);

  if (info->tags)
    gst_tag_list_unref (info->tags);

  G_OBJECT_CLASS (gst_discoverer_container_info_parent_class)->finalize
      (object);
}

static void
gst_discoverer_container_info_class_init (GObjectClass * klass)
{
  klass->finalize = gst_discoverer_container_info_finalize;
}

static GstDiscovererContainerInfo *
gst_stream_container_info_copy_int (GstDiscovererContainerInfo * ptr,
    GHashTable * stream_map)
{
  GstDiscovererContainerInfo *ret;
  GList *tmp;

  g_return_val_if_fail (ptr != NULL, NULL);

  ret = gst_discoverer_container_info_new ();

  for (tmp = ((GstDiscovererContainerInfo *) ptr)->streams; tmp;
      tmp = tmp->next) {
    GstDiscovererStreamInfo *subtop = gst_discoverer_info_copy_int (tmp->data,
        stream_map);
    ret->streams = g_list_append (ret->streams, subtop);
    if (stream_map)
      g_hash_table_insert (stream_map, tmp->data, subtop);
  }

  if (ptr->tags)
    ret->tags = gst_tag_list_copy (ptr->tags);

  return ret;
}

/* Audio information */
G_DEFINE_TYPE (GstDiscovererAudioInfo, gst_discoverer_audio_info,
    GST_TYPE_DISCOVERER_STREAM_INFO);

static void
gst_discoverer_audio_info_finalize (GObject * object)
{
  GstDiscovererAudioInfo *info = (GstDiscovererAudioInfo *) object;

  g_free (info->language);

  G_OBJECT_CLASS (gst_discoverer_audio_info_parent_class)->finalize (object);
}

static void
gst_discoverer_audio_info_class_init (GObjectClass * klass)
{
  klass->finalize = gst_discoverer_audio_info_finalize;
}

static void
gst_discoverer_audio_info_init (GstDiscovererAudioInfo * info)
{
  info->language = NULL;
}

static GstDiscovererAudioInfo *
gst_discoverer_audio_info_new (void)
{
  return (GstDiscovererAudioInfo *)
      g_object_new (GST_TYPE_DISCOVERER_AUDIO_INFO, NULL);
}

static GstDiscovererAudioInfo *
gst_discoverer_audio_info_copy_int (GstDiscovererAudioInfo * ptr)
{
  GstDiscovererAudioInfo *ret;

  ret = gst_discoverer_audio_info_new ();

  ret->channels = ptr->channels;
  ret->channel_mask = ptr->channel_mask;
  ret->sample_rate = ptr->sample_rate;
  ret->depth = ptr->depth;
  ret->bitrate = ptr->bitrate;
  ret->max_bitrate = ptr->max_bitrate;
  ret->language = g_strdup (ptr->language);

  return ret;
}

/* Subtitle information */
G_DEFINE_TYPE (GstDiscovererSubtitleInfo, gst_discoverer_subtitle_info,
    GST_TYPE_DISCOVERER_STREAM_INFO);

static void
gst_discoverer_subtitle_info_init (GstDiscovererSubtitleInfo * info)
{
  info->language = NULL;
}

static void
gst_discoverer_subtitle_info_finalize (GObject * object)
{
  GstDiscovererSubtitleInfo *info = (GstDiscovererSubtitleInfo *) object;

  g_free (info->language);

  G_OBJECT_CLASS (gst_discoverer_subtitle_info_parent_class)->finalize (object);
}

static void
gst_discoverer_subtitle_info_class_init (GObjectClass * klass)
{
  klass->finalize = gst_discoverer_subtitle_info_finalize;
}

static GstDiscovererSubtitleInfo *
gst_discoverer_subtitle_info_new (void)
{
  return (GstDiscovererSubtitleInfo *)
      g_object_new (GST_TYPE_DISCOVERER_SUBTITLE_INFO, NULL);
}

static GstDiscovererSubtitleInfo *
gst_discoverer_subtitle_info_copy_int (GstDiscovererSubtitleInfo * ptr)
{
  GstDiscovererSubtitleInfo *ret;

  ret = gst_discoverer_subtitle_info_new ();

  ret->language = g_strdup (ptr->language);

  return ret;
}

/* Video information */
G_DEFINE_TYPE (GstDiscovererVideoInfo, gst_discoverer_video_info,
    GST_TYPE_DISCOVERER_STREAM_INFO);

static void
gst_discoverer_video_info_class_init (GObjectClass * klass)
{
  /* Nothing to initialize */
}

static void
gst_discoverer_video_info_init (GstDiscovererVideoInfo * info)
{
  /* Nothing to initialize */
}

static GstDiscovererVideoInfo *
gst_discoverer_video_info_new (void)
{
  return (GstDiscovererVideoInfo *)
      g_object_new (GST_TYPE_DISCOVERER_VIDEO_INFO, NULL);
}

static GstDiscovererVideoInfo *
gst_discoverer_video_info_copy_int (GstDiscovererVideoInfo * ptr)
{
  GstDiscovererVideoInfo *ret;

  ret = gst_discoverer_video_info_new ();

  ret->width = ptr->width;
  ret->height = ptr->height;
  ret->depth = ptr->depth;
  ret->framerate_num = ptr->framerate_num;
  ret->framerate_denom = ptr->framerate_denom;
  ret->par_num = ptr->par_num;
  ret->par_denom = ptr->par_denom;
  ret->interlaced = ptr->interlaced;
  ret->bitrate = ptr->bitrate;
  ret->max_bitrate = ptr->max_bitrate;
  ret->is_image = ptr->is_image;

  return ret;
}

/* Global stream information */
G_DEFINE_TYPE (GstDiscovererInfo, gst_discoverer_info, G_TYPE_OBJECT);

static void
gst_discoverer_info_init (GstDiscovererInfo * info)
{
  info->missing_elements_details = g_ptr_array_new_with_free_func (g_free);
}

static void
gst_discoverer_info_finalize (GObject * object)
{
  GstDiscovererInfo *info = (GstDiscovererInfo *) object;
  g_free (info->uri);

  if (info->stream_info)
    g_object_unref ((GObject *) info->stream_info);

  if (info->misc)
    gst_structure_free (info->misc);

  g_list_free (info->stream_list);

  if (info->tags)
    gst_tag_list_unref (info->tags);

  if (info->toc)
    gst_toc_unref (info->toc);

  g_free (info->cachefile);

  g_ptr_array_unref (info->missing_elements_details);

  G_OBJECT_CLASS (gst_discoverer_info_parent_class)->finalize (object);
}

static GstDiscovererInfo *
gst_discoverer_info_new (void)
{
  return (GstDiscovererInfo *) g_object_new (GST_TYPE_DISCOVERER_INFO, NULL);
}

/**
 * gst_discoverer_info_copy:
 * @ptr: (transfer none): a #GstDiscovererInfo
 *
 * Returns: (transfer full): A copy of the #GstDiscovererInfo
 */
GstDiscovererInfo *
gst_discoverer_info_copy (GstDiscovererInfo * ptr)
{
  GstDiscovererInfo *ret;
  GHashTable *stream_map;
  GList *tmp;

  g_return_val_if_fail (ptr != NULL, NULL);

  stream_map = g_hash_table_new (g_direct_hash, NULL);

  ret = gst_discoverer_info_new ();

  ret->uri = g_strdup (ptr->uri);
  if (ptr->stream_info) {
    ret->stream_info = gst_discoverer_info_copy_int (ptr->stream_info,
        stream_map);
  }
  ret->duration = ptr->duration;
  ret->result = ptr->result;
  ret->seekable = ptr->seekable;
  ret->live = ptr->live;
  if (ptr->misc)
    ret->misc = gst_structure_copy (ptr->misc);

  /* We need to set up the new list of streams to correspond to old one. The
   * list just contains a set of pointers to streams in the stream_info tree,
   * so we keep a map of old stream info objects to the corresponding new
   * ones and use that to figure out correspondence in stream_list. */
  for (tmp = ptr->stream_list; tmp; tmp = tmp->next) {
    GstDiscovererStreamInfo *old_stream = (GstDiscovererStreamInfo *) tmp->data;
    GstDiscovererStreamInfo *new_stream = g_hash_table_lookup (stream_map,
        old_stream);
    g_assert (new_stream != NULL);
    ret->stream_list = g_list_append (ret->stream_list, new_stream);
  }

  if (ptr->tags)
    ret->tags = gst_tag_list_copy (ptr->tags);

  if (ptr->toc)
    ret->toc = gst_toc_ref (ptr->toc);

  g_hash_table_destroy (stream_map);
  return ret;
}

static void
gst_discoverer_info_class_init (GObjectClass * klass)
{
  klass->finalize = gst_discoverer_info_finalize;
}

/**
 * gst_discoverer_stream_info_list_free:
 * @infos: (element-type GstPbutils.DiscovererStreamInfo): a #GList of #GstDiscovererStreamInfo
 *
 * Decrements the reference count of all contained #GstDiscovererStreamInfo
 * and fress the #GList.
 */
void
gst_discoverer_stream_info_list_free (GList * infos)
{
  GList *tmp;

  for (tmp = infos; tmp; tmp = tmp->next)
    gst_discoverer_stream_info_unref ((GstDiscovererStreamInfo *) tmp->data);
  g_list_free (infos);
}

/**
 * gst_discoverer_info_get_streams:
 * @info: a #GstDiscovererInfo
 * @streamtype: a #GType derived from #GstDiscovererStreamInfo
 *
 * Finds the #GstDiscovererStreamInfo contained in @info that match the
 * given @streamtype.
 *
 * Returns: (transfer full) (element-type GstPbutils.DiscovererStreamInfo): A #GList of
 * matching #GstDiscovererStreamInfo. The caller should free it with
 * gst_discoverer_stream_info_list_free().
 */
GList *
gst_discoverer_info_get_streams (GstDiscovererInfo * info, GType streamtype)
{
  GList *tmp, *res = NULL;

  for (tmp = info->stream_list; tmp; tmp = tmp->next) {
    GstDiscovererStreamInfo *stmp = (GstDiscovererStreamInfo *) tmp->data;

    if (G_TYPE_CHECK_INSTANCE_TYPE (stmp, streamtype))
      res = g_list_append (res, gst_discoverer_stream_info_ref (stmp));
  }

  return res;
}

/**
 * gst_discoverer_info_get_audio_streams:
 * @info: a #GstDiscovererInfo
 *
 * Finds all the #GstDiscovererAudioInfo contained in @info
 *
 * Returns: (transfer full) (element-type GstPbutils.DiscovererStreamInfo): A #GList of
 * matching #GstDiscovererStreamInfo. The caller should free it with
 * gst_discoverer_stream_info_list_free().
 */
GList *
gst_discoverer_info_get_audio_streams (GstDiscovererInfo * info)
{
  return gst_discoverer_info_get_streams (info, GST_TYPE_DISCOVERER_AUDIO_INFO);
}

/**
 * gst_discoverer_info_get_video_streams:
 * @info: a #GstDiscovererInfo
 *
 * Finds all the #GstDiscovererVideoInfo contained in @info
 *
 * Returns: (transfer full) (element-type GstPbutils.DiscovererStreamInfo): A #GList of
 * matching #GstDiscovererStreamInfo. The caller should free it with
 * gst_discoverer_stream_info_list_free().
 */
GList *
gst_discoverer_info_get_video_streams (GstDiscovererInfo * info)
{
  return gst_discoverer_info_get_streams (info, GST_TYPE_DISCOVERER_VIDEO_INFO);
}

/**
 * gst_discoverer_info_get_subtitle_streams:
 * @info: a #GstDiscovererInfo
 *
 * Finds all the #GstDiscovererSubtitleInfo contained in @info
 *
 * Returns: (transfer full) (element-type GstPbutils.DiscovererStreamInfo): A #GList of
 * matching #GstDiscovererStreamInfo. The caller should free it with
 * gst_discoverer_stream_info_list_free().
 */
GList *
gst_discoverer_info_get_subtitle_streams (GstDiscovererInfo * info)
{
  return gst_discoverer_info_get_streams (info,
      GST_TYPE_DISCOVERER_SUBTITLE_INFO);
}

/**
 * gst_discoverer_info_get_container_streams:
 * @info: a #GstDiscovererInfo
 *
 * Finds all the #GstDiscovererContainerInfo contained in @info
 *
 * Returns: (transfer full) (element-type GstPbutils.DiscovererStreamInfo): A #GList of
 * matching #GstDiscovererStreamInfo. The caller should free it with
 * gst_discoverer_stream_info_list_free().
 */
GList *
gst_discoverer_info_get_container_streams (GstDiscovererInfo * info)
{
  return gst_discoverer_info_get_streams (info,
      GST_TYPE_DISCOVERER_CONTAINER_INFO);
}

/**
 * gst_discoverer_stream_info_get_stream_type_nick:
 * @info: a #GstDiscovererStreamInfo
 *
 * Returns: a human readable name for the stream type of the given @info (ex : "audio",
 * "container",...).
 */
const gchar *
gst_discoverer_stream_info_get_stream_type_nick (GstDiscovererStreamInfo * info)
{
  if (GST_IS_DISCOVERER_CONTAINER_INFO (info))
    return "container";
  if (GST_IS_DISCOVERER_AUDIO_INFO (info))
    return "audio";
  if (GST_IS_DISCOVERER_VIDEO_INFO (info)) {
    if (gst_discoverer_video_info_is_image ((GstDiscovererVideoInfo *)
            info))
      return "video(image)";
    else
      return "video";
  }
  if (GST_IS_DISCOVERER_SUBTITLE_INFO (info))
    return "subtitles";
  return "unknown";
}

/* ACCESSORS */


#define GENERIC_ACCESSOR_CODE(parent, parenttype, parentgtype, fieldname, type, failval) \
  type parent##_get_##fieldname(const parenttype info) {			\
    g_return_val_if_fail(G_TYPE_CHECK_INSTANCE_TYPE((info), parentgtype), failval); \
    return (info)->fieldname;				\
  }

/**
 * gst_discoverer_stream_info_get_previous:
 * @info: a #GstDiscovererStreamInfo
 *
 * Returns: (transfer full): the previous #GstDiscovererStreamInfo in a chain.
 * %NULL for starting points. Unref with #gst_discoverer_stream_info_unref
 * after usage.
 */
GstDiscovererStreamInfo *
gst_discoverer_stream_info_get_previous (GstDiscovererStreamInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_STREAM_INFO (info), NULL);

  if (info->previous)
    return gst_discoverer_stream_info_ref (info->previous);
  return NULL;
}

/**
 * gst_discoverer_stream_info_get_next:
 * @info: a #GstDiscovererStreamInfo
 *
 * Returns: (transfer full): the next #GstDiscovererStreamInfo in a chain. %NULL
 * for final streams.
 * Unref with #gst_discoverer_stream_info_unref after usage.
 */
GstDiscovererStreamInfo *
gst_discoverer_stream_info_get_next (GstDiscovererStreamInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_STREAM_INFO (info), NULL);

  if (info->next)
    return gst_discoverer_stream_info_ref (info->next);
  return NULL;
}


/**
 * gst_discoverer_stream_info_get_caps:
 * @info: a #GstDiscovererStreamInfo
 *
 * Returns: (transfer full): the #GstCaps of the stream. Unref with
 * #gst_caps_unref after usage.
 */
GstCaps *
gst_discoverer_stream_info_get_caps (GstDiscovererStreamInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_STREAM_INFO (info), NULL);

  if (info->caps)
    return gst_caps_ref (info->caps);
  return NULL;
}

/**
 * gst_discoverer_stream_info_get_tags:
 * @info: a #GstDiscovererStreamInfo
 *
 * Returns: (transfer none): the tags contained in this stream. If you wish to
 * use the tags after the life-time of @info you will need to copy them.
 */
const GstTagList *
gst_discoverer_stream_info_get_tags (GstDiscovererStreamInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_STREAM_INFO (info), NULL);

  return info->tags;
}

/**
 * gst_discoverer_stream_info_get_toc:
 * @info: a #GstDiscovererStreamInfo
 *
 * Returns: (transfer none): the TOC contained in this stream. If you wish to
 * use the TOC after the life-time of @info you will need to copy it.
 */
const GstToc *
gst_discoverer_stream_info_get_toc (GstDiscovererStreamInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_STREAM_INFO (info), NULL);

  return info->toc;
}

/**
 * gst_discoverer_stream_info_get_stream_id:
 * @info: a #GstDiscovererStreamInfo
 *
 * Returns: (transfer none): the stream ID of this stream. If you wish to
 * use the stream ID after the life-time of @info you will need to copy it.
 */
const gchar *
gst_discoverer_stream_info_get_stream_id (GstDiscovererStreamInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_STREAM_INFO (info), NULL);

  return info->stream_id;
}

#ifndef GST_REMOVE_DEPRECATED
/**
 * gst_discoverer_stream_info_get_misc:
 * @info: a #GstDiscovererStreamInfo
 *
 * Deprecated: This functions is deprecated since version 1.4, use
 * #gst_discoverer_info_get_missing_elements_installer_details
 *
 * Returns: (transfer none): additional information regarding the stream (for
 * example codec version, profile, etc..). If you wish to use the #GstStructure
 * after the life-time of @info you will need to copy it.
 */
const GstStructure *
gst_discoverer_stream_info_get_misc (GstDiscovererStreamInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_STREAM_INFO (info), NULL);

  return info->misc;
}
#endif

/**
 * gst_discoverer_stream_info_get_stream_number:
 * @info: a #GstDiscovererStreamInfo
 *
 * Returns: the stream number, -1 if no index could be determined. This property
 * acts as a unique identifier as a 'int' for the stream.
 *
 * Since: 1.20
 */
gint
gst_discoverer_stream_info_get_stream_number (GstDiscovererStreamInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_STREAM_INFO (info), -1);

  return info->stream_number;
}

/* GstDiscovererContainerInfo */

/**
 * gst_discoverer_container_info_get_streams:
 * @info: a #GstDiscovererStreamInfo
 *
 * Returns: (transfer full) (element-type GstPbutils.DiscovererStreamInfo): the list of
 * #GstDiscovererStreamInfo this container stream offers.
 * Free with gst_discoverer_stream_info_list_free() after usage.
 */

GList *
gst_discoverer_container_info_get_streams (GstDiscovererContainerInfo * info)
{
  GList *res = NULL, *tmp;

  g_return_val_if_fail (GST_IS_DISCOVERER_CONTAINER_INFO (info), NULL);

  for (tmp = info->streams; tmp; tmp = tmp->next)
    res =
        g_list_append (res,
        gst_discoverer_stream_info_ref ((GstDiscovererStreamInfo *) tmp->data));

  return res;
}

/**
 * gst_discoverer_container_info_get_tags:
 * @info: a #GstDiscovererStreamInfo
 *
 * Returns: (transfer none): tags specific to the given container. If you wish to use
 * the tags after the life-time of @info, you will need to copy them.
 *
 * Since: 1.20
 */
const GstTagList *
gst_discoverer_container_info_get_tags (const GstDiscovererContainerInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_CONTAINER_INFO (info), NULL);

  return info->tags;
}

/* GstDiscovererAudioInfo */

#define AUDIO_INFO_ACCESSOR_CODE(fieldname, type, failval)		\
  GENERIC_ACCESSOR_CODE(gst_discoverer_audio_info, GstDiscovererAudioInfo*, \
			GST_TYPE_DISCOVERER_AUDIO_INFO,		\
			fieldname, type, failval)

/**
 * gst_discoverer_audio_info_get_channels:
 * @info: a #GstDiscovererAudioInfo
 *
 * Returns: the number of channels in the stream.
 */

AUDIO_INFO_ACCESSOR_CODE (channels, guint, 0);

/**
 * gst_discoverer_audio_info_get_channel_mask:
 * @info: a #GstDiscovererAudioInfo
 *
 * Returns: the channel-mask of the stream, refer to
 * gst_audio_channel_positions_from_mask() for more
 * information.
 *
 * Since: 1.14
 */

AUDIO_INFO_ACCESSOR_CODE (channel_mask, guint64, G_MAXUINT64);

/**
 * gst_discoverer_audio_info_get_sample_rate:
 * @info: a #GstDiscovererAudioInfo
 *
 * Returns: the sample rate of the stream in Hertz.
 */

AUDIO_INFO_ACCESSOR_CODE (sample_rate, guint, 0);

/**
 * gst_discoverer_audio_info_get_depth:
 * @info: a #GstDiscovererAudioInfo
 *
 * Returns: the number of bits used per sample in each channel.
 */

AUDIO_INFO_ACCESSOR_CODE (depth, guint, 0);

/**
 * gst_discoverer_audio_info_get_bitrate:
 * @info: a #GstDiscovererAudioInfo
 *
 * Returns: the average or nominal bitrate of the stream in bits/second.
 */

AUDIO_INFO_ACCESSOR_CODE (bitrate, guint, 0);

/**
 * gst_discoverer_audio_info_get_max_bitrate:
 * @info: a #GstDiscovererAudioInfo
 *
 * Returns: the maximum bitrate of the stream in bits/second.
 */

AUDIO_INFO_ACCESSOR_CODE (max_bitrate, guint, 0);

/**
 * gst_discoverer_audio_info_get_language:
 * @info: a #GstDiscovererAudioInfo
 *
 * Returns: the language of the stream, or NULL if unknown.
 */

AUDIO_INFO_ACCESSOR_CODE (language, const gchar *, NULL);

/* GstDiscovererVideoInfo */

#define VIDEO_INFO_ACCESSOR_CODE(fieldname, type, failval)		\
  GENERIC_ACCESSOR_CODE(gst_discoverer_video_info, GstDiscovererVideoInfo*, \
			GST_TYPE_DISCOVERER_VIDEO_INFO,			\
			fieldname, type, failval)

/**
 * gst_discoverer_video_info_get_width:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: the width of the video stream in pixels.
 */

VIDEO_INFO_ACCESSOR_CODE (width, guint, 0);

/**
 * gst_discoverer_video_info_get_height:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: the height of the video stream in pixels.
 */

VIDEO_INFO_ACCESSOR_CODE (height, guint, 0);

/**
 * gst_discoverer_video_info_get_depth:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: the depth in bits of the video stream.
 */

VIDEO_INFO_ACCESSOR_CODE (depth, guint, 0);

/**
 * gst_discoverer_video_info_get_framerate_num:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: the framerate of the video stream (numerator).
 */

VIDEO_INFO_ACCESSOR_CODE (framerate_num, guint, 0);

/**
 * gst_discoverer_video_info_get_framerate_denom:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: the framerate of the video stream (denominator).
 */

VIDEO_INFO_ACCESSOR_CODE (framerate_denom, guint, 0);

/**
 * gst_discoverer_video_info_get_par_num:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: the Pixel Aspect Ratio (PAR) of the video stream (numerator).
 */

VIDEO_INFO_ACCESSOR_CODE (par_num, guint, 0);

/**
 * gst_discoverer_video_info_get_par_denom:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: the Pixel Aspect Ratio (PAR) of the video stream (denominator).
 */

VIDEO_INFO_ACCESSOR_CODE (par_denom, guint, 0);

/**
 * gst_discoverer_video_info_is_interlaced:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: %TRUE if the stream is interlaced, else %FALSE.
 */
gboolean
gst_discoverer_video_info_is_interlaced (const GstDiscovererVideoInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_VIDEO_INFO (info), FALSE);

  return info->interlaced;
}

/**
 * gst_discoverer_video_info_get_bitrate:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: the average or nominal bitrate of the video stream in bits/second.
 */

VIDEO_INFO_ACCESSOR_CODE (bitrate, guint, 0);

/**
 * gst_discoverer_video_info_get_max_bitrate:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: the maximum bitrate of the video stream in bits/second.
 */

VIDEO_INFO_ACCESSOR_CODE (max_bitrate, guint, 0);

/**
 * gst_discoverer_video_info_is_image:
 * @info: a #GstDiscovererVideoInfo
 *
 * Returns: %TRUE if the video stream corresponds to an image (i.e. only contains
 * one frame).
 */
gboolean
gst_discoverer_video_info_is_image (const GstDiscovererVideoInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_VIDEO_INFO (info), FALSE);

  return info->is_image;
}

/* GstDiscovererSubtitleInfo */

#define SUBTITLE_INFO_ACCESSOR_CODE(fieldname, type, failval)                     \
  GENERIC_ACCESSOR_CODE(gst_discoverer_subtitle_info, GstDiscovererSubtitleInfo*, \
			GST_TYPE_DISCOVERER_SUBTITLE_INFO,                        \
			fieldname, type, failval)

/**
 * gst_discoverer_subtitle_info_get_language:
 * @info: a #GstDiscovererSubtitleInfo
 *
 * Returns: the language of the stream, or NULL if unknown.
 */

SUBTITLE_INFO_ACCESSOR_CODE (language, const gchar *, NULL);

/* GstDiscovererInfo */

#define DISCOVERER_INFO_ACCESSOR_CODE(fieldname, type, failval)		\
  GENERIC_ACCESSOR_CODE(gst_discoverer_info, GstDiscovererInfo*,	\
			GST_TYPE_DISCOVERER_INFO,			\
			fieldname, type, failval)

/**
 * gst_discoverer_info_get_uri:
 * @info: a #GstDiscovererInfo
 *
 * Returns: (transfer none): the URI to which this information corresponds to.
 * Copy it if you wish to use it after the life-time of @info.
 */

DISCOVERER_INFO_ACCESSOR_CODE (uri, const gchar *, NULL);

/**
 * gst_discoverer_info_get_result:
 * @info: a #GstDiscovererInfo
 *
 * Returns: the result of the discovery as a #GstDiscovererResult.
 */

DISCOVERER_INFO_ACCESSOR_CODE (result, GstDiscovererResult, GST_DISCOVERER_OK);

/**
 * gst_discoverer_info_get_stream_info:
 * @info: a #GstDiscovererInfo
 *
 * Returns: (transfer full): the structure (or topology) of the URI as a
 * #GstDiscovererStreamInfo.
 * This structure can be traversed to see the original hierarchy. Unref with
 * gst_discoverer_stream_info_unref() after usage.
 */

GstDiscovererStreamInfo *
gst_discoverer_info_get_stream_info (GstDiscovererInfo * info)
{
  g_return_val_if_fail (GST_IS_DISCOVERER_INFO (info), NULL);

  if (info->stream_info)
    return gst_discoverer_stream_info_ref (info->stream_info);
  return NULL;
}

/**
 * gst_discoverer_info_get_stream_list:
 * @info: a #GstDiscovererInfo
 *
 * Returns: (transfer full) (element-type GstPbutils.DiscovererStreamInfo): the list of
 * all streams contained in the #info. Free after usage
 * with gst_discoverer_stream_info_list_free().
 */
GList *
gst_discoverer_info_get_stream_list (GstDiscovererInfo * info)
{
  GList *res = NULL, *tmp;

  g_return_val_if_fail (GST_IS_DISCOVERER_INFO (info), NULL);

  for (tmp = info->stream_list; tmp; tmp = tmp->next)
    res =
        g_list_append (res,
        gst_discoverer_stream_info_ref ((GstDiscovererStreamInfo *) tmp->data));

  return res;
}

/**
 * gst_discoverer_info_get_duration:
 * @info: a #GstDiscovererInfo
 *
 * Returns: the duration of the URI in #GstClockTime (nanoseconds).
 */

DISCOVERER_INFO_ACCESSOR_CODE (duration, GstClockTime, GST_CLOCK_TIME_NONE);

/**
 * gst_discoverer_info_get_seekable:
 * @info: a #GstDiscovererInfo
 *
 * Returns: the whether the URI is seekable.
 */

DISCOVERER_INFO_ACCESSOR_CODE (seekable, gboolean, FALSE);

/**
 * gst_discoverer_info_get_live:
 * @info: a #GstDiscovererInfo
 *
 * Returns: whether the URI is live.
 *
 * Since: 1.14
 */

DISCOVERER_INFO_ACCESSOR_CODE (live, gboolean, FALSE);

#ifndef GST_REMOVE_DEPRECATED
/**
 * gst_discoverer_info_get_misc:
 * @info: a #GstDiscovererInfo
 *
 * Deprecated: This functions is deprecated since version 1.4, use
 * #gst_discoverer_info_get_missing_elements_installer_details
 *
 * Returns: (transfer none): Miscellaneous information stored as a #GstStructure
 * (for example: information about missing plugins). If you wish to use the
 * #GstStructure after the life-time of @info, you will need to copy it.
 */

DISCOVERER_INFO_ACCESSOR_CODE (misc, const GstStructure *, NULL);

/**
 * gst_discoverer_info_get_tags:
 * @info: a #GstDiscovererInfo
 *
 * Returns: (transfer none): all tags contained in the URI. If you wish to use
 * the tags after the life-time of @info, you will need to copy them.
 *
 * Deprecated: 1.20: Use gst_discoverer_{container,stream}_info_get_tags() instead.
 */

DISCOVERER_INFO_ACCESSOR_CODE (tags, const GstTagList *, NULL);
#endif

/**
 * gst_discoverer_info_get_toc:
 * @info: a #GstDiscovererInfo
 *
 * Returns: (transfer none): TOC contained in the URI. If you wish to use
 * the TOC after the life-time of @info, you will need to copy it.
 */

DISCOVERER_INFO_ACCESSOR_CODE (toc, const GstToc *, NULL);

/**
 * gst_discoverer_info_ref:
 * @info: a #GstDiscovererInfo
 *
 * Increments the reference count of @info.
 *
 * Returns: the same #GstDiscovererInfo object
 */

/**
 * gst_discoverer_info_unref:
 * @info: a #GstDiscovererInfo
 *
 * Decrements the reference count of @info.
 */

/**
 * gst_discoverer_stream_info_ref:
 * @info: a #GstDiscovererStreamInfo
 *
 * Increments the reference count of @info.
 *
 * Returns: the same #GstDiscovererStreamInfo object
 */

/**
 * gst_discoverer_stream_info_unref:
 * @info: a #GstDiscovererStreamInfo
 *
 * Decrements the reference count of @info.
 */


/**
 * gst_discoverer_info_get_missing_elements_installer_details:
 * @info: a #GstDiscovererStreamInfo to retrieve installer detail
 * for the missing element
 *
 * Get the installer details for missing elements
 *
 * Returns: (transfer none) (array zero-terminated=1): An array of strings
 * containing information about how to install the various missing elements
 * for @info to be usable. If you wish to use the strings after the life-time
 * of @info, you will need to copy them.
 *
 * Since: 1.4
 */
const gchar **
gst_discoverer_info_get_missing_elements_installer_details (const
    GstDiscovererInfo * info)
{

  if (info->result != GST_DISCOVERER_MISSING_PLUGINS) {
    GST_WARNING_OBJECT (info, "Trying to get missing element installed details "
        "but result is not 'MISSING_PLUGINS'");

    return NULL;
  }

  if (info->missing_elements_details->pdata[info->missing_elements_details->
          len]) {
    GST_DEBUG ("Adding NULL pointer to the end of missing_elements_details");
    g_ptr_array_add (info->missing_elements_details, NULL);
  }

  return (const gchar **) info->missing_elements_details->pdata;
}
