diff Plugins/Input/mpg123/id3_frame.c @ 61:fa848bd484d8 trunk

[svn] Move plugins to Plugins/
author nenolod
date Fri, 28 Oct 2005 22:58:11 -0700
parents
children d539e5c5f730
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Input/mpg123/id3_frame.c	Fri Oct 28 22:58:11 2005 -0700
@@ -0,0 +1,765 @@
+/*********************************************************************
+ * 
+ *    Copyright (C) 1999-2000, 2001,  Espen Skoglund
+ *    Department of Computer Science, University of Tromsų
+ * 
+ * Filename:      id3_frame.c
+ * Description:   Code for handling ID3 frames.
+ * Author:        Espen Skoglund <espensk@stud.cs.uit.no>
+ * Created at:    Fri Feb  5 23:47:08 1999
+ * 
+ * $Id: id3_frame.c,v 1.5 2004/07/20 21:47:22 descender 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.
+ *                
+ ********************************************************************/
+#include "config.h"
+
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#endif
+#include <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "xmms-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 {
+    guint32 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(guint32 id)
+{
+    int i;
+    for (i = 0; i < 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;
+    guint32 id;
+    char *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 = g_malloc0(sizeof(*frame));
+
+    frame->fr_owner = id3;
+    /* FIXME v2.4.0 */
+    frame->fr_raw_size = buf[4] << 24 | buf[5] << 16 | buf[6] << 8 | buf[7];
+    if (frame->fr_raw_size < 0 || frame->fr_raw_size > 1000000) {
+        g_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) {
+            g_free(frame);
+            return -1;
+        }
+        return 0;
+    }
+
+    /*
+     * Initialize frame.
+     */
+
+    /*
+     * We allocate 2 extra bytes.  This simplifies retrieval of
+     * text strings.
+     */
+    frame->fr_raw_data = g_malloc0(frame->fr_raw_size + 2);
+    if (id3->id3_read(id3, frame->fr_raw_data, frame->fr_raw_size) == NULL) {
+        g_free(frame->fr_raw_data);
+        g_free(frame);
+        return -1;
+    }
+
+    /*
+     * Insert frame into linked list.
+     */
+    id3->id3_frame = g_list_append(id3->id3_frame, 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, guint32 type, int num)
+{
+    GList *node;
+
+    for (node = id3->id3_frame; node != NULL; node = node->next) {
+        struct id3_frame *fr = node->data;
+        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(*((guint32 *) 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:
+    g_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 ocupied by it.
+ *
+ */
+int
+id3_delete_frame(struct id3_frame *frame)
+{
+    GList *list = frame->fr_owner->id3_frame;
+    int ret;
+
+    /*
+     * Search for frame in list.
+     */
+
+    if (g_list_find(list, frame) != NULL) {
+        /*
+         * Frame does not exist in frame list.
+         */
+        ret = -1;
+
+    }
+    else {
+        /*
+         * Remove frame from frame list.
+         */
+        list = g_list_remove(list, frame);
+        frame->fr_owner->id3_altered = 1;
+        ret = 0;
+    }
+
+    /*
+     * Release memory occupied by frame.
+     */
+    if (frame->fr_raw_data)
+        g_free(frame->fr_raw_data);
+    if (frame->fr_data_z)
+        g_free(frame->fr_data_z);
+    g_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, guint32 type)
+{
+    struct id3_frame *frame;
+    int i;
+
+    /*
+     * Allocate frame.
+     */
+    frame = g_malloc0(sizeof(*frame));
+
+    /*
+     * Initialize frame
+     */
+    frame->fr_owner = id3;
+
+    /*
+     * Try finding the correct frame descriptor.
+     */
+    for (i = 0; i < sizeof(framedesc) / sizeof(struct id3_framedesc); i++) {
+        if (framedesc[i].fd_id == type) {
+            frame->fr_desc = &framedesc[i];
+            break;
+        }
+    }
+
+    /*
+     * Insert frame into linked list.
+     */
+    id3->id3_frame = g_list_append(id3->id3_frame, 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)
+{
+    GList *node;
+
+    for (node = id->id3_frame; node != NULL; node = node->next) {
+        struct id3_frame *frame = node->data;
+        /*
+         * Release memory occupied by frame.
+         */
+        if (frame->fr_raw_data)
+            g_free(frame->fr_raw_data);
+        if (frame->fr_data_z)
+            g_free(frame->fr_data_z);
+        g_free(frame);
+    }
+    g_list_free(id->id3_frame);
+    id->id3_frame = NULL;
+}
+
+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)
+        g_free(frame->fr_raw_data);
+    if (frame->fr_data_z)
+        g_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 guint32
+find_v24_id(guint32 v22)
+{
+    int i;
+    for (i = 0; i < 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;
+    guint32 id, idv24;
+    char *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 = g_malloc0(sizeof(*frame));
+
+    frame->fr_owner = id3;
+    frame->fr_raw_size = size;
+    if (frame->fr_raw_size < 0 || frame->fr_raw_size > 1000000) {
+        g_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 = g_malloc0(frame->fr_raw_size + 2);
+    if (id3->id3_read(id3, frame->fr_raw_data, frame->fr_raw_size) == NULL) {
+        g_free(frame->fr_raw_data);
+        g_free(frame);
+        return -1;
+    }
+
+    /*
+     * Insert frame into linked list.
+     */
+    id3->id3_frame = g_list_append(id3->id3_frame, frame);
+
+    frame->fr_data = frame->fr_raw_data;
+    frame->fr_size = frame->fr_raw_size;
+
+    return 0;
+}