tag: id3v2: add id3v2 tag parsing helpers

https://bugzilla.gnome.org/show_bug.cgi?id=654388
This commit is contained in:
Mark Nauwelaerts 2011-07-18 18:09:53 +02:00 committed by Tim-Philipp Müller
parent 4f042aeb1a
commit 08e6b5c54e
9 changed files with 122 additions and 82 deletions

View 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

View 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>

View 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

View file

@ -25,17 +25,14 @@
#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;
}

View file

@ -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
#define ID3V2_HDR_SIZE GST_TAG_ID3V2_HEADER_SIZE
typedef enum {
ID3TAGS_MORE_DATA,
ID3TAGS_READ_TAG,
ID3TAGS_BROKEN_TAG
} ID3TagsResult;
/* 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)
/* From id3v2frames.c */
gboolean id3demux_id3v2_parse_frame (ID3TagsWorking *work);
/* 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"
guint8 * id3demux_ununsync_data (const guint8 * unsync_data, guint32 * size);
/* From id3v2frames.c */
gboolean id3v2_parse_frame (ID3TagsWorking *work);
guint8 * id3v2_ununsync_data (const guint8 * unsync_data, guint32 * size);
G_END_DECLS

View file

@ -33,10 +33,7 @@
#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

View 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,

View 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;
}

View 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