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