tag: id3v2: all new LGPL id3 demuxer, can use zlib for compressed frames

Original commit message from CVS:
* configure.ac:
Check for optional dependency on zlib for id3demux

* gst-libs/gst/tag/Makefile.am:
* gst-libs/gst/tag/gstid3demux.c: (gst_gst_id3demux_get_type),
(gst_id3demux_base_init), (gst_id3demux_class_init),
(gst_id3demux_reset), (gst_id3demux_init), (gst_id3demux_dispose),
(gst_id3demux_add_srcpad), (gst_id3demux_remove_srcpad),
(gst_id3demux_trim_buffer), (gst_id3demux_chain),
(gst_id3demux_set_property), (gst_id3demux_get_property),
(id3demux_get_upstream_size), (gst_id3demux_srcpad_event),
(gst_id3demux_read_id3v1), (gst_id3demux_read_id3v2),
(gst_id3demux_sink_activate), (gst_id3demux_src_activate_pull),
(gst_id3demux_src_checkgetrange), (gst_id3demux_read_range),
(gst_id3demux_src_getrange), (gst_id3demux_change_state),
(gst_id3demux_pad_query), (gst_id3demux_get_query_types),
(simple_find_peek), (simple_find_suggest),
(gst_id3demux_do_typefind), (gst_id3demux_send_tag_event),
(plugin_init):
* gst-libs/gst/tag/gstid3demux.h:
* gst-libs/gst/tag/id3v2.c: (read_synch_uint),
(id3demux_read_id3v1_tag), (id3demux_read_id3v2_tag),
(id3demux_id3v2_frame_hdr_size), (convert_fid_to_v240),
(id3demux_id3v2_frames_to_tag_list):
* gst-libs/gst/tag/id3v2.h:
* gst-libs/gst/tag/id3v2.4.0-frames.txt:
* gst-libs/gst/tag/id3v2.4.0-structure.txt:
* gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame),
(parse_comment_frame), (parse_text_identification_frame),
(id3v2_tag_to_taglist), (parse_split_strings):
All new LGPL id3 demuxer. Can use zlib for compressed frames,
otherwise it discards them. Works on my test files.

* gst/wavparse/gstwavparse.c: (gst_wavparse_loop):
Don't send EOS to a non-existing srcpad
The debug category can be static
This commit is contained in:
Jan Schmidt 2005-12-18 15:14:44 +00:00 committed by Tim-Philipp Müller
parent 49ec6899f4
commit 83bffb642c
3 changed files with 925 additions and 0 deletions

431
gst-libs/gst/tag/id3v2.c Normal file
View file

@ -0,0 +1,431 @@
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */
/* Copyright 2005 Jan Schmidt <thaytan@mad.scientist.com>
* Copyright 2002,2003 Scott Wheeler <wheeler@kde.org> (portions from taglib)
*
* 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 "config.h"
#endif
#include <string.h>
#include <gst/tag/tag.h>
#include "id3tags.h"
GST_DEBUG_CATEGORY_EXTERN (id3demux_debug);
#define GST_CAT_DEFAULT (id3demux_debug)
#define HANDLE_INVALID_SYNCSAFE
static ID3TagsResult
id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size);
guint
read_synch_uint (guint8 * data, guint size)
{
gint i;
guint result = 0;
gint invalid = 0;
g_assert (size <= 4);
size--;
for (i = 0; i <= size; i++) {
invalid |= data[i] & 0x80;
result |= (data[i] & 0x7f) << ((size - i) * 7);
}
#ifdef HANDLE_INVALID_SYNCSAFE
if (invalid) {
GST_WARNING ("Invalid synch-safe integer in ID3v2 frame "
"- using the actual value instead");
result = 0;
for (i = 0; i <= size; i++) {
result |= data[i] << ((size - i) * 8);
}
}
#endif
return result;
}
ID3TagsResult
id3demux_read_id3v1_tag (GstBuffer * buffer, guint * id3v1_size,
GstTagList ** tags)
{
GstTagList *new_tags;
guint8 *data;
g_return_val_if_fail (buffer != NULL, ID3TAGS_V1_BAD_SIZE);
data = GST_BUFFER_DATA (buffer);
if (GST_BUFFER_SIZE (buffer) != ID3V1_TAG_SIZE)
return ID3TAGS_V1_BAD_SIZE;
/* Check that buffer starts with 'TAG' */
if (data[0] != 'T' || data[1] != 'A' || data[2] != 'G') {
if (id3v1_size)
*id3v1_size = 0;
GST_DEBUG ("No ID3v1 tag in data");
return ID3TAGS_READ_TAG;
}
g_return_val_if_fail (tags != NULL, ID3TAGS_READ_TAG);
new_tags = gst_tag_list_new_from_id3v1 (GST_BUFFER_DATA (buffer));
if (new_tags == NULL)
return ID3TAGS_BROKEN_TAG;
if (*tags) {
GstTagList *merged;
merged = gst_tag_list_merge (*tags, new_tags, GST_TAG_MERGE_REPLACE);
gst_tag_list_free (*tags);
gst_tag_list_free (new_tags);
*tags = merged;
} else
*tags = new_tags;
return ID3TAGS_READ_TAG;
}
ID3TagsResult
id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size,
GstTagList ** tags)
{
guint8 *data;
guint read_size;
ID3TagsWorking work;
guint8 flags;
ID3TagsResult result;
guint16 version;
g_return_val_if_fail (buffer != NULL, ID3TAGS_MORE_DATA);
if (GST_BUFFER_SIZE (buffer) < ID3V2_MARK_SIZE)
return ID3TAGS_MORE_DATA; /* Need more data to decide with */
data = GST_BUFFER_DATA (buffer);
/* Check for 'ID3' string at start of buffer */
if (data[0] != 'I' || data[1] != 'D' || data[2] != '3') {
if (id3v2_size)
*id3v2_size = 0;
GST_DEBUG ("No ID3v2 tag in data");
return ID3TAGS_READ_TAG;
}
/* OK, get enough data to read the entire header */
if (GST_BUFFER_SIZE (buffer) < ID3V2_HDR_SIZE)
return ID3TAGS_MORE_DATA; /* Need more data to decide with */
/* Read the version */
version = GST_READ_UINT16_BE (data + 3);
/* Read the flags */
flags = data[5];
/* Read the size from the header */
read_size = read_synch_uint (data + 6, 4);
if (read_size == 0) {
return ID3TAGS_BROKEN_TAG;
}
read_size += 10;
/* Expand the read size to include a footer if there is one */
if (flags & ID3V2_HDR_FLAG_FOOTER) {
read_size += 10;
}
if (id3v2_size)
*id3v2_size = read_size;
/* Validate the version. At the moment, we only support up to 2.4.0 */
if (ID3V2_VER_MAJOR (version) > 4 || ID3V2_VER_MINOR (version) > 0) {
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_READ_TAG;
}
GST_DEBUG ("ID3v2 tag with revision 2.%d.%d\n", version >> 8, version & 0xff);
if (GST_BUFFER_SIZE (buffer) < read_size)
return ID3TAGS_MORE_DATA; /* Need more data to decode with */
g_return_val_if_fail (tags != NULL, ID3TAGS_READ_TAG);
memset (&work, 0, sizeof (ID3TagsWorking));
work.buffer = buffer;
work.hdr.version = version;
work.hdr.size = read_size;
work.hdr.flags = flags;
work.hdr.frame_data = GST_BUFFER_DATA (buffer) + ID3V2_HDR_SIZE;
if (flags & ID3V2_HDR_FLAG_FOOTER)
work.hdr.frame_data_size = read_size - ID3V2_HDR_SIZE - 10;
else
work.hdr.frame_data_size = read_size - ID3V2_HDR_SIZE;
result = id3demux_id3v2_frames_to_tag_list (&work, read_size);
/* Actually read the tags */
if (work.tags != NULL) {
if (*tags) {
GstTagList *merged;
merged = gst_tag_list_merge (*tags, work.tags, GST_TAG_MERGE_REPLACE);
gst_tag_list_free (*tags);
gst_tag_list_free (work.tags);
*tags = merged;
} else
*tags = work.tags;
}
return result;
}
static guint
id3demux_id3v2_frame_hdr_size (guint id3v2ver)
{
/* ID3v2 < 2.3.0 only had 6 byte header */
switch (ID3V2_VER_MAJOR (id3v2ver)) {
case 0:
case 1:
case 2:
return 6;
case 3:
case 4:
default:
return 10;
}
}
static const gchar *obsolete_frame_ids[] = {
"CRM", "EQU", "LNK", "RVA", "TIM", "TSI", /* From 2.2 */
"EQUA", "RVAD", "TIME", "TRDA", "TSIZ", /* From 2.3 */
NULL
};
const struct ID3v2FrameIDConvert
{
gchar *orig;
gchar *new;
} frame_id_conversions[] = {
/* 2.3.x frames */
{
"TDAT", "TDRC"}, {
"TORY", "TDOR"}, {
"TYER", "TDRC"},
/* 2.2.x frames */
{
"BUF", "RBUF"}, {
"CNT", "PCNT"}, {
"COM", "COMM"}, {
"CRA", "AENC"}, {
"ETC", "ETCO"}, {
"GEO", "GEOB"}, {
"IPL", "TIPL"}, {
"MCI", "MCDI"}, {
"MLL", "MLLT"}, {
"PIC", "APIC"}, {
"POP", "POPM"}, {
"REV", "RVRB"}, {
"SLT", "SYLT"}, {
"STC", "SYTC"}, {
"TAL", "TALB"}, {
"TBP", "TBPM"}, {
"TCM", "TCOM"}, {
"TCR", "TCOP"}, {
"TDA", "TDRC"}, {
"TDY", "TDLY"}, {
"TEN", "TENC"}, {
"TFT", "TFLT"}, {
"TKE", "TKEY"}, {
"TLA", "TLAN"}, {
"TLE", "TLEN"}, {
"TMT", "TMED"}, {
"TOA", "TOAL"}, {
"TOF", "TOFN"}, {
"TOL", "TOLY"}, {
"TOR", "TDOR"}, {
"TOT", "TOAL"}, {
"TP1", "TPE1"}, {
"TP2", "TPE2"}, {
"TP3", "TPE3"}, {
"TP4", "TPE4"}, {
"TPA", "TPOS"}, {
"TPB", "TPUB"}, {
"TRC", "TSRC"}, {
"TRD", "TDRC"}, {
"TRK", "TRCK"}, {
"TSS", "TSSE"}, {
"TT1", "TIT1"}, {
"TT2", "TIT2"}, {
"TT3", "TIT3"}, {
"TXT", "TOLY"}, {
"TXX", "TXXX"}, {
"TYE", "TDRC"}, {
"UFI", "UFID"}, {
"ULT", "USLT"}, {
"WAF", "WOAF"}, {
"WAR", "WOAR"}, {
"WAS", "WOAS"}, {
"WCM", "WCOM"}, {
"WCP", "WCOP"}, {
"WPB", "WPUB"}, {
"WXX", "WXXX"}, {
NULL, NULL}
};
static gboolean
convert_fid_to_v240 (gchar * frame_id)
{
gint i = 0;
while (obsolete_frame_ids[i] != NULL) {
if (strncmp (frame_id, obsolete_frame_ids[i], 5) == 0)
return TRUE;
i++;
}
i = 0;
while (frame_id_conversions[i].orig != NULL) {
if (strncmp (frame_id, frame_id_conversions[i].orig, 5) == 0) {
strcpy (frame_id, frame_id_conversions[i].new);
return FALSE;
}
i++;
}
return FALSE;
}
static ID3TagsResult
id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size)
{
guint frame_hdr_size;
gboolean read_a_frame = FALSE;
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);
if (work->hdr.ext_hdr_size < 6 ||
(work->hdr.ext_hdr_size) > work->hdr.frame_data_size) {
return ID3TAGS_BROKEN_TAG;
}
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;
}
work->hdr.ext_flag_data = work->hdr.frame_data + 5;
work->hdr.frame_data += work->hdr.ext_hdr_size;
work->hdr.frame_data_size -= work->hdr.ext_hdr_size;
}
start = GST_BUFFER_DATA (work->buffer);
frame_hdr_size = id3demux_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 */
}
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;
gchar frame_id[5] = "";
guint16 frame_flags = 0x0;
gboolean obsolete_id = FALSE;
/* Read the header */
switch (ID3V2_VER_MAJOR (work->hdr.version)) {
case 0:
case 1:
case 2:
frame_id[0] = work->hdr.frame_data[0];
frame_id[1] = work->hdr.frame_data[1];
frame_id[2] = work->hdr.frame_data[2];
frame_id[3] = 0;
frame_id[4] = 0;
obsolete_id = convert_fid_to_v240 (frame_id);
frame_size = read_synch_uint (work->hdr.frame_data + 3, 3);
frame_flags = 0;
break;
case 3:
case 4:
default:
frame_id[0] = work->hdr.frame_data[0];
frame_id[1] = work->hdr.frame_data[1];
frame_id[2] = work->hdr.frame_data[2];
frame_id[3] = work->hdr.frame_data[3];
frame_id[4] = 0;
frame_size = read_synch_uint (work->hdr.frame_data + 4, 4);
frame_flags = GST_READ_UINT16_BE (work->hdr.frame_data + 8);
if (ID3V2_VER_MAJOR (work->hdr.version) == 3) {
frame_flags &= ID3V2_3_FRAME_FLAGS_MASK;
obsolete_id = convert_fid_to_v240 (frame_id);
}
break;
}
work->hdr.frame_data += frame_hdr_size;
work->hdr.frame_data_size -= frame_hdr_size;
if (frame_size > work->hdr.frame_data_size ||
frame_size == 0 || strcmp (frame_id, "") == 0)
break; /* No more frames to read */
#if 0
g_print
("Frame @ %d (0x%02x) id %s size %d, next=%d (0x%02x) obsolete=%d\n",
work->hdr.frame_data - start, work->hdr.frame_data - start, frame_id,
frame_size, work->hdr.frame_data + frame_hdr_size + frame_size - start,
work->hdr.frame_data + frame_hdr_size + frame_size - start,
obsolete_id);
#endif
if (!obsolete_id) {
/* Now, read, decompress etc the contents of the frame
* into a TagList entry */
work->cur_frame_size = frame_size;
work->frame_id = frame_id;
work->frame_flags = frame_flags;
if (id3demux_id3v2_parse_frame (work)) {
read_a_frame = TRUE;
GST_LOG ("Extracted frame with id %s", frame_id);
}
}
work->hdr.frame_data += frame_size;
work->hdr.frame_data_size -= frame_size;
}
if (!read_a_frame) {
GST_DEBUG ("Could not extract any frames from tag. Broken tag");
gst_tag_list_free (work->tags);
work->tags = NULL;
return ID3TAGS_BROKEN_TAG;
}
return ID3TAGS_READ_TAG;
}

112
gst-libs/gst/tag/id3v2.h Normal file
View file

@ -0,0 +1,112 @@
/* Copyright 2005 Jan Schmidt <thaytan@mad.scientist.com>
*
* 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 __ID3TAGS_H__
#define __ID3TAGS_H__
#include <gst/gst.h>
G_BEGIN_DECLS
#define ID3V1_TAG_SIZE 128
#define ID3V2_MARK_SIZE 3
#define ID3V2_HDR_SIZE 10
typedef enum {
ID3TAGS_V1_BAD_SIZE,
ID3TAGS_MORE_DATA,
ID3TAGS_READ_TAG,
ID3TAGS_BROKEN_TAG
} ID3TagsResult;
/* From id3tags.c */
ID3TagsResult id3demux_read_id3v1_tag (GstBuffer *buffer, guint *id3v1_size,
GstTagList **tags);
ID3TagsResult id3demux_read_id3v2_tag (GstBuffer *buffer, guint *id3v2_size,
GstTagList **tags);
G_END_DECLS
/* Things shared by id3tags.c and id3v2frames.c */
#define ID3V2_VERSION 0x0400
#define ID3V2_VER_MAJOR(v) ((v) >> 8)
#define ID3V2_VER_MINOR(v) ((v) & 0xff)
typedef struct {
guint16 version;
guint8 flags;
guint32 size;
guint8 *frame_data;
guint32 frame_data_size;
guint32 ext_hdr_size;
guint8 ext_flag_bytes;
guint8 *ext_flag_data;
} ID3v2Header;
typedef struct {
ID3v2Header hdr;
GstBuffer *buffer;
GstTagList *tags;
/* Current frame decoding */
guint cur_frame_size;
gchar *frame_id;
guint16 frame_flags;
guint8 *parse_data;
guint parse_size;
} ID3TagsWorking;
enum {
ID3V2_HDR_FLAG_UNSYNC = 0x80,
ID3V2_HDR_FLAG_EXTHDR = 0x40,
ID3V2_HDR_FLAG_EXPERIMENTAL = 0x20,
ID3V2_HDR_FLAG_FOOTER = 0x10
};
enum {
ID3V2_EXT_FLAG_UPDATE = 0x80,
ID3V2_EXT_FLAG_CRC = 0x40,
ID3V2_EXT_FLAG_RESTRICTED = 0x20
};
enum {
ID3V2_FRAME_STATUS_FRAME_ALTER_PRESERVE = 0x4000,
ID3V2_FRAME_STATUS_FILE_ALTER_PRESERVE = 0x2000,
ID3V2_FRAME_STATUS_READONLY = 0x1000,
ID3V2_FRAME_FORMAT_GROUPING_ID = 0x0040,
ID3V2_FRAME_FORMAT_COMPRESSION = 0x0008,
ID3V2_FRAME_FORMAT_ENCRYPTION = 0x0004,
ID3V2_FRAME_FORMAT_UNSYNCHRONISATION = 0x0002,
ID3V2_FRAME_FORMAT_DATA_LENGTH_INDICATOR = 0x0001
};
#define ID3V2_3_FRAME_FLAGS_MASK \
(ID3V2_FRAME_STATUS_FRAME_ALTER_PRESERVE | \
ID3V2_FRAME_STATUS_FILE_ALTER_PRESERVE | \
ID3V2_FRAME_STATUS_READONLY | \
ID3V2_FRAME_FORMAT_GROUPING_ID | \
ID3V2_FRAME_FORMAT_COMPRESSION | \
ID3V2_FRAME_FORMAT_ENCRYPTION)
/* From id3v2frames.c */
gboolean id3demux_id3v2_parse_frame (ID3TagsWorking *work);
#endif

View file

@ -0,0 +1,382 @@
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */
/* Copyright 2005 Jan Schmidt <thaytan@mad.scientist.com>
* Copyright 2002,2003 Scott Wheeler <wheeler@kde.org> (portions from taglib)
*
* 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 "config.h"
#endif
#include <string.h>
#include <gst/tag/tag.h>
#ifdef HAVE_ZLIB
#include <zlib.h>
#endif
#include "id3tags.h"
GST_DEBUG_CATEGORY_EXTERN (id3demux_debug);
#define GST_CAT_DEFAULT (id3demux_debug)
static gchar *parse_comment_frame (ID3TagsWorking * work);
static gchar *parse_text_identification_frame (ID3TagsWorking * work);
static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work,
const gchar * tag_name, gchar * tag_str);
static void parse_split_strings (ID3TagsWorking * work, guint8 encoding,
gchar ** field1, gchar ** field2);
#define ID3V2_ENCODING_ISO8859 0x00
#define ID3V2_ENCODING_UTF16 0x01
#define ID3V2_ENCODING_UTF16BE 0x02
#define ID3V2_ENCODING_UTF8 0x03
extern guint read_synch_uint (guint8 * data, guint size);
gboolean
id3demux_id3v2_parse_frame (ID3TagsWorking * work)
{
const gchar *tag_name;
gboolean result = FALSE;
gint i;
guint8 *frame_data = work->hdr.frame_data;
guint frame_data_size = work->cur_frame_size;
gchar *tag_str = NULL;
/* Check that the frame id is valid */
for (i = 0; i < 5 && work->frame_id[i] != '\0'; i++) {
if (!g_ascii_isalnum (work->frame_id[i])) {
GST_DEBUG ("Encountered invalid frame_id");
return FALSE;
}
}
/* Can't handle encrypted frames right now */
if (work->frame_flags & ID3V2_FRAME_FORMAT_ENCRYPTION) {
GST_WARNING ("Encrypted frames are not supported");
return FALSE;
}
if (work->frame_flags & ID3V2_FRAME_FORMAT_UNSYNCHRONISATION) {
GST_WARNING ("ID3v2 frame with unsupported unsynchronisation applied. "
"May fail badly");
}
tag_name = gst_tag_from_id3_tag (work->frame_id);
if (tag_name == NULL)
return FALSE;
if (work->frame_flags & (ID3V2_FRAME_FORMAT_COMPRESSION |
ID3V2_FRAME_FORMAT_DATA_LENGTH_INDICATOR)) {
if (work->hdr.frame_data_size <= 4)
return FALSE;
work->parse_size = read_synch_uint (frame_data, 4);
frame_data += 4;
frame_data_size -= 4;
} else
work->parse_size = frame_data_size;
if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) {
uLongf destSize = work->parse_size;
Bytef *dest, *src;
work->parse_data = g_malloc (work->parse_size);
g_return_val_if_fail (work->parse_data != NULL, FALSE);
dest = (Bytef *) work->parse_data;
src = (Bytef *) frame_data;
if (uncompress (dest, &destSize, src, frame_data_size) != Z_OK) {
g_free (work->parse_data);
return FALSE;
}
} else {
work->parse_data = work->hdr.frame_data;
}
if (work->frame_id[0] == 'T') {
if (strcmp (work->frame_id, "TXXX") != 0) {
/* Text identification frame */
tag_str = parse_text_identification_frame (work);
} else {
/* Handle user text frame */
}
} else if (!strcmp (work->frame_id, "COMM")) {
/* Comment */
tag_str = parse_comment_frame (work);
} else if (!strcmp (work->frame_id, "APIC")) {
/* Attached picture */
} else if (!strcmp (work->frame_id, "RVA2")) {
/* Relative volume */
} else if (!strcmp (work->frame_id, "UFID")) {
/* Unique file identifier */
}
if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) {
g_free (work->parse_data);
}
if (tag_str != NULL) {
/* g_print ("Tag %s value %s\n", tag_name, tag_str); */
result = id3v2_tag_to_taglist (work, tag_name, tag_str);
g_free (tag_str);
}
return result;
}
static gchar *
parse_comment_frame (ID3TagsWorking * work)
{
guint8 encoding;
gchar language[4];
gchar *description = NULL;
gchar *text = NULL;
gchar *out_str = NULL;
if (work->parse_size < 6)
return NULL;
encoding = work->parse_data[0];
language[0] = work->parse_data[1];
language[1] = work->parse_data[2];
language[2] = work->parse_data[3];
language[3] = 0;
parse_split_strings (work, encoding, &description, &text);
if (text == NULL || description == NULL) {
GST_ERROR ("Failed to decode comment frame");
goto fail;
}
if (!g_utf8_validate (text, -1, NULL)) {
GST_ERROR ("Converted string is not valid utf-8");
goto fail;
} else {
if (strlen (description) > 0 && g_utf8_validate (description, -1, NULL)) {
out_str = g_strdup_printf ("Description: %s\nComment: %s",
description, text);
} else {
out_str = g_strdup (text);
}
}
fail:
g_free (description);
g_free (text);
return out_str;
}
static gchar *
parse_text_identification_frame (ID3TagsWorking * work)
{
guchar encoding;
gchar *text = NULL;
if (work->parse_size < 2)
return NULL;
encoding = work->parse_data[0];
switch (encoding) {
case ID3V2_ENCODING_ISO8859:
text = g_convert ((gchar *) (work->parse_data + 1),
work->parse_size - 1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
break;
case ID3V2_ENCODING_UTF8:
text = g_strndup ((gchar *) (work->parse_data + 1), work->parse_size - 1);
break;
case ID3V2_ENCODING_UTF16:
text = g_convert ((gchar *) (work->parse_data + 1),
work->parse_size - 1, "UTF-8", "UTF-16", NULL, NULL, NULL);
break;
case ID3V2_ENCODING_UTF16BE:
text = g_convert ((gchar *) (work->parse_data + 1),
work->parse_size - 1, "UTF-8", "UTF-16BE", NULL, NULL, NULL);
break;
}
if (text != NULL && !g_utf8_validate (text, -1, NULL)) {
GST_ERROR ("Converted string is not valid utf-8");
g_free (text);
text = NULL;
}
return text;
}
static gboolean
id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name,
gchar * tag_str)
{
GType tag_type = gst_tag_get_type (tag_name);
GstTagList *tag_list = work->tags;
switch (tag_type) {
case G_TYPE_UINT:
{
guint tmp;
gchar *check;
tmp = strtoul ((char *) tag_str, &check, 10);
if (strcmp (tag_name, GST_TAG_DATE) == 0) {
GDate *d;
if (*check != '\0')
break;
if (tmp == 0)
break;
d = g_date_new_dmy (1, 1, tmp);
tmp = g_date_get_julian (d);
g_date_free (d);
} else if (strcmp (tag_name, GST_TAG_TRACK_NUMBER) == 0) {
if (*check == '/') {
guint total;
check++;
total = strtoul (check, &check, 10);
if (*check != '\0')
break;
gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
GST_TAG_TRACK_COUNT, total, NULL);
}
} else if (strcmp (tag_name, GST_TAG_ALBUM_VOLUME_NUMBER) == 0) {
if (*check == '/') {
guint total;
check++;
total = strtoul (check, &check, 10);
if (*check != '\0')
break;
gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
GST_TAG_ALBUM_VOLUME_COUNT, total, NULL);
}
}
if (*check != '\0')
break;
gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, tag_name, tmp, NULL);
break;
}
case G_TYPE_UINT64:
{
guint64 tmp;
g_assert (strcmp (tag_name, GST_TAG_DURATION) == 0);
tmp = strtoul ((char *) tag_str, NULL, 10);
if (tmp == 0) {
break;
}
gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
GST_TAG_DURATION, tmp * 1000 * 1000, NULL);
break;
}
case G_TYPE_STRING:{
gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
tag_name, (const gchar *) tag_str, NULL);
break;
}
/* handles GST_TYPE_DATE and anything else */
default:{
GValue src = { 0, };
GValue dest = { 0, };
g_value_init (&src, G_TYPE_STRING);
g_value_set_string (&src, (const gchar *) tag_str);
g_value_init (&dest, tag_type);
if (g_value_transform (&src, &dest)) {
gst_tag_list_add_values (tag_list, GST_TAG_MERGE_APPEND,
tag_name, &dest, NULL);
} else {
GST_WARNING ("Failed to transform tag from string to type '%s'",
g_type_name (tag_type));
}
g_value_unset (&src);
g_value_unset (&dest);
break;
}
}
return TRUE;
}
static void
parse_split_strings (ID3TagsWorking * work, guint8 encoding,
gchar ** field1, gchar ** field2)
{
guint text_pos;
*field1 = *field2 = NULL;
switch (encoding) {
case ID3V2_ENCODING_ISO8859:
for (text_pos = 4; text_pos < work->parse_size - 1; text_pos++) {
if (work->parse_data[text_pos] == 0) {
*field1 = g_convert ((gchar *) (work->parse_data + 4),
text_pos - 4, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
*field2 = g_convert ((gchar *) (work->parse_data + text_pos + 5),
work->parse_size - text_pos - 5,
"UTF-8", "ISO-8859-1", NULL, NULL, NULL);
break;
}
}
break;
case ID3V2_ENCODING_UTF8:
*field1 = g_strndup ((gchar *) (work->parse_data + 4),
work->parse_size - 4);
text_pos = 4 + strlen (*field1) + 1; /* Offset by one more for the null */
if (text_pos < work->parse_size) {
*field2 = g_strndup ((gchar *) (work->parse_data + text_pos),
work->parse_size - text_pos);
}
break;
case ID3V2_ENCODING_UTF16:
case ID3V2_ENCODING_UTF16BE:
{
/* Find '\0\0' terminator */
for (text_pos = 4; text_pos < work->parse_size - 2; text_pos++) {
if (work->parse_data[text_pos] == 0 &&
work->parse_data[text_pos + 1] == 0) {
/* found our delimiter */
if (encoding == ID3V2_ENCODING_UTF16) {
*field1 = g_convert ((gchar *) (work->parse_data + 4),
text_pos - 4, "UTF-8", "UTF-16", NULL, NULL, NULL);
*field2 = g_convert ((gchar *) (work->parse_data + text_pos + 6),
work->parse_size - text_pos - 6,
"UTF-8", "UTF-16", NULL, NULL, NULL);
} else {
*field1 = g_convert ((gchar *) (work->parse_data + 4),
text_pos - 4, "UTF-8", "UTF-16BE", NULL, NULL, NULL);
*field2 = g_convert ((gchar *) (work->parse_data + text_pos + 6),
work->parse_size - text_pos - 6,
"UTF-8", "UTF-16BE", NULL, NULL, NULL);
}
break;
}
}
break;
}
}
}