1 /* EasyTAG - Tag editor for audio files
2  * Copyright (C) 2014-2015  David King <amigadave@amigadave.com>
3  * Copyright (C) 2001-2003  Jerome Couderc <easytag@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include "config.h"
21 
22 #include <glib/gi18n.h>
23 #include <string.h>
24 #include <errno.h>
25 
26 #include "id3_tag.h"
27 #include "ape_tag.h"
28 #include "picture.h"
29 #include "easytag.h"
30 #include "genres.h"
31 #include "setting.h"
32 #include "misc.h"
33 #include "et_core.h"
34 #include "charset.h"
35 
36 #ifdef ENABLE_MP3
37 
38 #ifdef ENABLE_ID3LIB
39 #include <id3.h>
40 #include "id3lib/id3_bugfix.h"
41 #endif
42 
43 /****************
44  * Declarations *
45  ****************/
46 #define ID3V2_MAX_STRING_LEN 4096
47 #define MULTIFIELD_SEPARATOR " - "
48 
49 
50 #ifdef ENABLE_ID3LIB
51 
52 /**************
53  * Prototypes *
54  **************/
55 static gchar *Id3tag_Get_Error_Message (ID3_Err error);
56 static void Id3tag_Prepare_ID3v1 (ID3Tag *id3_tag);
57 static gchar *Id3tag_Rules_For_ISO_Fields (const gchar *string,
58                                            const gchar *from_codeset,
59                                            const gchar *to_codeset);
60 static gchar *Id3tag_Get_Field (const ID3Frame *id3_frame,
61                                 ID3_FieldID id3_fieldid);
62 static ID3_TextEnc Id3tag_Set_Field (const ID3Frame *id3_frame,
63                                      ID3_FieldID id3_fieldid,
64                                      const gchar *string);
65 
66 ID3_C_EXPORT size_t ID3Tag_Link_1         (ID3Tag *id3tag, const char *filename);
67 ID3_C_EXPORT size_t ID3Field_GetASCII_1   (const ID3Field *field, char *buffer,      size_t maxChars, size_t itemNum);
68 ID3_C_EXPORT size_t ID3Field_GetUNICODE_1 (const ID3Field *field, unicode_t *buffer, size_t maxChars, size_t itemNum);
69 
70 static gboolean id3tag_check_if_id3lib_is_buggy (GError **error);
71 
72 
73 
74 /*************
75  * Functions *
76  *************/
77 
78 /* et_id3_error_quark:
79  *
80  * Quark for EtID3Error domain.
81  *
82  * Returns: a GQuark for the EtID3Error domain
83  */
84 GQuark
et_id3_error_quark(void)85 et_id3_error_quark (void)
86 {
87     return g_quark_from_static_string ("et-id3-error-quark");
88 }
89 
90 /**
91  * et_id3tag_get_tpos_from_file_tag:
92  * @FileTag: File_Tag to get disc_number and disc_total from
93  *
94  * This function will return TPOS from FileTag.
95  * Returns: a newly allocated string, should be freed using g_free.
96  */
97 gchar *
et_id3tag_get_tpos_from_file_tag(const File_Tag * FileTag)98 et_id3tag_get_tpos_from_file_tag (const File_Tag *FileTag)
99 {
100     GString *gstring;
101     const gchar *p;
102 
103     gstring = g_string_new ("");
104 
105     if (!FileTag->disc_number)
106     {
107         return g_string_free (gstring, FALSE);
108     }
109 
110     p = FileTag->disc_number;
111 
112     while (*p)
113     {
114         if (!g_ascii_isdigit (*p))
115         {
116             break;
117         }
118 
119         g_string_append_c (gstring, *p);
120         p++;
121     }
122 
123     if (FileTag->disc_total && *FileTag->disc_total)
124     {
125         g_string_append_c (gstring, '/');
126         p = FileTag->disc_total;
127 
128         while (*p)
129         {
130             if (!g_ascii_isdigit (*p))
131             {
132                 break;
133             }
134 
135             g_string_append_c (gstring, *p);
136             p++;
137         }
138     }
139 
140     return g_string_free (gstring, FALSE);
141 }
142 
143 /*
144  * Write the ID3 tags to the file. Returns TRUE on success, else 0.
145  */
146 static gboolean
id3tag_write_file_v23tag(const ET_File * ETFile,GError ** error)147 id3tag_write_file_v23tag (const ET_File *ETFile,
148                           GError **error)
149 {
150     const File_Tag *FileTag;
151     const gchar *filename;
152     const gchar *filename_utf8;
153     gchar    *basename_utf8;
154     GFile *file;
155     ID3Tag   *id3_tag = NULL;
156     ID3_Err   error_strip_id3v1  = ID3E_NoError;
157     ID3_Err   error_strip_id3v2  = ID3E_NoError;
158     ID3_Err   error_update_id3v1 = ID3E_NoError;
159     ID3_Err   error_update_id3v2 = ID3E_NoError;
160     gboolean success = TRUE;
161     gint number_of_frames;
162     gboolean has_title       = FALSE;
163     gboolean has_artist      = FALSE;
164     gboolean has_album_artist= FALSE;
165     gboolean has_album       = FALSE;
166     gboolean has_disc_number = FALSE;
167     gboolean has_year        = FALSE;
168     gboolean has_track       = FALSE;
169     gboolean has_genre       = FALSE;
170     gboolean has_comment     = FALSE;
171     gboolean has_composer    = FALSE;
172     gboolean has_orig_artist = FALSE;
173     gboolean has_copyright   = FALSE;
174     gboolean has_url         = FALSE;
175     gboolean has_encoded_by  = FALSE;
176     gboolean has_picture     = FALSE;
177     //gboolean has_song_len    = FALSE;
178     static gboolean flag_first_check = TRUE;
179     static gboolean flag_id3lib_bugged = TRUE;
180 
181     ID3Frame *id3_frame;
182     ID3Field *id3_field;
183     //gchar *string;
184     gchar *string1;
185     EtPicture *pic;
186 
187     g_return_val_if_fail (ETFile != NULL && ETFile->FileTag != NULL, FALSE);
188     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
189 
190     // When writing the first MP3 file, we check if the version of id3lib of the
191     // system doesn't contain a bug when writting Unicode tags
192     if (flag_first_check
193         && g_settings_get_boolean (MainSettings, "id3v2-enable-unicode"))
194     {
195         flag_first_check = FALSE;
196         flag_id3lib_bugged = id3tag_check_if_id3lib_is_buggy (NULL);
197     }
198 
199     FileTag  = (File_Tag *)ETFile->FileTag->data;
200     filename      = ((File_Name *)ETFile->FileNameCur->data)->value;
201     filename_utf8 = ((File_Name *)ETFile->FileNameCur->data)->value_utf8;
202 
203     file = g_file_new_for_path (filename);
204 
205     /* This is a protection against a bug in id3lib that enters an infinite
206      * loop with corrupted MP3 files (files containing only zeroes) */
207     if (!et_id3tag_check_if_file_is_valid (file, error))
208     {
209         if (error)
210         {
211             g_debug ("Error while checking if ID3 tag is valid: %s",
212                      (*error)->message);
213         }
214 
215         g_clear_error (error);
216         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "%s",
217                      _("Corrupted file"));
218         g_object_unref (file);
219         return FALSE;
220     }
221 
222     /* We get again the tag from the file to keep also unused data (by EasyTAG), then
223      * we replace the changed data */
224     if ((id3_tag = ID3Tag_New ()) == NULL)
225     {
226         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOMEM, "%s",
227                      g_strerror (ENOMEM));
228         g_object_unref (file);
229         return FALSE;
230     }
231 
232 #ifdef G_OS_WIN32
233     /* On Windows, id3lib expects filenames to be in the system codepage. */
234     {
235         gchar *locale_filename;
236 
237         locale_filename = g_win32_locale_filename_from_utf8 (filename);
238 
239         if (!locale_filename)
240         {
241             g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "%s",
242                          g_strerror (EINVAL));
243             ID3Tag_Delete (id3_tag);
244             g_object_unref (file);
245             return FALSE;
246         }
247 
248         ID3Tag_Link (id3_tag, locale_filename);
249 
250         g_free (locale_filename);
251     }
252 #else
253     ID3Tag_Link (id3_tag, filename);
254 #endif
255 
256     /* Set padding when tag was changed, for faster writing */
257     ID3Tag_SetPadding(id3_tag,TRUE);
258 
259     basename_utf8 = g_path_get_basename (filename_utf8);
260 
261     /*********
262      * Title *
263      *********/
264     // To avoid problem with a corrupted field, we remove it before to create a new one.
265     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_TITLE)) )
266         ID3Tag_RemoveFrame(id3_tag,id3_frame);
267 
268     if (!et_str_empty (FileTag->title))
269     {
270         id3_frame = ID3Frame_NewID(ID3FID_TITLE);
271         ID3Tag_AttachFrame(id3_tag,id3_frame);
272         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->title);
273         has_title = TRUE;
274     }
275 
276 
277     /**********
278      * Artist *
279      **********/
280     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_LEADARTIST)) )
281         ID3Tag_RemoveFrame(id3_tag,id3_frame);
282 
283     if (!et_str_empty (FileTag->artist))
284     {
285         id3_frame = ID3Frame_NewID(ID3FID_LEADARTIST);
286         ID3Tag_AttachFrame(id3_tag,id3_frame);
287         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->artist);
288         has_artist = TRUE;
289     }
290 
291 	/****************
292      * Album Artist *
293      ***************/
294     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_BAND)) )
295         ID3Tag_RemoveFrame(id3_tag,id3_frame);
296 
297     if (!et_str_empty (FileTag->album_artist))
298     {
299         id3_frame = ID3Frame_NewID(ID3FID_BAND);
300         ID3Tag_AttachFrame(id3_tag,id3_frame);
301         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->album_artist);
302         has_album_artist = TRUE;
303     }
304 
305     /*********
306      * Album *
307      *********/
308     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_ALBUM)) )
309         ID3Tag_RemoveFrame(id3_tag,id3_frame);
310 
311     if (!et_str_empty (FileTag->album))
312     {
313         id3_frame = ID3Frame_NewID(ID3FID_ALBUM);
314         ID3Tag_AttachFrame(id3_tag,id3_frame);
315         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->album);
316         has_album = TRUE;
317     }
318 
319 
320     /*****************************
321      * Part of set and Set Total *
322      *****************************/
323     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_PARTINSET)) )
324         ID3Tag_RemoveFrame(id3_tag,id3_frame);
325 
326     if (!et_str_empty (FileTag->disc_number))
327     {
328         id3_frame = ID3Frame_NewID (ID3FID_PARTINSET);
329         ID3Tag_AttachFrame (id3_tag, id3_frame);
330         string1 = et_id3tag_get_tpos_from_file_tag (FileTag);
331         Id3tag_Set_Field (id3_frame, ID3FN_TEXT, string1);
332         g_free (string1);
333         has_disc_number = TRUE;
334     }
335 
336 
337     /********
338      * Year *
339      ********/
340     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_YEAR)) )
341         ID3Tag_RemoveFrame(id3_tag,id3_frame);
342 
343     if (!et_str_empty (FileTag->year))
344     {
345         id3_frame = ID3Frame_NewID(ID3FID_YEAR);
346         ID3Tag_AttachFrame(id3_tag,id3_frame);
347         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->year);
348         has_year = TRUE;
349     }
350 
351 
352     /*************************
353      * Track and Total Track *
354      *************************/
355     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_TRACKNUM)) )
356         ID3Tag_RemoveFrame(id3_tag,id3_frame);
357 
358     if (!et_str_empty (FileTag->track))
359     {
360         id3_frame = ID3Frame_NewID(ID3FID_TRACKNUM);
361         ID3Tag_AttachFrame(id3_tag,id3_frame);
362 
363         if (!et_str_empty (FileTag->track_total))
364             string1 = g_strconcat(FileTag->track,"/",FileTag->track_total,NULL);
365         else
366             string1 = g_strdup(FileTag->track);
367 
368         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, string1);
369         g_free(string1);
370         has_track = TRUE;
371     }
372 
373 
374     /*********
375      * Genre *
376      *********
377      * Genre is written like this :
378      *    - "(<genre_id>)"              -> "(3)"
379      *    - "(<genre_id>)<refinement>"  -> "(3)EuroDance"
380      */
381     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_CONTENTTYPE)) )
382         ID3Tag_RemoveFrame(id3_tag,id3_frame);
383 
384     if (!et_str_empty (FileTag->genre))
385     {
386         gchar *genre_string_tmp;
387         guchar genre_value;
388 
389         id3_frame = ID3Frame_NewID(ID3FID_CONTENTTYPE);
390         ID3Tag_AttachFrame(id3_tag,id3_frame);
391 
392         genre_value = Id3tag_String_To_Genre(FileTag->genre);
393         // If genre not defined don't write genre value between brackets! (priority problem noted with some tools)
394         if ((genre_value == ID3_INVALID_GENRE)
395             || g_settings_get_boolean (MainSettings, "id3v2-text-only-genre"))
396         {
397             genre_string_tmp = g_strdup_printf("%s",FileTag->genre);
398         }
399         else
400         {
401             genre_string_tmp = g_strdup_printf("(%d)",genre_value);
402         }
403 
404         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, genre_string_tmp);
405         g_free(genre_string_tmp);
406         has_genre = TRUE;
407     }
408 
409 
410     /***********
411      * Comment *
412      ***********/
413     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_COMMENT)) )
414         ID3Tag_RemoveFrame(id3_tag,id3_frame);
415 
416     if (!et_str_empty (FileTag->comment))
417     {
418         id3_frame = ID3Frame_NewID(ID3FID_COMMENT);
419         ID3Tag_AttachFrame(id3_tag,id3_frame);
420         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->comment);
421         // These 2 following fields allow synchronisation between id3v2 and id3v1 tags with id3lib
422         // Disabled as when using unicode, the comment field stay in ISO.
423         //Id3tag_Set_Field(id3_frame, ID3FN_DESCRIPTION, "ID3v1 Comment");
424         //Id3tag_Set_Field(id3_frame, ID3FN_LANGUAGE, "XXX");
425         has_comment = TRUE;
426     }
427 
428 
429     /************
430      * Composer *
431      ************/
432     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_COMPOSER)) )
433         ID3Tag_RemoveFrame(id3_tag,id3_frame);
434 
435     if (!et_str_empty (FileTag->composer))
436     {
437         id3_frame = ID3Frame_NewID(ID3FID_COMPOSER);
438         ID3Tag_AttachFrame(id3_tag,id3_frame);
439         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->composer);
440         has_composer = TRUE;
441     }
442 
443 
444     /*******************
445      * Original artist *
446      *******************/
447     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_ORIGARTIST)) )
448         ID3Tag_RemoveFrame(id3_tag,id3_frame);
449 
450     if (!et_str_empty (FileTag->orig_artist))
451     {
452         id3_frame = ID3Frame_NewID(ID3FID_ORIGARTIST);
453         ID3Tag_AttachFrame(id3_tag,id3_frame);
454         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->orig_artist);
455         has_orig_artist = TRUE;
456     }
457 
458 
459     /*************
460      * Copyright *
461      *************/
462     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_COPYRIGHT)) )
463         ID3Tag_RemoveFrame(id3_tag,id3_frame);
464 
465     if (!et_str_empty (FileTag->copyright))
466     {
467         id3_frame = ID3Frame_NewID(ID3FID_COPYRIGHT);
468         ID3Tag_AttachFrame(id3_tag,id3_frame);
469         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->copyright);
470         has_copyright = TRUE;
471     }
472 
473 
474     /*******
475      * URL *
476      *******/
477     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_WWWUSER)) )
478         ID3Tag_RemoveFrame(id3_tag,id3_frame);
479 
480     if (!et_str_empty (FileTag->url))
481     {
482         id3_frame = ID3Frame_NewID(ID3FID_WWWUSER);
483         ID3Tag_AttachFrame(id3_tag,id3_frame);
484         Id3tag_Set_Field(id3_frame, ID3FN_URL, FileTag->url);
485         has_composer = TRUE;
486     }
487 
488 
489     /**************
490      * Encoded by *
491      **************/
492     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_ENCODEDBY)) )
493         ID3Tag_RemoveFrame(id3_tag,id3_frame);
494 
495     if (!et_str_empty (FileTag->encoded_by))
496     {
497         id3_frame = ID3Frame_NewID(ID3FID_ENCODEDBY);
498         ID3Tag_AttachFrame(id3_tag,id3_frame);
499         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, FileTag->encoded_by);
500         has_encoded_by = TRUE;
501     }
502 
503 
504     /***********
505      * Picture *
506      ***********/
507     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_PICTURE)) )
508         ID3Tag_RemoveFrame(id3_tag,id3_frame);
509 
510     has_picture = FALSE;
511 
512     for (pic = FileTag->picture; pic != NULL; pic = pic->next)
513     {
514         Picture_Format format = Picture_Format_From_Data(pic);
515 
516         id3_frame = ID3Frame_NewID(ID3FID_PICTURE);
517         ID3Tag_AttachFrame(id3_tag,id3_frame);
518 
519         switch (format)
520         {
521             case PICTURE_FORMAT_JPEG:
522                 if ((id3_field = ID3Frame_GetField(id3_frame,ID3FN_MIMETYPE)))
523                     ID3Field_SetASCII(id3_field, Picture_Mime_Type_String(format));
524                 if ((id3_field = ID3Frame_GetField(id3_frame,ID3FN_IMAGEFORMAT)))
525                     ID3Field_SetASCII(id3_field, "JPG");
526                 break;
527 
528             case PICTURE_FORMAT_PNG:
529                 if ((id3_field = ID3Frame_GetField(id3_frame,ID3FN_MIMETYPE)))
530                     ID3Field_SetASCII(id3_field, Picture_Mime_Type_String(format));
531                 if ((id3_field = ID3Frame_GetField(id3_frame,ID3FN_IMAGEFORMAT)))
532                     ID3Field_SetASCII(id3_field, "PNG");
533                 break;
534             case PICTURE_FORMAT_GIF:
535                 if ((id3_field = ID3Frame_GetField (id3_frame,
536                                                     ID3FN_MIMETYPE)))
537                 {
538                     ID3Field_SetASCII (id3_field,
539                                        Picture_Mime_Type_String (format));
540                 }
541 
542                 if ((id3_field = ID3Frame_GetField (id3_frame,
543                                                     ID3FN_IMAGEFORMAT)))
544                 {
545                     /* I could find no reference for what ID3FN_IMAGEFORMAT
546                      * should contain, so this is a guess. */
547                     ID3Field_SetASCII (id3_field, "GIF");
548                 }
549                 break;
550             case PICTURE_FORMAT_UNKNOWN:
551             default:
552                 break;
553         }
554 
555         if ((id3_field = ID3Frame_GetField(id3_frame, ID3FN_PICTURETYPE)))
556             ID3Field_SetINT(id3_field, pic->type);
557 
558         if (pic->description)
559             Id3tag_Set_Field(id3_frame, ID3FN_DESCRIPTION, pic->description);
560 
561         if ((id3_field = ID3Frame_GetField(id3_frame,ID3FN_DATA)))
562         {
563             gconstpointer data;
564             gsize data_size;
565 
566             data = g_bytes_get_data (pic->bytes, &data_size);
567             ID3Field_SetBINARY (id3_field, data, data_size);
568         }
569 
570         has_picture = TRUE;
571     }
572 
573 
574     /*********************************
575      * File length (in milliseconds) *
576      *********************************/
577     /* Don't write this field, not useful? *
578     while ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_SONGLEN)) )
579         ID3Tag_RemoveFrame(id3_tag,id3_frame);
580     if (ETFile->ETFileInfo && ((ET_File_Info *)ETFile->ETFileInfo)->duration > 0 )
581     {
582         gchar *string;
583 
584         id3_frame = ID3Frame_NewID(ID3FID_SONGLEN);
585         ID3Tag_AttachFrame(id3_tag,id3_frame);
586 
587         string = g_strdup_printf("%d",((ET_File_Info *)ETFile->ETFileInfo)->duration * 1000);
588         Id3tag_Set_Field(id3_frame, ID3FN_TEXT, string);
589         g_free(string);
590         has_song_len = TRUE;
591     }*/
592 
593 
594     /******************************
595      * Delete an APE tag if found *
596      ******************************/
597     {
598         // Delete the APE tag (create a dummy ETFile for the Ape_Tag_... function)
599         ET_File   *ETFile_tmp    = ET_File_Item_New();
600         File_Name *FileName_tmp = et_file_name_new ();
601         File_Tag  *FileTag_tmp = et_file_tag_new();
602         // Same file...
603         FileName_tmp->value      = g_strdup(filename);
604         FileName_tmp->value_utf8 = g_strdup(filename_utf8);  // Not necessary to fill 'value_ck'
605         ETFile_tmp->FileNameList = g_list_append(NULL,FileName_tmp);
606         ETFile_tmp->FileNameCur  = ETFile_tmp->FileNameList;
607         // With empty tag...
608         ETFile_tmp->FileTagList  = g_list_append(NULL,FileTag_tmp);
609         ETFile_tmp->FileTag      = ETFile_tmp->FileTagList;
610         ape_tag_write_file_tag (ETFile_tmp, NULL);
611         ET_Free_File_List_Item(ETFile_tmp);
612     }
613 
614 
615     /*********************************
616      * Update id3v1.x and id3v2 tags *
617      *********************************/
618     /* Get the number of frames into the tag, cause if it is
619      * equal to 0, id3lib-3.7.12 doesn't update the tag */
620     number_of_frames = ID3Tag_NumFrames(id3_tag);
621 
622     /* If all fields (managed in the UI) are empty and option id3-strip-empty
623      * is set to TRUE, we strip the ID3v1.x and ID3v2 tags. Else, write ID3v2
624      * and/or ID3v1. */
625     if (g_settings_get_boolean (MainSettings, "id3-strip-empty")
626     && !has_title      && !has_artist   && !has_album_artist && !has_album       && !has_year      && !has_track
627     && !has_genre      && !has_composer && !has_orig_artist && !has_copyright && !has_url
628     && !has_encoded_by && !has_picture  && !has_comment     && !has_disc_number)//&& !has_song_len )
629     {
630         error_strip_id3v1 = ID3Tag_Strip(id3_tag,ID3TT_ID3V1);
631         error_strip_id3v2 = ID3Tag_Strip(id3_tag,ID3TT_ID3V2);
632         /* Check error messages */
633         if (error_strip_id3v1 == ID3E_NoError && error_strip_id3v2 == ID3E_NoError)
634         {
635             g_debug (_("Removed tag of ‘%s’"), basename_utf8);
636         }
637         else
638         {
639             if (error_strip_id3v1 != ID3E_NoError)
640             {
641                 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
642                              _("Error while removing ID3v1 tag of ‘%s’: %s"),
643                              basename_utf8,
644                              Id3tag_Get_Error_Message (error_strip_id3v1));
645             }
646 
647             if (error_strip_id3v2 != ID3E_NoError)
648             {
649                 g_clear_error (error);
650                 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
651                              _("Error while removing ID3v2 tag of ‘%s’: %s"),
652                              basename_utf8,
653                              Id3tag_Get_Error_Message (error_strip_id3v2));
654             }
655 
656             success = FALSE;
657         }
658 
659     }
660     else
661     {
662         /* It's better to remove the id3v1 tag before, to synchronize it with the
663          * id3v2 tag (else id3lib doesn't do it correctly)
664          */
665         error_strip_id3v1 = ID3Tag_Strip(id3_tag,ID3TT_ID3V1);
666 
667         /*
668          * ID3v2 tag
669          */
670         if (g_settings_get_boolean (MainSettings, "id3v2-enabled")
671             && number_of_frames != 0)
672         {
673             error_update_id3v2 = ID3Tag_UpdateByTagType(id3_tag,ID3TT_ID3V2);
674             if (error_update_id3v2 != ID3E_NoError)
675             {
676                 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
677                              _("Error while updating ID3v2 tag of ‘%s’: %s"),
678                              basename_utf8,
679                              Id3tag_Get_Error_Message (error_update_id3v2));
680                 success = FALSE;
681             }
682         }
683         else
684         {
685             error_strip_id3v2 = ID3Tag_Strip(id3_tag,ID3TT_ID3V2);
686             if (error_strip_id3v2 != ID3E_NoError)
687             {
688                 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
689                              _("Error while removing ID3v2 tag of ‘%s’: %s"),
690                              basename_utf8,
691                              Id3tag_Get_Error_Message (error_strip_id3v2));
692                 success = FALSE;
693             }
694         }
695 
696         /*
697          * ID3v1 tag
698          * Must be set after ID3v2 or ID3Tag_UpdateByTagType cause damage to unicode strings
699          */
700         // id3lib writes incorrectly the ID3v2 tag if unicode used when writing ID3v1 tag
701         if (g_settings_get_boolean (MainSettings, "id3v1-enabled")
702             && number_of_frames != 0)
703         {
704             // By default id3lib converts id3tag to ISO-8859-1 (single byte character set)
705             // Note : converting UTF-16 string (two bytes character set) to ISO-8859-1
706             //        remove only the second byte => a strange string appears...
707             Id3tag_Prepare_ID3v1(id3_tag);
708 
709             error_update_id3v1 = ID3Tag_UpdateByTagType(id3_tag,ID3TT_ID3V1);
710             if (error_update_id3v1 != ID3E_NoError)
711             {
712                 g_clear_error (error);
713                 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
714                              _("Error while updating ID3v1 tag of ‘%s’: %s"),
715                              basename_utf8,
716                              Id3tag_Get_Error_Message (error_update_id3v1));
717                 success = FALSE;
718             }
719         }else
720         {
721             error_strip_id3v1 = ID3Tag_Strip(id3_tag,ID3TT_ID3V1);
722             if (error_strip_id3v1 != ID3E_NoError)
723             {
724                 g_clear_error (error);
725                 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
726                              _("Error while removing ID3v1 tag of ‘%s’: %s"),
727                              basename_utf8,
728                              Id3tag_Get_Error_Message (error_strip_id3v1));
729                 success = FALSE;
730             }
731         }
732     }
733 
734     /* Do a one-time check that the id3lib version is not buggy. */
735     if (g_settings_get_boolean (MainSettings, "id3v2-enabled")
736         && number_of_frames != 0 && success)
737     {
738         /* See known problem on the top : [ 1016290 ] Unicode16 writing bug.
739          * When we write the tag in Unicode, we try to check if it was
740          * correctly written. So to test it : we read again the tag, and then
741          * compare with the previous one. We check up to find an error (as only
742          * some characters are affected).
743          * If the patch to id3lib was applied to fix the problem (tested
744          * by id3tag_check_if_id3lib_is_buggy) we didn't make the following
745          * test => OK */
746         if (flag_id3lib_bugged
747             && g_settings_get_boolean (MainSettings,
748                                        "id3v2-enable-unicode"))
749         {
750             File_Tag  *FileTag_tmp = et_file_tag_new ();
751 
752             if (id3tag_read_file_tag (file, FileTag_tmp, NULL) == TRUE
753                 && et_file_tag_detect_difference (FileTag,
754                                                   FileTag_tmp) == TRUE)
755             {
756                 flag_id3lib_bugged = FALSE; /* Report the error only once. */
757                 success = FALSE;
758                 g_set_error (error, ET_ID3_ERROR,
759                              ET_ID3_ERROR_BUGGY_ID3LIB, "%s",
760                              _("Buggy id3lib"));
761             }
762 
763             et_file_tag_free (FileTag_tmp);
764         }
765     }
766 
767     /* Free allocated data */
768     ID3Tag_Delete(id3_tag);
769     g_object_unref (file);
770     g_free(basename_utf8);
771 
772     return success;
773 }
774 
775 
Id3tag_Get_Error_Message(ID3_Err error)776 gchar *Id3tag_Get_Error_Message(ID3_Err error)
777 {
778     switch (error)
779     {
780         case ID3E_NoError:
781             return _("No error reported");
782         case ID3E_NoMemory:
783             return _("No available memory");
784         case ID3E_NoData:
785             return _("No data to parse");
786         case ID3E_BadData:
787             return _("Improperly formatted data");
788         case ID3E_NoBuffer:
789             return _("No buffer to write to");
790         case ID3E_SmallBuffer:
791             return _("Buffer is too small");
792         case ID3E_InvalidFrameID:
793             return _("Invalid frame ID");
794         case ID3E_FieldNotFound:
795             return _("Requested field not found");
796         case ID3E_UnknownFieldType:
797             return _("Unknown field type");
798         case ID3E_TagAlreadyAttached:
799             return _("Tag is already attached to a file");
800         case ID3E_InvalidTagVersion:
801             return _("Invalid tag version");
802         case ID3E_NoFile:
803             return _("No file to parse");
804         case ID3E_ReadOnly:
805             return _("Attempting to write to a read-only file");
806         case ID3E_zlibError:
807             return _("Error in compression/uncompression");
808         default:
809             return _("Unknown error message");
810     }
811 
812 }
813 
814 
815 
816 /*
817  * As the ID3Tag_Link function of id3lib-3.8.0pre2 returns the ID3v1 tags
818  * when a file has both ID3v1 and ID3v2 tags, we first try to explicitely
819  * get the ID3v2 tags with ID3Tag_LinkWithFlags and, if we cannot get them,
820  * fall back to the ID3v1 tags.
821  * (Written by Holger Schemel).
822  */
ID3Tag_Link_1(ID3Tag * id3tag,const char * filename)823 ID3_C_EXPORT size_t ID3Tag_Link_1 (ID3Tag *id3tag, const char *filename)
824 {
825     size_t offset;
826 
827 #ifdef G_OS_WIN32
828     /* On Windows, id3lib expects filenames to be in the system codepage. */
829     gchar *locale_filename;
830 
831     locale_filename = g_win32_locale_filename_from_utf8 (filename);
832 
833     if (!locale_filename)
834     {
835         g_debug ("Error converting filename '%s' to system codepage",
836                  filename);
837         return 0;
838     }
839 #endif
840 
841 #   if (0) // Link the file with the both tags may cause damage to unicode strings
842 //#   if ( (ID3LIB_MAJOR >= 3) && (ID3LIB_MINOR >= 8) && (ID3LIB_PATCH >= 1) ) // Same test used in Id3tag_Read_File_Tag to use ID3Tag_HasTagType
843         /* No problem of priority, so we link the file with the both tags
844          * to manage => ID3Tag_HasTagType works correctly */
845 #ifdef G_OS_WIN32
846         offset = ID3Tag_LinkWithFlags (id3tag, locale_filename,
847                                        ID3TT_ID3V1 | ID3TT_ID3V2);
848 #else
849         offset = ID3Tag_LinkWithFlags (id3tag, filename,
850                                        ID3TT_ID3V1 | ID3TT_ID3V2);
851 #endif
852 #   elif ( (ID3LIB_MAJOR >= 3) && (ID3LIB_MINOR >= 8) )
853         /* Version 3.8.0pre2 gives priority to tag id3v1 instead of id3v2, so we
854          * try to fix it by linking the file with the id3v2 tag first. This bug
855          * was fixed in the final version of 3.8.0 but we can't know it... */
856         /* First, try to get the ID3v2 tags */
857 #ifdef G_OS_WIN32
858         offset = ID3Tag_LinkWithFlags (id3tag, locale_filename, ID3TT_ID3V2);
859 #else
860         offset = ID3Tag_LinkWithFlags (id3tag, filename, ID3TT_ID3V2);
861 #endif
862 
863         if (offset == 0)
864         {
865             /* No ID3v2 tags available => try to get the ID3v1 tags */
866 #ifdef G_OS_WIN32
867             offset = ID3Tag_LinkWithFlags (id3tag, locale_filename,
868                                            ID3TT_ID3V1);
869 #else
870             offset = ID3Tag_LinkWithFlags (id3tag, filename, ID3TT_ID3V1);
871 #endif
872         }
873 #   else
874         /* Function 'ID3Tag_LinkWithFlags' is not defined up to id3lib-.3.7.13 */
875 #ifdef G_OS_WIN32
876         offset = ID3Tag_Link (id3tag, locale_filename);
877 #else
878         offset = ID3Tag_Link (id3tag, filename);
879 #endif
880 #   endif
881     //g_print("ID3 TAG SIZE: %d\t%s\n",offset,g_path_get_basename(filename));
882 
883 #ifdef G_OS_WIN32
884     g_free (locale_filename);
885 #endif
886 
887     return offset;
888 }
889 
890 
891 /*
892  * As the ID3Field_GetASCII function differs with the version of id3lib, we must redefine it.
893  */
ID3Field_GetASCII_1(const ID3Field * field,char * buffer,size_t maxChars,size_t itemNum)894 ID3_C_EXPORT size_t ID3Field_GetASCII_1(const ID3Field *field, char *buffer, size_t maxChars, size_t itemNum)
895 {
896 
897     /* Defined by id3lib:   ID3LIB_MAJOR_VERSION, ID3LIB_MINOR_VERSION, ID3LIB_PATCH_VERSION
898      * Defined by autoconf: ID3LIB_MAJOR,         ID3LIB_MINOR,         ID3LIB_PATCH
899      *
900      * <= 3.7.12 : first item num is 1 for ID3Field_GetASCII
901      *  = 3.7.13 : first item num is 0 for ID3Field_GetASCII
902      * >= 3.8.0  : doesn't need item num for ID3Field_GetASCII
903      */
904      //g_print("id3lib version: %d.%d.%d\n",ID3LIB_MAJOR,ID3LIB_MINOR,ID3LIB_PATCH);
905 #    if (ID3LIB_MAJOR >= 3)
906          // (>= 3.x.x)
907 #        if (ID3LIB_MINOR <= 7)
908              // (3.0.0 to 3.7.x)
909 #            if (ID3LIB_PATCH >= 13)
910                  // (>= 3.7.13)
911                  return ID3Field_GetASCII(field,buffer,maxChars,itemNum);
912 #            else
913                  return ID3Field_GetASCII(field,buffer,maxChars,itemNum+1);
914 #            endif
915 #        else
916              // (>= to 3.8.0)
917              //return ID3Field_GetASCII(field,buffer,maxChars);
918              return ID3Field_GetASCIIItem(field,buffer,maxChars,itemNum);
919 #        endif
920 #    else
921          // Not tested (< 3.x.x)
922          return ID3Field_GetASCII(field,buffer,maxChars,itemNum+1);
923 #    endif
924 }
925 
926 
927 
928 /*
929  * As the ID3Field_GetUNICODE function differs with the version of id3lib, we must redefine it.
930  */
ID3Field_GetUNICODE_1(const ID3Field * field,unicode_t * buffer,size_t maxChars,size_t itemNum)931 ID3_C_EXPORT size_t ID3Field_GetUNICODE_1 (const ID3Field *field, unicode_t *buffer, size_t maxChars, size_t itemNum)
932 {
933 
934     /* Defined by id3lib:   ID3LIB_MAJOR_VERSION, ID3LIB_MINOR_VERSION, ID3LIB_PATCH_VERSION
935      * Defined by autoconf: ID3LIB_MAJOR,         ID3LIB_MINOR,         ID3LIB_PATCH
936      *
937      * <= 3.7.12 : first item num is 1 for ID3Field_GetUNICODE
938      *  = 3.7.13 : first item num is 0 for ID3Field_GetUNICODE
939      * >= 3.8.0  : doesn't need item num for ID3Field_GetUNICODE
940      */
941      //g_print("id3lib version: %d.%d.%d\n",ID3LIB_MAJOR,ID3LIB_MINOR,ID3LIB_PATCH);
942 #    if (ID3LIB_MAJOR >= 3)
943          // (>= 3.x.x)
944 #        if (ID3LIB_MINOR <= 7)
945              // (3.0.0 to 3.7.x)
946 #            if (ID3LIB_PATCH >= 13)
947                  // (>= 3.7.13)
948                  return ID3Field_GetUNICODE(field,buffer,maxChars,itemNum);
949 #            else
950                  return ID3Field_GetUNICODE(field,buffer,maxChars,itemNum+1);
951 #            endif
952 #        else
953              // (>= to 3.8.0)
954              return ID3Field_GetUNICODE(field,buffer,maxChars);
955              // ID3Field_GetUNICODEItem always return 0 with id3lib3.8.3, it is bug in size_t D3_FieldImpl::Get()
956              //return ID3Field_GetUNICODEItem(field,buffer,maxChars,itemNum);
957 #        endif
958 #    else
959          // Not tested (< 3.x.x)
960          return ID3Field_GetUNICODE(field,buffer,maxChars,itemNum+1);
961 #    endif
962 }
963 
964 
965 
966 
967 /*
968  * Source : "http://www.id3.org/id3v2.4.0-structure.txt"
969  *
970  * Frames that allow different types of text encoding contains a text
971  *   encoding description byte. Possible encodings:
972  *
973  *     $00   ISO-8859-1 [ISO-8859-1]. Terminated with $00.
974  *     $01   UTF-16 [UTF-16] encoded Unicode [UNICODE] with BOM ($FF FE
975  *           or $FE FF). All strings in the same frame SHALL have the same
976  *           byteorder. Terminated with $00 00.
977  *     $02   UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM.
978  *           Terminated with $00 00.
979  *     $03   UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00.
980  *
981  * For example :
982  *         T  P  E  1  .  .  .  .  .  .  .  ?  ?  J  .  o  .  n  .     .  G  .  i  .  n  .  d  .  i  .  c  .  k  .
983  * 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
984  *                                       ^
985  *                                       |___ UTF-16
986  */
987 /*
988  * Read the content (ID3FN_TEXT, ID3FN_URL, ...) of the id3_field of the
989  * id3_frame, and convert the string if needed to UTF-8.
990  */
Id3tag_Get_Field(const ID3Frame * id3_frame,ID3_FieldID id3_fieldid)991 gchar *Id3tag_Get_Field (const ID3Frame *id3_frame, ID3_FieldID id3_fieldid)
992 {
993     ID3Field *id3_field = NULL;
994     ID3Field *id3_field_encoding = NULL;
995     size_t num_chars = 0;
996     gchar *string = NULL, *string1 = NULL;
997 
998     //g_print("Id3tag_Get_Field - ID3Frame '%s'\n",ID3FrameInfo_ShortName(ID3Frame_GetID(id3_frame)));
999 
1000     if ( (id3_field = ID3Frame_GetField(id3_frame,id3_fieldid)) )
1001     {
1002         ID3_TextEnc enc = ID3TE_NONE;
1003 
1004         // Data of the field must be a TEXT (ID3FTY_TEXTSTRING)
1005         if (ID3Field_GetType(id3_field) != ID3FTY_TEXTSTRING)
1006         {
1007             g_critical ("%s",
1008                         "Id3tag_Get_Field() must be used only for fields containing text");
1009             return NULL;
1010         }
1011 
1012         /*
1013          * We prioritize the encoding of the field. If the encoding of the field
1014          * is ISO-8859-1, it can be read with another single byte encoding.
1015          */
1016         // Get encoding from content of file...
1017         id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
1018         if (id3_field_encoding)
1019             enc = ID3Field_GetINT(id3_field_encoding);
1020         // Else, get encoding from the field
1021         //enc = ID3Field_GetEncoding(id3_field);
1022 
1023         if (enc != ID3TE_UTF16 && enc != ID3TE_UTF8) // Encoding is ISO-8859-1?
1024         {
1025             /* Override with another character set? */
1026             if (g_settings_get_boolean (MainSettings,
1027                                         "id3-override-read-encoding"))
1028             {
1029                 /* Encoding set by user to ???. */
1030                 gint id3v1v2_charset;
1031                 const gchar *charset;
1032 
1033                 id3v1v2_charset = g_settings_get_enum (MainSettings,
1034                                                        "id3v1v2-charset");
1035                 charset = et_charset_get_name_from_index (id3v1v2_charset);
1036                 if (strcmp (charset, "ISO-8859-1") == 0)
1037                 {
1038                     enc = ID3TE_ISO8859_1;
1039                 }
1040                 else if (strcmp (charset, "UTF-16BE") == 0
1041                          || strcmp (charset, "UTF-16LE") == 0)
1042                 {
1043                     enc = ID3TE_UTF16;
1044                 }
1045                 else if (strcmp (charset, "UTF-8") == 0)
1046                 {
1047                     enc = ID3TE_UTF8;
1048                 }
1049                 else if (ID3Field_IsEncodable (id3_field))
1050                 {
1051                     string = g_malloc0 (ID3V2_MAX_STRING_LEN + 1);
1052                     num_chars = ID3Field_GetASCII_1 (id3_field, string,
1053                                                      ID3V2_MAX_STRING_LEN, 0);
1054                     string1 = convert_string (string, charset, "UTF-8", FALSE);
1055                     /* Override to a non-standard character encoding. */
1056                     goto out;
1057                 }
1058             }
1059         }
1060 
1061         // Some fields, as URL, aren't encodable, so there were written using ISO characters.
1062         if ( !ID3Field_IsEncodable(id3_field) )
1063         {
1064             enc = ID3TE_ISO8859_1;
1065         }
1066 
1067         // Action according the encoding...
1068         switch ( enc )
1069         {
1070             case ID3TE_ISO8859_1:
1071                 string = g_malloc0 (ID3V2_MAX_STRING_LEN + 1);
1072                 num_chars = ID3Field_GetASCII_1(id3_field,string,ID3V2_MAX_STRING_LEN,0);
1073                 string1 = convert_string(string,"ISO-8859-1","UTF-8",FALSE);
1074                 break;
1075 
1076             case ID3TE_UTF8: // Shouldn't work with id3lib 3.8.3 (supports only ID3v2.3, not ID3v2.4)
1077                 string = g_malloc0 (ID3V2_MAX_STRING_LEN+1);
1078                 num_chars = ID3Field_GetASCII_1(id3_field,string,ID3V2_MAX_STRING_LEN,0);
1079                 //string1 = convert_string(string,"UTF-8","UTF-8",FALSE); // Nothing to do
1080                 if (g_utf8_validate(string,-1,NULL))
1081                     string1 = g_strdup(string);
1082                 break;
1083 
1084             case ID3TE_UTF16:
1085                 // Id3lib (3.8.3 at least) always returns Unicode strings in UTF-16BE.
1086             case ID3TE_UTF16BE:
1087                 string = g_malloc0(sizeof(unicode_t)*ID3V2_MAX_STRING_LEN+1);
1088                 num_chars = ID3Field_GetUNICODE_1(id3_field,(unicode_t *)string,ID3V2_MAX_STRING_LEN,0);
1089                 // "convert_string_1" as we need to pass length for UTF-16
1090                 string1 = convert_string_1(string,num_chars,"UTF-16BE","UTF-8",FALSE);
1091                 break;
1092 
1093             case ID3TE_NONE:
1094             case ID3TE_NUMENCODINGS:
1095             default:
1096                 string = g_malloc0 (4 * ID3V2_MAX_STRING_LEN + 1);
1097                 num_chars = ID3Field_GetASCII_1(id3_field,string,ID3V2_MAX_STRING_LEN,0);
1098 
1099                 if (g_utf8_validate (string, -1, NULL))
1100                 {
1101                     string1 = g_strdup (string);
1102                 }
1103                 else
1104                 {
1105                     GError *error = NULL;
1106 
1107                     string1 = g_locale_to_utf8 (string, -1, NULL, NULL,
1108                                                 &error);
1109 
1110                     if (string1 == NULL)
1111                     {
1112                         g_debug ("Error converting string from locale to UTF-8 encoding: %s",
1113                                  error->message);
1114                         g_error_free (error);
1115                     }
1116                 }
1117                 break;
1118         }
1119     }
1120     //g_print(">>ID:%d >'%s' (string1:'%s') (num_chars:%d)\n",ID3Field_GetINT(id3_field_encoding),string,string1,num_chars);
1121 
1122 out:
1123     /* In case the conversion fails, try character fix. */
1124     if (num_chars && !string1)
1125     {
1126         gchar *escaped_str = g_strescape(string, NULL);
1127         g_debug ("Id3tag_Get_Field: Trying to fix string '%s'…", escaped_str);
1128         g_free(escaped_str);
1129 
1130         string1 = g_filename_display_name (string);
1131 
1132         /* TODO: Set a GError instead. */
1133         if (!string1)
1134         {
1135             g_warning ("%s", "Error converting ID3 tag field encoding");
1136         }
1137     }
1138     g_free(string);
1139 
1140     return string1;
1141 }
1142 
1143 
1144 /*
1145  * Set the content (ID3FN_TEXT, ID3FN_URL, ...) of the id3_field of the
1146  * id3_frame. Check also if the string must be written from UTF-8 (gtk2) in the
1147  * ISO-8859-1 encoding or UTF-16.
1148  *
1149  * Return the encoding used as if UTF-16 was used, we musn't write the ID3v1 tag.
1150  *
1151  * Known problem with id3lib
1152  * - [ 1016290 ] Unicode16 writing bug
1153  *               http://sourceforge.net/tracker/index.php?func=detail&aid=1016290&group_id=979&atid=300979
1154  *               For example with Latin-1 characters (like éöäüß) not saved correctly
1155  *               in Unicode, due to id3lib (for "é" it will write "E9 FF" instead of "EF 00")
1156  */
1157 /*
1158  * OLD NOTE : PROBLEM with ID3LIB
1159  * - [ 1074169 ] Writing ID3v1 tag breaks Unicode string in v2 tags
1160  *               http://sourceforge.net/tracker/index.php?func=detail&aid=1074169&group_id=979&atid=100979
1161  *      => don't write id3v1 tag if Unicode is used, up to patch applied
1162  * - [ 1073951 ] Added missing Field Encoding functions to C wrapper
1163  *               http://sourceforge.net/tracker/index.php?func=detail&aid=1073951&group_id=979&atid=300979
1164  */
1165 ID3_TextEnc
Id3tag_Set_Field(const ID3Frame * id3_frame,ID3_FieldID id3_fieldid,const gchar * string)1166 Id3tag_Set_Field (const ID3Frame *id3_frame,
1167                   ID3_FieldID id3_fieldid,
1168                   const gchar *string)
1169 {
1170     ID3Field *id3_field = NULL;
1171     ID3Field *id3_field_encoding = NULL;
1172     gchar *string_converted = NULL;
1173 
1174     if ( (id3_field = ID3Frame_GetField(id3_frame,id3_fieldid)) )
1175     {
1176         ID3_TextEnc enc = ID3TE_NONE;
1177 
1178         // Data of the field must be a TEXT (ID3FTY_TEXTSTRING)
1179         if (ID3Field_GetType(id3_field) != ID3FTY_TEXTSTRING)
1180         {
1181             g_critical ("%s",
1182                         "Id3tag_Set_Field() must be used only for fields containing text");
1183             return ID3TE_NONE;
1184         }
1185 
1186          /* We prioritize the rule selected in options. If the encoding of the
1187          * field is ISO-8859-1, we can write it to another single byte encoding.
1188          */
1189         if (g_settings_get_boolean (MainSettings, "id3v2-enable-unicode"))
1190         {
1191             // Check if we can write the tag using ISO-8859-1 instead of UTF-16...
1192             if ( (string_converted = g_convert(string, strlen(string), "ISO-8859-1",
1193                                                "UTF-8", NULL, NULL ,NULL)) )
1194             {
1195                 enc = ID3TE_ISO8859_1;
1196                 g_free(string_converted);
1197             }else
1198             {
1199                 // Force to UTF-16 as UTF-8 isn't supported
1200                 enc = ID3TE_UTF16;
1201             }
1202         }
1203         else
1204         {
1205             gint id3v2_charset;
1206             const gchar *charset;
1207 
1208             id3v2_charset = g_settings_get_enum (MainSettings,
1209                                                  "id3v2-no-unicode-charset");
1210             charset = et_charset_get_name_from_index (id3v2_charset);
1211 
1212             /* Other encoding selected. Encoding set by user to ???. */
1213             if (strcmp (charset, "ISO-8859-1") == 0)
1214             {
1215                 enc = ID3TE_ISO8859_1;
1216             }
1217             else if (strcmp (charset, "UTF-16BE") == 0
1218                      || strcmp (charset, "UTF-16LE") == 0)
1219             {
1220                 enc = ID3TE_UTF16;
1221             }
1222             else if (strcmp (charset, "UTF-8") == 0)
1223             {
1224                 enc = ID3TE_UTF8;
1225             }
1226             else if (ID3Field_IsEncodable (id3_field))
1227             {
1228                 goto override;
1229             }
1230         }
1231 
1232         // Some fields, as URL, aren't encodable, so there were written using ISO characters!
1233         if ( !ID3Field_IsEncodable(id3_field) )
1234         {
1235             enc = ID3TE_ISO8859_1;
1236         }
1237 
1238         // Action according the encoding...
1239         switch ( enc )
1240         {
1241             case ID3TE_ISO8859_1:
1242                 // Write into ISO-8859-1
1243                 //string_converted = convert_string(string,"UTF-8","ISO-8859-1",TRUE);
1244                 string_converted = Id3tag_Rules_For_ISO_Fields(string,"UTF-8","ISO-8859-1");
1245                 ID3Field_SetEncoding(id3_field,ID3TE_ISO8859_1); // Not necessary for ISO-8859-1, but better to precise if field has another encoding...
1246                 ID3Field_SetASCII(id3_field,string_converted);
1247                 g_free(string_converted);
1248 
1249                 id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
1250                 if (id3_field_encoding)
1251                     ID3Field_SetINT(id3_field_encoding,ID3TE_ISO8859_1);
1252 
1253                 return ID3TE_ISO8859_1;
1254                 break;
1255 
1256             /*** Commented as it doesn't work with id3lib 3.8.3 :
1257              ***  - it writes a strange UTF-8 string (2 bytes per character. Second char is FF) with a BOM
1258              ***  - it set the frame encoded to UTF-8 : "$03" which is OK
1259             case ID3TE_UTF8: // Shouldn't work with id3lib 3.8.3 (supports only ID3v2.3, not ID3v2.4)
1260                 ID3Field_SetEncoding(id3_field,ID3TE_UTF8);
1261                 ID3Field_SetASCII(id3_field,string);
1262 
1263                 id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
1264                 if (id3_field_encoding)
1265                     ID3Field_SetINT(id3_field_encoding,ID3TE_UTF8);
1266 
1267                 return ID3TE_UTF8;
1268                 break;
1269              ***/
1270 
1271             case ID3TE_UTF16:
1272             //case ID3TE_UTF16BE:
1273 
1274                 /* See known problem on the top : [ 1016290 ] Unicode16 writing bug */
1275                 // Write into UTF-16
1276                 string_converted = convert_string_1(string, strlen(string), "UTF-8",
1277                                                     "UTF-16BE", FALSE);
1278 
1279                 // id3lib (3.8.3 at least) always takes big-endian input for Unicode
1280                 // fields, even if the field is set little-endian.
1281                 ID3Field_SetEncoding(id3_field,ID3TE_UTF16);
1282                 ID3Field_SetUNICODE(id3_field,(const unicode_t*)string_converted);
1283                 g_free(string_converted);
1284 
1285                 id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
1286                 if (id3_field_encoding)
1287                     ID3Field_SetINT(id3_field_encoding,ID3TE_UTF16);
1288 
1289                 return ID3TE_UTF16;
1290                 break;
1291 
1292 override:
1293             case ID3TE_UTF16BE:
1294             case ID3TE_UTF8:
1295             case ID3TE_NUMENCODINGS:
1296             case ID3TE_NONE:
1297             default:
1298             {
1299                 //string_converted = convert_string(string,"UTF-8",charset,TRUE);
1300                 gint id3v2_charset;
1301                 const gchar *charset;
1302 
1303                 id3v2_charset = g_settings_get_enum (MainSettings,
1304                                                      "id3v2-no-unicode-charset");
1305                 charset = et_charset_get_name_from_index (id3v2_charset);
1306 
1307                 string_converted = Id3tag_Rules_For_ISO_Fields (string,
1308                                                                 "UTF-8",
1309                                                                 charset);
1310                 ID3Field_SetEncoding(id3_field,ID3TE_ISO8859_1);
1311                 ID3Field_SetASCII(id3_field,string_converted);
1312                 g_free(string_converted);
1313 
1314                 id3_field_encoding = ID3Frame_GetField(id3_frame,ID3FN_TEXTENC);
1315                 if (id3_field_encoding)
1316                     ID3Field_SetINT(id3_field_encoding,ID3TE_ISO8859_1);
1317 
1318                 return ID3TE_NONE;
1319                 break;
1320             }
1321         }
1322     }
1323 
1324     return ID3TE_NONE;
1325 }
1326 
1327 
1328 /*
1329  * By default id3lib converts id3tag to ISO-8859-1 (single byte character set)
1330  * Note : converting UTF-16 string (two bytes character set) to ISO-8859-1
1331  *        remove only the second byte => a strange string appears...
1332  */
Id3tag_Prepare_ID3v1(ID3Tag * id3_tag)1333 void Id3tag_Prepare_ID3v1 (ID3Tag *id3_tag)
1334 {
1335     ID3Frame *frame;
1336     ID3Field *id3_field_encoding;
1337     ID3Field *id3_field_text;
1338 
1339     if ( id3_tag != NULL )
1340     {
1341         ID3TagIterator *id3_tag_iterator;
1342         size_t num_chars = 0;
1343         gchar *string, *string1, *string_converted;
1344 
1345         id3_tag_iterator = ID3Tag_CreateIterator(id3_tag);
1346         while ( NULL != (frame = ID3TagIterator_GetNext(id3_tag_iterator)) )
1347         {
1348             ID3_TextEnc enc = ID3TE_ISO8859_1;
1349             ID3_FrameID frameid;
1350 
1351             frameid = ID3Frame_GetID(frame);
1352 
1353             if (frameid != ID3FID_TITLE
1354             &&  frameid != ID3FID_LEADARTIST
1355             &&  frameid != ID3FID_BAND
1356             &&  frameid != ID3FID_ALBUM
1357             &&  frameid != ID3FID_YEAR
1358             &&  frameid != ID3FID_TRACKNUM
1359             &&  frameid != ID3FID_CONTENTTYPE
1360             &&  frameid != ID3FID_COMMENT)
1361                 continue;
1362 
1363             id3_field_encoding = ID3Frame_GetField(frame, ID3FN_TEXTENC);
1364             if (id3_field_encoding != NULL)
1365                 enc = ID3Field_GetINT(id3_field_encoding);
1366             id3_field_text = ID3Frame_GetField(frame, ID3FN_TEXT);
1367 
1368             /* The frames in ID3TE_ISO8859_1 are already converted to the selected
1369              * single-byte character set if used. So we treat only Unicode frames */
1370             if ( (id3_field_text != NULL)
1371             &&   (enc != ID3TE_ISO8859_1) )
1372             {
1373                 gint id3v1_charset;
1374                 const gchar *charset;
1375 
1376                 /* Read UTF-16 frame. */
1377                 string = g_malloc0(sizeof(unicode_t)*ID3V2_MAX_STRING_LEN+1);
1378                 num_chars = ID3Field_GetUNICODE_1(id3_field_text,(unicode_t *)string,ID3V2_MAX_STRING_LEN,0);
1379                 // "convert_string_1" as we need to pass length for UTF-16
1380                 string1 = convert_string_1(string,num_chars,"UTF-16BE","UTF-8",FALSE);
1381 
1382                 id3v1_charset = g_settings_get_enum (MainSettings,
1383                                                      "id3v1-charset");
1384                 charset = et_charset_get_name_from_index (id3v1_charset);
1385 
1386                 string_converted = Id3tag_Rules_For_ISO_Fields (string1,
1387                                                                 "UTF-8",
1388                                                                 charset);
1389 
1390                 if (string_converted)
1391                 {
1392                     ID3Field_SetEncoding(id3_field_text,ID3TE_ISO8859_1); // Not necessary for ISO-8859-1
1393                     ID3Field_SetASCII(id3_field_text,string_converted);
1394                     ID3Field_SetINT(id3_field_encoding,ID3TE_ISO8859_1);
1395                     g_free(string_converted);
1396                 }
1397                 g_free(string);
1398                 g_free(string1);
1399             }
1400         }
1401         ID3TagIterator_Delete(id3_tag_iterator);
1402     }
1403 }
1404 
1405 /*
1406  * This function must be used for tag fields containing ISO data.
1407  * We use specials functionalities of iconv : //TRANSLIT and //IGNORE (the last
1408  * one doesn't work on my system) to force conversion to the target encoding.
1409  */
1410 gchar *
Id3tag_Rules_For_ISO_Fields(const gchar * string,const gchar * from_codeset,const gchar * to_codeset)1411 Id3tag_Rules_For_ISO_Fields (const gchar *string,
1412                              const gchar *from_codeset,
1413                              const gchar *to_codeset)
1414 {
1415     gchar *string_converted = NULL;
1416     EtTagEncoding iconv_option;
1417 
1418     g_return_val_if_fail (string != NULL && from_codeset != NULL
1419                           && to_codeset != NULL, NULL);
1420 
1421     iconv_option = g_settings_get_enum (MainSettings, "id3v1-encoding-option");
1422 
1423     switch (iconv_option)
1424     {
1425         default:
1426         case ET_TAG_ENCODING_NONE:
1427             string_converted = convert_string (string, from_codeset,
1428                                                to_codeset, TRUE);
1429             break;
1430         case ET_TAG_ENCODING_TRANSLITERATE:
1431         {
1432             /* iconv_open (3):
1433              * When the string "//TRANSLIT" is appended to tocode,
1434              * transliteration is activated. This means that when a character
1435              * cannot be represented in the target character set, it can be
1436              * approximated through one or several similarly looking
1437              * characters.
1438              */
1439             gchar *to_enc = g_strconcat (to_codeset, "//TRANSLIT", NULL);
1440             string_converted = convert_string (string, from_codeset, to_enc,
1441                                                TRUE);
1442             g_free (to_enc);
1443             break;
1444         }
1445         case ET_TAG_ENCODING_IGNORE:
1446         {
1447             /* iconv_open (3):
1448              * When the string "//IGNORE" is appended to tocode, characters
1449              * that cannot be represented in the target character set will be
1450              * silently discarded.
1451              */
1452             gchar *to_enc = g_strconcat (to_codeset, "//IGNORE", NULL);
1453             string_converted = convert_string (string, from_codeset, to_enc,
1454                                                TRUE);
1455             g_free (to_enc);
1456             break;
1457         }
1458     }
1459 
1460     return string_converted;
1461 }
1462 
1463 /*
1464  * Some files which contains only zeroes create an infinite loop in id3lib...
1465  * To generate a file with zeroes : dd if=/dev/zero bs=1M count=6 of=test-corrupted-mp3-zero-contend.mp3
1466  */
1467 gboolean
et_id3tag_check_if_file_is_valid(GFile * file,GError ** error)1468 et_id3tag_check_if_file_is_valid (GFile *file, GError **error)
1469 {
1470     unsigned char tmp[256];
1471     unsigned char tmp0[256];
1472     gssize bytes_read;
1473     gboolean valid = FALSE;
1474     GFileInputStream *file_istream;
1475 
1476     g_return_val_if_fail (file != NULL, FALSE);
1477 
1478     file_istream = g_file_read (file, NULL, error);
1479 
1480     if (!file_istream)
1481     {
1482         g_assert (error == NULL || *error != NULL);
1483         return valid;
1484     }
1485 
1486     memset (&tmp0, 0, 256);
1487 
1488     /* Keep reading until EOF. */
1489     while ((bytes_read = g_input_stream_read (G_INPUT_STREAM (file_istream),
1490                                               tmp, 256, NULL, error)) != 0)
1491     {
1492         if (bytes_read == -1)
1493         {
1494             /* Error in reading file. */
1495             g_assert (error == NULL || *error != NULL);
1496             g_object_unref (file_istream);
1497             return valid;
1498         }
1499 
1500         /* Break out of the loop if there is a non-zero byte in the file. */
1501         if (memcmp (tmp, tmp0, bytes_read) != 0)
1502         {
1503             valid = TRUE;
1504             break;
1505         }
1506     }
1507 
1508     g_object_unref (file_istream);
1509 
1510     /* The error was not set by g_input_stream_read(), so the file must be
1511      * empty. */
1512     if (!valid)
1513     {
1514         g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s",
1515                      "Input truncated or empty");
1516     }
1517 
1518     return valid;
1519 }
1520 
1521 
1522 /*
1523  * Function to detect if id3lib isn't bugged when writting to Unicode
1524  * Returns TRUE if bugged, else FALSE
1525  */
1526 static gboolean
id3tag_check_if_id3lib_is_buggy(GError ** error)1527 id3tag_check_if_id3lib_is_buggy (GError **error)
1528 {
1529     GFile *file;
1530     GFileIOStream *iostream = NULL;
1531     GOutputStream *ostream = NULL;
1532     guchar tmp[16] = {0xFF, 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1533                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
1534     ID3Tag *id3_tag = NULL;
1535     gchar *path;
1536     gchar *result = NULL;
1537     ID3Frame *id3_frame;
1538     gboolean use_unicode;
1539     gsize bytes_written;
1540     const gchar test_str[] = "\xe5\x92\xbb";
1541 
1542     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1543 
1544     /* Create a temporary file. */
1545     file = g_file_new_tmp ("easytagXXXXXX.mp3", &iostream, error);
1546 
1547     if (!file)
1548     {
1549         /* TODO: Investigate whether error can be unset in this case. */
1550         if (!error)
1551         {
1552             g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "%s",
1553                          _("Error while creating temporary file"));
1554         }
1555 
1556         return FALSE;
1557     }
1558 
1559     /* Set data in the file. */
1560     ostream = g_io_stream_get_output_stream (G_IO_STREAM (iostream));
1561 
1562     if (!g_output_stream_write_all (G_OUTPUT_STREAM (ostream), tmp,
1563                                     sizeof (tmp), &bytes_written, NULL,
1564                                     error))
1565     {
1566         g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %" G_GSIZE_FORMAT
1567                  " bytes of data were written", bytes_written, sizeof (tmp));
1568 
1569         g_object_unref (file);
1570         g_object_unref (iostream);
1571 
1572         return FALSE;
1573     }
1574 
1575     g_output_stream_close (G_OUTPUT_STREAM (ostream), NULL, NULL);
1576     g_object_unref (iostream);
1577 
1578     /* Save state of switches as we must force to Unicode before writing.
1579      * FIXME! */
1580     g_settings_delay (MainSettings);
1581     use_unicode = g_settings_get_boolean (MainSettings,
1582                                           "id3v2-enable-unicode");
1583     g_settings_set_boolean (MainSettings, "id3v2-enable-unicode", TRUE);
1584 
1585     id3_tag = ID3Tag_New();
1586     path = g_file_get_path (file);
1587     ID3Tag_Link_1 (id3_tag, path);
1588 
1589     // Create a new 'title' field for testing
1590     id3_frame = ID3Frame_NewID(ID3FID_TITLE);
1591     ID3Tag_AttachFrame(id3_tag,id3_frame);
1592     /* Test a string that exposes an id3lib bug when converted to UTF-16.
1593      * http://sourceforge.net/p/id3lib/patches/64/ */
1594     Id3tag_Set_Field (id3_frame, ID3FN_TEXT, test_str);
1595 
1596     // Update the tag
1597     ID3Tag_UpdateByTagType(id3_tag,ID3TT_ID3V2);
1598     ID3Tag_Delete(id3_tag);
1599 
1600     g_settings_set_boolean (MainSettings, "id3v2-enable-unicode", use_unicode);
1601     g_settings_revert (MainSettings);
1602 
1603     id3_tag = ID3Tag_New();
1604     ID3Tag_Link_1 (id3_tag, path);
1605     // Read the written field
1606     if ( (id3_frame = ID3Tag_FindFrameWithID(id3_tag,ID3FID_TITLE)) )
1607     {
1608         result = Id3tag_Get_Field(id3_frame,ID3FN_TEXT);
1609     }
1610 
1611     ID3Tag_Delete(id3_tag);
1612     g_free (path);
1613     g_file_delete (file, NULL, NULL);
1614 
1615     g_object_unref (file);
1616 
1617     /* Same string found? if yes => not buggy. */
1618     if (result && strcmp (result, test_str) != 0)
1619     {
1620         g_free (result);
1621         return TRUE;
1622     }
1623 
1624     g_free (result);
1625     g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s",
1626                  "Buggy id3lib detected");
1627     return FALSE;
1628 }
1629 
1630 #endif /* ENABLE_ID3LIB */
1631 
1632 
1633 /*
1634  * Write tag according the version selected by the user
1635  */
1636 gboolean
id3tag_write_file_tag(const ET_File * ETFile,GError ** error)1637 id3tag_write_file_tag (const ET_File *ETFile,
1638                        GError **error)
1639 {
1640 #ifdef ENABLE_ID3LIB
1641     if (g_settings_get_boolean (MainSettings, "id3v2-version-4"))
1642     {
1643         return id3tag_write_file_v24tag (ETFile, error);
1644     }
1645     else
1646     {
1647         return id3tag_write_file_v23tag (ETFile, error);
1648     }
1649 #else
1650     return id3tag_write_file_v24tag (ETFile, error);
1651 #endif /* !ENABLE_ID3LIB */
1652 }
1653 
1654 #endif /* ENABLE_MP3 */
1655 
1656 
1657 // Placed out #ifdef ENABLE_MP3 as not dependant of id3lib, and used in CDDB
1658 /*
1659  * Returns the corresponding genre value of the input string (for ID3v1.x),
1660  * else returns 0xFF (unknown genre, but not invalid).
1661  */
1662 guchar
Id3tag_String_To_Genre(const gchar * genre)1663 Id3tag_String_To_Genre (const gchar *genre)
1664 {
1665     guint i;
1666 
1667     if (genre != NULL)
1668     {
1669         for (i=0; i<=GENRE_MAX; i++)
1670             if (strcasecmp(genre,id3_genres[i])==0)
1671                 return (guchar)i;
1672     }
1673     return (guchar)0xFF;
1674 }
1675 
1676 
1677 /*
1678  * Returns the name of a genre code if found
1679  * Three states for genre code :
1680  *    - defined (0 to GENRE_MAX)
1681  *    - undefined/unknown (GENRE_MAX+1 to ID3_INVALID_GENRE-1)
1682  *    - invalid (>ID3_INVALID_GENRE)
1683  */
1684 const gchar *
Id3tag_Genre_To_String(unsigned char genre_code)1685 Id3tag_Genre_To_String (unsigned char genre_code)
1686 {
1687     if (genre_code>=ID3_INVALID_GENRE)    /* empty */
1688         return "";
1689     else if (genre_code>GENRE_MAX)        /* unknown tag */
1690         return "Unknown";
1691     else                                  /* known tag */
1692         return id3_genres[genre_code];
1693 }
1694