1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */
2 /* Copyright 2005 Jan Schmidt <thaytan@mad.scientist.com>
3 * Copyright 2002,2003 Scott Wheeler <wheeler@kde.org> (portions from taglib)
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
26 #include <gst/tag/tag.h>
30 #define HANDLE_INVALID_SYNCSAFE
32 static gboolean id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size);
34 #ifndef GST_DISABLE_GST_DEBUG
36 #define GST_CAT_DEFAULT id3v2_ensure_debug_category()
39 id3v2_ensure_debug_category (void)
41 static gsize cat_gonce = 0;
43 if (g_once_init_enter (&cat_gonce)) {
46 cat = (gsize) _gst_debug_category_new ("id3v2", 0, "ID3v2 tag parsing");
48 g_once_init_leave (&cat_gonce, cat);
51 return (GstDebugCategory *) cat_gonce;
54 #endif /* GST_DISABLE_GST_DEBUG */
57 id3v2_read_synch_uint (const guint8 * data, guint size)
66 for (i = 0; i <= size; i++) {
67 invalid |= data[i] & 0x80;
68 result |= (data[i] & 0x7f) << ((size - i) * 7);
71 #ifdef HANDLE_INVALID_SYNCSAFE
73 GST_WARNING ("Invalid synch-safe integer in ID3v2 frame "
74 "- using the actual value instead");
76 for (i = 0; i <= size; i++) {
77 result |= data[i] << ((size - i) * 8);
85 * gst_tag_get_id3v2_tag_size:
86 * @buffer: buffer holding ID3v2 tag (or at least the start of one)
88 * Determines size of an ID3v2 tag on buffer containing at least ID3v2 header,
89 * i.e. at least #GST_TAG_ID3V2_HEADER_SIZE (10) bytes;
91 * Returns: Size of tag, or 0 if header is invalid or too small.
94 gst_tag_get_id3v2_tag_size (GstBuffer * buffer)
100 g_return_val_if_fail (buffer != NULL, 0);
102 gst_buffer_map (buffer, &info, GST_MAP_READ);
104 if (info.size < ID3V2_HDR_SIZE)
107 /* Check for 'ID3' string at start of buffer */
108 if (info.data[0] != 'I' || info.data[1] != 'D' || info.data[2] != '3')
112 flags = info.data[5];
114 /* Read the size from the header */
115 result = id3v2_read_synch_uint (info.data + 6, 4);
119 result += ID3V2_HDR_SIZE;
121 /* Expand the read size to include a footer if there is one */
122 if ((flags & ID3V2_HDR_FLAG_FOOTER))
125 GST_DEBUG ("ID3v2 tag, size: %u bytes", result);
128 gst_buffer_unmap (buffer, &info);
134 GST_DEBUG ("size too small");
139 GST_DEBUG ("No ID3v2 tag in data");
144 GST_DEBUG ("Empty tag size");
145 result = ID3V2_HDR_SIZE;
151 id3v2_ununsync_data (const guint8 * unsync_data, guint32 * size)
157 uu = out = g_malloc (*size);
159 for (end = unsync_data + *size; unsync_data < end - 1; ++unsync_data, ++uu) {
161 if (G_UNLIKELY (*unsync_data == 0xff && *(unsync_data + 1) == 0x00))
165 /* take care of last byte (if last two bytes weren't 0xff 0x00) */
166 if (unsync_data < end) {
172 GST_DEBUG ("size after un-unsyncing: %u (before: %u)", out_size, *size);
179 * gst_tag_list_from_id3v2_tag:
180 * @buffer: buffer to convert
182 * Creates a new tag list that contains the information parsed out of a
185 * Returns: A new #GstTagList with all tags that could be extracted from the
186 * given vorbiscomment buffer or NULL on error.
189 gst_tag_list_from_id3v2_tag (GstBuffer * buffer)
192 guint8 *uu_data = NULL;
198 read_size = gst_tag_get_id3v2_tag_size (buffer);
200 /* Ignore tag if it has no frames attached, but skip the header then */
201 if (read_size < ID3V2_HDR_SIZE)
204 gst_buffer_map (buffer, &info, GST_MAP_READ);
206 /* Read the version */
207 version = GST_READ_UINT16_BE (info.data + 3);
210 flags = info.data[5];
212 /* Validate the version. At the moment, we only support up to 2.4.0 */
213 if (ID3V2_VER_MAJOR (version) > 4 || ID3V2_VER_MINOR (version) > 0)
216 GST_DEBUG ("ID3v2 header flags: %s %s %s %s",
217 (flags & ID3V2_HDR_FLAG_UNSYNC) ? "UNSYNC" : "",
218 (flags & ID3V2_HDR_FLAG_EXTHDR) ? "EXTENDED_HEADER" : "",
219 (flags & ID3V2_HDR_FLAG_EXPERIMENTAL) ? "EXPERIMENTAL" : "",
220 (flags & ID3V2_HDR_FLAG_FOOTER) ? "FOOTER" : "");
222 /* This shouldn't really happen! Caller should have checked first */
223 if (info.size < read_size)
224 goto not_enough_data;
226 GST_DEBUG ("Reading ID3v2 tag with revision 2.%d.%d of size %u", version >> 8,
227 version & 0xff, read_size);
229 GST_MEMDUMP ("ID3v2 tag", info.data, read_size);
231 memset (&work, 0, sizeof (ID3TagsWorking));
232 work.buffer = buffer;
233 work.hdr.version = version;
234 work.hdr.size = read_size;
235 work.hdr.flags = flags;
236 work.hdr.frame_data = info.data + ID3V2_HDR_SIZE;
237 if (flags & ID3V2_HDR_FLAG_FOOTER)
238 work.hdr.frame_data_size = read_size - ID3V2_HDR_SIZE - 10;
240 work.hdr.frame_data_size = read_size - ID3V2_HDR_SIZE;
242 /* in v2.3 the frame sizes are not syncsafe, so the entire tag had to be
243 * unsynced. In v2.4 the frame sizes are syncsafe so it's just the frame
244 * data that needs un-unsyncing, but not the frame headers. */
245 if ((flags & ID3V2_HDR_FLAG_UNSYNC) != 0 && ID3V2_VER_MAJOR (version) <= 3) {
246 GST_DEBUG ("Un-unsyncing entire tag");
247 uu_data = id3v2_ununsync_data (work.hdr.frame_data,
248 &work.hdr.frame_data_size);
249 work.hdr.frame_data = uu_data;
250 GST_MEMDUMP ("ID3v2 tag (un-unsyced)", uu_data, work.hdr.frame_data_size);
253 id3v2_frames_to_tag_list (&work, work.hdr.frame_data_size);
257 gst_buffer_unmap (buffer, &info);
264 GST_WARNING ("ID3v2 tag is from revision 2.%d.%d, "
265 "but decoder only supports 2.%d.%d. Ignoring as per spec.",
266 version >> 8, version & 0xff, ID3V2_VERSION >> 8, ID3V2_VERSION & 0xff);
267 gst_buffer_unmap (buffer, &info);
273 ("Found ID3v2 tag with revision 2.%d.%d - need %u more bytes to read",
274 version >> 8, version & 0xff, (guint) (read_size - info.size));
275 gst_buffer_unmap (buffer, &info);
281 id3v2_frame_hdr_size (guint id3v2ver)
283 /* ID3v2 < 2.3.0 only had 6 byte header */
284 switch (ID3V2_VER_MAJOR (id3v2ver)) {
296 static const gchar obsolete_frame_ids[][5] = {
297 {"CRM"}, {"EQU"}, {"LNK"}, {"RVA"}, {"TIM"}, {"TSI"}, /* From 2.2 */
298 {"EQUA"}, {"RVAD"}, {"TIME"}, {"TRDA"}, {"TSIZ"} /* From 2.3 */
301 static const struct ID3v2FrameIDConvert
305 } frame_id_conversions[] = {
331 "TDA", "TDAT"}, { /* obsolete, but we need to parse it anyway */
372 convert_fid_to_v240 (gchar * frame_id)
376 for (i = 0; i < G_N_ELEMENTS (obsolete_frame_ids); ++i) {
377 if (strncmp (frame_id, obsolete_frame_ids[i], 5) == 0)
381 for (i = 0; i < G_N_ELEMENTS (frame_id_conversions); ++i) {
382 if (strncmp (frame_id, frame_id_conversions[i].orig, 5) == 0) {
383 strcpy (frame_id, frame_id_conversions[i].new);
391 /* add unknown or unhandled ID3v2 frames to the taglist as binary blobs */
393 id3v2_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size)
401 guint frame_size, header_size;
404 switch (ID3V2_VER_MAJOR (work->hdr.version)) {
411 header_size = 4 + 4 + 2;
414 g_return_if_reached ();
417 frame_data = work->hdr.frame_data - header_size;
418 frame_size = size + header_size;
420 blob = gst_buffer_new_and_alloc (frame_size);
421 gst_buffer_fill (blob, 0, frame_data, frame_size);
423 /* Sanitize frame id */
424 for (i = 0; i < 4; i++) {
425 if (!g_ascii_isalnum (frame_data[i]))
430 media_type = g_strdup_printf ("application/x-gst-id3v2-%c%c%c%c-frame",
431 g_ascii_tolower (frame_data[0]), g_ascii_tolower (frame_data[1]),
432 g_ascii_tolower (frame_data[2]), g_ascii_tolower (frame_data[3]));
433 caps = gst_caps_new_simple (media_type, "version", G_TYPE_INT,
434 (gint) ID3V2_VER_MAJOR (work->hdr.version), NULL);
435 gst_buffer_set_caps (blob, caps);
436 gst_caps_unref (caps);
440 /* gst_util_dump_mem (GST_BUFFER_DATA (blob), GST_BUFFER_SIZE (blob)); */
442 gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND,
443 GST_TAG_ID3V2_FRAME, blob, NULL);
444 gst_buffer_unref (blob);
448 id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size)
450 guint frame_hdr_size;
452 /* Extended header if present */
453 if (work->hdr.flags & ID3V2_HDR_FLAG_EXTHDR) {
454 work->hdr.ext_hdr_size = id3v2_read_synch_uint (work->hdr.frame_data, 4);
455 if (work->hdr.ext_hdr_size < 6 ||
456 (work->hdr.ext_hdr_size) > work->hdr.frame_data_size) {
457 GST_DEBUG ("Invalid extended header. Broken tag");
460 work->hdr.ext_flag_bytes = work->hdr.frame_data[4];
461 if (5 + work->hdr.ext_flag_bytes > work->hdr.frame_data_size) {
463 ("Tag claims extended header, but doesn't have enough bytes. Broken tag");
467 work->hdr.ext_flag_data = work->hdr.frame_data + 5;
468 work->hdr.frame_data += work->hdr.ext_hdr_size;
469 work->hdr.frame_data_size -= work->hdr.ext_hdr_size;
472 frame_hdr_size = id3v2_frame_hdr_size (work->hdr.version);
473 if (work->hdr.frame_data_size <= frame_hdr_size) {
474 GST_DEBUG ("Tag has no data frames. Broken tag");
475 return FALSE; /* Must have at least one frame */
478 work->tags = gst_tag_list_new_empty ();
480 while (work->hdr.frame_data_size > frame_hdr_size) {
481 guint frame_size = 0;
482 gchar frame_id[5] = "";
483 guint16 frame_flags = 0x0;
484 gboolean obsolete_id = FALSE;
485 gboolean read_synch_size = TRUE;
488 /* Read the header */
489 switch (ID3V2_VER_MAJOR (work->hdr.version)) {
493 frame_id[0] = work->hdr.frame_data[0];
494 frame_id[1] = work->hdr.frame_data[1];
495 frame_id[2] = work->hdr.frame_data[2];
498 obsolete_id = convert_fid_to_v240 (frame_id);
500 /* 3 byte non-synchsafe size */
501 frame_size = work->hdr.frame_data[3] << 16 |
502 work->hdr.frame_data[4] << 8 | work->hdr.frame_data[5];
506 read_synch_size = FALSE; /* 2.3 frame size is not synch-safe */
509 frame_id[0] = work->hdr.frame_data[0];
510 frame_id[1] = work->hdr.frame_data[1];
511 frame_id[2] = work->hdr.frame_data[2];
512 frame_id[3] = work->hdr.frame_data[3];
515 frame_size = id3v2_read_synch_uint (work->hdr.frame_data + 4, 4);
517 frame_size = GST_READ_UINT32_BE (work->hdr.frame_data + 4);
519 frame_flags = GST_READ_UINT16_BE (work->hdr.frame_data + 8);
521 if (ID3V2_VER_MAJOR (work->hdr.version) == 3) {
522 frame_flags &= ID3V2_3_FRAME_FLAGS_MASK;
523 obsolete_id = convert_fid_to_v240 (frame_id);
525 GST_DEBUG ("Ignoring v2.3 frame %s", frame_id);
530 work->hdr.frame_data += frame_hdr_size;
531 work->hdr.frame_data_size -= frame_hdr_size;
533 if (frame_size > work->hdr.frame_data_size || strcmp (frame_id, "") == 0)
534 break; /* No more frames to read */
536 /* Sanitize frame id */
537 switch (ID3V2_VER_MAJOR (work->hdr.version)) {
541 for (i = 0; i < 3; i++) {
542 if (!g_ascii_isalnum (frame_id[i]))
547 for (i = 0; i < 4; i++) {
548 if (!g_ascii_isalnum (frame_id[i]))
555 ("Frame @ %ld (0x%02lx) id %s size %u, next=%ld (0x%02lx) obsolete=%d",
556 (glong) (work->hdr.frame_data - start),
557 (glong) (work->hdr.frame_data - start), frame_id, frame_size,
558 (glong) (work->hdr.frame_data + frame_hdr_size + frame_size - start),
559 (glong) (work->hdr.frame_data + frame_hdr_size + frame_size - start),
562 #define flag_string(flag,str) \
563 ((frame_flags & (flag)) ? (str) : "")
564 GST_LOG ("Frame header flags: 0x%04x %s %s %s %s %s %s %s", frame_flags,
565 flag_string (ID3V2_FRAME_STATUS_FRAME_ALTER_PRESERVE, "ALTER_PRESERVE"),
566 flag_string (ID3V2_FRAME_STATUS_READONLY, "READONLY"),
567 flag_string (ID3V2_FRAME_FORMAT_GROUPING_ID, "GROUPING_ID"),
568 flag_string (ID3V2_FRAME_FORMAT_COMPRESSION, "COMPRESSION"),
569 flag_string (ID3V2_FRAME_FORMAT_ENCRYPTION, "ENCRYPTION"),
570 flag_string (ID3V2_FRAME_FORMAT_UNSYNCHRONISATION, "UNSYNC"),
571 flag_string (ID3V2_FRAME_FORMAT_DATA_LENGTH_INDICATOR, "LENGTH_IND"));
576 /* Now, read, decompress etc the contents of the frame
577 * into a TagList entry */
578 work->cur_frame_size = frame_size;
579 work->frame_id = frame_id;
580 work->frame_flags = frame_flags;
582 if (id3v2_parse_frame (work)) {
583 GST_LOG ("Extracted frame with id %s", frame_id);
585 GST_LOG ("Failed to extract frame with id %s", frame_id);
586 id3v2_add_id3v2_frame_blob_to_taglist (work, frame_size);
589 work->hdr.frame_data += frame_size;
590 work->hdr.frame_data_size -= frame_size;
593 if (gst_tag_list_n_tags (work->tags) == 0) {
594 GST_DEBUG ("Could not extract any frames from tag. Broken or empty tag");
595 gst_tag_list_unref (work->tags);
600 /* Set day/month now if they were in a separate (obsolete) TDAT frame */
601 /* FIXME: we could extract the time as well now */
602 if (work->pending_day != 0 && work->pending_month != 0) {
603 GstDateTime *dt = NULL;
605 if (gst_tag_list_get_date_time (work->tags, GST_TAG_DATE_TIME, &dt)) {
608 /* GstDateTime is immutable, so create new one and replace old one */
609 dt2 = gst_date_time_new_ymd (gst_date_time_get_year (dt),
610 work->pending_month, work->pending_day);
611 gst_tag_list_add (work->tags, GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME,
613 gst_date_time_unref (dt2);
614 gst_date_time_unref (dt);