view Plugins/General/scrobbler/tags/id3v2.c @ 1000:c0bc62c5dec1 trunk

[svn] A GCC warning pointed out that the *bp++ statement did not actually do anything useful, quite certain that the author intended \(*bp\)++ or *bp += 1 here.
author chainsaw
date Mon, 01 May 2006 15:08:28 -0700
parents 86ca43d8a845
children 62726fb1cb3b
line wrap: on
line source

/*
 *   libmetatag - A media file tag-reader library
 *   Copyright (C) 2003, 2004  Pipian
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "include/id3v2.h"
#include "include/endian.h"
#include "../fmt.h"
#include "../config.h"
#include "include/unicode.h"
#define BUFFER_SIZE 4096

id3_lookup_t id3v22_lookup[] = 
{
	{"BUF", ID3V22_BUF}, {"CNT", ID3V22_CNT}, {"COM", ID3V22_COM}, 
	{"CRA", ID3V22_CRA}, {"CRM", ID3V22_CRM}, {"ETC", ID3V22_ETC}, 
	{"EQU", ID3V22_EQU}, {"GEO", ID3V22_GEO}, {"IPL", ID3V22_IPL}, 
	{"LNK", ID3V22_LNK}, {"MCI", ID3V22_MCI}, {"MLL", ID3V22_MLL}, 
	{"PIC", ID3V22_PIC}, {"POP", ID3V22_POP}, {"REV", ID3V22_REV}, 
	{"RVA", ID3V22_RVA}, {"SLT", ID3V22_SLT}, {"STC", ID3V22_STC}, 
	{"TAL", ID3V22_TAL}, {"TBP", ID3V22_TBP}, {"TCM", ID3V22_TCM}, 
	{"TCO", ID3V22_TCO}, {"TCR", ID3V22_TCR}, {"TDA", ID3V22_TDA}, 
	{"TDY", ID3V22_TDY}, {"TEN", ID3V22_TEN}, {"TFT", ID3V22_TFT}, 
	{"TIM", ID3V22_TIM}, {"TKE", ID3V22_TKE}, {"TLA", ID3V22_TLA}, 
	{"TLE", ID3V22_TLE}, {"TMT", ID3V22_TMT}, {"TOA", ID3V22_TOA}, 
	{"TOF", ID3V22_TOF}, {"TOL", ID3V22_TOL}, {"TOR", ID3V22_TOR}, 
	{"TOT", ID3V22_TOT}, {"TP1", ID3V22_TP1}, {"TP2", ID3V22_TP2}, 
	{"TP3", ID3V22_TP3}, {"TP4", ID3V22_TP4}, {"TPA", ID3V22_TPA}, 
	{"TPB", ID3V22_TPB}, {"TRC", ID3V22_TRC}, {"TRD", ID3V22_TRD}, 
	{"TRK", ID3V22_TRK}, {"TSI", ID3V22_TSI}, {"TSS", ID3V22_TSS}, 
	{"TT1", ID3V22_TT1}, {"TT2", ID3V22_TT2}, {"TT3", ID3V22_TT3}, 
	{"TXT", ID3V22_TXT}, {"TXX", ID3V22_TXX}, {"TYE", ID3V22_TYE}, 
	{"UFI", ID3V22_UFI}, {"ULT", ID3V22_ULT}, {"WAF", ID3V22_WAF}, 
	{"WAR", ID3V22_WAR}, {"WAS", ID3V22_WAS}, {"WCM", ID3V22_WCM}, 
	{"WCP", ID3V22_WCP}, {"WPB", ID3V22_WPB}, {"WXX", ID3V22_WXX},
	{NULL, -1}
};

id3_lookup_t id3v24_lookup[] =
{
	{"AENC", ID3V24_AENC}, {"APIC", ID3V24_APIC}, {"ASPI", ID3V24_ASPI}, 
	{"COMM", ID3V24_COMM}, {"COMR", ID3V24_COMR}, {"ENCR", ID3V24_ENCR}, 
	{"EQU2", ID3V24_EQU2}, {"ETCO", ID3V24_ETCO}, {"GEOB", ID3V24_GEOB}, 
	{"GRID", ID3V24_GRID}, {"LINK", ID3V24_LINK}, {"MCDI", ID3V24_MCDI}, 
	{"MLLT", ID3V24_MLLT}, {"OWNE", ID3V24_OWNE}, {"PRIV", ID3V24_PRIV}, 
	{"PCNT", ID3V24_PCNT}, {"POPM", ID3V24_POPM}, {"POSS", ID3V24_POSS}, 
	{"RBUF", ID3V24_RBUF}, {"RVA2", ID3V24_RVA2}, {"RVRB", ID3V24_RVRB}, 
	{"SEEK", ID3V24_SEEK}, {"SIGN", ID3V24_SIGN}, {"SYLT", ID3V24_SYLT}, 
	{"SYTC", ID3V24_SYTC}, {"TALB", ID3V24_TALB}, {"TBPM", ID3V24_TBPM}, 
	{"TCOM", ID3V24_TCOM}, {"TCON", ID3V24_TCON}, {"TCOP", ID3V24_TCOP}, 
	{"TDEN", ID3V24_TDEN}, {"TDLY", ID3V24_TDLY}, {"TDOR", ID3V24_TDOR}, 
	{"TDRC", ID3V24_TDRC}, {"TDRL", ID3V24_TDRL}, {"TDTG", ID3V24_TDTG}, 
	{"TENC", ID3V24_TENC}, {"TEXT", ID3V24_TEXT}, {"TFLT", ID3V24_TFLT}, 
	{"TIPL", ID3V24_TIPL}, {"TIT1", ID3V24_TIT1}, {"TIT2", ID3V24_TIT2}, 
	{"TIT3", ID3V24_TIT3}, {"TKEY", ID3V24_TKEY}, {"TLAN", ID3V24_TLAN}, 
	{"TLEN", ID3V24_TLEN}, {"TMCL", ID3V24_TMCL}, {"TMED", ID3V24_TMED}, 
	{"TMOO", ID3V24_TMOO}, {"TOAL", ID3V24_TOAL}, {"TOFN", ID3V24_TOFN}, 
	{"TOLY", ID3V24_TOLY}, {"TOPE", ID3V24_TOPE}, {"TOWN", ID3V24_TOWN}, 
	{"TPE1", ID3V24_TPE1}, {"TPE2", ID3V24_TPE2}, {"TPE3", ID3V24_TPE3}, 
	{"TPE4", ID3V24_TPE4}, {"TPOS", ID3V24_TPOS}, {"TPRO", ID3V24_TPRO}, 
	{"TPUB", ID3V24_TPUB}, {"TRCK", ID3V24_TRCK}, {"TRSN", ID3V24_TRSN}, 
	{"TRSO", ID3V24_TRSO}, {"TSOA", ID3V24_TSOA}, {"TSOP", ID3V24_TSOP}, 
	{"TSOT", ID3V24_TSOT}, {"TSRC", ID3V24_TSRC}, {"TSSE", ID3V24_TSSE}, 
	{"TSST", ID3V24_TSST}, {"TXXX", ID3V24_TXXX}, {"UFID", ID3V24_UFID}, 
	{"USER", ID3V24_USER}, {"USLT", ID3V24_USLT}, {"WCOM", ID3V24_WCOM}, 
	{"WCOP", ID3V24_WCOP}, {"WOAF", ID3V24_WOAF}, {"WOAR", ID3V24_WOAR}, 
	{"WOAS", ID3V24_WOAS}, {"WORS", ID3V24_WORS}, {"WPAY", ID3V24_WPAY}, 
	{"WPUB", ID3V24_WPUB}, {"WXXX", ID3V24_WXXX}, {NULL, -1}
};

id3_lookup_t id3v23_lookup[] = 
{
	{"AENC", ID3V23_AENC}, {"APIC", ID3V23_APIC}, {"COMM", ID3V23_COMM}, 
	{"COMR", ID3V23_COMR}, {"ENCR", ID3V23_ENCR}, {"EQUA", ID3V23_EQUA}, 
	{"ETCO", ID3V23_ETCO}, {"GEOB", ID3V23_GEOB}, {"GRID", ID3V23_GRID}, 
	{"IPLS", ID3V23_IPLS}, {"LINK", ID3V23_LINK}, {"MCDI", ID3V23_MCDI}, 
	{"MLLT", ID3V23_MLLT}, {"OWNE", ID3V23_OWNE}, {"PRIV", ID3V23_PRIV}, 
	{"PCNT", ID3V23_PCNT}, {"POPM", ID3V23_POPM}, {"POSS", ID3V23_POSS}, 
	{"RBUF", ID3V23_RBUF}, {"RVAD", ID3V23_RVAD}, {"RVRB", ID3V23_RVRB}, 
	{"SYLT", ID3V23_SYLT}, {"SYTC", ID3V23_SYTC}, {"TALB", ID3V23_TALB}, 
	{"TBPM", ID3V23_TBPM}, {"TCOM", ID3V23_TCOM}, {"TCON", ID3V23_TCON}, 
	{"TCOP", ID3V23_TCOP}, {"TDAT", ID3V23_TDAT}, {"TDLY", ID3V23_TDLY}, 
	{"TENC", ID3V23_TENC}, {"TEXT", ID3V23_TEXT}, {"TFLT", ID3V23_TFLT}, 
	{"TIME", ID3V23_TIME}, {"TIT1", ID3V23_TIT1}, {"TIT2", ID3V23_TIT2}, 
	{"TIT3", ID3V23_TIT3}, {"TKEY", ID3V23_TKEY}, {"TLAN", ID3V23_TLAN}, 
	{"TLEN", ID3V23_TLEN}, {"TMED", ID3V23_TMED}, {"TOAL", ID3V23_TOAL}, 
	{"TOFN", ID3V23_TOFN}, {"TOLY", ID3V23_TOLY}, {"TOPE", ID3V23_TOPE}, 
	{"TORY", ID3V23_TORY}, {"TOWN", ID3V23_TOWN}, {"TPE1", ID3V23_TPE1}, 
	{"TPE2", ID3V23_TPE2}, {"TPE3", ID3V23_TPE3}, {"TPE4", ID3V23_TPE4}, 
	{"TPOS", ID3V23_TPOS}, {"TPUB", ID3V23_TPUB}, {"TRCK", ID3V23_TRCK}, 
	{"TRDA", ID3V23_TRDA}, {"TRSN", ID3V23_TRSN}, {"TRSO", ID3V23_TRSO}, 
	{"TSIZ", ID3V23_TSIZ}, {"TSRC", ID3V23_TSRC}, {"TSSE", ID3V23_TSSE}, 
	{"TYER", ID3V23_TYER}, {"TXXX", ID3V23_TXXX}, {"UFID", ID3V23_UFID}, 
	{"USER", ID3V23_USER}, {"USLT", ID3V23_USLT}, {"WCOM", ID3V23_WCOM}, 
	{"WCOP", ID3V23_WCOP}, {"WOAF", ID3V23_WOAF}, {"WOAR", ID3V23_WOAR}, 
	{"WOAS", ID3V23_WOAS}, {"WORS", ID3V23_WORS}, {"WPAY", ID3V23_WPAY}, 
	{"WPUB", ID3V23_WPUB}, {"WXXX", ID3V23_WXXX}, {NULL, -1}
};


typedef struct
{
	unsigned char *check;
	int count;
} resync_t;

typedef struct
{
	int unsync, extended, size;
	char version[2];
} id3header_t;

static resync_t *checkunsync(char *syncCheck, int size)
{
	int i, j;
	resync_t *sync;
	
	sync = malloc(sizeof(resync_t));

	sync->check = (unsigned char*)syncCheck;
	sync->count = 0;
	
	if(size == 0)
		size = strlen((char*)sync->check);

	for(i = 0; i < size; i++)
	{
		if(sync->check[i] == 0xFF && sync->check[i + 1] == 0x00)
		{
			for(j = i + 1; j < size - 1; j++)
				syncCheck[j] = syncCheck[j + 1];
			sync->check[j] = '\0';
			sync->count++;
		}
	}

	return sync;
}

static void unsync(char *data, char *bp)
{
	resync_t *unsynced;
	char *syncFix = NULL;
	int i;
	
	unsynced = checkunsync(data, 0);
	while(unsynced->count > 0)
	{
		if(syncFix != NULL)
			syncFix = realloc(syncFix, unsynced->count);
		else
			syncFix = malloc(unsynced->count);
		memcpy(syncFix, bp, unsynced->count);
		bp += unsynced->count;
		for(i = 0; i < unsynced->count; i++)
			data[4 - unsynced->count + i] = syncFix[i];
		free(unsynced);
		unsynced = checkunsync(data, 0);
	}
	free(unsynced);
	free(syncFix);
}

/*
 * Header:
 *
 * identifier: 3 bytes "ID3" ("3DI" if footer)
 * version: 2 bytes
 * flags: 1 byte
 * tag size: 4 bytes
 */
static id3header_t *read_header(VFSFile *fp)
{
	id3header_t *id3_data = calloc(sizeof(id3header_t), 1);
	char id3_flags, cToInt[4];
	int bottom = 0;

	vfs_fread(cToInt, 1, 3, fp);
	if(strncmp(cToInt, "3DI", 3) == 0)
		bottom = 1;
	vfs_fread(id3_data->version, 1, 2, fp);
	vfs_fread(&id3_flags, 1, 1, fp);
	if((id3_flags & 0x80) == 0x80)
		id3_data->unsync = 1;
	if((id3_flags & 0x40) == 0x40 && id3_data->version[0] > 0x02)
		id3_data->extended = 1;
	vfs_fread(cToInt, 1, 4, fp);
	id3_data->size = synchsafe2int(cToInt);
	if(bottom == 1)
		vfs_fseek(fp, -10 - id3_data->size, SEEK_CUR);
	
#ifdef META_DEBUG
	if(id3_data->version[0] == 0x04)
	{
		pdebug("Version: ID3v2.4", META_DEBUG);
	}
	else if(id3_data->version[0] == 0x03)
	{
		pdebug("Version: ID3v2.3", META_DEBUG);
	}
	else if(id3_data->version[0] == 0x02)
	{
		pdebug("Version: ID3v2.2", META_DEBUG);
	}
#endif
	if(id3_data->version[0] < 0x02 || id3_data->version[0] > 0x04)
	{
		free(id3_data);
		return NULL;
	}
		
	return id3_data;
}

static int id3_lookupframe(char *tag, int tagver)
{
	int i;

	switch (tagver) {
	case ID3v22:
		for (i = 0; id3v22_lookup[i].frameid; i++)
			if (!strcmp(tag, id3v22_lookup[i].frameid))
				return id3v22_lookup[i].code;
		return -1;
		break;
	case ID3v23:
		for (i = 0; id3v23_lookup[i].frameid; i++)
			if (!strcmp(tag, id3v23_lookup[i].frameid))
				return id3v23_lookup[i].code;
		return -1;
		break;
	case ID3v24:
		for (i = 0; id3v23_lookup[i].frameid; i++)
			if (!strcmp(tag, id3v24_lookup[i].frameid))
				return id3v24_lookup[i].code;
		return -1;
		break;
	}
	return -1;
}

static framedata_t *parseFrame(char **bp, char *end, id3header_t *id3_data)
{
	static unsigned char frameid[5];
	unsigned char frameflags[2] = "", cToInt[5];
	int framesize, frameidcode;
	framedata_t *framedata;

	/* TODO: Unsync, decompress, decrypt, grouping, data length */
	switch (id3_data->version[0]) {
	case 2:
		if (end - *bp < 6)
			return NULL;
		frameid[3] = 0;
		memcpy(frameid, *bp, 3);
		*bp += 3;
		frameidcode = id3_lookupframe((char*)frameid, ID3v22);
		memcpy(cToInt, *bp, 3);
		cToInt[3] = 0;
		if(id3_data->unsync)
			unsync((char*)cToInt, *bp);
		framesize = be24int(cToInt);
		*bp += 3;
		break;
	case 3:
		if (end - *bp < 10)
			return NULL;
		frameid[4] = 0;
		memcpy(frameid, *bp, 4);
		*bp += 4;
		frameidcode = id3_lookupframe((char*)frameid, ID3v23);
		memcpy(cToInt, *bp, 4);
		if(id3_data->unsync)
			unsync((char*)cToInt, *bp);
		framesize = be2int(cToInt);
		*bp += 4;

		memcpy(frameflags, *bp, 2);
		*bp += 2;
		break;
	case 4:
		if (end - *bp < 10)
			return NULL;
		frameid[4] = 0;
		memcpy(frameid, *bp, 4);
		*bp += 4;
		frameidcode = id3_lookupframe((char*)frameid, ID3v24);
		memcpy(cToInt, *bp, 4);
		framesize = synchsafe2int(cToInt);
		*bp += 4;

		memcpy(frameflags, *bp, 2);
		*bp += 2;
		break;
	default:
		/* Should not be reached */
		return NULL;
	}

	/* printf("found id:%s, size:%-8d\n", frameid, framesize); */
	if (framesize > end - *bp)
		return NULL;
	framedata = calloc(sizeof(framedata_t), 1);
	framedata->frameid = frameidcode;
	if(id3_data->unsync)
		frameflags[1] |= 0x02;
	framedata->flags = malloc(2);
	memcpy(framedata->flags, frameflags, 2);
	if(id3_data->version[0] == 0x04)
	{
		/*
		 * 4 bytes extra for compression or original size
		 * 1 byte extra for encyption
		 * 1 byte extra for grouping
		 */
		if((frameflags[1] & 0x08) == 0x08 ||
			(frameflags[1] & 0x01) == 0x01)
		{
			*bp += 4;
			framesize -= 4;
		}
		if((frameflags[1] & 0x04) == 0x04)
		{
			(*bp)++;
			framesize--;
		}
		if((frameflags[1] & 0x40) == 0x40)
		{
			(*bp)++;
			framesize--;
		}
	}
	else if(id3_data->version[0] == 0x03)
	{
		/*
		 * 4 bytes extra for compression or original size
		 * 1 byte extra for encyption
		 * 1 byte extra for grouping
		 */
		if((frameflags[1] & 0x80) == 0x80)
		{
			char tmp[4];
			
			memcpy(tmp, *bp, 4);
			*bp += 4;
			if((frameflags[1] & 0x02) == 0x02)
				unsync(tmp, *bp);
			framesize -= 4;
		}
		if((frameflags[1] & 0x40) == 0x40)
		{
			(*bp)++;
			framesize--;
		}
		if((frameflags[1] & 0x20) == 0x20)
		{
			(*bp)++;
			framesize--;
		}
	}
	framedata->len = framesize;
	framedata->data = malloc(framesize);
	memcpy(framedata->data, *bp, framesize);
	*bp += framesize;
	
	/* Parse text appropriately to UTF-8. */
	if(frameid[0] == 'T' && strcmp((char*)frameid, "TXXX") &&
		strcmp((char*)frameid, "TXX"))
	{
		unsigned char *ptr, *data = NULL, *utf = NULL;
		int encoding;
		
		ptr = framedata->data;

		if(framedata->len == 0)
		{
			framedata->data = realloc(framedata->data, 1);
			framedata->data[0] = '\0';
			return framedata;
		}
		
		encoding = *(ptr++);
		data = realloc(data, framedata->len);
		*(data + framedata->len - 1) = '\0';
		memcpy(data, ptr, framedata->len - 1);
		if((framedata->flags[1] & 0x02) == 0x02)
		{
			resync_t *unsync = checkunsync((char*)data, framedata->len);
			framedata->len -= unsync->count;
			free(unsync);
		}
		if(encoding == 0x00)
		{
			if(utf != NULL)
				free(utf);
			iso88591_to_utf8(data, framedata->len - 1, &utf);
		}
		else if(encoding == 0x01)
		{
			if(utf != NULL)
				free(utf);
			utf16bom_to_utf8(data, framedata->len - 1, &utf);
		}
		else if(encoding == 0x02)
		{
			if(utf != NULL)
				free(utf);
			utf16be_to_utf8(data, framedata->len - 1, &utf);
		}
		else if(encoding == 0x03)
		{
			utf = realloc(utf, framedata->len);
			strcpy((char*)utf, (char*)data);
		}
		framedata->len = strlen((char*)utf) + 1;
		framedata->data = realloc(framedata->data, framedata->len);
		strcpy((char*)framedata->data, (char*)utf);
		framedata->data[framedata->len - 1] = '\0';
		free(utf);
		free(data);
	}
	/* Or unsync. */
	else
	{
		unsigned char *data = NULL, *ptr;
		
		ptr = framedata->data;
		
		data = realloc(data, framedata->len);
		// *(data + framedata->len - 1) = '\0';
		memcpy(data, ptr, framedata->len);
		if((framedata->flags[1] & 0x02) == 0x02)
		{
			resync_t *unsync = checkunsync((char*)data, framedata->len);
			framedata->len -= unsync->count;
			free(unsync);
		}
		framedata->data = realloc(framedata->data, framedata->len);
		memcpy(framedata->data, data, framedata->len);
		free(data);
	}
	
	return framedata;
}

static id3v2_t *readFrames(char *bp, char *end, id3header_t *id3_data)
{
	id3v2_t *id3v2 = calloc(sizeof(id3v2_t), 1);

	while(bp < end)
	{
		framedata_t *framedata = NULL;

		if(*bp == '\0')
			break;
		
		framedata = parseFrame(&bp, end, id3_data);
		
		id3v2->items = realloc(id3v2->items, (id3v2->numitems + 1) * 
			sizeof(framedata_t *));
		id3v2->items[id3v2->numitems++] = framedata;
	}
	id3v2->version = id3_data->version[0];
	
	return id3v2;
}

/*unsigned char *ID3v2_parseText(framedata_t *frame)
{
	unsigned char *data = NULL, *utf = NULL, *ptr;
	char encoding;
	
	ptr = frame->data;
	
	encoding = *(ptr++);
	data = realloc(data, frame->len);
	memset(data, '\0', frame->len);
	memcpy(data, ptr, frame->len - 1);
	if((frame->flags[1] & 0x02) == 0x02)
	{
		resync_t *unsync = checkunsync(data, 0);
		free(unsync);
	}
	if(encoding == 0x00)
	{
		if(utf != NULL)
			free(utf);
		iso88591_to_utf8(data, &utf);
	}
	else if(encoding == 0x01)
	{
		if(utf != NULL)
			free(utf);
		utf16bom_to_utf8(data, frame->len - 1, &utf);
	}
	else if(encoding == 0x02)
	{
		if(utf != NULL)
			free(utf);
		utf16be_to_utf8(data, frame->len - 1, &utf);
	}
	else if(encoding == 0x03)
	{
		utf = realloc(utf, strlen(data) + 1);
		strcpy(utf, data);
	}
	free(data);
	
	return utf;
}

unsigned char *ID3v2_getData(framedata_t *frame)
{
	unsigned char *data = NULL, *ptr;
	
	ptr = frame->data;
	
	data = realloc(data, frame->len + 1);
	memset(data, '\0', frame->len + 1);
	memcpy(data, ptr, frame->len);
	if((frame->flags[1] & 0x02) == 0x02)
	{
		resync_t *unsync = checkunsync(data, frame->len);
		free(unsync);
	}
	
	return data;
}*/

int findID3v2(VFSFile *fp)
{
	unsigned char tag_buffer[BUFFER_SIZE], *bp = tag_buffer;
	int pos, search = -1, i, status = 0, charsRead;

	charsRead = vfs_fread(tag_buffer, 1, 10, fp);
	pos = 0;
	bp = tag_buffer;
	while(status == 0 && !vfs_feof(fp))
	{
		if(search == -1)
		{
			if((strncmp((char*)bp, "ID3", 3) == 0 ||
				strncmp((char*)bp, "3DI", 3) == 0))
					status = 1;
			else
			{
				vfs_fseek(fp, 3, SEEK_END);
				charsRead = vfs_fread(tag_buffer, 1, 3, fp);
				search = -2;
			}
		}
		else
		{
			if(search == -2)
			{
				bp = tag_buffer;
				pos = vfs_ftell(fp);
				if((strncmp((char*)bp, "ID3", 3) == 0 ||
					strncmp((char*)bp, "3DI", 3) == 0)) status = 1;
				search = 1;
			}
			if(status != 1)
			{
				pos = vfs_ftell(fp) - BUFFER_SIZE;
				vfs_fseek(fp, pos, SEEK_SET);
				charsRead = vfs_fread(tag_buffer, 1, BUFFER_SIZE,
						fp);

				bp = tag_buffer;
				for(i = 0; i < charsRead - 3 && status == 0;
					i++)
				{
					bp++;
					if((strncmp((char*)bp, "ID3", 3) == 0 ||
						strncmp((char*)bp, "3DI", 3) == 0))
							status = 1;
				}
				if(status == 1)
					pos += bp - tag_buffer;
				pos -= BUFFER_SIZE - 9;
				if((pos < -BUFFER_SIZE + 9 || vfs_feof(fp)) &&
					status != 1)
						status = -1;
			}
		}
		/*
		 * An ID3v2 tag can be detected with the following pattern:
		 *
		 * $49 44 33 yy yy xx zz zz zz zz
		 *
		 * Where yy is less than $FF, xx is the 'flags' byte and zz is 
		 * less than $80.
		 */
		if(status == 1 && *(bp + 3) < 0xFF && *(bp + 4) < 0xFF &&
			*(bp + 6) < 0x80 && *(bp + 7) < 0x80 &&
			*(bp + 8) < 0x80 && *(bp + 9) < 0x80)
				status = 1;
		else if(status != -1)
			status = 0;
		if(search == 0)
			search = -1;
	}
	if(status < 0 || vfs_feof(fp))
		return -1;
	else
		return pos;
}

id3v2_t *readID3v2(char *filename)
{
	VFSFile *fp;
	id3v2_t *id3v2 = NULL;
	int pos;
	
	fp = vfs_fopen(filename, "rb");

	if(!fp)
	{
		pdebug("Couldn't open file!", META_DEBUG);
		return NULL;
	}
	
	vfs_fseek(fp, 0, SEEK_SET);

	pdebug("Searching for tag...", META_DEBUG);
	pos = findID3v2(fp);
	if(pos > -1)
	{
		id3header_t *id3_data;
		char *tag_buffer, *bp;
		
		/* Found the tag. */
		vfs_fseek(fp, pos, SEEK_SET);
		pdebug("Found ID3v2 tag...", META_DEBUG);
		/* Read the header */
		id3_data = read_header(fp);
		if(id3_data == NULL)
		{
			pdebug("Or not.", META_DEBUG);
			vfs_fclose(fp);
			return NULL;
		}
		/* Read tag into buffer */
		tag_buffer = malloc(id3_data->size);
		vfs_fread(tag_buffer, 1, id3_data->size, fp);
		bp = tag_buffer;
		/* Skip extended header */
		if(id3_data->extended)
		{
			char cToInt[4];
			
			memcpy(cToInt, bp, 4);
			bp += 4;
			if(id3_data->version[0] == 0x03 &&
				id3_data->unsync == 1)
					unsync(cToInt, bp);
			if(id3_data->version[0] > 0x03)
				bp += synchsafe2int(cToInt);
			else
				bp += be2int(cToInt);
		}
		/* Read frames into id3v2_t */
		id3v2 = readFrames(bp, tag_buffer + id3_data->size, id3_data);

		free(tag_buffer);
		free(id3_data);
	}
	
	vfs_fclose(fp);
	
	return id3v2;
}

void freeID3v2(id3v2_t *id3v2)
{
	int i;
	
	for(i = 0; i < id3v2->numitems; i++)
	{
		framedata_t *frame;
		
		frame = id3v2->items[i];
		free(frame->flags);
		free(frame->data);
		free(frame);
	}
	free(id3v2->items);
	free(id3v2);
}