/* plugin_common - Routines common to several plugins * Copyright (C) 2002,2003,2004 Daisuke Shimamura * * Almost from id3_tag.c - 2001/02/16 * EasyTAG - Tag editor for MP3 and OGG files * Copyright (C) 2001-2002 Jerome Couderc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "id3v2.h" #ifdef HAVE_CONFIG_H #include #endif #ifdef FLAC__HAS_ID3LIB #include #include #include #include #include #include #include "FLAC/assert.h" #include "id3v1.h" /* for genre stuff */ #include "locale_hack.h" #define ID3V2_MAX_STRING_LEN 4096 #define NUMBER_TRACK_FORMATED 1 /* local__strip() based on glib's g_strchomp() and g_strchug(): * GLIB - Library of useful routines for C programming * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * (LGPL 2 follows) */ static void local__strip(char *string) { char *s; if(0 == string) return; for(s = string; *s && isspace(*s); s++) ; memmove(string, s, strlen((char*)s) + 1); if(!*string) return; for(s = string + strlen (string) - 1; s >= string && isspace(*s); s--) *s = '\0'; } /* * As the ID3Tag_Link function of id3lib-3.8.0pre2 returns the ID3v1 tags * when a file has both ID3v1 and ID3v2 tags, we first try to explicitely * get the ID3v2 tags with ID3Tag_LinkWithFlags and, if we cannot get them, * fall back to the ID3v1 tags. * (Written by Holger Schemel). */ static size_t local__ID3Tag_Link_wrapper(ID3Tag *id3tag, const char *filename) { size_t offset; # if ( (ID3LIB_MAJOR >= 3) && (ID3LIB_MINOR >= 8) ) /* First, try to get the ID3v2 tags */ offset = ID3Tag_LinkWithFlags(id3tag, filename, ID3TT_ID3V2); if (offset == 0) { /* No ID3v2 tags available => try to get the ID3v1 tags */ offset = ID3Tag_LinkWithFlags(id3tag, filename, ID3TT_ID3V1); } # else /* Function 'ID3Tag_LinkWithFlags' is not defined up to id3lib-.3.7.13 */ offset = ID3Tag_Link(id3tag, filename); # endif return offset; } /* * As the ID3Field_GetASCII function differs with the version of id3lib, we must redefine it. */ /* [JEC] old id3lib versions may have used index_t for itemNum but size_t is what it wants now and seems safe enough. */ static size_t local__ID3Field_GetASCII_wrapper(const ID3Field *field, char *buffer, size_t maxChars, size_t itemNum) { /* Defined by id3lib: ID3LIB_MAJOR_VERSION, ID3LIB_MINOR_VERSION, ID3LIB_PATCH_VERSION * Defined by autoconf: ID3LIB_MAJOR, ID3LIB_MINOR, ID3LIB_PATCH * * <= 3.7.12 : first item num is 1 for ID3Field_GetASCII * = 3.7.13 : first item num is 0 for ID3Field_GetASCII * >= 3.8.0 : doesn't need item num for ID3Field_GetASCII */ # if (ID3LIB_MAJOR >= 3) /* (>= 3.x.x) */ # if (ID3LIB_MINOR <= 7) /* (3.0.0 to 3.7.x) */ # if (ID3LIB_PATCH >= 13) /* (>= 3.7.13) */ return ID3Field_GetASCII(field, buffer, maxChars, itemNum); # else return ID3Field_GetASCII(field, buffer, maxChars, itemNum+1); # endif # else /* (>= to 3.8.0) */ /*return ID3Field_GetASCII(field, buffer, maxChars); */ return ID3Field_GetASCIIItem(field, buffer, maxChars, itemNum); # endif # else /* Not tested (< 3.x.x) */ return ID3Field_GetASCII(field, buffer, maxChars, itemNum+1); # endif } /* * Returns the name of a genre code if found * Three states for genre code : * - defined (0 to GENRE_MAX) * - undefined/unknown (GENRE_MAX+1 to ID3_INVALID_GENRE-1) * - invalid (>ID3_INVALID_GENRE) */ static const char *local__genre_to_string(unsigned genre_code) { if(genre_code >= FLAC_PLUGIN__ID3V1_TAG_INVALID_GENRE) return ""; else { const char *s = FLAC_plugin__id3v1_tag_get_genre_as_string((unsigned)genre_code); if(s[0] == 0) return "Unknown"; else return s; } } /* * Read id3v1.x / id3v2 tag and load data into the File_Tag structure using id3lib functions. * Returns true on success, else false. * If a tag entry exists (ex: title), we allocate memory, else value stays to NULL */ static FLAC__bool local__get_tag(const char *filename, FLAC_Plugin__CanonicalTag *tag) { FILE *file; ID3Tag *id3_tag = 0; /* Tag defined by id3lib */ char *string, *string1; FLAC__ASSERT(0 != filename); FLAC__ASSERT(0 != tag); if(0 == (file = fopen(filename, "r"))) { #ifdef DEBUG fprintf(stderr, _("ERROR while opening file: '%s' (%s).\n\a"), filename, strerror(errno)); #endif return false; } fclose(file); /* We close it cause id3lib opens/closes file itself */ /* Get data from tag */ if(0 != (id3_tag = ID3Tag_New())) { ID3Frame *id3_frame; ID3Field *id3_field; luint frm_size; luint num_chars; size_t field_num = 0; /* First field */ /* Link the file to the tag */ frm_size = local__ID3Tag_Link_wrapper(id3_tag, filename); string = malloc(ID3V2_MAX_STRING_LEN+1); /********* * Title * *********/ if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_TITLE))) { if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) { /* Note: if 'num_chars' is equal to 0, then the field is empty or corrupted! */ if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) { local__strip(string); tag->title = strdup(string); } } } /************ * Composer * ************/ if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_COMPOSER))) { if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) { if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) { local__strip(string); tag->composer = strdup(string); } } } /********** * Artist * **********/ if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_LEADARTIST))) { if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) { if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) { local__strip(string); tag->performer = strdup(string); } } } /********* * Album * *********/ if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_ALBUM))) { if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) { if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) { local__strip(string); tag->album = strdup(string); } } } /******** * Year * ********/ if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_YEAR))) { if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) { if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) { char *tmp_str; local__strip(string); /* Fix for id3lib 3.7.x: if the id3v1.x tag was filled with spaces * instead of zeroes, then the year field contains garbages! */ tmp_str = string; while (isdigit(*tmp_str)) tmp_str++; *tmp_str = 0; /* End of fix for id3lib 3.7.x */ local__strip(string); tag->year_recorded = strdup(string); tag->year_performed = strdup(string); } } } /************************* * Track and Total Track * *************************/ if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_TRACKNUM))) { if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) { if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) { local__strip(string); string1 = strchr(string, '/'); if (NUMBER_TRACK_FORMATED) { if (string1) { /* Just to have numbers like this : '01', '05', '12', ... */ tag->tracks_in_album = malloc(64); sprintf(tag->tracks_in_album, "%.2d", atoi(string1+1)); *string1 = '\0'; } /* Just to have numbers like this : '01', '05', '12', ... */ tag->track_number = malloc(64); sprintf(tag->track_number, "%.2d", atoi(string)); } else { if (string1) { tag->tracks_in_album = strdup(string1+1); *string1 = '\0'; } tag->track_number = strdup(string); } } } } /********* * Genre * *********/ if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_CONTENTTYPE))) { if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) { /* * We manipulate only the name of the genre */ if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) { char *tmp; local__strip(string); if((string[0]=='(') && (tmp=strchr(string, ')')) && (strlen((tmp+1))>0)) { /* Convert a genre written as '(3)Dance' into 'Dance' */ tag->genre = strdup(tmp+1); } else if((string[0]=='(') && (tmp=strchr(string, ')'))) { /* Convert a genre written as '(3)' into 'Dance' */ *tmp = 0; tag->genre = strdup(local__genre_to_string((unsigned)atoi(string+1))); } else { /* Genre is already written as 'Dance' */ tag->genre = strdup(string); } } } } /*********** * Comment * ***********/ if(0 != (id3_frame = ID3Tag_FindFrameWithID(id3_tag, ID3FID_COMMENT))) { if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_TEXT))) { if((num_chars = local__ID3Field_GetASCII_wrapper(id3_field, string, ID3V2_MAX_STRING_LEN, field_num)) > 0 && string != NULL) { local__strip(string); tag->comment = strdup(string); } } #if 0 if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_DESCRIPTION))) { char *comment1 = calloc(MAX_STRING_LEN+1); num_chars = ID3Field_GetASCII(id3_field, comment1, MAX_STRING_LEN, Item_Num); free(comment1); } if(0 != (id3_field = ID3Frame_GetField(id3_frame, ID3FN_LANGUAGE))) { char *comment2 = calloc(MAX_STRING_LEN+1); num_chars = ID3Field_GetASCII(id3_field, comment2, MAX_STRING_LEN, Item_Num); free(comment2); } #endif } free(string); /* Free allocated data */ ID3Tag_Delete(id3_tag); } return true; } #endif /* ifdef FLAC__HAS_ID3LIB */ FLAC__bool FLAC_plugin__id3v2_tag_get(const char *filename, FLAC_Plugin__CanonicalTag *tag) { #ifdef FLAC__HAS_ID3LIB return local__get_tag(filename, tag); #else (void)filename, (void)tag; return false; #endif }