//========================================================================= // FILENAME : tagutils-mp3.c // DESCRIPTION : MP3 metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ /* * This file is derived from mt-daap project. */ static int _get_mp3tags(char *file, struct song_metadata *psong) { struct id3_file *pid3file; struct id3_tag *pid3tag; struct id3_frame *pid3frame; int err; int index; int used; unsigned char *utf8_text; int genre = WINAMP_GENRE_UNKNOWN; int have_utf8; int have_text; id3_ucs4_t const *native_text; char *tmp; int got_numeric_genre; id3_byte_t const *image; id3_length_t image_size = 0; pid3file = id3_file_open(file, ID3_FILE_MODE_READONLY); if(!pid3file) { DPRINTF(E_ERROR, L_SCANNER, "Cannot open %s\n", file); return -1; } pid3tag = id3_file_tag(pid3file); if(!pid3tag) { err = errno; id3_file_close(pid3file); errno = err; DPRINTF(E_WARN, L_SCANNER, "Cannot get ID3 tag for %s\n", file); return -1; } index = 0; while((pid3frame = id3_tag_findframe(pid3tag, "", index))) { used = 0; utf8_text = NULL; native_text = NULL; have_utf8 = 0; have_text = 0; if(!strcmp(pid3frame->id, "YTCP")) /* for id3v2.2 */ { psong->compilation = 1; DPRINTF(E_DEBUG, L_SCANNER, "Compilation: %d [%s]\n", psong->compilation, basename(file)); } else if(!strcmp(pid3frame->id, "APIC") && !image_size) { if( (strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "image/jpeg") == 0) || (strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "image/jpg") == 0) || (strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "jpeg") == 0) ) { image = id3_field_getbinarydata(&pid3frame->fields[4], &image_size); if( image_size ) { psong->image = malloc(image_size); memcpy(psong->image, image, image_size); psong->image_size = image_size; //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Found thumbnail: %d\n", psong->image_size); } } } if(((pid3frame->id[0] == 'T') || (strcmp(pid3frame->id, "COMM") == 0)) && (id3_field_getnstrings(&pid3frame->fields[1]))) have_text = 1; if(have_text) { native_text = id3_field_getstrings(&pid3frame->fields[1], 0); if(native_text) { have_utf8 = 1; if(lang_index >= 0) utf8_text = _get_utf8_text(native_text); // through iconv else utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text); if(!strcmp(pid3frame->id, "TIT2")) { used = 1; psong->title = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TPE1")) { used = 1; psong->contributor[ROLE_ARTIST] = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TALB")) { used = 1; psong->album = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TCOM")) { used = 1; psong->contributor[ROLE_COMPOSER] = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TIT1")) { used = 1; psong->grouping = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TPE2")) { used = 1; psong->contributor[ROLE_BAND] = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TPE3")) { used = 1; psong->contributor[ROLE_CONDUCTOR] = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TCON")) { used = 1; psong->genre = (char*)utf8_text; got_numeric_genre = 0; if(psong->genre) { if(!strlen(psong->genre)) { genre = WINAMP_GENRE_UNKNOWN; got_numeric_genre = 1; } else if(isdigit(psong->genre[0])) { genre = atoi(psong->genre); got_numeric_genre = 1; } else if((psong->genre[0] == '(') && (isdigit(psong->genre[1]))) { genre = atoi((char*)&psong->genre[1]); got_numeric_genre = 1; } if(got_numeric_genre) { if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN)) genre = WINAMP_GENRE_UNKNOWN; free(psong->genre); psong->genre = strdup(winamp_genre[genre]); } } } else if(!strcmp(pid3frame->id, "COMM")) { used = 1; psong->comment = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TPOS")) { tmp = (char*)utf8_text; strsep(&tmp, "/"); if(tmp) { psong->total_discs = atoi(tmp); } psong->disc = atoi((char*)utf8_text); } else if(!strcmp(pid3frame->id, "TRCK")) { tmp = (char*)utf8_text; strsep(&tmp, "/"); if(tmp) { psong->total_tracks = atoi(tmp); } psong->track = atoi((char*)utf8_text); } else if(!strcmp(pid3frame->id, "TDRC")) { psong->year = atoi((char*)utf8_text); } else if(!strcmp(pid3frame->id, "TLEN")) { psong->song_length = atoi((char*)utf8_text); } else if(!strcmp(pid3frame->id, "TBPM")) { psong->bpm = atoi((char*)utf8_text); } else if(!strcmp(pid3frame->id, "TCMP")) { psong->compilation = (char)atoi((char*)utf8_text); } } } // check if text tag if((!used) && (have_utf8) && (utf8_text)) free(utf8_text); // v2 COMM if((!strcmp(pid3frame->id, "COMM")) && (pid3frame->nfields == 4)) { native_text = id3_field_getstring(&pid3frame->fields[2]); if(native_text) { utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text); if((utf8_text) && (strncasecmp((char*)utf8_text, "iTun", 4) != 0)) { // read comment free(utf8_text); native_text = id3_field_getfullstring(&pid3frame->fields[3]); if(native_text) { utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text); if(utf8_text) { free(psong->comment); psong->comment = (char*)utf8_text; } } } else { free(utf8_text); } } } index++; } id3_file_close(pid3file); //DEBUG DPRINTF(E_INFO, L_SCANNER, "Got id3 tag successfully for file=%s\n", file); return 0; } // _decode_mp3_frame static int _decode_mp3_frame(unsigned char *frame, struct mp3_frameinfo *pfi) { int ver; int layer_index; int sample_index; int bitrate_index; int samplerate_index; if((frame[0] != 0xFF) || (frame[1] < 224)) { pfi->is_valid = 0; return -1; } ver = (frame[1] & 0x18) >> 3; pfi->layer = 4 - ((frame[1] & 0x6) >> 1); layer_index = sample_index = -1; switch(ver) { case 0: pfi->mpeg_version = 0x25; // 2.5 sample_index = 2; if(pfi->layer == 1) layer_index = 3; if((pfi->layer == 2) || (pfi->layer == 3)) layer_index = 4; break; case 2: pfi->mpeg_version = 0x20; // 2.0 sample_index = 1; if(pfi->layer == 1) layer_index = 3; if((pfi->layer == 2) || (pfi->layer == 3)) layer_index = 4; break; case 3: pfi->mpeg_version = 0x10; // 1.0 sample_index = 0; if(pfi->layer == 1) layer_index = 0; if(pfi->layer == 2) layer_index = 1; if(pfi->layer == 3) layer_index = 2; break; } if((layer_index < 0) || (layer_index > 4)) { pfi->is_valid = 0; return -1; } if((sample_index < 0) || (sample_index >= 2)) { pfi->is_valid = 0; return -1; } if(pfi->layer == 1) pfi->samples_per_frame = 384; if(pfi->layer == 2) pfi->samples_per_frame = 1152; if(pfi->layer == 3) { if(pfi->mpeg_version == 0x10) pfi->samples_per_frame = 1152; else pfi->samples_per_frame = 576; } bitrate_index = (frame[2] & 0xF0) >> 4; samplerate_index = (frame[2] & 0x0C) >> 2; if((bitrate_index == 0xF) || (bitrate_index == 0x0)) { pfi->is_valid = 0; return -1; } if(samplerate_index == 3) { pfi->is_valid = 0; return -1; } pfi->bitrate = bitrate_tbl[layer_index][bitrate_index]; pfi->samplerate = sample_rate_tbl[sample_index][samplerate_index]; if((frame[3] & 0xC0 >> 6) == 3) pfi->stereo = 0; else pfi->stereo = 1; if(frame[2] & 0x02) pfi->padding = 1; else pfi->padding = 0; if(pfi->mpeg_version == 0x10) { if(pfi->stereo) pfi->xing_offset = 32; else pfi->xing_offset = 17; } else { if(pfi->stereo) pfi->xing_offset = 17; else pfi->xing_offset = 9; } pfi->crc_protected = frame[1] & 0xFE; if(pfi->layer == 1) pfi->frame_length = (12 * pfi->bitrate * 1000 / pfi->samplerate + pfi->padding) * 4; else pfi->frame_length = 144 * pfi->bitrate * 1000 / pfi->samplerate + pfi->padding; if((pfi->frame_length > 2880) || (pfi->frame_length <= 0)) { pfi->is_valid = 0; return -1; } pfi->is_valid = 1; return 0; } // _mp3_get_average_bitrate // read from midle of file, and estimate static void _mp3_get_average_bitrate(FILE *infile, struct mp3_frameinfo *pfi, const char *fname) { off_t file_size; unsigned char frame_buffer[2900]; unsigned char header[4]; int index = 0; int found = 0; off_t pos; struct mp3_frameinfo fi; int frame_count = 0; int bitrate_total = 0; fseek(infile, 0, SEEK_END); file_size = ftell(infile); pos = file_size >> 1; /* now, find the first frame */ fseek(infile, pos, SEEK_SET); if(fread(frame_buffer, 1, sizeof(frame_buffer), infile) != sizeof(frame_buffer)) return; while(!found) { while((frame_buffer[index] != 0xFF) && (index < (sizeof(frame_buffer) - 4))) index++; if(index >= (sizeof(frame_buffer) - 4)) // max mp3 framesize = 2880 { DPRINTF(E_DEBUG, L_SCANNER, "Could not find frame for %s\n", basename((char *)fname)); return; } if(!_decode_mp3_frame(&frame_buffer[index], &fi)) { /* see if next frame is valid */ fseek(infile, pos + index + fi.frame_length, SEEK_SET); if(fread(header, 1, sizeof(header), infile) != sizeof(header)) { DPRINTF(E_DEBUG, L_SCANNER, "Could not read frame header for %s\n", basename((char *)fname)); return; } if(!_decode_mp3_frame(header, &fi)) found = 1; } if(!found) index++; } pos += index; // got first frame while(frame_count < 10) { fseek(infile, pos, SEEK_SET); if(fread(header, 1, sizeof(header), infile) != sizeof(header)) { DPRINTF(E_DEBUG, L_SCANNER, "Could not read frame header for %s\n", basename((char *)fname)); return; } if(_decode_mp3_frame(header, &fi)) { DPRINTF(E_DEBUG, L_SCANNER, "Invalid frame header while averaging %s\n", basename((char *)fname)); return; } bitrate_total += fi.bitrate; frame_count++; pos += fi.frame_length; } pfi->bitrate = bitrate_total / frame_count; return; } // _mp3_get_frame_count // do brute scan static void __attribute__((unused)) _mp3_get_frame_count(FILE *infile, struct mp3_frameinfo *pfi) { int pos; int frames = 0; unsigned char frame_buffer[4]; struct mp3_frameinfo fi; off_t file_size; int err = 0; int cbr = 1; int last_bitrate = 0; fseek(infile, 0, SEEK_END); file_size = ftell(infile); pos = pfi->frame_offset; while(1) { err = 1; fseek(infile, pos, SEEK_SET); if(fread(frame_buffer, 1, sizeof(frame_buffer), infile) == sizeof(frame_buffer)) { // valid frame? if(!_decode_mp3_frame(frame_buffer, &fi)) { frames++; pos += fi.frame_length; err = 0; if((last_bitrate) && (fi.bitrate != last_bitrate)) cbr = 0; last_bitrate = fi.bitrate; // no sense to scan cbr if(cbr && (frames > 100)) { DPRINTF(E_DEBUG, L_SCANNER, "File appears to be CBR... quitting frame _mp3_get_frame_count()\n"); return; } } } if(err) { if(pos > (file_size - 4096)) { pfi->number_of_frames = frames; return; } else { DPRINTF(E_ERROR, L_SCANNER, "Frame count aborted on error. Pos=%d, Count=%d\n", pos, frames); return; } } } } // _get_mp3fileinfo static int _get_mp3fileinfo(char *file, struct song_metadata *psong) { FILE *infile; struct id3header *pid3; struct mp3_frameinfo fi; unsigned int size = 0; unsigned int n_read; off_t fp_size = 0; off_t file_size; unsigned char buffer[1024]; int index; int xing_flags; int found; int first_check = 0; char frame_buffer[4]; char id3v1taghdr[4]; if(!(infile = fopen(file, "rb"))) { DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file); return -1; } memset((void*)&fi, 0, sizeof(fi)); fseek(infile, 0, SEEK_END); file_size = ftell(infile); fseek(infile, 0, SEEK_SET); if(fread(buffer, 1, sizeof(buffer), infile) != sizeof(buffer)) { if(ferror(infile)) { DPRINTF(E_ERROR, L_SCANNER, "Error reading: %s [%s]\n", strerror(errno), file); } else { DPRINTF(E_WARN, L_SCANNER, "File too small. Probably corrupted. [%s]\n", file); } fclose(infile); return -1; } pid3 = (struct id3header*)buffer; found = 0; fp_size = 0; if(strncmp((char*)pid3->id, "ID3", 3) == 0) { char tagversion[16]; /* found an ID3 header... */ size = (pid3->size[0] << 21 | pid3->size[1] << 14 | pid3->size[2] << 7 | pid3->size[3]); fp_size = size + sizeof(struct id3header); first_check = 1; snprintf(tagversion, sizeof(tagversion), "ID3v2.%d.%d", pid3->version[0], pid3->version[1]); psong->tagversion = strdup(tagversion); } index = 0; /* Here we start the brute-force header seeking. Sure wish there * weren't so many crappy mp3 files out there */ while(!found) { fseek(infile, fp_size, SEEK_SET); if((n_read = fread(buffer, 1, sizeof(buffer), infile)) < 4) // at least mp3 frame header size (i.e. 4 bytes) { fclose(infile); return 0; } index = 0; while(!found) { while((buffer[index] != 0xFF) && (index < (n_read - 50))) index++; if((first_check) && (index)) { fp_size = 0; first_check = 0; if(n_read < sizeof(buffer)) { fclose(infile); return 0; } break; } if(index > (n_read - 50)) { fp_size += index; if(n_read < sizeof(buffer)) { fclose(infile); return 0; } break; } if(!_decode_mp3_frame(&buffer[index], &fi)) { if(!strncasecmp((char*)&buffer[index + fi.xing_offset + 4], "XING", 4)) { /* no need to check further... if there is a xing header there, * this is definately a valid frame */ found = 1; fp_size += index; } else { /* No Xing... check for next frame to validate current fram is correct */ fseek(infile, fp_size + index + fi.frame_length, SEEK_SET); if(fread(frame_buffer, 1, sizeof(frame_buffer), infile) == sizeof(frame_buffer)) { if(!_decode_mp3_frame((unsigned char*)frame_buffer, &fi)) { found = 1; fp_size += index; } } else { DPRINTF(E_ERROR, L_SCANNER, "Could not read frame header: %s\n", file); fclose(infile); return 0; } if(!found) { // cannot find second frame. Song may be too short. So assume first frame is valid. found = 1; fp_size += index; } } } if(!found) { index++; if(first_check) { DPRINTF(E_INFO, L_SCANNER, "Bad header... dropping back for full frame search [%s]\n", psong->path); first_check = 0; fp_size = 0; break; } } } } fi.frame_offset = fp_size; psong->audio_offset = fp_size; psong->audio_size = file_size - fp_size; // check if last 128 bytes is ID3v1.0 ID3v1.1 tag fseek(infile, file_size - 128, SEEK_SET); if(fread(id3v1taghdr, 1, 4, infile) == 4) { if(id3v1taghdr[0] == 'T' && id3v1taghdr[1] == 'A' && id3v1taghdr[2] == 'G') { psong->audio_size -= 128; } } if(_decode_mp3_frame(&buffer[index], &fi)) { fclose(infile); DPRINTF(E_ERROR, L_SCANNER, "Could not find sync frame: %s\n", file); return 0; } /* now check for an XING header */ psong->vbr_scale = -1; if(!strncasecmp((char*)&buffer[index + fi.xing_offset + 4], "XING", 4)) { xing_flags = buffer[index+fi.xing_offset+4+4] << 24 | buffer[index+fi.xing_offset+4+5] << 16 | buffer[index+fi.xing_offset+4+6] << 8 | buffer[index+fi.xing_offset+4+7]; psong->vbr_scale = 78; if(xing_flags & 0x1) { /* Frames field is valid... */ fi.number_of_frames = buffer[index+fi.xing_offset+4+8] << 24 | buffer[index+fi.xing_offset+4+9] << 16 | buffer[index+fi.xing_offset+4+10] << 8 | buffer[index+fi.xing_offset+4+11]; } } if((fi.number_of_frames == 0) && (!psong->song_length)) { _mp3_get_average_bitrate(infile, &fi, file); } psong->bitrate = fi.bitrate * 1000; psong->samplerate = fi.samplerate; if(!psong->song_length) { if(fi.number_of_frames) { psong->song_length = (int)((double)(fi.number_of_frames * fi.samples_per_frame * 1000.) / (double)fi.samplerate); psong->vbr_scale = 78; } else { psong->song_length = (int)((double)(file_size - fp_size) * 8. / (double)fi.bitrate); } } psong->channels = fi.stereo ? 2 : 1; fclose(infile); //DEBUG DPRINTF(E_INFO, L_SCANNER, "Got fileinfo successfully for file=%s song_length=%d\n", file, psong->song_length); psong->blockalignment = 1; xasprintf(&(psong->dlna_pn), "MP3"); return 0; }