diff --git a/configure.ac b/configure.ac index b1070ff10c..6889940a4b 100644 --- a/configure.ac +++ b/configure.ac @@ -12,7 +12,7 @@ AM_MAINTAINER_MODE dnl when going to/from release please set the nano (fourth number) right ! dnl releases only do Wall, cvs and prerelease does Werror too -AS_VERSION(gst-plugins, GST_PLUGINS_VERSION, 0, 7, 1, 1, GST_CVS="no", GST_CVS="yes") +AS_VERSION(gst-plugins, GST_PLUGINS_VERSION, 0, 7, 2, 1, GST_CVS="no", GST_CVS="yes") dnl add a suffix to apps if test x$program_suffix = xNONE ; then @@ -189,7 +189,6 @@ fi AC_SUBST(GST_CONTROL_LIBS) - dnl Set up conditionals for (target) architecture: dnl ============================================== @@ -241,6 +240,8 @@ if test "x$HAVE_GTK_22" = "xyes"; then AC_SUBST(GTK_VERSION) GTK_PREFIX=`$PKG_CONFIG --variable=prefix gdk-pixbuf-2.0` GTK_SYSCONFDIR=`$PKG_CONFIG --variable=prefix gdk-pixbuf-2.0` + GDK_PIXBUF_LIBDIR=`$PKG_CONFIG --variable=libdir gdk-pixbuf-2.0` + GDK_PIXBUF_PREFIXDIR=`$PKG_CONFIG --variable=prefix gdk-pixbuf-2.0` AC_SUBST(GTK_BASE_DIR) else PKG_CHECK_MODULES(GTK2, gtk+-2.0, HAVE_GTK_20=yes, HAVE_GTK_20=no) @@ -255,7 +256,7 @@ AC_SUBST(GTK_CFLAGS) AC_SUBST(HAVE_GTK) AM_CONDITIONAL(HAVE_GDK_LOADERS, test "x$HAVE_GTK_22" = "xyes") -GDK_PIXBUF_LOADER_DIR="\$(libdir)/gtk-2.0/\$(GTK_VERSION)/\loaders" +GDK_PIXBUF_LOADER_DIR="$GDK_PIXBUF_LIBDIR/gtk-2.0/\$(GTK_VERSION)/loaders" AC_ARG_WITH(gdk-pixbuf-loader-dir, AC_HELP_STRING([--with-gdk-pixbuf-loader-dir], [directory to install the gdk_pixbuf loader]), @@ -265,7 +266,7 @@ AC_ARG_WITH(gdk-pixbuf-loader-dir, ]) AC_SUBST(GDK_PIXBUF_LOADER_DIR) -GDK_PIXBUF_CONFFILE="\$(sysconfdir)/gtk-2.0/gdk-pixbuf.loaders" +GDK_PIXBUF_CONFFILE="$GDK_PIXBUF_PREFIXDIR/gtk-2.0/gdk-pixbuf.loaders" AC_ARG_WITH(gdk-pixbuf-conffile, AC_HELP_STRING([--with-gdk-pixbuf-conffile], [path to the gdk_pixbuf config file]), @@ -334,6 +335,7 @@ GST_PLUGINS_ALL="\ speed \ stereo \ synaesthesia \ + tags \ tcp \ typefind \ udp \ @@ -970,16 +972,15 @@ dnl FIXME: we could use header checks here as well IMO translit(dnm, m, l) AM_CONDITIONAL(USE_MAD, true) GST_CHECK_FEATURE(MAD, [mad mp3 decoder], mad, [ dnl check with pkg-config first - PKG_CHECK_MODULES(MAD, mad id3tag, HAVE_MAD="yes", HAVE_MAD="no") + PKG_CHECK_MODULES(MAD, mad >= 0.15 id3tag >= 0.15, HAVE_MAD="yes", HAVE_MAD="no") if test "x$HAVE_MAD" = "xno"; then dnl fall back to oldskool detection AC_CHECK_LIB(mad, mad_decoder_finish, HAVE_MAD="yes" MAD_LIBS="-lmad") if test "x$HAVE_MAD" = "xyes"; then - # installed with mad >= 0.14 HAVE_MAD="no" save_libs=$LIBS LIBS="-lz" - AC_CHECK_LIB(id3tag, id3_tag_query, HAVE_MAD="yes" MAD_LIBS="$MAD_LIBS -lid3tag") + AC_CHECK_LIB(id3tag, id3_tag_options, HAVE_MAD="yes" MAD_LIBS="-lmad -lid3tag -lz") LIBS=$save_LIBS fi fi @@ -1096,6 +1097,13 @@ GST_CHECK_FEATURE(TARKIN, [tarkinenc tarkindec], tarkin, [ HAVE_TARKIN="yes" ]) +dnl *** ogg *** +translit(dnm, m, l) AM_CONDITIONAL(USE_OGG, true) +GST_CHECK_FEATURE(OGG, [ogg de/encoder], oggdemux oggmux, [ + XIPH_PATH_OGG(HAVE_OGG=yes, HAVE_OGG=no) + AS_SCRUB_INCLUDE(OGG_CFLAGS) +]) + dnl *** vorbis *** dnl AM_PATH_VORBIS only takes two options translit(dnm, m, l) AM_CONDITIONAL(USE_VORBIS, true) @@ -1365,6 +1373,7 @@ gst/spectrum/Makefile gst/speed/Makefile gst/stereo/Makefile gst/synaesthesia/Makefile +gst/tags/Makefile gst/tcp/Makefile gst/typefind/Makefile gst/udp/Makefile @@ -1427,6 +1436,7 @@ ext/mas/Makefile ext/mikmod/Makefile ext/mpeg2dec/Makefile ext/mplex/Makefile +ext/ogg/Makefile ext/pango/Makefile ext/raw1394/Makefile ext/sdl/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index d70a37e80c..a26660d717 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -196,6 +196,12 @@ else MPLEX_DIR= endif +if USE_OGG +OGG_DIR=ogg +else +OGG_DIR= +endif + if USE_PANGO PANGO_DIR=pango else @@ -313,6 +319,7 @@ SUBDIRS=\ $(MIKMOD_DIR) \ $(MPEG2DEC_DIR) \ $(MPLEX_DIR) \ + $(OGG_DIR) \ $(PANGO_DIR) \ $(RAW1394_DIR) \ $(SDL_DIR) \ @@ -361,6 +368,7 @@ DIST_SUBDIRS=\ mikmod \ mpeg2dec \ mplex \ + ogg \ pango \ raw1394 \ sdl \ diff --git a/ext/mad/Makefile.am b/ext/mad/Makefile.am index 515fd1d64f..e9a738a01c 100644 --- a/ext/mad/Makefile.am +++ b/ext/mad/Makefile.am @@ -1,8 +1,9 @@ +plugin_LTLIBRARIES = libgstmad.la -plugin_LTLIBRARIES = libgstmad.la +libgstmad_la_SOURCES = gstmad.c gstid3tag.c -libgstmad_la_SOURCES = gstmad.c libgstmad_la_CFLAGS = $(GST_CFLAGS) -libgstmad_la_LIBADD = $(MAD_LIBS) +libgstmad_la_LIBADD = $(MAD_LIBS) $(ID3_LIBS) libgstmad_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +noinst_HEADERS = gstmad.h diff --git a/ext/mad/gstid3tag.c b/ext/mad/gstid3tag.c new file mode 100644 index 0000000000..91b0a2f67b --- /dev/null +++ b/ext/mad/gstid3tag.c @@ -0,0 +1,895 @@ +/* GStreamer + * Copyright (C) 2003 Benjamin Otte + * + * gstid3tagsetter.c: plugin for reading / modifying id3 tags + * + * 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 "gstmad.h" +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_id3_tag_debug); +#define GST_CAT_DEFAULT gst_id3_tag_debug + +#define GST_TYPE_ID3_TAG (gst_id3_tag_get_type()) +#define GST_ID3_TAG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ID3_TAG, GstID3Tag)) +#define GST_ID3_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ID3_TAG, GstID3Tag)) +#define GST_IS_ID3_TAG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ID3_TAG)) +#define GST_IS_ID3_TAG_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ID3_TAG)) + +typedef struct _GstID3Tag GstID3Tag; +typedef struct _GstID3TagClass GstID3TagClass; + +typedef enum { + GST_ID3_TAG_STATE_READING_V2_TAG, + GST_ID3_TAG_STATE_SEEKING_TO_V1_TAG, + GST_ID3_TAG_STATE_READING_V1_TAG, + GST_ID3_TAG_STATE_SEEKING_TO_NORMAL, + GST_ID3_TAG_STATE_NORMAL_START, + GST_ID3_TAG_STATE_NORMAL, +} GstID3TagState; + +struct _GstID3Tag { + GstElement element; + + /* pads */ + GstPad * sinkpad; + GstPad * srcpad; + + /* caps */ + gint layer; + gboolean parse_mode; + + /* tags */ + GstTagList * parsed_tags; + + /* state */ + GstID3TagState state; + + GstBuffer * buffer; + gboolean prefer_v1tag; + glong v1tag_size; + glong v1tag_size_new; + guint64 v1tag_offset; + gboolean v1tag_render; + glong v2tag_size; + glong v2tag_size_new; + gboolean v2tag_render; +}; + +struct _GstID3TagClass { + GstElementClass parent_class; +}; + +/* elementfactory information */ +static GstElementDetails gst_id3_tag_details = GST_ELEMENT_DETAILS ( + "id3 tag extractor", + "Tag", + "Extract tagging information from mp3s", + "Benjamin Otte " +); + + +/* signals and args */ +enum { + /* FILL ME */ + LAST_SIGNAL +}; + +enum { + ARG_0, + ARG_V1_TAG, + ARG_V2_TAG, + ARG_PREFER_V1 + /* FILL ME */ +}; + +GST_PAD_TEMPLATE_FACTORY (id3_tag_src_template_factory, + "src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_CAPS_NEW ( + "id3_tag_data_src", + "audio/mpeg", + "layer", GST_PROPS_INT_RANGE (1, 3) + ), + GST_CAPS_NEW ( + "id3_tag_tag_src", + "application/x-gst-tags", + NULL + ) +) + +GST_PAD_TEMPLATE_FACTORY (id3_tag_sink_template_factory, + "sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_CAPS_NEW ( + "id3_tag_data_sink", + "audio/mpeg", + "layer", GST_PROPS_INT_RANGE (1, 3) + ) +) + + +static void gst_id3_tag_base_init (gpointer g_class); +static void gst_id3_tag_class_init (GstID3TagClass * klass); +static void gst_id3_tag_init (GstID3Tag * tag); +static void gst_id3_tag_set_property (GObject * object, + guint prop_id, + const GValue * value, + GParamSpec * pspec); +static void gst_id3_tag_get_property (GObject * object, + guint prop_id, + GValue * value, + GParamSpec * pspec); + +static gboolean gst_id3_tag_src_event (GstPad * pad, + GstEvent * event); +static const GstEventMask* gst_id3_tag_get_event_masks (GstPad * pad); +static const GstQueryType* gst_id3_tag_get_query_types (GstPad * pad); + +static GstPadLinkReturn gst_id3_tag_link_sink (GstPad * pad, + GstCaps * caps); +static gboolean gst_id3_tag_src_query (GstPad * pad, + GstQueryType type, + GstFormat * format, + gint64 * value); + +static void gst_id3_tag_chain (GstPad * pad, + GstData * data); + +static GstElementStateReturn gst_id3_tag_change_state (GstElement * element); + +static GstElementClass *parent_class = NULL; +/* static guint gst_id3_tag_signals[LAST_SIGNAL] = { 0 }; */ + +GType +gst_id3_tag_get_type (void) +{ + static GType id3_tag_type = 0; + + if (!id3_tag_type) { + static const GTypeInfo id3_tag_info = { + sizeof (GstID3TagClass), + gst_id3_tag_base_init, + NULL, + (GClassInitFunc) gst_id3_tag_class_init, + NULL, + NULL, + sizeof (GstID3Tag), + 0, + (GInstanceInitFunc) gst_id3_tag_init, + }; + static const GInterfaceInfo tag_setter_info = { + NULL, + NULL, + NULL + }; + + id3_tag_type = g_type_register_static(GST_TYPE_ELEMENT, "GstID3Tag", &id3_tag_info, 0); + + g_type_add_interface_static (id3_tag_type, GST_TYPE_TAG_SETTER, &tag_setter_info); + + GST_DEBUG_CATEGORY_INIT (gst_id3_tag_debug, "id3tag", 0, "id3 tag reader / setter"); + } + return id3_tag_type; +} +static void +gst_id3_tag_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details (element_class, &gst_id3_tag_details); + + gst_element_class_add_pad_template (element_class, + GST_PAD_TEMPLATE_GET (id3_tag_sink_template_factory)); + gst_element_class_add_pad_template (element_class, + GST_PAD_TEMPLATE_GET (id3_tag_src_template_factory)); +} +static void +gst_id3_tag_class_init (GstID3TagClass *klass) +{ + GstElementClass *gstelement_class; + GObjectClass *gobject_class; + + gstelement_class = (GstElementClass*) klass; + gobject_class = (GObjectClass*) klass; + + parent_class = g_type_class_ref(GST_TYPE_ELEMENT); + + gstelement_class->change_state = gst_id3_tag_change_state; + + g_object_class_install_property (gobject_class, ARG_V1_TAG, + g_param_spec_boolean ("v1-tag", "add version 1 tag", "Add version 1 tag at end of file", + FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, ARG_V2_TAG, + g_param_spec_boolean ("v2-tag", "add version 2 tag", "Add version 2 tag at start of file", + TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, ARG_PREFER_V1, + g_param_spec_boolean ("prefer-v1", "prefer version 1 tag", "Prefer tags from tag at end of file", + FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_id3_tag_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_id3_tag_get_property); +} + +static void +gst_id3_tag_init (GstID3Tag *tag) +{ + /* create the sink and src pads */ + tag->sinkpad = gst_pad_new_from_template( + GST_PAD_TEMPLATE_GET (id3_tag_sink_template_factory), "sink"); + gst_element_add_pad (GST_ELEMENT (tag), tag->sinkpad); + gst_pad_set_chain_function (tag->sinkpad, GST_DEBUG_FUNCPTR (gst_id3_tag_chain)); + gst_pad_set_link_function (tag->sinkpad, GST_DEBUG_FUNCPTR (gst_id3_tag_link_sink)); + + tag->srcpad = gst_pad_new_from_template( + GST_PAD_TEMPLATE_GET (id3_tag_src_template_factory), "src"); + gst_element_add_pad (GST_ELEMENT (tag), tag->srcpad); + gst_pad_set_event_function (tag->srcpad, GST_DEBUG_FUNCPTR (gst_id3_tag_src_event)); + gst_pad_set_event_mask_function (tag->srcpad, GST_DEBUG_FUNCPTR (gst_id3_tag_get_event_masks)); + gst_pad_set_query_function (tag->srcpad, GST_DEBUG_FUNCPTR (gst_id3_tag_src_query)); + gst_pad_set_query_type_function (tag->srcpad, GST_DEBUG_FUNCPTR (gst_id3_tag_get_query_types)); + + tag->parsed_tags = NULL; + tag->state = GST_ID3_TAG_STATE_READING_V2_TAG; + tag->buffer = NULL; + + GST_FLAG_SET (tag, GST_ELEMENT_EVENT_AWARE); +} +static void +gst_id3_tag_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GstID3Tag *tag; + + tag = GST_ID3_TAG (object); + + switch (prop_id) { + case ARG_V1_TAG: + tag->v1tag_render = g_value_get_boolean (value); + tag->v1tag_size_new = (tag->v1tag_render && (tag->parsed_tags != NULL || + gst_tag_setter_get_list (GST_TAG_SETTER (tag)) != NULL)) ? 128 : 0; + g_object_notify (object, "v1-tag"); + break; + case ARG_V2_TAG: + tag->v2tag_render = g_value_get_boolean (value); + g_object_notify (object, "v2-tag"); + break; + case ARG_PREFER_V1: + tag->prefer_v1tag = g_value_get_boolean (value); + g_object_notify (object, "prefer-v1"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} +static void +gst_id3_tag_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + GstID3Tag *tag; + tag = GST_ID3_TAG (object); + + switch (prop_id) { + case ARG_V1_TAG: + g_value_set_boolean (value, tag->v1tag_render); + break; + case ARG_V2_TAG: + g_value_set_boolean (value, tag->v2tag_render); + break; + case ARG_PREFER_V1: + g_value_set_boolean (value, tag->prefer_v1tag); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} +static const GstEventMask* +gst_id3_tag_get_event_masks (GstPad *pad) +{ + static const GstEventMask gst_id3_tag_src_event_masks[] = { + { GST_EVENT_SEEK, GST_SEEK_METHOD_SET | + GST_SEEK_FLAG_FLUSH }, + { 0, } + }; + return gst_id3_tag_src_event_masks; +} +static const GstQueryType* +gst_id3_tag_get_query_types (GstPad *pad) +{ + static const GstQueryType gst_id3_tag_src_query_types[] = { + GST_QUERY_TOTAL, + GST_QUERY_POSITION, + 0 + }; + return gst_id3_tag_src_query_types; +} + +static gboolean +gst_id3_tag_src_query (GstPad *pad, GstQueryType type, + GstFormat *format, gint64 *value) +{ + gboolean res = FALSE; + GstID3Tag *tag; + + tag = GST_ID3_TAG (gst_pad_get_parent (pad)); + + switch (type) { + case GST_QUERY_TOTAL: { + switch (*format) { + case GST_FORMAT_BYTES: + if (GST_PAD_PEER (tag->sinkpad) && + tag->state == GST_ID3_TAG_STATE_NORMAL && + gst_pad_query (GST_PAD_PEER (tag->sinkpad), GST_QUERY_TOTAL, format, value)) { + *value -= tag->v2tag_size + tag->v1tag_size; + *value += tag->v2tag_size_new + tag->v1tag_size; + res = TRUE; + } + break; + default: + break; + } + break; + } + case GST_QUERY_POSITION: + switch (*format) { + case GST_FORMAT_BYTES: + if (GST_PAD_PEER (tag->sinkpad) && + gst_pad_query (GST_PAD_PEER (tag->sinkpad), GST_QUERY_POSITION, format, value)) { + if (tag->state == GST_ID3_TAG_STATE_NORMAL) { + *value -= tag->v2tag_size + tag->v2tag_size_new; + } else { + *value = 0; + } + res = TRUE; + } + break; + default: + break; + } + break; + default: + break; + } + return res; +} + +static gboolean +gst_id3_tag_src_event (GstPad *pad, GstEvent *event) +{ + GstID3Tag *tag; + + tag = GST_ID3_TAG (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + if (GST_EVENT_SEEK_FORMAT (event) == GST_FORMAT_BYTES && + tag->state == GST_ID3_TAG_STATE_NORMAL && + GST_PAD_PEER (tag->sinkpad)) { + GstEvent *new; + gint diff; + + switch (GST_EVENT_SEEK_METHOD (event)) { + case GST_SEEK_METHOD_SET: diff = tag->v2tag_size_new - tag->v2tag_size; + case GST_SEEK_METHOD_CUR: diff = 0; + case GST_SEEK_METHOD_END: diff = GST_EVENT_SEEK_OFFSET(event) ? tag->v1tag_size_new - tag->v1tag_size : 0; + default: g_assert_not_reached(); + } + new = gst_event_new_seek (GST_EVENT_SEEK_TYPE (event), + GST_EVENT_SEEK_OFFSET(event) + diff); + gst_event_unref (event); + return gst_pad_send_event (GST_PAD_PEER (tag->sinkpad), new); + } + break; + default: + break; + } + + gst_event_unref (event); + return FALSE; +} +static GstPadLinkReturn +gst_id3_tag_link_sink (GstPad *pad, GstCaps *caps) +{ + GstID3Tag *tag; + + tag = GST_ID3_TAG (gst_pad_get_parent (pad)); + + if (!GST_CAPS_IS_FIXED (caps)) + return GST_PAD_LINK_DELAYED; + + if (!gst_caps_get_int (caps, "layer", &tag->layer)) + return GST_PAD_LINK_DELAYED; + + return GST_PAD_LINK_OK; +} +GstTagList* +gst_mad_id3_to_tag_list(const struct id3_tag *tag) +{ + const struct id3_frame *frame; + const id3_ucs4_t *ucs4; + id3_utf8_t *utf8; + GstTagList *tag_list; + guint i = 0; + + tag_list = gst_tag_list_new (); + + while ((frame = id3_tag_findframe(tag, NULL, i++)) != NULL) { + const union id3_field *field; + unsigned int nstrings, j; + const gchar *tag_name; + /* find me the function to query the frame id */ + gchar *id = g_strndup (frame->id, 5); + + tag_name = gst_tag_from_id3_tag (id); + if (tag_name == NULL) { + g_free (id); + continue; + } + + field = &frame->fields[1]; + nstrings = id3_field_getnstrings(field); + + for (j = 0; j < nstrings; ++j) { + ucs4 = id3_field_getstrings(field, j); + g_assert(ucs4); + + if (strcmp(id, ID3_FRAME_GENRE) == 0) + ucs4 = id3_genre_name(ucs4); + + utf8 = id3_ucs4_utf8duplicate(ucs4); + if (utf8 == 0) + continue; + + /* be sure to add non-string tags here */ + switch (gst_tag_get_type (tag_name)) { + case G_TYPE_UINT: + { + guint tmp; + gchar *check; + tmp = strtoul (utf8, &check, 10); + if (*check != '\0') break; + if (strcmp (tag_name, GST_TAG_DATE) == 0) { + if (tmp == 0) break; + GDate *d = g_date_new_dmy (1, 1, tmp); + tmp = g_date_get_julian (d); + g_date_free (d); + } + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, tag_name, tmp, NULL); + break; + } + default: + g_assert (gst_tag_get_type (tag_name) == G_TYPE_STRING); + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, tag_name, utf8, NULL); + break; + } + free (utf8); + } + g_free (id); + } + + return tag_list; +} +static void +tag_list_to_id3_tag_foreach (const GstTagList *list, const gchar *tag_name, gpointer user_data) +{ + struct id3_frame *frame; + union id3_field *field; + guint values = gst_tag_list_get_tag_size (list, tag_name); + const gchar *id = gst_tag_to_id3_tag (tag_name); + struct id3_tag *tag = (struct id3_tag *) user_data; + + if (id == NULL) + return; + + if (values == 0) + return; + + frame = id3_frame_new (id); + if (id3_tag_attachframe (tag, frame) != 0) { + GST_WARNING ("could not attach frame (%s) to id3 tag", id); + return; + } + field = id3_frame_field (frame, 1); + g_assert (field); + while (values-- > 0) { + id3_ucs4_t *put; + + if (strcmp (tag_name, GST_TAG_DATE) == 0) { + gchar *str; + guint u; + GDate *d; + + g_assert (gst_tag_list_get_uint_index (list, tag_name, values, &u)); + d = g_date_new_julian (u); + str = g_strdup_printf ("%u", (guint) (g_date_get_year (d))); + put = id3_utf8_ucs4duplicate (str); + g_date_free (d); + g_free (str); + } else if (strcmp (tag_name, GST_TAG_TRACK_NUMBER) == 0) { + gchar *str; + guint u; + + g_assert (gst_tag_list_get_uint_index (list, tag_name, values, &u)); + str = g_strdup_printf ("%u", u); + put = id3_utf8_ucs4duplicate (str); + g_free (str); + } else { + gchar *str; + + if (gst_tag_get_type (tag_name) != G_TYPE_STRING) { + GST_WARNING ("unhandled GStreamer tag %s", tag_name); + return; + } + g_assert (gst_tag_list_get_string_index (list, tag_name, values, &str)); + put = id3_utf8_ucs4duplicate (str); + g_free (str); + } + if (id3_field_addstring (field, put) != 0) { + GST_WARNING ("could not add a string to id3 tag field"); + return; + } + } +} +struct id3_tag * +gst_mad_tag_list_to_id3_tag (GstTagList *list) +{ + struct id3_tag *tag; + + tag = id3_tag_new (); + + gst_tag_list_foreach (list, tag_list_to_id3_tag_foreach, tag); + return tag; +} +static void +gst_id3_tag_handle_event (GstPad *pad, GstEvent *event) +{ + GstID3Tag *tag = GST_ID3_TAG (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_DISCONTINUOUS: + switch (tag->state) { + case GST_ID3_TAG_STATE_READING_V2_TAG: + gst_element_error (GST_ELEMENT (tag), "Seek during ID3v2 tag reading"); + break; + case GST_ID3_TAG_STATE_SEEKING_TO_V1_TAG: + /* just assume it's the right seek for now */ + tag->state = GST_ID3_TAG_STATE_READING_V1_TAG; + break; + case GST_ID3_TAG_STATE_READING_V1_TAG: + gst_element_error (GST_ELEMENT (tag), "Seek during ID3v1 tag reading"); + break; + case GST_ID3_TAG_STATE_SEEKING_TO_NORMAL: + /* just assume it's the right seek for now */ + tag->state = GST_ID3_TAG_STATE_NORMAL_START; + break; + case GST_ID3_TAG_STATE_NORMAL_START: + GST_ERROR_OBJECT (tag, "tag event loss, FIXME"); + tag->state = GST_ID3_TAG_STATE_NORMAL; + /* fall through */ + case GST_ID3_TAG_STATE_NORMAL: { + gint64 value; + GstEvent *new; + + if (gst_event_discont_get_value (event, GST_FORMAT_BYTES, &value)) { + value += tag->v1tag_size; + new = gst_event_new_discontinuous (GST_FORMAT_BYTES, value, 0); + gst_data_unref (GST_DATA (event)); + gst_pad_push (tag->srcpad, GST_DATA (new)); + } else { + gst_pad_event_default (pad, event); + } + break; + } + default: + g_assert_not_reached (); + } + break; + case GST_EVENT_TAG: + gst_tag_list_insert (tag->parsed_tags, gst_event_tag_get_list (event), GST_TAG_MERGE_PREPEND); + gst_data_unref (GST_DATA (event)); + break; + case GST_EVENT_EOS: + if (tag->v1tag_render && !tag->parse_mode) { + GstTagList *merged; + struct id3_tag *id3; + + GST_LOG_OBJECT (tag, "rendering v1 tag after eos event"); + merged = gst_tag_list_merge (gst_tag_setter_get_list (GST_TAG_SETTER (tag)), tag->parsed_tags, + gst_tag_setter_get_merge_mode (GST_TAG_SETTER (tag))); + if (merged) { + id3 = gst_mad_tag_list_to_id3_tag (merged); + if (id3) { + GstBuffer *tag_buffer; + + id3_tag_options (id3, ID3_TAG_OPTION_ID3V1, ID3_TAG_OPTION_ID3V1); + tag_buffer = gst_buffer_new_and_alloc (128); + g_assert (128 == id3_tag_render (id3, tag_buffer->data)); + gst_pad_push (tag->srcpad, GST_DATA (tag_buffer)); + id3_tag_delete (id3); + } + gst_tag_list_free (merged); + } + } + /* fall through */ + default: + gst_pad_event_default (pad, event); + break; + } + return; +} +static void +gst_id3_tag_chain (GstPad *pad, GstData *data) +{ + GstID3Tag *tag; + GstBuffer *buffer; + + /* handle events */ + if (GST_IS_EVENT (data)) { + gst_id3_tag_handle_event (pad, GST_EVENT (data)); + return; + } + buffer = GST_BUFFER (data); + + tag = GST_ID3_TAG (gst_pad_get_parent (pad)); + + switch (tag->state) { + case GST_ID3_TAG_STATE_READING_V2_TAG: + if (tag->buffer) { + tag->buffer = gst_buffer_merge (tag->buffer, buffer); + } else { + GstCaps *caps; + /* do caps nego */ +capsnego: + if (tag->layer > 0) { + caps = GST_CAPS_NEW ("id3_tag_data_src", "audio/mpeg", "layer", GST_PROPS_INT (tag->layer)); + } else { + caps = GST_CAPS_NEW ("id3_tag_data_src", "audio/mpeg", NULL); + } + if (gst_pad_try_set_caps (tag->srcpad, caps) != GST_PAD_LINK_REFUSED) { + tag->parse_mode = FALSE; + GST_LOG_OBJECT (tag, "normal operation, using audio/mpeg output"); + } else { + if (gst_pad_try_set_caps (tag->srcpad, + GST_CAPS_NEW ("id3_tag_tag_src", "application/x-gst-tags", NULL)) + != GST_PAD_LINK_REFUSED) { + tag->parse_mode = TRUE; + GST_LOG_OBJECT (tag, "fast operation, just outputting tags"); + } else { + GstCaps *caps = gst_pad_template_get_caps (GST_PAD_TEMPLATE_GET (id3_tag_src_template_factory)); + if (gst_pad_recover_caps_error (tag->srcpad, caps)) { + goto capsnego; + } else { + return; + } + } + } + /* done caps nego */ + tag->buffer = buffer; + } + if (GST_BUFFER_SIZE (tag->buffer) < 10) + return; + if (tag->v2tag_size == 0) { + tag->v2tag_size = id3_tag_query (GST_BUFFER_DATA (tag->buffer), + GST_BUFFER_SIZE (tag->buffer)); + /* no footers supported */ + if (tag->v2tag_size < 0) + tag->v2tag_size = 0; + } + if (tag->v2tag_size != 0) { + if (GST_BUFFER_SIZE (tag->buffer) > tag->v2tag_size) { + struct id3_tag *v2tag; + + v2tag = id3_tag_parse (GST_BUFFER_DATA (tag->buffer), + GST_BUFFER_SIZE (tag->buffer)); + if (v2tag) { + g_assert (tag->parsed_tags == NULL); + tag->parsed_tags = gst_mad_id3_to_tag_list (v2tag); + id3_tag_delete (v2tag); + } else { + GST_WARNING_OBJECT (tag, "detected ID3v2 tag, but couldn't parse it"); + tag->v2tag_size = 0; + } + } else { + return; + } + } + /* seek to ID3v1 tag */ + if (gst_pad_send_event (GST_PAD_PEER (tag->sinkpad), + gst_event_new_seek (GST_FORMAT_BYTES | GST_SEEK_METHOD_END | + GST_SEEK_FLAG_FLUSH, -128))) { + tag->state = GST_ID3_TAG_STATE_SEEKING_TO_V1_TAG; + } else { + GstBuffer *sub; + + tag->state = GST_ID3_TAG_STATE_NORMAL_START; + sub = gst_buffer_create_sub (tag->buffer, tag->v2tag_size, + GST_BUFFER_SIZE (tag->buffer) - tag->v2tag_size); + gst_pad_push (tag->srcpad, GST_DATA (sub)); + } + gst_data_unref (GST_DATA (tag->buffer)); + tag->buffer = NULL; + return; + case GST_ID3_TAG_STATE_SEEKING_TO_V1_TAG: + case GST_ID3_TAG_STATE_SEEKING_TO_NORMAL: + /* we're waiting for the seek to finish, just discard all the stuff */ + gst_data_unref (GST_DATA (buffer)); + return; + case GST_ID3_TAG_STATE_READING_V1_TAG: + if (tag->buffer) { + tag->buffer = gst_buffer_merge (tag->buffer, buffer); + } else { + tag->buffer = buffer; + tag->v1tag_offset = buffer->offset; + } + if (GST_BUFFER_SIZE (tag->buffer) < 128) + return; + g_assert (tag->v1tag_size == 0); + tag->v1tag_size = id3_tag_query (GST_BUFFER_DATA (tag->buffer), + GST_BUFFER_SIZE (tag->buffer)); + if (tag->v1tag_size == 128) { + struct id3_tag *v1tag; + + v1tag = id3_tag_parse (GST_BUFFER_DATA (tag->buffer), + GST_BUFFER_SIZE (tag->buffer)); + if (v1tag) { + GstTagList *newtag; + + newtag = gst_mad_id3_to_tag_list (v1tag); + id3_tag_delete (v1tag); + if (tag->parsed_tags) { + gst_tag_list_insert (tag->parsed_tags, newtag, + tag->prefer_v1tag ? GST_TAG_MERGE_REPLACE : GST_TAG_MERGE_KEEP); + gst_tag_list_free (newtag); + } else { + tag->parsed_tags = newtag; + } + } else { + GST_WARNING_OBJECT (tag, "detected ID3v1 tag, but couldn't parse it"); + tag->v2tag_size = 0; + } + } else if (tag->v1tag_size != 0) { + GST_WARNING_OBJECT (tag, "bad non-ID3v1 tag at end of file"); + tag->v1tag_size = 0; + } + gst_data_unref (GST_DATA (tag->buffer)); + tag->buffer = NULL; + if (!tag->parse_mode) { + /* seek to beginning */ + GST_LOG_OBJECT (tag, "seeking back to beginning"); + if (gst_pad_send_event (GST_PAD_PEER (tag->sinkpad), + gst_event_new_seek (GST_FORMAT_BYTES | GST_SEEK_METHOD_SET | + GST_SEEK_FLAG_FLUSH, tag->v2tag_size))) { + tag->state = GST_ID3_TAG_STATE_SEEKING_TO_NORMAL; + } else { + gst_element_error (GST_ELEMENT (tag), "can't seek back to beginning from reading ID3v1 tag"); + } + } else { + if (tag->parsed_tags) + gst_element_found_tags_for_pad (GST_ELEMENT (tag), tag->srcpad, 0, + gst_tag_list_copy (tag->parsed_tags)); + /* set eos, we're done parsing tags */ + GST_LOG_OBJECT (tag, "setting EOS after reading ID3v1 tag"); + tag->state = GST_ID3_TAG_STATE_NORMAL; + gst_element_set_eos (GST_ELEMENT (tag)); + gst_pad_push (tag->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS))); + } + return; + case GST_ID3_TAG_STATE_NORMAL_START: + if (tag->parse_mode) { + if (tag->parsed_tags) + gst_element_found_tags_for_pad (GST_ELEMENT (tag), tag->srcpad, 0, + gst_tag_list_copy (tag->parsed_tags)); + } else { + struct id3_tag *id3; + GstTagList *merged; + GstBuffer *tag_buffer; + + /* send event */ + if (tag->parsed_tags) + gst_element_found_tags (GST_ELEMENT (tag), tag->parsed_tags); + /* render tag */ + tag->v2tag_size_new = 0; + if (tag->v2tag_render) { + merged = gst_tag_list_merge (gst_tag_setter_get_list (GST_TAG_SETTER (tag)), tag->parsed_tags, + gst_tag_setter_get_merge_mode (GST_TAG_SETTER (tag))); + if (merged) { + id3 = gst_mad_tag_list_to_id3_tag (merged); + if (id3) { + glong estimated; + estimated = id3_tag_render (id3, NULL); + tag_buffer = gst_buffer_new_and_alloc (estimated); + tag->v2tag_size_new = id3_tag_render (id3, GST_BUFFER_DATA (tag_buffer)); + g_assert (estimated >= tag->v2tag_size_new); + GST_BUFFER_SIZE (tag_buffer) = tag->v2tag_size_new; + gst_pad_push (tag->srcpad, GST_DATA (tag_buffer)); + id3_tag_delete (id3); + } + gst_tag_list_free (merged); + } + } + } + /* fall through */ + tag->state = GST_ID3_TAG_STATE_NORMAL; + tag->v1tag_size_new = (tag->v1tag_render && (tag->parsed_tags != NULL || + gst_tag_setter_get_list (GST_TAG_SETTER (tag)) != NULL)) ? 128 : 0; + case GST_ID3_TAG_STATE_NORMAL: + if (tag->parse_mode) { + gst_element_set_eos (GST_ELEMENT (tag)); + gst_pad_push (tag->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS))); + } else { + if (buffer->offset >= tag->v1tag_offset) { + gst_data_unref (GST_DATA (buffer)); + return; + } else if (buffer->offset + buffer->size > tag->v1tag_offset) { + GstBuffer *sub = gst_buffer_create_sub (buffer, 0, + buffer->size - 128); + gst_data_unref (GST_DATA (buffer)); + buffer = sub; + } + gst_pad_push (tag->srcpad, GST_DATA (buffer)); + } + return; + } + g_assert_not_reached (); +} + +static GstElementStateReturn +gst_id3_tag_change_state (GstElement *element) +{ + GstID3Tag *tag; + + tag = GST_ID3_TAG (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_NULL_TO_READY: + break; + case GST_STATE_READY_TO_PAUSED: + g_assert (tag->parsed_tags == NULL); + g_assert (tag->buffer == NULL); + tag->v1tag_size = 0; + tag->v1tag_offset = G_MAXUINT64; + tag->v2tag_size = 0; + tag->layer = 0; + break; + case GST_STATE_PAUSED_TO_PLAYING: + /* do something to get out of the chain function faster */ + break; + case GST_STATE_PLAYING_TO_PAUSED: + break; + case GST_STATE_PAUSED_TO_READY: + if (tag->parsed_tags) { + gst_tag_list_free (tag->parsed_tags); + tag->parsed_tags = NULL; + } + if (tag->buffer) { + gst_data_unref (GST_DATA (tag->buffer)); + tag->buffer = NULL; + } + break; + case GST_STATE_READY_TO_NULL: + break; + } + + return parent_class->change_state (element); +} diff --git a/ext/mad/gstmad.c b/ext/mad/gstmad.c index b024ff1ec6..034c652a7b 100644 --- a/ext/mad/gstmad.c +++ b/ext/mad/gstmad.c @@ -20,13 +20,9 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif -#include - -GST_DEBUG_CATEGORY_EXTERN (GST_CAT_SEEK); #include -#include -#include "id3tag.h" +#include "gstmad.h" #define GST_TYPE_MAD \ (gst_mad_get_type()) @@ -73,8 +69,7 @@ struct _GstMad { gboolean half; gboolean ignore_crc; - GstCaps *metadata; - GstCaps *streaminfo; + GstTagList * tags; /* negotiated format */ gint rate; @@ -294,12 +289,6 @@ gst_mad_class_init (GstMadClass *klass) g_object_class_install_property (gobject_class, ARG_IGNORE_CRC, g_param_spec_boolean ("ignore_crc", "Ignore CRC", "Ignore CRC errors", FALSE, G_PARAM_READWRITE)); - g_object_class_install_property (gobject_class, ARG_METADATA, - g_param_spec_boxed ("metadata", "Metadata", "Metadata", - GST_TYPE_CAPS, G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, ARG_STREAMINFO, - g_param_spec_boxed ("streaminfo", "Streaminfo", "Streaminfo", - GST_TYPE_CAPS, G_PARAM_READABLE)); } static void @@ -337,8 +326,7 @@ gst_mad_init (GstMad *mad) mad->segment_start = 0; mad->header.mode = -1; mad->header.emphasis = -1; - mad->metadata = NULL; - mad->streaminfo = NULL; + mad->tags = NULL; mad->half = FALSE; mad->ignore_crc = FALSE; @@ -625,7 +613,7 @@ index_seek (GstMad *mad, GstPad *pad, GstEvent *event) if (gst_index_entry_assoc_map (entry, *try_formats, &value)) { /* lookup succeeded, create the seek */ - GST_CAT_DEBUG (GST_CAT_SEEK, "index %s %" G_GINT64_FORMAT + GST_DEBUG ("index %s %" G_GINT64_FORMAT " -> %s %" G_GINT64_FORMAT, gst_format_get_details (GST_EVENT_SEEK_FORMAT (event))->nick, GST_EVENT_SEEK_OFFSET (event), @@ -799,54 +787,12 @@ gst_mad_get_property (GObject *object, guint prop_id, case ARG_IGNORE_CRC: g_value_set_boolean (value, mad->ignore_crc); break; - case ARG_METADATA: - g_value_set_boxed (value, mad->metadata); - break; - case ARG_STREAMINFO: - g_value_set_boxed (value, mad->streaminfo); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } -static GstCaps* -gst_mad_get_streaminfo (GstMad *mad) -{ - GstCaps *caps; - GstProps *props; - GstPropsEntry *entry; - GEnumValue *value; - GEnumClass *klass; - - props = gst_props_empty_new (); - - entry = gst_props_entry_new ("layer", GST_PROPS_INT (mad->header.layer)); - gst_props_add_entry (props, (GstPropsEntry *) entry); - - klass = g_type_class_ref (GST_TYPE_MAD_MODE); - value = g_enum_get_value (klass, - mad->header.mode); - if (value) - entry = gst_props_entry_new ("mode", GST_PROPS_STRING (value->value_nick)); - g_type_class_unref (klass); - gst_props_add_entry (props, (GstPropsEntry *) entry); - - klass = g_type_class_ref (GST_TYPE_MAD_EMPHASIS); - value = g_enum_get_value (klass, - mad->header.emphasis); - if (value) - entry = gst_props_entry_new ("emphasis", GST_PROPS_STRING (value->value_nick)); - g_type_class_unref (klass); - gst_props_add_entry (props, (GstPropsEntry *) entry); - - caps = gst_caps_new ("mad_streaminfo", - "application/x-gst-streaminfo", - props); - return caps; -} - static void gst_mad_update_info (GstMad *mad) { @@ -882,148 +828,10 @@ G_STMT_START{ \ } mad->new_header = FALSE; - if (changed) { - gst_caps_replace_sink (&mad->streaminfo, gst_mad_get_streaminfo (mad)); - g_object_notify (G_OBJECT (mad), "streaminfo"); - } #undef CHECK_HEADER } -/* gracefuly ripped from madplay */ -static GstCaps* -id3_to_caps(struct id3_tag const *tag) -{ - unsigned int i; - struct id3_frame const *frame; - id3_ucs4_t const *ucs4; - id3_utf8_t *utf8; - GstProps *props; - GstPropsEntry *entry; - GstCaps *caps; - GList *values; - - struct { - char const *id; - char const *name; - } const info[] = { - { ID3_FRAME_TITLE, "Title" }, - { "TIT3", "Subtitle" }, - { "TCOP", "Copyright" }, - { "TPRO", "Produced" }, - { "TCOM", "Composer" }, - { ID3_FRAME_ARTIST, "Artist" }, - { "TPE2", "Orchestra" }, - { "TPE3", "Conductor" }, - { "TEXT", "Lyricist" }, - { ID3_FRAME_ALBUM, "Album" }, - { ID3_FRAME_YEAR, "Year" }, - { ID3_FRAME_TRACK, "Track" }, - { "TPUB", "Publisher" }, - { ID3_FRAME_GENRE, "Genre" }, - { "TRSN", "Station" }, - { "TENC", "Encoder" }, - }; - - /* text information */ - props = gst_props_empty_new (); - - for (i = 0; i < sizeof(info) / sizeof(info[0]); ++i) { - union id3_field const *field; - unsigned int nstrings, namelen, j; - char const *name; - - frame = id3_tag_findframe(tag, info[i].id, 0); - if (frame == 0) - continue; - - field = &frame->fields[1]; - nstrings = id3_field_getnstrings(field); - - name = info[i].name; - - if (name) { - namelen = name ? strlen(name) : 0; - - values = NULL; - for (j = 0; j < nstrings; ++j) { - ucs4 = id3_field_getstrings(field, j); - g_assert(ucs4); - - if (strcmp(info[i].id, ID3_FRAME_GENRE) == 0) - ucs4 = id3_genre_name(ucs4); - - utf8 = id3_ucs4_utf8duplicate(ucs4); - if (utf8 == 0) - goto fail; - - entry = gst_props_entry_new (name, GST_PROPS_STRING_TYPE, utf8); - values = g_list_prepend (values, entry); - free(utf8); - } - if (values) { - values = g_list_reverse (values); - - if (g_list_length (values) == 1) { - gst_props_add_entry (props, (GstPropsEntry *) values->data); - } - else { - entry = gst_props_entry_new(name, GST_PROPS_GLIST_TYPE, values); - gst_props_add_entry (props, (GstPropsEntry *) entry); - } - g_list_free (values); - } - } - } - - values = NULL; - i = 0; - while ((frame = id3_tag_findframe(tag, ID3_FRAME_COMMENT, i++))) { - ucs4 = id3_field_getstring(&frame->fields[2]); - g_assert(ucs4); - - if (*ucs4) - continue; - - ucs4 = id3_field_getfullstring(&frame->fields[3]); - g_assert(ucs4); - - utf8 = id3_ucs4_utf8duplicate(ucs4); - if (utf8 == 0) - goto fail; - - entry = gst_props_entry_new ("Comment", GST_PROPS_STRING_TYPE, utf8); - values = g_list_prepend (values, entry); - free(utf8); - } - if (values) { - values = g_list_reverse (values); - - if (g_list_length (values) == 1) { - gst_props_add_entry (props, (GstPropsEntry *) values->data); - } - else { - entry = gst_props_entry_new("Comment", GST_PROPS_GLIST_TYPE, values); - gst_props_add_entry (props, (GstPropsEntry *) entry); - } - g_list_free (values); - } - - gst_props_debug (props); - - caps = gst_caps_new ("mad_metadata", - "application/x-gst-metadata", - props); - if (0) { -fail: - g_warning ("mad: could not parse ID3 tag"); - - return NULL; - } - - return caps; -} - static void gst_mad_handle_event (GstPad *pad, GstBuffer *buffer) { @@ -1212,9 +1020,22 @@ gst_mad_chain (GstPad *pad, GstData *_data) tag = id3_tag_parse (data, tagsize); if (tag) { - gst_caps_replace_sink (&mad->metadata, id3_to_caps (tag)); + GstTagList *list; + + list = gst_mad_id3_to_tag_list (tag); id3_tag_delete (tag); - g_object_notify (G_OBJECT (mad), "metadata"); + GST_DEBUG_OBJECT (mad, "found tag"); + gst_element_found_tags (GST_ELEMENT (mad), list); + if (mad->tags) { + gst_tag_list_insert (mad->tags, list, GST_TAG_MERGE_PREPEND); + } else { + mad->tags = gst_tag_list_copy (list); + } + if (GST_PAD_IS_USABLE (mad->srcpad)) { + gst_pad_push (mad->srcpad, GST_DATA (gst_event_new_tag (list))); + } else { + gst_tag_list_free (list); + } } } } @@ -1405,8 +1226,10 @@ gst_mad_change_state (GstElement *element) mad_frame_finish (&mad->frame); mad_stream_finish (&mad->stream); mad->restart = TRUE; - gst_caps_replace (&mad->metadata, NULL); - gst_caps_replace (&mad->streaminfo, NULL); + if (mad->tags) { + gst_tag_list_free (mad->tags); + mad->tags = NULL; + } break; case GST_STATE_READY_TO_NULL: break; @@ -1418,8 +1241,13 @@ gst_mad_change_state (GstElement *element) static gboolean plugin_init (GstPlugin *plugin) { + /* we need the gsttags plugin for metadata querying */ + if (!gst_plugin_load ("gsttags")) + return FALSE; + /* create an elementfactory for the mad element */ - if (!gst_element_register (plugin, "mad", GST_RANK_PRIMARY, GST_TYPE_MAD)) + if (!gst_element_register (plugin, "mad", GST_RANK_PRIMARY, GST_TYPE_MAD) || + !gst_element_register (plugin, "id3tag", GST_RANK_PRIMARY, gst_id3_tag_get_type ())) return FALSE; return TRUE; @@ -1429,7 +1257,7 @@ GST_PLUGIN_DEFINE ( GST_VERSION_MAJOR, GST_VERSION_MINOR, "mad", - "mp3 decoding based on the mad library", + "mp3 decoding and tag editing based on the mad library", plugin_init, VERSION, "GPL", diff --git a/ext/mad/gstmad.h b/ext/mad/gstmad.h new file mode 100644 index 0000000000..87c4932c1c --- /dev/null +++ b/ext/mad/gstmad.h @@ -0,0 +1,41 @@ +/* GStreamer + * Copyright (C) 2003 Benjamin Otte + * + * 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_GNOME_VFS_H__ +#define __GST_GNOME_VFS_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + + +GType gst_mad_get_type (void); +GType gst_id3_tag_get_type (void); + +GstTagList* gst_mad_id3_to_tag_list (const struct id3_tag * tag); +struct id3_tag * gst_mad_tag_list_to_id3_tag (GstTagList * list); + + +G_END_DECLS + +#endif /* __GST_GNOME_VFS_H__ */ diff --git a/gst-libs/ext/ffmpeg/Makefile.am b/gst-libs/ext/ffmpeg/Makefile.am index 0a7a076d04..eaa23fcbc6 100644 --- a/gst-libs/ext/ffmpeg/Makefile.am +++ b/gst-libs/ext/ffmpeg/Makefile.am @@ -25,43 +25,6 @@ patches: SUBDIRS = -EXTRA_DIST = \ - $(PATCHES) \ - Tag \ - ffmpeg/CREDITS \ - ffmpeg/INSTALL \ - ffmpeg/README \ - ffmpeg/config.mak \ - ffmpeg/configure \ - ffmpeg/cygwin_inttypes.h \ - ffmpeg/ffserver.h \ - ffmpeg/ffmpeg.c \ - ffmpeg/ffserver.c \ - ffmpeg/berrno.h \ - ffmpeg/config.h \ - ffmpeg/libavcodec/alpha/*.c \ - ffmpeg/libavcodec/alpha/*.h \ - ffmpeg/libavcodec/alpha/*.S \ - ffmpeg/libavcodec/armv4l/*.c \ - ffmpeg/libavcodec/armv4l/*.S \ - ffmpeg/libavcodec/i386/*.c \ - ffmpeg/libavcodec/i386/*.h \ - ffmpeg/libavcodec/liba52/*.c \ - ffmpeg/libavcodec/liba52/*.h \ - ffmpeg/libavcodec/ppc/*.c \ - ffmpeg/libavcodec/ppc/*.h \ - ffmpeg/libavcodec/ps2/*.c \ - ffmpeg/libavcodec/ps2/*.h \ - ffmpeg/libavcodec/*.c \ - ffmpeg/libavcodec/*.h \ - ffmpeg/libavformat/*.c \ - ffmpeg/libavformat/*.h \ - ffmpeg/tests/*.c \ - ffmpeg/tests/*.ref \ - ffmpeg/tests/*.sh \ - ffmpeg/tests/test.conf \ - ffmpeg/vhook/*.c - checkout: cvs -d:pserver:anonymous@mplayerhq.hu:/cvsroot/ffmpeg co ffmpeg @@ -164,9 +127,7 @@ libavformat_la_CFLAGS = $(defs) -Wall -I$(srcdir) -I$(srcdir)/ffmpeg \ -I$(top_srcdir)/gst-libs/ext/linux/ -DHAVE_AV_CONFIG_H \ -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_GNU_SOURCE - -if HAVE_CPU_I386 -sources_i386 = \ +files_i386 = \ ffmpeg/libavcodec/i386/cputest.c \ ffmpeg/libavcodec/i386/dsputil_mmx.c \ ffmpeg/libavcodec/i386/fdct_mmx.c \ @@ -175,12 +136,13 @@ sources_i386 = \ ffmpeg/libavcodec/i386/motion_est_mmx.c \ ffmpeg/libavcodec/i386/mpegvideo_mmx.c \ ffmpeg/libavcodec/i386/simple_idct_mmx.c +if HAVE_CPU_I386 +sources_i386 = $(files_i386) else sources_i386 = endif -if HAVE_CPU_PPC -sources_powerpc = \ +files_powerpc = \ ffmpeg/libavcodec/ppc/dsputil_ppc.c \ ffmpeg/libavcodec/ppc/mpegvideo_ppc.c # disabled Altivec support for now until someone shows up that make them compile conditionally @@ -189,6 +151,8 @@ sources_powerpc = \ # ffmpeg/libavcodec/ppc/gmc_altivec.c \ # ffmpeg/libavcodec/ppc/idct_altivec.c \ # ffmpeg/libavcodec/ppc/mpegvideo_altivec.c +if HAVE_CPU_PPC +sources_powerpc = $(files_powerpc) else sources_powerpc = endif @@ -270,3 +234,86 @@ libavcodec_la_SOURCES = \ $(sources_i386) \ $(sources_powerpc) +more_libavcodec_files = \ + ffmpeg/libavcodec/mdec.c \ + ffmpeg/libavcodec/motion_est_template.c \ + ffmpeg/libavcodec/svq3.c \ + ffmpeg/libavcodec/wmv2.c + + +all_headers = \ + ffmpeg/berrno.h \ + ffmpeg/cmdutils.h \ + ffmpeg/cygwin_inttypes.h \ + ffmpeg/ffserver.h \ + ffmpeg/xvmc_render.h \ + ffmpeg/libavcodec/ac3.h \ + ffmpeg/libavcodec/ac3tab.h \ + ffmpeg/libavcodec/avcodec.h \ + ffmpeg/libavcodec/bswap.h \ + ffmpeg/libavcodec/cabac.h \ + ffmpeg/libavcodec/common.h \ + ffmpeg/libavcodec/dsputil.h \ + ffmpeg/libavcodec/dvdata.h \ + ffmpeg/libavcodec/faandct.h \ + ffmpeg/libavcodec/fastmemcpy.h \ + ffmpeg/libavcodec/golomb.h \ + ffmpeg/libavcodec/h263data.h \ + ffmpeg/libavcodec/h264data.h \ + ffmpeg/libavcodec/imgconvert_template.h \ + ffmpeg/libavcodec/indeo3data.h \ + ffmpeg/libavcodec/mpeg12data.h \ + ffmpeg/libavcodec/mpeg4data.h \ + ffmpeg/libavcodec/mpegaudio.h \ + ffmpeg/libavcodec/mpegaudiodectab.h \ + ffmpeg/libavcodec/mpegaudiotab.h \ + ffmpeg/libavcodec/mpegvideo.h \ + ffmpeg/libavcodec/msmpeg4data.h \ + ffmpeg/libavcodec/oggvorbis.h \ + ffmpeg/libavcodec/ra144.h \ + ffmpeg/libavcodec/ra288.h \ + ffmpeg/libavcodec/rational.h \ + ffmpeg/libavcodec/simple_idct.h \ + ffmpeg/libavcodec/sp5x.h \ + ffmpeg/libavcodec/svq1_cb.h \ + ffmpeg/libavcodec/svq1_vlc.h \ + ffmpeg/libavcodec/vp3data.h \ + ffmpeg/libavcodec/wmadata.h \ + ffmpeg/libavcodec/i386/dsputil_mmx_avg.h \ + ffmpeg/libavcodec/i386/dsputil_mmx_rnd.h \ + ffmpeg/libavcodec/i386/mmx.h \ + ffmpeg/libavcodec/ppc/dsputil_altivec.h \ + ffmpeg/libavcodec/ppc/dsputil_ppc.h \ + ffmpeg/libavcodec/ppc/gcc_fixes.h \ + ffmpeg/libavformat/avformat.h \ + ffmpeg/libavformat/avi.h \ + ffmpeg/libavformat/avio.h \ + ffmpeg/libavformat/barpainet.h \ + ffmpeg/libavformat/dv.h \ + ffmpeg/libavformat/dv1394.h \ + ffmpeg/libavformat/framehook.h \ + ffmpeg/libavformat/mpegts.h \ + ffmpeg/libavformat/os_support.h \ + ffmpeg/libavformat/rtp.h \ + ffmpeg/libavformat/rtsp.h \ + ffmpeg/libavformat/rtspcodes.h + +DISTCLEANFILES = \ + ffmpeg/config.h \ + ffmpeg/config.mak + +EXTRA_DIST = \ + $(PATCHES) \ + Tag \ + ffmpeg/CREDITS \ + ffmpeg/INSTALL \ + ffmpeg/README \ + ffmpeg/configure \ + ffmpeg/ffmpeg.c \ + ffmpeg/ffserver.c \ + $(libavcodec_la_SOURCES) \ + $(more_libavcodec_files) \ + $(libavformat_la_SOURCES) \ + $(files_i386) \ + $(files_powerpc) \ + $(all_headers) diff --git a/m4/as-slurp-ffmpeg.m4 b/m4/as-slurp-ffmpeg.m4 index a3398420ec..0df909c653 100644 --- a/m4/as-slurp-ffmpeg.m4 +++ b/m4/as-slurp-ffmpeg.m4 @@ -22,7 +22,10 @@ AC_DEFUN(AS_SLURP_FFMPEG, DIRECTORY=`pwd` # get/update cvs if test ! -d $1; then mkdir -p $1; fi - cd $1 + dnl we need to check $srcdir/$1 or it will always checkout ffmpeg even if it is there + dnl at least when top_srcdir != top_builddir. + dnl FIXME: unfortunately this makes the checkout go into top_srcdir + cd $srcdir/$1 if test ! -e ffmpeg/README; then # check out cvs code diff --git a/m4/ogg.m4 b/m4/ogg.m4 new file mode 100644 index 0000000000..c73bab5497 --- /dev/null +++ b/m4/ogg.m4 @@ -0,0 +1,102 @@ +# Configure paths for libogg +# Jack Moffitt 10-21-2000 +# Shamelessly stolen from Owen Taylor and Manish Singh + +dnl XIPH_PATH_OGG([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Test for libogg, and define OGG_CFLAGS and OGG_LIBS +dnl +AC_DEFUN(XIPH_PATH_OGG, +[dnl +dnl Get the cflags and libraries +dnl +AC_ARG_WITH(ogg,[ --with-ogg=PFX Prefix where libogg is installed (optional)], ogg_prefix="$withval", ogg_prefix="") +AC_ARG_WITH(ogg-libraries,[ --with-ogg-libraries=DIR Directory where libogg library is installed (optional)], ogg_libraries="$withval", ogg_libraries="") +AC_ARG_WITH(ogg-includes,[ --with-ogg-includes=DIR Directory where libogg header files are installed (optional)], ogg_includes="$withval", ogg_includes="") +AC_ARG_ENABLE(oggtest, [ --disable-oggtest Do not try to compile and run a test Ogg program],, enable_oggtest=yes) + + if test "x$ogg_libraries" != "x" ; then + OGG_LIBS="-L$ogg_libraries" + elif test "x$ogg_prefix" != "x" ; then + OGG_LIBS="-L$ogg_prefix/lib" + elif test "x$prefix" != "xNONE" ; then + OGG_LIBS="-L$prefix/lib" + fi + + OGG_LIBS="$OGG_LIBS -logg" + + if test "x$ogg_includes" != "x" ; then + OGG_CFLAGS="-I$ogg_includes" + elif test "x$ogg_prefix" != "x" ; then + OGG_CFLAGS="-I$ogg_prefix/include" + elif test "$prefix" != "xNONE"; then + OGG_CFLAGS="-I$prefix/include" + fi + + AC_MSG_CHECKING(for Ogg) + no_ogg="" + + + if test "x$enable_oggtest" = "xyes" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $OGG_CFLAGS" + LIBS="$LIBS $OGG_LIBS" +dnl +dnl Now check if the installed Ogg is sufficiently new. +dnl + rm -f conf.oggtest + AC_TRY_RUN([ +#include +#include +#include +#include + +int main () +{ + system("touch conf.oggtest"); + return 0; +} + +],, no_ogg=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + + if test "x$no_ogg" = "x" ; then + AC_MSG_RESULT(yes) + ifelse([$1], , :, [$1]) + else + AC_MSG_RESULT(no) + if test -f conf.oggtest ; then + : + else + echo "*** Could not run Ogg test program, checking why..." + CFLAGS="$CFLAGS $OGG_CFLAGS" + LIBS="$LIBS $OGG_LIBS" + AC_TRY_LINK([ +#include +#include +], [ return 0; ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding Ogg or finding the wrong" + echo "*** version of Ogg. If it is not finding Ogg, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means Ogg was incorrectly installed" + echo "*** or that you have moved Ogg since it was installed." ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + OGG_CFLAGS="" + OGG_LIBS="" + ifelse([$2], , :, [$2]) + fi + AC_SUBST(OGG_CFLAGS) + AC_SUBST(OGG_LIBS) + rm -f conf.oggtest +])