<commit_before>/*
 * GeeXboX libplayer: a multimedia A/V abstraction layer API.
 * Copyright (C) 2008 <NAME> <<EMAIL>>
 *
 * This file is part of libplayer.
 *
 * libplayer is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * libplayer 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with libplayer; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <math.h>

#include <gst/gst.h>
#include <gst/interfaces/streamvolume.h>

#ifdef USE_X11
#include <gst/interfaces/xoverlay.h>
#endif /* USE_X11 */

#include "player.h"
#include "player_internals.h"
#include "playlist.h"
#include "logs.h"
#include "event.h"
#include "fs_utils.h"
#include "wrapper_gstreamer.h"
#ifdef USE_X11
#include "x11_common.h"
#endif /* USE_X11 */

#define MODULE_NAME "gstreamer"

#define NS_TO_MS(ns) (ns / 1000000)
#define MS_TO_NS(ms) (ms * 1000000)

/* player specific structure */
typedef struct gstreamer_player_s {
  GstBus *bus;
  GstElement *bin;
  GstElement *video_sink;
  GstElement *audio_sink;
  GstElement *volume_ctrl;
} gstreamer_player_t;

typedef struct gstreamer_identifier_s {
  player_t *player;
  mrl_t *mrl;
  char *audio_codec;
  char *video_codec;
  GstElement *bin;
  int flags;
} gstreamer_identifier_t;


static char *
get_uri (mrl_t *m)
{
  switch (m->resource)
  {
  case MRL_RESOURCE_FILE:
  {
    mrl_resource_local_args_t *args;

    args = m->priv;
    if (!args)
      break;

    /* check if given MRL is a relative UNIX path */
    if (args->location[0] == '/')
    {
      char uri[1024];
      snprintf (uri, sizeof (uri), "file://%s", args->location);
      return strdup (uri);
    }

    return strdup (args->location);
  }
  default:
    break;
  }
  return NULL;
}

static void
gstreamer_set_eof (player_t *player)
{
  gstreamer_player_t *g;

  if (!player)
    return;

  g = player->priv;

  /* properly shutdown playback engine */
  gst_element_set_state (g->bin, GST_STATE_NULL);

  /* tell player */
  player_event_send (player, PLAYER_EVENT_PLAYBACK_FINISHED);
}

static gboolean
bus_callback (pl_unused GstBus *bus, GstMessage *msg, gpointer data)
{
  player_t *player      = data;

  pl_log (player, PLAYER_MSG_VERBOSE,
          MODULE_NAME, "Message Type: %s", GST_MESSAGE_TYPE_NAME (msg));

  switch (GST_MESSAGE_TYPE (msg))
  {
  case GST_MESSAGE_EOS:
  {
    pl_log (player, PLAYER_MSG_INFO,
            MODULE_NAME, "Playback of stream has ended");

    gstreamer_set_eof (player);
    break;
  }
  case GST_MESSAGE_ERROR:
  {
    gchar *debug;
    GError *err;

    gst_message_parse_error (msg, &err, &debug);
    g_free (debug);

    pl_log (player, PLAYER_MSG_ERROR,
            MODULE_NAME, "Error: %s", err->message);
    g_error_free (err);

    gstreamer_set_eof (player);
    break;
  }
  case GST_MESSAGE_STATE_CHANGED:
  {
    GstState old_state, new_state;
    gchar *src;

    gst_message_parse_state_changed (msg, &old_state, &new_state, NULL);

    if (old_state == new_state)
      break;

    src = gst_object_get_name (msg->src);
    pl_log (player, PLAYER_MSG_VERBOSE, MODULE_NAME,
            "%s changed state from %s to %s", src,
            gst_element_state_get_name (old_state),
            gst_element_state_get_name (new_state));
    g_free (src);
    break;
  }
  default:
    pl_log (player, PLAYER_MSG_VERBOSE,
            MODULE_NAME, "Unhandled message: %" GST_PTR_FORMAT, msg);
    break;
  }

  return TRUE;
}

#define VIDEO_SINK_NAME "video-sink"

static GstElement *
gstreamer_set_video_sink (player_t *player)
{
  GstElement *sink = NULL;
  int use_x11 = 0;
  int ret;

  if (!player)
    return NULL;

  switch (player->vo)
  {
  case PLAYER_VO_AUTO:
    sink = gst_element_factory_make ("gconfvideosink", VIDEO_SINK_NAME);
    if (!sink)
      sink = gst_element_factory_make ("autovideosink", VIDEO_SINK_NAME);
    break;
  case PLAYER_VO_NULL:
    sink = gst_element_factory_make ("fakesink", VIDEO_SINK_NAME);
    if (sink)
      g_object_set (sink, "sync", TRUE, NULL);
    break;
  case PLAYER_VO_X11:
    sink = gst_element_factory_make ("ximagesink", VIDEO_SINK_NAME);
    use_x11 = 1;
    break;
  case PLAYER_VO_X11_SDL:
    sink = gst_element_factory_make ("sdlvideosink", VIDEO_SINK_NAME);
    use_x11 = 1;
    break;
  case PLAYER_VO_XV:
    sink = gst_element_factory_make ("xvimagesink", VIDEO_SINK_NAME);
    use_x11 = 1;
    break;
  default:
    break;
  }

#ifdef USE_X11
  ret = pl_x11_init (player);
  if (player->vo != PLAYER_VO_AUTO && !ret)
  {
    gst_object_unref (GST_OBJECT (sink));
    pl_log (player, PLAYER_MSG_ERROR,
            MODULE_NAME, "X initialization has failed");
    return NULL;
  }
#endif /* USE_X11 */

  return sink;
}

#define AUDIO_SINK_NAME "audio-sink"

static GstElement *
gstreamer_set_audio_sink (player_t *player)
{
  GstElement *sink = NULL;

  if (!player)
    return NULL;

  switch (player->ao) {
  case PLAYER_AO_AUTO:
    sink = gst_element_factory_make ("gconfaudiosink", AUDIO_SINK_NAME);
    if (!sink)
      sink = gst_element_factory_make ("autoaudiosink", AUDIO_SINK_NAME);
    break;
  case PLAYER_AO_NULL:
    sink = gst_element_factory_make ("fakesink", AUDIO_SINK_NAME);
    break;
  case PLAYER_AO_ALSA:
    sink = gst_element_factory_make ("alsasink", AUDIO_SINK_NAME);
    break;
  case PLAYER_AO_OSS:
    sink = gst_element_factory_make ("osssink", AUDIO_SINK_NAME);
    break;
  case PLAYER_AO_PULSE:
    sink = gst_element_factory_make ("pulsesink", AUDIO_SINK_NAME);
    break;
  default:
    break;
  }

  return sink;
}

#ifdef USE_X11
static GstBusSyncReply
bus_sync_handler_cb (pl_unused GstBus *bus, GstMessage *message, gpointer data)
{
  player_t *player = data;
  const GstStructure *str;
  GstXOverlay *ov;

  str = gst_message_get_structure (message);
  if (!str)
    return GST_BUS_PASS;

  if (!gst_structure_has_name (str, "prepare-xwindow-id"))
    return GST_BUS_PASS;

  ov = GST_X_OVERLAY (GST_MESSAGE_SRC (message));
  if (ov)
    gst_x_overlay_set_xwindow_id (ov, pl_x11_get_window (player->x11));

  return GST_BUS_DROP;
}
#endif /* USE_X11 */

static void
gstreamer_get_tag (GstTagList *list, char **meta, const gchar *tag)
{
  gboolean r;

  if (!list || !tag)
    return;

  if (gst_tag_get_type (tag) == G_TYPE_STRING)
  {
    gchar *value;

    r = gst_tag_list_get_string (list, tag, &value);
    if (!r)
      return;

    PFREE (*meta);
    *meta = strdup (value);

    g_free (value);
  }
  else if (gst_tag_get_type (tag) == G_TYPE_UINT)
  {
    guint value;
    char buf[128] = { 0 };

    r = gst_tag_list_get_uint (list, tag, &value);
    if (!r)
      return;

    snprintf (buf, sizeof (buf), "%d", value);
    PFREE (*meta);
    *meta = strdup (buf);
  }
  else if (gst_tag_get_type (tag) == G_TYPE_INT)
  {
    gint value;
    char buf[128] = { 0 };

    r = gst_tag_list_get_int (list, tag, &value);
    if (!r)
      return;

    snprintf (buf, sizeof (buf), "%d", value);
    PFREE (*meta);
    *meta = strdup (buf);
  }
}

#define GET_TAG(tag, name) \
  gstreamer_get_tag (tags, &meta->tag, GST_TAG_##name);

static void
identify_get_props (gstreamer_identifier_t *id)
{
  GstElement *bin = id->bin;
  mrl_properties_t *prop = id->mrl->prop;
  gint n_video, n_audio, i;

  g_object_get (bin,
                "n-audio", &n_audio,
                "n-video", &n_video,
                NULL);

  pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
          "n_audio=%d, n_video=%d", n_audio, n_video);

  /* note that caps chould change dynamically.. but I don't think libplayer
   * has any mechanism to deal with dynamically changing caps, so I'll
   * ignore installing notify::caps signal handlers on the pads..
   */

  if (n_video > 0)
  {
    GstCaps *caps = NULL;

    if (!prop->video)
      prop->video = mrl_properties_video_new ();

    for (i = 0; i < n_video && !caps; i++)
    {
      GstPad *pad = NULL;
      g_signal_emit_by_name (bin, "get-video-pad", i, &pad);

      pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
              "video: pad=%"GST_PTR_FORMAT, pad);

      if (pad)
      {
        caps = gst_pad_get_negotiated_caps (pad);
        gst_object_unref (pad);
      }
    }

    if (caps)
    {
      GstStructure *s = gst_caps_get_structure (caps, 0);
      gint fps_n, fps_d;

      pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
          "video: caps=%"GST_PTR_FORMAT, caps);

      if (id->video_codec)
        prop->video->codec = strdup (id->video_codec);

      gst_structure_get_int (s, "width",  (gint *) &prop->video->width);
      gst_structure_get_int (s, "height", (gint *) &prop->video->height);

      // XXX prop->video->aspect

      if (gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d))
      {
        prop->video->frameduration = (uint32_t)
          (PLAYER_VIDEO_FRAMEDURATION_RATIO_DIV * (float) fps_d / fps_n);
      }

      gst_caps_unref (caps);
    }
  }

  if (n_audio > 0)
  {
    GstCaps *caps = NULL;

    if (!prop->audio)
      prop->audio = mrl_properties_audio_new ();

    for (i = 0; i < n_audio && !caps; i++)
    {
      GstPad *pad = NULL;
      g_signal_emit_by_name (bin, "get-audio-pad", i, &pad);

      pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
              "audio: pad=%"GST_PTR_FORMAT, pad);

      if (pad)
      {
        caps = gst_pad_get_negotiated_caps (pad);
        gst_object_unref(pad);
      }
    }

    if (caps)
    {
      GstStructure *s = gst_caps_get_structure (caps, 0);

      pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
              "audio: caps=%"GST_PTR_FORMAT, caps);

      // XXX prop->audio->bitrate comes from tags??

      if (id->audio_codec)
        prop->audio->codec = strdup (id->audio_codec);

      gst_structure_get_int (s, "channels", (gint *) &prop->audio->channels);
      gst_structure_get_int (s, "rate",     (gint *) &prop->audio->samplerate);
      gst_structure_get_int (s, "width",    (gint *) &prop->audio->bits);

      gst_caps_unref (caps);
    }
  }
}

static gboolean
identify_bus_callback (pl_unused GstBus *bus, GstMessage *msg, gpointer data)
{
  gstreamer_identifier_t *id = data;

  pl_log (id->player, PLAYER_MSG_VERBOSE,
          MODULE_NAME, "Message Type: %s", GST_MESSAGE_TYPE_NAME (msg));

  switch (GST_MESSAGE_TYPE (msg))
  {
  case GST_MESSAGE_ERROR:
  {
    GError *err = NULL;
    gchar *dbg_info = NULL;

    gst_message_parse_error (msg, &err, &dbg_info);
    pl_log (id->player, PLAYER_MSG_ERROR, MODULE_NAME,
            "error from element: %s: %s",
            GST_OBJECT_NAME (msg->src), err->message);
    pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
            "debugging info: %s", dbg_info ? dbg_info : "none");
    g_error_free (err);
    g_free (dbg_info);
    /* fall-thru */
  }
  case GST_MESSAGE_ASYNC_DONE:
  {
    if (id->mrl->prop)
    {
      GstFormat fmt = GST_FORMAT_TIME;
      gint64 len;

      if (gst_element_query_duration (id->bin, &fmt, &len)
          && (fmt == GST_FORMAT_TIME))
      {
        id->mrl->prop->length = NS_TO_MS (len);

        /* TODO maybe we should attempt a seek to start or end of file to
         * figure out if the seek completes.. but the seek operation
         * completes asynchronously so for now we just assume that if we
         * can figure out the duration, then we can probably seek too..
         */
        id->mrl->prop->seekable = TRUE;

        pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
                "len=%d, assuming seekable..", len);
      }

      identify_get_props (id);
    }

    /* we now have enough stream information, shut it down */
    gst_element_set_state (id->bin, GST_STATE_NULL);
    return FALSE;
  }
  case GST_MESSAGE_TAG:
  {
    GstTagList *tags;
    mrl_metadata_t *meta = id->mrl->meta;

    /* the video/audio codec is needed for props */
    gstreamer_get_tag (tags, &id->video_codec, GST_TAG_VIDEO_CODEC);<commit_msg>gstreamer: len is int64 (thanks to <NAME>)<commit_after>/*
 * GeeXboX libplayer: a multimedia A/V abstraction layer API.
 * Copyright (C) 2008 <NAME> <<EMAIL>>
 *
 * This file is part of libplayer.
 *
 * libplayer is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * libplayer 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with libplayer; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <math.h>

#include <gst/gst.h>
#include <gst/interfaces/streamvolume.h>

#ifdef USE_X11
#include <gst/interfaces/xoverlay.h>
#endif /* USE_X11 */

#include "player.h"
#include "player_internals.h"
#include "playlist.h"
#include "logs.h"
#include "event.h"
#include "fs_utils.h"
#include "wrapper_gstreamer.h"
#ifdef USE_X11
#include "x11_common.h"
#endif /* USE_X11 */

#define MODULE_NAME "gstreamer"

#define NS_TO_MS(ns) (ns / 1000000)
#define MS_TO_NS(ms) (ms * 1000000)

/* player specific structure */
typedef struct gstreamer_player_s {
  GstBus *bus;
  GstElement *bin;
  GstElement *video_sink;
  GstElement *audio_sink;
  GstElement *volume_ctrl;
} gstreamer_player_t;

typedef struct gstreamer_identifier_s {
  player_t *player;
  mrl_t *mrl;
  char *audio_codec;
  char *video_codec;
  GstElement *bin;
  int flags;
} gstreamer_identifier_t;


static char *
get_uri (mrl_t *m)
{
  switch (m->resource)
  {
  case MRL_RESOURCE_FILE:
  {
    mrl_resource_local_args_t *args;

    args = m->priv;
    if (!args)
      break;

    /* check if given MRL is a relative UNIX path */
    if (args->location[0] == '/')
    {
      char uri[1024];
      snprintf (uri, sizeof (uri), "file://%s", args->location);
      return strdup (uri);
    }

    return strdup (args->location);
  }
  default:
    break;
  }
  return NULL;
}

static void
gstreamer_set_eof (player_t *player)
{
  gstreamer_player_t *g;

  if (!player)
    return;

  g = player->priv;

  /* properly shutdown playback engine */
  gst_element_set_state (g->bin, GST_STATE_NULL);

  /* tell player */
  player_event_send (player, PLAYER_EVENT_PLAYBACK_FINISHED);
}

static gboolean
bus_callback (pl_unused GstBus *bus, GstMessage *msg, gpointer data)
{
  player_t *player      = data;

  pl_log (player, PLAYER_MSG_VERBOSE,
          MODULE_NAME, "Message Type: %s", GST_MESSAGE_TYPE_NAME (msg));

  switch (GST_MESSAGE_TYPE (msg))
  {
  case GST_MESSAGE_EOS:
  {
    pl_log (player, PLAYER_MSG_INFO,
            MODULE_NAME, "Playback of stream has ended");

    gstreamer_set_eof (player);
    break;
  }
  case GST_MESSAGE_ERROR:
  {
    gchar *debug;
    GError *err;

    gst_message_parse_error (msg, &err, &debug);
    g_free (debug);

    pl_log (player, PLAYER_MSG_ERROR,
            MODULE_NAME, "Error: %s", err->message);
    g_error_free (err);

    gstreamer_set_eof (player);
    break;
  }
  case GST_MESSAGE_STATE_CHANGED:
  {
    GstState old_state, new_state;
    gchar *src;

    gst_message_parse_state_changed (msg, &old_state, &new_state, NULL);

    if (old_state == new_state)
      break;

    src = gst_object_get_name (msg->src);
    pl_log (player, PLAYER_MSG_VERBOSE, MODULE_NAME,
            "%s changed state from %s to %s", src,
            gst_element_state_get_name (old_state),
            gst_element_state_get_name (new_state));
    g_free (src);
    break;
  }
  default:
    pl_log (player, PLAYER_MSG_VERBOSE,
            MODULE_NAME, "Unhandled message: %" GST_PTR_FORMAT, msg);
    break;
  }

  return TRUE;
}

#define VIDEO_SINK_NAME "video-sink"

static GstElement *
gstreamer_set_video_sink (player_t *player)
{
  GstElement *sink = NULL;
  int use_x11 = 0;
  int ret;

  if (!player)
    return NULL;

  switch (player->vo)
  {
  case PLAYER_VO_AUTO:
    sink = gst_element_factory_make ("gconfvideosink", VIDEO_SINK_NAME);
    if (!sink)
      sink = gst_element_factory_make ("autovideosink", VIDEO_SINK_NAME);
    break;
  case PLAYER_VO_NULL:
    sink = gst_element_factory_make ("fakesink", VIDEO_SINK_NAME);
    if (sink)
      g_object_set (sink, "sync", TRUE, NULL);
    break;
  case PLAYER_VO_X11:
    sink = gst_element_factory_make ("ximagesink", VIDEO_SINK_NAME);
    use_x11 = 1;
    break;
  case PLAYER_VO_X11_SDL:
    sink = gst_element_factory_make ("sdlvideosink", VIDEO_SINK_NAME);
    use_x11 = 1;
    break;
  case PLAYER_VO_XV:
    sink = gst_element_factory_make ("xvimagesink", VIDEO_SINK_NAME);
    use_x11 = 1;
    break;
  default:
    break;
  }

#ifdef USE_X11
  ret = pl_x11_init (player);
  if (player->vo != PLAYER_VO_AUTO && !ret)
  {
    gst_object_unref (GST_OBJECT (sink));
    pl_log (player, PLAYER_MSG_ERROR,
            MODULE_NAME, "X initialization has failed");
    return NULL;
  }
#endif /* USE_X11 */

  return sink;
}

#define AUDIO_SINK_NAME "audio-sink"

static GstElement *
gstreamer_set_audio_sink (player_t *player)
{
  GstElement *sink = NULL;

  if (!player)
    return NULL;

  switch (player->ao) {
  case PLAYER_AO_AUTO:
    sink = gst_element_factory_make ("gconfaudiosink", AUDIO_SINK_NAME);
    if (!sink)
      sink = gst_element_factory_make ("autoaudiosink", AUDIO_SINK_NAME);
    break;
  case PLAYER_AO_NULL:
    sink = gst_element_factory_make ("fakesink", AUDIO_SINK_NAME);
    break;
  case PLAYER_AO_ALSA:
    sink = gst_element_factory_make ("alsasink", AUDIO_SINK_NAME);
    break;
  case PLAYER_AO_OSS:
    sink = gst_element_factory_make ("osssink", AUDIO_SINK_NAME);
    break;
  case PLAYER_AO_PULSE:
    sink = gst_element_factory_make ("pulsesink", AUDIO_SINK_NAME);
    break;
  default:
    break;
  }

  return sink;
}

#ifdef USE_X11
static GstBusSyncReply
bus_sync_handler_cb (pl_unused GstBus *bus, GstMessage *message, gpointer data)
{
  player_t *player = data;
  const GstStructure *str;
  GstXOverlay *ov;

  str = gst_message_get_structure (message);
  if (!str)
    return GST_BUS_PASS;

  if (!gst_structure_has_name (str, "prepare-xwindow-id"))
    return GST_BUS_PASS;

  ov = GST_X_OVERLAY (GST_MESSAGE_SRC (message));
  if (ov)
    gst_x_overlay_set_xwindow_id (ov, pl_x11_get_window (player->x11));

  return GST_BUS_DROP;
}
#endif /* USE_X11 */

static void
gstreamer_get_tag (GstTagList *list, char **meta, const gchar *tag)
{
  gboolean r;

  if (!list || !tag)
    return;

  if (gst_tag_get_type (tag) == G_TYPE_STRING)
  {
    gchar *value;

    r = gst_tag_list_get_string (list, tag, &value);
    if (!r)
      return;

    PFREE (*meta);
    *meta = strdup (value);

    g_free (value);
  }
  else if (gst_tag_get_type (tag) == G_TYPE_UINT)
  {
    guint value;
    char buf[128] = { 0 };

    r = gst_tag_list_get_uint (list, tag, &value);
    if (!r)
      return;

    snprintf (buf, sizeof (buf), "%d", value);
    PFREE (*meta);
    *meta = strdup (buf);
  }
  else if (gst_tag_get_type (tag) == G_TYPE_INT)
  {
    gint value;
    char buf[128] = { 0 };

    r = gst_tag_list_get_int (list, tag, &value);
    if (!r)
      return;

    snprintf (buf, sizeof (buf), "%d", value);
    PFREE (*meta);
    *meta = strdup (buf);
  }
}

#define GET_TAG(tag, name) \
  gstreamer_get_tag (tags, &meta->tag, GST_TAG_##name);

static void
identify_get_props (gstreamer_identifier_t *id)
{
  GstElement *bin = id->bin;
  mrl_properties_t *prop = id->mrl->prop;
  gint n_video, n_audio, i;

  g_object_get (bin,
                "n-audio", &n_audio,
                "n-video", &n_video,
                NULL);

  pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
          "n_audio=%d, n_video=%d", n_audio, n_video);

  /* note that caps chould change dynamically.. but I don't think libplayer
   * has any mechanism to deal with dynamically changing caps, so I'll
   * ignore installing notify::caps signal handlers on the pads..
   */

  if (n_video > 0)
  {
    GstCaps *caps = NULL;

    if (!prop->video)
      prop->video = mrl_properties_video_new ();

    for (i = 0; i < n_video && !caps; i++)
    {
      GstPad *pad = NULL;
      g_signal_emit_by_name (bin, "get-video-pad", i, &pad);

      pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
              "video: pad=%"GST_PTR_FORMAT, pad);

      if (pad)
      {
        caps = gst_pad_get_negotiated_caps (pad);
        gst_object_unref (pad);
      }
    }

    if (caps)
    {
      GstStructure *s = gst_caps_get_structure (caps, 0);
      gint fps_n, fps_d;

      pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
          "video: caps=%"GST_PTR_FORMAT, caps);

      if (id->video_codec)
        prop->video->codec = strdup (id->video_codec);

      gst_structure_get_int (s, "width",  (gint *) &prop->video->width);
      gst_structure_get_int (s, "height", (gint *) &prop->video->height);

      // XXX prop->video->aspect

      if (gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d))
      {
        prop->video->frameduration = (uint32_t)
          (PLAYER_VIDEO_FRAMEDURATION_RATIO_DIV * (float) fps_d / fps_n);
      }

      gst_caps_unref (caps);
    }
  }

  if (n_audio > 0)
  {
    GstCaps *caps = NULL;

    if (!prop->audio)
      prop->audio = mrl_properties_audio_new ();

    for (i = 0; i < n_audio && !caps; i++)
    {
      GstPad *pad = NULL;
      g_signal_emit_by_name (bin, "get-audio-pad", i, &pad);

      pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
              "audio: pad=%"GST_PTR_FORMAT, pad);

      if (pad)
      {
        caps = gst_pad_get_negotiated_caps (pad);
        gst_object_unref(pad);
      }
    }

    if (caps)
    {
      GstStructure *s = gst_caps_get_structure (caps, 0);

      pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
              "audio: caps=%"GST_PTR_FORMAT, caps);

      // XXX prop->audio->bitrate comes from tags??

      if (id->audio_codec)
        prop->audio->codec = strdup (id->audio_codec);

      gst_structure_get_int (s, "channels", (gint *) &prop->audio->channels);
      gst_structure_get_int (s, "rate",     (gint *) &prop->audio->samplerate);
      gst_structure_get_int (s, "width",    (gint *) &prop->audio->bits);

      gst_caps_unref (caps);
    }
  }
}

static gboolean
identify_bus_callback (pl_unused GstBus *bus, GstMessage *msg, gpointer data)
{
  gstreamer_identifier_t *id = data;

  pl_log (id->player, PLAYER_MSG_VERBOSE,
          MODULE_NAME, "Message Type: %s", GST_MESSAGE_TYPE_NAME (msg));

  switch (GST_MESSAGE_TYPE (msg))
  {
  case GST_MESSAGE_ERROR:
  {
    GError *err = NULL;
    gchar *dbg_info = NULL;

    gst_message_parse_error (msg, &err, &dbg_info);
    pl_log (id->player, PLAYER_MSG_ERROR, MODULE_NAME,
            "error from element: %s: %s",
            GST_OBJECT_NAME (msg->src), err->message);
    pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
            "debugging info: %s", dbg_info ? dbg_info : "none");
    g_error_free (err);
    g_free (dbg_info);
    /* fall-thru */
  }
  case GST_MESSAGE_ASYNC_DONE:
  {
    if (id->mrl->prop)
    {
      GstFormat fmt = GST_FORMAT_TIME;
      gint64 len;

      if (gst_element_query_duration (id->bin, &fmt, &len)
          && (fmt == GST_FORMAT_TIME))
      {
        id->mrl->prop->length = NS_TO_MS (len);

        /* TODO maybe we should attempt a seek to start or end of file to
         * figure out if the seek completes.. but the seek operation
         * completes asynchronously so for now we just assume that if we
         * can figure out the duration, then we can probably seek too..
         */
        id->mrl->prop->seekable = TRUE;

        pl_log (id->player, PLAYER_MSG_VERBOSE, MODULE_NAME,
                "len=%"PRIi64", assuming seekable..", len);
      }

      identify_get_props (id);
    }

    /* we now have enough stream information, shut it down */
    gst_element_set_state (id->bin, GST_STATE_NULL);
    return FALSE;
  }
  case GST_MESSAGE_TAG:
  {
    GstTagList *tags;
    mrl_metadata_t *meta = id->mrl->meta;

    /* the video/audio codec is needed for props */
    gstreamer_get_tag (tags, &id->video_codec, GST_TAG_VIDEO_CODEC);
{'fraction_non_alphanumeric': 0.12005245588936576, 'fraction_numerical': 0.007629947544110634, 'mean_word_length': 3.0845641941243302, 'pattern_counts': {'":': 0, '<': 38, '<?xml version=': 0, '>': 176, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '321386', 'n_tokens_mistral': 9758, 'n_tokens_neox': 9365, 'n_words': 2251}