tag: id3v2: add id3v2 tag parsing helpers
authorMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Mon, 18 Jul 2011 16:09:53 +0000 (18:09 +0200)
committerTim-Philipp Müller <tim.muller@collabora.co.uk>
Sun, 14 Aug 2011 23:10:34 +0000 (00:10 +0100)
https://bugzilla.gnome.org/show_bug.cgi?id=654388

configure.ac
docs/libs/gst-plugins-base-libs-sections.txt
gst-libs/gst/tag/Makefile.am
gst-libs/gst/tag/id3v2.c
gst-libs/gst/tag/id3v2.h
gst-libs/gst/tag/id3v2frames.c
gst-libs/gst/tag/tag.h
gst-libs/gst/tag/tags.c
win32/common/libgsttag.def

index f14abe1..a76691f 100644 (file)
@@ -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
index 54d9722..19cd66f 100644 (file)
@@ -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
 </SECTION>
 
 <SECTION>
index 48137d6..e7dd562 100644 (file)
@@ -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
index d201d7c..5c99262 100644 (file)
 #include <string.h>
 #include <gst/tag/tag.h>
 
-#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;
 }
index 14a42de..d9ce504 100644 (file)
 
 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
 
index e51bbb7..f6b997c 100644 (file)
 #include <zlib.h>
 #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
index 08ff942..ba8f878 100644 (file)
@@ -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,
index 51f3bc7..f89b348 100644 (file)
@@ -26,6 +26,7 @@
 #include <gst/base/gsttypefindhelper.h>
 #include <gst/gst.h>
 #include "tag.h"
+#include "id3v2.h"
 
 #include <string.h>
 
@@ -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;
 }
 
index f8572d8..8fa522c 100644 (file)
@@ -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