APE v1/2 tag reader plus typefind function.
authorRonald S. Bultje <rbultje@ronald.bitfreak.net>
Thu, 25 Nov 2004 20:14:32 +0000 (20:14 +0000)
committerRonald S. Bultje <rbultje@ronald.bitfreak.net>
Thu, 25 Nov 2004 20:14:32 +0000 (20:14 +0000)
Original commit message from CVS:
* configure.ac:
* gst/apetag/Makefile.am:
* gst/apetag/apedemux.c: (gst_ape_demux_get_type),
(gst_ape_demux_base_init), (gst_ape_demux_class_init),
(gst_ape_demux_init), (gst_ape_demux_get_src_formats),
(gst_ape_demux_get_src_query_types),
(gst_ape_demux_handle_src_query), (gst_ape_demux_get_event_mask),
(gst_ape_demux_handle_src_event), (gst_ape_demux_handle_event),
(gst_ape_demux_typefind_peek), (gst_ape_demux_typefind_get_length),
(gst_ape_demux_typefind_suggest), (gst_ape_demux_typefind),
(gst_ape_demux_parse_tags), (gst_ape_demux_stream_init),
(gst_ape_demux_stream_data), (gst_ape_demux_loop),
(gst_ape_demux_change_state):
* gst/apetag/apedemux.h:
* gst/apetag/apetag.c: (plugin_init):
* gst/typefind/gsttypefindfunctions.c: (apetag_type_find),
(plugin_init):
APE v1/2 tag reader plus typefind function.

ChangeLog
configure.ac
gst/apetag/Makefile.am [new file with mode: 0644]
gst/apetag/apedemux.c [new file with mode: 0644]
gst/apetag/apedemux.h [new file with mode: 0644]
gst/apetag/apetag.c [new file with mode: 0644]

index 9c429c2..1ff39fe 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,27 @@
 2004-11-25  Ronald S. Bultje  <rbultje@ronald.bitfreak.net>
 
        * configure.ac:
+       * gst/apetag/Makefile.am:
+       * gst/apetag/apedemux.c: (gst_ape_demux_get_type),
+       (gst_ape_demux_base_init), (gst_ape_demux_class_init),
+       (gst_ape_demux_init), (gst_ape_demux_get_src_formats),
+       (gst_ape_demux_get_src_query_types),
+       (gst_ape_demux_handle_src_query), (gst_ape_demux_get_event_mask),
+       (gst_ape_demux_handle_src_event), (gst_ape_demux_handle_event),
+       (gst_ape_demux_typefind_peek), (gst_ape_demux_typefind_get_length),
+       (gst_ape_demux_typefind_suggest), (gst_ape_demux_typefind),
+       (gst_ape_demux_parse_tags), (gst_ape_demux_stream_init),
+       (gst_ape_demux_stream_data), (gst_ape_demux_loop),
+       (gst_ape_demux_change_state):
+       * gst/apetag/apedemux.h:
+       * gst/apetag/apetag.c: (plugin_init):
+       * gst/typefind/gsttypefindfunctions.c: (apetag_type_find),
+       (plugin_init):
+         APE v1/2 tag reader plus typefind function.
+
+2004-11-25  Ronald S. Bultje  <rbultje@ronald.bitfreak.net>
+
+       * configure.ac:
        * gst/playback/gstplaybasebin.c: (gst_play_base_bin_add_element):
        * gst/typefind/gsttypefindfunctions.c: (mp3_type_find):
          Remove hacks for older core. Require newer core version
index 025b85a..d7b8413 100644 (file)
@@ -329,6 +329,7 @@ GST_PLUGINS_ALL="\
        ac3parse \
        adder \
        alpha \
+       apetag \
        asfdemux \
        audioconvert \
        audioscale \
@@ -1846,12 +1847,13 @@ gst/Makefile
 gst/ac3parse/Makefile
 gst/adder/Makefile
 gst/alpha/Makefile
+gst/apetag/Makefile
+gst/asfdemux/Makefile
 gst/audioconvert/Makefile
 gst/audioscale/Makefile
 gst/audiorate/Makefile
 gst/auparse/Makefile
 gst/avi/Makefile
-gst/asfdemux/Makefile
 gst/cdxaparse/Makefile
 gst/chart/Makefile
 gst/colorspace/Makefile
diff --git a/gst/apetag/Makefile.am b/gst/apetag/Makefile.am
new file mode 100644 (file)
index 0000000..59872d7
--- /dev/null
@@ -0,0 +1,11 @@
+plugin_LTLIBRARIES = libgstapetag.la
+
+libgstapetag_la_SOURCES = \
+       apedemux.c \
+       apetag.c
+libgstapetag_la_CFLAGS = $(GST_CFLAGS)
+libgstapetag_la_LIBADD =
+libgstapetag_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+
+noinst_HEADERS = \
+       apedemux.h
diff --git a/gst/apetag/apedemux.c b/gst/apetag/apedemux.c
new file mode 100644 (file)
index 0000000..8767f1c
--- /dev/null
@@ -0,0 +1,772 @@
+/* GStreamer APEv1/2 tag reader
+ * Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <gst/gst.h>
+#include <gst/bytestream/bytestream.h>
+
+#include "apedemux.h"
+
+GST_DEBUG_CATEGORY_STATIC (apedemux_debug);
+#define GST_CAT_DEFAULT apedemux_debug
+
+static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("application/x-apetag")
+    );
+
+static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,          /* spider/decodebin hack */
+    GST_STATIC_CAPS_ANY);
+
+static void gst_ape_demux_base_init (GstApeDemuxClass * klass);
+static void gst_ape_demux_class_init (GstApeDemuxClass * klass);
+static void gst_ape_demux_init (GstApeDemux * ape);
+
+static void gst_ape_demux_loop (GstElement * element);
+
+static const GstEventMask *gst_ape_demux_get_event_mask (GstPad * pad);
+static gboolean gst_ape_demux_handle_src_event (GstPad * pad, GstEvent * event);
+static const GstFormat *gst_ape_demux_get_src_formats (GstPad * pad);
+static const GstQueryType *gst_ape_demux_get_src_query_types (GstPad * pad);
+static gboolean gst_ape_demux_handle_src_query (GstPad * pad,
+    GstQueryType type, GstFormat * format, gint64 * value);
+
+static GstElementStateReturn gst_ape_demux_change_state (GstElement * element);
+
+static GstElementClass *parent_class = NULL;
+
+GType
+gst_ape_demux_get_type (void)
+{
+  static GType ape_demux_type = 0;
+
+  if (!ape_demux_type) {
+    static const GTypeInfo ape_demux_info = {
+      sizeof (GstApeDemuxClass),
+      (GBaseInitFunc) gst_ape_demux_base_init,
+      NULL,
+      (GClassInitFunc) gst_ape_demux_class_init,
+      NULL,
+      NULL,
+      sizeof (GstApeDemux),
+      0,
+      (GInstanceInitFunc) gst_ape_demux_init,
+    };
+
+    ape_demux_type =
+        g_type_register_static (GST_TYPE_ELEMENT,
+        "GstApeDemux", &ape_demux_info, 0);
+  }
+
+  return ape_demux_type;
+}
+
+static void
+gst_ape_demux_base_init (GstApeDemuxClass * klass)
+{
+  static GstElementDetails gst_ape_demux_details =
+      GST_ELEMENT_DETAILS ("Ape tag reader",
+      "Codec/Demuxer/Audio",
+      "Reads APEv1/2 tags",
+      "Ronald Bultje <rbultje@ronald.bitfreak.net>");
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&sink_templ));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&src_templ));
+  gst_element_class_set_details (element_class, &gst_ape_demux_details);
+}
+
+static void
+gst_ape_demux_class_init (GstApeDemuxClass * klass)
+{
+  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+
+  GST_DEBUG_CATEGORY_INIT (apedemux_debug, "apedemux",
+      0, "Demuxer for APE tag reader");
+
+  parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
+
+  gstelement_class->change_state = gst_ape_demux_change_state;
+}
+
+static void
+gst_ape_demux_init (GstApeDemux * ape)
+{
+  GST_FLAG_SET (ape, GST_ELEMENT_EVENT_AWARE);
+
+  ape->sinkpad =
+      gst_pad_new_from_template (gst_static_pad_template_get (&sink_templ),
+      "sink");
+  gst_element_add_pad (GST_ELEMENT (ape), ape->sinkpad);
+
+#if 0
+  ape->srcpad =
+      gst_pad_new_from_template (gst_static_pad_template_get (&src_templ),
+      "src");
+  gst_pad_set_formats_function (ape->srcpad, gst_ape_demux_get_src_formats);
+  gst_pad_set_event_mask_function (ape->srcpad, gst_ape_demux_get_event_mask);
+  gst_pad_set_event_function (ape->srcpad, gst_ape_demux_handle_src_event);
+  gst_pad_set_query_type_function (ape->srcpad,
+      gst_ape_demux_get_src_query_types);
+  gst_pad_set_query_function (ape->srcpad, gst_ape_demux_handle_src_query);
+  gst_pad_use_explicit_caps (ape->srcpad);
+  gst_element_add_pad (GST_ELEMENT (ape), ape->srcpad);
+#endif
+  ape->srcpad = NULL;
+
+  gst_element_set_loop_function (GST_ELEMENT (ape), gst_ape_demux_loop);
+
+  ape->state = GST_APE_DEMUX_TAGREAD;
+  ape->start_off = ape->end_off = 0;
+}
+
+static const GstFormat *
+gst_ape_demux_get_src_formats (GstPad * pad)
+{
+  static const GstFormat formats[] = {
+    GST_FORMAT_BYTES,
+    0
+  };
+
+  return formats;
+}
+
+static const GstQueryType *
+gst_ape_demux_get_src_query_types (GstPad * pad)
+{
+  static const GstQueryType types[] = {
+    GST_QUERY_TOTAL,
+    GST_QUERY_POSITION,
+    0
+  };
+
+  return types;
+}
+
+static gboolean
+gst_ape_demux_handle_src_query (GstPad * pad,
+    GstQueryType type, GstFormat * format, gint64 * value)
+{
+  GstApeDemux *ape = GST_APE_DEMUX (gst_pad_get_parent (pad));
+  gboolean res;
+
+  res = gst_pad_query (GST_PAD_PEER (ape->sinkpad), type, format, value);
+  if (!res)
+    return FALSE;
+
+  switch (type) {
+    case GST_QUERY_TOTAL:
+      switch (*format) {
+        case GST_FORMAT_BYTES:
+          *value -= (ape->start_off + ape->end_off);
+          break;
+        default:
+          break;
+      }
+      break;
+    case GST_QUERY_POSITION:
+      switch (*format) {
+        case GST_FORMAT_BYTES:
+          *value -= ape->start_off;
+          break;
+        default:
+          break;
+      }
+      break;
+    default:
+      break;
+  }
+
+  return res;
+}
+
+static const GstEventMask *
+gst_ape_demux_get_event_mask (GstPad * pad)
+{
+  static const GstEventMask masks[] = {
+    {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_KEY_UNIT},
+    {0,}
+  };
+
+  return masks;
+}
+
+static gboolean
+gst_ape_demux_handle_src_event (GstPad * pad, GstEvent * event)
+{
+  GstApeDemux *ape = GST_APE_DEMUX (gst_pad_get_parent (pad));
+
+  if (ape->state != GST_APE_DEMUX_IDENTITY)
+    return FALSE;
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SEEK:
+      switch (GST_EVENT_SEEK_FORMAT (event)) {
+        case GST_FORMAT_BYTES:{
+          GstEvent *new;
+          gint64 new_off;
+
+          new_off = GST_EVENT_SEEK_OFFSET (event);
+          new_off += ape->start_off;
+          new = gst_event_new_seek (GST_EVENT_SEEK_TYPE (event), new_off);
+          gst_event_unref (event);
+          event = new;
+          break;
+        }
+        default:
+          break;
+      }
+      break;
+    default:
+      break;
+  }
+
+  return gst_pad_send_event (GST_PAD_PEER (ape->sinkpad), event);
+}
+
+/*
+ * Handle an event during 'open' stage.
+ */
+
+static gboolean
+gst_ape_demux_handle_event (GstApeDemux * ape, GstByteStream * bs)
+{
+  GstEvent *event;
+  guint32 remaining;
+  gboolean res = FALSE;
+
+  gst_bytestream_get_status (bs, &remaining, &event);
+  if (!event) {
+    GST_ELEMENT_ERROR (ape, RESOURCE, READ, (NULL), (NULL));
+    return FALSE;
+  }
+
+  switch (GST_EVENT_TYPE (event)) {
+      /* this shouldn't happen. We definately can't deal with it. */
+    case GST_EVENT_EOS:
+    case GST_EVENT_INTERRUPT:
+      GST_ELEMENT_ERROR (ape, RESOURCE, READ, (NULL),
+          ("Cannot deal with EOS/interrupt events during init stage"));
+      break;
+    case GST_EVENT_DISCONTINUOUS:
+    case GST_EVENT_FLUSH:
+      /* we disregard those during init stage */
+      res = TRUE;
+      break;
+    default:
+      gst_pad_event_default (ape->sinkpad, event);
+      return TRUE;
+  }
+
+  gst_event_unref (event);
+
+  return res;
+}
+
+/*
+ * Find media type. Simple for now.
+ */
+
+typedef struct _GstApeDemuxTypeFind
+{
+  GstApeDemux *ape;
+  GstByteStream *bs;
+  gboolean seekable;
+  guint64 len;
+  GstCaps *caps;
+  guint probability;
+  gboolean stop;
+} GstApeDemuxTypeFind;
+
+static guint8 *
+gst_ape_demux_typefind_peek (gpointer ptr, gint64 offset, guint size)
+{
+  GstApeDemuxTypeFind *apetf = ptr;
+  guint8 *data;
+
+  /* non-seekable first - easy */
+  if (!apetf->seekable || offset == 0) {
+    /* don't seek outside reach */
+    if (offset != 0 || size > apetf->len)
+      return NULL;
+
+    /* try to get data, fatal event *is* fatal for typefinding */
+    while (gst_bytestream_peek_bytes (apetf->bs, &data, size) != size) {
+      if (!gst_ape_demux_handle_event (apetf->ape, apetf->bs)) {
+        apetf->stop = TRUE;
+        return NULL;
+      }
+    }
+
+    return data;
+  }
+
+  /* FIXME: theoretically we could embed mp3 and we'd like to seek
+   * beyond just the beginnings then. */
+  return NULL;
+}
+
+static guint64
+gst_ape_demux_typefind_get_length (gpointer ptr)
+{
+  GstApeDemuxTypeFind *apetf = ptr;
+
+  return apetf->len;
+}
+
+static void
+gst_ape_demux_typefind_suggest (gpointer ptr,
+    guint probability, const GstCaps * caps)
+{
+  GstApeDemuxTypeFind *apetf = ptr;
+
+  GST_LOG ("Found type of mime %s, probability %u",
+      gst_structure_get_name (gst_caps_get_structure (caps, 0)), probability);
+
+  if (probability > apetf->probability) {
+    if (apetf->caps)
+      gst_caps_free (apetf->caps);
+    apetf->caps = gst_caps_copy (caps);
+    apetf->probability = probability;
+  }
+}
+
+static gboolean
+gst_ape_demux_typefind (GstApeDemux * ape,
+    GstByteStream * bs, gboolean seekable)
+{
+  GstApeDemuxTypeFind apetf;
+  GstTypeFind tf;
+  GList *factories;
+
+  GST_LOG ("Doing typefinding now");
+
+  /* prepare */
+  memset (&apetf, 0, sizeof (apetf));
+  memset (&tf, 0, sizeof (tf));
+  tf.peek = gst_ape_demux_typefind_peek;
+  tf.suggest = gst_ape_demux_typefind_suggest;
+  tf.data = &apetf;
+  apetf.bs = bs;
+  apetf.ape = ape;
+  apetf.len = gst_bytestream_length (bs);
+  if (apetf.len != (guint64) - 1) {
+    apetf.len -= ape->start_off + ape->end_off;
+    tf.get_length = gst_ape_demux_typefind_get_length;
+  }
+  apetf.seekable = seekable;
+
+  /* run */
+  for (factories = gst_type_find_factory_get_list ();
+      factories != NULL && !apetf.stop &&
+      apetf.probability < GST_TYPE_FIND_MAXIMUM; factories = factories->next) {
+    gst_type_find_factory_call_function (factories->data, &tf);
+  }
+
+  /* fatal error */
+  if (apetf.stop)
+    return FALSE;
+
+  /* type found? */
+  if (!apetf.caps || apetf.probability < GST_TYPE_FIND_MINIMUM) {
+    GST_ELEMENT_ERROR (ape, STREAM, TYPE_NOT_FOUND, (NULL), (NULL));
+    return FALSE;
+  }
+
+  GST_LOG ("Done typefinding, found mime %s",
+      gst_structure_get_name (gst_caps_get_structure (apetf.caps, 0)));
+
+  ape->srcpad =
+      gst_pad_new_from_template (gst_static_pad_template_get (&src_templ),
+      "src");
+  gst_pad_set_formats_function (ape->srcpad, gst_ape_demux_get_src_formats);
+  gst_pad_set_event_mask_function (ape->srcpad, gst_ape_demux_get_event_mask);
+  gst_pad_set_event_function (ape->srcpad, gst_ape_demux_handle_src_event);
+  gst_pad_set_query_type_function (ape->srcpad,
+      gst_ape_demux_get_src_query_types);
+  gst_pad_set_query_function (ape->srcpad, gst_ape_demux_handle_src_query);
+  gst_pad_use_explicit_caps (ape->srcpad);
+  gst_pad_set_explicit_caps (ape->srcpad, apetf.caps);
+  gst_element_add_pad (GST_ELEMENT (ape), ape->srcpad);
+
+  return TRUE;
+}
+
+/*
+ * Parse tags from a buffer.
+ */
+
+static void
+gst_ape_demux_parse_tags (GstApeDemux * ape, guint8 * data, gint size)
+{
+  GstTagList *taglist = gst_tag_list_new ();
+  gboolean have_tag = FALSE;
+
+  GST_LOG ("Reading tags from chunk of size %u bytes", size);
+
+  /* get rid of header/footer */
+  if (!memcmp (data, "APETAGEX", 8)) {
+    data += 32;
+    size -= 32;
+  }
+  if (!memcmp (data + size - 32, "APETAGEX", 8)) {
+    size -= 32;
+  }
+
+  /* read actual tags - at least 10 bytes for tag header */
+  while (size >= 10) {
+    guint len, n = 8;
+    gchar *tag, *val;
+    const gchar *type = NULL;
+    gboolean i = FALSE;
+
+    /* find tag type and size */
+    len = GST_READ_UINT32_LE (data);
+    while (n < size && data[n] != 0x0)
+      n++;
+    if (n == size)
+      break;
+    g_assert (data[n] == 0x0);
+    n++;
+    if (size - n < len)
+      break;
+
+    /* read */
+    tag = g_strndup (&data[8], n - 9);
+    val = g_strndup (&data[n], len);
+    if (!strcasecmp (tag, "title")) {
+      type = GST_TAG_TITLE;
+    } else if (!strcasecmp (tag, "artist")) {
+      type = GST_TAG_ARTIST;
+    } else if (!strcasecmp (tag, "album")) {
+      type = GST_TAG_ALBUM;
+    } else if (!strcasecmp (tag, "comment")) {
+      type = GST_TAG_COMMENT;
+    } else if (!strcasecmp (tag, "copyright")) {
+      type = GST_TAG_COPYRIGHT;
+    } else if (!strcasecmp (tag, "isrc")) {
+      type = GST_TAG_ISRC;
+    } else if (!strcasecmp (tag, "track")) {
+      type = GST_TAG_TRACK_NUMBER;
+      i = TRUE;
+    }
+    if (type) {
+      GValue v = { 0 };
+
+      if (i) {
+        g_value_init (&v, G_TYPE_INT);
+        g_value_set_int (&v, atoi (val));
+      } else {
+        g_value_init (&v, G_TYPE_STRING);
+        g_value_set_string (&v, val);
+      }
+      gst_tag_list_add_values (taglist, GST_TAG_MERGE_APPEND, type, &v, NULL);
+      g_value_unset (&v);
+      have_tag = TRUE;
+    }
+    GST_DEBUG ("Read tag %s: %s", tag, val);
+    g_free (tag);
+    g_free (val);
+
+    /* move data pointer */
+    size -= len + n;
+    data += len + n;
+  }
+
+  /* let people know */
+  if (have_tag) {
+    gst_element_found_tags (GST_ELEMENT (ape), taglist);
+    /*gst_pad_push (ape->srcpad, GST_DATA (gst_event_new_tag (taglist))); */
+  } else {
+    gst_tag_list_free (taglist);
+  }
+}
+
+/*
+ * "Open" a APEv1/2 file.
+ */
+
+static gboolean
+gst_ape_demux_stream_init (GstApeDemux * ape)
+{
+  GstByteStream *bs;
+  gboolean seekable = TRUE, res = TRUE;
+  guint8 *data;
+  guint32 size = 0;
+
+  GST_LOG ("Initializing stream, stripping tags");
+
+  /* start off, we'll want byte-reading here */
+  bs = gst_bytestream_new (ape->sinkpad);
+
+  /* can we seek? */
+  if (!gst_bytestream_seek (bs, 0, GST_SEEK_METHOD_END)) {
+    seekable = FALSE;
+  } else {
+    if (!gst_bytestream_seek (bs, 0, GST_SEEK_METHOD_SET)) {
+      GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL),
+          ("Couldn't seek back to start - cannot handle that"));
+      res = FALSE;
+      goto the_city;
+    }
+  }
+
+  /* ape tags at start? */
+  while (gst_bytestream_peek_bytes (bs, &data, 32) != 32) {
+    if (!gst_ape_demux_handle_event (ape, bs)) {
+      res = FALSE;
+      goto the_city;
+    }
+  }
+  if (!memcmp (data, "APETAGEX", 8)) {
+    GST_LOG ("Found tags at start");
+
+    /* APEv2 at start of file - note that the flags are useless because
+     * I have yet to see the first writer that writes correct HAS_HEADER
+     * and HAS_FOOTER flags... So we detect it ourselves. */
+    size = GST_READ_UINT32_LE (data + 12);
+
+    /* Size is without the header and with the footer. So add 32 because
+     * we're still at position 0 here (peek != read). */
+    size += 32;
+    while (gst_bytestream_peek_bytes (bs, &data, size) != size) {
+      if (!gst_ape_demux_handle_event (ape, bs)) {
+        res = FALSE;
+        goto the_city;
+      }
+    }
+    gst_ape_demux_parse_tags (ape, data, size);
+    ape->start_off = size;
+  }
+
+  /* if we're not seekable, then this is it already. Flush the tags,
+   * and forward the rest of the data to the next element. */
+  if (!seekable) {
+    if (size != 0)
+      gst_bytestream_flush_fast (bs, size);
+
+    if (!gst_ape_demux_typefind (ape, bs, FALSE)) {
+      res = FALSE;
+      goto the_city;
+    }
+
+    gst_bytestream_get_status (bs, &size, NULL);
+    if (size) {
+      GstBuffer *buf = NULL;
+
+      gst_bytestream_read (bs, &buf, size);
+      g_assert (buf);
+      gst_pad_push (ape->srcpad, GST_DATA (buf));
+    }
+
+    goto the_city;
+  }
+
+  /* now look for tags at the end */
+  if (!gst_bytestream_seek (bs, -32, GST_SEEK_METHOD_END)) {
+    GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL), (NULL));
+    res = FALSE;
+    goto the_city;
+  }
+  while (gst_bytestream_peek_bytes (bs, &data, 32) != 32) {
+    if (!gst_ape_demux_handle_event (ape, bs)) {
+      res = FALSE;
+      goto the_city;
+    }
+  }
+  if (!memcmp (data, "APETAGEX", 8)) {
+    GST_LOG ("Found tags at end");
+
+    /* APEv1/2 at start of file - note that the flags are useless because
+     * I have yet to see the first writer that writes correct HAS_HEADER
+     * and HAS_FOOTER flags... So we detect it ourselves. */
+    size = GST_READ_UINT32_LE (data + 12);
+
+    /* size is without header, so add 32 to detect that. */
+    size += 32;
+    if (!gst_bytestream_seek (bs, -(gint64) size, GST_SEEK_METHOD_END)) {
+      GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL), (NULL));
+      res = FALSE;
+      goto the_city;
+    }
+    while (gst_bytestream_peek_bytes (bs, &data, size) != size) {
+      if (!gst_ape_demux_handle_event (ape, bs)) {
+        res = FALSE;
+        goto the_city;
+      }
+    }
+    if (memcmp (data, "APETAGEX", 8) != 0) {
+      data += 32;
+      size -= 32;
+    }
+    gst_ape_demux_parse_tags (ape, data, size);
+    ape->end_off = size;
+  }
+
+  /* seek back to beginning */
+  if (!gst_bytestream_seek (bs, ape->start_off, GST_SEEK_METHOD_SET)) {
+    GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL), (NULL));
+    res = FALSE;
+    goto the_city;
+  }
+
+  /* get any events */
+  while (gst_bytestream_peek_bytes (bs, &data, 1) != 1) {
+    if (!gst_ape_demux_handle_event (ape, bs)) {
+      res = FALSE;
+      goto the_city;
+    }
+  }
+
+  /* typefind */
+  if (!gst_ape_demux_typefind (ape, bs, TRUE)) {
+    res = FALSE;
+    goto the_city;
+  }
+
+  /* push any leftover data */
+  gst_bytestream_get_status (bs, &size, NULL);
+  if (size) {
+    GstBuffer *buf = NULL;
+
+    gst_bytestream_read (bs, &buf, size);
+    g_assert (buf);
+    gst_pad_push (ape->srcpad, GST_DATA (buf));
+  }
+
+the_city:
+  /* become rich & famous */
+  gst_bytestream_destroy (bs);
+
+  return res;
+}
+
+/*
+ * Forward one buffer (we're an identity here).
+ */
+
+static void
+gst_ape_demux_stream_data (GstApeDemux * ape)
+{
+  GstData *data;
+
+  data = gst_pad_pull (ape->sinkpad);
+  if (GST_IS_EVENT (data)) {
+    GstEvent *event = GST_EVENT (data);
+
+    switch (GST_EVENT_TYPE (event)) {
+      case GST_EVENT_DISCONTINUOUS:{
+        GstEvent *new;
+        gint64 new_off = ape->start_off;
+
+        gst_event_discont_get_value (event, GST_FORMAT_BYTES, &new_off);
+        new_off -= ape->start_off;
+        new = gst_event_new_discontinuous (GST_EVENT_DISCONT_NEW_MEDIA (event),
+            GST_FORMAT_BYTES, new_off, GST_FORMAT_UNDEFINED);
+        gst_event_unref (event);
+        data = GST_DATA (new);
+        break;
+      }
+      default:
+        break;
+    }
+  } else {
+    GstBuffer *buf = GST_BUFFER (data), *kid;
+    gint64 pos, len;
+    GstFormat fmt = GST_FORMAT_BYTES;
+
+    kid = gst_buffer_create_sub (buf, 0, GST_BUFFER_SIZE (buf));
+    GST_BUFFER_OFFSET (kid) -= ape->start_off;
+    gst_buffer_unref (buf);
+    data = GST_DATA (kid);
+
+    /* if the plugin allows us to, see if we're close to eos */
+    if (gst_pad_query (GST_PAD_PEER (ape->sinkpad),
+            GST_QUERY_POSITION, &fmt, &pos) &&
+        gst_pad_query (GST_PAD_PEER (ape->sinkpad),
+            GST_QUERY_TOTAL, &fmt, &len)) {
+      if (pos > len - ape->end_off) {
+        if (pos - GST_BUFFER_SIZE (buf) >= len - ape->end_off) {
+          gst_buffer_unref (kid);
+          data = NULL;
+        } else {
+          GST_BUFFER_SIZE (kid) -= ape->end_off - (len - pos);
+        }
+      }
+    }
+  }
+
+  if (data)
+    gst_pad_push (ape->srcpad, data);
+}
+
+static void
+gst_ape_demux_loop (GstElement * element)
+{
+  GstApeDemux *ape = GST_APE_DEMUX (element);
+
+  switch (ape->state) {
+    case GST_APE_DEMUX_TAGREAD:
+      if (!gst_ape_demux_stream_init (ape))
+        return;
+      GST_LOG ("From now on, we're in identity mode");
+      ape->state = GST_APE_DEMUX_IDENTITY;
+      break;
+
+    case GST_APE_DEMUX_IDENTITY:
+      gst_ape_demux_stream_data (ape);
+      break;
+
+    default:
+      g_assert (0);
+  }
+}
+
+static GstElementStateReturn
+gst_ape_demux_change_state (GstElement * element)
+{
+  GstApeDemux *ape = GST_APE_DEMUX (element);
+
+  switch (GST_STATE_TRANSITION (element)) {
+    case GST_STATE_PAUSED_TO_READY:
+      if (ape->srcpad) {
+        gst_element_remove_pad (element, ape->srcpad);
+        ape->srcpad = NULL;
+      }
+      ape->state = GST_APE_DEMUX_TAGREAD;
+      ape->start_off = ape->end_off = 0;
+      break;
+    default:
+      break;
+  }
+
+  if (GST_ELEMENT_CLASS (parent_class)->change_state)
+    return GST_ELEMENT_CLASS (parent_class)->change_state (element);
+
+  return GST_STATE_SUCCESS;
+}
diff --git a/gst/apetag/apedemux.h b/gst/apetag/apedemux.h
new file mode 100644 (file)
index 0000000..e0be158
--- /dev/null
@@ -0,0 +1,64 @@
+/* GStreamer APEv1/2 tag reader
+ * Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_APE_DEMUX_H__
+#define __GST_APE_DEMUX_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_APE_DEMUX \
+  (gst_ape_demux_get_type ())
+#define GST_APE_DEMUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_APE_DEMUX, GstApeDemux))
+#define GST_APE_DEMUX_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_APE_DEMUX, GstApeDemux))
+#define GST_IS_APE_DEMUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_APE_DEMUX))
+#define GST_IS_APE_DEMUX_CLASS(obj) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_APE_DEMUX))
+
+typedef enum {
+  GST_APE_DEMUX_TAGREAD,
+  GST_APE_DEMUX_IDENTITY
+} GstApeDemuxState;
+
+typedef struct _GstApeDemux {
+  GstElement parent;
+
+  /* pads */
+  GstPad *srcpad, *sinkpad;
+
+  /* tag read state */
+  GstApeDemuxState state;
+
+  /* length of ape tag at start/end */
+  guint64 start_off, end_off;
+} GstApeDemux;
+
+typedef struct _GstApeDemuxClass {
+  GstElementClass parent_class;
+} GstApeDemuxClass;
+
+GType          gst_ape_demux_get_type          (void);
+
+G_END_DECLS
+
+#endif /* __GST_APE_DEMUX_H__ */
diff --git a/gst/apetag/apetag.c b/gst/apetag/apetag.c
new file mode 100644 (file)
index 0000000..854fbe2
--- /dev/null
@@ -0,0 +1,40 @@
+/* GStreamer APEv1/2 tag reader
+ * Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/* Element-Checklist-Version: 5 */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "apedemux.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  if (!gst_library_load ("gstbytestream"))
+    return FALSE;
+
+  return (gst_element_register (plugin, "apedemux",
+          GST_RANK_PRIMARY, GST_TYPE_APE_DEMUX));
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    "apetag",
+    "APEv1/2 tag reader", plugin_init, VERSION, "LGPL", GST_PACKAGE, GST_ORIGIN)