Mercurial > audlegacy
view 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 source
/* * 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; }