/*
 *    Copyright (C) 1999-2000, 2001,  Espen Skoglund
 *    Copyright (C) 1999-2004  Haavard Kvaalen
 *
 * $Id: id3_frame.c,v 1.21 2004/04/04 16:14:31 havard Exp $
 *
 * 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.
 */
#ifdef HAVE_LIBZ
#include <zlib.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>

#include "id3.h"
#include "id3_header.h"

static void* id3_frame_get_dataptr(struct id3_frame *frame);
static int id3_frame_get_size(struct id3_frame *frame);
static int id3_read_frame_v22(struct id3_tag *id3);


/*
 * Description of all valid ID3v2 frames.
 */
static struct id3_framedesc framedesc[] = {
	{ID3_AENC, "AENC", "Audio encryption"},
	{ID3_APIC, "APIC", "Attached picture"},
	{ID3_ASPI, "ASPI", "Audio seek point index"},		/* v4 only */

	{ID3_COMM, "COMM", "Comments"},
	{ID3_COMR, "COMR", "Commercial frame"},

	{ID3_ENCR, "ENCR", "Encryption method registration"},
	{ID3_EQUA, "EQUA", "Equalization"},			/* v3 only */
	{ID3_EQU2, "EQU2", "Equalization (2)"},			/* v4 only */
	{ID3_ETCO, "ETCO", "Event timing codes"},

	{ID3_GEOB, "GEOB", "General encapsulated object"},
	{ID3_GRID, "GRID", "Group identification registration"},

	{ID3_IPLS, "IPLS", "Involved people list"},		/* v3 only */

	{ID3_LINK, "LINK", "Linked information"},

	{ID3_MCDI, "MCDI", "Music CD identifier"},
	{ID3_MLLT, "MLLT", "MPEG location lookup table"},

	{ID3_OWNE, "OWNE", "Ownership frame"},

	{ID3_PRIV, "PRIV", "Private frame"},
	{ID3_PCNT, "PCNT", "Play counter"},
	{ID3_POPM, "POPM", "Popularimeter"},
	{ID3_POSS, "POSS", "Position synchronisation frame"},

	{ID3_RBUF, "RBUF", "Recommended buffer size"},
	{ID3_RVAD, "RVAD", "Relative volume adjustment"},	/* v3 only */
	{ID3_RVA2, "RVA2", "RVA2 Relative volume adjustment (2)"},/* v4 only */
	{ID3_RVRB, "RVRB", "Reverb"},

	{ID3_SEEK, "SEEK", "Seek frame"},			/* v4 only */
	{ID3_SIGN, "SIGN", "Signature frame"},			/* v4 only */
	{ID3_SYLT, "SYLT", "Synchronized lyric/text"},
	{ID3_SYTC, "SYTC", "Synchronized tempo codes"},

	{ID3_TALB, "TALB", "Album/Movie/Show title"},
	{ID3_TBPM, "TBPM", "BPM (beats per minute)"},
	{ID3_TCOM, "TCOM", "Composer"},
	{ID3_TCON, "TCON", "Content type"},
	{ID3_TCOP, "TCOP", "Copyright message"},
	{ID3_TDAT, "TDAT", "Date"},				/* v3 only */
	{ID3_TDEN, "TDEN", "Encoding time"},			/* v4 only */
	{ID3_TDLY, "TDLY", "Playlist delay"},
	{ID3_TDOR, "TDOR", "Original release time"},		/* v4 only */
	{ID3_TDRC, "TDRC", "Recording time"},			/* v4 only */
	{ID3_TDRL, "TDRL", "Release time"},			/* v4 only */
	{ID3_TDTG, "TDTG", "Tagging time"},			/* v4 only */

	{ID3_TENC, "TENC", "Encoded by"},
	{ID3_TEXT, "TEXT", "Lyricist/Text writer"},
	{ID3_TFLT, "TFLT", "File type"},
	{ID3_TIME, "TIME", "Time"},				/* v3 only */
	{ID3_TIPL, "TIPL", "Involved people list"},		/* v4 only */
	{ID3_TIT1, "TIT1", "Content group description"},
	{ID3_TIT2, "TIT2", "Title/songname/content description"},
	{ID3_TIT3, "TIT3", "Subtitle/Description refinement"},
	{ID3_TKEY, "TKEY", "Initial key"},
	{ID3_TLAN, "TLAN", "Language(s)"},
	{ID3_TLEN, "TLEN", "Length"},
	{ID3_TMCL, "TMCL", "Musician credits list"},		/* v4 only */
	{ID3_TMOO, "TMOO", "Mood"},				/* v4 only */
	{ID3_TMED, "TMED", "Media type"},
	{ID3_TOAL, "TOAL", "Original album/movie/show title"},
	{ID3_TOFN, "TOFN", "Original filename"},
	{ID3_TOLY, "TOLY", "Original lyricist(s)/text writer(s)"},
	{ID3_TOPE, "TOPE", "Original artist(s)/performer(s)"},
	{ID3_TORY, "TORY", "Original release year"},		/* v3 only */
	{ID3_TOWN, "TOWN", "File owner/licensee"},
	{ID3_TPE1, "TPE1", "Lead performer(s)/Soloist(s)"},
	{ID3_TPE2, "TPE2", "Band/orchestra/accompaniment"},
	{ID3_TPE3, "TPE3", "Conductor/performer refinement"},
	{ID3_TPE4, "TPE4", "Interpreted, remixed, or otherwise modified by"},
	{ID3_TPOS, "TPOS", "Part of a set"},
	{ID3_TPRO, "TPRO", "Produced notice"},			/* v4 only */
	{ID3_TPUB, "TPUB", "Publisher"},
	{ID3_TRCK, "TRCK", "Track number/Position in set"},
	{ID3_TRDA, "TRDA", "Recording dates"},			/* v3 only */
	{ID3_TRSN, "TRSN", "Internet radio station name"},
	{ID3_TRSO, "TRSO", "Internet radio station owner"},
	{ID3_TSIZ, "TSIZ", "Size"},				/* v3 only */
	{ID3_TSOA, "TSOA", "Album sort order"},			/* v4 only */
	{ID3_TSOP, "TSOP", "Performer sort order"},		/* v4 only */
	{ID3_TSOT, "TSOT", "Title sort order"},			/* v4 only */

	{ID3_TSRC, "TSRC", "ISRC (international standard recording code)"},
	{ID3_TSSE, "TSSE", "Software/Hardware and settings used for encoding"},
	{ID3_TSST, "TSST", "Set subtitle"},			/* v4 only */
	{ID3_TYER, "TYER", "Year"},				/* v3 only */
	{ID3_TXXX, "TXXX", "User defined text information frame"},

	{ID3_UFID, "UFID", "Unique file identifier"},
	{ID3_USER, "USER", "Terms of use"},
	{ID3_USLT, "USLT", "Unsychronized lyric/text transcription"},

	{ID3_WCOM, "WCOM", "Commercial information"},
	{ID3_WCOP, "WCOP", "Copyright/Legal information"},
	{ID3_WOAF, "WOAF", "Official audio file webpage"},
	{ID3_WOAR, "WOAR", "Official artist/performer webpage"},
	{ID3_WOAS, "WOAS", "Official audio source webpage"},
	{ID3_WORS, "WORS", "Official internet radio station homepage"},
	{ID3_WPAY, "WPAY", "Payment"},
	{ID3_WPUB, "WPUB", "Publishers official webpage"},
	{ID3_WXXX, "WXXX", "User defined URL link frame"},
};

struct id3_framedesc22 {
	uint32_t fd_v22, fd_v24;
};

static struct id3_framedesc22 framedesc22[] = {
	{ID3_BUF, ID3_RBUF}, /* Recommended buffer size */

	{ID3_CNT, ID3_PCNT}, /* Play counter */
	{ID3_COM, ID3_COMM}, /* Comments */
	{ID3_CRA, ID3_AENC}, /* Audio encryption */
	{ID3_CRM, 0},        /* Encrypted meta frame */

	{ID3_ETC, ID3_ETCO}, /* Event timing codes */
	/* Could be converted to EQU2 */
	{ID3_EQU, 0},        /* Equalization */

	{ID3_GEO, ID3_GEOB}, /* General encapsulated object */

	/* Would need conversion to TIPL */
	{ID3_IPL, 0},        /* Involved people list */

	/* This is so fragile it's not worth trying to save */
	{ID3_LNK, 0},        /* Linked information */

	{ID3_MCI, ID3_MCDI}, /* Music CD Identifier */
	{ID3_MLL, ID3_MLLT}, /* MPEG location lookup table */

	/* Would need to convert header for APIC */
	{ID3_PIC, 0},        /* Attached picture */
	{ID3_POP, ID3_POPM}, /* Popularimeter */

	{ID3_REV, ID3_RVRB}, /* Reverb */
	/* Could be converted to RVA2 */
	{ID3_RVA, 0},        /* Relative volume adjustment */

	{ID3_SLT, ID3_SYLT}, /* Synchronized lyric/text */
	{ID3_STC, ID3_SYTC}, /* Synced tempo codes */
 
	{ID3_TAL, ID3_TALB}, /* Album/Movie/Show title */
	{ID3_TBP, ID3_TBPM}, /* BPM (Beats Per Minute) */
	{ID3_TCM, ID3_TCOM}, /* Composer */
	{ID3_TCO, ID3_TCON}, /* Content type */
	{ID3_TCR, ID3_TCOP}, /* Copyright message */
	/* This could be incorporated into TDRC */
	{ID3_TDA, 0},        /* Date */
	{ID3_TDY, ID3_TDLY}, /* Playlist delay */
	{ID3_TEN, ID3_TENC}, /* Encoded by */
	{ID3_TFT, ID3_TFLT}, /* File type */
	/* This could be incorporated into TDRC */
	{ID3_TIM, 0},        /* Time */
	{ID3_TKE, ID3_TKEY}, /* Initial key */
	{ID3_TLA, ID3_TLAN}, /* Language(s) */
	{ID3_TLE, ID3_TLEN}, /* Length */
	{ID3_TMT, ID3_TMED}, /* Media type */
	{ID3_TOA, ID3_TOPE}, /* Original artist(s)/performer(s) */
	{ID3_TOF, ID3_TOFN}, /* Original filename */
	{ID3_TOL, ID3_TOLY}, /* Original Lyricist(s)/text writer(s) */
	/*
	 * The docs says that original release year should be in
	 * milliseconds!  Hopefully that is a typo.
	 */
	{ID3_TOR, ID3_TDOR}, /* Original release year */
	{ID3_TOT, ID3_TOAL}, /* Original album/Movie/Show title */
	{ID3_TP1, ID3_TPE1}, /* Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group */
	{ID3_TP2, ID3_TPE2}, /* Band/Orchestra/Accompaniment */
	{ID3_TP3, ID3_TPE3}, /* Conductor/Performer refinement */
	{ID3_TP4, ID3_TPE4}, /* Interpreted, remixed, or otherwise modified by */
	{ID3_TPA, ID3_TPOS}, /* Part of a set */
	{ID3_TPB, ID3_TPUB}, /* Publisher */
	{ID3_TRC, ID3_TSRC}, /* ISRC (International Standard Recording Code) */
	{ID3_TRD, 0},        /* Recording dates */
	{ID3_TRK, ID3_TRCK}, /* Track number/Position in set */
	{ID3_TSI, 0},        /* Size */
	{ID3_TSS, ID3_TSSE}, /* Software/hardware and settings used for encoding */
	{ID3_TT1, ID3_TIT1}, /* Content group description */
	{ID3_TT2, ID3_TIT2}, /* Title/Songname/Content description */
	{ID3_TT3, ID3_TIT3}, /* Subtitle/Description refinement */
	{ID3_TXT, ID3_TEXT}, /* Lyricist/text writer */
	{ID3_TXX, ID3_TXXX}, /* User defined text information frame */
	{ID3_TYE, ID3_TDRC}, /* Year */

	{ID3_UFI, ID3_UFID}, /* Unique file identifier */
	{ID3_ULT, ID3_USLT}, /* Unsychronized lyric/text transcription */

	{ID3_WAF, ID3_WOAF}, /* Official audio file webpage */
	{ID3_WAR, ID3_WOAR}, /* Official artist/performer webpage */
	{ID3_WAS, ID3_WOAS}, /* Official audio source webpage */
	{ID3_WCM, ID3_WCOM}, /* Commercial information */
	{ID3_WCP, ID3_WCOP}, /* Copyright/Legal information */
	{ID3_WPB, ID3_WPUB}, /* Publishers official webpage */
	{ID3_WXX, ID3_WXXX}, /* User defined URL link frame */
};

static struct id3_framedesc *find_frame_description(uint32_t id)
{
	int i;
	for (i = 0; i < (int) (sizeof(framedesc) / sizeof(struct id3_framedesc)); i++)
		if (framedesc[i].fd_id == id)
			return &framedesc[i];
	return NULL;
}


/*
 * Function id3_read_frame (id3)
 *
 *    Read next frame from the indicated ID3 tag.  Return 0 upon
 *    success, or -1 if an error occured.
 *
 */
int id3_read_frame(struct id3_tag *id3)
{
	struct id3_frame *frame;
	uint32_t id;
	uint8_t *buf;

	if (id3->id3_version == 2)
		return id3_read_frame_v22(id3);

	/*
	 * Read frame header.
	 */
	buf = id3->id3_read(id3, NULL, ID3_FRAMEHDR_SIZE);
	if (buf == NULL)
		return -1;

	/*
	 * If we encounter an invalid frame id, we assume that there is
	 * some padding in the header.  We just skip the rest of the ID3
	 * tag.
	 */
	if (!((buf[0] >= '0' && buf[0] <= '9') || (buf[0] >= 'A' && buf[0] <= 'Z')))
	{
		id3->id3_seek(id3, id3->id3_tagsize - id3->id3_pos);
		return 0;
	}
	id = ID3_FRAME_ID(buf[0], buf[1], buf[2], buf[3]);

	/*
	 * Allocate frame.
	 */
	frame = calloc(sizeof(*frame), 1);

	frame->fr_owner = id3;

	/* revision 2.4 uses syncsafe frame size, so we do that, but I don't think 2.4 is fully supported */
	if (id3->id3_version == 4)
		frame->fr_raw_size = ID3_GET_SIZE28(buf[4], buf[5], buf[6], buf[7]);
	else
		frame->fr_raw_size = buf[4] << 24 | buf[5] << 16 | buf[6] << 8 | buf[7];

	if (frame->fr_raw_size > 1000000)
	{
		free(frame);
		return -1;
	}
	frame->fr_flags = buf[8] << 8 | buf[9];

	/*
	 * Determine the type of the frame.
	 */

	frame->fr_desc = find_frame_description(id);

	/*
	 * Check if frame had a valid id.
	 */
	if (frame->fr_desc == NULL)
	{
		/*
		 * No. Ignore the frame.
		 */
		if (id3->id3_seek(id3, frame->fr_raw_size) < 0)
		{
			free(frame);
			return -1;
		}
		return 0;
	}

	/*
	 * Initialize frame.
	 */

	/*
	 * We allocate 2 extra bytes.  This simplifies retrieval of
	 * text strings.
	 */
	frame->fr_raw_data = calloc(frame->fr_raw_size + 2, 1);
	if (id3->id3_read(id3, frame->fr_raw_data, frame->fr_raw_size) == NULL)
	{
		free(frame->fr_raw_data);
		free(frame);
		return -1;
	}

	/*
	 * Insert frame into linked list.
	 */
    list_add_tail(&frame->siblings, &id3->id3_frame);

	/*
	 * Check if frame is compressed using zlib.
	 */
	if (frame->fr_flags & ID3_FHFLAG_COMPRESS)
		return 0;

	frame->fr_data = id3_frame_get_dataptr(frame);
	frame->fr_size = id3_frame_get_size(frame);

	return 0;
}


/*
 * Function id3_get_frame (id3, type, num)
 *
 *    Search in the list of frames for the ID3-tag, and return a frame
 *    of the indicated type.  If tag contains several frames of the
 *    indicated type, the third argument tells which of the frames to
 *    return.
 *
 */
struct id3_frame *id3_get_frame(struct id3_tag *id3, uint32_t type, int num)
{
    struct list_head *node;
    struct id3_frame *fr;

    list_for_each(node, &id3->id3_frame)
    {
        fr = list_entry(node, struct id3_frame, siblings);
        if (fr->fr_desc && fr->fr_desc->fd_id == type)
        {
            if (--num <= 0)
                return fr;
        }
    }

    return NULL;
}

/*
 * Function decompress_frame(frame)
 *
 *    Uncompress the indicated frame.  Return 0 upon success, or -1 if
 *    an error occured.
 *
 */
static int decompress_frame(struct id3_frame *frame)
{
#ifdef HAVE_LIBZ
	z_stream z;
	int r;

	/*
	 * Fetch the size of the decompressed data.
	 */
	frame->fr_size_z = g_ntohl(*((uint32_t *) frame->fr_raw_data));
	if (frame->fr_size_z < 0 || frame->fr_size_z > 1000000)
		return -1;

	/*
	 * Allocate memory to hold uncompressed frame.
	 */
	frame->fr_data_z = g_malloc(frame->fr_size_z +
				    (id3_frame_is_text(frame) ? 2 : 0));

	/*
	 * Initialize zlib.
	 */
	z.next_in = id3_frame_get_dataptr(frame);
	z.avail_in = id3_frame_get_size(frame);
	z.zalloc = NULL;
	z.zfree = NULL;
	z.opaque = NULL;

	r = inflateInit(&z);
	switch (r)
	{
		case Z_OK:
			break;
		case Z_MEM_ERROR:
			id3_error(frame->fr_owner, "zlib - no memory");
			goto Error_init;
		case Z_VERSION_ERROR:
			id3_error(frame->fr_owner, "zlib - invalid version");
			goto Error_init;
		default:
			id3_error(frame->fr_owner, "zlib - unknown error");
			goto Error_init;
	}

	/*
	 * Decompress frame.
	 */
	z.next_out = frame->fr_data_z;
	z.avail_out = frame->fr_size_z;
	r = inflate(&z, Z_SYNC_FLUSH);
	switch (r)
	{
		case Z_STREAM_END:
			break;
		case Z_OK:
			if (z.avail_in == 0)
				/*
				 * This should not be possible with a
				 * correct stream.  We will be nice
				 * however, and try to go on.
				 */
				break;
			id3_error(frame->fr_owner, "zlib - buffer exhausted");
			goto Error_inflate;
		default:
			id3_error(frame->fr_owner, "zlib - unknown error");
			goto Error_inflate;
	}

	r = inflateEnd(&z);
	if (r != Z_OK)
		id3_error(frame->fr_owner, "zlib - inflateEnd error");

	/*
	 * Null-terminate text frames.
	 */
	if (id3_frame_is_text(frame))
	{
		((char *) frame->fr_data_z)[frame->fr_size_z] = 0;
		((char *) frame->fr_data_z)[frame->fr_size_z + 1] = 0;
	}
	frame->fr_data = frame->fr_data_z;
	frame->fr_size = frame->fr_size_z + (id3_frame_is_text(frame) ? 2 : 0);

	return 0;

	/*
	 * Cleanup code.
	 */
 Error_inflate:
	r = inflateEnd(&z);
 Error_init:
	free(frame->fr_data_z);
	frame->fr_data_z = NULL;
#endif
	return -1;
}

/*
 * Function id3_decompress_frame(frame)
 *
 *    Check if frame is compressed, and uncompress if necessary.
 *    Return 0 upon success, or -1 if an error occured.
 *
 */
int id3_decompress_frame(struct id3_frame *frame)
{
	if (!(frame->fr_flags & ID3_FHFLAG_COMPRESS))
		/* Frame not compressed */
		return 0;
	if (frame->fr_data_z)
		/* Frame already decompressed */
		return 0;
	/* Do decompression */
	return decompress_frame(frame);
}


/*
 * Function id3_delete_frame (frame)
 *
 *    Remove frame from ID3 tag and release memory occupied by it.
 *
 */
int id3_delete_frame(struct id3_frame *frame)
{
    struct list_head *node;
    struct id3_frame *fr;
    int ret = -1;

	/*
	 * Search for frame in list.
	 */
    list_for_each(node, &frame->fr_owner->id3_frame)
    {
        fr = list_entry(node, struct id3_frame, siblings);
        if (fr == frame)
        {
		    /*
		     * Remove frame from frame list.
		     */
            list_del(node);
            
            frame->fr_owner->id3_altered = 1;
		    ret = 0;

            break;
        }
    }

	/*
	 * Release memory occupied by frame.
	 */
	if (frame->fr_raw_data)
		free(frame->fr_raw_data);
	if (frame->fr_data_z)
		free(frame->fr_data_z);
	free(frame);

	return ret;
}


/*
 * Function id3_add_frame (id3, type)
 *
 *    Add a new frame to the ID3 tag.  Return a pointer to the new
 *    frame, or NULL if an error occured.
 *
 */
struct id3_frame *id3_add_frame(struct id3_tag *id3, uint32_t type)
{
	struct id3_frame *frame;
	int i;

	/*
	 * Allocate frame.
	 */
	frame = calloc(sizeof(*frame), 1);

	/*
	 * Initialize frame
	 */
	frame->fr_owner = id3;

	/*
	 * Try finding the correct frame descriptor.
	 */
	for (i = 0; i < (int) (sizeof(framedesc) / sizeof(struct id3_framedesc)); i++)
	{
		if (framedesc[i].fd_id == type)
		{
			frame->fr_desc = &framedesc[i];
			break;
		}
	}

	/*
	 * Insert frame into linked list.
	 */
    list_add_tail(&frame->siblings, &id3->id3_frame);
	id3->id3_altered = 1;

	return frame;
}


/*
 * Destroy all frames  in an id3 tag, and free all data
 */
void id3_destroy_frames(struct id3_tag *id)
{
    struct list_head *node;
    struct id3_frame *frame;

    while (!list_empty(&id->id3_frame))
    {
        node = id->id3_frame.next;
        frame = list_entry(node, struct id3_frame, siblings);
        list_del(node);

		/*
		 * Release memory occupied by frame.
		 */
		if (frame->fr_raw_data)
			free(frame->fr_raw_data);
		if (frame->fr_data_z)
			free(frame->fr_data_z);
		free(frame);
    }
}

static int id3_frame_extra_headers(struct id3_frame *frame)
{
	int retv = 0;
	/*
	 * If frame is encrypted, we have four extra bytes in the
	 * header.
	 */
	if (frame->fr_flags & ID3_FHFLAG_COMPRESS)
		retv += 4;
	/*
	 * If frame is encrypted, we have one extra byte in the
	 * header.
	 */
	if (frame->fr_flags & ID3_FHFLAG_ENCRYPT)
		retv += 1;

	/*
	 * If frame has grouping identity, we have one extra byte in
	 * the header.
	 */
	if (frame->fr_flags & ID3_FHFLAG_GROUP)
		retv += 1;

	return retv;
}

static void* id3_frame_get_dataptr(struct id3_frame *frame)
{
	char *ptr = frame->fr_raw_data;

	ptr += id3_frame_extra_headers(frame);

	return ptr;
}

static int id3_frame_get_size(struct id3_frame *frame)
{
	return frame->fr_raw_size - id3_frame_extra_headers(frame);
}

void id3_frame_clear_data(struct id3_frame *frame)
{
	if (frame->fr_raw_data)
		free(frame->fr_raw_data);
	if (frame->fr_data_z)
		free(frame->fr_data_z);
	frame->fr_raw_data = NULL;
	frame->fr_raw_size = 0;
	frame->fr_data = NULL;
	frame->fr_size = 0;
	frame->fr_data_z = NULL;
	frame->fr_size_z = 0;
}

static uint32_t find_v24_id(uint32_t v22)
{
	int i;
	for (i = 0; i < (int) (sizeof(framedesc22) / sizeof(framedesc22[0])); i++)
		if (framedesc22[i].fd_v22 == v22)
			return framedesc22[i].fd_v24;

	return 0;
}

static int id3_read_frame_v22(struct id3_tag *id3)
{
	struct id3_frame *frame;
	uint32_t id, idv24;
	uint8_t *buf;
	int size;

	/*
	 * Read frame header.
	 */
	buf = id3->id3_read(id3, NULL, ID3_FRAMEHDR_SIZE_22);
	if (buf == NULL)
		return -1;

	/*
	 * If we encounter an invalid frame id, we assume that there
	 * is some.  We just skip the rest of the ID3 tag.
	 */
	if (!((buf[0] >= '0' && buf[0] <= '9') || (buf[0] >= 'A' && buf[0] <= 'Z')))
	{
		id3->id3_seek(id3, id3->id3_tagsize - id3->id3_pos);
		return 0;
	}

	id = ID3_FRAME_ID_22(buf[0], buf[1], buf[2]);
	size = buf[3] << 16 | buf[4] << 8 | buf[5];

	if ((idv24 = find_v24_id(id)) == 0)
	{
		if (id3->id3_seek(id3, size) < 0)
			return -1;
		return 0;
	}

	/*
	 * Allocate frame.
	 */
	frame = calloc(sizeof(*frame), 1);

	frame->fr_owner = id3;
	frame->fr_raw_size = size;
	if (frame->fr_raw_size > 1000000)
	{
		free(frame);
		return -1;
	}

	/*
	 * Initialize frame.
	 */
	frame->fr_desc = find_frame_description(idv24);

	/*
	 * We allocate 2 extra bytes.  This simplifies retrieval of
	 * text strings.
	 */
	frame->fr_raw_data = calloc(frame->fr_raw_size + 2, 1);
	if (id3->id3_read(id3, frame->fr_raw_data, frame->fr_raw_size) == NULL)
	{
		free(frame->fr_raw_data);
		free(frame);
		return -1;
	}

	/*
	 * Insert frame into linked list.
	 */
    list_add_tail(&frame->siblings, &id3->id3_frame);

	frame->fr_data = frame->fr_raw_data;
	frame->fr_size = frame->fr_raw_size;

	return 0;
}
