diff options
author | Ronald S. Bultje <rbultje@ronald.bitfreak.net> | 2004-11-25 20:14:32 +0000 |
---|---|---|
committer | Ronald S. Bultje <rbultje@ronald.bitfreak.net> | 2004-11-25 20:14:32 +0000 |
commit | a9434b9d75a8c7b99fb34289837e81e63dffddd9 (patch) | |
tree | 54c321ac10db5e5a6e112fcb9a7d754126f2156d | |
parent | 56c00b370a12ae1fd0f1d634238b3f6c83ee4e27 (diff) | |
download | gst-plugins-bad-a9434b9d75a8c7b99fb34289837e81e63dffddd9.tar.gz gst-plugins-bad-a9434b9d75a8c7b99fb34289837e81e63dffddd9.tar.bz2 gst-plugins-bad-a9434b9d75a8c7b99fb34289837e81e63dffddd9.zip |
APE v1/2 tag reader plus typefind function.
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.
-rw-r--r-- | ChangeLog | 21 | ||||
-rw-r--r-- | configure.ac | 4 | ||||
-rw-r--r-- | gst/apetag/Makefile.am | 11 | ||||
-rw-r--r-- | gst/apetag/apedemux.c | 772 | ||||
-rw-r--r-- | gst/apetag/apedemux.h | 64 | ||||
-rw-r--r-- | gst/apetag/apetag.c | 40 |
6 files changed, 911 insertions, 1 deletions
@@ -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) |