From 08e6b5c54e030da30bca944dd1b0e0aca00fb6eb Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Mon, 18 Jul 2011 18:09:53 +0200 Subject: [PATCH] tag: id3v2: add id3v2 tag parsing helpers https://bugzilla.gnome.org/show_bug.cgi?id=654388 --- configure.ac | 11 +++ docs/libs/gst-plugins-base-libs-sections.txt | 2 + gst-libs/gst/tag/Makefile.am | 8 +- gst-libs/gst/tag/id3v2.c | 109 +++++++++++++++------------ gst-libs/gst/tag/id3v2.h | 34 ++++----- gst-libs/gst/tag/id3v2frames.c | 15 ++-- gst-libs/gst/tag/tag.h | 16 ++++ gst-libs/gst/tag/tags.c | 5 ++ win32/common/libgsttag.def | 2 + 9 files changed, 121 insertions(+), 81 deletions(-) diff --git a/configure.ac b/configure.ac index f14abe1..a76691f 100644 --- a/configure.ac +++ b/configure.ac @@ -505,6 +505,17 @@ else AM_CONDITIONAL(USE_ISO_CODES, false) fi +dnl *** zlib is optionally used by id3 tag parsing in libgsttag *** +translit(dnm, m, l) AM_CONDITIONAL(USE_ZLIB, true) +AG_GST_CHECK_FEATURE(ZLIB, [zlib support for ID3 parsing in libgsttag],, [ + AG_GST_CHECK_LIBHEADER(ZLIB, + z, uncompress,, zlib.h, [ + HAVE_ZLIB="yes" + ZLIB_LIBS="-lz" + AC_SUBST(ZLIB_LIBS) + ]) +]) + dnl *** sys plug-ins *** echo diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index 54d9722..19cd66f 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -1782,6 +1782,8 @@ gst_tag_from_id3_tag gst_tag_from_id3_user_tag gst_tag_to_id3_tag gst_tag_list_add_id3_image +gst_tag_get_id3v2_tag_size +gst_tag_list_from_id3v2_tag
diff --git a/gst-libs/gst/tag/Makefile.am b/gst-libs/gst/tag/Makefile.am index 48137d6..e7dd562 100644 --- a/gst-libs/gst/tag/Makefile.am +++ b/gst-libs/gst/tag/Makefile.am @@ -9,19 +9,19 @@ lib_LTLIBRARIES = libgsttag-@GST_MAJORMINOR@.la libgsttag_@GST_MAJORMINOR@_la_SOURCES = \ gstvorbistag.c gstid3tag.c gstxmptag.c gstexiftag.c \ lang.c licenses.c tags.c gsttagdemux.c gsttagmux.c \ - gsttageditingprivate.c xmpwriter.c + gsttageditingprivate.c id3v2.c id3v2frames.c xmpwriter.c libgsttag_@GST_MAJORMINOR@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) \ - $(GST_BASE_CFLAGS) $(GST_CFLAGS) \ + $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(ZLIB_CFLAGS) \ -DLICENSE_TRANSLATIONS_PATH=\"$(pkgdatadir)/license-translations.dict\" -libgsttag_@GST_MAJORMINOR@_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) +libgsttag_@GST_MAJORMINOR@_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) $(ZLIB_LIBS) libgsttag_@GST_MAJORMINOR@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) # lang-tables.dat contains generated static data and is included by lang.c # licenses-tables.dat contains generated data and is included by licenses.c EXTRA_DIST = lang-tables.dat licenses-tables.dat -noinst_HEADERS = gsttageditingprivate.h +noinst_HEADERS = gsttageditingprivate.h id3v2.h if HAVE_INTROSPECTION BUILT_GIRSOURCES = GstTag-@GST_MAJORMINOR@.gir diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index d201d7c..5c99262 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -25,17 +25,14 @@ #include #include -#include "id3tags.h" - -GST_DEBUG_CATEGORY_EXTERN (id3demux_debug); -#define GST_CAT_DEFAULT (id3demux_debug) +#include "id3v2.h" #define HANDLE_INVALID_SYNCSAFE -static ID3TagsResult -id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size); + +static gboolean id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size); guint -read_synch_uint (const guint8 * data, guint size) +id3v2_read_synch_uint (const guint8 * data, guint size) { gint i; guint result = 0; @@ -62,16 +59,29 @@ read_synch_uint (const guint8 * data, guint size) return result; } +/** + * gst_tag_get_id3v2_tag_size: + * @buffer: buffer holding ID3v2 tag (or at least the start of one) + * + * Determines size of an ID3v2 tag on buffer containing at least ID3v2 header, + * i.e. at least #GST_TAG_ID3V2_HEADER_SIZE (10) bytes; + * + * Returns: Size of tag, or 0 if header is invalid or too small. + * + * Since: 0.10.36 + */ guint -id3demux_calc_id3v2_tag_size (GstBuffer * buf) +gst_tag_get_id3v2_tag_size (GstBuffer * buffer) { guint8 *data, flags; guint size; - g_assert (buf != NULL); - g_assert (GST_BUFFER_SIZE (buf) >= ID3V2_HDR_SIZE); + g_return_val_if_fail (buffer != NULL, 0); - data = GST_BUFFER_DATA (buf); + if (GST_BUFFER_SIZE (buffer) < ID3V2_HDR_SIZE) + return 0; + + data = GST_BUFFER_DATA (buffer); /* Check for 'ID3' string at start of buffer */ if (data[0] != 'I' || data[1] != 'D' || data[2] != '3') { @@ -83,7 +93,7 @@ id3demux_calc_id3v2_tag_size (GstBuffer * buf) flags = data[5]; /* Read the size from the header */ - size = read_synch_uint (data + 6, 4); + size = id3v2_read_synch_uint (data + 6, 4); if (size == 0) return ID3V2_HDR_SIZE; @@ -98,7 +108,7 @@ id3demux_calc_id3v2_tag_size (GstBuffer * buf) } guint8 * -id3demux_ununsync_data (const guint8 * unsync_data, guint32 * size) +id3v2_ununsync_data (const guint8 * unsync_data, guint32 * size) { const guint8 *end; guint8 *out, *uu; @@ -125,26 +135,32 @@ id3demux_ununsync_data (const guint8 * unsync_data, guint32 * size) return out; } -/* caller must pass buffer with full ID3 tag */ -ID3TagsResult -id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, - GstTagList ** tags) +/** + * gst_tag_list_from_id3v2_tag: + * @buffer: buffer to convert + * + * Creates a new tag list that contains the information parsed out of a + * ID3 tag. + * + * Returns: A new #GstTagList with all tags that could be extracted from the + * given vorbiscomment buffer or NULL on error. + * + * Since: 0.10.36 + */ +GstTagList * +gst_tag_list_from_id3v2_tag (GstBuffer * buffer) { guint8 *data, *uu_data = NULL; guint read_size; ID3TagsWorking work; guint8 flags; - ID3TagsResult result; guint16 version; - read_size = id3demux_calc_id3v2_tag_size (buffer); - - if (id3v2_size) - *id3v2_size = read_size; + read_size = gst_tag_get_id3v2_tag_size (buffer); /* Ignore tag if it has no frames attached, but skip the header then */ - if (read_size <= ID3V2_HDR_SIZE) - return ID3TAGS_BROKEN_TAG; + if (read_size < ID3V2_HDR_SIZE) + return NULL; data = GST_BUFFER_DATA (buffer); @@ -159,7 +175,7 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, GST_WARNING ("ID3v2 tag is from revision 2.%d.%d, " "but decoder only supports 2.%d.%d. Ignoring as per spec.", version >> 8, version & 0xff, ID3V2_VERSION >> 8, ID3V2_VERSION & 0xff); - return ID3TAGS_BROKEN_TAG; + return NULL; } GST_DEBUG ("ID3v2 header flags: %s %s %s %s", @@ -174,14 +190,12 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, ("Found ID3v2 tag with revision 2.%d.%d - need %u more bytes to read", version >> 8, version & 0xff, (guint) (read_size - GST_BUFFER_SIZE (buffer))); - return ID3TAGS_MORE_DATA; /* Need more data to decode with */ + return NULL; } GST_DEBUG ("Reading ID3v2 tag with revision 2.%d.%d of size %u", version >> 8, version & 0xff, read_size); - g_return_val_if_fail (tags != NULL, ID3TAGS_READ_TAG); - GST_MEMDUMP ("ID3v2 tag", GST_BUFFER_DATA (buffer), read_size); memset (&work, 0, sizeof (ID3TagsWorking)); @@ -200,23 +214,21 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, * data that needs un-unsyncing, but not the frame headers. */ if ((flags & ID3V2_HDR_FLAG_UNSYNC) != 0 && ID3V2_VER_MAJOR (version) <= 3) { GST_DEBUG ("Un-unsyncing entire tag"); - uu_data = id3demux_ununsync_data (work.hdr.frame_data, + uu_data = id3v2_ununsync_data (work.hdr.frame_data, &work.hdr.frame_data_size); work.hdr.frame_data = uu_data; GST_MEMDUMP ("ID3v2 tag (un-unsyced)", uu_data, work.hdr.frame_data_size); } - result = id3demux_id3v2_frames_to_tag_list (&work, work.hdr.frame_data_size); - - *tags = work.tags; + id3v2_frames_to_tag_list (&work, work.hdr.frame_data_size); g_free (uu_data); - return result; + return work.tags; } static guint -id3demux_id3v2_frame_hdr_size (guint id3v2ver) +id3v2_frame_hdr_size (guint id3v2ver) { /* ID3v2 < 2.3.0 only had 6 byte header */ switch (ID3V2_VER_MAJOR (id3v2ver)) { @@ -333,7 +345,7 @@ convert_fid_to_v240 (gchar * frame_id) /* add unknown or unhandled ID3v2 frames to the taglist as binary blobs */ static void -id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) +id3v2_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) { GstBuffer *blob; GstCaps *caps; @@ -379,29 +391,29 @@ id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) /* gst_util_dump_mem (GST_BUFFER_DATA (blob), GST_BUFFER_SIZE (blob)); */ gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, - GST_ID3_DEMUX_TAG_ID3V2_FRAME, blob, NULL); + GST_TAG_ID3V2_FRAME, blob, NULL); gst_buffer_unref (blob); } -static ID3TagsResult -id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) +static gboolean +id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) { guint frame_hdr_size; guint8 *start; /* Extended header if present */ if (work->hdr.flags & ID3V2_HDR_FLAG_EXTHDR) { - work->hdr.ext_hdr_size = read_synch_uint (work->hdr.frame_data, 4); + work->hdr.ext_hdr_size = id3v2_read_synch_uint (work->hdr.frame_data, 4); if (work->hdr.ext_hdr_size < 6 || (work->hdr.ext_hdr_size) > work->hdr.frame_data_size) { GST_DEBUG ("Invalid extended header. Broken tag"); - return ID3TAGS_BROKEN_TAG; + return FALSE; } work->hdr.ext_flag_bytes = work->hdr.frame_data[4]; if (5 + work->hdr.ext_flag_bytes > work->hdr.frame_data_size) { GST_DEBUG ("Tag claims extended header, but doesn't have enough bytes. Broken tag"); - return ID3TAGS_BROKEN_TAG; + return FALSE; } work->hdr.ext_flag_data = work->hdr.frame_data + 5; @@ -410,14 +422,13 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) } start = GST_BUFFER_DATA (work->buffer); - frame_hdr_size = id3demux_id3v2_frame_hdr_size (work->hdr.version); + frame_hdr_size = id3v2_frame_hdr_size (work->hdr.version); if (work->hdr.frame_data_size <= frame_hdr_size) { GST_DEBUG ("Tag has no data frames. Broken tag"); - return ID3TAGS_BROKEN_TAG; /* Must have at least one frame */ + return FALSE; /* Must have at least one frame */ } work->tags = gst_tag_list_new (); - g_return_val_if_fail (work->tags != NULL, ID3TAGS_READ_TAG); while (work->hdr.frame_data_size > frame_hdr_size) { guint frame_size = 0; @@ -454,7 +465,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) frame_id[3] = work->hdr.frame_data[3]; frame_id[4] = 0; if (read_synch_size) - frame_size = read_synch_uint (work->hdr.frame_data + 4, 4); + frame_size = id3v2_read_synch_uint (work->hdr.frame_data + 4, 4); else frame_size = GST_READ_UINT32_BE (work->hdr.frame_data + 4); @@ -519,11 +530,11 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) work->frame_id = frame_id; work->frame_flags = frame_flags; - if (id3demux_id3v2_parse_frame (work)) { + if (id3v2_parse_frame (work)) { GST_LOG ("Extracted frame with id %s", frame_id); } else { GST_LOG ("Failed to extract frame with id %s", frame_id); - id3demux_add_id3v2_frame_blob_to_taglist (work, frame_size); + id3v2_add_id3v2_frame_blob_to_taglist (work, frame_size); } } work->hdr.frame_data += frame_size; @@ -534,7 +545,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) GST_DEBUG ("Could not extract any frames from tag. Broken or empty tag"); gst_tag_list_free (work->tags); work->tags = NULL; - return ID3TAGS_BROKEN_TAG; + return FALSE; } /* Set day/month now if they were in a separate (obsolete) TDAT frame */ @@ -550,5 +561,5 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) } } - return ID3TAGS_READ_TAG; + return TRUE; } diff --git a/gst-libs/gst/tag/id3v2.h b/gst-libs/gst/tag/id3v2.h index 14a42de..d9ce504 100644 --- a/gst-libs/gst/tag/id3v2.h +++ b/gst-libs/gst/tag/id3v2.h @@ -23,25 +23,11 @@ G_BEGIN_DECLS -/* private tag for storing unprocessed ID3v2 frames */ -#define GST_ID3_DEMUX_TAG_ID3V2_FRAME "private-id3v2-frame" - -#define ID3V1_TAG_SIZE 128 #define ID3V2_MARK_SIZE 3 -#define ID3V2_HDR_SIZE 10 - -typedef enum { - ID3TAGS_MORE_DATA, - ID3TAGS_READ_TAG, - ID3TAGS_BROKEN_TAG -} ID3TagsResult; +#define ID3V2_HDR_SIZE GST_TAG_ID3V2_HEADER_SIZE -/* From id3tags.c */ -guint id3demux_calc_id3v2_tag_size (GstBuffer * buf); -ID3TagsResult id3demux_read_id3v2_tag (GstBuffer *buffer, guint *id3v2_size, - GstTagList **tags); - -guint read_synch_uint (const guint8 * data, guint size); +/* From id3v2.c */ +guint id3v2_read_synch_uint (const guint8 * data, guint size); /* Things shared by id3tags.c and id3v2frames.c */ #define ID3V2_VERSION 0x0400 @@ -112,10 +98,20 @@ enum { ID3V2_FRAME_FORMAT_COMPRESSION | \ ID3V2_FRAME_FORMAT_ENCRYPTION) +/* FIXME 0.11: remove 'private' bit from GST_TAG_ID3V2_FRAME */ +/** + * GST_TAG_ID3V2_FRAME: + * + * Contains a single unprocessed ID3v2 frame. (buffer) + * + * (Not public API for now) + */ +#define GST_TAG_ID3V2_FRAME "private-id3v2-frame" + /* From id3v2frames.c */ -gboolean id3demux_id3v2_parse_frame (ID3TagsWorking *work); +gboolean id3v2_parse_frame (ID3TagsWorking *work); -guint8 * id3demux_ununsync_data (const guint8 * unsync_data, guint32 * size); +guint8 * id3v2_ununsync_data (const guint8 * unsync_data, guint32 * size); G_END_DECLS diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index e51bbb7..f6b997c 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -33,10 +33,7 @@ #include #endif -#include "id3tags.h" - -GST_DEBUG_CATEGORY_EXTERN (id3demux_debug); -#define GST_CAT_DEFAULT (id3demux_debug) +#include "id3v2.h" static gboolean parse_comment_frame (ID3TagsWorking * work); static gchar *parse_url_link_frame (ID3TagsWorking * work, @@ -65,7 +62,7 @@ static gboolean parse_picture_frame (ID3TagsWorking * work); #define ID3V2_ENCODING_UTF8 0x03 gboolean -id3demux_id3v2_parse_frame (ID3TagsWorking * work) +id3v2_parse_frame (ID3TagsWorking * work) { const gchar *tag_name; gboolean result = FALSE; @@ -112,7 +109,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) if (ID3V2_VER_MAJOR (work->hdr.version) == 3) { work->parse_size = GST_READ_UINT32_BE (frame_data); } else { - work->parse_size = read_synch_uint (frame_data, 4); + work->parse_size = id3v2_read_synch_uint (frame_data, 4); } frame_data += 4; frame_data_size -= 4; @@ -132,7 +129,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) if ((work->hdr.flags & ID3V2_HDR_FLAG_UNSYNC) != 0 || ((work->frame_flags & ID3V2_FRAME_FORMAT_UNSYNCHRONISATION) != 0)) { GST_DEBUG ("Un-unsyncing frame %s", work->frame_id); - uu_data = id3demux_ununsync_data (frame_data, &frame_data_size); + uu_data = id3v2_ununsync_data (frame_data, &frame_data_size); frame_data = uu_data; GST_MEMDUMP ("ID3v2 frame (un-unsyced)", frame_data, frame_data_size); } @@ -165,8 +162,8 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) } work->parse_data = uncompressed_data; #else - GST_WARNING ("Compressed ID3v2 tag frame could not be decompressed" - " because gstid3demux was compiled without zlib support"); + GST_WARNING ("Compressed ID3v2 tag frame could not be decompressed, because" + " libgsttag-" GST_MAJORMINOR " was compiled without zlib support"); g_free (uu_data); return FALSE; #endif diff --git a/gst-libs/gst/tag/tag.h b/gst-libs/gst/tag/tag.h index 08ff942..ba8f878 100644 --- a/gst-libs/gst/tag/tag.h +++ b/gst-libs/gst/tag/tag.h @@ -442,6 +442,14 @@ typedef enum { #define GST_TYPE_TAG_IMAGE_TYPE (gst_tag_image_type_get_type ()) GType gst_tag_image_type_get_type (void); +/** + * GST_TAG_ID3V2_HEADER_SIZE: + * + * ID3V2 header size considered minimum input for some functions. + * + * Since: 0.10.36 + */ +#define GST_TAG_ID3V2_HEADER_SIZE 10 /* functions for vorbis comment manipulation */ @@ -466,6 +474,10 @@ GstBuffer * gst_tag_list_to_vorbiscomment_buffer (const GstTagLis /* functions for ID3 tag manipulation */ +/* FIXME 0.11: inconsistent API naming: gst_tag_list_new_from_id3v1(), gst_tag_list_from_*_buffer(), + * gst_tag_list_from_id3v2_tag(). Also, note gst.tag.list_xyz() namespace vs. gst.tag_list_xyz(), + * which is a bit confusing and possibly doesn't map too well */ + guint gst_tag_id3_genre_count (void); const gchar * gst_tag_id3_genre_get (const guint id); GstTagList * gst_tag_list_new_from_id3v1 (const guint8 * data); @@ -480,6 +492,10 @@ gboolean gst_tag_list_add_id3_image (GstTagList * tag_list, guint image_data_len, guint id3_picture_type); +GstTagList * gst_tag_list_from_id3v2_tag (GstBuffer * buffer); + +guint gst_tag_get_id3v2_tag_size (GstBuffer * buffer); + /* functions to convert GstBuffers with xmp packets contents to GstTagLists and back */ GstTagList * gst_tag_list_from_xmp_buffer (const GstBuffer * buffer); GstBuffer * gst_tag_list_to_xmp_buffer (const GstTagList * list, diff --git a/gst-libs/gst/tag/tags.c b/gst-libs/gst/tag/tags.c index 51f3bc7..f89b348 100644 --- a/gst-libs/gst/tag/tags.c +++ b/gst-libs/gst/tag/tags.c @@ -26,6 +26,7 @@ #include #include #include "tag.h" +#include "id3v2.h" #include @@ -189,6 +190,10 @@ gst_tag_register_tags_internal (gpointer unused) G_TYPE_DOUBLE, _("image vertical ppi"), _("Media (image/video) intended vertical pixel density in ppi"), NULL); + gst_tag_register (GST_TAG_ID3V2_FRAME, GST_TAG_FLAG_META, + GST_TYPE_BUFFER, _("ID3v2 frame"), _("unparsed id3v2 tag frame"), + gst_tag_merge_use_first); + return NULL; } diff --git a/win32/common/libgsttag.def b/win32/common/libgsttag.def index f8572d8..8fa522c 100644 --- a/win32/common/libgsttag.def +++ b/win32/common/libgsttag.def @@ -5,6 +5,7 @@ EXPORTS gst_tag_from_id3_tag gst_tag_from_id3_user_tag gst_tag_from_vorbis_tag + gst_tag_get_id3v2_tag_size gst_tag_get_language_code_iso_639_1 gst_tag_get_language_code_iso_639_2B gst_tag_get_language_code_iso_639_2T @@ -25,6 +26,7 @@ EXPORTS gst_tag_list_add_id3_image gst_tag_list_from_exif_buffer gst_tag_list_from_exif_buffer_with_tiff_header + gst_tag_list_from_id3v2_tag gst_tag_list_from_vorbiscomment_buffer gst_tag_list_from_xmp_buffer gst_tag_list_new_from_id3v1 -- 2.7.4