summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog21
-rw-r--r--configure.ac4
-rw-r--r--gst/apetag/Makefile.am11
-rw-r--r--gst/apetag/apedemux.c772
-rw-r--r--gst/apetag/apedemux.h64
-rw-r--r--gst/apetag/apetag.c40
6 files changed, 911 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index 9c429c2f..1ff39fe7 100644
--- 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
diff --git a/configure.ac b/configure.ac
index 025b85a7..d7b84133 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
index 00000000..59872d78
--- /dev/null
+++ b/gst/apetag/Makefile.am
@@ -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
index 00000000..8767f1c3
--- /dev/null
+++ b/gst/apetag/apedemux.c
@@ -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
index 00000000..e0be1589
--- /dev/null
+++ b/gst/apetag/apedemux.h
@@ -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
index 00000000..854fbe23
--- /dev/null
+++ b/gst/apetag/apetag.c
@@ -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)