From fe38f53572ff91b63a3dc5a5a5ed78e13040d16c Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 21 May 2009 13:15:46 -0700 Subject: id3tag: Add new id3 tagging plugin, supports v1, v2.3, and v2.4. By default, does v1 and v2.3, but there are properties to select. Will hopefully replace id3mux, id3v2mux, in the not-too-distant future. --- gst/id3tag/Makefile.am | 19 + gst/id3tag/gstid3tag.c | 229 ++++++++++ gst/id3tag/gstid3tag.h | 63 +++ gst/id3tag/gsttagmux.c | 490 ++++++++++++++++++++ gst/id3tag/gsttagmux.h | 79 ++++ gst/id3tag/id3tag.c | 1194 ++++++++++++++++++++++++++++++++++++++++++++++++ gst/id3tag/id3tag.h | 32 ++ 7 files changed, 2106 insertions(+) create mode 100644 gst/id3tag/Makefile.am create mode 100644 gst/id3tag/gstid3tag.c create mode 100644 gst/id3tag/gstid3tag.h create mode 100644 gst/id3tag/gsttagmux.c create mode 100644 gst/id3tag/gsttagmux.h create mode 100644 gst/id3tag/id3tag.c create mode 100644 gst/id3tag/id3tag.h (limited to 'gst/id3tag') diff --git a/gst/id3tag/Makefile.am b/gst/id3tag/Makefile.am new file mode 100644 index 00000000..9595be0f --- /dev/null +++ b/gst/id3tag/Makefile.am @@ -0,0 +1,19 @@ +plugin_LTLIBRARIES = libgstid3tag.la + +libgstid3tag_la_SOURCES = \ + gsttagmux.c \ + id3tag.c \ + gstid3tag.c + +libgstid3tag_la_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_CFLAGS) + +libgstid3tag_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) -lgsttag-$(GST_MAJORMINOR) \ + $(GST_LIBS) + +libgstid3tag_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstid3tag_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstid3tag.h id3tag.h gsttagmux.h diff --git a/gst/id3tag/gstid3tag.c b/gst/id3tag/gstid3tag.c new file mode 100644 index 00000000..f67d781f --- /dev/null +++ b/gst/id3tag/gstid3tag.c @@ -0,0 +1,229 @@ +/* GStreamer ID3 v1 and v2 muxer + * + * Copyright (C) 2006 Christophe Fergeau + * Copyright (C) 2006 Tim-Philipp Müller + * Copyright (C) 2009 Pioneers of the Inevitable + * + * 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. + */ + +/** + * SECTION:element-id3tag + * @see_also: #GstID3Demux, #GstTagSetter + * + * This element adds ID3v2 tags to the beginning of a stream, and ID3v1 tags + * to the end. + * + * It defaults to writing ID3 version 2.3.0 tags (since those are the most + * widely supported), but can optionally write version 2.4.0 tags. + * + * Applications can set the tags to write using the #GstTagSetter interface. + * Tags sent by upstream elements will be picked up automatically (and merged + * according to the merge mode set via the tag setter interface). + * + * + * Example pipelines + * |[ + * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! lame ! id3tag ! filesink location=foo.mp3 + * ]| A pipeline that transcodes a file from Ogg/Vorbis to mp3 format with + * ID3 tags that contain the same metadata as the the Ogg/Vorbis file. + * Make sure the Ogg/Vorbis file actually has comments to preserve. + * |[ + * gst-launch -m filesrc location=foo.mp3 ! id3demux ! fakesink silent=TRUE 2> /dev/null | grep taglist + * ]| Verify that tags have been written. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstid3tag.h" +#include + +#include + +GST_DEBUG_CATEGORY (gst_id3tag_debug); +#define GST_CAT_DEFAULT gst_id3tag_debug + +enum +{ + ARG_0, + ARG_WRITE_V1, + ARG_WRITE_V2, + ARG_V2_MAJOR_VERSION +}; + +#define DEFAULT_WRITE_V1 TRUE +#define DEFAULT_WRITE_V2 TRUE +#define DEFAULT_V2_MAJOR_VERSION 3 + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-id3")); + +GST_BOILERPLATE (GstID3Tag, gst_id3tag, GstTagMux, GST_TYPE_TAG_MUX); + +static GstBuffer *gst_id3tag_render_v2_tag (GstTagMux * mux, + GstTagList * taglist); +static GstBuffer *gst_id3tag_render_v1_tag (GstTagMux * mux, + GstTagList * taglist); + +static void gst_id3tag_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_id3tag_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void +gst_id3tag_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + + gst_element_class_set_details_simple (element_class, + "ID3 v1 and v2 Muxer", "Formatter/Metadata", + "Adds an ID3v2 header and ID3v1 footer to a file", + "Michael Smith , " + "Tim-Philipp Müller "); + + GST_DEBUG_CATEGORY_INIT (gst_id3tag_debug, "id3tag", 0, + "ID3 v1 and v2 tag muxer"); +} + +static void +gst_id3tag_class_init (GstID3TagClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->set_property = gst_id3tag_set_property; + gobject_class->get_property = gst_id3tag_get_property; + + g_object_class_install_property (gobject_class, ARG_WRITE_V1, + g_param_spec_boolean ("write-v1", "Write id3v1 tag", + "Write an id3v1 tag at the end of the file", DEFAULT_WRITE_V1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (gobject_class, ARG_WRITE_V2, + g_param_spec_boolean ("write-v2", "Write id3v2 tag", + "Write an id3v2 tag at the start of the file", DEFAULT_WRITE_V2, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (gobject_class, ARG_V2_MAJOR_VERSION, + g_param_spec_int ("v2-version", "Version (3 or 4) of id3v2 tag", + "Set version (3 for id3v2.3, 4 for id3v2.4) of id3v2 tags", + 3, 4, DEFAULT_V2_MAJOR_VERSION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + GST_TAG_MUX_CLASS (klass)->render_start_tag = + GST_DEBUG_FUNCPTR (gst_id3tag_render_v2_tag); + + GST_TAG_MUX_CLASS (klass)->render_end_tag = gst_id3tag_render_v1_tag; +} + +static void +gst_id3tag_init (GstID3Tag * id3mux, GstID3TagClass * id3mux_class) +{ + id3mux->write_v1 = DEFAULT_WRITE_V1; + id3mux->write_v2 = DEFAULT_WRITE_V2; + + id3mux->v2_major_version = DEFAULT_V2_MAJOR_VERSION; +} + +static void +gst_id3tag_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstID3Tag *mux = GST_ID3TAG (object); + + switch (prop_id) { + case ARG_WRITE_V1: + mux->write_v1 = g_value_get_boolean (value); + break; + case ARG_WRITE_V2: + mux->write_v2 = g_value_get_boolean (value); + break; + case ARG_V2_MAJOR_VERSION: + mux->v2_major_version = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_id3tag_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstID3Tag *mux = GST_ID3TAG (object); + + switch (prop_id) { + case ARG_WRITE_V1: + g_value_set_boolean (value, mux->write_v1); + break; + case ARG_WRITE_V2: + g_value_set_boolean (value, mux->write_v2); + break; + case ARG_V2_MAJOR_VERSION: + g_value_set_int (value, mux->v2_major_version); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstBuffer * +gst_id3tag_render_v2_tag (GstTagMux * mux, GstTagList * taglist) +{ + GstID3Tag *id3mux = GST_ID3TAG (mux); + + if (id3mux->write_v2) + return gst_id3mux_render_v2_tag (mux, taglist, id3mux->v2_major_version); + else + return NULL; +} + +static GstBuffer * +gst_id3tag_render_v1_tag (GstTagMux * mux, GstTagList * taglist) +{ + GstID3Tag *id3mux = GST_ID3TAG (mux); + + if (id3mux->write_v1) + return gst_id3mux_render_v1_tag (mux, taglist); + else + return NULL; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "id3tag", GST_RANK_NONE, GST_TYPE_ID3TAG)) + return FALSE; + + gst_tag_register_musicbrainz_tags (); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "id3tag", + "ID3 v1 and v2 muxing plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gst/id3tag/gstid3tag.h b/gst/id3tag/gstid3tag.h new file mode 100644 index 00000000..6b33df25 --- /dev/null +++ b/gst/id3tag/gstid3tag.h @@ -0,0 +1,63 @@ +/* GStreamer ID3 v1 and v2 muxer + * + * Copyright (C) 2006 Christophe Fergeau + * Copyright (C) 2006 Tim-Philipp Müller + * Copyright (C) 2009 Pioneers of the Inevitable + * + * 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_ID3TAG_H +#define GST_ID3TAG_H + +#include "gsttagmux.h" +#include "id3tag.h" + +G_BEGIN_DECLS + +typedef struct _GstID3Tag GstID3Tag; +typedef struct _GstID3TagClass GstID3TagClass; + +struct _GstID3Tag { + GstTagMux tagmux; + + gboolean write_v1; + gboolean write_v2; + + gint v2_major_version; +}; + +struct _GstID3TagClass { + GstTagMuxClass tagmux_class; +}; + +#define GST_TYPE_ID3TAG \ + (gst_id3tag_get_type()) +#define GST_ID3TAG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ID3TAG,GstID3Tag)) +#define GST_ID3TAG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ID3TAG,GstID3TagClass)) +#define GST_IS_ID3TAG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ID3TAG)) +#define GST_IS_ID3TAG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ID3TAG)) + +GType gst_id3tag_get_type (void); + +G_END_DECLS + +#endif /* GST_ID3TAG_H */ + diff --git a/gst/id3tag/gsttagmux.c b/gst/id3tag/gsttagmux.c new file mode 100644 index 00000000..bfa4e1bc --- /dev/null +++ b/gst/id3tag/gsttagmux.c @@ -0,0 +1,490 @@ +/* GStreamer tag muxer base class + * + * Copyright (C) 2006 Christophe Fergeau + * Copyright (C) 2006 Tim-Philipp Müller + * Copyright (C) 2006 Sebastian Dröge + * Copyright (C) 2009 Pioneers of the Inevitable + * + * 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 +#endif + +#include +#include +#include + +#include "gsttagmux.h" + +GST_DEBUG_CATEGORY_STATIC (gst_tag_mux_debug); +#define GST_CAT_DEFAULT gst_tag_mux_debug + +/* Subclass provides a src template and pad. We accept anything as input here, + however. */ + +static GstStaticPadTemplate gst_tag_mux_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("ANY")); + +static void +gst_tag_mux_iface_init (GType tag_type) +{ + static const GInterfaceInfo tag_setter_info = { + NULL, + NULL, + NULL + }; + + g_type_add_interface_static (tag_type, GST_TYPE_TAG_SETTER, &tag_setter_info); +} + +GST_BOILERPLATE_FULL (GstTagMux, gst_tag_mux, + GstElement, GST_TYPE_ELEMENT, gst_tag_mux_iface_init); + + +static GstStateChangeReturn +gst_tag_mux_change_state (GstElement * element, GstStateChange transition); +static GstFlowReturn gst_tag_mux_chain (GstPad * pad, GstBuffer * buffer); +static gboolean gst_tag_mux_sink_event (GstPad * pad, GstEvent * event); + +static void +gst_tag_mux_finalize (GObject * obj) +{ + GstTagMux *mux = GST_TAG_MUX (obj); + + if (mux->newsegment_ev) { + gst_event_unref (mux->newsegment_ev); + mux->newsegment_ev = NULL; + } + + if (mux->event_tags) { + gst_tag_list_free (mux->event_tags); + mux->event_tags = NULL; + } + + if (mux->final_tags) { + gst_tag_list_free (mux->final_tags); + mux->final_tags = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (obj); +} + +static void +gst_tag_mux_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_tag_mux_sink_template)); + + GST_DEBUG_CATEGORY_INIT (gst_tag_mux_debug, "tagmux", 0, + "tag muxer base class"); +} + +static void +gst_tag_mux_class_init (GstTagMuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_tag_mux_finalize); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_tag_mux_change_state); +} + +static void +gst_tag_mux_init (GstTagMux * mux, GstTagMuxClass * mux_class) +{ + GstElementClass *element_klass = GST_ELEMENT_CLASS (mux_class); + GstPadTemplate *tmpl; + + /* pad through which data comes in to the element */ + mux->sinkpad = + gst_pad_new_from_static_template (&gst_tag_mux_sink_template, "sink"); + gst_pad_set_chain_function (mux->sinkpad, + GST_DEBUG_FUNCPTR (gst_tag_mux_chain)); + gst_pad_set_event_function (mux->sinkpad, + GST_DEBUG_FUNCPTR (gst_tag_mux_sink_event)); + gst_element_add_pad (GST_ELEMENT (mux), mux->sinkpad); + + /* pad through which data goes out of the element */ + tmpl = gst_element_class_get_pad_template (element_klass, "src"); + if (tmpl) { + mux->srcpad = gst_pad_new_from_template (tmpl, "src"); + gst_pad_use_fixed_caps (mux->srcpad); + gst_pad_set_caps (mux->srcpad, gst_pad_template_get_caps (tmpl)); + gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad); + } + + mux->render_start_tag = TRUE; + mux->render_end_tag = TRUE; +} + +static GstTagList * +gst_tag_mux_get_tags (GstTagMux * mux) +{ + GstTagSetter *tagsetter = GST_TAG_SETTER (mux); + const GstTagList *tagsetter_tags; + GstTagMergeMode merge_mode; + + if (mux->final_tags) + return mux->final_tags; + + tagsetter_tags = gst_tag_setter_get_tag_list (tagsetter); + merge_mode = gst_tag_setter_get_tag_merge_mode (tagsetter); + + GST_LOG_OBJECT (mux, "merging tags, merge mode = %d", merge_mode); + GST_LOG_OBJECT (mux, "event tags: %" GST_PTR_FORMAT, mux->event_tags); + GST_LOG_OBJECT (mux, "set tags: %" GST_PTR_FORMAT, tagsetter_tags); + + mux->final_tags = + gst_tag_list_merge (tagsetter_tags, mux->event_tags, merge_mode); + + GST_LOG_OBJECT (mux, "final tags: %" GST_PTR_FORMAT, mux->final_tags); + + return mux->final_tags; +} + +static GstFlowReturn +gst_tag_mux_render_start_tag (GstTagMux * mux) +{ + GstTagMuxClass *klass; + GstBuffer *buffer; + GstTagList *taglist; + GstEvent *event; + GstFlowReturn ret; + + taglist = gst_tag_mux_get_tags (mux); + + klass = GST_TAG_MUX_CLASS (G_OBJECT_GET_CLASS (mux)); + + if (klass->render_start_tag == NULL) + goto no_vfunc; + + buffer = klass->render_start_tag (mux, taglist); + + /* Null buffer is ok, just means we're not outputting anything */ + if (buffer == NULL) { + GST_INFO_OBJECT (mux, "No start tag generated"); + mux->start_tag_size = 0; + return GST_FLOW_OK; + } + + mux->start_tag_size = GST_BUFFER_SIZE (buffer); + GST_LOG_OBJECT (mux, "tag size = %" G_GSIZE_FORMAT " bytes", + mux->start_tag_size); + + /* Send newsegment event from byte position 0, so the tag really gets + * written to the start of the file, independent of the upstream segment */ + gst_pad_push_event (mux->srcpad, + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0)); + + /* Send an event about the new tags to downstream elements */ + /* gst_event_new_tag takes ownership of the list, so use a copy */ + event = gst_event_new_tag (gst_tag_list_copy (taglist)); + gst_pad_push_event (mux->srcpad, event); + + GST_BUFFER_OFFSET (buffer) = 0; + ret = gst_pad_push (mux->srcpad, buffer); + + mux->current_offset = mux->start_tag_size; + mux->max_offset = MAX (mux->max_offset, mux->current_offset); + + return ret; + +no_vfunc: + { + GST_ERROR_OBJECT (mux, "Subclass does not implement " + "render_start_tag vfunc!"); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_tag_mux_render_end_tag (GstTagMux * mux) +{ + GstTagMuxClass *klass; + GstBuffer *buffer; + GstTagList *taglist; + GstFlowReturn ret; + + taglist = gst_tag_mux_get_tags (mux); + + klass = GST_TAG_MUX_CLASS (G_OBJECT_GET_CLASS (mux)); + + if (klass->render_end_tag == NULL) + goto no_vfunc; + + buffer = klass->render_end_tag (mux, taglist); + + if (buffer == NULL) { + GST_INFO_OBJECT (mux, "No end tag generated"); + mux->end_tag_size = 0; + return GST_FLOW_OK; + } + + mux->end_tag_size = GST_BUFFER_SIZE (buffer); + GST_LOG_OBJECT (mux, "tag size = %" G_GSIZE_FORMAT " bytes", + mux->end_tag_size); + + /* Send newsegment event from the end of the file, so it gets written there, + independent of whatever new segment events upstream has sent us */ + gst_pad_push_event (mux->srcpad, + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, mux->max_offset, + -1, 0)); + + GST_BUFFER_OFFSET (buffer) = mux->max_offset; + ret = gst_pad_push (mux->srcpad, buffer); + + return ret; + +no_vfunc: + { + GST_ERROR_OBJECT (mux, "Subclass does not implement " + "render_end_tag vfunc!"); + return GST_FLOW_ERROR; + } +} + +static GstEvent * +gst_tag_mux_adjust_event_offsets (GstTagMux * mux, + const GstEvent * newsegment_event) +{ + GstFormat format; + gint64 start, stop, cur; + + gst_event_parse_new_segment ((GstEvent *) newsegment_event, NULL, NULL, + &format, &start, &stop, &cur); + + g_assert (format == GST_FORMAT_BYTES); + + if (start != -1) + start += mux->start_tag_size; + if (stop != -1) + stop += mux->start_tag_size; + if (cur != -1) + cur += mux->start_tag_size; + + GST_DEBUG_OBJECT (mux, "adjusting newsegment event offsets to start=%" + G_GINT64_FORMAT ", stop=%" G_GINT64_FORMAT ", cur=%" G_GINT64_FORMAT + " (delta = +%" G_GSIZE_FORMAT ")", start, stop, cur, mux->start_tag_size); + + return gst_event_new_new_segment (TRUE, 1.0, format, start, stop, cur); +} + +static GstFlowReturn +gst_tag_mux_chain (GstPad * pad, GstBuffer * buffer) +{ + GstTagMux *mux = GST_TAG_MUX (GST_OBJECT_PARENT (pad)); + GstFlowReturn ret; + int length; + + if (mux->render_start_tag) { + + GST_INFO_OBJECT (mux, "Adding tags to stream"); + ret = gst_tag_mux_render_start_tag (mux); + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (mux, "flow: %s", gst_flow_get_name (ret)); + gst_buffer_unref (buffer); + return ret; + } + + /* Now send the cached newsegment event that we got from upstream */ + if (mux->newsegment_ev) { + gint64 start; + GstEvent *newseg; + + GST_DEBUG_OBJECT (mux, "sending cached newsegment event"); + newseg = gst_tag_mux_adjust_event_offsets (mux, mux->newsegment_ev); + gst_event_unref (mux->newsegment_ev); + mux->newsegment_ev = NULL; + + gst_event_parse_new_segment (newseg, NULL, NULL, NULL, &start, NULL, + NULL); + + gst_pad_push_event (mux->srcpad, newseg); + mux->current_offset = start; + mux->max_offset = MAX (mux->max_offset, mux->current_offset); + } else { + /* upstream sent no newsegment event or only one in a non-BYTE format */ + } + + mux->render_start_tag = FALSE; + } + + buffer = gst_buffer_make_metadata_writable (buffer); + + if (GST_BUFFER_OFFSET (buffer) != GST_BUFFER_OFFSET_NONE) { + GST_LOG_OBJECT (mux, "Adjusting buffer offset from %" G_GINT64_FORMAT + " to %" G_GINT64_FORMAT, GST_BUFFER_OFFSET (buffer), + GST_BUFFER_OFFSET (buffer) + mux->start_tag_size); + GST_BUFFER_OFFSET (buffer) += mux->start_tag_size; + } + + length = GST_BUFFER_SIZE (buffer); + + gst_buffer_set_caps (buffer, GST_PAD_CAPS (mux->srcpad)); + ret = gst_pad_push (mux->srcpad, buffer); + + mux->current_offset += length; + mux->max_offset = MAX (mux->max_offset, mux->current_offset); + + return ret; +} + +static gboolean +gst_tag_mux_sink_event (GstPad * pad, GstEvent * event) +{ + GstTagMux *mux; + gboolean result; + + mux = GST_TAG_MUX (gst_pad_get_parent (pad)); + result = FALSE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_TAG:{ + GstTagList *tags; + + gst_event_parse_tag (event, &tags); + + GST_INFO_OBJECT (mux, "Got tag event: %" GST_PTR_FORMAT, tags); + + if (mux->event_tags != NULL) { + gst_tag_list_insert (mux->event_tags, tags, GST_TAG_MERGE_REPLACE); + } else { + mux->event_tags = gst_tag_list_copy (tags); + } + + GST_INFO_OBJECT (mux, "Event tags are now: %" GST_PTR_FORMAT, + mux->event_tags); + + /* just drop the event, we'll push a new tag event in render_start_tag */ + gst_event_unref (event); + result = TRUE; + break; + } + case GST_EVENT_NEWSEGMENT:{ + GstFormat fmt; + gint64 start; + + gst_event_parse_new_segment (event, NULL, NULL, &fmt, &start, NULL, NULL); + + if (fmt != GST_FORMAT_BYTES) { + GST_WARNING_OBJECT (mux, "dropping newsegment event in %s format", + gst_format_get_name (fmt)); + gst_event_unref (event); + break; + } + + if (mux->render_start_tag) { + /* we have not rendered the tag yet, which means that we don't know + * how large it is going to be yet, so we can't adjust the offsets + * here at this point and need to cache the newsegment event for now + * (also, there could be tag events coming after this newsegment event + * and before the first buffer). */ + if (mux->newsegment_ev) { + GST_WARNING_OBJECT (mux, "discarding old cached newsegment event"); + gst_event_unref (mux->newsegment_ev); + } + + GST_LOG_OBJECT (mux, "caching newsegment event for later"); + mux->newsegment_ev = event; + } else { + GST_DEBUG_OBJECT (mux, "got newsegment event, adjusting offsets"); + gst_pad_push_event (mux->srcpad, + gst_tag_mux_adjust_event_offsets (mux, event)); + gst_event_unref (event); + + mux->current_offset = start; + mux->max_offset = MAX (mux->max_offset, mux->current_offset); + } + event = NULL; + result = TRUE; + break; + } + case GST_EVENT_EOS:{ + if (mux->render_end_tag) { + GstFlowReturn ret; + + GST_INFO_OBJECT (mux, "Adding tags to stream"); + ret = gst_tag_mux_render_end_tag (mux); + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (mux, "flow: %s", gst_flow_get_name (ret)); + return ret; + } + + mux->render_end_tag = FALSE; + } + + /* Now forward EOS */ + result = gst_pad_event_default (pad, event); + break; + } + default: + result = gst_pad_event_default (pad, event); + break; + } + + gst_object_unref (mux); + + return result; +} + + +static GstStateChangeReturn +gst_tag_mux_change_state (GstElement * element, GstStateChange transition) +{ + GstTagMux *mux; + GstStateChangeReturn result; + + mux = GST_TAG_MUX (element); + + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (result != GST_STATE_CHANGE_SUCCESS) { + return result; + } + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY:{ + if (mux->newsegment_ev) { + gst_event_unref (mux->newsegment_ev); + mux->newsegment_ev = NULL; + } + if (mux->event_tags) { + gst_tag_list_free (mux->event_tags); + mux->event_tags = NULL; + } + mux->start_tag_size = 0; + mux->end_tag_size = 0; + mux->render_start_tag = TRUE; + mux->render_end_tag = TRUE; + mux->current_offset = 0; + mux->max_offset = 0; + break; + } + default: + break; + } + + return result; +} diff --git a/gst/id3tag/gsttagmux.h b/gst/id3tag/gsttagmux.h new file mode 100644 index 00000000..c13a7326 --- /dev/null +++ b/gst/id3tag/gsttagmux.h @@ -0,0 +1,79 @@ +/* GStreamer tag muxer base class + * + * Copyright (C) 2006 Christophe Fergeau + * Copyright (C) 2006 Tim-Philipp Müller + * Copyright (C) 2009 Pioneers of the Inevitable + * + * 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_TAG_MUX_H +#define GST_TAG_MUX_H + +#include + +G_BEGIN_DECLS + +typedef struct _GstTagMux GstTagMux; +typedef struct _GstTagMuxClass GstTagMuxClass; + +/* Definition of structure storing data for this element. */ +struct _GstTagMux { + GstElement element; + + GstPad *srcpad; + GstPad *sinkpad; + GstTagList *event_tags; /* tags received from upstream elements */ + GstTagList *final_tags; /* Final set of tags used for muxing */ + gsize start_tag_size; + gsize end_tag_size; + gboolean render_start_tag; + gboolean render_end_tag; + + gint64 current_offset; + gint64 max_offset; + + GstEvent *newsegment_ev; /* cached newsegment event from upstream */ +}; + +/* Standard definition defining a class for this element. */ +struct _GstTagMuxClass { + GstElementClass parent_class; + + /* vfuncs */ + GstBuffer * (*render_start_tag) (GstTagMux * mux, GstTagList * tag_list); + GstBuffer * (*render_end_tag) (GstTagMux * mux, GstTagList * tag_list); +}; + +/* Standard macros for defining types for this element. */ +#define GST_TYPE_TAG_MUX \ + (gst_tag_mux_get_type()) +#define GST_TAG_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAG_MUX,GstTagMux)) +#define GST_TAG_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAG_MUX,GstTagMuxClass)) +#define GST_IS_TAG_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAG_MUX)) +#define GST_IS_TAG_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAG_MUX)) + +/* Standard function returning type information. */ +GType gst_tag_mux_get_type (void); + +G_END_DECLS + +#endif + diff --git a/gst/id3tag/id3tag.c b/gst/id3tag/id3tag.c new file mode 100644 index 00000000..0e040f7e --- /dev/null +++ b/gst/id3tag/id3tag.c @@ -0,0 +1,1194 @@ +/* GStreamer ID3v2 tag writer + * + * Copyright (C) 2006 Christophe Fergeau + * Copyright (C) 2006-2009 Tim-Philipp Müller + * Copyright (C) 2009 Pioneers of the Inevitable + * + * 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. + */ + +#include "id3tag.h" +#include + +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_id3tag_debug); +#define GST_CAT_DEFAULT gst_id3tag_debug + +#define ID3V2_APIC_PICTURE_OTHER 0 +#define ID3V2_APIC_PICTURE_FILE_ICON 1 + +/* ======================================================================== */ + +typedef GString GstByteWriter; + +static inline GstByteWriter * +gst_byte_writer_new (guint size) +{ + return (GstByteWriter *) g_string_sized_new (size); +} + +static inline guint +gst_byte_writer_get_length (GstByteWriter * w) +{ + return ((GString *) w)->len; +} + +static inline void +gst_byte_writer_write_bytes (GstByteWriter * w, const guint8 * data, guint len) +{ + g_string_append_len ((GString *) w, (const gchar *) data, len); +} + +static inline void +gst_byte_writer_write_uint8 (GstByteWriter * w, guint8 val) +{ + guint8 data[1]; + + GST_WRITE_UINT8 (data, val); + gst_byte_writer_write_bytes (w, data, 1); +} + +static inline void +gst_byte_writer_write_uint16 (GstByteWriter * w, guint16 val) +{ + guint8 data[2]; + + GST_WRITE_UINT16_BE (data, val); + gst_byte_writer_write_bytes (w, data, 2); +} + +static inline void +gst_byte_writer_write_uint32 (GstByteWriter * w, guint32 val) +{ + guint8 data[4]; + + GST_WRITE_UINT32_BE (data, val); + gst_byte_writer_write_bytes (w, data, 4); +} + +static inline void +gst_byte_writer_write_uint32_syncsafe (GstByteWriter * w, guint32 val) +{ + guint8 data[4]; + + data[0] = (guint8) ((val >> 21) & 0x7f); + data[1] = (guint8) ((val >> 14) & 0x7f); + data[2] = (guint8) ((val >> 7) & 0x7f); + data[3] = (guint8) ((val >> 0) & 0x7f); + gst_byte_writer_write_bytes (w, data, 4); +} + +static void +gst_byte_writer_copy_bytes (GstByteWriter * w, guint8 * dest, guint offset, + gint size) +{ + guint length; + + length = gst_byte_writer_get_length (w); + + if (size == -1) + size = length - offset; + +#if GLIB_CHECK_VERSION(2,16,0) + g_warn_if_fail (length >= (offset + size)); +#endif + + memcpy (dest, w->str + offset, MIN (size, length - offset)); +} + +static inline void +gst_byte_writer_free (GstByteWriter * w) +{ + g_string_free (w, TRUE); +} + +/* ======================================================================== */ + +/* +typedef enum { + GST_ID3V2_FRAME_FLAG_NONE = 0, + GST_ID3V2_FRAME_FLAG_ +} GstID3v2FrameMsgFlags; +*/ + +typedef struct +{ + gchar id[5]; + guint32 len; /* Length encoded in the header; this is the + total length - header size */ + guint16 flags; + GstByteWriter *writer; + gboolean dirty; /* TRUE if frame header needs updating */ +} GstId3v2Frame; + +typedef struct +{ + GArray *frames; + guint major_version; /* The 3 in v2.3.0 */ +} GstId3v2Tag; + +typedef void (*GstId3v2AddTagFunc) (GstId3v2Tag * tag, const GstTagList * list, + const gchar * gst_tag, guint num_tags, const gchar * data); + +#define ID3V2_ENCODING_UTF8 0x03 + +static gboolean id3v2_tag_init (GstId3v2Tag * tag, guint major_version); +static void id3v2_tag_unset (GstId3v2Tag * tag); + +static void id3v2_frame_init (GstId3v2Frame * frame, + const gchar * frame_id, guint16 flags); +static void id3v2_frame_unset (GstId3v2Frame * frame); +static void id3v2_frame_finish (GstId3v2Tag * tag, GstId3v2Frame * frame); +static guint id3v2_frame_get_size (GstId3v2Tag * tag, GstId3v2Frame * frame); + +static void id3v2_tag_add_text_frame (GstId3v2Tag * tag, + const gchar * frame_id, gchar ** strings, int num_strings); + +static gboolean +id3v2_tag_init (GstId3v2Tag * tag, guint major_version) +{ + if (major_version != 3 && major_version != 4) + return FALSE; + + tag->major_version = major_version; + tag->frames = g_array_new (TRUE, TRUE, sizeof (GstId3v2Frame)); + + return TRUE; +} + +static void +id3v2_tag_unset (GstId3v2Tag * tag) +{ + guint i; + + for (i = 0; i < tag->frames->len; ++i) + id3v2_frame_unset (&g_array_index (tag->frames, GstId3v2Frame, i)); + + g_array_free (tag->frames, TRUE); + memset (tag, 0, sizeof (GstId3v2Tag)); +} + +#ifndef GST_ROUND_UP_1024 +#define GST_ROUND_UP_1024(num) (((num)+1023)&~1023) +#endif + +static GstBuffer * +id3v2_tag_to_buffer (GstId3v2Tag * tag) +{ + GstByteWriter *w; + GstBuffer *buf; + guint8 *dest; + guint i, size, offset, size_frames = 0; + + GST_DEBUG ("Creating buffer for ID3v2 tag containing %d frames", + tag->frames->len); + + for (i = 0; i < tag->frames->len; ++i) { + GstId3v2Frame *frame = &g_array_index (tag->frames, GstId3v2Frame, i); + + id3v2_frame_finish (tag, frame); + size_frames += id3v2_frame_get_size (tag, frame); + } + + size = GST_ROUND_UP_1024 (10 + size_frames); + + w = gst_byte_writer_new (10); + gst_byte_writer_write_uint8 (w, 'I'); + gst_byte_writer_write_uint8 (w, 'D'); + gst_byte_writer_write_uint8 (w, '3'); + gst_byte_writer_write_uint8 (w, tag->major_version); + gst_byte_writer_write_uint8 (w, 0); /* micro version */ + gst_byte_writer_write_uint8 (w, 0); /* flags */ + gst_byte_writer_write_uint32_syncsafe (w, size - 10); + + buf = gst_buffer_new_and_alloc (size); + dest = GST_BUFFER_DATA (buf); + gst_byte_writer_copy_bytes (w, dest, 0, 10); + offset = 10; + + for (i = 0; i < tag->frames->len; ++i) { + GstId3v2Frame *frame = &g_array_index (tag->frames, GstId3v2Frame, i); + + gst_byte_writer_copy_bytes (frame->writer, dest + offset, 0, -1); + offset += id3v2_frame_get_size (tag, frame); + } + + /* Zero out any additional space in our buffer as padding. */ + memset (dest + offset, 0, size - offset); + + gst_byte_writer_free (w); + + return buf; +} + +static inline void +id3v2_frame_write_bytes (GstId3v2Frame * frame, const guint8 * data, guint len) +{ + gst_byte_writer_write_bytes (frame->writer, data, len); + frame->dirty = TRUE; +} + +static inline void +id3v2_frame_write_uint8 (GstId3v2Frame * frame, guint8 val) +{ + gst_byte_writer_write_uint8 (frame->writer, val); + frame->dirty = TRUE; +} + +static inline void +id3v2_frame_write_uint16 (GstId3v2Frame * frame, guint16 val) +{ + gst_byte_writer_write_uint16 (frame->writer, val); + frame->dirty = TRUE; +} + +static inline void +id3v2_frame_write_uint32 (GstId3v2Frame * frame, guint32 val) +{ + gst_byte_writer_write_uint32 (frame->writer, val); + frame->dirty = TRUE; +} + +static inline void +id3v2_frame_write_uint32_syncsafe (GstId3v2Frame * frame, guint32 val) +{ + guint8 data[4]; + + data[0] = (guint8) ((val >> 21) & 0x7f); + data[1] = (guint8) ((val >> 14) & 0x7f); + data[2] = (guint8) ((val >> 7) & 0x7f); + data[3] = (guint8) ((val >> 0) & 0x7f); + gst_byte_writer_write_bytes (frame->writer, data, 4); + frame->dirty = TRUE; +} + +static void +id3v2_frame_init (GstId3v2Frame * frame, const gchar * frame_id, guint16 flags) +{ + g_assert (strlen (frame_id) == 4); /* we only handle 2.3.0/2.4.0 */ + memcpy (frame->id, frame_id, 4 + 1); + frame->flags = flags; + frame->len = 0; + frame->writer = gst_byte_writer_new (64); + id3v2_frame_write_bytes (frame, (const guint8 *) frame->id, 4); + id3v2_frame_write_uint32 (frame, 0); /* size, set later */ + id3v2_frame_write_uint16 (frame, frame->flags); +} + +static void +id3v2_frame_finish (GstId3v2Tag * tag, GstId3v2Frame * frame) +{ + if (frame->dirty) { + frame->len = frame->writer->len - 10; + GST_LOG ("[%s] %u bytes", frame->id, frame->len); + if (tag->major_version == 3) { + GST_WRITE_UINT32_BE (frame->writer->str + 4, frame->len); + } else { + /* Version 4 uses a syncsafe int here */ + GST_WRITE_UINT8 (frame->writer->str + 4, (frame->len >> 21) & 0x7f); + GST_WRITE_UINT8 (frame->writer->str + 5, (frame->len >> 14) & 0x7f); + GST_WRITE_UINT8 (frame->writer->str + 6, (frame->len >> 7) & 0x7f); + GST_WRITE_UINT8 (frame->writer->str + 7, (frame->len >> 0) & 0x7f); + } + frame->dirty = FALSE; + } +} + +static guint +id3v2_frame_get_size (GstId3v2Tag * tag, GstId3v2Frame * frame) +{ + id3v2_frame_finish (tag, frame); + return gst_byte_writer_get_length (frame->writer); +} + +static void +id3v2_frame_unset (GstId3v2Frame * frame) +{ + gst_byte_writer_free (frame->writer); + memset (frame, 0, sizeof (GstId3v2Frame)); +} + +static void +id3v2_tag_add_text_frame (GstId3v2Tag * tag, const gchar * frame_id, + gchar ** strings_utf8, int num_strings) +{ + GstId3v2Frame frame; + guint len, i; + + if (num_strings < 1 || strings_utf8 == NULL || strings_utf8[0] == NULL) { + GST_LOG ("Not adding text frame, no strings"); + return; + } + + id3v2_frame_init (&frame, frame_id, 0); + id3v2_frame_write_uint8 (&frame, ID3V2_ENCODING_UTF8); + + GST_LOG ("Adding text frame %s with %d strings", frame_id, num_strings); + + for (i = 0; i < num_strings; ++i) { + len = strlen (strings_utf8[i]); + g_return_if_fail (g_utf8_validate (strings_utf8[i], len, NULL)); + + /* write NUL terminator as well */ + id3v2_frame_write_bytes (&frame, (const guint8 *) strings_utf8[i], len + 1); + + /* only v2.4.0 supports multiple strings per frame (according to the + * earlier specs tag readers should just ignore everything after the first + * string, but we probably shouldn't write anything there, just in case + * tag readers that only support the old version are not expecting + * more data after the first string) */ + if (tag->major_version < 4) + break; + } + + if (i < num_strings - 1) { + GST_WARNING ("Only wrote one of multiple string values for text frame %s " + "- ID3v2 supports multiple string values only since v2.4.0, but writing" + "v2.%u.0 tag", frame_id, tag->major_version); + } + + g_array_append_val (tag->frames, frame); +} + +/* ====================================================================== */ + +static void +add_text_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * frame_id) +{ + gchar **strings; + guint n, i; + + GST_LOG ("Adding '%s' frame", frame_id); + + strings = g_new0 (gchar *, num_tags + 1); + for (n = 0, i = 0; n < num_tags; ++n) { + if (gst_tag_list_get_string_index (list, tag, n, &strings[i]) && + strings[i] != NULL) { + GST_LOG ("%s: %s[%u] = '%s'", frame_id, tag, i, strings[i]); + ++i; + } + } + + if (strings[0] != NULL) { + id3v2_tag_add_text_frame (id3v2tag, frame_id, strings, i); + } else { + GST_WARNING ("Empty list for tag %s, skipping", tag); + } + + g_strfreev (strings); +} + +static void +add_id3v2frame_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * unused) +{ + guint i; + + for (i = 0; i < num_tags; ++i) { + const GValue *val; + GstBuffer *buf; + + val = gst_tag_list_get_value_index (list, tag, i); + buf = (GstBuffer *) gst_value_get_mini_object (val); + + if (buf && GST_BUFFER_CAPS (buf)) { + GstStructure *s; + gint version = 0; + + s = gst_caps_get_structure (GST_BUFFER_CAPS (buf), 0); + /* We can only add it if this private buffer is for the same ID3 version, + because we don't understand the contents at all. */ + if (s && gst_structure_get_int (s, "version", &version) && + version == id3v2tag->major_version) { + GstId3v2Frame frame; + gchar frame_id[5]; + guint16 flags; + guint8 *data = GST_BUFFER_DATA (buf); + gint size = GST_BUFFER_SIZE (buf); + + if (size < 10) /* header size */ + return; + + /* We only reach here if the frame version matches the muxer. Since the + muxer only does v2.3 or v2.4, the frame must be one of those - and + so the frame header is the same format */ + memcpy (frame_id, data, 4); + frame_id[4] = 0; + flags = GST_READ_UINT16_BE (data + 8); + + id3v2_frame_init (&frame, frame_id, flags); + id3v2_frame_write_bytes (&frame, data + 10, size - 10); + + g_array_append_val (id3v2tag->frames, frame); + GST_DEBUG ("Added unparsed tag with %d bytes", size); + } else { + GST_WARNING ("Discarding unrecognised ID3 tag for different ID3 " + "version"); + } + } + } +} + +static void +add_text_tag_v4 (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * frame_id) +{ + if (id3v2tag->major_version == 4) + add_text_tag (id3v2tag, list, tag, num_tags, frame_id); + else { + GST_WARNING ("Cannot serialise tag '%s' in ID3v2.%d", frame_id, + id3v2tag->major_version); + } +} + +static void +add_count_or_num_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * frame_id) +{ + static const struct + { + const gchar *gst_tag; + const gchar *corr_count; /* corresponding COUNT tag (if number) */ + const gchar *corr_num; /* corresponding NUMBER tag (if count) */ + } corr[] = { + { + GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT, NULL}, { + GST_TAG_TRACK_COUNT, NULL, GST_TAG_TRACK_NUMBER}, { + GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, NULL}, { + GST_TAG_ALBUM_VOLUME_COUNT, NULL, GST_TAG_ALBUM_VOLUME_NUMBER} + }; + guint idx; + + for (idx = 0; idx < G_N_ELEMENTS (corr); ++idx) { + if (strcmp (corr[idx].gst_tag, tag) == 0) + break; + } + + g_assert (idx < G_N_ELEMENTS (corr)); + g_assert (frame_id && strlen (frame_id) == 4); + + if (corr[idx].corr_num == NULL) { + guint number; + + /* number tag */ + if (gst_tag_list_get_uint_index (list, tag, 0, &number)) { + gchar *tag_str; + guint count; + + if (gst_tag_list_get_uint_index (list, corr[idx].corr_count, 0, &count)) + tag_str = g_strdup_printf ("%u/%u", number, count); + else + tag_str = g_strdup_printf ("%u", number); + + GST_DEBUG ("Setting %s to %s (frame_id = %s)", tag, tag_str, frame_id); + + id3v2_tag_add_text_frame (id3v2tag, frame_id, &tag_str, 1); + g_free (tag_str); + } + } else if (corr[idx].corr_count == NULL) { + guint count; + + /* count tag */ + if (gst_tag_list_get_uint_index (list, corr[idx].corr_num, 0, &count)) { + GST_DEBUG ("%s handled with %s, skipping", tag, corr[idx].corr_num); + } else if (gst_tag_list_get_uint_index (list, tag, 0, &count)) { + gchar *tag_str = g_strdup_printf ("0/%u", count); + GST_DEBUG ("Setting %s to %s (frame_id = %s)", tag, tag_str, frame_id); + + id3v2_tag_add_text_frame (id3v2tag, frame_id, &tag_str, 1); + g_free (tag_str); + } + } + + if (num_tags > 1) { + GST_WARNING ("more than one %s, can only handle one", tag); + } +} + +static void +add_comment_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * unused) +{ + guint n; + + GST_LOG ("Adding comment frames"); + for (n = 0; n < num_tags; ++n) { + gchar *s = NULL; + + if (gst_tag_list_get_string_index (list, tag, n, &s) && s != NULL) { + gchar *desc = NULL, *val = NULL, *lang = NULL; + int desclen, vallen; + GstId3v2Frame frame; + + id3v2_frame_init (&frame, "COMM", 0); + id3v2_frame_write_uint8 (&frame, ID3V2_ENCODING_UTF8); + + if (strcmp (tag, GST_TAG_COMMENT) == 0 || + !gst_tag_parse_extended_comment (s, &desc, &lang, &val, TRUE)) { + /* create dummy description fields */ + desc = g_strdup ("Comment"); + val = g_strdup (s); + } + + /* If we don't have a valid language, match what taglib does for + unknown languages */ + if (!lang || strlen (lang) < 3) + lang = g_strdup ("XXX"); + + desclen = strlen (desc); + g_return_if_fail (g_utf8_validate (desc, desclen, NULL)); + vallen = strlen (val); + g_return_if_fail (g_utf8_validate (val, vallen, NULL)); + + GST_LOG ("%s[%u] = '%s' (%s|%s|%s)", tag, n, s, GST_STR_NULL (desc), + GST_STR_NULL (lang), GST_STR_NULL (val)); + + id3v2_frame_write_bytes (&frame, (const guint8 *) lang, 3); + /* write description and value, each including NULL terminator */ + id3v2_frame_write_bytes (&frame, (const guint8 *) desc, desclen + 1); + id3v2_frame_write_bytes (&frame, (const guint8 *) val, vallen + 1); + + g_free (lang); + g_free (desc); + g_free (val); + + g_array_append_val (id3v2tag->frames, frame); + } + g_free (s); + } +} + +static void +add_image_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * unused) +{ + guint n; + + for (n = 0; n < num_tags; ++n) { + const GValue *val; + GstBuffer *image; + + GST_DEBUG ("image %u/%u", n + 1, num_tags); + + val = gst_tag_list_get_value_index (list, tag, n); + image = (GstBuffer *) gst_value_get_mini_object (val); + + if (GST_IS_BUFFER (image) && GST_BUFFER_SIZE (image) > 0 && + GST_BUFFER_CAPS (image) != NULL && + !gst_caps_is_empty (GST_BUFFER_CAPS (image))) { + const gchar *mime_type; + GstStructure *s; + + s = gst_caps_get_structure (GST_BUFFER_CAPS (image), 0); + mime_type = gst_structure_get_name (s); + if (mime_type != NULL) { + const gchar *desc; + GstId3v2Frame frame; + + /* APIC frame specifies "-->" if we're providing a URL to the image + rather than directly embedding it */ + if (strcmp (mime_type, "text/uri-list") == 0) + mime_type = "-->"; + + GST_DEBUG ("Attaching picture of %u bytes and mime type %s", + GST_BUFFER_SIZE (image), mime_type); + + id3v2_frame_init (&frame, "APIC", 0); + id3v2_frame_write_uint8 (&frame, ID3V2_ENCODING_UTF8); + id3v2_frame_write_bytes (&frame, (const guint8 *) mime_type, + strlen (mime_type) + 1); + + /* FIXME set image type properly from caps */ + if (strcmp (tag, GST_TAG_PREVIEW_IMAGE) == 0) + id3v2_frame_write_uint8 (&frame, ID3V2_APIC_PICTURE_FILE_ICON); + else + id3v2_frame_write_uint8 (&frame, ID3V2_APIC_PICTURE_OTHER); + + desc = gst_structure_get_string (s, "image-description"); + if (!desc) + desc = ""; + id3v2_frame_write_bytes (&frame, (const guint8 *) desc, + strlen (desc) + 1); + + g_array_append_val (id3v2tag->frames, frame); + } + } else { + GST_WARNING ("NULL image or no caps on image buffer (%p, caps=%" + GST_PTR_FORMAT ")", image, (image) ? GST_BUFFER_CAPS (image) : NULL); + } + } +} + +static void +add_musicbrainz_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * data) +{ + static const struct + { + const gchar gst_tag[28]; + const gchar spec_id[28]; + const gchar realworld_id[28]; + } mb_ids[] = { + { + GST_TAG_MUSICBRAINZ_ARTISTID, "MusicBrainz Artist Id", + "musicbrainz_artistid"}, { + GST_TAG_MUSICBRAINZ_ALBUMID, "MusicBrainz Album Id", "musicbrainz_albumid"}, { + GST_TAG_MUSICBRAINZ_ALBUMARTISTID, "MusicBrainz Album Artist Id", + "musicbrainz_albumartistid"}, { + GST_TAG_MUSICBRAINZ_TRMID, "MusicBrainz TRM Id", "musicbrainz_trmid"}, { + GST_TAG_CDDA_MUSICBRAINZ_DISCID, "MusicBrainz DiscID", + "musicbrainz_discid"}, { + /* the following one is more or less made up, there seems to be little + * evidence that any popular application is actually putting this info + * into TXXX frames; the first one comes from a musicbrainz wiki 'proposed + * tags' page, the second one is analogue to the vorbis/ape/flac tag. */ + GST_TAG_CDDA_CDDB_DISCID, "CDDB DiscID", "discid"} + }; + guint i, idx; + + idx = (guint8) data[0]; + g_assert (idx < G_N_ELEMENTS (mb_ids)); + + for (i = 0; i < num_tags; ++i) { + gchar *id_str; + + if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { + /* add two frames, one with the ID the musicbrainz.org spec mentions + * and one with the ID that applications use in the real world */ + GstId3v2Frame frame1, frame2; + + GST_DEBUG ("Setting '%s' to '%s'", mb_ids[idx].spec_id, id_str); + + id3v2_frame_init (&frame1, "TXXX", 0); + id3v2_frame_write_uint8 (&frame1, ID3V2_ENCODING_UTF8); + id3v2_frame_write_bytes (&frame1, (const guint8 *) mb_ids[idx].spec_id, + strlen (mb_ids[idx].spec_id) + 1); + id3v2_frame_write_bytes (&frame1, (const guint8 *) id_str, + strlen (id_str) + 1); + g_array_append_val (id3v2tag->frames, frame1); + + id3v2_frame_init (&frame2, "TXXX", 0); + id3v2_frame_write_uint8 (&frame2, ID3V2_ENCODING_UTF8); + id3v2_frame_write_bytes (&frame2, + (const guint8 *) mb_ids[idx].realworld_id, + strlen (mb_ids[idx].realworld_id) + 1); + id3v2_frame_write_bytes (&frame2, (const guint8 *) id_str, + strlen (id_str) + 1); + g_array_append_val (id3v2tag->frames, frame2); + + g_free (id_str); + } + } +} + +static void +add_unique_file_id_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * unused) +{ + const gchar *origin = "http://musicbrainz.org"; + gchar *id_str = NULL; + + if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { + GstId3v2Frame frame; + + GST_LOG ("Adding %s (%s): %s", tag, origin, id_str); + + id3v2_frame_init (&frame, "UFID", 0); + id3v2_frame_write_bytes (&frame, (const guint8 *) origin, + strlen (origin) + 1); + id3v2_frame_write_bytes (&frame, (const guint8 *) id_str, + strlen (id_str) + 1); + g_array_append_val (id3v2tag->frames, frame); + + g_free (id_str); + } +} + +static void +add_date_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * unused) +{ + guint n; + guint i = 0; + const gchar *frame_id; + gchar **strings; + + if (id3v2tag->major_version == 3) + frame_id = "TYER"; + else + frame_id = "TDRC"; + + GST_LOG ("Adding date frame"); + + strings = g_new0 (gchar *, num_tags + 1); + for (n = 0; n < num_tags; ++n) { + GDate *date = NULL; + + if (gst_tag_list_get_date_index (list, tag, n, &date) && date != NULL) { + GDateYear year; + gchar *s; + + year = g_date_get_year (date); + if (year > 500 && year < 2100) { + s = g_strdup_printf ("%u", year); + GST_LOG ("%s[%u] = '%s'", tag, n, s); + strings[i] = s; + i++; + } else { + GST_WARNING ("invalid year %u, skipping", year); + } + + g_date_free (date); + } + } + + if (strings[0] != NULL) { + id3v2_tag_add_text_frame (id3v2tag, frame_id, strings, i); + } else { + GST_WARNING ("Empty list for tag %s, skipping", tag); + } + + g_strfreev (strings); +} + +static void +add_encoder_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * unused) +{ + guint n; + gchar **strings; + int i = 0; + + /* ENCODER_VERSION is either handled with the ENCODER tag or not at all */ + if (strcmp (tag, GST_TAG_ENCODER_VERSION) == 0) + return; + + strings = g_new0 (gchar *, num_tags + 1); + for (n = 0; n < num_tags; ++n) { + gchar *encoder = NULL; + + if (gst_tag_list_get_string_index (list, tag, n, &encoder) && encoder) { + guint encoder_version; + gchar *s; + + if (gst_tag_list_get_uint_index (list, GST_TAG_ENCODER_VERSION, n, + &encoder_version) && encoder_version > 0) { + s = g_strdup_printf ("%s %u", encoder, encoder_version); + } else { + s = g_strdup (encoder); + } + + GST_LOG ("encoder[%u] = '%s'", n, s); + strings[i] = s; + i++; + g_free (encoder); + } + } + + if (strings[0] != NULL) { + id3v2_tag_add_text_frame (id3v2tag, "TSSE", strings, i); + } else { + GST_WARNING ("Empty list for tag %s, skipping", tag); + } + + g_strfreev (strings); +} + +static void +add_uri_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * frame_id) +{ + gchar *url = NULL; + + g_assert (frame_id != NULL); + + /* URI tags are limited to one of each per taglist */ + if (gst_tag_list_get_string_index (list, tag, 0, &url) && url != NULL) { + guint url_len; + + url_len = strlen (url); + if (url_len > 0 && gst_uri_is_valid (url)) { + GstId3v2Frame frame; + + id3v2_frame_init (&frame, frame_id, 0); + id3v2_frame_write_bytes (&frame, (const guint8 *) url, strlen (url) + 1); + g_array_append_val (id3v2tag->frames, frame); + } else { + GST_WARNING ("Tag %s does not contain a valid URI (%s)", tag, url); + } + + g_free (url); + } +} + +static void +add_relative_volume_tag (GstId3v2Tag * id3v2tag, const GstTagList * list, + const gchar * tag, guint num_tags, const gchar * unused) +{ + const char *gain_tag_name; + const char *peak_tag_name; + gdouble peak_val; + gdouble gain_val; + const char *identification; + guint16 peak_int; + gint16 gain_int; + guint8 peak_bits; + GstId3v2Frame frame; + gchar *frame_id; + + /* figure out tag names and the identification string to use */ + if (strcmp (tag, GST_TAG_TRACK_PEAK) == 0 || + strcmp (tag, GST_TAG_TRACK_GAIN) == 0) { + gain_tag_name = GST_TAG_TRACK_GAIN; + peak_tag_name = GST_TAG_TRACK_PEAK; + identification = "track"; + GST_DEBUG ("adding track relative-volume frame"); + } else { + gain_tag_name = GST_TAG_ALBUM_GAIN; + peak_tag_name = GST_TAG_ALBUM_PEAK; + identification = "album"; + + if (id3v2tag->major_version == 3) { + GST_WARNING ("Cannot store replaygain album gain data in ID3v2.3"); + return; + } + GST_DEBUG ("adding album relative-volume frame"); + } + + /* find the value for the paired tag (gain, if this is peak, and + * vice versa). if both tags exist, only write the frame when + * we're processing the peak tag. + */ + if (strcmp (tag, GST_TAG_TRACK_PEAK) == 0 || + strcmp (tag, GST_TAG_ALBUM_PEAK) == 0) { + + gst_tag_list_get_double (list, tag, &peak_val); + + if (gst_tag_list_get_tag_size (list, gain_tag_name) > 0) { + gst_tag_list_get_double (list, gain_tag_name, &gain_val); + GST_DEBUG ("setting volume adjustment %g", gain_val); + gain_int = (gint16) (gain_val * 512.0); + } else + gain_int = 0; + + /* copying mutagen: always write as 16 bits for sanity. */ + peak_int = (short) (peak_val * G_MAXSHORT); + peak_bits = 16; + } else { + gst_tag_list_get_double (list, tag, &gain_val); + GST_DEBUG ("setting volume adjustment %g", gain_val); + + gain_int = (gint16) (gain_val * 512.0); + peak_bits = 0; + peak_int = 0; + + if (gst_tag_list_get_tag_size (list, peak_tag_name) != 0) { + GST_DEBUG + ("both gain and peak tags exist, not adding frame this time around"); + return; + } + } + + if (id3v2tag->major_version == 4) { + /* 2.4: Use RVA2 tag */ + frame_id = "RVA2"; + } else { + /* 2.3: Use XRVA tag - this is experimental, but useful in the real world. + This version only officially supports the 'RVAD' tag, but that appears + to not be widely implemented in reality. */ + frame_id = "XRVA"; + } + + id3v2_frame_init (&frame, frame_id, 0); + id3v2_frame_write_bytes (&frame, (const guint8 *) identification, + strlen (identification) + 1); + id3v2_frame_write_uint8 (&frame, 0x01); /* Master volume */ + id3v2_frame_write_uint16 (&frame, gain_int); + id3v2_frame_write_uint8 (&frame, peak_bits); + if (peak_bits) + id3v2_frame_write_uint16 (&frame, peak_int); + + g_array_append_val (id3v2tag->frames, frame); +} + +/* id3demux produces these for frames it cannot parse */ +#define GST_ID3_DEMUX_TAG_ID3V2_FRAME "private-id3v2-frame" + +static const struct +{ + const gchar *gst_tag; + const GstId3v2AddTagFunc func; + const gchar *data; +} add_funcs[] = { + { + /* Simple text tags */ + GST_TAG_ARTIST, add_text_tag, "TPE1"}, { + GST_TAG_TITLE, add_text_tag, "TIT2"}, { + GST_TAG_ALBUM, add_text_tag, "TALB"}, { + GST_TAG_COPYRIGHT, add_text_tag, "TCOP"}, { + GST_TAG_COMPOSER, add_text_tag, "TCOM"}, { + GST_TAG_GENRE, add_text_tag, "TCON"}, { + + /* Private frames */ + GST_ID3_DEMUX_TAG_ID3V2_FRAME, add_id3v2frame_tag, NULL}, { + + /* Track and album numbers */ + GST_TAG_TRACK_NUMBER, add_count_or_num_tag, "TRCK"}, { + GST_TAG_TRACK_COUNT, add_count_or_num_tag, "TRCK"}, { + GST_TAG_ALBUM_VOLUME_NUMBER, add_count_or_num_tag, "TPOS"}, { + GST_TAG_ALBUM_VOLUME_COUNT, add_count_or_num_tag, "TPOS"}, { + + /* Comment tags */ + GST_TAG_COMMENT, add_comment_tag, NULL}, { + GST_TAG_EXTENDED_COMMENT, add_comment_tag, NULL}, { + + /* Images */ + GST_TAG_IMAGE, add_image_tag, NULL}, { + GST_TAG_PREVIEW_IMAGE, add_image_tag, NULL}, { + + /* Misc user-defined text tags for IDs (and UFID frame) */ + GST_TAG_MUSICBRAINZ_ARTISTID, add_musicbrainz_tag, "\000"}, { + GST_TAG_MUSICBRAINZ_ALBUMID, add_musicbrainz_tag, "\001"}, { + GST_TAG_MUSICBRAINZ_ALBUMARTISTID, add_musicbrainz_tag, "\002"}, { + GST_TAG_MUSICBRAINZ_TRMID, add_musicbrainz_tag, "\003"}, { + GST_TAG_CDDA_MUSICBRAINZ_DISCID, add_musicbrainz_tag, "\004"}, { + GST_TAG_CDDA_CDDB_DISCID, add_musicbrainz_tag, "\005"}, { + GST_TAG_MUSICBRAINZ_TRACKID, add_unique_file_id_tag, NULL}, { + + /* Info about encoder */ + GST_TAG_ENCODER, add_encoder_tag, NULL}, { + GST_TAG_ENCODER_VERSION, add_encoder_tag, NULL}, { + + /* URIs */ + GST_TAG_COPYRIGHT_URI, add_uri_tag, "WCOP"}, { + GST_TAG_LICENSE_URI, add_uri_tag, "WCOP"}, { + + /* Up to here, all the frame ids and contents have been the same between + versions 2.3 and 2.4. The rest of them differ... */ + /* Date (in ID3v2.3, this is a TYER tag. In v2.4, it's a TDRC tag */ + GST_TAG_DATE, add_date_tag, NULL}, { + + /* Replaygain data (not really supported in 2.3, we use an experimental + tag there) */ + GST_TAG_TRACK_PEAK, add_relative_volume_tag, NULL}, { + GST_TAG_TRACK_GAIN, add_relative_volume_tag, NULL}, { + GST_TAG_ALBUM_PEAK, add_relative_volume_tag, NULL}, { + GST_TAG_ALBUM_GAIN, add_relative_volume_tag, NULL}, { + + /* Sortable version of various tags. These are all v2.4 ONLY */ + GST_TAG_ARTIST_SORTNAME, add_text_tag_v4, "TSOP"}, { + GST_TAG_ALBUM_SORTNAME, add_text_tag_v4, "TSOA"}, { + GST_TAG_TITLE_SORTNAME, add_text_tag_v4, "TSOT"} +}; + +static void +foreach_add_tag (const GstTagList * list, const gchar * tag, gpointer userdata) +{ + GstId3v2Tag *id3v2tag = (GstId3v2Tag *) userdata; + guint num_tags, i; + + num_tags = gst_tag_list_get_tag_size (list, tag); + + GST_LOG ("Processing tag %s (num=%u)", tag, num_tags); + + if (num_tags > 1 && gst_tag_is_fixed (tag)) { + GST_WARNING ("Multiple occurences of fixed tag '%s', ignoring some", tag); + num_tags = 1; + } + + for (i = 0; i < G_N_ELEMENTS (add_funcs); ++i) { + if (strcmp (add_funcs[i].gst_tag, tag) == 0) { + add_funcs[i].func (id3v2tag, list, tag, num_tags, add_funcs[i].data); + break; + } + } + + if (i == G_N_ELEMENTS (add_funcs)) { + GST_WARNING ("Unsupported tag '%s' - not written", tag); + } +} + +GstBuffer * +gst_id3mux_render_v2_tag (GstTagMux * mux, GstTagList * taglist, int version) +{ + GstId3v2Tag tag; + GstBuffer *buf; + + if (!id3v2_tag_init (&tag, version)) { + GST_WARNING_OBJECT (mux, "Unsupported version %d", version); + return NULL; + } + + /* Render the tag */ + gst_tag_list_foreach (taglist, foreach_add_tag, &tag); + +#if 0 + /* Do we want to add our own signature to the tag somewhere? */ + { + gchar *tag_producer_str; + + tag_producer_str = g_strdup_printf ("(GStreamer id3v2mux %s, using " + "taglib %u.%u)", VERSION, TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION); + add_one_txxx_tag (id3v2tag, "tag_encoder", tag_producer_str); + g_free (tag_producer_str); + } +#endif + + /* Create buffer with tag */ + buf = id3v2_tag_to_buffer (&tag); + gst_buffer_set_caps (buf, GST_PAD_CAPS (mux->srcpad)); + GST_LOG_OBJECT (mux, "tag size = %d bytes", GST_BUFFER_SIZE (buf)); + + id3v2_tag_unset (&tag); + + return buf; +} + +#define ID3_V1_TAG_SIZE 128 + +typedef void (*GstId3v1WriteFunc) (const GstTagList * list, + const gchar * gst_tag, guint8 * dst, int len); + +static void +latin1_convert (const GstTagList * list, const gchar * tag, + guint8 * dst, int maxlen) +{ + gchar *str; + gsize len; + gchar *latin1; + + if (!gst_tag_list_get_string (list, tag, &str)) + return; + + /* Convert to Latin-1 (ISO-8859-1), replacing unrepresentable characters + with '?' */ + latin1 = g_convert_with_fallback (str, -1, "ISO-8859-1", "UTF-8", "?", + NULL, &len, NULL); + + if (latin1) { + len = MIN (len, maxlen); + memcpy (dst, latin1, len); + g_free (latin1); + } + + g_free (str); +} + +static void +date_v1_convert (const GstTagList * list, const gchar * tag, + guint8 * dst, int maxlen) +{ + GDate *date; + + /* Only one date supported */ + if (gst_tag_list_get_date_index (list, tag, 0, &date) && date != NULL) { + GDateYear year = g_date_get_year (date); + /* Check for plausible year */ + if (year > 500 && year < 2100) { + gchar str[5]; + g_snprintf (str, 5, "%.4u", year); + memcpy (dst, str, 4); + } else { + GST_WARNING ("invalid year %u, skipping", year); + } + + g_date_free (date); + } +} + +static void +genre_v1_convert (const GstTagList * list, const gchar * tag, + guint8 * dst, int maxlen) +{ + gchar *str; + int genreidx = -1; + guint i, max; + + /* We only support one genre */ + if (!gst_tag_list_get_string_index (list, tag, 0, &str)) + return; + + max = gst_tag_id3_genre_count (); + + for (i = 0; i < max; i++) { + const gchar *genre = gst_tag_id3_genre_get (i); + if (g_str_equal (str, genre)) { + genreidx = i; + break; + } + } + + if (genreidx >= 0 && genreidx <= 127) + *dst = (guint8) genreidx; + + g_free (str); +} + +static void +track_number_convert (const GstTagList * list, const gchar * tag, + guint8 * dst, int maxlen) +{ + guint tracknum; + + /* We only support one track number */ + if (!gst_tag_list_get_uint_index (list, tag, 0, &tracknum)) + return; + + if (tracknum <= 127) + *dst = (guint8) tracknum; +} + +static const struct +{ + const gchar *gst_tag; + const gint offset; + const gint length; + const GstId3v1WriteFunc func; +} v1_funcs[] = { + { + GST_TAG_TITLE, 3, 30, latin1_convert}, { + GST_TAG_ARTIST, 33, 30, latin1_convert}, { + GST_TAG_ALBUM, 63, 30, latin1_convert}, { + GST_TAG_DATE, 93, 4, date_v1_convert}, { + GST_TAG_COMMENT, 97, 28, latin1_convert}, { + /* Note: one-byte gap here */ + GST_TAG_TRACK_NUMBER, 126, 1, track_number_convert}, { + GST_TAG_GENRE, 127, 1, genre_v1_convert} +}; + +GstBuffer * +gst_id3mux_render_v1_tag (GstTagMux * mux, GstTagList * taglist) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (ID3_V1_TAG_SIZE); + guint8 *data = GST_BUFFER_DATA (buf); + int i; + + memset (data, 0, ID3_V1_TAG_SIZE); + + data[0] = 'T'; + data[1] = 'A'; + data[2] = 'G'; + + for (i = 0; i < G_N_ELEMENTS (v1_funcs); i++) { + v1_funcs[i].func (taglist, v1_funcs[i].gst_tag, data + v1_funcs[i].offset, + v1_funcs[i].length); + } + + gst_buffer_set_caps (buf, GST_PAD_CAPS (mux->srcpad)); + return buf; +} diff --git a/gst/id3tag/id3tag.h b/gst/id3tag/id3tag.h new file mode 100644 index 00000000..1fb59376 --- /dev/null +++ b/gst/id3tag/id3tag.h @@ -0,0 +1,32 @@ +/* GStreamer ID3v2 tag writer + * Copyright (C) 2009 Tim-Philipp Müller + * + * 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. + */ + +#include "gsttagmux.h" + +G_BEGIN_DECLS + +#define ID3_VERSION_2_3 3 +#define ID3_VERSION_2_4 4 + +GstBuffer * gst_id3mux_render_v2_tag (GstTagMux * mux, GstTagList * taglist, + int version); +GstBuffer * gst_id3mux_render_v1_tag (GstTagMux * mux, GstTagList * taglist); + +G_END_DECLS + -- cgit v1.2.1 From 02bff8754b34a3d5f596f26c8dd7e83ae356130f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 22 May 2009 01:27:09 +0100 Subject: id3tag: register GType of the base class with a less generic name .. so we can easily move the base class into -base later without causing GType name conflicts. --- gst/id3tag/gsttagmux.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'gst/id3tag') diff --git a/gst/id3tag/gsttagmux.c b/gst/id3tag/gsttagmux.c index bfa4e1bc..257f82bb 100644 --- a/gst/id3tag/gsttagmux.c +++ b/gst/id3tag/gsttagmux.c @@ -55,7 +55,12 @@ gst_tag_mux_iface_init (GType tag_type) g_type_add_interface_static (tag_type, GST_TYPE_TAG_SETTER, &tag_setter_info); } -GST_BOILERPLATE_FULL (GstTagMux, gst_tag_mux, +/* make sure to register a less generic type so we can easily move this + * GstTagMux base class into -base without causing GType name conflicts */ +typedef GstTagMux GstID3TagMux; +typedef GstTagMuxClass GstID3TagMuxClass; + +GST_BOILERPLATE_FULL (GstID3TagMux, gst_tag_mux, GstElement, GST_TYPE_ELEMENT, gst_tag_mux_iface_init); -- cgit v1.2.1 From fdaeae57c90ebd1fa001f398be6fec9050129781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 22 May 2009 09:51:29 +0100 Subject: id3tag: change GType to GstId3Tag so it doesn't conflict with the id3tag plugin in -ugly --- gst/id3tag/gstid3tag.c | 14 +++++++------- gst/id3tag/gstid3tag.h | 12 ++++++------ gst/id3tag/gsttagmux.c | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) (limited to 'gst/id3tag') diff --git a/gst/id3tag/gstid3tag.c b/gst/id3tag/gstid3tag.c index f67d781f..98b05d0d 100644 --- a/gst/id3tag/gstid3tag.c +++ b/gst/id3tag/gstid3tag.c @@ -76,7 +76,7 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_ALWAYS, GST_STATIC_CAPS ("application/x-id3")); -GST_BOILERPLATE (GstID3Tag, gst_id3tag, GstTagMux, GST_TYPE_TAG_MUX); +GST_BOILERPLATE (GstId3Tag, gst_id3tag, GstTagMux, GST_TYPE_TAG_MUX); static GstBuffer *gst_id3tag_render_v2_tag (GstTagMux * mux, GstTagList * taglist); @@ -107,7 +107,7 @@ gst_id3tag_base_init (gpointer g_class) } static void -gst_id3tag_class_init (GstID3TagClass * klass) +gst_id3tag_class_init (GstId3TagClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; @@ -137,7 +137,7 @@ gst_id3tag_class_init (GstID3TagClass * klass) } static void -gst_id3tag_init (GstID3Tag * id3mux, GstID3TagClass * id3mux_class) +gst_id3tag_init (GstId3Tag * id3mux, GstId3TagClass * id3mux_class) { id3mux->write_v1 = DEFAULT_WRITE_V1; id3mux->write_v2 = DEFAULT_WRITE_V2; @@ -149,7 +149,7 @@ static void gst_id3tag_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { - GstID3Tag *mux = GST_ID3TAG (object); + GstId3Tag *mux = GST_ID3TAG (object); switch (prop_id) { case ARG_WRITE_V1: @@ -171,7 +171,7 @@ static void gst_id3tag_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { - GstID3Tag *mux = GST_ID3TAG (object); + GstId3Tag *mux = GST_ID3TAG (object); switch (prop_id) { case ARG_WRITE_V1: @@ -192,7 +192,7 @@ gst_id3tag_get_property (GObject * object, guint prop_id, static GstBuffer * gst_id3tag_render_v2_tag (GstTagMux * mux, GstTagList * taglist) { - GstID3Tag *id3mux = GST_ID3TAG (mux); + GstId3Tag *id3mux = GST_ID3TAG (mux); if (id3mux->write_v2) return gst_id3mux_render_v2_tag (mux, taglist, id3mux->v2_major_version); @@ -203,7 +203,7 @@ gst_id3tag_render_v2_tag (GstTagMux * mux, GstTagList * taglist) static GstBuffer * gst_id3tag_render_v1_tag (GstTagMux * mux, GstTagList * taglist) { - GstID3Tag *id3mux = GST_ID3TAG (mux); + GstId3Tag *id3mux = GST_ID3TAG (mux); if (id3mux->write_v1) return gst_id3mux_render_v1_tag (mux, taglist); diff --git a/gst/id3tag/gstid3tag.h b/gst/id3tag/gstid3tag.h index 6b33df25..60643594 100644 --- a/gst/id3tag/gstid3tag.h +++ b/gst/id3tag/gstid3tag.h @@ -28,10 +28,10 @@ G_BEGIN_DECLS -typedef struct _GstID3Tag GstID3Tag; -typedef struct _GstID3TagClass GstID3TagClass; +typedef struct _GstId3Tag GstId3Tag; +typedef struct _GstId3TagClass GstId3TagClass; -struct _GstID3Tag { +struct _GstId3Tag { GstTagMux tagmux; gboolean write_v1; @@ -40,16 +40,16 @@ struct _GstID3Tag { gint v2_major_version; }; -struct _GstID3TagClass { +struct _GstId3TagClass { GstTagMuxClass tagmux_class; }; #define GST_TYPE_ID3TAG \ (gst_id3tag_get_type()) #define GST_ID3TAG(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ID3TAG,GstID3Tag)) + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ID3TAG,GstId3Tag)) #define GST_ID3TAG_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ID3TAG,GstID3TagClass)) + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ID3TAG,GstId3TagClass)) #define GST_IS_ID3TAG(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ID3TAG)) #define GST_IS_ID3TAG_CLASS(klass) \ diff --git a/gst/id3tag/gsttagmux.c b/gst/id3tag/gsttagmux.c index 257f82bb..3b7ff119 100644 --- a/gst/id3tag/gsttagmux.c +++ b/gst/id3tag/gsttagmux.c @@ -57,10 +57,10 @@ gst_tag_mux_iface_init (GType tag_type) /* make sure to register a less generic type so we can easily move this * GstTagMux base class into -base without causing GType name conflicts */ -typedef GstTagMux GstID3TagMux; -typedef GstTagMuxClass GstID3TagMuxClass; +typedef GstTagMux GstId3TagMux; +typedef GstTagMuxClass GstId3TagMuxClass; -GST_BOILERPLATE_FULL (GstID3TagMux, gst_tag_mux, +GST_BOILERPLATE_FULL (GstId3TagMux, gst_tag_mux, GstElement, GST_TYPE_ELEMENT, gst_tag_mux_iface_init); -- cgit v1.2.1 From e5b1c976c4dbbea82d0bc1f0de7c0bf4b6d1705d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 22 May 2009 09:54:57 +0100 Subject: id3tag: canonicalise function names --- gst/id3tag/gstid3tag.c | 38 +++++++++++++++++++------------------- gst/id3tag/gstid3tag.h | 4 ++-- gst/id3tag/id3tag.c | 4 ++-- 3 files changed, 23 insertions(+), 23 deletions(-) (limited to 'gst/id3tag') diff --git a/gst/id3tag/gstid3tag.c b/gst/id3tag/gstid3tag.c index 98b05d0d..9c8072c0 100644 --- a/gst/id3tag/gstid3tag.c +++ b/gst/id3tag/gstid3tag.c @@ -56,8 +56,8 @@ #include -GST_DEBUG_CATEGORY (gst_id3tag_debug); -#define GST_CAT_DEFAULT gst_id3tag_debug +GST_DEBUG_CATEGORY (gst_id3_tag_debug); +#define GST_CAT_DEFAULT gst_id3_tag_debug enum { @@ -76,20 +76,20 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_ALWAYS, GST_STATIC_CAPS ("application/x-id3")); -GST_BOILERPLATE (GstId3Tag, gst_id3tag, GstTagMux, GST_TYPE_TAG_MUX); +GST_BOILERPLATE (GstId3Tag, gst_id3_tag, GstTagMux, GST_TYPE_TAG_MUX); -static GstBuffer *gst_id3tag_render_v2_tag (GstTagMux * mux, +static GstBuffer *gst_id3_tag_render_v2_tag (GstTagMux * mux, GstTagList * taglist); -static GstBuffer *gst_id3tag_render_v1_tag (GstTagMux * mux, +static GstBuffer *gst_id3_tag_render_v1_tag (GstTagMux * mux, GstTagList * taglist); -static void gst_id3tag_set_property (GObject * object, guint prop_id, +static void gst_id3_tag_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); -static void gst_id3tag_get_property (GObject * object, guint prop_id, +static void gst_id3_tag_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void -gst_id3tag_base_init (gpointer g_class) +gst_id3_tag_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); @@ -102,17 +102,17 @@ gst_id3tag_base_init (gpointer g_class) "Michael Smith , " "Tim-Philipp Müller "); - GST_DEBUG_CATEGORY_INIT (gst_id3tag_debug, "id3tag", 0, + GST_DEBUG_CATEGORY_INIT (gst_id3_tag_debug, "id3tag", 0, "ID3 v1 and v2 tag muxer"); } static void -gst_id3tag_class_init (GstId3TagClass * klass) +gst_id3_tag_class_init (GstId3TagClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; - gobject_class->set_property = gst_id3tag_set_property; - gobject_class->get_property = gst_id3tag_get_property; + gobject_class->set_property = gst_id3_tag_set_property; + gobject_class->get_property = gst_id3_tag_get_property; g_object_class_install_property (gobject_class, ARG_WRITE_V1, g_param_spec_boolean ("write-v1", "Write id3v1 tag", @@ -131,13 +131,13 @@ gst_id3tag_class_init (GstId3TagClass * klass) G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); GST_TAG_MUX_CLASS (klass)->render_start_tag = - GST_DEBUG_FUNCPTR (gst_id3tag_render_v2_tag); + GST_DEBUG_FUNCPTR (gst_id3_tag_render_v2_tag); - GST_TAG_MUX_CLASS (klass)->render_end_tag = gst_id3tag_render_v1_tag; + GST_TAG_MUX_CLASS (klass)->render_end_tag = gst_id3_tag_render_v1_tag; } static void -gst_id3tag_init (GstId3Tag * id3mux, GstId3TagClass * id3mux_class) +gst_id3_tag_init (GstId3Tag * id3mux, GstId3TagClass * id3mux_class) { id3mux->write_v1 = DEFAULT_WRITE_V1; id3mux->write_v2 = DEFAULT_WRITE_V2; @@ -146,7 +146,7 @@ gst_id3tag_init (GstId3Tag * id3mux, GstId3TagClass * id3mux_class) } static void -gst_id3tag_set_property (GObject * object, guint prop_id, +gst_id3_tag_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstId3Tag *mux = GST_ID3TAG (object); @@ -168,7 +168,7 @@ gst_id3tag_set_property (GObject * object, guint prop_id, } static void -gst_id3tag_get_property (GObject * object, guint prop_id, +gst_id3_tag_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstId3Tag *mux = GST_ID3TAG (object); @@ -190,7 +190,7 @@ gst_id3tag_get_property (GObject * object, guint prop_id, } static GstBuffer * -gst_id3tag_render_v2_tag (GstTagMux * mux, GstTagList * taglist) +gst_id3_tag_render_v2_tag (GstTagMux * mux, GstTagList * taglist) { GstId3Tag *id3mux = GST_ID3TAG (mux); @@ -201,7 +201,7 @@ gst_id3tag_render_v2_tag (GstTagMux * mux, GstTagList * taglist) } static GstBuffer * -gst_id3tag_render_v1_tag (GstTagMux * mux, GstTagList * taglist) +gst_id3_tag_render_v1_tag (GstTagMux * mux, GstTagList * taglist) { GstId3Tag *id3mux = GST_ID3TAG (mux); diff --git a/gst/id3tag/gstid3tag.h b/gst/id3tag/gstid3tag.h index 60643594..a9a1ad1c 100644 --- a/gst/id3tag/gstid3tag.h +++ b/gst/id3tag/gstid3tag.h @@ -45,7 +45,7 @@ struct _GstId3TagClass { }; #define GST_TYPE_ID3TAG \ - (gst_id3tag_get_type()) + (gst_id3_tag_get_type()) #define GST_ID3TAG(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ID3TAG,GstId3Tag)) #define GST_ID3TAG_CLASS(klass) \ @@ -55,7 +55,7 @@ struct _GstId3TagClass { #define GST_IS_ID3TAG_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ID3TAG)) -GType gst_id3tag_get_type (void); +GType gst_id3_tag_get_type (void); G_END_DECLS diff --git a/gst/id3tag/id3tag.c b/gst/id3tag/id3tag.c index 0e040f7e..a39e2a8e 100644 --- a/gst/id3tag/id3tag.c +++ b/gst/id3tag/id3tag.c @@ -25,8 +25,8 @@ #include -GST_DEBUG_CATEGORY_EXTERN (gst_id3tag_debug); -#define GST_CAT_DEFAULT gst_id3tag_debug +GST_DEBUG_CATEGORY_EXTERN (gst_id3_tag_debug); +#define GST_CAT_DEFAULT gst_id3_tag_debug #define ID3V2_APIC_PICTURE_OTHER 0 #define ID3V2_APIC_PICTURE_FILE_ICON 1 -- cgit v1.2.1