function GenerateID3v2Tag($data, $majorversion = 4, $minorversion = 0, $paddedlength = 0, $extendedheader = '', $footer = FALSE, $showerrors = TRUE, $noerrorsonly = TRUE)
{
    ID3v2FrameIsAllowed(NULL, '', '');
    // clear static array in case this isn't the first call to GenerateID3v2Tag()
    if (is_array($data)) {
        if (is_array($extendedheader)) {
            // not supported yet
        }
        foreach ($data as $frame_name => $frame_rawinputdata) {
            if (!is_array($frame_rawinputdata[0])) {
                // force everything to be arrayed so only one processing loop
                $frame_rawinputdata = array($frame_rawinputdata);
            }
            foreach ($frame_rawinputdata as $irrelevantindex => $frame_inputdata) {
                if (IsValidID3v2FrameName($frame_name, $majorversion)) {
                    unset($frame_length);
                    unset($frame_flags);
                    $frame_data = FALSE;
                    if (ID3v2FrameIsAllowed($frame_name, $frame_inputdata, $majorversion, $showerrors)) {
                        if ($frame_data = GenerateID3v2FrameData($frame_name, $frame_inputdata, $majorversion, $showerrors)) {
                            if ($majorversion >= 4) {
                                // frame-level unsynchronization
                                $FrameUnsynchronisation = FALSE;
                                $unsynchdata = Unsynchronise($frame_data);
                                if (strlen($unsynchdata) != strlen($frame_data)) {
                                    // unsynchronization needed
                                    $FrameUnsynchronisation = TRUE;
                                    $frame_data = $unsynchdata;
                                    if (isset($TagUnsynchronisation) && $TagUnsynchronisation === FALSE) {
                                        // only set to true if ALL frames are unsynchronised
                                    } else {
                                        $TagUnsynchronisation = TRUE;
                                    }
                                } else {
                                    if (isset($TagUnsynchronisation)) {
                                        $TagUnsynchronisation = FALSE;
                                    }
                                }
                                unset($unsynchdata);
                                $frame_length = BigEndian2String(strlen($frame_data), 4, TRUE);
                            } else {
                                $frame_length = BigEndian2String(strlen($frame_data), 4, FALSE);
                            }
                            $frame_flags = GenerateID3v2FrameFlags($majorversion, ID3v2FrameFlagsLookupTagAlter($frame_name, $majorversion), ID3v2FrameFlagsLookupFileAlter($frame_name, $majorversion), FALSE, FALSE, FALSE, FALSE, $FrameUnsynchronisation, FALSE);
                        }
                    } else {
                        if ($showerrors) {
                            echo 'Frame "' . $frame_name . '" is NOT allowed<BR>';
                        }
                    }
                    if ($frame_data === FALSE) {
                        if ($showerrors) {
                            echo 'GenerateID3v2FrameData() failed for "' . $frame_name . '"<BR>';
                            echo 'Error generated in getID3() v' . GETID3VERSION . '<BR>';
                        }
                        if ($noerrorsonly) {
                            return FALSE;
                        } else {
                            unset($frame_name);
                        }
                    }
                } else {
                    // ignore any invalid frame names, including 'title', 'header', etc
                    unset($frame_name);
                    unset($frame_length);
                    unset($frame_flags);
                    unset($frame_data);
                }
                $tagstring .= $frame_name . $frame_length . $frame_flags . $frame_data;
            }
        }
        if ($footer) {
            if ($showerrors) {
                echo 'Footer not supported (yet)<BR>';
            }
            return FALSE;
        }
        //echo number_format(strlen($tagstring));
        if ($majorversion <= 3) {
            // tag-level unsynchronization
            $unsynchdata = Unsynchronise($tagstring);
            if (strlen($unsynchdata) != strlen($tagstring)) {
                // unsynchronization needed
                $TagUnsynchronisation = TRUE;
                $tagstring = $unsynchdata;
            }
        }
        //echo ' - '.number_format(strlen($tagstring)).'<BR>';
        if (!$footer && $paddedlength > strlen($tagstring) + ID3v2HeaderLength($id3info['majorversion'])) {
            // pad up to $paddedlength bytes if unpadded tag is shorter than $paddedlength
            // "Furthermore it MUST NOT have any padding when a tag footer is added to the tag."
            $tagstring .= @str_repeat(chr(0), $paddedlength - strlen($tagstring));
        }
        if (substr($tagstring, strlen($tagstring) - 1, 1) == chr(255)) {
            // special unsynchronization case:
            // if last byte == $FF then appended a $00
            $TagUnsynchronisation = TRUE;
            $tagstring .= chr(0);
        }
        $tagheader = 'ID3';
        $tagheader .= chr($majorversion);
        $tagheader .= chr($minorversion);
        $tagheader .= GenerateID3v2TagFlags($majorversion, $TagUnsynchronisation, FALSE, (bool) $extendedheader, FALSE, $footer);
        $tagheader .= BigEndian2String(strlen($tagstring), 4, TRUE);
        return $tagheader . $tagstring;
    } else {
        return FALSE;
    }
}
Example #2
0
function getID3v2Filepointer(&$fd, &$ThisFileInfo)
{
    //    Overall tag structure:
    //        +-----------------------------+
    //        |      Header (10 bytes)      |
    //        +-----------------------------+
    //        |       Extended Header       |
    //        | (variable length, OPTIONAL) |
    //        +-----------------------------+
    //        |   Frames (variable length)  |
    //        +-----------------------------+
    //        |           Padding           |
    //        | (variable length, OPTIONAL) |
    //        +-----------------------------+
    //        | Footer (10 bytes, OPTIONAL) |
    //        +-----------------------------+
    //    Header
    //        ID3v2/file identifier      "ID3"
    //        ID3v2 version              $04 00
    //        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
    //        ID3v2 size             4 * %0xxxxxxx
    rewind($fd);
    $header = fread($fd, 10);
    if (substr($header, 0, 3) == 'ID3') {
        $ThisFileInfo['id3v2']['header'] = true;
        $ThisFileInfo['id3v2']['majorversion'] = ord($header[3]);
        $ThisFileInfo['id3v2']['minorversion'] = ord($header[4]);
    } else {
        return false;
    }
    if ($ThisFileInfo['id3v2']['majorversion'] > 4) {
        // this script probably won't correctly parse ID3v2.5.x and above.
        $ThisFileInfo['error'] .= "\n" . 'this script only parses up to ID3v2.4.x - this tag is ID3v2.' . $ThisFileInfo['id3v2']['majorversion'] . '.' . $ThisFileInfo['id3v2']['minorversion'];
        return false;
    }
    $id3_flags = BigEndian2Bin($header[5]);
    switch ($ThisFileInfo['id3v2']['majorversion']) {
        case 2:
            // %ab000000 in v2.2
            $ThisFileInfo['id3v2']['flags']['unsynch'] = $id3_flags[0];
            // a - Unsynchronisation
            $ThisFileInfo['id3v2']['flags']['compression'] = $id3_flags[1];
            // b - Compression
            break;
        case 3:
            // %abc00000 in v2.3
            $ThisFileInfo['id3v2']['flags']['unsynch'] = $id3_flags[0];
            // a - Unsynchronisation
            $ThisFileInfo['id3v2']['flags']['exthead'] = $id3_flags[1];
            // b - Extended header
            $ThisFileInfo['id3v2']['flags']['experim'] = $id3_flags[2];
            // c - Experimental indicator
            break;
        case 4:
            // %abcd0000 in v2.4
            $ThisFileInfo['id3v2']['flags']['unsynch'] = $id3_flags[0];
            // a - Unsynchronisation
            $ThisFileInfo['id3v2']['flags']['exthead'] = $id3_flags[1];
            // b - Extended header
            $ThisFileInfo['id3v2']['flags']['experim'] = $id3_flags[2];
            // c - Experimental indicator
            $ThisFileInfo['id3v2']['flags']['isfooter'] = $id3_flags[3];
            // d - Footer present
            break;
    }
    $ThisFileInfo['id3v2']['headerlength'] = BigEndian2Int(substr($header, 6, 4), 1) + ID3v2HeaderLength($ThisFileInfo['id3v2']['majorversion']);
    //    Extended Header
    if (isset($ThisFileInfo['id3v2']['flags']['exthead']) && $ThisFileInfo['id3v2']['flags']['exthead']) {
        //            Extended header size   4 * %0xxxxxxx
        //            Number of flag bytes       $01
        //            Extended Flags             $xx
        //            Where the 'Extended header size' is the size of the whole extended header, stored as a 32 bit synchsafe integer.
        $extheader = fread($fd, 4);
        $ThisFileInfo['id3v2']['extheaderlength'] = BigEndian2Int($extheader, 1);
        //            The extended flags field, with its size described by 'number of flag  bytes', is defined as:
        //                %0bcd0000
        //            b - Tag is an update
        //                Flag data length       $00
        //            c - CRC data present
        //                Flag data length       $05
        //                Total frame CRC    5 * %0xxxxxxx
        //            d - Tag restrictions
        //                Flag data length       $01
        $extheaderflagbytes = fread($fd, 1);
        $extheaderflags = fread($fd, $extheaderflagbytes);
        $id3_exthead_flags = BigEndian2Bin(substr($header, 5, 1));
        $ThisFileInfo['id3v2']['exthead_flags']['update'] = substr($id3_exthead_flags, 1, 1);
        $ThisFileInfo['id3v2']['exthead_flags']['CRC'] = substr($id3_exthead_flags, 2, 1);
        if ($ThisFileInfo['id3v2']['exthead_flags']['CRC']) {
            $extheaderrawCRC = fread($fd, 5);
            $ThisFileInfo['id3v2']['exthead_flags']['CRC'] = BigEndian2Int($extheaderrawCRC, 1);
        }
        $ThisFileInfo['id3v2']['exthead_flags']['restrictions'] = substr($id3_exthead_flags, 3, 1);
        if ($ThisFileInfo['id3v2']['exthead_flags']['restrictions']) {
            // Restrictions           %ppqrrstt
            $extheaderrawrestrictions = fread($fd, 1);
            $ThisFileInfo['id3v2']['exthead_flags']['restrictions_tagsize'] = (bindec('11000000') & ord($extheaderrawrestrictions)) >> 6;
            // p - Tag size restrictions
            $ThisFileInfo['id3v2']['exthead_flags']['restrictions_textenc'] = (bindec('00100000') & ord($extheaderrawrestrictions)) >> 5;
            // q - Text encoding restrictions
            $ThisFileInfo['id3v2']['exthead_flags']['restrictions_textsize'] = (bindec('00011000') & ord($extheaderrawrestrictions)) >> 3;
            // r - Text fields size restrictions
            $ThisFileInfo['id3v2']['exthead_flags']['restrictions_imgenc'] = (bindec('00000100') & ord($extheaderrawrestrictions)) >> 2;
            // s - Image encoding restrictions
            $ThisFileInfo['id3v2']['exthead_flags']['restrictions_imgsize'] = (bindec('00000011') & ord($extheaderrawrestrictions)) >> 0;
            // t - Image size restrictions
        }
    }
    // end extended header
    //    Frames
    //        All ID3v2 frames consists of one frame header followed by one or more
    //        fields containing the actual information. The header is always 10
    //        bytes and laid out as follows:
    //
    //        Frame ID      $xx xx xx xx  (four characters)
    //        Size      4 * %0xxxxxxx
    //        Flags         $xx xx
    $sizeofframes = $ThisFileInfo['id3v2']['headerlength'] - ID3v2HeaderLength($ThisFileInfo['id3v2']['majorversion']);
    if (isset($ThisFileInfo['id3v2']['extheaderlength'])) {
        $sizeofframes -= $ThisFileInfo['id3v2']['extheaderlength'];
    }
    if (isset($ThisFileInfo['id3v2']['flags']['isfooter']) && $ThisFileInfo['id3v2']['flags']['isfooter']) {
        $sizeofframes -= 10;
        // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
    }
    if ($sizeofframes > 0) {
        $framedata = fread($fd, $sizeofframes);
        // read all frames from file into $framedata variable
        //    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
        if (isset($ThisFileInfo['id3v2']['flags']['unsynch']) && $ThisFileInfo['id3v2']['flags']['unsynch'] && $ThisFileInfo['id3v2']['majorversion'] <= 3) {
            $framedata = DeUnSynchronise($framedata);
        }
        //        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
        //        of on tag level, making it easier to skip frames, increasing the streamability
        //        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
        //        there exists an unsynchronised frame, while the new unsynchronisation flag in
        //        the frame header [S:4.1.2] indicates unsynchronisation.
        $framedataoffset = 10;
        // how many bytes into the stream - start from after the 10-byte header
        while (isset($framedata) && strlen($framedata) > 0) {
            // cycle through until no more frame data is left to parse
            if (strlen($framedata) < ID3v2HeaderLength($ThisFileInfo['id3v2']['majorversion'])) {
                // insufficient room left in ID3v2 header for actual data - must be padding
                $ThisFileInfo['id3v2']['padding']['start'] = $framedataoffset;
                $ThisFileInfo['id3v2']['padding']['length'] = strlen($framedata);
                $ThisFileInfo['id3v2']['padding']['valid'] = true;
                for ($i = 0; $i < $ThisFileInfo['id3v2']['padding']['length']; $i++) {
                    if (substr($framedata, $i, 1) != chr(0)) {
                        $ThisFileInfo['id3v2']['padding']['valid'] = false;
                        $ThisFileInfo['id3v2']['padding']['errorpos'] = $ThisFileInfo['id3v2']['padding']['start'] + $i;
                        $ThisFileInfo['warning'] .= "\n" . 'Invalid ID3v2 padding found at offset ' . $ThisFileInfo['id3v2']['padding']['errorpos'];
                        break;
                    }
                }
                break;
                // skip rest of ID3v2 header
            }
            if ($ThisFileInfo['id3v2']['majorversion'] == 2) {
                // Frame ID  $xx xx xx (three characters)
                // Size      $xx xx xx (24-bit integer)
                // Flags     $xx xx
                $frame_header = substr($framedata, 0, 6);
                // take next 6 bytes for header
                $framedata = substr($framedata, 6);
                // and leave the rest in $framedata
                $frame_name = substr($frame_header, 0, 3);
                $frame_size = BigEndian2Int(substr($frame_header, 3, 3), 0);
                $frame_flags = '';
                // not used for anything, just to avoid E_NOTICEs
            } elseif ($ThisFileInfo['id3v2']['majorversion'] > 2) {
                // Frame ID  $xx xx xx xx (four characters)
                // Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
                // Flags     $xx xx
                $frame_header = substr($framedata, 0, 10);
                // take next 10 bytes for header
                $framedata = substr($framedata, 10);
                // and leave the rest in $framedata
                $frame_name = substr($frame_header, 0, 4);
                if ($ThisFileInfo['id3v2']['majorversion'] == 3) {
                    $frame_size = BigEndian2Int(substr($frame_header, 4, 4), 0);
                    // 32-bit integer
                } else {
                    // ID3v2.4+
                    $frame_size = BigEndian2Int(substr($frame_header, 4, 4), 1);
                    // 32-bit synchsafe integer (28-bit value)
                }
                if ($frame_size < strlen($framedata) + 4) {
                    $nextFrameID = substr($framedata, $frame_size, 4);
                    if (IsValidID3v2FrameName($nextFrameID, $ThisFileInfo['id3v2']['majorversion'])) {
                        // next frame is OK
                    } elseif ($frame_name == chr(0) . 'MP3' || $frame_name == chr(0) . chr(0) . 'MP' || $frame_name == ' MP3' || $frame_name == 'MP3e') {
                        // MP3ext known broken frames - "ok" for the purposes of this test
                    } elseif ($ThisFileInfo['id3v2']['majorversion'] == 4 && IsValidID3v2FrameName(substr($framedata, BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3)) {
                        $ThisFileInfo['warning'] .= "\n" . 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of Helium2 (www.helium2.com) is a known culprit of this. Tag has been parsed as ID3v2.3';
                        $ThisFileInfo['id3v2']['majorversion'] = 3;
                        $frame_size = BigEndian2Int(substr($frame_header, 4, 4), 0);
                        // 32-bit integer
                    }
                }
                $frame_flags = BigEndian2Bin(substr($frame_header, 8, 2));
            }
            if ($ThisFileInfo['id3v2']['majorversion'] == 2 && $frame_name == chr(0) . chr(0) . chr(0) || $frame_name == chr(0) . chr(0) . chr(0) . chr(0)) {
                // padding encountered
                $ThisFileInfo['id3v2']['padding']['start'] = $framedataoffset;
                $ThisFileInfo['id3v2']['padding']['length'] = strlen($framedata);
                $ThisFileInfo['id3v2']['padding']['valid'] = true;
                for ($i = 0; $i < $ThisFileInfo['id3v2']['padding']['length']; $i++) {
                    if (substr($framedata, $i, 1) != chr(0)) {
                        $ThisFileInfo['id3v2']['padding']['valid'] = false;
                        $ThisFileInfo['id3v2']['padding']['errorpos'] = $ThisFileInfo['id3v2']['padding']['start'] + $i;
                        $ThisFileInfo['warning'] .= "\n" . 'Invalid ID3v2 padding found at offset ' . $ThisFileInfo['id3v2']['padding']['errorpos'];
                        break;
                    }
                }
                break;
                // skip rest of ID3v2 header
            }
            if ($frame_name == 'COM ') {
                $ThisFileInfo['warning'] .= "\n" . 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $ThisFileInfo['id3v2']['majorversion'] . ' tag). (ERROR: !IsValidID3v2FrameName("' . str_replace(chr(0), ' ', $frame_name) . '", ' . $ThisFileInfo['id3v2']['majorversion'] . '))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
                $frame_name = 'COMM';
            }
            if ($frame_size <= strlen($framedata) && IsValidID3v2FrameName($frame_name, $ThisFileInfo['id3v2']['majorversion'])) {
                $ThisFileInfo['id3v2']["{$frame_name}"]['data'] = substr($framedata, 0, $frame_size);
                $ThisFileInfo['id3v2']["{$frame_name}"]['datalength'] = CastAsInt($frame_size);
                $ThisFileInfo['id3v2']["{$frame_name}"]['dataoffset'] = $framedataoffset;
                $framedata = substr($framedata, $frame_size);
                // in getid3.frames.php - this function does all the FrameID-level parsing
                ID3v2FrameProcessing($frame_name, $frame_flags, $ThisFileInfo);
            } else {
                // invalid frame length or FrameID
                if ($frame_size <= strlen($framedata)) {
                    if (IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $ThisFileInfo['id3v2']['majorversion'])) {
                        // next frame is valid, just skip the current frame
                        $framedata = substr($framedata, $frame_size);
                        $InvalidFrameMessageType = 'warning';
                        $InvalidFrameMessageText = ' Next frame is valid, skipping current frame.';
                    } else {
                        // next frame is invalid too, abort processing
                        unset($framedata);
                        $InvalidFrameMessageType = 'error';
                        $InvalidFrameMessageText = ' Next frame is also invalid, aborting processing.';
                    }
                } elseif ($frame_size == strlen($framedata)) {
                    // this is the last frame, just skip
                    $InvalidFrameMessageType = 'warning';
                    $InvalidFrameMessageText = ' This was the last frame.';
                } else {
                    // next frame is invalid too, abort processing
                    unset($framedata);
                    $InvalidFrameMessageType = 'error';
                    $InvalidFrameMessageText = ' Invalid frame size, aborting.';
                }
                if (!IsValidID3v2FrameName($frame_name, $ThisFileInfo['id3v2']['majorversion'])) {
                    switch ($frame_name) {
                        case chr(0) . chr(0) . 'MP':
                        case chr(0) . 'MP3':
                        case ' MP3':
                        case 'MP3e':
                        case chr(0) . 'MP':
                        case ' MP':
                        case 'MP3':
                            $InvalidFrameMessageType = 'warning';
                            $ThisFileInfo["{$InvalidFrameMessageType}"] .= "\n" . 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $ThisFileInfo['id3v2']['majorversion'] . ' tag). (ERROR: !IsValidID3v2FrameName("' . str_replace(chr(0), ' ', $frame_name) . '", ' . $ThisFileInfo['id3v2']['majorversion'] . '))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
                            break;
                        default:
                            $ThisFileInfo["{$InvalidFrameMessageType}"] .= "\n" . 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $ThisFileInfo['id3v2']['majorversion'] . ' tag). (ERROR: !IsValidID3v2FrameName("' . str_replace(chr(0), ' ', $frame_name) . '", ' . $ThisFileInfo['id3v2']['majorversion'] . '))).';
                            break;
                    }
                } elseif ($frame_size > strlen($framedata)) {
                    $ThisFileInfo["{$InvalidFrameMessageType}"] .= "\n" . 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $ThisFileInfo['id3v2']['majorversion'] . ' tag). (ERROR: $frame_size (' . $frame_size . ') > strlen($framedata) (' . strlen($framedata) . ')).';
                } else {
                    $ThisFileInfo["{$InvalidFrameMessageType}"] .= "\n" . 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $ThisFileInfo['id3v2']['majorversion'] . ' tag).';
                }
                $ThisFileInfo["{$InvalidFrameMessageType}"] .= $InvalidFrameMessageText;
            }
            $framedataoffset += $frame_size + ID3v2HeaderLength($ThisFileInfo['id3v2']['majorversion']);
        }
    }
    //    Footer
    //    The footer is a copy of the header, but with a different identifier.
    //        ID3v2 identifier           "3DI"
    //        ID3v2 version              $04 00
    //        ID3v2 flags                %abcd0000
    //        ID3v2 size             4 * %0xxxxxxx
    if (isset($ThisFileInfo['id3v2']['flags']['isfooter']) && $ThisFileInfo['id3v2']['flags']['isfooter']) {
        $footer = fread($fd, 10);
        if (substr($footer, 0, 3) == '3DI') {
            $ThisFileInfo['id3v2']['footer'] = true;
            $ThisFileInfo['id3v2']['majorversion_footer'] = ord(substr($footer, 3, 1));
            $ThisFileInfo['id3v2']['minorversion_footer'] = ord(substr($footer, 4, 1));
        }
        if ($ThisFileInfo['id3v2']['majorversion_footer'] <= 4) {
            $id3_flags = BigEndian2Bin(substr($footer, 5, 1));
            $ThisFileInfo['id3v2']['flags']['unsynch_footer'] = substr($id3_flags, 0, 1);
            $ThisFileInfo['id3v2']['flags']['extfoot_footer'] = substr($id3_flags, 1, 1);
            $ThisFileInfo['id3v2']['flags']['experim_footer'] = substr($id3_flags, 2, 1);
            $ThisFileInfo['id3v2']['flags']['isfooter_footer'] = substr($id3_flags, 3, 1);
            $ThisFileInfo['id3v2']['footerlength'] = BigEndian2Int(substr($footer, 6, 4), 1);
        }
    }
    // end footer
    if (isset($ThisFileInfo['id3v2']['comments']['genre'])) {
        foreach ($ThisFileInfo['id3v2']['comments']['genre'] as $key => $value) {
            unset($ThisFileInfo['id3v2']['comments']['genre'][$key]);
            $ThisFileInfo['id3v2']['comments'] = array_merge_noclobber($ThisFileInfo['id3v2']['comments'], ParseID3v2GenreString($value));
        }
    }
    if (isset($ThisFileInfo['id3v2']['comments']['track'])) {
        foreach ($ThisFileInfo['id3v2']['comments']['track'] as $key => $value) {
            if (strstr($value, '/')) {
                list($ThisFileInfo['id3v2']['comments']['track'][$key], $ThisFileInfo['id3v2']['comments']['totaltracks'][$key]) = explode('/', $ThisFileInfo['id3v2']['comments']['track'][$key]);
            }
            // Convert track number to integer (ID3v2 track numbers could be returned as a
            // string ('03' for example) - this will ensure all track numbers are integers
            $ThisFileInfo['id3v2']['comments']['track'][$key] = intval($ThisFileInfo['id3v2']['comments']['track'][$key]);
        }
    }
    return true;
}
             $id3info['id3']['id3v2']['flags']['exthead'] = $id3_flags[1];
             // b - Extended header
             $id3info['id3']['id3v2']['flags']['experim'] = $id3_flags[2];
             // c - Experimental indicator
             $id3info['id3']['id3v2']['flags']['isfooter'] = $id3_flags[3];
             // d - Footer present
         }
     }
 }
 $id3info['id3']['id3v2']['headerlength'] = BigEndian2Int(substr($id3v2header, 6, 4), 1) + ID3v2HeaderLength($id3info['id3']['id3v2']['majorversion']);
 $id3v2dataoffset = 10;
 if (isset($id3info['id3']['id3v2']['flags']['exthead']) && $id3info['id3']['id3v2']['flags']['exthead']) {
     $id3v2dataoffset += BigEndian2Int(substr($id3v2header, 10, 4), 1);
 }
 fseek($fd, $id3v2dataoffset, SEEK_SET);
 $sizeofframes = $id3info['id3']['id3v2']['headerlength'] - ID3v2HeaderLength($id3info['id3']['id3v2']['majorversion']);
 if (isset($id3info['id3']['id3v2']['extheaderlength'])) {
     $sizeofframes -= $id3info['id3']['id3v2']['extheaderlength'];
 }
 if (isset($id3info['id3']['id3v2']['flags']['isfooter']) && $id3info['id3']['id3v2']['flags']['isfooter']) {
     $sizeofframes -= 10;
     // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
 }
 if ($sizeofframes > 0) {
     $framedata = fread($fd, $sizeofframes);
     // read all frames from file into $framedata variable
     //	if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
     if (isset($id3info['id3']['id3v2']['flags']['unsynch']) && $id3info['id3']['id3v2']['flags']['unsynch'] && $id3info['id3']['id3v2']['majorversion'] <= 3) {
         $framedata = DeUnSynchronise($framedata);
     }
     $framedata = substr($framedata, $frameoffset - 10);
function getID3v2Filepointer($fd, &$MP3fileInfo)
{
    //	Overall tag structure:
    //		+-----------------------------+
    //		|      Header (10 bytes)      |
    //		+-----------------------------+
    //		|       Extended Header       |
    //		| (variable length, OPTIONAL) |
    //		+-----------------------------+
    //		|   Frames (variable length)  |
    //		+-----------------------------+
    //		|           Padding           |
    //		| (variable length, OPTIONAL) |
    //		+-----------------------------+
    //		| Footer (10 bytes, OPTIONAL) |
    //		+-----------------------------+
    //	Header
    //		ID3v2/file identifier      "ID3"
    //		ID3v2 version              $04 00
    //		ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
    //		ID3v2 size             4 * %0xxxxxxx
    rewind($fd);
    $header = fread($fd, 10);
    if (substr($header, 0, 3) == 'ID3') {
        $MP3fileInfo['id3']['id3v2']['header'] = TRUE;
        $MP3fileInfo['id3']['id3v2']['majorversion'] = ord($header[3]);
        $MP3fileInfo['id3']['id3v2']['minorversion'] = ord($header[4]);
    }
    if (isset($MP3fileInfo['id3']['id3v2']['header']) && $MP3fileInfo['id3']['id3v2']['majorversion'] <= 4) {
        // this script probably won't correctly parse ID3v2.5.x and above.
        $id3_flags = BigEndian2Bin($header[5]);
        if ($MP3fileInfo['id3']['id3v2']['majorversion'] == 2) {
            // %ab000000 in v2.2
            $MP3fileInfo['id3']['id3v2']['flags']['unsynch'] = $id3_flags[0];
            // a - Unsynchronisation
            $MP3fileInfo['id3']['id3v2']['flags']['compression'] = $id3_flags[1];
            // b - Compression
        } else {
            if ($MP3fileInfo['id3']['id3v2']['majorversion'] == 3) {
                // %abc00000 in v2.3
                $MP3fileInfo['id3']['id3v2']['flags']['unsynch'] = $id3_flags[0];
                // a - Unsynchronisation
                $MP3fileInfo['id3']['id3v2']['flags']['exthead'] = $id3_flags[1];
                // b - Extended header
                $MP3fileInfo['id3']['id3v2']['flags']['experim'] = $id3_flags[2];
                // c - Experimental indicator
            } else {
                if ($MP3fileInfo['id3']['id3v2']['majorversion'] == 4) {
                    // %abcd0000 in v2.4
                    $MP3fileInfo['id3']['id3v2']['flags']['unsynch'] = $id3_flags[0];
                    // a - Unsynchronisation
                    $MP3fileInfo['id3']['id3v2']['flags']['exthead'] = $id3_flags[1];
                    // b - Extended header
                    $MP3fileInfo['id3']['id3v2']['flags']['experim'] = $id3_flags[2];
                    // c - Experimental indicator
                    $MP3fileInfo['id3']['id3v2']['flags']['isfooter'] = $id3_flags[3];
                    // d - Footer present
                }
            }
        }
        $MP3fileInfo['id3']['id3v2']['headerlength'] = BigEndian2Int(substr($header, 6, 4), 1) + ID3v2HeaderLength($MP3fileInfo['id3']['id3v2']['majorversion']);
        //	Extended Header
        if (isset($MP3fileInfo['id3']['id3v2']['flags']['exthead']) && $MP3fileInfo['id3']['id3v2']['flags']['exthead']) {
            //			Extended header size   4 * %0xxxxxxx
            //			Number of flag bytes       $01
            //			Extended Flags             $xx
            //			Where the 'Extended header size' is the size of the whole extended header, stored as a 32 bit synchsafe integer.
            $extheader = fread($fd, 4);
            $MP3fileInfo['id3']['id3v2']['extheaderlength'] = BigEndian2Int($extheader, 1);
            //			The extended flags field, with its size described by 'number of flag  bytes', is defined as:
            //				%0bcd0000
            //			b - Tag is an update
            //				Flag data length       $00
            //			c - CRC data present
            //				Flag data length       $05
            //				Total frame CRC    5 * %0xxxxxxx
            //			d - Tag restrictions
            //				Flag data length       $01
            $extheaderflagbytes = fread($fd, 1);
            $extheaderflags = fread($fd, $extheaderflagbytes);
            $id3_exthead_flags = BigEndian2Bin(substr($header, 5, 1));
            $MP3fileInfo['id3']['id3v2']['exthead_flags']['update'] = substr($id3_exthead_flags, 1, 1);
            $MP3fileInfo['id3']['id3v2']['exthead_flags']['CRC'] = substr($id3_exthead_flags, 2, 1);
            if ($MP3fileInfo['id3']['id3v2']['exthead_flags']['CRC']) {
                $extheaderrawCRC = fread($fd, 5);
                $MP3fileInfo['id3']['id3v2']['exthead_flags']['CRC'] = BigEndian2Int($extheaderrawCRC, 1);
            }
            $MP3fileInfo['id3']['id3v2']['exthead_flags']['restrictions'] = substr($id3_exthead_flags, 3, 1);
            if ($MP3fileInfo['id3']['id3v2']['exthead_flags']['restrictions']) {
                // Restrictions           %ppqrrstt
                $extheaderrawrestrictions = fread($fd, 1);
                $MP3fileInfo['id3']['id3v2']['exthead_flags']['restrictions_tagsize'] = (bindec('11000000') & ord($extheaderrawrestrictions)) >> 6;
                // p - Tag size restrictions
                $MP3fileInfo['id3']['id3v2']['exthead_flags']['restrictions_textenc'] = (bindec('00100000') & ord($extheaderrawrestrictions)) >> 5;
                // q - Text encoding restrictions
                $MP3fileInfo['id3']['id3v2']['exthead_flags']['restrictions_textsize'] = (bindec('00011000') & ord($extheaderrawrestrictions)) >> 3;
                // r - Text fields size restrictions
                $MP3fileInfo['id3']['id3v2']['exthead_flags']['restrictions_imgenc'] = (bindec('00000100') & ord($extheaderrawrestrictions)) >> 2;
                // s - Image encoding restrictions
                $MP3fileInfo['id3']['id3v2']['exthead_flags']['restrictions_imgsize'] = (bindec('00000011') & ord($extheaderrawrestrictions)) >> 0;
                // t - Image size restrictions
            }
        }
        // end extended header
        //	Frames
        //		All ID3v2 frames consists of one frame header followed by one or more
        //		fields containing the actual information. The header is always 10
        //		bytes and laid out as follows:
        //
        //		Frame ID      $xx xx xx xx  (four characters)
        //		Size      4 * %0xxxxxxx
        //		Flags         $xx xx
        $sizeofframes = $MP3fileInfo['id3']['id3v2']['headerlength'] - ID3v2HeaderLength($MP3fileInfo['id3']['id3v2']['majorversion']);
        if (isset($MP3fileInfo['id3']['id3v2']['extheaderlength'])) {
            $sizeofframes -= $MP3fileInfo['id3']['id3v2']['extheaderlength'];
        }
        if (isset($MP3fileInfo['id3']['id3v2']['flags']['isfooter']) && $MP3fileInfo['id3']['id3v2']['flags']['isfooter']) {
            $sizeofframes -= 10;
            // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
        }
        if ($sizeofframes > 0) {
            $framedata = fread($fd, $sizeofframes);
            // read all frames from file into $framedata variable
            //	if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
            if (isset($MP3fileInfo['id3']['id3v2']['flags']['unsynch']) && $MP3fileInfo['id3']['id3v2']['flags']['unsynch'] && $MP3fileInfo['id3']['id3v2']['majorversion'] <= 3) {
                $framedata = DeUnSynchronise($framedata);
            }
            //		[in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
            //		of on tag level, making it easier to skip frames, increasing the streamability
            //		of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
            //		there exists an unsynchronised frame, while the new unsynchronisation flag in
            //		the frame header [S:4.1.2] indicates unsynchronisation.
            include_once GETID3_INCLUDEPATH . 'getid3.frames.php';
            // ID3v2FrameProcessing()
            $framedataoffset = 10;
            // how many bytes into the stream - start from after the 10-byte header
            while (isset($framedata) && strlen($framedata) > 0) {
                // cycle through until no more frame data is left to parse
                if ($MP3fileInfo['id3']['id3v2']['majorversion'] == 2) {
                    // Frame ID  $xx xx xx (three characters)
                    // Size      $xx xx xx (24-bit integer)
                    // Flags     $xx xx
                    $frame_header = substr($framedata, 0, 6);
                    // take next 6 bytes for header
                    $framedata = substr($framedata, 6);
                    // and leave the rest in $framedata
                    $frame_name = substr($frame_header, 0, 3);
                    $frame_size = BigEndian2Int(substr($frame_header, 3, 3), 0);
                    $frame_flags = '';
                    // not used for anything, just to avoid E_NOTICEs
                } else {
                    if ($MP3fileInfo['id3']['id3v2']['majorversion'] > 2) {
                        // Frame ID  $xx xx xx xx (four characters)
                        // Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
                        // Flags     $xx xx
                        $frame_header = substr($framedata, 0, 10);
                        // take next 10 bytes for header
                        $framedata = substr($framedata, 10);
                        // and leave the rest in $framedata
                        $frame_name = substr($frame_header, 0, 4);
                        if ($MP3fileInfo['id3']['id3v2']['majorversion'] == 3) {
                            $frame_size = BigEndian2Int(substr($frame_header, 4, 4), 0);
                            // 32-bit integer
                        } else {
                            // ID3v2.4+
                            $frame_size = BigEndian2Int(substr($frame_header, 4, 4), 1);
                            // 32-bit synchsafe integer (28-bit value)
                        }
                        if ($frame_size < strlen($framedata) + 4) {
                            $nextFrameID = substr($framedata, $frame_size, 4);
                            if (IsValidID3v2FrameName($nextFrameID, $MP3fileInfo['id3']['id3v2']['majorversion'])) {
                                // next frame is OK
                            } else {
                                if ($frame_name == chr(0) . 'MP3' || $frame_name == ' MP3' || $frame_name == 'MP3e') {
                                    // MP3ext known broken frames - "ok" for the purposes of this test
                                } else {
                                    if ($MP3fileInfo['id3']['id3v2']['majorversion'] == 4 && IsValidID3v2FrameName(substr($framedata, BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3)) {
                                        $MP3fileInfo['error'] .= "\n" . 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of Helium2 (www.helium2.com) is a known culprit of this. Tag has been parsed as ID3v2.3';
                                        $MP3fileInfo['id3']['id3v2']['majorversion'] = 3;
                                        $frame_size = BigEndian2Int(substr($frame_header, 4, 4), 0);
                                        // 32-bit integer
                                    }
                                }
                            }
                        }
                        $frame_flags = BigEndian2Bin(substr($frame_header, 8, 2));
                    }
                }
                if ($frame_name == chr(0) . chr(0) . chr(0) . chr(0)) {
                    // padding encountered
                    // $MP3fileInfo['id3']['id3v2']['padding']['start']  = $MP3fileInfo['id3']['id3v2']['headerlength'] - strlen($framedata);
                    $MP3fileInfo['id3']['id3v2']['padding']['start'] = $framedataoffset;
                    $MP3fileInfo['id3']['id3v2']['padding']['length'] = strlen($framedata);
                    $MP3fileInfo['id3']['id3v2']['padding']['valid'] = TRUE;
                    for ($i = 0; $i < $MP3fileInfo['id3']['id3v2']['padding']['length']; $i++) {
                        if (substr($framedata, $i, 1) != chr(0)) {
                            $MP3fileInfo['id3']['id3v2']['padding']['valid'] = FALSE;
                            $MP3fileInfo['id3']['id3v2']['padding']['errorpos'] = $MP3fileInfo['id3']['id3v2']['padding']['start'] + $i;
                            break;
                        }
                    }
                    break;
                    // skip rest of ID3v2 header
                }
                if ($frame_size <= strlen($framedata) && IsValidID3v2FrameName($frame_name, $MP3fileInfo['id3']['id3v2']['majorversion'])) {
                    $MP3fileInfo['id3']['id3v2']["{$frame_name}"]['data'] = substr($framedata, 0, $frame_size);
                    $MP3fileInfo['id3']['id3v2']["{$frame_name}"]['datalength'] = CastAsInt($frame_size);
                    $MP3fileInfo['id3']['id3v2']["{$frame_name}"]['dataoffset'] = $framedataoffset;
                    $framedata = substr($framedata, $frame_size);
                    // in getid3.frames.php - this function does all the FrameID-level parsing
                    ID3v2FrameProcessing($frame_name, $frame_flags, $MP3fileInfo);
                    $framedataoffset += $frame_size + ID3v2HeaderLength($MP3fileInfo['id3']['id3v2']['majorversion']);
                } else {
                    // invalid frame length or FrameID
                    $MP3fileInfo['error'] .= "\n" . 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $MP3fileInfo['id3']['id3v2']['majorversion'] . ' tag).';
                    if ($frame_size > strlen($framedata)) {
                        $MP3fileInfo['error'] .= ' (ERROR: $frame_size (' . $frame_size . ') > strlen($framedata) (' . strlen($framedata) . ')).';
                    }
                    if (!IsValidID3v2FrameName($frame_name, $MP3fileInfo['id3']['id3v2']['majorversion'])) {
                        $MP3fileInfo['error'] .= ' (ERROR: !IsValidID3v2FrameName("' . str_replace(chr(0), ' ', $frame_name) . '", ' . $MP3fileInfo['id3']['id3v2']['majorversion'] . '))).';
                        if ($frame_name == chr(0) . 'MP3' || $frame_name == ' MP3' || $frame_name == 'MP3e') {
                            $MP3fileInfo['error'] .= ' [Note: this particular error has been known to happen with tags edited by "MP3ext V3.3.17(unicode)"]';
                        } else {
                            if ($frame_name == 'COM ') {
                                $MP3fileInfo['error'] .= ' [Note: this particular error has been known to happen with tags edited by "iTunes X v2.0.3"]';
                            }
                        }
                    }
                    if ($frame_size <= strlen($framedata) && IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $MP3fileInfo['id3']['id3v2']['majorversion'])) {
                        // next frame is valid, just skip the current frame
                        $framedata = substr($framedata, $frame_size);
                    } else {
                        // next frame is invalid too, abort processing
                        unset($framedata);
                    }
                }
            }
        }
        //	Footer
        //	The footer is a copy of the header, but with a different identifier.
        //		ID3v2 identifier           "3DI"
        //		ID3v2 version              $04 00
        //		ID3v2 flags                %abcd0000
        //		ID3v2 size             4 * %0xxxxxxx
        if (isset($MP3fileInfo['id3']['id3v2']['flags']['isfooter']) && $MP3fileInfo['id3']['id3v2']['flags']['isfooter']) {
            $footer = fread($fd, 10);
            if (substr($footer, 0, 3) == '3DI') {
                $MP3fileInfo['id3']['id3v2']['footer'] = true;
                $MP3fileInfo['id3']['id3v2']['majorversion_footer'] = ord(substr($footer, 3, 1));
                $MP3fileInfo['id3']['id3v2']['minorversion_footer'] = ord(substr($footer, 4, 1));
            }
            if ($MP3fileInfo['id3']['id3v2']['majorversion_footer'] <= 4) {
                $id3_flags = BigEndian2Bin(substr($footer, 5, 1));
                $MP3fileInfo['id3']['id3v2']['flags']['unsynch_footer'] = substr($id3_flags, 0, 1);
                $MP3fileInfo['id3']['id3v2']['flags']['extfoot_footer'] = substr($id3_flags, 1, 1);
                $MP3fileInfo['id3']['id3v2']['flags']['experim_footer'] = substr($id3_flags, 2, 1);
                $MP3fileInfo['id3']['id3v2']['flags']['isfooter_footer'] = substr($id3_flags, 3, 1);
                $MP3fileInfo['id3']['id3v2']['footerlength'] = BigEndian2Int(substr($footer, 6, 4), 1);
            }
        }
        // end footer
        // Translate most common ID3v2 FrameIDs to easier-to-understand names
        if ($MP3fileInfo['id3']['id3v2']['majorversion'] == 2) {
            if (isset($MP3fileInfo['id3']['id3v2']['TT2'])) {
                $MP3fileInfo['id3']['id3v2']['title'] = $MP3fileInfo['id3']['id3v2']['TT2']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['TP1'])) {
                $MP3fileInfo['id3']['id3v2']['artist'] = $MP3fileInfo['id3']['id3v2']['TP1']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['TAL'])) {
                $MP3fileInfo['id3']['id3v2']['album'] = $MP3fileInfo['id3']['id3v2']['TAL']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['TYE'])) {
                $MP3fileInfo['id3']['id3v2']['year'] = $MP3fileInfo['id3']['id3v2']['TYE']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['TRK'])) {
                $MP3fileInfo['id3']['id3v2']['track'] = $MP3fileInfo['id3']['id3v2']['TRK']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['TCO'])) {
                $MP3fileInfo['id3']['id3v2']['genre'] = $MP3fileInfo['id3']['id3v2']['TCO']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['COM'][0]['asciidata'])) {
                $MP3fileInfo['id3']['id3v2']['comment'] = $MP3fileInfo['id3']['id3v2']['COM'][0]['asciidata'];
            }
        } else {
            // $MP3fileInfo['id3']['id3v2']['majorversion'] > 2
            if (isset($MP3fileInfo['id3']['id3v2']['TIT2'])) {
                $MP3fileInfo['id3']['id3v2']['title'] = $MP3fileInfo['id3']['id3v2']['TIT2']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['TPE1'])) {
                $MP3fileInfo['id3']['id3v2']['artist'] = $MP3fileInfo['id3']['id3v2']['TPE1']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['TALB'])) {
                $MP3fileInfo['id3']['id3v2']['album'] = $MP3fileInfo['id3']['id3v2']['TALB']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['TYER'])) {
                $MP3fileInfo['id3']['id3v2']['year'] = $MP3fileInfo['id3']['id3v2']['TYER']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['TRCK'])) {
                $MP3fileInfo['id3']['id3v2']['track'] = $MP3fileInfo['id3']['id3v2']['TRCK']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['TCON'])) {
                $MP3fileInfo['id3']['id3v2']['genre'] = $MP3fileInfo['id3']['id3v2']['TCON']['asciidata'];
            }
            if (isset($MP3fileInfo['id3']['id3v2']['COMM'][0]['asciidata'])) {
                $MP3fileInfo['id3']['id3v2']['comment'] = $MP3fileInfo['id3']['id3v2']['COMM'][0]['asciidata'];
            }
        }
        if (isset($MP3fileInfo['id3']['id3v2']['genre'])) {
            $MP3fileInfo['id3']['id3v2']['genrelist'] = ParseID3v2GenreString($MP3fileInfo['id3']['id3v2']['genre']);
            if ($MP3fileInfo['id3']['id3v2']['genrelist']['genreid'][0] !== '') {
                $MP3fileInfo['id3']['id3v2']['genreid'] = $MP3fileInfo['id3']['id3v2']['genrelist']['genreid'][0];
            }
            $MP3fileInfo['id3']['id3v2']['genre'] = $MP3fileInfo['id3']['id3v2']['genrelist']['genre'][0];
        }
        if (isset($MP3fileInfo['id3']['id3v2']['track']) && strpos($MP3fileInfo['id3']['id3v2']['track'], '/') !== FALSE) {
            $tracktotaltracks = explode('/', $MP3fileInfo['id3']['id3v2']['track']);
            $MP3fileInfo['id3']['id3v2']['track'] = $tracktotaltracks[0];
            $MP3fileInfo['id3']['id3v2']['totaltracks'] = $tracktotaltracks[1];
        }
    } else {
        // MajorVersion is > 4, or no ID3v2 header present
        if (isset($MP3fileInfo['id3']['id3v2']['header'])) {
            // MajorVersion is > 4
            $MP3fileInfo['error'] .= "\n" . 'this script only parses up to ID3v2.4.x - this tag is ID3v2.' . $MP3fileInfo['id3']['id3v2']['majorversion'] . '.' . $MP3fileInfo['id3']['id3v2']['minorversion'];
        } else {
            // no ID3v2 header present - this is fine, just don't process anything.
        }
    }
    return TRUE;
}