Diff of /tagutils/tagutils-mp3.c [000000] .. [b74e2d]  Maximize  Restore

Switch to side-by-side view

--- a
+++ b/tagutils/tagutils-mp3.c
@@ -0,0 +1,776 @@
+//=========================================================================
+// 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;
+	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\n", psong->compilation);
+		}
+		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]), "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
+					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);
+	//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\n", strerror(errno));
+		}
+		else
+		{
+			DPRINTF(E_ERROR, L_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_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_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_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, 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;
+	asprintf(&(psong->dlna_pn), "MP3");
+
+	return 0;
+}

Get latest updates about Open Source Projects, Conferences and News.

Sign up for the SourceForge newsletter:

JavaScript is required for this form.





No, thanks