Mercurial > audlegacy
diff src/libaudtag/id3/id3v2.c @ 4887:0ddbd0025174 default tip
added libaudtag. (not used yet.)
| author | Yoshiki Yazawa <yaz@honeyplanet.jp> |
|---|---|
| date | Wed, 05 May 2010 18:26:06 +0900 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/libaudtag/id3/id3v2.c Wed May 05 18:26:06 2010 +0900 @@ -0,0 +1,691 @@ +/* + * Copyright 2009 Paula Stanciu + * + * This file is part of Audacious. + * + * Audacious is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3 of the License. + * + * Audacious 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Audacious. If not, see <http://www.gnu.org/licenses/>. + * + * The Audacious team does not consider modular code linking to Audacious or + * using our public API to be a derived work. + */ + +#include <glib.h> +#include <glib/gstdio.h> + +#include "id3v2.h" +#include "../util.h" +#include <inttypes.h> +#include "../tag_module.h" +#include "frame.h" + +#define TAG_SIZE 1 + +guint32 read_syncsafe_int32(VFSFile * fd) +{ + guint32 val = read_BEuint32(fd); + guint32 mask = 0x7f; + guint32 intVal = 0; + intVal = ((intVal) | (val & mask)); + int i; + for (i = 0; i < 3; i++) + { + mask = mask << 8; + guint32 tmp = (val & mask); + tmp = tmp >> 1; + intVal = intVal | tmp; + }; + return intVal; +} + +ID3v2Header *readHeader(VFSFile * fd) +{ + ID3v2Header *header = g_new0(ID3v2Header, 1); + header->id3 = read_char_data(fd, 3); + header->version = read_LEuint16(fd); + header->flags = *read_char_data(fd, 1); + header->size = read_syncsafe_int32(fd); + return header; +} + +ExtendedHeader *readExtendedHeader(VFSFile * fd) +{ + ExtendedHeader *header = g_new0(ExtendedHeader, 1); + header->header_size = read_syncsafe_int32(fd); + header->flags = read_LEuint16(fd); + header->padding_size = read_BEuint32(fd); + return header; +} + +ID3v2FrameHeader *readID3v2FrameHeader(VFSFile * fd) +{ + ID3v2FrameHeader *frameheader = g_new0(ID3v2FrameHeader, 1); + frameheader->frame_id = read_char_data(fd, 4); + frameheader->size = read_syncsafe_int32(fd); + frameheader->flags = read_LEuint16(fd); + if ((frameheader->flags & 0x100) == 0x100) + frameheader->size = read_syncsafe_int32(fd); + return frameheader; +} + +static gint unsyncsafe (gchar * data, gint size) +{ + gchar * get = data, * set = data; + + while (size --) + { + gchar c = * set ++ = * get ++; + + if (c == (gchar) 0xff && size) + { + size --; + get ++; + } + } + + return set - data; +} + +static gchar * read_text_frame (VFSFile * handle, ID3v2FrameHeader * header) +{ + gint size = header->size; + gchar data[size]; + + if (vfs_fread (data, 1, size, handle) != size) + return NULL; + + if (header->flags & 0x200) + size = unsyncsafe (data, size); + + switch (data[0]) + { + case 0: + return g_convert (data + 1, size - 1, "UTF-8", "ISO-8859-1", NULL, NULL, + NULL); + case 1: + if (data[1] == (gchar) 0xff) + return g_convert (data + 3, size - 3, "UTF-8", "UTF-16LE", NULL, + NULL, NULL); + else + return g_convert (data + 3, size - 3, "UTF-8", "UTF-16BE", NULL, + NULL, NULL); + case 2: + return g_convert (data + 1, size - 1, "UTF-8", "UTF-16BE", NULL, NULL, + NULL); + case 3: + return g_strndup (data + 1, size - 1); + default: + AUDDBG ("Throwing away %i bytes of text due to invalid encoding %d\n", + size - 1, (gint) data[0]); + return NULL; + } +} + +static gboolean read_comment_frame (VFSFile * handle, ID3v2FrameHeader * header, + gchar * * lang, gchar * * type, gchar * * value) +{ + gint size = header->size; + gchar data[size]; + gchar * pair, * sep; + gsize converted; + + if (vfs_fread (data, 1, size, handle) != size) + return FALSE; + + if (header->flags & 0x200) + size = unsyncsafe (data, size); + + switch (data[0]) + { + case 0: + pair = g_convert (data + 4, size - 4, "UTF-8", "ISO-8859-1", NULL, + & converted, NULL); + break; + case 1: + if (data[4] == (gchar) 0xff) + pair = g_convert (data + 6, size - 6, "UTF-8", "UTF-16LE", NULL, + & converted, NULL); + else + pair = g_convert (data + 6, size - 6, "UTF-8", "UTF-16BE", NULL, + & converted, NULL); + break; + case 2: + pair = g_convert (data + 4, size - 4, "UTF-8", "UTF-16BE", NULL, + & converted, NULL); + break; + case 3: + pair = g_malloc (size - 3); + memcpy (pair, data + 4, size - 4); + pair[size - 4] = 0; + converted = size - 4; + break; + default: + AUDDBG ("Throwing away %i bytes of text due to invalid encoding %d\n", + size - 4, (gint) data[0]); + pair = NULL; + break; + } + + if (pair == NULL || (sep = memchr (pair, 0, converted)) == NULL) + return FALSE; + + * lang = g_strndup (data + 1, 3); + * type = g_strdup (pair); + * value = g_strdup (sep + 1); + + g_free (pair); + return TRUE; +} + +GenericFrame *readGenericFrame(VFSFile * fd, GenericFrame * gf) +{ + gf->header = readID3v2FrameHeader(fd); + gf->frame_body = read_char_data(fd, gf->header->size); + + return gf; +} + + +void readAllFrames(VFSFile * fd, int framesSize) +{ + int pos = 0; + int i = 0; + while (pos < framesSize) + { + GenericFrame *gframe = g_new0(GenericFrame, 1); + gframe = readGenericFrame(fd, gframe); + if (isValidFrame(gframe)) + { + mowgli_dictionary_add(frames, gframe->header->frame_id, gframe); + mowgli_node_add(gframe->header->frame_id, mowgli_node_create(), frameIDs); + pos += gframe->header->size; + i++; + } + else + break; + } + +} + +void write_int32(VFSFile * fd, guint32 val) +{ + guint32 be_val = GUINT32_TO_BE(val); + vfs_fwrite(&be_val, 4, 1, fd); +} + +void write_syncsafe_int32(VFSFile * fd, guint32 val) +{ + //TODO write the correct function - this is just for testing + int i = 0; + guint32 tmp = 0x0; + guint32 mask = 0x7f; + guint32 syncVal = 0; + tmp = val & mask; + syncVal = tmp; + for (i = 0; i < 3; i++) + { + tmp = 0; + mask <<= 7; + tmp = val & mask; + tmp <<= 1; + syncVal |= tmp; + } + guint32 be_val = GUINT32_TO_BE(syncVal); + vfs_fwrite(&be_val, 4, 1, fd); +} + + +void write_ASCII(VFSFile * fd, int size, gchar * value) +{ + vfs_fwrite(value, size, 1, fd); +} + + +void write_utf8(VFSFile * fd, int size, gchar * value) +{ + GError *error = NULL; + gsize bytes_read = 0, bytes_write = 0; + gchar *isoVal = g_convert(value, size, "ISO-8859-1", "UTF-8", &bytes_read, &bytes_write, &error); + vfs_fwrite(isoVal, size, 1, fd); +} + +guint32 writeAllFramesToFile(VFSFile * fd) +{ + guint32 size = 0; + mowgli_node_t *n, *tn; + MOWGLI_LIST_FOREACH_SAFE(n, tn, frameIDs->head) + { + GenericFrame *frame = (GenericFrame *) mowgli_dictionary_retrieve(frames, (gchar *) (n->data)); + if (frame) + { + writeGenericFrame(fd, frame); + size += frame->header->size + 10; + } + } + return size; +} + +void writeID3HeaderToFile(VFSFile * fd, ID3v2Header * header) +{ + vfs_fwrite(header->id3, 3, 1, fd); + vfs_fwrite(&header->version, 2, 1, fd); + vfs_fwrite(&header->flags, 1, 1, fd); + write_syncsafe_int32(fd, header->size); +} + +void writePaddingToFile(VFSFile * fd, int ksize) +{ + gchar padding = 0; + int i = 0; + for (i = 0; i < ksize; i++) + vfs_fwrite(&padding, 1, 1, fd); +} + + +void writeID3FrameHeaderToFile(VFSFile * fd, ID3v2FrameHeader * header) +{ + vfs_fwrite(header->frame_id, 4, 1, fd); + write_int32(fd, header->size); + vfs_fwrite(&header->flags, 2, 1, fd); +} + +void writeGenericFrame(VFSFile * fd, GenericFrame * frame) +{ + writeID3FrameHeaderToFile(fd, frame->header); + vfs_fwrite(frame->frame_body, frame->header->size, 1, fd); +} + +gboolean isExtendedHeader(ID3v2Header * header) +{ + if ((header->flags & 0x40) == (0x40)) + return TRUE; + else + return FALSE; +} + +int getFrameID(ID3v2FrameHeader * header) +{ + int i = 0; + for (i = 0; i < ID3_TAGS_NO; i++) + { + if (!strcmp(header->frame_id, id3_frames[i])) + return i; + } + return -1; +} + + +void skipFrame(VFSFile * fd, guint32 size) +{ + vfs_fseek(fd, size, SEEK_CUR); +} + +static void associate_string (Tuple * tuple, VFSFile * handle, gint field, + const gchar * customfield, ID3v2FrameHeader * header) +{ + gchar * text = read_text_frame (handle, header); + + if (text == NULL) + return; + + if (customfield != NULL) + AUDDBG ("custom field %s = %s\n", customfield, text); + else + AUDDBG ("field %i = %s\n", field, text); + + tuple_associate_string (tuple, field, customfield, text); + g_free (text); +} + +static void associate_int (Tuple * tuple, VFSFile * handle, gint field, + const gchar * customfield, ID3v2FrameHeader * header) +{ + gchar * text = read_text_frame (handle, header); + + if (text == NULL) + return; + + if (customfield != NULL) + AUDDBG ("custom field %s = %s\n", customfield, text); + else + AUDDBG ("field %i = %s\n", field, text); + + tuple_associate_int (tuple, field, customfield, atoi (text)); + g_free (text); +} + +static void decode_private_info(Tuple * tuple, VFSFile * fd, ID3v2FrameHeader * header) +{ + gchar *value = read_char_data(fd, header->size); + if (!strncmp(value, "WM/", 3)) + { + AUDDBG("Windows Media tag: %s\n", value); + } else { + AUDDBG("Unable to decode private data, skipping: %s\n", value); + } +} + +static void decode_comment (Tuple * tuple, VFSFile * handle, ID3v2FrameHeader * + header) +{ + gchar * lang, * type, * value; + + if (! read_comment_frame (handle, header, & lang, & type, & value)) + return; + + AUDDBG ("comment: lang = %s, type = %s, value = %s\n", lang, type, value); + + if (! type[0]) /* blank type == actual comment */ + tuple_associate_string (tuple, FIELD_COMMENT, NULL, value); + + g_free (lang); + g_free (type); + g_free (value); +} + +static void decode_txxx (Tuple * tuple, VFSFile * handle, ID3v2FrameHeader * header) +{ + gchar * text = read_text_frame (handle, header); + + if (text == NULL) + return; + + gchar * separator = strchr(text, 0); + + if (separator == NULL) + return; + + gchar * value = separator + 1; + AUDDBG ("Field '%s' has value '%s'\n", text, value); + tuple_associate_string (tuple, -1, text, value); + + g_free (text); +} + +Tuple *decodeGenre(Tuple * tuple, VFSFile * fd, ID3v2FrameHeader header) +{ + gint numericgenre; + gchar * text = read_text_frame (fd, & header); + + if (text == NULL) + return tuple; + + if (text[0] == '(') + numericgenre = atoi (text + 1); + else + numericgenre = atoi (text); + + if (numericgenre > 0) + { + tuple_associate_string(tuple, FIELD_GENRE, NULL, convert_numericgenre_to_text(numericgenre)); + return tuple; + } + tuple_associate_string(tuple, FIELD_GENRE, NULL, text); + g_free (text); + return tuple; +} + +gboolean isValidFrame(GenericFrame * frame) +{ + if (strlen(frame->header->frame_id) != 0) + return TRUE; + else + return FALSE; +} + + + +void add_newISO8859_1FrameFromString(const gchar * value, int id3_field) +{ + GError *error = NULL; + gsize bytes_read = 0, bytes_write = 0; + gchar *retVal = g_convert(value, strlen(value), "ISO-8859-1", "UTF-8", &bytes_read, &bytes_write, &error); + ID3v2FrameHeader *header = g_new0(ID3v2FrameHeader, 1); + header->frame_id = id3_frames[id3_field]; + header->flags = 0; + header->size = strlen(retVal) + 1; + gchar *buf = g_new0(gchar, header->size + 1); + memcpy(buf + 1, retVal, header->size); + GenericFrame *frame = g_new0(GenericFrame, 1); + frame->header = header; + frame->frame_body = buf; + mowgli_dictionary_add(frames, header->frame_id, frame); + mowgli_node_add(frame->header->frame_id, mowgli_node_create(), frameIDs); + +} + + +void add_newFrameFromTupleStr(Tuple * tuple, int field, int id3_field) +{ + const gchar *value = tuple_get_string(tuple, field, NULL); + add_newISO8859_1FrameFromString(value, id3_field); +} + + +void add_newFrameFromTupleInt(Tuple * tuple, int field, int id3_field) +{ + int intvalue = tuple_get_int(tuple, field, NULL); + gchar *value = g_strdup_printf("%d", intvalue); + add_newISO8859_1FrameFromString(value, id3_field); + +} + + + +void add_frameFromTupleStr(Tuple * tuple, int field, int id3_field) +{ + const gchar *value = tuple_get_string(tuple, field, NULL); + GError *error = NULL; + gsize bytes_read = 0, bytes_write = 0; + gchar *retVal = g_convert(value, strlen(value), "ISO-8859-1", "UTF-8", &bytes_read, &bytes_write, &error); + + GenericFrame *frame = mowgli_dictionary_retrieve(frames, id3_frames[id3_field]); + if (frame != NULL) + { + frame->header->size = strlen(retVal) + 1; + gchar *buf = g_new0(gchar, frame->header->size + 1); + memcpy(buf + 1, retVal, frame->header->size); + frame->frame_body = buf; + } + else + add_newFrameFromTupleStr(tuple, field, id3_field); + +} + +void add_frameFromTupleInt(Tuple * tuple, int field, int id3_field) +{ + int intvalue = tuple_get_int(tuple, field, NULL); + gchar *value = g_strdup_printf("%d", intvalue); + GError *error = NULL; + gsize bytes_read = 0, bytes_write = 0; + gchar *retVal = g_convert(value, strlen(value), "ISO-8859-1", "UTF-8", &bytes_read, &bytes_write, &error); + + GenericFrame *frame = mowgli_dictionary_retrieve(frames, id3_frames[id3_field]); + if (frame != NULL) + { + frame->header->size = strlen(retVal) + 1; + gchar *buf = g_new0(gchar, frame->header->size + 1); + memcpy(buf + 1, retVal, frame->header->size); + frame->frame_body = buf; + } + else + add_newFrameFromTupleStr(tuple, field, id3_field); + +} + +gboolean id3v2_can_handle_file(VFSFile * f) +{ + ID3v2Header *header = readHeader(f); + if (!strcmp(header->id3, "ID3")) + return TRUE; + return FALSE; +} + + + +Tuple *id3v2_populate_tuple_from_file(Tuple * tuple, VFSFile * f) +{ + vfs_fseek(f, 0, SEEK_SET); + ExtendedHeader *extHeader; + ID3v2Header *header = readHeader(f); + int pos = 0; + if (isExtendedHeader(header)) + { + extHeader = readExtendedHeader(f); + vfs_fseek(f, 10 + extHeader->header_size, SEEK_SET); + } + + while (pos < header->size) + { + ID3v2FrameHeader *frame = readID3v2FrameHeader(f); + if (frame->size == 0) + break; + int id = getFrameID(frame); + pos = pos + frame->size + 10; + if (pos > header->size) + break; + switch (id) + { + case ID3_ALBUM: + associate_string (tuple, f, FIELD_ALBUM, NULL, frame); + break; + case ID3_TITLE: + associate_string (tuple, f, FIELD_TITLE, NULL, frame); + break; + case ID3_COMPOSER: + associate_string (tuple, f, FIELD_ARTIST, NULL, frame); + break; + case ID3_COPYRIGHT: + associate_string (tuple, f, FIELD_COPYRIGHT, NULL, frame); + break; + case ID3_DATE: + associate_string (tuple, f, FIELD_DATE, NULL, frame); + break; + case ID3_TIME: + associate_int (tuple, f, FIELD_LENGTH, NULL, frame); + break; + case ID3_LENGTH: + associate_int (tuple, f, FIELD_LENGTH, NULL, frame); + break; + case ID3_ARTIST: + associate_string (tuple, f, FIELD_ARTIST, NULL, frame); + break; + case ID3_TRACKNR: + associate_int (tuple, f, FIELD_TRACK_NUMBER, NULL, frame); + break; + case ID3_YEAR: + case ID3_RECORDING_TIME: + associate_int (tuple, f, FIELD_YEAR, NULL, frame); + break; + case ID3_GENRE: + tuple = decodeGenre(tuple, f, *frame); + break; + case ID3_COMMENT: + decode_comment (tuple, f, frame); + break; + case ID3_PRIVATE: + decode_private_info (tuple, f, frame); + break; + case ID3_ENCODER: + associate_string (tuple, f, -1, "encoder", frame); + break; + case ID3_TXXX: + decode_txxx (tuple, f, frame); + break; + default: + AUDDBG("Skipping %i bytes over unsupported ID3 frame %s\n", frame->size, frame->frame_id); + skipFrame(f, frame->size); + } + } + return tuple; +} + + +gboolean id3v2_write_tuple_to_file(Tuple * tuple, VFSFile * f) +{ + VFSFile *tmp; + vfs_fseek(f, 0, SEEK_SET); + + ExtendedHeader *extHeader; + if (frameIDs != NULL) + { + mowgli_node_t *n, *tn; + MOWGLI_LIST_FOREACH_SAFE(n, tn, frameIDs->head) + { + mowgli_node_delete(n, frameIDs); + } + } + frameIDs = mowgli_list_create(); + ID3v2Header *header = readHeader(f); + int framesSize = header->size; + + if (isExtendedHeader(header)) + { + extHeader = readExtendedHeader(f); + framesSize -= 10; + framesSize -= extHeader->padding_size; + } + + //read all frames into generic frames; + frames = mowgli_dictionary_create(strcasecmp); + readAllFrames(f, header->size); + + //make the new frames from tuple and replace in the dictionary the old frames with the new ones + if (tuple_get_string(tuple, FIELD_ARTIST, NULL)) + add_frameFromTupleStr(tuple, FIELD_ARTIST, ID3_ARTIST); + + if (tuple_get_string(tuple, FIELD_TITLE, NULL)) + add_frameFromTupleStr(tuple, FIELD_TITLE, ID3_TITLE); + + if (tuple_get_string(tuple, FIELD_ALBUM, NULL)) + add_frameFromTupleStr(tuple, FIELD_ALBUM, ID3_ALBUM); + + if (tuple_get_string(tuple, FIELD_COMMENT, NULL)) + add_frameFromTupleStr(tuple, FIELD_COMMENT, ID3_COMMENT); + + if (tuple_get_string(tuple, FIELD_GENRE, NULL)) + add_frameFromTupleStr(tuple, FIELD_GENRE, ID3_GENRE); + + if (tuple_get_int(tuple, FIELD_YEAR, NULL) != 0) + add_frameFromTupleInt(tuple, FIELD_YEAR, ID3_YEAR); + + if (tuple_get_int(tuple, FIELD_TRACK_NUMBER, NULL) != 0) + add_frameFromTupleInt(tuple, FIELD_TRACK_NUMBER, ID3_TRACKNR); + + const gchar *tmpdir = g_get_tmp_dir(); + gchar *tmp_path = g_strdup_printf("file://%s/%s", tmpdir, "tmp.mpc"); + tmp = vfs_fopen(tmp_path, "w+"); + + int oldSize = header->size; + header->size = TAG_SIZE * 1024; + + writeID3HeaderToFile(tmp, header); + + int size = writeAllFramesToFile(tmp); + writePaddingToFile(tmp, TAG_SIZE * 1024 - size - 10); + + copyAudioToFile(f, tmp, oldSize); + + + gchar *uri = g_strdup(f->uri); + vfs_fclose(tmp); + gchar *f1 = g_filename_from_uri(tmp_path, NULL, NULL); + gchar *f2 = g_filename_from_uri(uri, NULL, NULL); + if (g_rename(f1, f2) == 0) + { + AUDDBG("the tag was updated successfully\n"); + } + else + { + AUDDBG("an error has occured\n"); + } + return TRUE; +}
