Logo Search packages:      
Sourcecode: easytag version File versions  Download package

id3_tag.c

/* id3tag.c - 2001/02/16 */
/*
 *  EasyTAG - Tag editor for MP3 and Ogg Vorbis files
 *  Copyright (C) 2001-2003  Jerome Couderc <easytag@gmail.com>
 *
 *  This program 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; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <config.h>

#include <gtk/gtk.h>
#include <glib/gi18n-lib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>

#include "id3_tag.h"
#include "id3lib/id3_bugfix.h"
#include "picture.h"
#include "easytag.h"
#include "browser.h"
#include "genres.h"
#include "setting.h"
#include "misc.h"
#include "et_core.h"
#include "msgbox.h"
#include "charset.h"

#ifdef ENABLE_MP3

#include <id3.h>


/****************
 * Declarations *
 ****************/
#define ID3V2_MAX_STRING_LEN 4096
#define MULTIFIELD_SEPARATOR " - "


/**************
 * Prototypes *
 **************/
gchar *Id3tag_Get_Error_Message (ID3_Err error);
gint   Id3tag_Get_Id3v2_Version (gchar *filename);
void   Id3tag_Prepare_ID3v1     (ID3Tag *id3_tag);
gchar *Id3tag_Rules_For_ISO_Fields (const gchar *string, const gchar *from_codeset, const gchar *to_codeset);
gchar *Id3tag_Get_Field         (const ID3Frame *id3_frame, ID3_FieldID id3_fieldid);
ID3_TextEnc Id3tag_Set_Field    (const ID3Frame *id3_frame, ID3_FieldID id3_fieldid, gchar *string);

ID3_C_EXPORT size_t ID3Tag_Link_1         (ID3Tag *id3tag, const char *filename);
ID3_C_EXPORT size_t ID3Field_GetASCII_1   (const ID3Field *field, char *buffer,      size_t maxChars, size_t itemNum);
ID3_C_EXPORT size_t ID3Field_GetUNICODE_1 (const ID3Field *field, unicode_t *buffer, size_t maxChars, size_t itemNum);

gboolean Id3tag_Check_If_File_Is_Corrupted (gchar *filename);


/*************
 * Functions *
 *************/

/*
 * Read id3v1.x / id3v2 tag and load data into the File_Tag structure using id3lib functions.
 * Returns TRUE on success, else FALSE.
 * If a tag entry exists (ex: title), we allocate memory, else value stays to NULL
 */
gboolean Id3tag_Read_File_Tag (gchar *filename, File_Tag *FileTag)
{
    FILE   *file;
    ID3Tag *id3_tag = NULL;    /* Tag defined by the id3lib */
    gchar  *string, *string1, *string2;
    ID3Frame *id3_frame, *id3_first_frame;
    ID3Field *id3_field;
    size_t offset;
    Picture *prev_pic = NULL;
    unsigned char tmp[4];
    gboolean has_id3v1_tag = TRUE;
    gboolean has_id3v2_tag = TRUE;

    if (!filename || !FileTag)
        return FALSE;

    if ( (file=fopen(filename,"rb"))==NULL )
    {
        gchar *filename_utf8 = filename_to_display(filename);
        g_print(_("ERROR while opening file: '%s' (%s).\n\a"),filename_utf8,g_strerror(errno));
        g_free(filename_utf8);
        return FALSE;
    }
    
    // Check if the file has an ID3v2 tag or/and an ID3v1 tags
    // 1) ID3v2 tag
    if (fread(tmp, 1, 4, file) != 4)
    {
        fclose(file);
        return FALSE;
    }
    if (tmp[0] != 'I' || tmp[1] != 'D' || tmp[2] != '3' || tmp[3] >= 0xFF) // ID3v2 tag skipeer $49 44 33 yy yy xx zz zz zz zz [zz size]
    {
        // ID3v2 tag not found!
        if (WRITE_ID3V2_TAG)
            FileTag->saved = FALSE;
        has_id3v2_tag = FALSE;
    }
    // 2) ID3v1 tag
    if (fseek(file,-128, SEEK_END)!=0 // Go to the beginning of ID3v1 tag
    ||  fread(tmp,1,3,file) != 3)
    {
        fclose(file);
        return FALSE;
    }
    if (tmp[0] != 'T' || tmp[1] != 'A' || tmp[2] != 'G')
    {
        // ID3v1 tag not found!
        if (WRITE_ID3V1_TAG)
            FileTag->saved = FALSE;
        has_id3v1_tag = FALSE;
    }
    
    fclose(file); // We close it cause id3lib opens/closes file itself

    /* This is a protection against a bug in id3lib that generate an infinite
     * loop with corrupted MP3 files (files containing only zeroes) */
    if (Id3tag_Check_If_File_Is_Corrupted(filename))
        return FALSE;

    // If no tag found, it isn't useful to try to read it with id3lib
    if (!has_id3v1_tag && !has_id3v2_tag)
        return FALSE;

    /* Get data from tag */
    if ( (id3_tag = ID3Tag_New()) == NULL )
        return FALSE;

    /* Link the file to the tag */
    offset = ID3Tag_Link_1(id3_tag,filename);
    if ( offset && CONVERT_OLD_ID3V2_TAG_VERSION )
    {
        gint id3v2_version = Id3tag_Get_Id3v2_Version(filename);
        // If it's an old tag version, we force to change it to an id3v2.3 one
        if (id3v2_version>0 && id3v2_version<3)
            FileTag->saved = FALSE;
    }

    /* If the specified tags (in preferences window) aren't in the file => we save it
     * (Note: the file must be linked with the both tags) */
/*** // Disabled (see on top on replacement) as we don't link the file with the both tags (because may cause damage to unicode strings)
#   if ( (ID3LIB_MAJOR >= 3) && (ID3LIB_MINOR >= 8) && (ID3LIB_PATCH >= 1) )
    if (ID3Tag_HasTagType(id3_tag,ID3TT_ID3V1) != WRITE_ID3V1_TAG
    ||  ID3Tag_HasTagType(id3_tag,ID3TT_ID3V2) != WRITE_ID3V2_TAG)
        FileTag->saved = FALSE;
#   endif
****/

    /* Protection against invalid ID3v2 tag, for example ID3v2.4 in
     * id3lib-3.8.0 : if offset is not nul and the tag contains no
     * frame, may be an invalid id3v2 tag. So we read the id3v1 tag */
#   if ( (ID3LIB_MAJOR >= 3) && (ID3LIB_MINOR >= 8)  )
    if ( offset!=0 && ID3Tag_NumFrames(id3_tag)==0 )
        offset = ID3Tag_LinkWithFlags(id3_tag,filename,ID3TT_ID3V1);
#   endif

    string = g_malloc(ID3V2_MAX_STRING_LEN+1);


    /****************
     * Title (TIT2) *
     ****************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_TITLE)) )
    {
        FileTag->title = Id3tag_Get_Field(id3_frame,ID3FN_TEXT);
    }


    /*****************
     * Artist (TPE1) *
     *****************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_LEADARTIST)) )
    {
        FileTag->artist = Id3tag_Get_Field(id3_frame,ID3FN_TEXT);
    }


    /****************
     * Album (TALB) *
     ****************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_ALBUM)) )
    {
        FileTag->album = Id3tag_Get_Field(id3_frame,ID3FN_TEXT);
    }


    /************************
     * Part of a set (TPOS) *
     ************************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_PARTINSET)) )
    {
        FileTag->disc_number = Id3tag_Get_Field(id3_frame,ID3FN_TEXT);
    }


    /***************
     * Year (TYER) *
     ***************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_YEAR)) )
    {
        if ( (string1 = Id3tag_Get_Field(id3_frame,ID3FN_TEXT)) )
        {
            gchar *tmp_str;
    
            Strip_String(string1);
    
            /* Fix for id3lib 3.7.x: if the id3v1.x tag was filled with spaces
             * instead of zeroes, then the year field contains garbages! */
            tmp_str = string1;
            while (isdigit((guchar)*tmp_str)) tmp_str++;
            *tmp_str = 0;
            /* End of fix for id3lib 3.7.x */
            FileTag->year = string1;
        }
    }


    /********************************
     * Track and Total Track (TRCK) *
     ********************************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_TRACKNUM)) )
    {
        if ( (string1 = Id3tag_Get_Field(id3_frame,ID3FN_TEXT)) )
        {
            string2 = g_utf8_strchr(string1,-1,'/');
    
            if (NUMBER_TRACK_FORMATED)
            {
                if (string2)
                {
                    FileTag->track_total = g_strdup_printf("%.*d",NUMBER_TRACK_FORMATED_SPIN_BUTTON,atoi(string2+1)); // Just to have numbers like this : '01', '05', '12', ...
                    *string2 = '\0';
                }
                FileTag->track = g_strdup_printf("%.*d",NUMBER_TRACK_FORMATED_SPIN_BUTTON,atoi(string1)); // Just to have numbers like this : '01', '05', '12', ...
            }else
            {
                if (string2)
                {
                    FileTag->track_total = g_strdup(string2+1);
                    *string2 = '\0';
                }
                FileTag->track = g_strdup(string1);
            }
            g_free(string1);
            
        }
    }


    /****************
     * Genre (TCON) *
     ****************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_CONTENTTYPE)) )
    {
        if ( (string1 = Id3tag_Get_Field(id3_frame,ID3FN_TEXT)) )
        {
            /*
             * We manipulate only the name of the genre
             * Genre is written like this :
             *    - "(<genre_id>)"              -> "(3)"
             *    - "<genre_name>"              -> "Dance"
             *    - "(<genre_id>)<refinement>"  -> "(3)EuroDance"
             */
            gchar *tmp;
    
            if ( (string1[0]=='(') && (tmp=strchr(string1,')')) && (tmp+1) && (strlen((tmp+1))>0) )
                /* Convert a genre written as '(3)EuroDance' into 'EuroDance' */
            {
                FileTag->genre = g_strdup(tmp+1);
            } else if ( (string1[0]=='(') && (tmp=strchr(string1,')')) )
            {
                /* Convert a genre written as '(3)' into 'Dance' */
                FileTag->genre = g_strdup( Id3tag_Genre_To_String(atoi(string1+1)) );
            } else
            {
                /* Genre is already written as 'Dance' */
                FileTag->genre = g_strdup(string1);
            }
            g_free(string1);
        }
    }


    /******************
     * Comment (COMM) *
     ******************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_COMMENT)) )
    {
        FileTag->comment = Id3tag_Get_Field(id3_frame,ID3FN_TEXT);
        /*{
            gchar *comment1 = Id3tag_Get_Field(id3_frame,ID3FN_DESCRIPTION)
            gchar *comment2 = Id3tag_Get_Field(id3_frame,ID3FN_LANGUAGE)
        }*/
    }


    /*******************
     * Composer (TCOM) *
     *******************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_COMPOSER)) )
    {
        FileTag->composer = Id3tag_Get_Field(id3_frame,ID3FN_TEXT);
    }


    /**************************
     * Original artist (TOPE) *
     **************************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_ORIGARTIST)) )
    {
        FileTag->orig_artist = Id3tag_Get_Field(id3_frame,ID3FN_TEXT);
    }


    /*******************
     * Copyright (TCOP)*
     *******************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_COPYRIGHT)) )
    {
        FileTag->copyright = Id3tag_Get_Field(id3_frame,ID3FN_TEXT);
    }


    /**************
     * URL (WXXX) *
     **************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_WWWUSER)) )
    {
        FileTag->url = Id3tag_Get_Field(id3_frame,ID3FN_URL);
    }


    /*********************
     * Encoded by (TENC) *
     *********************/
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_ENCODEDBY)) )
    {
        FileTag->encoded_by = Id3tag_Get_Field(id3_frame,ID3FN_TEXT);
    }


    /******************
     * Picture (APIC) *
     ******************/
    // Have a look to id3/misc_support.h (id3lib)
    id3_first_frame = NULL;
    for(;;)
    {
        Picture *pic;
          
        id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_PICTURE);
        if (!id3_frame || id3_frame == id3_first_frame)
            break;
        if (!id3_first_frame)
            id3_first_frame = id3_frame;
        
        pic = Picture_Allocate();
        if (!prev_pic)
            FileTag->picture = pic;
        else
            prev_pic->next = pic;
        prev_pic = pic;
        
        // Picture file data
        if ( (id3_field = ID3Frame_GetField(id3_frame, ID3FN_DATA)) )
        {
            pic->size = ID3Field_Size(id3_field);
            pic->data = g_malloc(pic->size);
            ID3Field_GetBINARY(id3_field, pic->data, pic->size);
        }
        
        // Picture type
        if ( (id3_field = ID3Frame_GetField(id3_frame, ID3FN_PICTURETYPE)) )
            pic->type = ID3Field_GetINT(id3_field);
        
        // Picture description
        pic->description = Id3tag_Get_Field(id3_frame, ID3FN_DESCRIPTION);
            
        //g_print("Image format : %s\n",Id3tag_Get_Field(ID3FN_IMAGEFORMAT));
    }


    /*****************
     * Lyrics (SYLC) *
     *****************/
    /** see also id3/misc_support.h  **
    if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_SYNCEDLYRICS)) )
    {
        gulong  size = 0;
        guchar *data = NULL;
        gchar  *description = NULL;
        gchar  *language = NULL;
        gint timestamp_format = 0;
        gint sync_type = 0;
    
        // SyncLyrics data
        if ( (id3_field = ID3Frame_GetField(id3_frame, ID3FN_DATA)) )
        {
            size = ID3Field_Size(id3_field);
            data = g_malloc(size);
            ID3Field_GetBINARY(id3_field, data, size);
        }

        // SyncLyrics description
        description = Id3tag_Get_Field(id3_frame, ID3FN_DESCRIPTION);

        // SyncLyrics language
        language = Id3tag_Get_Field(id3_frame, ID3FN_LANGUAGE);

        // SyncLyrics timestamp field
        if ( (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TIMESTAMPFORMAT)) )
        {
            timestamp_format = ID3Field_GetINT(id3_field);
        }
        
        // SyncLyrics content type
        if ( (id3_field = ID3Frame_GetField(id3_frame, ID3FN_CONTENTTYPE)) )
        {
            sync_type = ID3Field_GetINT(id3_field);
        }

        // Print data
        // j.a. Pouwelse - pouwelse :
        //      http://sourceforge.net/tracker/index.php?func=detail&aid=401873&group_id=979&atid=300979
        {
            char tag[255];
            unsigned int time;
            luint pos = 0;
            
            g_print("SyncLyrics/description      : %s\n",description);
            g_print("SyncLyrics/language         : %s\n",language);
            g_print("SyncLyrics/timestamp format : %s (%d)\n",timestamp_format==ID3TSF_FRAME ? "ID3TSF_FRAME" : timestamp_format==ID3TSF_MS ? "ID3TSF_MS" : "" ,timestamp_format);
            g_print("SyncLyrics/sync type        : %s (%d)\n",sync_type==ID3CT_LYRICS ? "ID3CT_LYRICS" : "",sync_type);
            
            
            g_print("SyncLyrics size             : %d\n", size);
            g_print("Lyrics/data :\n");
            while (pos < size)
            {
                strcpy(tag,data+pos);
                //g_print("txt start=%d ",pos);
                pos+=strlen(tag)+1;             // shift string and terminating \0
                //g_print("txt end=%d ",pos);
                memcpy(&time,data+pos,4);
                pos+=4;
                //g_print("%d -> %s\n",time,tag);
                g_print("%s",tag);
            }
        }
        
    } **/


    g_free(string);

    /* Free allocated data */
    ID3Tag_Delete(id3_tag);

    return TRUE;
}


/*
 * Write the ID3 tags to the file. Returns TRUE on success, else 0.
 */
gboolean Id3tag_Write_File_Tag (ET_File *ETFile)
{
    File_Tag *FileTag;
    gchar    *filename;
    gchar    *basename_utf8;
    gchar    *temp;
    FILE     *file;
    ID3Tag   *id3_tag = NULL;
    //ID3_TextEnc encoding_used = ID3TE_NONE;
    //gboolean unicode_used = FALSE;
    ID3_Err   error_strip_id3v1  = ID3E_NoError;
    ID3_Err   error_strip_id3v2  = ID3E_NoError;
    ID3_Err   error_update_id3v1 = ID3E_NoError;
    ID3_Err   error_update_id3v2 = ID3E_NoError;
    gint error = 0;
    gint number_of_frames;
    gboolean has_title       = FALSE;
    gboolean has_artist      = FALSE;
    gboolean has_album       = FALSE;
    gboolean has_disc_number = FALSE;
    gboolean has_year        = FALSE;
    gboolean has_track       = FALSE;
    gboolean has_genre       = FALSE;
    gboolean has_comment     = FALSE;
    gboolean has_composer    = FALSE;
    gboolean has_orig_artist = FALSE;
    gboolean has_copyright   = FALSE;
    gboolean has_url         = FALSE;
    gboolean has_encoded_by  = FALSE;
    gboolean has_picture     = FALSE;
    gboolean has_song_len    = FALSE;

    ID3Frame *id3_frame;
    ID3Field *id3_field;
    //gchar *string;
    gchar *string1;
    Picture *pic;

    if (!ETFile || !ETFile->FileTag)
        return FALSE;

    FileTag  = (File_Tag *)ETFile->FileTag->data;
    filename = ((File_Name *)ETFile->FileNameCur->data)->value;

    /* Test to know if we can write into the file */
    if ( (file=fopen(filename,"r+"))==NULL )
    {
        gchar *filename_utf8 = filename_to_display(filename);
        g_print(_("ERROR while opening file: '%s' (%s).\n\a"),filename_utf8,g_strerror(errno));
        g_free(filename_utf8);
        return FALSE;
    }
    fclose(file);

    /* This is a protection against a bug in id3lib that generate an infinite
     * loop with corrupted MP3 files (files containing only zeroes) */
    if (Id3tag_Check_If_File_Is_Corrupted(filename))
        return FALSE;

    /* We get again the tag from the file to keep also unused data (by EasyTAG), then
     * we replace the changed data */
    if ( (id3_tag = ID3Tag_New()) == NULL )
        return FALSE;

    temp = g_path_get_basename(filename);
    basename_utf8 = filename_to_display(temp);
    g_free(temp);

    ID3Tag_Link(id3_tag,filename);

    /* Set padding when tag was changed, for faster writing */
    ID3Tag_SetPadding(id3_tag,USE_ID3V2_PADDING);


    /*********
     * Title *
     *********/
    // To avoid problem with a corrupted field, we remove it before to create a new one.
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_TITLE)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);

    if (FileTag->title && g_utf8_strlen(FileTag->title, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_TITLE);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->title);
        has_title = TRUE;
    }


    /**********
     * Artist *
     **********/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_LEADARTIST)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->artist && g_utf8_strlen(FileTag->artist, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_LEADARTIST);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->artist);
        has_artist = TRUE;
    }


    /*********
     * Album *
     *********/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_ALBUM)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->album && g_utf8_strlen(FileTag->album, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_ALBUM);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->album);
        has_album = TRUE;
    }


    /***************
     * Part of set *
     ***************/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_PARTINSET)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->disc_number && g_utf8_strlen(FileTag->disc_number, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_PARTINSET);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->disc_number);
        has_disc_number = TRUE;
    }


    /********
     * Year *
     ********/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_YEAR)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->year && g_utf8_strlen(FileTag->year, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_YEAR);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->year);
        has_year = TRUE;
    }


    /*************************
     * Track and Total Track *
     *************************/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_TRACKNUM)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->track && g_utf8_strlen(FileTag->track, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_TRACKNUM);
        ID3Tag_AttachFrame(id3_tag,id3_frame);

        if ( FileTag->track_total && g_utf8_strlen(FileTag->track_total, -1) > 0)
            string1 = g_strconcat(FileTag->track,"/",FileTag->track_total,NULL);
        else
            string1 = g_strdup(FileTag->track);

        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, string1);
        g_free(string1);
        has_track = TRUE;
    }


    /*********
     * Genre *
     *********
     * Genre is written like this :
     *    - "(<genre_id>)"              -> "(3)"
     *    - "(<genre_id>)<refinement>"  -> "(3)EuroDance"
     */
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_CONTENTTYPE)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->genre && strlen(FileTag->genre)>0 )
    {
        gchar *genre_string_tmp;
        guchar genre_value;

        id3_frame = ID3Frame_NewID(ID3FID_CONTENTTYPE);
        ID3Tag_AttachFrame(id3_tag,id3_frame);

        genre_value = Id3tag_String_To_Genre(FileTag->genre);
        // If genre not defined don't write genre value between brackets! (priority problem noted with some tools)
        if (genre_value == ID3_INVALID_GENRE)
            genre_string_tmp = g_strdup_printf("%s",FileTag->genre);
        else
            genre_string_tmp = g_strdup_printf("(%d)",genre_value);

        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, genre_string_tmp);
        g_free(genre_string_tmp);
        has_genre = TRUE;
    }


    /***********
     * Comment *
     ***********/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_COMMENT)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->comment && g_utf8_strlen(FileTag->comment, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_COMMENT);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->comment);
        // These 2 following fields allow synchronisation between id3v2 and id3v1 tags with id3lib
        // Disabled as when using unicode, the comment field stay in ISO.
        //Id3tag_Set_Field(id3_frame, ID3FN_DESCRIPTION, "ID3v1 Comment");
        //Id3tag_Set_Field(id3_frame, ID3FN_LANGUAGE, "XXX");
        has_comment = TRUE;
    }


    /************
     * Composer *
     ************/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_COMPOSER)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->composer && g_utf8_strlen(FileTag->composer, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_COMPOSER);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->composer);
        has_composer = TRUE;
    }


    /*******************
     * Original artist *
     *******************/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_ORIGARTIST)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->orig_artist && g_utf8_strlen(FileTag->orig_artist, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_ORIGARTIST);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->orig_artist);
        has_orig_artist = TRUE;
    }


    /*************
     * Copyright *
     *************/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_COPYRIGHT)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->copyright && g_utf8_strlen(FileTag->copyright, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_COPYRIGHT);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->copyright);
        has_copyright = TRUE;
    }


    /*******
     * URL *
     *******/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_WWWUSER)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->url && g_utf8_strlen(FileTag->url, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_WWWUSER);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_URL, FileTag->url);
        has_composer = TRUE;
    }


    /**************
     * Encoded by *
     **************/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_ENCODEDBY)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (FileTag->encoded_by && g_utf8_strlen(FileTag->encoded_by, -1) > 0)
    {
        id3_frame = ID3Frame_NewID(ID3FID_ENCODEDBY);
        ID3Tag_AttachFrame(id3_tag,id3_frame);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->encoded_by);
        has_encoded_by = TRUE;
    }


    /***********
     * Picture *
     ***********/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_PICTURE)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    pic = FileTag->picture;
    if (!pic)
        has_picture = 0;
    while (pic)
    {
        id3_frame = ID3Frame_NewID(ID3FID_PICTURE);
        ID3Tag_AttachFrame(id3_tag,id3_frame);

        switch(Picture_Format(pic))
        {
            case PICTURE_FORMAT_JPEG:
                if ((id3_field = ID3Frame_GetField(id3_frame,ID3FN_MIMETYPE)))
                    ID3Field_SetASCII(id3_field, "image/jpeg");
                if ((id3_field = ID3Frame_GetField(id3_frame,ID3FN_IMAGEFORMAT)))
                    ID3Field_SetASCII(id3_field, "JPG");
                break;
        
            case PICTURE_FORMAT_PNG:
                if ((id3_field = ID3Frame_GetField(id3_frame,ID3FN_MIMETYPE)))
                    ID3Field_SetASCII(id3_field, "image/png");
                if ((id3_field = ID3Frame_GetField(id3_frame,ID3FN_IMAGEFORMAT)))
                    ID3Field_SetASCII(id3_field, "PNG");
                break;
        }
        
        if ((id3_field = ID3Frame_GetField(id3_frame, ID3FN_PICTURETYPE)))
            ID3Field_SetINT(id3_field, pic->type);

        if (pic->description)
            Id3tag_Set_Field(id3_frame, ID3FN_DESCRIPTION, pic->description);
        
        if ((id3_field = ID3Frame_GetField(id3_frame,ID3FN_DATA)))
            ID3Field_SetBINARY(id3_field, pic->data, pic->size);
        
        pic = pic->next;
        has_picture = TRUE;
    }


    /*********************************
     * File length (in milliseconds) *
     *********************************/
    while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_SONGLEN)) )
        ID3Tag_RemoveFrame(id3_tag,id3_frame);
    if (ETFile->ETFileInfo && ((ET_File_Info *)ETFile->ETFileInfo)->duration > 0 )
    {
        gchar *string;
        
        id3_frame = ID3Frame_NewID(ID3FID_SONGLEN);
        ID3Tag_AttachFrame(id3_tag,id3_frame);

        string = g_strdup_printf("%d",((ET_File_Info *)ETFile->ETFileInfo)->duration * 1000);
        Id3tag_Set_Field(id3_frame, ID3FN_TEXT, string);
        g_free(string);
        has_song_len = TRUE;
    }


    /*********************************
     * Update id3v1.x and id3v2 tags *
     *********************************/
    /* Get the number of frames into the tag, cause if it is
     * equal to 0, id3lib-3.7.12 doesn't update the tag */
    number_of_frames = ID3Tag_NumFrames(id3_tag);

    /* If all fields (managed in the UI) are empty and option STRIP_TAG_WHEN_EMPTY_FIELDS
     * is set to 1, we strip the ID3v1.x and ID3v2 tags. Else, write ID3v2 and/or ID3v1
     */
    if ( STRIP_TAG_WHEN_EMPTY_FIELDS
    && !has_title      && !has_artist   && !has_album       && !has_year      && !has_track
    && !has_genre      && !has_composer && !has_orig_artist && !has_copyright && !has_url
    && !has_encoded_by && !has_picture  && !has_comment     && !has_disc_number)//&& !has_song_len )
    {
        error_strip_id3v1 = ID3Tag_Strip(id3_tag,ID3TT_ID3V1);
        error_strip_id3v2 = ID3Tag_Strip(id3_tag,ID3TT_ID3V2);
        /* Check error messages */
        if (error_strip_id3v1 == ID3E_NoError && error_strip_id3v2 == ID3E_NoError)
        {
            g_print(_("Removed tag of '%s'\n"),basename_utf8);
        }else
        {
            if (error_strip_id3v1 != ID3E_NoError)
                g_print(_("Error while removing ID3v1 tag of '%s' (%s)\n"),basename_utf8,Id3tag_Get_Error_Message(error_strip_id3v1));
            if (error_strip_id3v2 != ID3E_NoError)
                g_print(_("Error while removing ID3v2 tag of '%s' (%s)\n"),basename_utf8,Id3tag_Get_Error_Message(error_strip_id3v2));
            error++;
        }

    }else
    {
        /* It's better to remove the id3v1 tag before, to synchronize it with the
         * id3v2 tag (else id3lib doesn't do it correctly)
         */
        error_strip_id3v1 = ID3Tag_Strip(id3_tag,ID3TT_ID3V1);

        /*
         * ID3v2 tag
         */
        if (WRITE_ID3V2_TAG && number_of_frames!=0)
        {
            error_update_id3v2 = ID3Tag_UpdateByTagType(id3_tag,ID3TT_ID3V2);
            if (error_update_id3v2 != ID3E_NoError)
            {
                g_print(_("Error while updating ID3v2 tag of '%s' (%s)\n"),basename_utf8,Id3tag_Get_Error_Message(error_update_id3v2));
                error++;
            }
        }else
        {
            error_strip_id3v2 = ID3Tag_Strip(id3_tag,ID3TT_ID3V2);
            if (error_strip_id3v2 != ID3E_NoError)
            {
                g_print(_("Error while removing ID3v2 tag of '%s' (%s)\n"),basename_utf8,Id3tag_Get_Error_Message(error_strip_id3v2));
                error++;
            }
        }

        /*
         * ID3v1 tag
         * Must be set after ID3v2 or ID3Tag_UpdateByTagType cause damage to unicode strings
         */
        // id3lib writes incorrectly the ID3v2 tag if unicode used when writing ID3v1 tag
        if (WRITE_ID3V1_TAG && number_of_frames!=0)
        {
            // By default id3lib converts id3tag to ISO-8859-1 (single byte character set)
            // If USE_NON_STANDARD_ID3_WRITING_CHARACTER_SET is activated, the character set
            // in ID3_FILE_WRITING_CHARACTER_SET will be used, but must be also a single
            // byte character set.
            // Note : converting UTF-16 string (two bytes character set) to ISO-8859-1
            //        remove only the second byte => a strange string appears...
            Id3tag_Prepare_ID3v1(id3_tag);
            
            error_update_id3v1 = ID3Tag_UpdateByTagType(id3_tag,ID3TT_ID3V1);
            if (error_update_id3v1 != ID3E_NoError)
            {
                g_print(_("Error while updating ID3v1 tag of '%s' (%s)\n"),basename_utf8,Id3tag_Get_Error_Message(error_update_id3v1));
                error++;
            }
        }else
        {
            error_strip_id3v1 = ID3Tag_Strip(id3_tag,ID3TT_ID3V1);
            if (error_strip_id3v1 != ID3E_NoError)
            {
                g_print(_("Error while removing ID3v1 tag of '%s' (%s)\n"),basename_utf8,Id3tag_Get_Error_Message(error_strip_id3v1));
                error++;
            }
        }

        if (error == 0)
            g_print(_("Updated tag of '%s'\n"),basename_utf8);

    }

    /* Free allocated data */
    ID3Tag_Delete(id3_tag);
    g_free(basename_utf8);

    if (error) return FALSE;
    else       return TRUE;

}


/*
 * Return the sub version of the ID3v2 tag, for example id3v2.2, id3v2.3
 */
gint Id3tag_Get_Id3v2_Version (gchar *filename)
{
    FILE  *file;
    guchar tmp[4];

    if ( filename!=NULL && (file=fopen(filename,"r"))!=NULL )
    {
        fseek(file,0,SEEK_SET);
        if (fread(tmp,1,4,file) != 4)
        {
            fclose(file);
            return -1;
        }

        if (tmp[0] == 'I' && tmp[1] == 'D' && tmp[2] == '3' && tmp[3] < 0xFF)
        {
            fclose(file);
            return (gint)tmp[3];
        }else
        {
            fclose(file);
            return -1;
        }
    }else
    {
        return -1;
    }
}


gchar *Id3tag_Get_Error_Message(ID3_Err error)
{
    switch (error)
    {
        case ID3E_NoError:
            return _("No error reported");
        case ID3E_NoMemory:
            return _("No available memory");
        case ID3E_NoData:
            return _("No data to parse");
        case ID3E_BadData:
            return _("Improperly formatted data");
        case ID3E_NoBuffer:
            return _("No buffer to write to");
        case ID3E_SmallBuffer:
            return _("Buffer is too small");
        case ID3E_InvalidFrameID:
            return _("Invalid frame ID");
        case ID3E_FieldNotFound:
            return _("Requested field not found");
        case ID3E_UnknownFieldType:
            return _("Unknown field type");
        case ID3E_TagAlreadyAttached:
            return _("Tag is already attached to a file");
        case ID3E_InvalidTagVersion:
            return _("Invalid tag version");
        case ID3E_NoFile:
            return _("No file to parse");
        case ID3E_ReadOnly:
            return _("Attempting to write to a read-only file");
        case ID3E_zlibError:
            return _("Error in compression/uncompression");
        default:
            return _("Unknown error message!");
    }

}



/*
 * As the ID3Tag_Link function of id3lib-3.8.0pre2 returns the ID3v1 tags
 * when a file has both ID3v1 and ID3v2 tags, we first try to explicitely
 * get the ID3v2 tags with ID3Tag_LinkWithFlags and, if we cannot get them,
 * fall back to the ID3v1 tags.
 * (Written by Holger Schemel).
 */
ID3_C_EXPORT size_t ID3Tag_Link_1 (ID3Tag *id3tag, const char *filename)
{
    size_t offset;

#   if (0) // Link the file with the both tags may cause damage to unicode strings
//#   if ( (ID3LIB_MAJOR >= 3) && (ID3LIB_MINOR >= 8) && (ID3LIB_PATCH >= 1) ) // Same test used in Id3tag_Read_File_Tag to use ID3Tag_HasTagType
        /* No problem of priority, so we link the file with the both tags
         * to manage => ID3Tag_HasTagType works correctly */
        offset = ID3Tag_LinkWithFlags(id3tag,filename,ID3TT_ID3V1 | ID3TT_ID3V2);
#   elif ( (ID3LIB_MAJOR >= 3) && (ID3LIB_MINOR >= 8) )
        /* Version 3.8.0pre2 gives priority to tag id3v1 instead of id3v2, so we
         * try to fix it by linking the file with the id3v2 tag first. This bug 
         * was fixed in the final version of 3.8.0 but we can't know it... */
        /* First, try to get the ID3v2 tags */
        offset = ID3Tag_LinkWithFlags(id3tag,filename,ID3TT_ID3V2);
        if (offset == 0)
        {
            /* No ID3v2 tags available => try to get the ID3v1 tags */
            offset = ID3Tag_LinkWithFlags(id3tag,filename,ID3TT_ID3V1);
        }
#   else
        /* Function 'ID3Tag_LinkWithFlags' is not defined up to id3lib-.3.7.13 */
        offset = ID3Tag_Link(id3tag,filename);
#   endif
    //g_print("ID3 TAG SIZE: %d\t%s\n",offset,g_path_get_basename(filename));
    return offset;
}


/*
 * As the ID3Field_GetASCII function differs with the version of id3lib, we must redefine it.
 */
ID3_C_EXPORT size_t ID3Field_GetASCII_1(const ID3Field *field, char *buffer, size_t maxChars, size_t itemNum)
{

    /* Defined by id3lib:   ID3LIB_MAJOR_VERSION, ID3LIB_MINOR_VERSION, ID3LIB_PATCH_VERSION
     * Defined by autoconf: ID3LIB_MAJOR,         ID3LIB_MINOR,         ID3LIB_PATCH
     *
     * <= 3.7.12 : first item num is 1 for ID3Field_GetASCII
     *  = 3.7.13 : first item num is 0 for ID3Field_GetASCII
     * >= 3.8.0  : doesn't need item num for ID3Field_GetASCII
     */
     //g_print("id3lib version: %d.%d.%d\n",ID3LIB_MAJOR,ID3LIB_MINOR,ID3LIB_PATCH);
#    if (ID3LIB_MAJOR >= 3)
         // (>= 3.x.x)
#        if (ID3LIB_MINOR <= 7)
             // (3.0.0 to 3.7.x)
#            if (ID3LIB_PATCH >= 13)
                 // (>= 3.7.13)
                 return ID3Field_GetASCII(field,buffer,maxChars,itemNum);
#            else
                 return ID3Field_GetASCII(field,buffer,maxChars,itemNum+1);
#            endif
#        else
             // (>= to 3.8.0)
             //return ID3Field_GetASCII(field,buffer,maxChars);
             return ID3Field_GetASCIIItem(field,buffer,maxChars,itemNum);
#        endif
#    else
         // Not tested (< 3.x.x)
         return ID3Field_GetASCII(field,buffer,maxChars,itemNum+1);
#    endif
}



/*
 * As the ID3Field_GetUNICODE function differs with the version of id3lib, we must redefine it.
 */
ID3_C_EXPORT size_t ID3Field_GetUNICODE_1 (const ID3Field *field, unicode_t *buffer, size_t maxChars, size_t itemNum)
{

    /* Defined by id3lib:   ID3LIB_MAJOR_VERSION, ID3LIB_MINOR_VERSION, ID3LIB_PATCH_VERSION
     * Defined by autoconf: ID3LIB_MAJOR,         ID3LIB_MINOR,         ID3LIB_PATCH
     *
     * <= 3.7.12 : first item num is 1 for ID3Field_GetUNICODE
     *  = 3.7.13 : first item num is 0 for ID3Field_GetUNICODE
     * >= 3.8.0  : doesn't need item num for ID3Field_GetUNICODE
     */
     //g_print("id3lib version: %d.%d.%d\n",ID3LIB_MAJOR,ID3LIB_MINOR,ID3LIB_PATCH);
#    if (ID3LIB_MAJOR >= 3)
         // (>= 3.x.x)
#        if (ID3LIB_MINOR <= 7)
             // (3.0.0 to 3.7.x)
#            if (ID3LIB_PATCH >= 13)
                 // (>= 3.7.13)
                 return ID3Field_GetUNICODE(field,buffer,maxChars,itemNum);
#            else
                 return ID3Field_GetUNICODE(field,buffer,maxChars,itemNum+1);
#            endif
#        else
             // (>= to 3.8.0)
             return ID3Field_GetUNICODE(field,buffer,maxChars);
             // ID3Field_GetUNICODEItem always return 0 with id3lib3.8.3, it is bug in size_t D3_FieldImpl::Get()
             //return ID3Field_GetUNICODEItem(field,buffer,maxChars,itemNum);
#        endif
#    else
         // Not tested (< 3.x.x)
         return ID3Field_GetUNICODE(field,buffer,maxChars,itemNum+1);
#    endif
}




/*
 * Source : "http://www.id3.org/id3v2.4.0-structure.txt"
 *
 * Frames that allow different types of text encoding contains a text
 *   encoding description byte. Possible encodings:
 *
 *     $00   ISO-8859-1 [ISO-8859-1]. Terminated with $00.
 *     $01   UTF-16 [UTF-16] encoded Unicode [UNICODE] with BOM ($FF FE
 *           or $FE FF). All strings in the same frame SHALL have the same
 *           byteorder. Terminated with $00 00.
 *     $02   UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM.
 *           Terminated with $00 00.
 *     $03   UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00.
 *
 * For example :
 *         T  P  E  1  .  .  .  .  .  .  .  ?  ?  J  .  o  .  n  .     .  G  .  i  .  n  .  d  .  i  .  c  .  k  . 
 * Hex :   54 50 45 31 00 00 00 19 00 00 01 ff fe 4a 00 6f 00 6e 00 20 00 47 00 69 00 6e 00 64 00 6e 00 63 00 6b 00
 *                                       ^
 *                                       |___ UTF-16
 */
/*
 * Read the content (ID3FN_TEXT, ID3FN_URL, ...) of the id3_field of the
 * id3_frame, and convert the string if needed to UTF-8.
 */
gchar *Id3tag_Get_Field (const ID3Frame *id3_frame, ID3_FieldID id3_fieldid)
{
    ID3Field *id3_field = NULL;
    ID3Field *id3_field_encoding = NULL;
    size_t num_chars = 0;
    gchar *string = NULL, *string1 = NULL;

    //g_print("Id3tag_Get_Field - ID3Frame '%s'\n",ID3FrameInfo_ShortName(ID3Frame_GetID(id3_frame)));

    if ( (id3_field = ID3Frame_GetField(id3_frame,id3_fieldid)) )
    {
        ID3_TextEnc enc = ID3TE_NONE;

        // Data of the field must be a TEXT (ID3FTY_TEXTSTRING)
        if (ID3Field_GetType(id3_field) != ID3FTY_TEXTSTRING)
        {
            g_print("Id3tag_Get_Field() must be used only for fields containing text.\n");
            return NULL;
        }

        #if (0)

        /*
         * In this part we prioritize the encoding specified by the user.
         */
        // Get encoding of the field
        if (USE_NON_STANDARD_ID3_READING_CHARACTER_SET) // Override with an other character set?
        {
            // Encoding set by user to ???.
            if ( strcmp(ID3_FILE_READING_CHARACTER_SET,"ISO-8859-1") == 0 )
            {
                enc = ID3TE_ISO8859_1;
            }else if ( strcmp(ID3_FILE_READING_CHARACTER_SET,"UTF-16BE") == 0 
                  ||   strcmp(ID3_FILE_READING_CHARACTER_SET,"UTF-16LE") == 0 )
            {
                enc = ID3TE_UTF16;
            }else if ( strcmp(ID3_FILE_READING_CHARACTER_SET,"UTF-8") == 0 )
            {
                enc = ID3TE_UTF8;
            }else
            {
                enc = 9999;
            }
        }else
        {
            // No encoding set by user. Get encoding from content of file...
            id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
            if (id3_field_encoding)
                enc = ID3Field_GetINT(id3_field_encoding);
            // Else, get encoding from the field
            //enc = ID3Field_GetEncoding(id3_field);
        }
        
        #else
        
        /*
         * In this part we prioritize the encoding of the field. If the encoding 
         * of the field is ISO-8859-1, it can be read with an other single byte encoding.
         */
        // Get encoding from content of file...
        id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
        if (id3_field_encoding)
            enc = ID3Field_GetINT(id3_field_encoding);
        // Else, get encoding from the field
        //enc = ID3Field_GetEncoding(id3_field);
        
        if (enc != ID3TE_UTF16 && enc != ID3TE_UTF8) // Encoding is ISO-8859-1?
        {
            if (USE_NON_STANDARD_ID3_READING_CHARACTER_SET) // Override with an other character set?
            {
                // Encoding set by user to ???.
                if ( strcmp(ID3_FILE_READING_CHARACTER_SET,"ISO-8859-1") == 0 )
                {
                    enc = ID3TE_ISO8859_1;
                }else if ( strcmp(ID3_FILE_READING_CHARACTER_SET,"UTF-16BE") == 0 
                      ||   strcmp(ID3_FILE_READING_CHARACTER_SET,"UTF-16LE") == 0 )
                {
                    enc = ID3TE_UTF16;
                }else if ( strcmp(ID3_FILE_READING_CHARACTER_SET,"UTF-8") == 0 )
                {
                    enc = ID3TE_UTF8;
                }else
                {
                    enc = 9999;
                }
            }
        }
        #endif

        // Some fields, as URL, aren't encodable, so there were written using ISO characters.
        if ( !ID3Field_IsEncodable(id3_field) )
        {
            enc = ID3TE_ISO8859_1;
        }
        
        // Action according the encoding...
        switch ( enc )
        {
            case ID3TE_ISO8859_1:
                string = g_malloc0(sizeof(char)*ID3V2_MAX_STRING_LEN+1);
                num_chars = ID3Field_GetASCII_1(id3_field,string,ID3V2_MAX_STRING_LEN,0);
                string1 = convert_string(string,"ISO-8859-1","UTF-8",FALSE);
                break;

            case ID3TE_UTF8: // Shouldn't work with id3lib 3.8.3 (supports only ID3v2.3, not ID3v2.4)
                // For UTF-8, this part do the same thing that enc=9999
                string = g_malloc0(sizeof(char)*ID3V2_MAX_STRING_LEN+1);
                num_chars = ID3Field_GetASCII_1(id3_field,string,ID3V2_MAX_STRING_LEN,0);
                //string1 = convert_string(string,"UTF-8","UTF-8",FALSE); // Nothing to do
                if (g_utf8_validate(string,-1,NULL))
                    string1 = g_strdup(string);
                break;

            case ID3TE_UTF16:
                // Id3lib (3.8.3 at least) always returns Unicode strings in UTF-16BE.
            case ID3TE_UTF16BE:
                string = g_malloc0(sizeof(unicode_t)*ID3V2_MAX_STRING_LEN+1);
                num_chars = ID3Field_GetUNICODE_1(id3_field,(unicode_t *)string,ID3V2_MAX_STRING_LEN,0);
                // "convert_string_1" as we need to pass length for UTF-16
                string1 = convert_string_1(string,num_chars,"UTF-16BE","UTF-8",FALSE);
                break;

            case 9999:
                string = g_malloc0(sizeof(char)*ID3V2_MAX_STRING_LEN+1);
                num_chars = ID3Field_GetASCII_1(id3_field,string,ID3V2_MAX_STRING_LEN,0);
                string1 = convert_string(string,ID3_FILE_READING_CHARACTER_SET,"UTF-8",FALSE);
                break;

            default:
                string = g_malloc0(sizeof(char)*4*ID3V2_MAX_STRING_LEN+1);
                num_chars = ID3Field_GetASCII_1(id3_field,string,ID3V2_MAX_STRING_LEN,0);
                string1 = convert_to_utf8(string);
                break;
        }
    }
    //g_print(">>ID:%d >'%s' (string1:'%s') (num_chars:%d)\n",ID3Field_GetINT(id3_field_encoding),string,string1,num_chars);

    // In case the conversion fails, try 'filename_to_display' character fix...
    if (num_chars && !string1)
    {
        gchar *escaped_str = g_strescape(string, NULL);
        g_print("Id3tag_Get_Field: Trying to fix string '%s' ...",escaped_str);
        g_free(escaped_str);
        
        string1 = filename_to_display(string);
        
        if (string1)
            g_print("OK\n");
        else
            g_print("KO\n");
    }
    g_free(string);

    return string1;
}


/*
 * Set the content (ID3FN_TEXT, ID3FN_URL, ...) of the id3_field of the
 * id3_frame. Check also if the string must be written from UTF-8 (gtk2) in the
 * ISO-8859-1 encoding or UTF-16.
 *
 * Return the encoding used as if UTF-16 was used, we musn't write the ID3v1 tag.
 */
/*
 * OLD NOTE : PROBLEM with ID3LIB
 * - [ 1074169 ] Writing ID3v1 tag breaks Unicode string in v2 tags
 *               http://sourceforge.net/tracker/index.php?func=detail&aid=1074169&group_id=979&atid=100979
 *      => don't write id3v1 tag if Unicode is used, up to patch applied
 * - [ 1073951 ] Added missing Field Encoding functions to C wrapper
 *               http://sourceforge.net/tracker/index.php?func=detail&aid=1073951&group_id=979&atid=300979
 */
ID3_TextEnc Id3tag_Set_Field (const ID3Frame *id3_frame, ID3_FieldID id3_fieldid, gchar *string)
{
    ID3Field *id3_field = NULL;
    ID3Field *id3_field_encoding = NULL;
    gchar *string_converted = NULL;

    if ( (id3_field = ID3Frame_GetField(id3_frame,id3_fieldid)) )
    {
        ID3_TextEnc enc = ID3TE_NONE;

        // Data of the field must be a TEXT (ID3FTY_TEXTSTRING)
        if (ID3Field_GetType(id3_field) != ID3FTY_TEXTSTRING)
        {
            g_print("Id3tag_Set_Field() must be used only for fields containing text.\n");
            return ID3TE_NONE;
        }

        #if (0)

        /*
         * In this part we prioritize the encoding specified by the user.
         */
        // Determine encoding to use for the field
        if (USE_NON_STANDARD_ID3_WRITING_CHARACTER_SET) // Override with an other character set?
        {
            // Encoding set by user to ???.
            if ( strcmp(ID3_FILE_WRITING_CHARACTER_SET,"ISO-8859-1") == 0 )
            {
                enc = ID3TE_ISO8859_1;
            }else if ( strcmp(ID3_FILE_WRITING_CHARACTER_SET,"UTF-16BE") == 0 
                  ||   strcmp(ID3_FILE_WRITING_CHARACTER_SET,"UTF-16LE") == 0 )
            {
                enc = ID3TE_UTF16;
            }else if ( strcmp(ID3_FILE_WRITING_CHARACTER_SET,"UTF-8") == 0 )
            {
                enc = ID3TE_UTF8;
            }else
            {
                enc = 9999;
            }
        }else if (USE_ID3_ISO_8859_1_CHARACTER_SET)
        {
            enc = ID3TE_ISO8859_1;
            
        }else if (USE_ID3_ISO_8859_1_THEN_UNICODE_CHARACTER_SET)
        {
            // No encoding set by user. Check if we can write the tag using ISO-8859-1 or UTF-16...
            if ( (string_converted = g_convert(string, strlen(string), "ISO-8859-1", 
                                               "UTF-8", NULL, NULL ,NULL)) )
            {
                enc = ID3TE_ISO8859_1;
                g_free(string_converted);
            }else
            {
                enc = ID3TE_UTF16;
            }
        }else if (USE_ID3_UNICODE_CHARACTER_SET)
        {
            enc = ID3TE_UTF16;
        }
        
        #else
        
        /*
         * In this part we prioritize the rule selected in options. If the encoding 
         * of the field is ISO-8859-1, we can write it to an other single byte encoding.
         */
        if (USE_ID3_ISO_8859_1_CHARACTER_SET)
        {
            enc = ID3TE_ISO8859_1;
            
        }else if (USE_ID3_ISO_8859_1_THEN_UNICODE_CHARACTER_SET)
        {
            // No encoding set by user. Check if we can write the tag using ISO-8859-1 or UTF-16...
            if ( (string_converted = g_convert(string, strlen(string), "ISO-8859-1", 
                                               "UTF-8", NULL, NULL ,NULL)) )
            {
                enc = ID3TE_ISO8859_1;
                g_free(string_converted);
            }else
            {
                enc = ID3TE_UTF16;
            }
        }else //if (USE_ID3_UNICODE_CHARACTER_SET)
        {
            enc = ID3TE_UTF16;
        }
        
        if (enc != ID3TE_UTF16) // Encoding is ISO-8859-1?
        {
            // Determine encoding to use for the field
            if (USE_NON_STANDARD_ID3_WRITING_CHARACTER_SET) // Override with an other character set?
            {
                // Encoding set by user to ???.
                if ( strcmp(ID3_FILE_WRITING_CHARACTER_SET,"ISO-8859-1") == 0 )
                {
                    enc = ID3TE_ISO8859_1;
                }else if ( strcmp(ID3_FILE_WRITING_CHARACTER_SET,"UTF-16BE") == 0 
                      ||   strcmp(ID3_FILE_WRITING_CHARACTER_SET,"UTF-16LE") == 0 )
                {
                    enc = ID3TE_UTF16;
                }else if ( strcmp(ID3_FILE_WRITING_CHARACTER_SET,"UTF-8") == 0 )
                {
                    enc = ID3TE_UTF8;
                }else
                {
                    enc = 9999;
                }
            }
        }
        
        #endif

        // Some fields, as URL, aren't encodable, so there were written using ISO characters!
        if ( !ID3Field_IsEncodable(id3_field) )
        {
            enc = ID3TE_ISO8859_1;
        }
        
        // Action according the encoding...
        switch ( enc )
        {
            case ID3TE_ISO8859_1:
                // Write into ISO-8859-1
                //string_converted = convert_string(string,"UTF-8","ISO-8859-1",TRUE);
                string_converted = Id3tag_Rules_For_ISO_Fields(string,"UTF-8","ISO-8859-1");
                ID3Field_SetEncoding(id3_field,ID3TE_ISO8859_1); // Not necessary for ISO-8859-1, but better to precise if field has an other encoding...
                ID3Field_SetASCII(id3_field,string_converted);
                g_free(string_converted);
                
                id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
                if (id3_field_encoding)
                    ID3Field_SetINT(id3_field_encoding,ID3TE_ISO8859_1);
                
                return ID3TE_ISO8859_1;
                break;

            /*** Commented as it doesn't with id3lib 3.8.3 :
             ***  - it writes a strange UTF-8 string (2 bytes per character. Second char is FF) with a BOM
             ***  - it set the frame encoded to UTF-8 : "$03" which is OK
            case ID3TE_UTF8: // Shouldn't work with id3lib 3.8.3 (supports only ID3v2.3, not ID3v2.4)
                ID3Field_SetEncoding(id3_field,ID3TE_UTF8);
                ID3Field_SetASCII(id3_field,string);
                
                id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
                if (id3_field_encoding)
                    ID3Field_SetINT(id3_field_encoding,ID3TE_UTF8);
                
                return ID3TE_UTF8;
                break;
             ***/

            case ID3TE_UTF16:
            //case ID3TE_UTF16BE:
                // Write into UTF-16
                string_converted = convert_string_1(string, strlen(string), "UTF-8",
                                                    "UTF-16BE", FALSE);
    
                // id3lib (3.8.3 at least) always takes big-endian input for Unicode
                // fields, even if the field is set little-endian.
                ID3Field_SetEncoding(id3_field,ID3TE_UTF16);
                ID3Field_SetUNICODE(id3_field,(const unicode_t*)string_converted);
                g_free(string_converted);
                
                id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
                if (id3_field_encoding)
                    ID3Field_SetINT(id3_field_encoding,ID3TE_UTF16);
                
                return ID3TE_UTF16;
                break;
            
            case 9999:
                //string_converted = convert_string(string,"UTF-8",ID3_FILE_WRITING_CHARACTER_SET,TRUE);
                string_converted = Id3tag_Rules_For_ISO_Fields(string,"UTF-8",ID3_FILE_WRITING_CHARACTER_SET);
                ID3Field_SetEncoding(id3_field,ID3TE_ISO8859_1);
                ID3Field_SetASCII(id3_field,string_converted);
                g_free(string_converted);
                
                id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
                if (id3_field_encoding)
                    ID3Field_SetINT(id3_field_encoding,ID3TE_ISO8859_1);
                
                return ID3TE_NONE;
                break;

            default:
                string_converted = convert_from_utf8(string);
                ID3Field_SetEncoding(id3_field,ID3TE_ISO8859_1);
                ID3Field_SetASCII(id3_field,string_converted);
                g_free(string_converted);
                
                id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
                if (id3_field_encoding)
                    ID3Field_SetINT(id3_field_encoding,ID3TE_ISO8859_1);
                
                return ID3TE_NONE;
                break;
        }
    }
    
    return ID3TE_NONE;
}


/*
 * By default id3lib converts id3tag to ISO-8859-1 (single byte character set)
 * If USE_NON_STANDARD_ID3_WRITING_CHARACTER_SET is activated, the character set
 * in ID3_FILE_WRITING_CHARACTER_SET will be used, but must be also a single
 * byte character set.
 * Note : converting UTF-16 string (two bytes character set) to ISO-8859-1
 *        remove only the second byte => a strange string appears...
 */
void Id3tag_Prepare_ID3v1 (ID3Tag *id3_tag)
{
    ID3Frame *frame;
    ID3Field *id3_field_encoding;
    ID3Field *id3_field_text;
    
    if ( id3_tag != NULL )
    {
        ID3TagIterator *id3_tag_iterator;
        size_t num_chars = 0;
        gchar *string, *string1, *string_converted;
        
        id3_tag_iterator = ID3Tag_CreateIterator(id3_tag);
        while ( NULL != (frame = ID3TagIterator_GetNext(id3_tag_iterator)) )
        {
            ID3_TextEnc enc = ID3TE_ISO8859_1;
            ID3_FrameID frameid;
            
            frameid = ID3Frame_GetID(frame);
            
            if (frameid != ID3FID_TITLE
            &&  frameid != ID3FID_LEADARTIST
            &&  frameid != ID3FID_ALBUM
            &&  frameid != ID3FID_YEAR
            &&  frameid != ID3FID_TRACKNUM
            &&  frameid != ID3FID_CONTENTTYPE
            &&  frameid != ID3FID_COMMENT)
                continue;
            
            id3_field_encoding = ID3Frame_GetField(frame, ID3FN_TEXTENC);
            if (id3_field_encoding != NULL)
                enc = ID3Field_GetINT(id3_field_encoding);
            id3_field_text = ID3Frame_GetField(frame, ID3FN_TEXT);
            
            /* The frames in ID3TE_ISO8859_1 are already converted to the selected
             * single-byte character set if used. So we treat only Unicode frames */
            if ( (id3_field_text != NULL)
            &&   (enc != ID3TE_ISO8859_1) )
            {
                // Read UTF-16 frame
                string = g_malloc0(sizeof(unicode_t)*ID3V2_MAX_STRING_LEN+1);
                num_chars = ID3Field_GetUNICODE_1(id3_field_text,(unicode_t *)string,ID3V2_MAX_STRING_LEN,0);
                // "convert_string_1" as we need to pass length for UTF-16
                string1 = convert_string_1(string,num_chars,"UTF-16BE","UTF-8",FALSE);
                
                // Write frame to the selected single byte character set.
                if (USE_NON_STANDARD_ID3_WRITING_CHARACTER_SET)
                {
                    //string_converted = convert_string(string1,"UTF-8",ID3_FILE_WRITING_CHARACTER_SET,TRUE);
                    string_converted = Id3tag_Rules_For_ISO_Fields(string1,"UTF-8",ID3_FILE_WRITING_CHARACTER_SET);
                }else
                {
                    //string_converted = convert_string(string1,"UTF-8","ISO-8859-1",TRUE);
                    string_converted = Id3tag_Rules_For_ISO_Fields(string1,"UTF-8","ISO-8859-1");
                }
                
                if (string_converted)
                {
                    ID3Field_SetEncoding(id3_field_text,ID3TE_ISO8859_1); // Not necessary for ISO-8859-1
                    ID3Field_SetASCII(id3_field_text,string_converted);
                    ID3Field_SetINT(id3_field_encoding,ID3TE_ISO8859_1);
                    g_free(string_converted);
                }
                g_free(string);
                g_free(string1);
            }
        }
        ID3TagIterator_Delete(id3_tag_iterator);
    }
}

/*
 * This function must be used for tag fields containing ISO data.
 * We use specials functionalities of iconv : //TRANSLIT and //IGNORE (the last 
 * one doesn't work on my system) to force conversion to the target encoding.
 */
gchar *Id3tag_Rules_For_ISO_Fields (const gchar *string, const gchar *from_codeset, const gchar *to_codeset)
{
    gchar *string_converted = NULL;
    
    if (!string || !from_codeset || !to_codeset)
        return NULL;
    
    if (FILE_WRITING_CHARACTER_SET_OTHER)
    {
        string_converted = convert_string(string,from_codeset,to_codeset,TRUE);
        
    }else if (FILE_WRITING_CHARACTER_SET_APPROXIMATE)
    {
        // iconv_open (3):
        // When the string "//TRANSLIT" is appended to tocode, transliteration
        // is activated. This means that when a character cannot be represented
        // in the target character set, it can be approximated through one or
        // several similarly looking characters.
        gchar *to_enc = g_strconcat(to_codeset, "//TRANSLIT", NULL);
        string_converted = convert_string(string,from_codeset,to_enc,TRUE);
        g_free(to_enc);
        
    }else if (FILE_WRITING_CHARACTER_SET_DISCARD)
    {
        // iconv_open (3):
        // When the string "//IGNORE" is appended to tocode, characters that
        // cannot be represented in the target character set will be silently
        // discarded.
        gchar *to_enc = g_strconcat(to_codeset, "//IGNORE", NULL);
        string_converted = convert_string(string,from_codeset,to_enc,TRUE);
        g_free(to_enc);
    }
    
    return string_converted;
}

/*
 * Some files which contains only zeroes create an infinite loop in id3lib...
 * To generate a file with zeroes : dd if=/dev/zero bs=1M count=6 of=test-corrupted-mp3-zero-contend.mp3
 */
gboolean Id3tag_Check_If_File_Is_Corrupted (gchar *filename)
{
    FILE *file;
    unsigned char tmp[256];
    unsigned char tmp0[256];
    gint bytes_read;
    gboolean result = TRUE;
    gint cmp;
    
    if (!filename)
        return FALSE;
    
    if ( (file=fopen(filename,"rb"))==NULL )
    {
        gchar *filename_utf8 = filename_to_display(filename);
        g_print(_("ERROR while opening file: '%s' (%s).\n\a"),filename_utf8,g_strerror(errno));
        g_free(filename_utf8);
        return FALSE;
    }
    
    memset(&tmp0,0,256);
    while (!feof(file))
    {
        bytes_read = fread(tmp, 1, 256, file);
        if ( (cmp=memcmp(tmp,tmp0,bytes_read)) != 0)
        {
            result = FALSE;
            break;
        }
    }
    fclose(file);
    
    if (result)
    {
        GtkWidget *msgbox = NULL;
        gchar *msg;
        gchar *basename;
        gchar *basename_utf8;
        
        basename = g_path_get_basename(filename);
        basename_utf8 = filename_to_display(basename);

        msg = g_strdup_printf(_("As the following corrupted file: '%s'\nwill cause "
            "an error in id3lib, it will not be processed by the program."),basename_utf8);
        msgbox = msg_box_new (_("Corrupted file..."),msg,GTK_STOCK_DIALOG_ERROR,BUTTON_CLOSE,0);
        msg_box_hide_check_button(MSG_BOX(msgbox));
        msg_box_run(MSG_BOX(msgbox));
        gtk_widget_destroy(msgbox);
        g_free(msg);
        g_free(basename);
        g_free(basename_utf8);
    }
    
    return result;
}

#endif /* ENABLE_MP3 */



// Placed out #ifdef ENABLE_MP3 as not dependant of id3lib, and used in CDDB
/*
 * Returns the corresponding genre value of the input string (for ID3v1.x),
 * else returns 0xFF (unknown genre, but not invalid).
 */
guchar Id3tag_String_To_Genre (gchar *genre)
{
    guint i;

    if (genre != NULL)
    {
        for (i=0; i<=GENRE_MAX; i++)
            if (strcasecmp(genre,id3_genres[i])==0)
                return (guchar)i;
    }
    return (guchar)0xFF;
}


/*
 * Returns the name of a genre code if found
 * Three states for genre code :
 *    - defined (0 to GENRE_MAX)
 *    - undefined/unknown (GENRE_MAX+1 to ID3_INVALID_GENRE-1)
 *    - invalid (>ID3_INVALID_GENRE)
 */
gchar *Id3tag_Genre_To_String (unsigned char genre_code)
{
    if (genre_code>=ID3_INVALID_GENRE)    /* empty */
        return "";
    else if (genre_code>GENRE_MAX)        /* unknown tag */
        return "Unknown";
    else                                  /* known tag */
        return id3_genres[genre_code];
}

Generated by  Doxygen 1.6.0   Back to index