//========================================================================= // 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * This file is derived from mt-daap project. */ // _get_mp3tags 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; pid3file = id3_file_open(file, ID3_FILE_MODE_READONLY); if (!pid3file) { DPRINTF(E_ERROR, L_SCAN_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_SCAN_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_SCAN_SCANNER, "Compilation: %d\n", psong->compilation); } else if (!strcmp(pid3frame->id, "APIC") && pid3frame->fields[4].binary.length && pid3frame->fields[4].binary.data) { psong->image_size = pid3frame->fields[4].binary.length; psong->image = malloc(psong->image_size); memcpy(psong->image, pid3frame->fields[4].binary.data, psong->image_size); } else 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 if (utf8_text) free(utf8_text); native_text = id3_field_getfullstring(&pid3frame->fields[3]); if (native_text) { //if (psong->comment) // free(psong->comment); utf8_text = (unsigned char*) id3_ucs4_utf8duplicate(native_text); if (utf8_text) { psong->comment = (char*) utf8_text; } } } else { if (utf8_text) free(utf8_text); } } } index++; } id3_file_close(pid3file); DPRINTF(E_INFO, L_SCAN_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) { 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_SCAN_SCANNER, "Could not find frame... quitting\n"); 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_SCAN_SCANNER, "Could not read frame header\n"); 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_SCAN_SCANNER, "Could not read frame header\n"); return; } if (_decode_mp3_frame(header,&fi)) { DPRINTF(E_DEBUG, L_SCAN_SCANNER, "Invalid frame header while averaging\n"); 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_SCAN_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_SCAN_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_SCAN_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_SCAN_SCANNER, "Error reading: %s\n", strerror(errno)); } else { DPRINTF(E_ERROR, L_SCAN_SCANNER, "File too small. Probably corrupted.\n"); } 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_SCAN_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_WARN, L_SCAN_SCANNER, "Bad header... dropping back for full frame search\n"); 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_SCAN_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 = *((int*)&buffer[index+fi.xing_offset+4+4]); xing_flags = ntohs(xing_flags); psong->vbr_scale = 78; if (xing_flags & 0x1) { /* Frames field is valid... */ fi.number_of_frames = *((int*)&buffer[index+fi.xing_offset+4+8]); fi.number_of_frames = ntohs(fi.number_of_frames); } } if ((fi.number_of_frames == 0) && (!psong->song_length)) { _mp3_get_average_bitrate(infile, &fi); } 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); } } fclose(infile); DPRINTF(E_INFO, L_SCAN_SCANNER, "Got fileinfo successfully for file=%s song_length=%d\n", file, psong->song_length); psong->blockalignment = 1; return 0; }