changeset 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 54b4f7aaca24
children
files src/Makefile src/libaudtag/Makefile src/libaudtag/aac/aac.c src/libaudtag/aac/aac.h src/libaudtag/ape/ape.c src/libaudtag/ape/ape.h src/libaudtag/audtag.c src/libaudtag/audtag.h src/libaudtag/id3/frame.h src/libaudtag/id3/id3v1.c src/libaudtag/id3/id3v1.h src/libaudtag/id3/id3v2.c src/libaudtag/id3/id3v2.h src/libaudtag/tag_module.c src/libaudtag/tag_module.h src/libaudtag/util.c src/libaudtag/util.h src/libaudtag/wma/guid.c src/libaudtag/wma/guid.h src/libaudtag/wma/module.h src/libaudtag/wma/wma.c src/libaudtag/wma/wma.h src/libaudtag/wma/wma_fmt.h
diffstat 23 files changed, 3791 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/src/Makefile	Wed Nov 11 15:34:23 2009 +0900
+++ b/src/Makefile	Wed May 05 18:26:06 2010 +0900
@@ -1,4 +1,4 @@
-SUBDIRS = libguess libSAD audlegacy libid3tag libaudutil
+SUBDIRS = libguess libSAD audlegacy libid3tag libaudutil libaudtag
 
 include ../extra.mk
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/Makefile	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,22 @@
+LIB = ${LIB_PREFIX}audtag${LIB_SUFFIX}
+LIB_MAJOR = 1
+LIB_MINOR = 0
+
+SRCS = audtag.c		\
+       util.c		\
+       tag_module.c	\
+       wma/guid.c	\
+       wma/wma.c	\
+       id3/id3v1.c	\
+       id3/id3v2.c	\
+       ape/ape.c	\
+       aac/aac.c
+
+INCLUDES = audtag.h
+
+include ../../buildsys.mk
+include ../../extra.mk
+
+CPPFLAGS += ${LIB_CPPFLAGS} ${GLIB_CFLAGS} ${MOWGLI_CFLAGS} -D_AUDACIOUS_CORE -I.. -I../..
+CFLAGS += ${LIB_CFLAGS}
+LIBS += ${GLIB_LIBS} ${MOWGLI_LIBS}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/aac/aac.c	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,479 @@
+/*
+ * 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/gstdio.h>
+#include <audlegacy/tuple.h>
+#include <audlegacy/vfs.h>
+#include "../tag_module.h"
+#include "aac.h"
+#include "../util.h"
+
+static const char *ID3v1GenreList[] = {
+    "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk",
+    "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies",
+    "Other", "Pop", "R&B", "Rap", "Reggae", "Rock",
+    "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks",
+    "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk",
+    "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House",
+    "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass",
+    "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock",
+    "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk",
+    "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta",
+    "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret",
+    "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi",
+    "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical",
+    "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing",
+    "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde",
+    "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band",
+    "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
+    "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
+    "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba",
+    "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet",
+    "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall",
+    "Goa", "Drum & Bass", "Club House", "Hardcore", "Terror",
+    "Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat",
+    "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary C",
+    "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop",
+    "SynthPop",
+};
+
+gchar *atom_types[] = { "\251alb", "\251nam", "cprt", "\251art", "\251ART", "trkn", "\251day", "gnre", "desc" };
+
+Atom *readAtom(VFSFile * fd)
+{
+    guint32 size = read_BEuint32(fd);
+    if (size > vfs_fsize(fd))
+        return NULL;
+
+    Atom *atom = g_new0(Atom, 1);
+    atom->size = size;
+    atom->name = read_char_data(fd, 4);
+    atom->body = read_char_data(fd, atom->size - 8);
+    atom->type = 0;
+    return atom;
+}
+
+void writeAtom(VFSFile * fd, Atom * atom)
+{
+    write_BEuint32(fd, atom->size);
+    vfs_fwrite(atom->name, 4, 1, fd);
+    vfs_fwrite(atom->body, atom->size - 8, 1, fd);
+}
+
+void printAtom(Atom * atom)
+{
+    AUDDBG("size = %x\n", atom->size);
+    AUDDBG("name = %s\n", atom->name);
+}
+
+StrDataAtom *readStrDataAtom(VFSFile * fd)
+{
+    StrDataAtom *atom = g_new0(StrDataAtom, 1);
+    atom->atomsize = read_BEuint32(fd);
+    atom->name = read_char_data(fd, 4);
+    atom->datasize = read_BEuint32(fd);
+    atom->dataname = read_char_data(fd, 4);
+    atom->vflag = read_BEuint32(fd);
+    atom->nullData = read_BEuint32(fd);
+    atom->data = read_char_data(fd, atom->datasize - 16);
+    atom->type = 1;
+    return atom;
+}
+
+void writeStrDataAtom(VFSFile * fd, StrDataAtom * atom)
+{
+    write_BEuint32(fd, atom->atomsize);
+    vfs_fwrite(atom->name, 4, 1, fd);
+    write_BEuint32(fd, atom->datasize);
+    vfs_fwrite(atom->dataname, 4, 1, fd);
+    write_BEuint32(fd, atom->vflag);
+    write_BEuint32(fd, atom->nullData);
+    vfs_fwrite(atom->data, atom->datasize - 16, 1, fd);
+}
+
+Atom *findAtom(VFSFile * fd, gchar * name)
+{
+    Atom *atom = readAtom(fd);
+    while (strcmp(atom->name, name) && !vfs_feof(fd))
+    {
+        g_free(atom);
+        atom = readAtom(fd);
+    }
+    if (vfs_feof(fd))
+    {
+        g_free(atom);
+        AUDDBG("The atom %s could not be found\n", name);
+        return NULL;
+    }
+    return atom;
+}
+
+Atom *getilstAtom(VFSFile * fd)
+{
+    Atom *moov = findAtom(fd, MOOV);
+
+    // search atom childs
+    vfs_fseek(fd, -(moov->size - 7), SEEK_CUR);
+    Atom *udta = findAtom(fd, UDTA);
+
+
+    vfs_fseek(fd, -(udta->size - 7), SEEK_CUR);
+    Atom *meta = findAtom(fd, META);
+
+    vfs_fseek(fd, -(meta->size - 11), SEEK_CUR);
+    Atom *ilst = findAtom(fd, ILST);
+
+    int zz = vfs_ftell(fd);
+    AUDDBG("zzz = %d\n", zz);
+    ilstFileOffset = vfs_ftell(fd) - ilst->size;
+    vfs_fseek(fd, -(ilst->size - 7), SEEK_CUR);
+
+    return ilst;
+
+}
+
+int getAtomID(gchar * name)
+{
+    g_return_val_if_fail(name != NULL, -1);
+    int i = 0;
+    for (i = 0; i < MP4_ITEMS_NO; i++)
+    {
+        if (!strcmp(name, atom_types[i]))
+            return i;
+    }
+    return -1;
+}
+
+StrDataAtom *makeAtomWithData(const gchar * data, StrDataAtom * atom, int field)
+{
+    guint32 charsize = strlen(data);
+    atom->atomsize = charsize + 24;
+    atom->name = atom_types[field];
+    atom->datasize = charsize + 16;
+    atom->dataname = "data";
+    atom->vflag = 0x0;
+    atom->nullData = 0x0;
+    atom->data = (gchar *) data;
+    atom->type = 1;
+    return atom;
+
+}
+
+void writeAtomListToFile(VFSFile * from, VFSFile * to, int offset, mowgli_list_t * list)
+{
+    //read free atom if we have any :D
+    guint32 oset = ilstFileOffset + ilstAtom->size;
+    vfs_fseek(from, oset, SEEK_SET);
+    mowgli_list_t *atoms_before_free = mowgli_list_create();
+    Atom *atom = readAtom(from);
+    while (strcmp(atom->name, "free") && !vfs_feof(from))
+    {
+        mowgli_node_add(atom, mowgli_node_create(), atoms_before_free);
+        g_free(atom);
+        atom = readAtom(from);
+    }
+    g_free(atom);
+    if (vfs_feof(from))
+    {
+        AUDDBG("No free atoms\n");
+        g_free(atom);
+        atom = NULL;
+    }
+
+    //write ilst atom header
+    gchar il[4] = ILST;
+    vfs_fwrite(&newilstSize, 4, 1, to);
+    vfs_fwrite(il, 4, 1, to);
+    //write ilst
+
+    mowgli_node_t *n, *tn;
+
+    MOWGLI_LIST_FOREACH_SAFE(n, tn, list->head)
+    {
+        if (((Atom *) (n->data))->type == 0)
+        {
+            writeAtom(to, (Atom *) (n->data));
+        }
+        else
+        {
+            writeStrDataAtom(to, (StrDataAtom *) (n->data));
+        }
+    }
+
+    //write all atoms before free
+    if (atoms_before_free->count != 0)
+    {
+
+        MOWGLI_LIST_FOREACH_SAFE(n, tn, list->head)
+        {
+            writeAtom(to, (Atom *) (n->data));
+        }
+    }
+    if (atom != NULL)
+    {
+        atom->size -= newilstSize - ilstAtom->size;
+    }
+    writeAtom(to, atom);
+}
+
+gboolean aac_can_handle_file(VFSFile * f)
+{
+    Atom *first_atom = readAtom(f);
+    if (first_atom == NULL)
+        return FALSE;
+    if (!strcmp(first_atom->name, FTYP))
+        return TRUE;
+    return FALSE;
+}
+
+Tuple *aac_populate_tuple_from_file(Tuple * tuple, VFSFile * f)
+{
+    if (ilstAtom)
+        g_free(ilstAtom);
+    ilstAtom = getilstAtom(f);
+    int size_read = 0;
+
+    if (dataAtoms != NULL)
+    {
+        mowgli_node_t *n, *tn;
+
+        MOWGLI_LIST_FOREACH_SAFE(n, tn, dataAtoms->head)
+        {
+            mowgli_node_delete(n, dataAtoms);
+        }
+    }
+    dataAtoms = mowgli_list_create();
+
+    while (size_read < ilstAtom->size)
+    {
+        Atom *at = readAtom(f);
+        mowgli_node_add(at, mowgli_node_create(), dataAtoms);
+        int atomtype = getAtomID(at->name);
+        if (atomtype == -1)
+        {
+            size_read += at->size;
+            continue;
+        }
+        g_free(at);
+        vfs_fseek(f, -(at->size), SEEK_CUR);
+        StrDataAtom *a = readStrDataAtom(f);
+        size_read += a->atomsize;
+
+        switch (atomtype)
+        {
+          case MP4_ALBUM:
+          {
+              tuple_associate_string(tuple, FIELD_ALBUM, NULL, a->data);
+          }
+              break;
+          case MP4_TITLE:
+          {
+              tuple_associate_string(tuple, FIELD_TITLE, NULL, a->data);
+          }
+              break;
+          case MP4_COPYRIGHT:
+          {
+              tuple_associate_string(tuple, FIELD_COPYRIGHT, NULL, a->data);
+          }
+              break;
+          case MP4_ARTIST:
+          case MP4_ARTIST2:
+          {
+              tuple_associate_string(tuple, FIELD_ARTIST, NULL, a->data);
+          }
+              break;
+          case MP4_TRACKNR:
+          {
+              //tuple_associate_string(tuple,FIELD_ALBUM,NULL,a->data);
+          }
+              break;
+          case MP4_YEAR:
+          {
+              tuple_associate_int(tuple, FIELD_YEAR, NULL, atoi(a->data));
+          }
+              break;
+          case MP4_GENRE:
+          {
+              guint8 *val = (guint8 *) (a->data + (a->datasize - 17));
+              const gchar *genre = ID3v1GenreList[*val - 1];
+              tuple_associate_string(tuple, FIELD_GENRE, NULL, genre);
+          }
+              break;
+          case MP4_COMMENT:
+          {
+              tuple_associate_string(tuple, FIELD_COMMENT, NULL, a->data);
+          }
+              break;
+        }
+    }
+    return tuple;
+}
+
+gboolean aac_write_tuple_to_file(Tuple * tuple, VFSFile * f)
+{
+#ifdef BROKEN
+    return FALSE;
+#endif
+    newilstSize = 0;
+    mowgli_node_t *n, *tn;
+    mowgli_list_t *newdataAtoms;
+    newdataAtoms = mowgli_list_create();
+
+    MOWGLI_LIST_FOREACH_SAFE(n, tn, dataAtoms->head)
+    {
+        int atomtype = getAtomID(((StrDataAtom *) (n->data))->name);
+        switch (atomtype)
+        {
+          case MP4_ALBUM:
+          {
+              const gchar *strVal = tuple_get_string(tuple, FIELD_ALBUM, NULL);
+              if (strVal != NULL)
+              {
+                  StrDataAtom *atom = g_new0(StrDataAtom, 1);
+                  atom = makeAtomWithData(strVal, atom, MP4_ALBUM);
+                  mowgli_node_add(atom, mowgli_node_create(), newdataAtoms);
+                  newilstSize += atom->atomsize;
+              }
+              else
+              {
+                  mowgli_node_add(n->data, mowgli_node_create(), newdataAtoms);
+                  newilstSize += ((Atom *) (n->data))->size;
+              }
+          }
+              break;
+          case MP4_TITLE:
+          {
+              const gchar *strVal = tuple_get_string(tuple, FIELD_TITLE, NULL);
+              if (strVal != NULL)
+              {
+                  StrDataAtom *atom = g_new0(StrDataAtom, 1);
+                  atom = makeAtomWithData(strVal, atom, MP4_TITLE);
+                  mowgli_node_add(atom, mowgli_node_create(), newdataAtoms);
+                  newilstSize += atom->atomsize;
+              }
+              else
+              {
+                  mowgli_node_add(n->data, mowgli_node_create(), newdataAtoms);
+                  newilstSize += ((Atom *) (n->data))->size;
+              }
+          }
+              break;
+          case MP4_COPYRIGHT:
+          {
+              const gchar *strVal = tuple_get_string(tuple, FIELD_COPYRIGHT, NULL);
+              if (strVal != NULL)
+              {
+                  StrDataAtom *atom = g_new0(StrDataAtom, 1);
+                  atom = makeAtomWithData(strVal, atom, MP4_COPYRIGHT);
+                  mowgli_node_add(atom, mowgli_node_create(), newdataAtoms);
+                  newilstSize += atom->atomsize;
+              }
+              else
+              {
+                  mowgli_node_add(n->data, mowgli_node_create(), newdataAtoms);
+                  newilstSize += ((Atom *) (n->data))->size;
+              }
+          }
+              break;
+          case MP4_ARTIST:
+          case MP4_ARTIST2:
+          {
+              const gchar *strVal = tuple_get_string(tuple, FIELD_ARTIST, NULL);
+              if (strVal != NULL)
+              {
+                  StrDataAtom *atom = g_new0(StrDataAtom, 1);
+                  atom = makeAtomWithData(strVal, atom, MP4_ARTIST2);
+                  mowgli_node_add(atom, mowgli_node_create(), newdataAtoms);
+                  newilstSize += atom->atomsize;
+              }
+              else
+              {
+                  mowgli_node_add(n->data, mowgli_node_create(), newdataAtoms);
+                  newilstSize += ((Atom *) (n->data))->size;
+              }
+          }
+              break;
+          case MP4_TRACKNR:
+          {
+              //tuple_associate_string(tuple,FIELD_ALBUM,NULL,a->data);
+          }
+              break;
+          case MP4_YEAR:
+          {
+              int iyear = tuple_get_int(tuple, FIELD_YEAR, NULL);
+              gchar *strVal = g_strdup_printf("%d", iyear);
+              if (strVal != NULL)
+              {
+                  StrDataAtom *atom = g_new0(StrDataAtom, 1);
+                  atom = makeAtomWithData(strVal, atom, MP4_YEAR);
+                  mowgli_node_add(atom, mowgli_node_create(), newdataAtoms);
+                  newilstSize += atom->atomsize;
+              }
+              else
+              {
+                  mowgli_node_add(n->data, mowgli_node_create(), newdataAtoms);
+                  newilstSize += ((Atom *) (n->data))->size;
+              }
+          }
+              break;
+              /*
+                 case MP4_GENRE:
+                 {
+
+                 guint8 *val = (guint8*)(a->data + (a->datasize-17));
+                 const gchar* genre = ID3v1GenreList[*val-1];
+                 tuple_associate_string(tuple,FIELD_GENRE,NULL,genre);
+
+                 }break;
+               */
+          case MP4_COMMENT:
+          {
+              const gchar *strVal = tuple_get_string(tuple, FIELD_COMMENT, NULL);
+              if (strVal != NULL)
+              {
+                  StrDataAtom *atom = g_new0(StrDataAtom, 1);
+                  atom = makeAtomWithData(strVal, atom, MP4_COMMENT);
+                  mowgli_node_add(atom, mowgli_node_create(), newdataAtoms);
+                  newilstSize += atom->atomsize;
+              }
+              else
+              {
+                  mowgli_node_add(n->data, mowgli_node_create(), newdataAtoms);
+                  newilstSize += ((Atom *) (n->data))->size;
+              }
+          }
+              break;
+          default:
+          {
+              mowgli_node_add(n->data, mowgli_node_create(), newdataAtoms);
+              newilstSize += ((Atom *) (n->data))->size;
+          }
+              break;
+        }
+    }
+
+    VFSFile *tmp;
+    const gchar *tmpdir = g_get_tmp_dir();
+    gchar *tmp_path = g_strdup_printf("file://%s/%s", tmpdir, "tmp.mp4");
+    tmp = vfs_fopen(tmp_path, "w");
+    copyAudioData(f, tmp, 0, ilstFileOffset);
+    writeAtomListToFile(f, tmp, ilstFileOffset, newdataAtoms);
+    return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/aac/aac.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#ifndef AAC_H
+#define	AAC_H
+#include <audlegacy/tuple.h>
+#include <audlegacy/vfs.h>
+#include "../tag_module.h"
+
+#define FTYP    "ftyp"
+#define MOOV    "moov"
+#define ILST    "ilst"
+#define UDTA    "udta"
+#define META    "meta"
+#define ILST    "ilst"
+#define FREE    "free"
+
+enum {
+    MP4_ALBUM = 0,
+    MP4_TITLE,
+    MP4_COPYRIGHT,
+    MP4_ARTIST,
+    MP4_ARTIST2,
+    MP4_TRACKNR,
+    MP4_YEAR,
+    MP4_GENRE,
+    MP4_COMMENT,
+    MP4_ITEMS_NO
+};
+
+typedef struct mp4atom
+{
+    guint32 size;
+    gchar* name; //4 bytes
+    gchar* body;
+    int type;
+}Atom;
+
+
+typedef struct strdataatom
+{
+    guint32 atomsize;
+    gchar* name;
+    guint32 datasize;
+    gchar* dataname;
+    guint32 vflag;
+    guint32 nullData;
+    gchar*  data;
+    int type;
+}StrDataAtom;
+
+Atom *ilstAtom;
+guint64 ilstFileOffset;
+guint32 newilstSize ;
+mowgli_list_t *dataAtoms;
+mowgli_dictionary_t *ilstAtoms;
+
+/* TAG plugin API */
+gboolean aac_can_handle_file(VFSFile *f);
+Tuple *aac_populate_tuple_from_file(Tuple *tuple,VFSFile *f);
+gboolean aac_write_tuple_to_file(Tuple* tuple, VFSFile *f);
+
+static const tag_module_t aac = {
+    .name = "AAC",
+    .can_handle_file = aac_can_handle_file,
+    .populate_tuple_from_file = aac_populate_tuple_from_file,
+    .write_tuple_to_file = aac_write_tuple_to_file,
+};
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/ape/ape.c	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2010 John Lindgren
+ *
+ * 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.
+ */
+
+/* TODO:
+ * - ReplayGain info
+ * - Support updating files that have their tag at the beginning?
+ */
+
+#include <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <audlegacy/vfs.h>
+
+#include "ape.h"
+
+#define DEBUG(...) fprintf (stderr, "APE: " __VA_ARGS__)
+
+typedef struct
+{
+    gchar magic[8];
+    guint32 version; /* LE */
+    guint32 length; /* LE */
+    guint32 items; /* LE */
+    guint32 flags; /* LE */
+    guint64 reserved;
+}
+APEHeader;
+
+typedef struct
+{
+    gchar * key, * value;
+}
+ValuePair;
+
+#define APE_FLAG_HAS_HEADER (1 << 31)
+#define APE_FLAG_HAS_NO_FOOTER (1 << 30)
+#define APE_FLAG_IS_HEADER (1 << 29)
+
+static gboolean ape_read_header (VFSFile * handle, APEHeader * header)
+{
+    if (vfs_fread (header, 1, sizeof (APEHeader), handle) != sizeof (APEHeader))
+        return FALSE;
+
+    if (strncmp (header->magic, "APETAGEX", 8))
+        return FALSE;
+
+    header->version = GUINT32_FROM_LE (header->version);
+    header->length = GUINT32_FROM_LE (header->length);
+    header->items = GUINT32_FROM_LE (header->items);
+    header->flags = GUINT32_FROM_LE (header->flags);
+
+    if (header->length < sizeof (APEHeader))
+        return FALSE;
+
+    return TRUE;
+}
+
+static gboolean ape_find_header (VFSFile * handle, APEHeader * header, gint *
+ start, gint * length, gint * data_start, gint * data_length)
+{
+    APEHeader secondary;
+
+    if (vfs_fseek (handle, 0, SEEK_SET))
+        return FALSE;
+
+    if (ape_read_header (handle, header))
+    {
+        DEBUG ("Found header at 0, length = %d, version = %d.\n", (gint)
+         header->length, (gint) header->version);
+        * start = 0;
+        * length = header->length;
+        * data_start = sizeof (APEHeader);
+        * data_length = header->length - sizeof (APEHeader);
+
+        if (! (header->flags & APE_FLAG_HAS_HEADER) || ! (header->flags &
+         APE_FLAG_IS_HEADER))
+        {
+            DEBUG ("Invalid header flags (%u).\n", (guint) header->flags);
+            return FALSE;
+        }
+
+        if (! (header->flags & APE_FLAG_HAS_NO_FOOTER))
+        {
+            if (vfs_fseek (handle, header->length, SEEK_CUR))
+                return FALSE;
+
+            if (! ape_read_header (handle, & secondary))
+            {
+                DEBUG ("Expected footer, but found none.\n");
+                return FALSE;
+            }
+
+            * length += sizeof (APEHeader);
+        }
+
+        return TRUE;
+    }
+
+    if (vfs_fseek (handle, -sizeof (APEHeader), SEEK_END))
+        return FALSE;
+
+    if (ape_read_header (handle, header))
+    {
+        DEBUG ("Found footer at %d, length = %d, version = %d.\n", (gint)
+         vfs_ftell (handle) - (gint) sizeof (APEHeader), (gint) header->length,
+         (gint) header->version);
+        * start = vfs_ftell (handle) - header->length;
+        * length = header->length;
+        * data_start = vfs_ftell (handle) - header->length;
+        * data_length = header->length - sizeof (APEHeader);
+
+        if ((header->flags & APE_FLAG_HAS_NO_FOOTER) || (header->flags &
+         APE_FLAG_IS_HEADER))
+        {
+            DEBUG ("Invalid footer flags (%u).\n", (guint) header->flags);
+            return FALSE;
+        }
+
+        if (header->flags & APE_FLAG_HAS_HEADER)
+        {
+            if (vfs_fseek (handle, -(gint) header->length - sizeof (APEHeader),
+             SEEK_CUR))
+                return FALSE;
+
+            if (! ape_read_header (handle, & secondary))
+            {
+                DEBUG ("Expected header, but found none.\n");
+                return FALSE;
+            }
+
+            * start -= sizeof (APEHeader);
+            * length += sizeof (APEHeader);
+        }
+
+        return TRUE;
+    }
+
+    DEBUG ("No header found.\n");
+    return FALSE;
+}
+
+static gboolean ape_is_our_file (VFSFile * handle)
+{
+    APEHeader header;
+    gint start, length, data_start, data_length;
+
+    return ape_find_header (handle, & header, & start, & length, & data_start,
+     & data_length);
+}
+
+static ValuePair * ape_read_item (void * * data, gint length)
+{
+    guint32 * header = * data;
+    gchar * value;
+    ValuePair * pair;
+
+    if (length < 8)
+    {
+        DEBUG ("Expected item, but only %d bytes remain in tag.\n", length);
+        return NULL;
+    }
+
+    value = memchr ((gchar *) (* data) + 8, 0, length - 8);
+
+    if (value == NULL)
+    {
+        DEBUG ("Unterminated item key (max length = %d).\n", length - 8);
+        return NULL;
+    }
+
+    value ++;
+
+    if (header[0] > (gchar *) (* data) + length - value)
+    {
+        DEBUG ("Item value of length %d, but only %d bytes remain in tag.\n",
+         (gint) header[0], (gint) ((gchar *) (* data) + length - value));
+        return NULL;
+    }
+
+    pair = g_malloc (sizeof (ValuePair));
+    pair->key = g_strdup ((gchar *) (* data) + 8);
+    pair->value = g_strndup (value, header[0]);
+
+    * data = value + header[0];
+
+    return pair;
+}
+
+static GList * ape_read_tag (VFSFile * handle)
+{
+    GList * list = NULL;
+    APEHeader header;
+    gint start, length, data_start, data_length;
+    void * data, * item;
+
+    if (! ape_find_header (handle, & header, & start, & length, & data_start,
+     & data_length))
+        return NULL;
+
+    if (vfs_fseek (handle, data_start, SEEK_SET))
+        return NULL;
+
+    data = g_malloc (data_length);
+
+    if (vfs_fread (data, 1, data_length, handle) != data_length)
+    {
+        g_free (data);
+        return NULL;
+    }
+
+    DEBUG ("Reading %d items:\n", header.items);
+    item = data;
+
+    while (header.items --)
+    {
+        ValuePair * pair = ape_read_item (& item, (gchar *) data + data_length -
+         (gchar *) item);
+
+        if (pair == NULL)
+            break;
+
+        DEBUG ("Read: %s = %s.\n", pair->key, pair->value);
+        list = g_list_prepend (list, pair);
+    }
+
+    g_free (data);
+    return g_list_reverse (list);
+}
+
+static void free_tag_list (GList * list)
+{
+    while (list != NULL)
+    {
+        g_free (((ValuePair *) list->data)->key);
+        g_free (((ValuePair *) list->data)->value);
+        g_free (list->data);
+        list = g_list_delete_link (list, list);
+    }
+}
+
+static Tuple * ape_fill_tuple (Tuple * tuple, VFSFile * handle)
+{
+    GList * list = ape_read_tag (handle), * node;
+
+    for (node = list; node != NULL; node = node->next)
+    {
+        gchar * key = ((ValuePair *) node->data)->key;
+        gchar * value = ((ValuePair *) node->data)->value;
+
+        if (! strcmp (key, "Artist"))
+            tuple_associate_string (tuple, FIELD_ARTIST, NULL, value);
+        else if (! strcmp (key, "Title"))
+            tuple_associate_string (tuple, FIELD_TITLE, NULL, value);
+        else if (! strcmp (key, "Album"))
+            tuple_associate_string (tuple, FIELD_ALBUM, NULL, value);
+        else if (! strcmp (key, "Comment"))
+            tuple_associate_string (tuple, FIELD_COMMENT, NULL, value);
+        else if (! strcmp (key, "Genre"))
+            tuple_associate_string (tuple, FIELD_GENRE, NULL, value);
+        else if (! strcmp (key, "Track"))
+            tuple_associate_int (tuple, FIELD_TRACK_NUMBER, NULL, atoi (value));
+        else if (! strcmp (key, "Date"))
+            tuple_associate_int (tuple, FIELD_YEAR, NULL, atoi (value));
+    }
+
+    free_tag_list (list);
+    return tuple;
+}
+
+static gboolean ape_write_item (VFSFile * handle, const gchar * key,
+ const gchar * value, int * written_length)
+{
+    gint key_len = strlen (key) + 1;
+    gint value_len = strlen (value);
+    guint32 header[2];
+
+    DEBUG ("Write: %s = %s.\n", key, value);
+
+    header[0] = GUINT32_TO_LE (value_len);
+    header[1] = 0;
+
+    if (vfs_fwrite (header, 1, 8, handle) != 8)
+        return FALSE;
+
+    if (vfs_fwrite (key, 1, key_len, handle) != key_len)
+        return FALSE;
+
+    if (vfs_fwrite (value, 1, value_len, handle) != value_len)
+        return FALSE;
+
+    * written_length += 8 + key_len + value_len;
+    return TRUE;
+}
+
+static gboolean write_string_item (Tuple * tuple, int field, VFSFile * handle,
+ const gchar * key, int * written_length, int * written_items)
+{
+    const gchar * value = tuple_get_string (tuple, field, NULL);
+
+    if (value == NULL)
+        return TRUE;
+
+    if (! ape_write_item (handle, key, value, written_length))
+        return FALSE;
+
+    (* written_items) ++;
+    return TRUE;
+}
+
+static gboolean write_integer_item (Tuple * tuple, int field, VFSFile * handle,
+ const gchar * key, int * written_length, int * written_items)
+{
+    gint value = tuple_get_int (tuple, field, NULL);
+    gchar scratch[32];
+
+    if (! value)
+        return TRUE;
+
+    snprintf (scratch, sizeof scratch, "%d", value);
+
+    if (! ape_write_item (handle, key, scratch, written_length))
+        return FALSE;
+
+    (* written_items) ++;
+    return TRUE;
+}
+
+static gboolean write_header (gint data_length, gint items, gboolean is_header,
+ VFSFile * handle)
+{
+    APEHeader header;
+
+    memcpy (header.magic, "APETAGEX", 8);
+    header.version = GUINT32_TO_LE (2000);
+    header.length = GUINT32_TO_LE (data_length + sizeof (APEHeader));
+    header.items = GUINT32_TO_LE (items);
+    header.flags = is_header ? GUINT32_TO_LE (APE_FLAG_HAS_HEADER |
+     APE_FLAG_IS_HEADER) : GUINT32_TO_LE (APE_FLAG_HAS_HEADER);
+    header.reserved = 0;
+
+    return vfs_fwrite (& header, 1, sizeof (APEHeader), handle) == sizeof
+     (APEHeader);
+}
+
+static gboolean ape_write_tag (Tuple * tuple, VFSFile * handle)
+{
+    GList * list = ape_read_tag (handle), * node;
+    APEHeader header;
+    gint start, length, data_start, data_length, items;
+
+    if (! ape_find_header (handle, & header, & start, & length, & data_start,
+     & data_length))
+        goto ERROR;
+
+    if (start + length != vfs_fsize (handle))
+    {
+        DEBUG ("Writing tags is only supported at end of file.\n");
+        goto ERROR;
+    }
+
+    if (vfs_truncate (handle, start) || vfs_fseek (handle, start, SEEK_SET) ||
+     ! write_header (0, 0, TRUE, handle))
+        goto ERROR;
+
+    length = 0;
+    items = 0;
+
+    if (! write_string_item (tuple, FIELD_ARTIST, handle, "Artist", & length,
+     & items) || ! write_string_item (tuple, FIELD_TITLE, handle, "Title",
+     & length, & items) || ! write_string_item (tuple, FIELD_ALBUM, handle,
+     "Album", & length, & items) || ! write_string_item (tuple, FIELD_COMMENT,
+     handle, "Comment", & length, & items) || ! write_string_item (tuple,
+     FIELD_GENRE, handle, "Genre", & length, & items) || ! write_integer_item
+     (tuple, FIELD_TRACK_NUMBER, handle, "Track", & length, & items) ||
+     ! write_integer_item (tuple, FIELD_YEAR, handle, "Date", & length, & items))
+        goto ERROR;
+
+    for (node = list; node != NULL; node = node->next)
+    {
+        gchar * key = ((ValuePair *) node->data)->key;
+        gchar * value = ((ValuePair *) node->data)->value;
+
+        if (! strcmp (key, "Artist") || ! strcmp (key, "Title") || ! strcmp
+         (key, "Album") || ! strcmp (key, "Comment") || ! strcmp (key, "Genre")
+         || ! strcmp (key, "Track") || ! strcmp (key, "Date"))
+            continue;
+
+        if (! ape_write_item (handle, key, value, & length))
+            goto ERROR;
+
+        items ++;
+    }
+
+    DEBUG ("Wrote %d items, %d bytes.\n", items, length);
+
+    if (write_header (length, items, FALSE, handle) || vfs_fseek (handle, start,
+     SEEK_SET) || ! write_header (length, items, TRUE, handle))
+        goto ERROR;
+
+    free_tag_list (list);
+    return TRUE;
+
+ERROR:
+    free_tag_list (list);
+    return FALSE;
+}
+
+const tag_module_t ape =
+{
+    .name = "APE",
+    .can_handle_file = ape_is_our_file,
+    .populate_tuple_from_file = ape_fill_tuple,
+    .write_tuple_to_file = ape_write_tag,
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/ape/ape.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 John Lindgren
+ *
+ * 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.
+ */
+
+#ifndef AUDTAG_APE_H
+#define AUDTAG_APE_H
+
+#include "../tag_module.h"
+
+extern const tag_module_t ape;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/audtag.c	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,52 @@
+/*
+ * 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 "audlegacy/tuple.h"
+#include "audtag.h"
+#include "tag_module.h"
+#include "util.h"
+
+void tag_init(void)
+{
+    init_tag_modules();
+}
+
+/* The tuple's file-related attributes are already set */
+
+gboolean tag_tuple_read (Tuple * tuple, VFSFile * fd)
+{
+    tag_module_t *mod = find_tag_module(fd);
+
+    if (mod == NULL)
+        return FALSE;
+
+    AUDDBG("Tag module %s has accepted %s\n", mod->name, fd->uri);
+    return mod->populate_tuple_from_file (tuple, fd) != NULL;
+}
+
+gboolean tag_tuple_write_to_file(Tuple * tuple, VFSFile * fd)
+{
+    tag_module_t *mod = find_tag_module(fd);
+
+    if (mod == NULL)
+        return FALSE;
+
+    return mod->write_tuple_to_file(tuple, fd);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/audtag.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+/* External Interface of the tagging library */
+
+#ifndef AUDTAG_H
+#define AUDTAG_H
+
+G_BEGIN_DECLS
+
+#include <glib.h>
+#include <mowgli.h>
+#include "audlegacy/tuple.h"
+#include "audlegacy/vfs.h"
+
+void tag_init(void);
+void tag_terminate(void);
+
+gboolean tag_tuple_read (Tuple * tuple, VFSFile *fd);
+gboolean tag_tuple_write_to_file(Tuple *tuple, VFSFile *fd);
+
+G_END_DECLS
+#endif /* AUDTAG_H */
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/id3/frame.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#ifndef AUD_ID3_FRAME
+#define AUD_ID3_FRAME
+
+#include <glib-2.0/glib.h>
+
+enum {
+    ID3_ALBUM = 0,
+    ID3_TITLE,
+    ID3_COMPOSER,
+    ID3_COPYRIGHT,
+    ID3_DATE,
+    ID3_TIME,
+    ID3_LENGTH,
+    ID3_ARTIST,
+    ID3_TRACKNR,
+    ID3_YEAR,
+    ID3_GENRE,
+    ID3_COMMENT,
+    ID3_PRIVATE,
+    ID3_ENCODER,
+    ID3_RECORDING_TIME,
+    ID3_TXXX,
+    ID3_TAGS_NO
+};
+
+char * id3_frames[] = {"TALB","TIT2","TCOM", "TCOP", "TDAT", "TIME", "TLEN",
+"TPE1", "TRCK", "TYER","TCON", "COMM", "PRIV", "TSSE", "TDRC", "TXXX"};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/id3/id3v1.c	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2010 Tony Vroon
+ *
+ * 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 "id3v1.h"
+#include "../util.h"
+#include <inttypes.h>
+#include "../tag_module.h"
+#include "../../audlegacy/strings.h"
+
+static gboolean has_id3v1_ext;
+
+gboolean id3v1_can_handle_file(VFSFile *f)
+{
+    gchar *tag = g_new0(gchar, 4);
+
+    vfs_fseek(f, -355, SEEK_END);
+    tag = read_char_data(f, 4);
+    if (!strncmp(tag, "TAG+", 4))
+        has_id3v1_ext = TRUE;
+    else
+        has_id3v1_ext = FALSE;
+
+    vfs_fseek(f, -128, SEEK_END);
+    tag = read_char_data(f, 3);
+    if (!strncmp(tag, "TAG", 3))
+    {
+        g_free(tag);
+        return TRUE;
+    }
+
+    g_free(tag);
+    return FALSE;
+}
+
+static gchar *convert_to_utf8(gchar *str)
+{
+    return g_strchomp(str_to_utf8(str));
+}
+
+Tuple *id3v1_populate_tuple_from_file(Tuple *tuple, VFSFile *f)
+{
+    gchar *title = g_new0(gchar, 30);
+    gchar *artist = g_new0(gchar, 30);
+    gchar *album = g_new0(gchar, 30);
+    gchar *year = g_new0(gchar, 4);
+    gchar *comment = g_new0(gchar, 30);
+    gchar *track = g_new0(gchar, 1);
+    gchar *genre = g_new0(gchar, 1);
+    gboolean genre_set = FALSE;
+    vfs_fseek(f, -125, SEEK_END);
+    title = read_char_data(f, 30);
+    artist = read_char_data(f, 30);
+    album = read_char_data(f, 30);
+    year = read_char_data(f, 4);
+    comment = read_char_data(f, 30);
+    genre = read_char_data(f, 1);
+
+    if (comment[28] == 0 && comment[29] != 0)
+    {
+        *track = comment[29];
+    }
+
+    title = convert_to_utf8(title);
+    artist = convert_to_utf8(artist);
+    album = convert_to_utf8(album);
+    comment = convert_to_utf8(comment);
+
+    if (has_id3v1_ext)
+    {
+        vfs_fseek(f, -351, SEEK_END);
+        gchar *tmp_title = g_strconcat(title, convert_to_utf8(read_char_data(f, 60)), NULL);
+        gchar *tmp_artist = g_strconcat(artist, convert_to_utf8(read_char_data(f, 60)), NULL);
+        gchar *tmp_album = g_strconcat(album, convert_to_utf8(read_char_data(f, 60)), NULL);
+        vfs_fseek(f, -170, SEEK_END);
+        gchar *tmp_genre = g_new0(gchar, 30);
+        tmp_genre = convert_to_utf8(read_char_data(f, 30));
+        g_free(title);
+        g_free(artist);
+        g_free(album);
+        title = tmp_title;
+        artist = tmp_artist;
+        album = tmp_album;
+
+        if (g_strcmp0(tmp_genre, NULL) == 1)
+        {
+            tuple_associate_string(tuple, FIELD_GENRE, NULL, tmp_genre);
+            genre_set = TRUE;
+        }
+
+        g_free(tmp_genre);
+    }
+
+    tuple_associate_string(tuple, FIELD_TITLE, NULL, title);
+    tuple_associate_string(tuple, FIELD_ARTIST, NULL, artist);
+    tuple_associate_string(tuple, FIELD_ALBUM, NULL, album);
+    tuple_associate_int(tuple, FIELD_YEAR, NULL, atoi(year));
+    tuple_associate_string(tuple, FIELD_COMMENT, NULL, comment);
+    tuple_associate_int(tuple, FIELD_TRACK_NUMBER, NULL, *track);
+    if (!genre_set) tuple_associate_string(tuple, FIELD_GENRE, NULL, convert_numericgenre_to_text(*genre));
+
+    g_free(title);
+    g_free(artist);
+    g_free(album);
+    g_free(year);
+    g_free(comment);
+    g_free(track);
+    g_free(genre);
+    return tuple;
+}
+
+gboolean id3v1_write_tuple_to_file(Tuple * tuple, VFSFile * f)
+{
+    return FALSE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/id3/id3v1.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 Tony Vroon
+ *
+ * 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.
+ */
+
+#ifndef ID3V1_H
+
+#define ID3V1_H
+
+#include <audlegacy/tuple.h>
+#include <audlegacy/vfs.h>
+#include "../tag_module.h"
+
+/* TAG plugin API */
+gboolean id3v1_can_handle_file(VFSFile *f);
+Tuple *id3v1_populate_tuple_from_file(Tuple *tuple,VFSFile *f);
+gboolean id3v1_write_tuple_to_file(Tuple* tuple, VFSFile *f);
+
+static const tag_module_t id3v1 = {
+    .name = "ID3v1",
+    .can_handle_file = id3v1_can_handle_file,
+    .populate_tuple_from_file = id3v1_populate_tuple_from_file,
+    .write_tuple_to_file = id3v1_write_tuple_to_file,
+};
+#endif
--- /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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/id3/id3v2.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+#ifndef ID3_H
+
+#define ID3_H
+
+#include <audlegacy/tuple.h>
+#include <audlegacy/vfs.h>
+#include "../tag_module.h"
+
+
+typedef struct id3v2
+{
+    gchar *id3;
+    guint16 version;
+    gchar  flags;
+    guint32 size;
+} ID3v2Header;
+
+typedef struct extHeader
+{
+    guint32 header_size;
+    guint16 flags;
+    guint32 padding_size;
+}ExtendedHeader;
+
+typedef struct frameheader
+{
+    gchar* frame_id;
+    guint32 size;
+    guint16 flags;
+}ID3v2FrameHeader;
+
+typedef struct genericframe
+{
+    ID3v2FrameHeader *header;
+    gchar*           frame_body;
+}GenericFrame;
+
+guint32 read_syncsafe_int32(VFSFile *fd);
+
+ID3v2Header *readHeader(VFSFile *fd);
+
+ExtendedHeader *readExtendedHeader(VFSFile *fd);
+
+ID3v2FrameHeader *readID3v2FrameHeader(VFSFile *fd);
+
+gchar* readFrameBody(VFSFile *fd,int size);
+
+GenericFrame *readGenericFrame(VFSFile *fd,GenericFrame *gf);
+
+void readAllFrames(VFSFile *fd,int framesSize);
+
+void write_int32(VFSFile *fd, guint32 val);
+
+void  write_syncsafe_int32(VFSFile *fd, guint32 val);
+
+void write_ASCII(VFSFile *fd, int size, gchar* value);
+
+void write_utf8(VFSFile *fd, int size,gchar* value);
+
+guint32 writeAllFramesToFile(VFSFile *fd);
+
+void writeID3HeaderToFile(VFSFile *fd,ID3v2Header *header);
+
+void writePaddingToFile(VFSFile *fd, int ksize);
+
+void writeID3FrameHeaderToFile(VFSFile *fd, ID3v2FrameHeader *header);
+
+void writeGenericFrame(VFSFile *fd,GenericFrame *frame);
+
+gboolean isExtendedHeader(ID3v2Header *header);
+
+int getFrameID(ID3v2FrameHeader *header);
+
+void skipFrame(VFSFile *fd, guint32 size);
+
+gboolean isValidFrame(GenericFrame *frame);
+
+void add_newISO8859_1FrameFromString(const gchar *value,int id3_field);
+
+void add_newFrameFromTupleStr(Tuple *tuple, int field,int id3_field);
+
+void add_newFrameFromTupleInt(Tuple *tuple,int field,int id3_field);
+
+void add_frameFromTupleStr(Tuple *tuple, int field,int id3_field);
+
+void add_frameFromTupleInt(Tuple *tuple, int field,int id3_field);
+
+mowgli_dictionary_t *frames ;
+mowgli_list_t *frameIDs;
+
+/* TAG plugin API */
+gboolean id3v2_can_handle_file(VFSFile *f);
+Tuple *id3v2_populate_tuple_from_file(Tuple *tuple,VFSFile *f);
+gboolean id3v2_write_tuple_to_file(Tuple* tuple, VFSFile *f);
+
+static const tag_module_t id3v2 = {
+    .name = "ID3v2",
+    .can_handle_file = id3v2_can_handle_file,
+    .populate_tuple_from_file = id3v2_populate_tuple_from_file,
+    .write_tuple_to_file = id3v2_write_tuple_to_file,
+};
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/tag_module.c	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,55 @@
+/*
+ * 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 <audlegacy/tuple.h>
+#include <audlegacy/vfs.h>
+#include "util.h"
+#include "tag_module.h"
+#include "wma/module.h"
+#include "id3/id3v1.h"
+#include "id3/id3v2.h"
+#include "ape/ape.h"
+#include "aac/aac.h"
+
+void init_tag_modules(void)
+{
+    mowgli_node_add((void *)&wma, mowgli_node_create(), &tag_modules);
+    mowgli_node_add((void *)&id3v2, mowgli_node_create(), &tag_modules);
+    mowgli_node_add((void *)&ape, mowgli_node_create(), &tag_modules);
+    mowgli_node_add((void *)&id3v1, mowgli_node_create(), &tag_modules);
+/*
+    mowgli_node_add((void *)&aac, mowgli_node_create(), &tag_modules);
+*/
+}
+
+tag_module_t *find_tag_module(VFSFile * fd)
+{
+    mowgli_node_t *mod, *tmod;
+    MOWGLI_LIST_FOREACH_SAFE(mod, tmod, tag_modules.head)
+    {
+        vfs_fseek(fd, 0, SEEK_SET);
+        if (((tag_module_t *) (mod->data))->can_handle_file(fd))
+            return (tag_module_t *) (mod->data);
+    }
+
+    AUDDBG("no module found\n");
+    return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/tag_module.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+/* Interface of the tagging library */
+
+#ifndef TAG_MODULE_H
+#define TAG_MODULE_H
+
+G_BEGIN_DECLS
+
+#include <glib.h>
+#include <mowgli.h>
+#include "audlegacy/tuple.h"
+#include "audlegacy/vfs.h"
+
+mowgli_list_t tag_modules;
+int number_of_modules;
+typedef Tuple* pTuple;
+
+typedef struct _module {
+    gchar *name;
+    gboolean(*can_handle_file) (VFSFile *fd);
+    pTuple(*populate_tuple_from_file)(Tuple *tuple, VFSFile* fd);
+    gboolean(*write_tuple_to_file) (Tuple * tuple, VFSFile *fd);
+} tag_module_t;
+
+/* this function must be modified when including new modules */
+void init_tag_modules(void);
+
+tag_module_t *find_tag_module(VFSFile *fd);
+
+G_END_DECLS
+#endif /* TAG_MODULE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/util.c	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,446 @@
+/*
+ * 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 "util.h"
+#include <inttypes.h>
+
+/* convert windows time to unix time */
+time_t unix_time(guint64 win_time)
+{
+    guint64 t = (guint64) ((win_time / 10000000LL) - 11644473600LL);
+    return (time_t) t;
+}
+
+guint16 get_year(guint64 win_time)
+{
+    GDate *d = g_date_new();
+    g_date_set_time_t(d, unix_time(win_time));
+    guint16 year = g_date_get_year(d);
+    g_date_free(d);
+    return year;
+}
+
+Tuple *makeTuple(Tuple * tuple, const gchar * title, const gchar * artist, const gchar * comment, const gchar * album, const gchar * genre, const gchar * year, const gchar * filePath, int tracnr)
+{
+
+    tuple_associate_string(tuple, FIELD_ARTIST, NULL, artist);
+    tuple_associate_string(tuple, FIELD_TITLE, NULL, title);
+    tuple_associate_string(tuple, FIELD_COMMENT, NULL, comment);
+    tuple_associate_string(tuple, FIELD_ALBUM, NULL, album);
+    tuple_associate_string(tuple, FIELD_GENRE, NULL, genre);
+    tuple_associate_string(tuple, FIELD_YEAR, NULL, year);
+    tuple_associate_int(tuple, FIELD_TRACK_NUMBER, NULL, tracnr);
+    tuple_associate_string(tuple, FIELD_FILE_PATH, NULL, filePath);
+    return tuple;
+}
+
+const gchar *get_complete_filepath(Tuple * tuple)
+{
+    const gchar *filepath;
+    const gchar *dir;
+    const gchar *file;
+
+    dir = tuple_get_string(tuple, FIELD_FILE_PATH, NULL);
+    file = tuple_get_string(tuple, FIELD_FILE_NAME, NULL);
+    filepath = g_strdup_printf("%s/%s", dir, file);
+    AUDDBG("file path = %s\n", filepath);
+    return filepath;
+}
+
+void print_tuple(Tuple * tuple)
+{
+#if WMA_DEBUG
+    AUDDBG("--------------TUPLE PRINT --------------------\n");
+    const gchar *title = tuple_get_string(tuple, FIELD_TITLE, NULL);
+    AUDDBG("title = %s\n", title);
+    /* artist */
+    const gchar *artist = tuple_get_string(tuple, FIELD_ARTIST, NULL);
+    AUDDBG("artist = %s\n", artist);
+
+    /* copyright */
+    const gchar *copyright = tuple_get_string(tuple, FIELD_COPYRIGHT, NULL);
+    AUDDBG("copyright = %s\n", copyright);
+
+    /* comment / description */
+
+    const gchar *comment = tuple_get_string(tuple, FIELD_COMMENT, NULL);
+    AUDDBG("comment = %s\n", comment);
+
+    /* codec name */
+    const gchar *codec_name = tuple_get_string(tuple, FIELD_CODEC, NULL);
+    AUDDBG("codec = %s\n", codec_name);
+
+    /* album */
+    const gchar *album = tuple_get_string(tuple, FIELD_ALBUM, NULL);
+    AUDDBG("Album = %s\n", album);
+
+    /*track number */
+    gint track_nr = tuple_get_int(tuple, FIELD_TRACK_NUMBER, NULL);
+    AUDDBG("Track nr = %d\n", track_nr);
+
+    /* genre */
+    const gchar *genre = tuple_get_string(tuple, FIELD_GENRE, NULL);
+    AUDDBG("Genre = %s \n", genre);
+
+    /* length */
+    gint length = tuple_get_int(tuple, FIELD_LENGTH, NULL);
+    AUDDBG("Length = %d\n", length);
+
+    /* year */
+    gint year = tuple_get_int(tuple, FIELD_YEAR, NULL);
+    AUDDBG("Year = %d\n", year);
+
+    /* quality */
+    const gchar *quality = tuple_get_string(tuple, FIELD_QUALITY, NULL);
+    AUDDBG("quality = %s\n", quality);
+
+    /* path */
+    const gchar *path = tuple_get_string(tuple, FIELD_FILE_PATH, NULL);
+    AUDDBG("path = %s\n", path);
+
+    /* filename */
+    const gchar *filename = tuple_get_string(tuple, FIELD_FILE_NAME, NULL);
+    AUDDBG("filename = %s\n", filename);
+
+    AUDDBG("-----------------END---------------------\n");
+#endif
+}
+
+void seek(VFSFile * f, long pos)
+{
+
+    vfs_fseek(f, pos, SEEK_SET);
+}
+
+void skip(VFSFile * f, int amount)
+{
+    vfs_fseek(f, amount, SEEK_CUR);
+}
+
+gchar *read_char_data(VFSFile * fd, int size)
+{
+    gchar *value = g_new0(gchar, size);
+    vfs_fread(value, size, 1, fd);
+    return value;
+}
+
+gboolean write_char_data(VFSFile * f, gchar * data, size_t i)
+{
+    return (vfs_fwrite(data, i, 1, f) == i);
+}
+
+gchar *utf8(gunichar2 * s)
+{
+    g_return_val_if_fail(s != NULL, NULL);
+    return g_utf16_to_utf8(s, -1, NULL, NULL, NULL);
+}
+
+gunichar2 *fread_utf16(VFSFile * f, guint64 size)
+{
+    gunichar2 *p = (gunichar2 *) g_malloc0(size);
+    if (vfs_fread(p, 1, size, f) != size)
+    {
+        g_free(p);
+        p = NULL;
+    }
+    gchar *s = utf8(p);
+    AUDDBG("Converted to UTF8: '%s'\n", s);
+    g_free(s);
+    return p;
+}
+
+gboolean write_utf16(VFSFile * f, gunichar2 * data, size_t i)
+{
+    return (vfs_fwrite(data, i, 1, f) == i);
+}
+
+guint8 read_uint8(VFSFile * fd)
+{
+    guint16 i;
+    if (vfs_fread(&i, 1, 1, fd) == 1)
+    {
+        return i;
+    }
+    return -1;
+}
+
+guint16 read_LEuint16(VFSFile * fd)
+{
+    guint16 a;
+    if (vfs_fget_le16(&a, fd))
+        return a;
+    else
+        return -1;
+}
+
+guint16 read_BEuint16(VFSFile * fd)
+{
+    guint16 a;
+    if (vfs_fget_be16(&a, fd))
+        return a;
+    else
+        return -1;
+}
+
+guint32 read_LEuint32(VFSFile * fd)
+{
+    guint32 a;
+    if (vfs_fget_le32(&a, fd))
+        return a;
+    else
+        return -1;
+}
+
+guint32 read_BEuint32(VFSFile * fd)
+{
+    guint32 a;
+    if (vfs_fget_be32(&a, fd))
+        return a;
+    else
+        return -1;
+}
+
+guint64 read_LEuint64(VFSFile * fd)
+{
+    guint64 a;
+    if (vfs_fget_le64(&a, fd))
+        return a;
+    else
+        return -1;
+}
+
+guint64 read_BEuint64(VFSFile * fd)
+{
+    guint64 a;
+    if (vfs_fget_be64(&a, fd))
+        return a;
+    else
+        return 1;
+}
+
+gboolean write_uint8(VFSFile * fd, guint8 val)
+{
+    return (vfs_fwrite(&val, 1, 1, fd) == 1);
+}
+
+gboolean write_LEuint16(VFSFile * fd, guint16 val)
+{
+    guint16 le_val = GUINT32_TO_LE(val);
+    return (vfs_fwrite(&le_val, 2, 1, fd) == 2);
+}
+
+gboolean write_BEuint32(VFSFile * fd, guint32 val)
+{
+    guint32 be_val = GUINT32_TO_BE(val);
+    return (vfs_fwrite(&be_val, 4, 1, fd) == 4);
+}
+
+gboolean write_LEuint32(VFSFile * fd, guint32 val)
+{
+    guint32 le_val = GUINT32_TO_LE(val);
+    return (vfs_fwrite(&le_val, 4, 1, fd) == 4);
+}
+
+gboolean write_LEuint64(VFSFile * fd, guint64 val)
+{
+    guint64 le_val = GUINT64_TO_LE(val);
+    return (vfs_fwrite(&le_val, 8, 1, fd) == 8);
+}
+
+void copyAudioToFile(VFSFile * from, VFSFile * to, guint32 pos)
+{
+    vfs_fseek(from, pos, SEEK_SET);
+    while (vfs_feof(from) == 0)
+    {
+        gchar buf[4096];
+        gint n = vfs_fread(buf, 1, 4096, from);
+        vfs_fwrite(buf, n, 1, to);
+    }
+}
+
+void copyAudioData(VFSFile * from, VFSFile * to, guint32 pos_from, guint32 pos_to)
+{
+    vfs_fseek(from, pos_from, SEEK_SET);
+    int bytes_read = pos_from;
+    while (bytes_read < pos_to - 4096)
+    {
+        gchar buf[4096];
+        guint32 n = vfs_fread(buf, 1, 4096, from);
+        vfs_fwrite(buf, n, 1, to);
+        bytes_read += n;
+    }
+    if (bytes_read < pos_to)
+    {
+        guint32 buf_size = pos_to - bytes_read;
+        gchar buf2[buf_size];
+        int nn = vfs_fread(buf2, 1, buf_size, from);
+        vfs_fwrite(buf2, nn, 1, to);
+    }
+}
+
+gchar *convert_numericgenre_to_text(gint numericgenre)
+{
+    const struct
+    {
+        gint numericgenre;
+        gchar *genre;
+    }
+    table[] =
+    {
+        {GENRE_BLUES, "Blues"},
+        {GENRE_CLASSIC_ROCK, "Classic Rock"},
+        {GENRE_COUNTRY, "Country"},
+        {GENRE_DANCE, "Dance"},
+        {GENRE_DISCO, "Disco"},
+        {GENRE_FUNK, "Funk"},
+        {GENRE_GRUNGE, "Grunge"},
+        {GENRE_HIPHOP, "Hip-Hop"},
+        {GENRE_JAZZ, "Jazz"},
+        {GENRE_METAL, "Metal"},
+        {GENRE_NEW_AGE, "New Age"},
+        {GENRE_OLDIES, "Oldies"},
+        {GENRE_OTHER, "Other"},
+        {GENRE_POP, "Pop"},
+        {GENRE_R_B, "R&B"},
+        {GENRE_RAP, "Rap"},
+        {GENRE_REGGAE, "Reggae"},
+        {GENRE_ROCK, "Rock"},
+        {GENRE_TECHNO, "Techno"},
+        {GENRE_INDUSTRIAL, "Industrial"},
+        {GENRE_ALTERNATIVE, "Alternative"},
+        {GENRE_SKA, "Ska"},
+        {GENRE_DEATH_METAL, "Death Metal"},
+        {GENRE_PRANKS, "Pranks"},
+        {GENRE_SOUNDTRACK, "Soundtrack"},
+        {GENRE_EURO_TECHNO, "Euro-Techno"},
+        {GENRE_AMBIENT, "Ambient"},
+        {GENRE_TRIP_HOP, "Trip-Hop"},
+        {GENRE_VOCAL, "Vocal"},
+        {GENRE_JAZZ_FUNK, "Jazz+Funk"},
+        {GENRE_FUSION, "Fusion"},
+        {GENRE_TRANCE, "Trance"},
+        {GENRE_CLASSICAL, "Classical"},
+        {GENRE_INSTRUMENTAL, "Instrumental"},
+        {GENRE_ACID, "Acid"},
+        {GENRE_HOUSE, "House"},
+        {GENRE_GAME, "Game"},
+        {GENRE_SOUND_CLIP, "Sound Clip"},
+        {GENRE_GOSPEL, "Gospel"},
+        {GENRE_NOISE, "Noise"},
+        {GENRE_ALTERNROCK, "AlternRock"},
+        {GENRE_BASS, "Bass"},
+        {GENRE_SOUL, "Soul"},
+        {GENRE_PUNK, "Punk"},
+        {GENRE_SPACE, "Space"},
+        {GENRE_MEDITATIVE, "Meditative"},
+        {GENRE_INSTRUMENTAL_POP, "Instrumental Pop"},
+        {GENRE_INSTRUMENTAL_ROCK, "Instrumental Rock"},
+        {GENRE_ETHNIC, "Ethnic"},
+        {GENRE_GOTHIC, "Gothic"},
+        {GENRE_DARKWAVE, "Darkwave"},
+        {GENRE_TECHNO_INDUSTRIAL, "Techno-Industrial"},
+        {GENRE_ELECTRONIC, "Electronic"},
+        {GENRE_POP_FOLK, "Pop-Folk"},
+        {GENRE_EURODANCE, "Eurodance"},
+        {GENRE_DREAM, "Dream"},
+        {GENRE_SOUTHERN_ROCK, "Southern Rock"},
+        {GENRE_COMEDY, "Comedy"},
+        {GENRE_CULT, "Cult"},
+        {GENRE_GANGSTA, "Gangsta"},
+        {GENRE_TOP40, "Top 40"},
+        {GENRE_CHRISTIAN_RAP, "Christian Rap"},
+        {GENRE_POP_FUNK, "Pop/Funk"},
+        {GENRE_JUNGLE, "Jungle"},
+        {GENRE_NATIVE_AMERICAN, "Native American"},
+        {GENRE_CABARET, "Cabaret"},
+        {GENRE_NEW_WAVE, "New Wave"},
+        {GENRE_PSYCHADELIC, "Psychadelic"},
+        {GENRE_RAVE, "Rave"},
+        {GENRE_SHOWTUNES, "Showtunes"},
+        {GENRE_TRAILER, "Trailer"},
+        {GENRE_LO_FI, "Lo-Fi"},
+        {GENRE_TRIBAL, "Tribal"},
+        {GENRE_ACID_PUNK, "Acid Punk"},
+        {GENRE_ACID_JAZZ, "Acid Jazz"},
+        {GENRE_POLKA, "Polka"},
+        {GENRE_RETRO, "Retro"},
+        {GENRE_MUSICAL, "Musical"},
+        {GENRE_ROCK_ROLL, "Rock & Roll"},
+        {GENRE_HARD_ROCK, "Hard Rock"},
+        {GENRE_FOLK, "Folk"},
+        {GENRE_FOLK_ROCK, "Folk-Rock"},
+        {GENRE_NATIONAL_FOLK, "National Folk"},
+        {GENRE_SWING, "Swing"},
+        {GENRE_FAST_FUSION, "Fast Fusion"},
+        {GENRE_BEBOB, "Bebob"},
+        {GENRE_LATIN, "Latin"},
+        {GENRE_REVIVAL, "Revival"},
+        {GENRE_CELTIC, "Celtic"},
+        {GENRE_BLUEGRASS, "Bluegrass"},
+        {GENRE_AVANTGARDE, "Avantgarde"},
+        {GENRE_GOTHIC_ROCK, "Gothic Rock"},
+        {GENRE_PROGRESSIVE_ROCK, "Progressive Rock"},
+        {GENRE_PSYCHEDELIC_ROCK, "Psychedelic Rock"},
+        {GENRE_SYMPHONIC_ROCK, "Symphonic Rock"},
+        {GENRE_SLOW_ROCK, "Slow Rock"},
+        {GENRE_BIG_BAND, "Big Band"},
+        {GENRE_CHORUS, "Chorus"},
+        {GENRE_EASY_LISTENING, "Easy Listening"},
+        {GENRE_ACOUSTIC, "Acoustic"},
+        {GENRE_HUMOUR, "Humour"},
+        {GENRE_SPEECH, "Speech"},
+        {GENRE_CHANSON, "Chanson"},
+        {GENRE_OPERA, "Opera"},
+        {GENRE_CHAMBER_MUSIC, "Chamber Music"},
+        {GENRE_SONATA, "Sonata"},
+        {GENRE_SYMPHONY, "Symphony"},
+        {GENRE_BOOTY_BASS, "Booty Bass"},
+        {GENRE_PRIMUS, "Primus"},
+        {GENRE_PORN_GROOVE, "Porn Groove"},
+        {GENRE_SATIRE, "Satire"},
+        {GENRE_SLOW_JAM, "Slow Jam"},
+        {GENRE_CLUB, "Club"},
+        {GENRE_TANGO, "Tango"},
+        {GENRE_SAMBA, "Samba"},
+        {GENRE_FOLKLORE, "Folklore"},
+        {GENRE_BALLAD, "Ballad"},
+        {GENRE_POWER_BALLAD, "Power Ballad"},
+        {GENRE_RHYTHMIC_SOUL, "Rhythmic Soul"},
+        {GENRE_FREESTYLE, "Freestyle"},
+        {GENRE_DUET, "Duet"},
+        {GENRE_PUNK_ROCK, "Punk Rock"},
+        {GENRE_DRUM_SOLO, "Drum Solo"},
+        {GENRE_A_CAPELLA, "A capella"},
+        {GENRE_EURO_HOUSE, "Euro-House"},
+    };
+
+    gint count;
+
+    for (count = 0; count < G_N_ELEMENTS(table); count++)
+    {
+        if (table[count].numericgenre == numericgenre)
+        {
+             return table[count].genre;
+        }
+    }
+
+    return "Unknown";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/util.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,211 @@
+/*
+ * 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.
+ */
+
+#ifndef TAGUTIL_H
+
+#define TAGUTIL_H
+
+#include <glib.h>
+#include "audlegacy/tuple.h"
+#include "audlegacy/vfs.h"
+
+#define WMA_DEBUG 1
+
+#define BROKEN 1
+
+enum {
+    GENRE_BLUES = 0,
+    GENRE_CLASSIC_ROCK,
+    GENRE_COUNTRY,
+    GENRE_DANCE,
+    GENRE_DISCO,
+    GENRE_FUNK,
+    GENRE_GRUNGE,
+    GENRE_HIPHOP,
+    GENRE_JAZZ,
+    GENRE_METAL,
+    GENRE_NEW_AGE,
+    GENRE_OLDIES,
+    GENRE_OTHER,
+    GENRE_POP,
+    GENRE_R_B,
+    GENRE_RAP,
+    GENRE_REGGAE,
+    GENRE_ROCK,
+    GENRE_TECHNO,
+    GENRE_INDUSTRIAL,
+    GENRE_ALTERNATIVE,
+    GENRE_SKA,
+    GENRE_DEATH_METAL,
+    GENRE_PRANKS,
+    GENRE_SOUNDTRACK,
+    GENRE_EURO_TECHNO,
+    GENRE_AMBIENT,
+    GENRE_TRIP_HOP,
+    GENRE_VOCAL,
+    GENRE_JAZZ_FUNK,
+    GENRE_FUSION,
+    GENRE_TRANCE,
+    GENRE_CLASSICAL,
+    GENRE_INSTRUMENTAL,
+    GENRE_ACID,
+    GENRE_HOUSE,
+    GENRE_GAME,
+    GENRE_SOUND_CLIP,
+    GENRE_GOSPEL,
+    GENRE_NOISE,
+    GENRE_ALTERNROCK,
+    GENRE_BASS,
+    GENRE_SOUL,
+    GENRE_PUNK,
+    GENRE_SPACE,
+    GENRE_MEDITATIVE,
+    GENRE_INSTRUMENTAL_POP,
+    GENRE_INSTRUMENTAL_ROCK,
+    GENRE_ETHNIC,
+    GENRE_GOTHIC,
+    GENRE_DARKWAVE,
+    GENRE_TECHNO_INDUSTRIAL,
+    GENRE_ELECTRONIC,
+    GENRE_POP_FOLK,
+    GENRE_EURODANCE,
+    GENRE_DREAM,
+    GENRE_SOUTHERN_ROCK,
+    GENRE_COMEDY,
+    GENRE_CULT,
+    GENRE_GANGSTA,
+    GENRE_TOP40,
+    GENRE_CHRISTIAN_RAP,
+    GENRE_POP_FUNK,
+    GENRE_JUNGLE,
+    GENRE_NATIVE_AMERICAN,
+    GENRE_CABARET,
+    GENRE_NEW_WAVE,
+    GENRE_PSYCHADELIC,
+    GENRE_RAVE,
+    GENRE_SHOWTUNES,
+    GENRE_TRAILER,
+    GENRE_LO_FI,
+    GENRE_TRIBAL,
+    GENRE_ACID_PUNK,
+    GENRE_ACID_JAZZ,
+    GENRE_POLKA,
+    GENRE_RETRO,
+    GENRE_MUSICAL,
+    GENRE_ROCK_ROLL,
+    GENRE_HARD_ROCK,
+    GENRE_FOLK,
+    GENRE_FOLK_ROCK,
+    GENRE_NATIONAL_FOLK,
+    GENRE_SWING,
+    GENRE_FAST_FUSION,
+    GENRE_BEBOB,
+    GENRE_LATIN,
+    GENRE_REVIVAL,
+    GENRE_CELTIC,
+    GENRE_BLUEGRASS,
+    GENRE_AVANTGARDE,
+    GENRE_GOTHIC_ROCK,
+    GENRE_PROGRESSIVE_ROCK,
+    GENRE_PSYCHEDELIC_ROCK,
+    GENRE_SYMPHONIC_ROCK,
+    GENRE_SLOW_ROCK,
+    GENRE_BIG_BAND,
+    GENRE_CHORUS,
+    GENRE_EASY_LISTENING,
+    GENRE_ACOUSTIC,
+    GENRE_HUMOUR,
+    GENRE_SPEECH,
+    GENRE_CHANSON,
+    GENRE_OPERA,
+    GENRE_CHAMBER_MUSIC,
+    GENRE_SONATA,
+    GENRE_SYMPHONY,
+    GENRE_BOOTY_BASS,
+    GENRE_PRIMUS,
+    GENRE_PORN_GROOVE,
+    GENRE_SATIRE,
+    GENRE_SLOW_JAM,
+    GENRE_CLUB,
+    GENRE_TANGO,
+    GENRE_SAMBA,
+    GENRE_FOLKLORE,
+    GENRE_BALLAD,
+    GENRE_POWER_BALLAD,
+    GENRE_RHYTHMIC_SOUL,
+    GENRE_FREESTYLE,
+    GENRE_DUET,
+    GENRE_PUNK_ROCK,
+    GENRE_DRUM_SOLO,
+    GENRE_A_CAPELLA,
+    GENRE_EURO_HOUSE
+};
+
+time_t unix_time(guint64 win_time);
+
+guint16 get_year(guint64 win_time);
+
+void print_tuple(Tuple *tuple);
+
+//Tuple *makeTuple(Tuple *tuple, const gchar* title, const gchar* artist,
+//        const gchar* comment, const gchar* album,
+//        const gchar * genre, const gchar* year,
+//        const gchar* filePath, int tracnr);
+
+gchar *utf8(gunichar2* s);
+const gchar* get_complete_filepath(Tuple *tuple);
+
+gchar *read_char_data(VFSFile *fd, int size);
+gboolean write_char_data(VFSFile *f, gchar *data, size_t i);
+
+gunichar2 *fread_utf16(VFSFile* f, guint64 size);
+gboolean write_utf16(VFSFile *f, gunichar2 *data, size_t i);
+
+guint8 read_uint8(VFSFile *fd);
+guint16 read_LEuint16(VFSFile *fd);
+guint16 read_BEuint16(VFSFile *fd);
+guint32 read_LEuint32(VFSFile *fd);
+guint32 read_BEuint32(VFSFile *fd);
+guint64 read_LEuint64(VFSFile *fd);
+guint64 read_BEuint64(VFSFile *fd);
+
+
+gboolean write_uint8(VFSFile *fd, guint8 val);
+gboolean write_BEuint16(VFSFile *fd, guint16 val);
+gboolean write_LEuint16(VFSFile *fd, guint16 val);
+gboolean write_BEuint32(VFSFile *fd, guint32 val);
+gboolean write_LEuint32(VFSFile *fd, guint32 val);
+gboolean write_BEuint64(VFSFile *fd, guint64 val);
+gboolean write_LEuint64(VFSFile *fd, guint64 val);
+
+guint64 read_LEint64(VFSFile *fd);
+void copyAudioToFile(VFSFile *from, VFSFile *to, guint32 pos);
+void copyAudioData(VFSFile* from, VFSFile *to, guint32 pos_from, guint32 pos_to);
+
+gchar *convert_numericgenre_to_text(gint numericgenre);
+
+/* macro for debug print */
+#ifdef WMA_DEBUG
+#  define AUDDBG(...) do { g_print("%s:%d %s(): ", __FILE__, (int)__LINE__, __FUNCTION__); g_print(__VA_ARGS__); } while (0)
+#else
+#  define AUDDBG(...) do { } while (0)
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/wma/guid.c	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+/* this stuff may be moved to ../util.h if needed by other formats */
+
+#include <inttypes.h>
+
+#include "audlegacy/vfs.h"
+#include "../util.h"
+#include "guid.h"
+#include "wma_fmt.h"
+
+GUID *guid_read_from_file(VFSFile * f)
+{
+    GUID temp;
+
+    if ((f == NULL) || (vfs_fread(&temp, sizeof(GUID), 1, f) != 1))
+        return NULL;
+
+    temp.le32 = GUINT32_FROM_LE(temp.le32);
+    temp.le16_1 = GUINT16_FROM_LE(temp.le16_1);
+    temp.le16_2 = GUINT16_FROM_LE(temp.le16_2);
+    temp.be64 = GUINT64_FROM_BE(temp.be64);
+
+    return g_memdup(&temp, sizeof(GUID));
+}
+
+gboolean guid_write_to_file(VFSFile * f, int guid_type)
+{
+    g_return_val_if_fail(f != NULL, FALSE);
+
+    GUID *g = guid_convert_from_string(wma_guid_map(guid_type));
+
+    gboolean ret = write_LEuint32(f, g->le32) && write_LEuint16(f, g->le16_1) && write_LEuint16(f, g->le16_1) && write_LEuint64(f, g->be64);
+    g_free(g);
+    return ret;
+}
+
+GUID *guid_convert_from_string(const gchar * string)
+{
+    GUID temp;
+
+    if (sscanf(string, "%" SCNx32 "-%" SCNx16 "-%" SCNx16 "-%" SCNx64, &temp.le32, &temp.le16_1, &temp.le16_2, &temp.be64) != 4)
+        return NULL;
+    return g_memdup(&temp, sizeof(GUID));
+}
+
+gchar *guid_convert_to_string(const GUID * g)
+{
+
+    return g_strdup_printf("%8x-%hx-%hx-%" PRIx64 "\n", GUINT32_TO_LE(g->le32), GUINT16_TO_LE(g->le16_1), GUINT16_TO_LE(g->le16_2), GUINT64_TO_BE(g->be64));
+}
+
+gboolean guid_equal(GUID * g1, GUID * g2)
+{
+    /*
+       AUDDBG("GUID 1 = %8x-%hx-%hx-%"PRIx64"\n", g1->le32, g1->le16_1, g1->le16_2, g1->be64);
+       AUDDBG("GUID 2 = %8x-%hx-%hx-%"PRIx64"\n", g2->le32, g2->le16_1, g2->le16_2, g2->be64);
+     */
+
+    g_return_val_if_fail((g1 != NULL) && (g2 != NULL), FALSE);
+    if (!memcmp(g1, g2, 16))
+    {
+        //        AUDDBG("equal\n");
+
+        return TRUE;
+    }
+    /* AUDDBG("not equal\n"); */
+    return FALSE;
+}
+
+int get_guid_type(GUID * g)
+{
+    GUID *g1;
+    int i;
+    for (i = 0; i < ASF_OBJECT_LAST - 1; i++)
+    {
+        g1 = guid_convert_from_string(wma_guid_map(i));
+        if (guid_equal(g, g1))
+        {
+
+            g_free(g1);
+            return i;
+        }
+    }
+    return -1;
+}
+
+const gchar *wma_guid_map(int i)
+{
+    const gchar *_guid_map[ASF_OBJECT_LAST] = {
+        ASF_HEADER_OBJECT_GUID,
+        ASF_FILE_PROPERTIES_OBJECT_GUID,
+        ASF_STREAM_PROPERTIES_OBJECT_GUID,
+        ASF_HEADER_EXTENSION_OBJECT_GUID,
+        ASF_CODEC_LIST_OBJECT_GUID,
+        ASF_SCRIPT_COMMAND_OBJECT_GUID,
+        ASF_MARKER_OBJECT_GUID,
+        ASF_BITRATE_MUTUAL_EXCLUSION_OBJECT_GUID,
+        ASF_ERROR_CORRECTION_OBJECT_GUID,
+        ASF_CONTENT_DESCRIPTION_OBJECT_GUID,
+        ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID,
+        ASF_CONTENT_BRANDING_OBJECT_GUID,
+        ASF_STREAM_BITRATE_PROPERTIES_OBJECT_GUID,
+        ASF_CONTENT_ENCRYPTION_OBJECT_GUID,
+        ASF_EXTENDED_CONTENT_ENCRYPTION_OBJECT_GUID,
+        ASF_DIGITAL_SIGNATURE_OBJECT_GUID,
+        ASF_PADDING_OBJECT_GUID
+    };
+    return _guid_map[i];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/wma/guid.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#ifndef _GUID_H
+#define _GUID_H
+
+#include <glib.h>
+#include <audlegacy/vfs.h>
+
+/* this stuff may be moved to ../util.h if needed by other formats */
+typedef struct _guid {
+    guint32 le32;
+    guint16 le16_1;
+    guint16 le16_2;
+    guint64 be64;
+}GUID;
+
+
+GUID *guid_read_from_file(VFSFile *);
+gboolean guid_write_to_file(VFSFile *, int);
+
+GUID *guid_convert_from_string(const gchar *);
+gchar *guid_convert_to_string(const GUID*);
+gboolean guid_equal(GUID *, GUID *);
+int get_guid_type(GUID *);
+const gchar *wma_guid_map(int);
+
+#endif /* _GUID_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/wma/module.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+#ifndef TAG_WMA_MODULE_H
+
+#define TAG_WMA_MODULE_H
+#include "../tag_module.h"
+#include "wma.h"
+static const tag_module_t wma = {
+    .name = "WMA",
+    .can_handle_file = wma_can_handle_file,
+    .populate_tuple_from_file = wma_populate_tuple_from_file,
+    .write_tuple_to_file = wma_write_tuple_to_file,
+};
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/wma/wma.c	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,429 @@
+/*
+ * 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 <inttypes.h>
+#include <glib-2.0/glib/gstdio.h>
+#include <audlegacy/tuple.h>
+
+#include "guid.h"
+#include "wma.h"
+#include "wma_fmt.h"
+#include "module.h"
+#include "../util.h"
+
+/* static functions */
+static GenericHeader *read_generic_header(VFSFile * f, gboolean read_data)
+{
+    AUDDBG("read top-level header object\n");
+    g_return_val_if_fail((f != NULL), NULL);
+    GenericHeader *header = g_new0(GenericHeader, 1);
+    header->guid = guid_read_from_file(f);
+    header->size = read_LEuint64(f);
+    if (read_data)
+        header->data = (gchar *) read_char_data(f, header->size);
+    else
+        header->data = NULL;
+
+    gchar *s = guid_convert_to_string(header->guid);
+    AUDDBG("read GUID: %s\n", s);
+    g_free(s);
+
+    return header;
+}
+
+static HeaderObj *read_top_header_object(VFSFile * f)
+{
+    AUDDBG("read top-level header object\n");
+    g_return_val_if_fail((f != NULL), NULL);
+    HeaderObj *header = g_new0(HeaderObj, 1);
+
+    //we have already read the GUID so we skip it (16 bytes)
+    vfs_fseek(f, 16, SEEK_SET);
+
+    header->size = read_LEuint64(f);
+    header->objectsNr = read_LEuint32(f);
+    AUDDBG("Number of child objects: %d\n", header->objectsNr);
+
+    header->res1 = read_uint8(f);
+    header->res2 = read_uint8(f);
+
+    if ((header->size == -1) || (header->objectsNr == -1) || (header->res2 != 2))
+    {
+        g_free(header);
+        return NULL;
+    }
+    return header;
+}
+
+static ContentDescrObj *read_content_descr_obj(VFSFile * f)
+{
+    ContentDescrObj *cdo = g_new0(ContentDescrObj, 1);
+    cdo->guid = guid_read_from_file(f);
+    cdo->size = read_LEuint64(f);
+    cdo->title_length = read_LEuint16(f);
+    cdo->author_length = read_LEuint16(f);
+    cdo->copyright_length = read_LEuint16(f);
+    cdo->desc_length = read_LEuint16(f);
+    cdo->rating_length = read_LEuint16(f);
+    cdo->title = fread_utf16(f, cdo->title_length);
+    cdo->author = fread_utf16(f, cdo->author_length);
+    cdo->copyright = fread_utf16(f, cdo->copyright_length);
+    cdo->description = fread_utf16(f, cdo->desc_length);
+    cdo->rating = fread_utf16(f, cdo->rating_length);
+    return cdo;
+}
+
+static ExtContentDescrObj *read_ext_content_descr_obj(VFSFile * f, Tuple * t, gboolean populate_tuple)
+{
+    ExtContentDescrObj *ecdo = g_new0(ExtContentDescrObj, 1);
+    ecdo->guid = guid_read_from_file(f);
+    ecdo->size = read_LEuint64(f);
+    ecdo->content_desc_count = read_LEuint16(f);
+    ecdo->descriptors = read_descriptors(f, ecdo->content_desc_count, t, populate_tuple);
+    return ecdo;
+}
+
+static guint find_descriptor_id(gchar * s)
+{
+    AUDDBG("finding descriptor id for '%s'\n", s);
+    g_return_val_if_fail(s != NULL, -1);
+    gchar *l[DESC_LAST] = { DESC_ALBUM_STR, DESC_YEAR_STR, DESC_GENRE_STR, DESC_TRACK_STR };
+    guint i;
+    for (i = 0; i < DESC_LAST; i++)
+        if (!strcmp(l[i], s))
+        {
+            AUDDBG("found descriptor %s\n", s);
+            return i;
+        }
+    return -1;
+}
+
+static ContentDescriptor *read_descriptor(VFSFile * f, Tuple * t, gboolean populate_tuple)
+{
+    ContentDescriptor *cd = g_new0(ContentDescriptor, 1);
+    gchar *val = NULL, *name = NULL;
+    guint32 intval = -1;
+    gint dtype;
+    AUDDBG("reading name_len\n");
+    cd->name_len = read_LEuint16(f);
+    AUDDBG("reading name\n");
+    cd->name = fread_utf16(f, cd->name_len);
+    AUDDBG("reading val_type\n");
+    cd->val_type = read_LEuint16(f);
+    AUDDBG("reading val_len\n");
+    cd->val_len = read_LEuint16(f);
+
+    name = utf8(cd->name);
+    dtype = find_descriptor_id(name);
+    g_free(name);
+
+    AUDDBG("reading val\n");
+
+    if (populate_tuple)
+    {
+        /*we only parse int's and UTF strings, everything else is handled as raw data */
+        if (cd->val_type == 0)
+        {                       /*UTF16 */
+            cd->val = read_char_data(f, cd->val_len);
+            val = utf8((gunichar2 *) cd->val);
+            AUDDBG("val: '%s' dtype: %d\n", val, dtype);
+            if (dtype == DESC_ALBUM)
+                tuple_associate_string(t, FIELD_ALBUM, NULL, val);
+            if (dtype == DESC_GENRE)
+                tuple_associate_string(t, FIELD_GENRE, NULL, val);
+            if (dtype == DESC_TRACK)
+                tuple_associate_int(t, FIELD_TRACK_NUMBER, NULL, atoi(val));
+            if (dtype == DESC_YEAR)
+                tuple_associate_int(t, FIELD_YEAR, NULL, atoi(val));
+        }
+        else
+        {
+            if (cd->val_type == 3)
+            {
+                intval = read_LEuint32(f);
+                AUDDBG("intval: %d, dtype: %d\n", intval, dtype);
+                if (dtype == DESC_TRACK)
+                    tuple_associate_int(t, FIELD_TRACK_NUMBER, NULL, intval);
+            }
+            else
+                cd->val = read_char_data(f, cd->val_len);
+        }
+    }
+    else
+        cd->val = read_char_data(f, cd->val_len);
+    AUDDBG("read str_val: '%s', intval: %d\n", val, intval);
+    AUDDBG("exiting read_descriptor \n\n");
+    return cd;
+}
+
+static ContentDescriptor **read_descriptors(VFSFile * f, guint count, Tuple * t, gboolean populate_tuple)
+{
+    if (count == 0)
+        return NULL;
+    ContentDescriptor **descs = g_new0(ContentDescriptor *, count);
+    int i;
+    for (i = 0; i < count; i++)
+        descs[i] = read_descriptor(f, t, populate_tuple);
+    return descs;
+}
+
+void free_content_descr_obj(ContentDescrObj * c)
+{
+    g_free(c->guid);
+    g_free(c->title);
+    g_free(c->author);
+    g_free(c->copyright);
+    g_free(c->description);
+    g_free(c->rating);
+    g_free(c);
+}
+
+void free_content_descr(ContentDescriptor * cd)
+{
+    g_free(cd->name);
+    g_free(cd->val);
+    g_free(cd);
+}
+
+void free_ext_content_descr_obj(ExtContentDescrObj * ecdo)
+{
+    int i;
+    g_free(ecdo->guid);
+    for (i = 0; i < ecdo->content_desc_count; i++)
+        free_content_descr(ecdo->descriptors[i]);
+    g_free(ecdo);
+}
+
+/* returns the offset of the object in the file */
+static long ftell_object_by_guid(VFSFile * f, GUID * g)
+{
+    AUDDBG("seeking object %s, with ID %d \n", guid_convert_to_string(g), get_guid_type(g));
+    HeaderObj *h = read_top_header_object(f);
+    g_return_val_if_fail((f != NULL) && (g != NULL) && (h != NULL), -1);
+
+    gint i = 0;
+    while (i < h->objectsNr)
+    {
+        GenericHeader *gen_hdr = read_generic_header(f, FALSE);
+        AUDDBG("encountered GUID %s, with ID %d\n", guid_convert_to_string(gen_hdr->guid), get_guid_type(gen_hdr->guid));
+        if (guid_equal(gen_hdr->guid, g))
+        {
+            g_free(h);
+            g_free(gen_hdr);
+            guint64 ret = vfs_ftell(f) - 24;
+            AUDDBG("at offset %" PRIx64 "\n", ret);
+            return ret;
+        }
+        vfs_fseek(f, gen_hdr->size - 24, SEEK_CUR);     //most headers have a size as their second field"
+        i++;
+    }
+    AUDDBG("The object was not found\n");
+
+    return -1;
+}
+
+VFSFile *make_temp_file()
+{
+    /* create a temporary file */
+    const gchar *tmpdir = g_get_tmp_dir();
+    gchar *tmp_path = g_strdup_printf("file://%s/%s", tmpdir, "wmatmp.wma");
+    return vfs_fopen(tmp_path, "w+");
+}
+
+long ftell_object_by_str(VFSFile * f, gchar * s)
+{
+    GUID *g = guid_convert_from_string(s);
+    long res = ftell_object_by_guid(f, g);
+    g_free(g);
+    return res;
+}
+
+static void write_content_descr_obj_from_tuple(VFSFile * f, ContentDescrObj * cdo, Tuple * t)
+{
+    glong size;
+    gboolean free_cdo = FALSE;
+    if (cdo == NULL)
+    {
+        cdo = g_new0(ContentDescrObj, 1);
+        free_cdo = TRUE;
+    }
+
+    cdo->title = g_utf8_to_utf16(tuple_get_string(t, FIELD_TITLE, NULL), -1, NULL, &size, NULL);
+    cdo->title_length = 2 * (size + 1);
+    cdo->author = g_utf8_to_utf16(tuple_get_string(t, FIELD_ARTIST, NULL), -1, NULL, &size, NULL);
+    cdo->author_length = 2 * (size + 1);
+    cdo->copyright = g_utf8_to_utf16(tuple_get_string(t, FIELD_COPYRIGHT, NULL), -1, NULL, &size, NULL);
+    cdo->copyright_length = 2 * (size + 1);
+    cdo->description = g_utf8_to_utf16(tuple_get_string(t, FIELD_COMMENT, NULL), -1, NULL, &size, NULL);
+    cdo->desc_length = 2 * (size + 1);
+    cdo->size = 34 + cdo->title_length + cdo->author_length + cdo->copyright_length + cdo->desc_length;
+    guid_write_to_file(f, ASF_CONTENT_DESCRIPTION_OBJECT);
+    write_LEuint64(f, cdo->size);
+    write_LEuint16(f, cdo->title_length);
+    write_LEuint16(f, cdo->author_length);
+    write_LEuint16(f, cdo->copyright_length);
+    write_LEuint16(f, cdo->desc_length);
+    write_LEuint16(f, 2);       // rating_length = 2
+    write_utf16(f, cdo->title, cdo->title_length);
+    write_utf16(f, cdo->title, cdo->title_length);
+    write_utf16(f, cdo->author, cdo->author_length);
+    write_utf16(f, cdo->copyright, cdo->copyright_length);
+    write_utf16(f, cdo->description, cdo->desc_length);
+    write_utf16(f, NULL, 2);    //rating == NULL
+
+    if (free_cdo)
+        free_content_descr_obj(cdo);
+}
+
+static void write_ext_content_descr_obj_from_tuple(VFSFile * f, ExtContentDescrObj * ecdo, Tuple * tuple)
+{
+}
+
+static gboolean write_generic_header(VFSFile * f, GenericHeader * gh)
+{
+    AUDDBG("Writing generic header\n");
+    guid_write_to_file(f, get_guid_type(gh->guid));
+    return write_char_data(f, gh->data, gh->size);
+}
+
+static void free_generic_header(GenericHeader * gh)
+{
+    g_free(gh->guid);
+    g_free(gh->data);
+    g_free(gh);
+}
+
+static gboolean write_top_header_object(VFSFile * f, HeaderObj * header)
+{
+    AUDDBG("write header object\n");
+    vfs_fseek(f, 0, SEEK_SET);
+    return (guid_write_to_file(f, ASF_HEADER_OBJECT) && write_LEuint64(f, header->size) && write_LEuint32(f, header->objectsNr) && write_uint8(f, header->res1) &&      /* the reserved fields */
+            write_uint8(f, header->res2));
+}
+
+/* interface functions */
+gboolean wma_can_handle_file(VFSFile * f)
+{
+    GUID *guid1 = guid_read_from_file(f);
+    GUID *guid2 = guid_convert_from_string(ASF_HEADER_OBJECT_GUID);
+    gboolean equal = ((guid1 != NULL) && guid_equal(guid1, guid2));
+
+    g_free(guid1);
+    g_free(guid2);
+
+    return equal;
+}
+
+Tuple *wma_populate_tuple_from_file(Tuple * t, VFSFile * f)
+{
+    gchar *artist = NULL, *title = NULL, *comment = NULL;
+
+    print_tuple(t);
+    gint seek_res = vfs_fseek(f, ftell_object_by_str(f, ASF_CONTENT_DESCRIPTION_OBJECT_GUID), SEEK_SET);
+    if (seek_res == 0)
+    {                           //if the CONTENT_DESCRIPTION_OBJECT was found
+        ContentDescrObj *cdo = read_content_descr_obj(f);
+        artist = utf8(cdo->author);
+        title = utf8(cdo->title);
+        comment = utf8(cdo->description);
+        free_content_descr_obj(cdo);
+        tuple_associate_string(t, FIELD_ARTIST, NULL, artist);
+        tuple_associate_string(t, FIELD_TITLE, NULL, title);
+        tuple_associate_string(t, FIELD_COMMENT, NULL, comment);
+    }
+    seek_res = vfs_fseek(f, ftell_object_by_str(f, ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID), SEEK_SET);
+    /*this populates the tuple with the attributes stored as extended descriptors */
+    ExtContentDescrObj *ecdo = read_ext_content_descr_obj(f, t, TRUE);
+    free_ext_content_descr_obj(ecdo);
+    print_tuple(t);
+    return t;
+}
+
+gboolean wma_write_tuple_to_file(Tuple * tuple, VFSFile * f)
+{
+
+
+#if BROKEN
+    return FALSE;
+#endif
+
+    HeaderObj *top_ho = read_top_header_object(f);
+    VFSFile *tmpfile = make_temp_file();
+    gint i, cdo_pos, ecdo_pos;
+    GUID *g;
+    /*read all the headers and write them to the new file */
+    /*the headers that contain tuple data will be overwritten */
+    AUDDBG("Header Object size: %" PRId64 "\n", top_ho->size);
+    //vfs_fseek(tmpfile, )
+    for (i = 0; i < top_ho->objectsNr; i++)
+    {
+        GenericHeader *gh = read_generic_header(f, TRUE);
+        g = guid_convert_from_string(ASF_CONTENT_DESCRIPTION_OBJECT_GUID);
+        if (guid_equal(gh->guid, g))
+        {
+            write_content_descr_obj_from_tuple(tmpfile, (ContentDescrObj *) gh, tuple);
+            g_free(g);
+        }
+        else
+        {
+            g = guid_convert_from_string(ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID);
+            if (guid_equal(gh->guid, g))
+                write_ext_content_descr_obj_from_tuple(tmpfile, (ExtContentDescrObj *) gh, tuple);
+            else
+            {
+                write_generic_header(tmpfile, gh);
+                g_free(g);
+            }
+        }
+        free_generic_header(gh);
+    }
+    /*check wether these headers existed in the  original file */
+    cdo_pos = ftell_object_by_str(f, ASF_CONTENT_DESCRIPTION_OBJECT_GUID);
+    ecdo_pos = ftell_object_by_str(f, ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID);
+    if (cdo_pos == -1)
+    {
+        write_content_descr_obj_from_tuple(tmpfile, NULL, tuple);
+        top_ho->objectsNr++;
+    }
+    if (ecdo_pos != -1)
+    {
+        write_ext_content_descr_obj_from_tuple(tmpfile, NULL, tuple);
+        top_ho->objectsNr++;
+    }
+    write_top_header_object(tmpfile, top_ho);
+
+    gchar *f1 = g_filename_from_uri(tmpfile->uri, NULL, NULL);
+    gchar *f2 = g_filename_from_uri(f->uri, NULL, NULL);
+    vfs_fclose(tmpfile);
+    /*
+       if (g_rename(f1, f2) == 0)
+       {
+       AUDDBG("the tag was updated successfully\n");
+       }
+       else
+       {
+       AUDDBG("an error has occured\n");
+       }
+     */
+    g_free(f1);
+    g_free(f2);
+
+    return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/wma/wma.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#ifndef _WMA_H
+
+#define _WMA_H
+
+#include "audlegacy/tuple.h"
+#include "audlegacy/vfs.h"
+#include "wma_fmt.h"
+
+#ifndef TAG_WMA_MODULE_H
+/* static functions */
+
+/* reads the whole structure, containing the data */
+static GenericHeader *read_generic_header(VFSFile *f, gboolean read_data);
+
+static HeaderObj *read_top_header_object(VFSFile *f);
+
+static gboolean write_top_header_object(VFSFile *f, HeaderObj *h);
+
+static ContentDescriptor **read_descriptors(VFSFile *f, guint count, Tuple*t, gboolean populate_tuple);
+
+static ContentDescrObj *read_content_descr_obj(VFSFile *f);
+
+static ExtContentDescrObj *read_ext_content_descr_obj(VFSFile * f, Tuple *t, gboolean populate_tuple);
+
+static long ftell_object_by_guid(VFSFile *f, GUID *g);
+static long ftell_object_by_str(VFSFile *f, gchar *s);
+#endif
+
+/* TAG plugin API */
+gboolean wma_can_handle_file(VFSFile *f);
+Tuple *wma_populate_tuple_from_file(Tuple *t, VFSFile *f);
+gboolean wma_write_tuple_to_file(Tuple *t, VFSFile *f);
+
+#endif /* _WMA_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libaudtag/wma/wma_fmt.h	Wed May 05 18:26:06 2010 +0900
@@ -0,0 +1,162 @@
+/*
+ * 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.
+ */
+
+#ifndef _WMA_FMT_H
+#define _WMA_FMT_H
+
+#include "guid.h"
+
+
+#define ASF_HEADER_OBJECT_GUID				"75B22630-668E-11CF-A6D900AA0062CE6C"
+#define ASF_FILE_PROPERTIES_OBJECT_GUID			"8CABDCA1-A947-11CF-8EE400C00C205365"
+#define ASF_STREAM_PROPERTIES_OBJECT_GUID		"B7DC0791-A9B7-11CF-8EE600C00C205365"
+#define ASF_HEADER_EXTENSION_OBJECT_GUID		"5FBF03B5-A92E-11CF-8EE300C00C205365"
+#define ASF_CODEC_LIST_OBJECT_GUID			"86D15240-311D-11D0-A3A400A0C90348F6"
+#define ASF_SCRIPT_COMMAND_OBJECT_GUID			"1EFB1A30-0B62-11D0-A39B00A0C90348F6"
+#define ASF_MARKER_OBJECT_GUID				"F487CD01-A951-11CF-8EE600C00C205365"
+#define ASF_BITRATE_MUTUAL_EXCLUSION_OBJECT_GUID	"D6E229DC-35DA-11D1-903400A0C90349BE"
+#define ASF_ERROR_CORRECTION_OBJECT_GUID		"75B22635-668E-11CF-A6D900AA0062CE6C"
+#define ASF_CONTENT_DESCRIPTION_OBJECT_GUID		"75B22633-668E-11CF-A6D900AA0062CE6C"
+#define ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID	"D2D0A440-E307-11D2-97F000A0C95EA850"
+#define ASF_CONTENT_BRANDING_OBJECT_GUID		"2211B3FA-BD23-11D2-B4B700A0C955FC6E"
+#define ASF_STREAM_BITRATE_PROPERTIES_OBJECT_GUID	"7BF875CE-468D-11D1-8D82006097C9A2B2"
+#define ASF_CONTENT_ENCRYPTION_OBJECT_GUID		"2211B3FB-BD23-11D2-B4B700A0C955FC6E"
+#define ASF_EXTENDED_CONTENT_ENCRYPTION_OBJECT_GUID	"298AE614-2622-4C17-B935DAE07EE9289C"
+#define ASF_DIGITAL_SIGNATURE_OBJECT_GUID		"2211B3FC-BD23-11D2-B4B700A0C955FC6E"
+#define ASF_PADDING_OBJECT_GUID				"1806D474-CADF-4509-A4BA9AABCB96AAE8"
+
+typedef enum {
+    ASF_HEADER_OBJECT = 0,
+    ASF_FILE_PROPERTIES_OBJECT, /*              1 */
+    ASF_STREAM_PROPERTIES_OBJECT,
+    ASF_HEADER_EXTENSION_OBJECT,
+    ASF_CODEC_LIST_OBJECT,
+    ASF_SCRIPT_COMMAND_OBJECT, /*               5 */
+    ASF_MARKER_OBJECT,
+    ASF_BITRATE_MUTUAL_EXCLUSION_OBJECT,
+    ASF_ERROR_CORRECTION_OBJECT,
+    ASF_CONTENT_DESCRIPTION_OBJECT,
+    ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT, /* 10*/
+    ASF_CONTENT_BRANDING_OBJECT,
+    ASF_STREAM_BITRATE_PROPERTIES_OBJECT,
+    ASF_CONTENT_ENCRYPTION_OBJECT,
+    ASF_EXTENDED_CONTENT_ENCRYPTION_OBJECT,
+    ASF_DIGITAL_SIGNATURE_OBJECT, /*            15 */
+    ASF_PADDING_OBJECT,
+    ASF_OBJECT_LAST /*                          dummy */
+} ObjectType;
+
+#define DESC_ALBUM_STR "WM/AlbumTitle"
+#define DESC_YEAR_STR "WM/Year"
+#define DESC_GENRE_STR "WM/Genre"
+#define DESC_TRACK_STR "WM/TrackNumber"
+
+typedef enum {
+    DESC_ALBUM = 0,
+    DESC_YEAR,
+    DESC_GENRE,
+    DESC_TRACK,
+    DESC_LAST
+} DescrIndexes;
+
+/*
+ * this should be fine for all headers whose content is irrelevant,
+ * but the size is needed so that we can skip it
+ */
+typedef struct _generic_header {
+    GUID *guid;
+    guint64 size;
+    gchar *data;
+} GenericHeader;
+
+typedef struct _header_object {
+    GUID *guid;
+    guint64 size;
+    guint32 objectsNr;
+    guint8 res1;
+    guint8 res2;
+} HeaderObj;
+
+/*
+ * this is special, its size does not include the size of the ext_data
+ */
+typedef struct _header_extension_object {
+    GUID *guid;
+    guint64 size;
+    guint32 objects_count;
+    guint8 res1;
+    guint8 res2;
+    guint32 ext_data_size;
+    gchar *ext_data;
+} HeaderExtensionObject;
+
+typedef struct _file_properties_header {
+    GUID *guid;
+    guint64 size;
+    gchar dontcare1[16];
+    guint64 duration; //expressed as the count of 100ns intervals
+    gchar dontcare2[32];
+} FilePropertiesHeader;
+
+typedef struct _content_description_object {
+    GUID *guid;
+    guint64 size;
+    guint16 title_length;
+    guint16 author_length;
+    guint16 copyright_length; /* dontcare*/
+    guint16 desc_length;
+    guint16 rating_length; /* dontcare*/
+    gunichar2 *title;
+    gunichar2 *author;
+    gunichar2 *copyright; /* dontcare*/
+    gunichar2 *description;
+    gunichar2 *rating; /* dontcare*/
+} ContentDescrObj;
+
+/* descr_val_type's meaning
+ * Value  Type           length
+ * 0x0000 Unicode string varies
+ * 0x0001 BYTE array     varies
+ * 0x0002 BOOL           32
+ * 0x0003 DWORD          32
+ * 0x0004 QWORD          64
+ * 0x0005 WORD           16
+ */
+typedef struct _content_descriptor {
+    guint16 name_len;
+    gunichar2 *name;
+    guint16 val_type;
+    guint16 val_len;
+    gchar * val;
+} ContentDescriptor;
+
+typedef struct _extended_content_description_object {
+    GUID *guid;
+    guint64 size;
+    guint16 content_desc_count;
+    ContentDescriptor **descriptors;
+} ExtContentDescrObj;
+
+typedef struct _content_field {
+    guint16 size;
+    gunichar2 *strValue;
+} ContentField;
+
+#endif /* _WMA_FMT_H */