obsolete
[flac.git] / src / plugin_common / id3v2.c
1 /* plugin_common - Routines common to several plugins
2  * Copyright (C) 2002,2003,2004  Daisuke Shimamura
3  *
4  * Almost from id3_tag.c - 2001/02/16
5  *  EasyTAG - Tag editor for MP3 and OGG files
6  *  Copyright (C) 2001-2002  Jerome Couderc <j.couderc@ifrance.com>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include "FLAC/assert.h"
28
29 #include <stdlib.h> /* for free() */
30 #include <string.h> /* for memset() */
31
32 #ifdef FLAC__HAS_ID3LIB
33 #include <id3.h>
34 #include <stdio.h>
35 #include <errno.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <unistd.h>
39
40 #include "id3v1.h" /* for genre stuff */
41 #include "locale_hack.h"
42
43 #define ID3V2_MAX_STRING_LEN 4096
44 #define NUMBER_TRACK_FORMATED 1
45 #endif
46
47 /*
48  * This should come after #include<id3.h> because id3.h doesn't #undef
49  * true and false before redefining them, causing warnings.
50  */
51 #include "id3v2.h"
52
53 #ifdef FLAC__HAS_ID3LIB
54 /* local__strip() based on glib's g_strchomp() and g_strchug():
55  * GLIB - Library of useful routines for C programming
56  * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
57  * (LGPL 2 follows)
58  */
59 static void local__strip(char *string)
60 {
61         char *s;
62
63         if(0 == string)
64                 return;
65
66         for(s = string; *s && isspace(*s); s++)
67                 ;
68
69         memmove(string, s, strlen((char*)s) + 1);
70
71         if(!*string)
72                 return;
73
74         for(s = string + strlen (string) - 1; s >= string && isspace(*s); s--)
75                 *s = '\0';
76 }
77
78
79 /*
80  * As the ID3Tag_Link function of id3lib-3.8.0pre2 returns the ID3v1 tags
81  * when a file has both ID3v1 and ID3v2 tags, we first try to explicitely
82  * get the ID3v2 tags with ID3Tag_LinkWithFlags and, if we cannot get them,
83  * fall back to the ID3v1 tags.
84  * (Written by Holger Schemel).
85  */
86 static size_t local__ID3Tag_Link_wrapper(ID3Tag *id3tag, const char *filename)
87 {
88         size_t offset;
89
90 #   if ( (ID3LIB_MAJOR >= 3) && (ID3LIB_MINOR >= 8)  )
91                 /* First, try to get the ID3v2 tags */
92                 offset = ID3Tag_LinkWithFlags(id3tag, filename, ID3TT_ID3V2);
93                 if (offset == 0) {
94                         /* No ID3v2 tags available => try to get the ID3v1 tags */
95                         offset = ID3Tag_LinkWithFlags(id3tag, filename, ID3TT_ID3V1);
96                 }
97 #   else
98                 /* Function 'ID3Tag_LinkWithFlags' is not defined up to id3lib-.3.7.13 */
99                 offset = ID3Tag_Link(id3tag, filename);
100 #   endif
101         return offset;
102 }
103
104
105 /*
106  * As the ID3Field_GetASCII function differs with the version of id3lib, we must redefine it.
107  */
108 /* [JEC] old id3lib versions may have used index_t for itemNum but size_t is what it wants now and seems safe enough. */
109 static size_t local__ID3Field_GetASCII_wrapper(const ID3Field *field, char *buffer, size_t maxChars, size_t itemNum)
110 {
111
112         /* Defined by id3lib:   ID3LIB_MAJOR_VERSION, ID3LIB_MINOR_VERSION, ID3LIB_PATCH_VERSION
113          * Defined by autoconf: ID3LIB_MAJOR,         ID3LIB_MINOR,         ID3LIB_PATCH
114          *
115          * <= 3.7.12 : first item num is 1 for ID3Field_GetASCII
116          *  = 3.7.13 : first item num is 0 for ID3Field_GetASCII
117          * >= 3.8.0  : doesn't need item num for ID3Field_GetASCII
118          */
119 #       if (ID3LIB_MAJOR >= 3)
120                  /* (>= 3.x.x) */
121 #               if (ID3LIB_MINOR <= 7)
122                          /* (3.0.0 to 3.7.x) */
123 #                       if (ID3LIB_PATCH >= 13)
124                                  /* (>= 3.7.13) */
125                                  return ID3Field_GetASCII(field, buffer, maxChars, itemNum);
126 #                       else
127                                  return ID3Field_GetASCII(field, buffer, maxChars, itemNum+1);
128 #                       endif
129 #               else
130                          /* (>= to 3.8.0) */
131                          /*return ID3Field_GetASCII(field, buffer, maxChars); */
132                          return ID3Field_GetASCIIItem(field, buffer, maxChars, itemNum);
133 #               endif
134 #       else
135                  /* Not tested (< 3.x.x) */
136                  return ID3Field_GetASCII(field, buffer, maxChars, itemNum+1);
137 #       endif
138 }
139
140
141 /*
142  * Returns the name of a genre code if found
143  * Three states for genre code :
144  *    - defined (0 to GENRE_MAX)
145  *    - undefined/unknown (GENRE_MAX+1 to ID3_INVALID_GENRE-1)
146  *    - invalid (>ID3_INVALID_GENRE)
147  */
148 static const char *local__genre_to_string(unsigned genre_code)
149 {
150         if(genre_code >= FLAC_PLUGIN__ID3V1_TAG_INVALID_GENRE)
151                 return "";
152         else {
153                 const char *s = FLAC_plugin__id3v1_tag_get_genre_as_string((unsigned)genre_code);
154                 if(s[0] == 0)
155                         return "Unknown";
156                 else
157                         return s;
158         }
159 }
160
161
162 /*
163  * Read id3v1.x / id3v2 tag and load data into the File_Tag structure using id3lib functions.
164  * Returns true on success, else false.
165  * If a tag entry exists (ex: title), we allocate memory, else value stays to NULL
166  */
167 static FLAC__bool local__get_tag(const char *filename, FLAC_Plugin__Id3v2_Tag *tag)
168 {
169         FILE *file;
170         ID3Tag *id3_tag = 0; /* Tag defined by id3lib */
171         char *string, *string1;
172
173         FLAC__ASSERT(0 != filename);
174         FLAC__ASSERT(0 != tag);
175
176         if(0 == (file = fopen(filename, "r"))) {
177 #ifdef DEBUG
178                 fprintf(stderr, _("ERROR while opening file: '%s' (%s).\n\a"), filename, strerror(errno));
179 #endif
180                 return false;
181         }
182         fclose(file); /* We close it cause id3lib opens/closes file itself */
183
184
185         /* Get data from tag */
186         if(0 != (id3_tag = ID3Tag_New())) {
187                 ID3Frame *id3_frame;
188                 ID3Field *id3_field;
189                 luint frm_size;
190                 luint num_chars;
191                 size_t field_num = 0; /* First field */
192
193                 /* Link the file to the tag */
194                 frm_size = local__ID3Tag_Link_wrapper(id3_tag, filename);
195
196                 string = malloc(ID3V2_MAX_STRING_LEN+1);
197
198                 /*********
199                  * Title *
200                  *********/
201                 if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_TITLE))) {
202                         if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) {
203                                 /* Note: if 'num_chars' is equal to 0, then the field is empty or corrupted! */
204                                 if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) {
205                                         local__strip(string);
206                                         tag->title = strdup(string);
207                                 }
208                         }
209                 }
210
211
212                 /************
213                  * Composer *
214                  ************/
215                 if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_COMPOSER))) {
216                         if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) {
217                                 if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) {
218                                         local__strip(string);
219                                         tag->composer = strdup(string);
220                                 }
221                         }
222                 }
223
224
225                 /**********
226                  * Artist *
227                  **********/
228                 if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_LEADARTIST))) {
229                         if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) {
230                                 if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) {
231                                         local__strip(string);
232                                         tag->performer = strdup(string);
233                                 }
234                         }
235                 }
236
237
238                 /*********
239                  * Album *
240                  *********/
241                 if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_ALBUM))) {
242                         if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) {
243                                 if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) {
244                                         local__strip(string);
245                                         tag->album = strdup(string);
246                                 }
247                         }
248                 }
249
250
251                 /********
252                  * Year *
253                  ********/
254                 if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_YEAR))) {
255                         if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) {
256                                 if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) {
257                                         char *tmp_str;
258
259                                         local__strip(string);
260
261                                         /* Fix for id3lib 3.7.x: if the id3v1.x tag was filled with spaces
262                                          * instead of zeroes, then the year field contains garbages! */
263                                         tmp_str = string;
264                                         while (isdigit(*tmp_str)) tmp_str++;
265                                         *tmp_str = 0;
266                                         /* End of fix for id3lib 3.7.x */
267
268                                         local__strip(string);
269                                         tag->year_recorded = strdup(string);
270                                         tag->year_performed = strdup(string);
271                                 }
272                         }
273                 }
274
275
276                 /*************************
277                  * Track and Total Track *
278                  *************************/
279                 if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_TRACKNUM))) {
280                         if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) {
281                                 if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) {
282                                         local__strip(string);
283
284                                         string1 = strchr(string, '/');
285                                         if (NUMBER_TRACK_FORMATED) {
286                                                 if (string1) {
287                                                         /* Just to have numbers like this : '01', '05', '12', ... */
288                                                         tag->tracks_in_album = malloc(64);
289                                                         sprintf(tag->tracks_in_album, "%.2d", atoi(string1+1));
290                                                         *string1 = '\0';
291                                                 }
292                                                 /* Just to have numbers like this : '01', '05', '12', ... */
293                                                 tag->track_number = malloc(64);
294                                                 sprintf(tag->track_number, "%.2d", atoi(string));
295                                         }
296                                         else {
297                                                 if (string1) {
298                                                         tag->tracks_in_album = strdup(string1+1);
299                                                         *string1 = '\0';
300                                                 }
301                                                 tag->track_number = strdup(string);
302                                         }
303                                 }
304                         }
305                 }
306
307
308                 /*********
309                  * Genre *
310                  *********/
311                 if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_CONTENTTYPE))) {
312                         if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) {
313                                 /*
314                                  * We manipulate only the name of the genre
315                                  */
316                                 if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) {
317                                         char *tmp;
318
319                                         local__strip(string);
320
321                                         if((string[0]=='(') && (tmp=strchr(string, ')')) && (strlen((tmp+1))>0)) {
322                                                 /* Convert a genre written as '(3)Dance' into 'Dance' */
323                                                 tag->genre = strdup(tmp+1);
324
325                                         }
326                                         else if((string[0]=='(') && (tmp=strchr(string, ')'))) {
327                                                 /* Convert a genre written as '(3)' into 'Dance' */
328                                                 *tmp = 0;
329                                                 tag->genre = strdup(local__genre_to_string((unsigned)atoi(string+1)));
330
331                                         }
332                                         else {
333                                                 /* Genre is already written as 'Dance' */
334                                                 tag->genre = strdup(string);
335                                         }
336                                 }
337                         }
338                 }
339
340
341                 /***********
342                  * Comment *
343                  ***********/
344                 if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_COMMENT))) {
345                         if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) {
346                                 if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) {
347                                         local__strip(string);
348                                         tag->comment = strdup(string);
349                                 }
350                         }
351 #if 0
352                         if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_DESCRIPTION))) {
353                                 char *comment1 = calloc(MAX_STRING_LEN+1);
354                                 num_chars = ID3Field_GetASCII(id3_field, comment1, MAX_STRING_LEN, Item_Num);
355                                 free(comment1);
356                         }
357                         if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_LANGUAGE))) {
358                                 char *comment2 = calloc(MAX_STRING_LEN+1);
359                                 num_chars = ID3Field_GetASCII(id3_field, comment2, MAX_STRING_LEN, Item_Num);
360                                 free(comment2);
361                         }
362 #endif
363                 }
364                 free(string);
365
366                 /* Free allocated data */
367                 ID3Tag_Delete(id3_tag);
368         }
369
370         return true;
371 }
372 #endif /* ifdef FLAC__HAS_ID3LIB */
373
374 FLAC__bool FLAC_plugin__id3v2_tag_get(const char *filename, FLAC_Plugin__Id3v2_Tag *tag)
375 {
376         FLAC__ASSERT(0 != tag);
377         if(
378                 0 != tag->title ||
379                 0 != tag->composer ||
380                 0 != tag->performer ||
381                 0 != tag->album ||
382                 0 != tag->year_recorded ||
383                 0 != tag->year_performed ||
384                 0 != tag->track_number ||
385                 0 != tag->tracks_in_album ||
386                 0 != tag->genre ||
387                 0 != tag->comment
388         )
389                 return false;
390 #ifdef FLAC__HAS_ID3LIB
391         return local__get_tag(filename, tag);
392 #else
393         (void)filename, (void)tag;
394         return false;
395 #endif
396 }
397
398 void FLAC_plugin__id3v2_tag_clear(FLAC_Plugin__Id3v2_Tag *tag)
399 {
400         FLAC__ASSERT(0 != tag);
401         if(0 != tag->title)
402                 free(tag->title);
403         if(0 != tag->composer)
404                 free(tag->composer);
405         if(0 != tag->performer)
406                 free(tag->performer);
407         if(0 != tag->album)
408                 free(tag->album);
409         if(0 != tag->year_recorded)
410                 free(tag->year_recorded);
411         if(0 != tag->year_performed)
412                 free(tag->year_performed);
413         if(0 != tag->track_number)
414                 free(tag->track_number);
415         if(0 != tag->tracks_in_album)
416                 free(tag->tracks_in_album);
417         if(0 != tag->genre)
418                 free(tag->genre);
419         if(0 != tag->comment)
420                 free(tag->comment);
421         memset(tag, 0, sizeof(*tag));
422 }