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