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