File indexing completed on 2024-12-22 05:33:14

0001 <?php
0002 /////////////////////////////////////////////////////////////////
0003 /// getID3() by James Heinrich <info@getid3.org>               //
0004 //  available at http://getid3.sourceforge.net                 //
0005 //            or http://www.getid3.org                         //
0006 //          also https://github.com/JamesHeinrich/getID3       //
0007 /////////////////////////////////////////////////////////////////
0008 // See readme.txt for more details                             //
0009 /////////////////////////////////////////////////////////////////
0010 ///                                                            //
0011 // module.tag.id3v2.php                                        //
0012 // module for analyzing ID3v2 tags                             //
0013 // dependencies: module.tag.id3v1.php                          //
0014 //                                                            ///
0015 /////////////////////////////////////////////////////////////////
0016 
0017 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
0018 
0019 class getid3_id3v2 extends getid3_handler
0020 {
0021   public $StartingOffset = 0;
0022 
0023   public function Analyze() {
0024     $info = &$this->getid3->info;
0025 
0026     //    Overall tag structure:
0027     //        +-----------------------------+
0028     //        |      Header (10 bytes)      |
0029     //        +-----------------------------+
0030     //        |       Extended Header       |
0031     //        | (variable length, OPTIONAL) |
0032     //        +-----------------------------+
0033     //        |   Frames (variable length)  |
0034     //        +-----------------------------+
0035     //        |           Padding           |
0036     //        | (variable length, OPTIONAL) |
0037     //        +-----------------------------+
0038     //        | Footer (10 bytes, OPTIONAL) |
0039     //        +-----------------------------+
0040 
0041     //    Header
0042     //        ID3v2/file identifier      "ID3"
0043     //        ID3v2 version              $04 00
0044     //        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
0045     //        ID3v2 size             4 * %0xxxxxxx
0046 
0047 
0048     // shortcuts
0049     $info['id3v2']['header'] = true;
0050     $thisfile_id3v2                  = &$info['id3v2'];
0051     $thisfile_id3v2['flags']         =  array();
0052     $thisfile_id3v2_flags            = &$thisfile_id3v2['flags'];
0053 
0054 
0055     $this->fseek($this->StartingOffset);
0056     $header = $this->fread(10);
0057     if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
0058 
0059       $thisfile_id3v2['majorversion'] = ord($header{3});
0060       $thisfile_id3v2['minorversion'] = ord($header{4});
0061 
0062       // shortcut
0063       $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
0064 
0065     } else {
0066 
0067       unset($info['id3v2']);
0068       return false;
0069 
0070     }
0071 
0072     if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
0073 
0074       $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
0075       return false;
0076 
0077     }
0078 
0079     $id3_flags = ord($header{5});
0080     switch ($id3v2_majorversion) {
0081       case 2:
0082         // %ab000000 in v2.2
0083         $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
0084         $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
0085         break;
0086 
0087       case 3:
0088         // %abc00000 in v2.3
0089         $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
0090         $thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
0091         $thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
0092         break;
0093 
0094       case 4:
0095         // %abcd0000 in v2.4
0096         $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
0097         $thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
0098         $thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
0099         $thisfile_id3v2_flags['isfooter']    = (bool) ($id3_flags & 0x10); // d - Footer present
0100         break;
0101     }
0102 
0103     $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
0104 
0105     $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
0106     $thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
0107 
0108 
0109 
0110     // create 'encoding' key - used by getid3::HandleAllTags()
0111     // in ID3v2 every field can have it's own encoding type
0112     // so force everything to UTF-8 so it can be handled consistantly
0113     $thisfile_id3v2['encoding'] = 'UTF-8';
0114 
0115 
0116   //    Frames
0117 
0118   //        All ID3v2 frames consists of one frame header followed by one or more
0119   //        fields containing the actual information. The header is always 10
0120   //        bytes and laid out as follows:
0121   //
0122   //        Frame ID      $xx xx xx xx  (four characters)
0123   //        Size      4 * %0xxxxxxx
0124   //        Flags         $xx xx
0125 
0126     $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
0127     if (!empty($thisfile_id3v2['exthead']['length'])) {
0128       $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
0129     }
0130     if (!empty($thisfile_id3v2_flags['isfooter'])) {
0131       $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
0132     }
0133     if ($sizeofframes > 0) {
0134 
0135       $framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
0136 
0137       //    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
0138       if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
0139         $framedata = $this->DeUnsynchronise($framedata);
0140       }
0141       //        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
0142       //        of on tag level, making it easier to skip frames, increasing the streamability
0143       //        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
0144       //        there exists an unsynchronised frame, while the new unsynchronisation flag in
0145       //        the frame header [S:4.1.2] indicates unsynchronisation.
0146 
0147 
0148       //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
0149       $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
0150 
0151 
0152       //    Extended Header
0153       if (!empty($thisfile_id3v2_flags['exthead'])) {
0154         $extended_header_offset = 0;
0155 
0156         if ($id3v2_majorversion == 3) {
0157 
0158           // v2.3 definition:
0159           //Extended header size  $xx xx xx xx   // 32-bit integer
0160           //Extended Flags        $xx xx
0161           //     %x0000000 %00000000 // v2.3
0162           //     x - CRC data present
0163           //Size of padding       $xx xx xx xx
0164 
0165           $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
0166           $extended_header_offset += 4;
0167 
0168           $thisfile_id3v2['exthead']['flag_bytes'] = 2;
0169           $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
0170           $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
0171 
0172           $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
0173 
0174           $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
0175           $extended_header_offset += 4;
0176 
0177           if ($thisfile_id3v2['exthead']['flags']['crc']) {
0178             $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
0179             $extended_header_offset += 4;
0180           }
0181           $extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
0182 
0183         } elseif ($id3v2_majorversion == 4) {
0184 
0185           // v2.4 definition:
0186           //Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
0187           //Number of flag bytes       $01
0188           //Extended Flags             $xx
0189           //     %0bcd0000 // v2.4
0190           //     b - Tag is an update
0191           //         Flag data length       $00
0192           //     c - CRC data present
0193           //         Flag data length       $05
0194           //         Total frame CRC    5 * %0xxxxxxx
0195           //     d - Tag restrictions
0196           //         Flag data length       $01
0197 
0198           $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
0199           $extended_header_offset += 4;
0200 
0201           $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
0202           $extended_header_offset += 1;
0203 
0204           $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
0205           $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
0206 
0207           $thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
0208           $thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
0209           $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
0210 
0211           if ($thisfile_id3v2['exthead']['flags']['update']) {
0212             $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
0213             $extended_header_offset += 1;
0214           }
0215 
0216           if ($thisfile_id3v2['exthead']['flags']['crc']) {
0217             $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
0218             $extended_header_offset += 1;
0219             $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
0220             $extended_header_offset += $ext_header_chunk_length;
0221           }
0222 
0223           if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
0224             $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
0225             $extended_header_offset += 1;
0226 
0227             // %ppqrrstt
0228             $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
0229             $extended_header_offset += 1;
0230             $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
0231             $thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
0232             $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
0233             $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
0234             $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
0235 
0236             $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize']  = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
0237             $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc']  = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
0238             $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
0239             $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
0240             $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
0241           }
0242 
0243           if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
0244             $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
0245           }
0246         }
0247 
0248         $framedataoffset += $extended_header_offset;
0249         $framedata = substr($framedata, $extended_header_offset);
0250       } // end extended header
0251 
0252 
0253       while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
0254         if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
0255           // insufficient room left in ID3v2 header for actual data - must be padding
0256           $thisfile_id3v2['padding']['start']  = $framedataoffset;
0257           $thisfile_id3v2['padding']['length'] = strlen($framedata);
0258           $thisfile_id3v2['padding']['valid']  = true;
0259           for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
0260             if ($framedata{$i} != "\x00") {
0261               $thisfile_id3v2['padding']['valid'] = false;
0262               $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
0263               $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
0264               break;
0265             }
0266           }
0267           break; // skip rest of ID3v2 header
0268         }
0269         if ($id3v2_majorversion == 2) {
0270           // Frame ID  $xx xx xx (three characters)
0271           // Size      $xx xx xx (24-bit integer)
0272           // Flags     $xx xx
0273 
0274           $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
0275           $framedata    = substr($framedata, 6);    // and leave the rest in $framedata
0276           $frame_name   = substr($frame_header, 0, 3);
0277           $frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
0278           $frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
0279 
0280         } elseif ($id3v2_majorversion > 2) {
0281 
0282           // Frame ID  $xx xx xx xx (four characters)
0283           // Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
0284           // Flags     $xx xx
0285 
0286           $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
0287           $framedata    = substr($framedata, 10);    // and leave the rest in $framedata
0288 
0289           $frame_name = substr($frame_header, 0, 4);
0290           if ($id3v2_majorversion == 3) {
0291             $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
0292           } else { // ID3v2.4+
0293             $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
0294           }
0295 
0296           if ($frame_size < (strlen($framedata) + 4)) {
0297             $nextFrameID = substr($framedata, $frame_size, 4);
0298             if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
0299               // next frame is OK
0300             } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
0301               // MP3ext known broken frames - "ok" for the purposes of this test
0302             } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
0303               $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
0304               $id3v2_majorversion = 3;
0305               $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
0306             }
0307           }
0308 
0309 
0310           $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
0311         }
0312 
0313         if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
0314           // padding encountered
0315 
0316           $thisfile_id3v2['padding']['start']  = $framedataoffset;
0317           $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
0318           $thisfile_id3v2['padding']['valid']  = true;
0319 
0320           $len = strlen($framedata);
0321           for ($i = 0; $i < $len; $i++) {
0322             if ($framedata{$i} != "\x00") {
0323               $thisfile_id3v2['padding']['valid'] = false;
0324               $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
0325               $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
0326               break;
0327             }
0328           }
0329           break; // skip rest of ID3v2 header
0330         }
0331 
0332         if ($frame_name == 'COM ') {
0333           $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$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)]';
0334           $frame_name = 'COMM';
0335         }
0336         if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
0337 
0338           unset($parsedFrame);
0339           $parsedFrame['frame_name']      = $frame_name;
0340           $parsedFrame['frame_flags_raw'] = $frame_flags;
0341           $parsedFrame['data']            = substr($framedata, 0, $frame_size);
0342           $parsedFrame['datalength']      = getid3_lib::CastAsInt($frame_size);
0343           $parsedFrame['dataoffset']      = $framedataoffset;
0344 
0345           $this->ParseID3v2Frame($parsedFrame);
0346           $thisfile_id3v2[$frame_name][] = $parsedFrame;
0347 
0348           $framedata = substr($framedata, $frame_size);
0349 
0350         } else { // invalid frame length or FrameID
0351 
0352           if ($frame_size <= strlen($framedata)) {
0353 
0354             if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
0355 
0356               // next frame is valid, just skip the current frame
0357               $framedata = substr($framedata, $frame_size);
0358               $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
0359 
0360             } else {
0361 
0362               // next frame is invalid too, abort processing
0363               //unset($framedata);
0364               $framedata = null;
0365               $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
0366 
0367             }
0368 
0369           } elseif ($frame_size == strlen($framedata)) {
0370 
0371             // this is the last frame, just skip
0372             $info['warning'][] = 'This was the last ID3v2 frame.';
0373 
0374           } else {
0375 
0376             // next frame is invalid too, abort processing
0377             //unset($framedata);
0378             $framedata = null;
0379             $info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
0380 
0381           }
0382           if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
0383 
0384             switch ($frame_name) {
0385               case "\x00\x00".'MP':
0386               case "\x00".'MP3':
0387               case ' MP3':
0388               case 'MP3e':
0389               case "\x00".'MP':
0390               case ' MP':
0391               case 'MP3':
0392                 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
0393                 break;
0394 
0395               default:
0396                 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
0397                 break;
0398             }
0399 
0400           } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
0401 
0402             $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).';
0403 
0404           } else {
0405 
0406             $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
0407 
0408           }
0409 
0410         }
0411         $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
0412 
0413       }
0414 
0415     }
0416 
0417 
0418   //    Footer
0419 
0420   //    The footer is a copy of the header, but with a different identifier.
0421   //        ID3v2 identifier           "3DI"
0422   //        ID3v2 version              $04 00
0423   //        ID3v2 flags                %abcd0000
0424   //        ID3v2 size             4 * %0xxxxxxx
0425 
0426     if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
0427       $footer = $this->fread(10);
0428       if (substr($footer, 0, 3) == '3DI') {
0429         $thisfile_id3v2['footer'] = true;
0430         $thisfile_id3v2['majorversion_footer'] = ord($footer{3});
0431         $thisfile_id3v2['minorversion_footer'] = ord($footer{4});
0432       }
0433       if ($thisfile_id3v2['majorversion_footer'] <= 4) {
0434         $id3_flags = ord(substr($footer{5}));
0435         $thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
0436         $thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
0437         $thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
0438         $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
0439 
0440         $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
0441       }
0442     } // end footer
0443 
0444     if (isset($thisfile_id3v2['comments']['genre'])) {
0445       foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
0446         unset($thisfile_id3v2['comments']['genre'][$key]);
0447         $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
0448       }
0449     }
0450 
0451     if (isset($thisfile_id3v2['comments']['track'])) {
0452       foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
0453         if (strstr($value, '/')) {
0454           list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
0455         }
0456       }
0457     }
0458 
0459     if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
0460       $thisfile_id3v2['comments']['year'] = array($matches[1]);
0461     }
0462 
0463 
0464     if (!empty($thisfile_id3v2['TXXX'])) {
0465       // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
0466       foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
0467         switch ($txxx_array['description']) {
0468           case 'replaygain_track_gain':
0469             if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
0470               $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
0471             }
0472             break;
0473           case 'replaygain_track_peak':
0474             if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
0475               $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
0476             }
0477             break;
0478           case 'replaygain_album_gain':
0479             if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
0480               $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
0481             }
0482             break;
0483         }
0484       }
0485     }
0486 
0487 
0488     // Set avdataoffset
0489     $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
0490     if (isset($thisfile_id3v2['footer'])) {
0491       $info['avdataoffset'] += 10;
0492     }
0493 
0494     return true;
0495   }
0496 
0497 
0498   public function ParseID3v2GenreString($genrestring) {
0499     // Parse genres into arrays of genreName and genreID
0500     // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
0501     // ID3v2.4.x: '21' $00 'Eurodisco' $00
0502     $clean_genres = array();
0503     if (strpos($genrestring, "\x00") === false) {
0504       $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
0505     }
0506     $genre_elements = explode("\x00", $genrestring);
0507     foreach ($genre_elements as $element) {
0508       $element = trim($element);
0509       if ($element) {
0510         if (preg_match('#^[0-9]{1,3}#', $element)) {
0511           $clean_genres[] = getid3_id3v1::LookupGenreName($element);
0512         } else {
0513           $clean_genres[] = str_replace('((', '(', $element);
0514         }
0515       }
0516     }
0517     return $clean_genres;
0518   }
0519 
0520 
0521   public function ParseID3v2Frame(&$parsedFrame) {
0522 
0523     // shortcuts
0524     $info = &$this->getid3->info;
0525     $id3v2_majorversion = $info['id3v2']['majorversion'];
0526 
0527     $parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
0528     if (empty($parsedFrame['framenamelong'])) {
0529       unset($parsedFrame['framenamelong']);
0530     }
0531     $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
0532     if (empty($parsedFrame['framenameshort'])) {
0533       unset($parsedFrame['framenameshort']);
0534     }
0535 
0536     if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
0537       if ($id3v2_majorversion == 3) {
0538         //    Frame Header Flags
0539         //    %abc00000 %ijk00000
0540         $parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
0541         $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
0542         $parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
0543         $parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
0544         $parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
0545         $parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
0546 
0547       } elseif ($id3v2_majorversion == 4) {
0548         //    Frame Header Flags
0549         //    %0abc0000 %0h00kmnp
0550         $parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
0551         $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
0552         $parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
0553         $parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
0554         $parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
0555         $parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
0556         $parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
0557         $parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
0558 
0559         // Frame-level de-unsynchronisation - ID3v2.4
0560         if ($parsedFrame['flags']['Unsynchronisation']) {
0561           $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
0562         }
0563 
0564         if ($parsedFrame['flags']['DataLengthIndicator']) {
0565           $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
0566           $parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
0567         }
0568       }
0569 
0570       //    Frame-level de-compression
0571       if ($parsedFrame['flags']['compression']) {
0572         $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
0573         if (!function_exists('gzuncompress')) {
0574           $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
0575         } else {
0576           if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
0577           //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
0578             $parsedFrame['data'] = $decompresseddata;
0579             unset($decompresseddata);
0580           } else {
0581             $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
0582           }
0583         }
0584       }
0585     }
0586 
0587     if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
0588       if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
0589         $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
0590       }
0591     }
0592 
0593     if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
0594 
0595       $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
0596       switch ($parsedFrame['frame_name']) {
0597         case 'WCOM':
0598           $warning .= ' (this is known to happen with files tagged by RioPort)';
0599           break;
0600 
0601         default:
0602           break;
0603       }
0604       $info['warning'][] = $warning;
0605 
0606     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
0607       (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
0608       //   There may be more than one 'UFID' frame in a tag,
0609       //   but only one with the same 'Owner identifier'.
0610       // <Header for 'Unique file identifier', ID: 'UFID'>
0611       // Owner identifier        <text string> $00
0612       // Identifier              <up to 64 bytes binary data>
0613       $exploded = explode("\x00", $parsedFrame['data'], 2);
0614       $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
0615       $parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : '');
0616 
0617     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
0618         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
0619       //   There may be more than one 'TXXX' frame in each tag,
0620       //   but only one with the same description.
0621       // <Header for 'User defined text information frame', ID: 'TXXX'>
0622       // Text encoding     $xx
0623       // Description       <text string according to encoding> $00 (00)
0624       // Value             <text string according to encoding>
0625 
0626       $frame_offset = 0;
0627       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
0628 
0629       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
0630         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
0631       }
0632       $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
0633       if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
0634         $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
0635       }
0636       $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
0637       if (ord($frame_description) === 0) {
0638         $frame_description = '';
0639       }
0640       $parsedFrame['encodingid']  = $frame_textencoding;
0641       $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
0642 
0643       $parsedFrame['description'] = $frame_description;
0644       $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
0645       if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
0646         $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
0647         if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
0648           $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
0649         } else {
0650           $info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
0651         }
0652       }
0653       //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
0654 
0655 
0656     } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
0657       //   There may only be one text information frame of its kind in an tag.
0658       // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
0659       // excluding 'TXXX' described in 4.2.6.>
0660       // Text encoding                $xx
0661       // Information                  <text string(s) according to encoding>
0662 
0663       $frame_offset = 0;
0664       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
0665       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
0666         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
0667       }
0668 
0669       $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
0670 
0671       $parsedFrame['encodingid'] = $frame_textencoding;
0672       $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
0673 
0674       if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
0675         // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
0676         // This of course breaks when an artist name contains slash character, e.g. "AC/DC"
0677         // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
0678         // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
0679         switch ($parsedFrame['encoding']) {
0680           case 'UTF-16':
0681           case 'UTF-16BE':
0682           case 'UTF-16LE':
0683             $wordsize = 2;
0684             break;
0685           case 'ISO-8859-1':
0686           case 'UTF-8':
0687           default:
0688             $wordsize = 1;
0689             break;
0690         }
0691         $Txxx_elements = array();
0692         $Txxx_elements_start_offset = 0;
0693         for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
0694           if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
0695             $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
0696             $Txxx_elements_start_offset = $i + $wordsize;
0697           }
0698         }
0699         $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
0700         foreach ($Txxx_elements as $Txxx_element) {
0701           $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
0702           if (!empty($string)) {
0703             $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
0704           }
0705         }
0706         unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
0707       }
0708 
0709     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
0710         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
0711       //   There may be more than one 'WXXX' frame in each tag,
0712       //   but only one with the same description
0713       // <Header for 'User defined URL link frame', ID: 'WXXX'>
0714       // Text encoding     $xx
0715       // Description       <text string according to encoding> $00 (00)
0716       // URL               <text string>
0717 
0718       $frame_offset = 0;
0719       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
0720       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
0721         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
0722       }
0723       $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
0724       if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
0725         $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
0726       }
0727       $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
0728 
0729       if (ord($frame_description) === 0) {
0730         $frame_description = '';
0731       }
0732       $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
0733 
0734       $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
0735       if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
0736         $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
0737       }
0738       if ($frame_terminatorpos) {
0739         // there are null bytes after the data - this is not according to spec
0740         // only use data up to first null byte
0741         $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
0742       } else {
0743         // no null bytes following data, just use all data
0744         $frame_urldata = (string) $parsedFrame['data'];
0745       }
0746 
0747       $parsedFrame['encodingid']  = $frame_textencoding;
0748       $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
0749 
0750       $parsedFrame['url']         = $frame_urldata;
0751       $parsedFrame['description'] = $frame_description;
0752       if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
0753         $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
0754       }
0755       unset($parsedFrame['data']);
0756 
0757 
0758     } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
0759       //   There may only be one URL link frame of its kind in a tag,
0760       //   except when stated otherwise in the frame description
0761       // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
0762       // described in 4.3.2.>
0763       // URL              <text string>
0764 
0765       $parsedFrame['url'] = trim($parsedFrame['data']);
0766       if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
0767         $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
0768       }
0769       unset($parsedFrame['data']);
0770 
0771 
0772     } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
0773         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
0774       // http://id3.org/id3v2.3.0#sec4.4
0775       //   There may only be one 'IPL' frame in each tag
0776       // <Header for 'User defined URL link frame', ID: 'IPL'>
0777       // Text encoding     $xx
0778       // People list strings    <textstrings>
0779 
0780       $frame_offset = 0;
0781       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
0782       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
0783         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
0784       }
0785       $parsedFrame['encodingid'] = $frame_textencoding;
0786       $parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
0787       $parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
0788 
0789       // http://www.getid3.org/phpBB3/viewtopic.php?t=1369
0790       // "this tag typically contains null terminated strings, which are associated in pairs"
0791       // "there are users that use the tag incorrectly"
0792       $IPLS_parts = array();
0793       if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
0794         $IPLS_parts_unsorted = array();
0795         if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
0796           // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
0797           $thisILPS  = '';
0798           for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
0799             $twobytes = substr($parsedFrame['data_raw'], $i, 2);
0800             if ($twobytes === "\x00\x00") {
0801               $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
0802               $thisILPS  = '';
0803             } else {
0804               $thisILPS .= $twobytes;
0805             }
0806           }
0807           if (strlen($thisILPS) > 2) { // 2-byte BOM
0808             $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
0809           }
0810         } else {
0811           // ISO-8859-1 or UTF-8 or other single-byte-null character set
0812           $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
0813         }
0814         if (count($IPLS_parts_unsorted) == 1) {
0815           // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
0816           foreach ($IPLS_parts_unsorted as $key => $value) {
0817             $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
0818             $position = '';
0819             foreach ($IPLS_parts_sorted as $person) {
0820               $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
0821             }
0822           }
0823         } elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
0824           $position = '';
0825           $person   = '';
0826           foreach ($IPLS_parts_unsorted as $key => $value) {
0827             if (($key % 2) == 0) {
0828               $position = $value;
0829             } else {
0830               $person   = $value;
0831               $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
0832               $position = '';
0833               $person   = '';
0834             }
0835           }
0836         } else {
0837           foreach ($IPLS_parts_unsorted as $key => $value) {
0838             $IPLS_parts[] = array($value);
0839           }
0840         }
0841 
0842       } else {
0843         $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
0844       }
0845       $parsedFrame['data'] = $IPLS_parts;
0846 
0847       if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
0848         $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
0849       }
0850 
0851 
0852     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
0853         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
0854       //   There may only be one 'MCDI' frame in each tag
0855       // <Header for 'Music CD identifier', ID: 'MCDI'>
0856       // CD TOC                <binary data>
0857 
0858       if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
0859         $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
0860       }
0861 
0862 
0863     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
0864         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
0865       //   There may only be one 'ETCO' frame in each tag
0866       // <Header for 'Event timing codes', ID: 'ETCO'>
0867       // Time stamp format    $xx
0868       //   Where time stamp format is:
0869       // $01  (32-bit value) MPEG frames from beginning of file
0870       // $02  (32-bit value) milliseconds from beginning of file
0871       //   Followed by a list of key events in the following format:
0872       // Type of event   $xx
0873       // Time stamp      $xx (xx ...)
0874       //   The 'Time stamp' is set to zero if directly at the beginning of the sound
0875       //   or after the previous event. All events MUST be sorted in chronological order.
0876 
0877       $frame_offset = 0;
0878       $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
0879 
0880       while ($frame_offset < strlen($parsedFrame['data'])) {
0881         $parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1);
0882         $parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']);
0883         $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
0884         $frame_offset += 4;
0885       }
0886       unset($parsedFrame['data']);
0887 
0888 
0889     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
0890         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
0891       //   There may only be one 'MLLT' frame in each tag
0892       // <Header for 'Location lookup table', ID: 'MLLT'>
0893       // MPEG frames between reference  $xx xx
0894       // Bytes between reference        $xx xx xx
0895       // Milliseconds between reference $xx xx xx
0896       // Bits for bytes deviation       $xx
0897       // Bits for milliseconds dev.     $xx
0898       //   Then for every reference the following data is included;
0899       // Deviation in bytes         %xxx....
0900       // Deviation in milliseconds  %xxx....
0901 
0902       $frame_offset = 0;
0903       $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
0904       $parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
0905       $parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
0906       $parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
0907       $parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
0908       $parsedFrame['data'] = substr($parsedFrame['data'], 10);
0909       while ($frame_offset < strlen($parsedFrame['data'])) {
0910         $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
0911       }
0912       $reference_counter = 0;
0913       while (strlen($deviationbitstream) > 0) {
0914         $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
0915         $parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
0916         $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
0917         $reference_counter++;
0918       }
0919       unset($parsedFrame['data']);
0920 
0921 
0922     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
0923           (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
0924       //   There may only be one 'SYTC' frame in each tag
0925       // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
0926       // Time stamp format   $xx
0927       // Tempo data          <binary data>
0928       //   Where time stamp format is:
0929       // $01  (32-bit value) MPEG frames from beginning of file
0930       // $02  (32-bit value) milliseconds from beginning of file
0931 
0932       $frame_offset = 0;
0933       $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
0934       $timestamp_counter = 0;
0935       while ($frame_offset < strlen($parsedFrame['data'])) {
0936         $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
0937         if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
0938           $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
0939         }
0940         $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
0941         $frame_offset += 4;
0942         $timestamp_counter++;
0943       }
0944       unset($parsedFrame['data']);
0945 
0946 
0947     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
0948         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
0949       //   There may be more than one 'Unsynchronised lyrics/text transcription' frame
0950       //   in each tag, but only one with the same language and content descriptor.
0951       // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
0952       // Text encoding        $xx
0953       // Language             $xx xx xx
0954       // Content descriptor   <text string according to encoding> $00 (00)
0955       // Lyrics/text          <full text string according to encoding>
0956 
0957       $frame_offset = 0;
0958       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
0959       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
0960         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
0961       }
0962       $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
0963       $frame_offset += 3;
0964       $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
0965       if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
0966         $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
0967       }
0968       $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
0969       if (ord($frame_description) === 0) {
0970         $frame_description = '';
0971       }
0972       $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
0973 
0974       $parsedFrame['encodingid']   = $frame_textencoding;
0975       $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
0976 
0977       $parsedFrame['data']         = $parsedFrame['data'];
0978       $parsedFrame['language']     = $frame_language;
0979       $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
0980       $parsedFrame['description']  = $frame_description;
0981       if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
0982         $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
0983       }
0984       unset($parsedFrame['data']);
0985 
0986 
0987     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
0988         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
0989       //   There may be more than one 'SYLT' frame in each tag,
0990       //   but only one with the same language and content descriptor.
0991       // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
0992       // Text encoding        $xx
0993       // Language             $xx xx xx
0994       // Time stamp format    $xx
0995       //   $01  (32-bit value) MPEG frames from beginning of file
0996       //   $02  (32-bit value) milliseconds from beginning of file
0997       // Content type         $xx
0998       // Content descriptor   <text string according to encoding> $00 (00)
0999       //   Terminated text to be synced (typically a syllable)
1000       //   Sync identifier (terminator to above string)   $00 (00)
1001       //   Time stamp                                     $xx (xx ...)
1002 
1003       $frame_offset = 0;
1004       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1005       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1006         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1007       }
1008       $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1009       $frame_offset += 3;
1010       $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1011       $parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1012       $parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1013       $parsedFrame['encodingid']      = $frame_textencoding;
1014       $parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding);
1015 
1016       $parsedFrame['language']        = $frame_language;
1017       $parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);
1018 
1019       $timestampindex = 0;
1020       $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1021       while (strlen($frame_remainingdata)) {
1022         $frame_offset = 0;
1023         $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding));
1024         if ($frame_terminatorpos === false) {
1025           $frame_remainingdata = '';
1026         } else {
1027           if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1028             $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1029           }
1030           $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1031 
1032           $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1033           if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
1034             // timestamp probably omitted for first data item
1035           } else {
1036             $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1037             $frame_remainingdata = substr($frame_remainingdata, 4);
1038           }
1039           $timestampindex++;
1040         }
1041       }
1042       unset($parsedFrame['data']);
1043 
1044 
1045     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
1046         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
1047       //   There may be more than one comment frame in each tag,
1048       //   but only one with the same language and content descriptor.
1049       // <Header for 'Comment', ID: 'COMM'>
1050       // Text encoding          $xx
1051       // Language               $xx xx xx
1052       // Short content descrip. <text string according to encoding> $00 (00)
1053       // The actual text        <full text string according to encoding>
1054 
1055       if (strlen($parsedFrame['data']) < 5) {
1056 
1057         $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
1058 
1059       } else {
1060 
1061         $frame_offset = 0;
1062         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1063         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1064           $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1065         }
1066         $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1067         $frame_offset += 3;
1068         $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1069         if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1070           $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1071         }
1072         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1073         if (ord($frame_description) === 0) {
1074           $frame_description = '';
1075         }
1076         $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1077 
1078         $parsedFrame['encodingid']   = $frame_textencoding;
1079         $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1080 
1081         $parsedFrame['language']     = $frame_language;
1082         $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1083         $parsedFrame['description']  = $frame_description;
1084         $parsedFrame['data']         = $frame_text;
1085         if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1086           $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
1087           if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
1088             $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1089           } else {
1090             $info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1091           }
1092         }
1093 
1094       }
1095 
1096     } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1097       //   There may be more than one 'RVA2' frame in each tag,
1098       //   but only one with the same identification string
1099       // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1100       // Identification          <text string> $00
1101       //   The 'identification' string is used to identify the situation and/or
1102       //   device where this adjustment should apply. The following is then
1103       //   repeated for every channel:
1104       // Type of channel         $xx
1105       // Volume adjustment       $xx xx
1106       // Bits representing peak  $xx
1107       // Peak volume             $xx (xx ...)
1108 
1109       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1110       $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1111       if (ord($frame_idstring) === 0) {
1112         $frame_idstring = '';
1113       }
1114       $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1115       $parsedFrame['description'] = $frame_idstring;
1116       $RVA2channelcounter = 0;
1117       while (strlen($frame_remainingdata) >= 5) {
1118         $frame_offset = 0;
1119         $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1120         $parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid;
1121         $parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1122         $parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1123         $frame_offset += 2;
1124         $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1125         if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1126           $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
1127           break;
1128         }
1129         $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1130         $parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1131         $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1132         $RVA2channelcounter++;
1133       }
1134       unset($parsedFrame['data']);
1135 
1136 
1137     } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
1138           (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
1139       //   There may only be one 'RVA' frame in each tag
1140       // <Header for 'Relative volume adjustment', ID: 'RVA'>
1141       // ID3v2.2 => Increment/decrement     %000000ba
1142       // ID3v2.3 => Increment/decrement     %00fedcba
1143       // Bits used for volume descr.        $xx
1144       // Relative volume change, right      $xx xx (xx ...) // a
1145       // Relative volume change, left       $xx xx (xx ...) // b
1146       // Peak volume right                  $xx xx (xx ...)
1147       // Peak volume left                   $xx xx (xx ...)
1148       //   ID3v2.3 only, optional (not present in ID3v2.2):
1149       // Relative volume change, right back $xx xx (xx ...) // c
1150       // Relative volume change, left back  $xx xx (xx ...) // d
1151       // Peak volume right back             $xx xx (xx ...)
1152       // Peak volume left back              $xx xx (xx ...)
1153       //   ID3v2.3 only, optional (not present in ID3v2.2):
1154       // Relative volume change, center     $xx xx (xx ...) // e
1155       // Peak volume center                 $xx xx (xx ...)
1156       //   ID3v2.3 only, optional (not present in ID3v2.2):
1157       // Relative volume change, bass       $xx xx (xx ...) // f
1158       // Peak volume bass                   $xx xx (xx ...)
1159 
1160       $frame_offset = 0;
1161       $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1162       $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1163       $parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1);
1164       $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1165       $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1166       $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1167       if ($parsedFrame['incdec']['right'] === false) {
1168         $parsedFrame['volumechange']['right'] *= -1;
1169       }
1170       $frame_offset += $frame_bytesvolume;
1171       $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1172       if ($parsedFrame['incdec']['left'] === false) {
1173         $parsedFrame['volumechange']['left'] *= -1;
1174       }
1175       $frame_offset += $frame_bytesvolume;
1176       $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1177       $frame_offset += $frame_bytesvolume;
1178       $parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1179       $frame_offset += $frame_bytesvolume;
1180       if ($id3v2_majorversion == 3) {
1181         $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1182         if (strlen($parsedFrame['data']) > 0) {
1183           $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1184           $parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1);
1185           $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1186           if ($parsedFrame['incdec']['rightrear'] === false) {
1187             $parsedFrame['volumechange']['rightrear'] *= -1;
1188           }
1189           $frame_offset += $frame_bytesvolume;
1190           $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1191           if ($parsedFrame['incdec']['leftrear'] === false) {
1192             $parsedFrame['volumechange']['leftrear'] *= -1;
1193           }
1194           $frame_offset += $frame_bytesvolume;
1195           $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1196           $frame_offset += $frame_bytesvolume;
1197           $parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1198           $frame_offset += $frame_bytesvolume;
1199         }
1200         $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1201         if (strlen($parsedFrame['data']) > 0) {
1202           $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1203           $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1204           if ($parsedFrame['incdec']['center'] === false) {
1205             $parsedFrame['volumechange']['center'] *= -1;
1206           }
1207           $frame_offset += $frame_bytesvolume;
1208           $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1209           $frame_offset += $frame_bytesvolume;
1210         }
1211         $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1212         if (strlen($parsedFrame['data']) > 0) {
1213           $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1214           $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1215           if ($parsedFrame['incdec']['bass'] === false) {
1216             $parsedFrame['volumechange']['bass'] *= -1;
1217           }
1218           $frame_offset += $frame_bytesvolume;
1219           $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1220           $frame_offset += $frame_bytesvolume;
1221         }
1222       }
1223       unset($parsedFrame['data']);
1224 
1225 
1226     } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
1227       //   There may be more than one 'EQU2' frame in each tag,
1228       //   but only one with the same identification string
1229       // <Header of 'Equalisation (2)', ID: 'EQU2'>
1230       // Interpolation method  $xx
1231       //   $00  Band
1232       //   $01  Linear
1233       // Identification        <text string> $00
1234       //   The following is then repeated for every adjustment point
1235       // Frequency          $xx xx
1236       // Volume adjustment  $xx xx
1237 
1238       $frame_offset = 0;
1239       $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1240       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1241       $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1242       if (ord($frame_idstring) === 0) {
1243         $frame_idstring = '';
1244       }
1245       $parsedFrame['description'] = $frame_idstring;
1246       $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1247       while (strlen($frame_remainingdata)) {
1248         $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1249         $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1250         $frame_remainingdata = substr($frame_remainingdata, 4);
1251       }
1252       $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1253       unset($parsedFrame['data']);
1254 
1255 
1256     } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
1257         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
1258       //   There may only be one 'EQUA' frame in each tag
1259       // <Header for 'Relative volume adjustment', ID: 'EQU'>
1260       // Adjustment bits    $xx
1261       //   This is followed by 2 bytes + ('adjustment bits' rounded up to the
1262       //   nearest byte) for every equalisation band in the following format,
1263       //   giving a frequency range of 0 - 32767Hz:
1264       // Increment/decrement   %x (MSB of the Frequency)
1265       // Frequency             (lower 15 bits)
1266       // Adjustment            $xx (xx ...)
1267 
1268       $frame_offset = 0;
1269       $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1270       $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1271 
1272       $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1273       while (strlen($frame_remainingdata) > 0) {
1274         $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1275         $frame_incdec    = (bool) substr($frame_frequencystr, 0, 1);
1276         $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1277         $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1278         $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1279         if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1280           $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1281         }
1282         $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1283       }
1284       unset($parsedFrame['data']);
1285 
1286 
1287     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
1288         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
1289       //   There may only be one 'RVRB' frame in each tag.
1290       // <Header for 'Reverb', ID: 'RVRB'>
1291       // Reverb left (ms)                 $xx xx
1292       // Reverb right (ms)                $xx xx
1293       // Reverb bounces, left             $xx
1294       // Reverb bounces, right            $xx
1295       // Reverb feedback, left to left    $xx
1296       // Reverb feedback, left to right   $xx
1297       // Reverb feedback, right to right  $xx
1298       // Reverb feedback, right to left   $xx
1299       // Premix left to right             $xx
1300       // Premix right to left             $xx
1301 
1302       $frame_offset = 0;
1303       $parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1304       $frame_offset += 2;
1305       $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1306       $frame_offset += 2;
1307       $parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1308       $parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1309       $parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1310       $parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1311       $parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1312       $parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1313       $parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1314       $parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1315       unset($parsedFrame['data']);
1316 
1317 
1318     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
1319         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
1320       //   There may be several pictures attached to one file,
1321       //   each in their individual 'APIC' frame, but only one
1322       //   with the same content descriptor
1323       // <Header for 'Attached picture', ID: 'APIC'>
1324       // Text encoding      $xx
1325       // ID3v2.3+ => MIME type          <text string> $00
1326       // ID3v2.2  => Image format       $xx xx xx
1327       // Picture type       $xx
1328       // Description        <text string according to encoding> $00 (00)
1329       // Picture data       <binary data>
1330 
1331       $frame_offset = 0;
1332       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1333       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1334         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1335       }
1336 
1337       if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1338         $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1339         if (strtolower($frame_imagetype) == 'ima') {
1340           // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1341           // MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
1342           $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1343           $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1344           if (ord($frame_mimetype) === 0) {
1345             $frame_mimetype = '';
1346           }
1347           $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1348           if ($frame_imagetype == 'JPEG') {
1349             $frame_imagetype = 'JPG';
1350           }
1351           $frame_offset = $frame_terminatorpos + strlen("\x00");
1352         } else {
1353           $frame_offset += 3;
1354         }
1355       }
1356       if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1357         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1358         $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1359         if (ord($frame_mimetype) === 0) {
1360           $frame_mimetype = '';
1361         }
1362         $frame_offset = $frame_terminatorpos + strlen("\x00");
1363       }
1364 
1365       $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1366 
1367       if ($frame_offset >= $parsedFrame['datalength']) {
1368         $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
1369       } else {
1370         $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1371         if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1372           $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1373         }
1374         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1375         if (ord($frame_description) === 0) {
1376           $frame_description = '';
1377         }
1378         $parsedFrame['encodingid']       = $frame_textencoding;
1379         $parsedFrame['encoding']         = $this->TextEncodingNameLookup($frame_textencoding);
1380 
1381         if ($id3v2_majorversion == 2) {
1382           $parsedFrame['imagetype']    = $frame_imagetype;
1383         } else {
1384           $parsedFrame['mime']         = $frame_mimetype;
1385         }
1386         $parsedFrame['picturetypeid']    = $frame_picturetype;
1387         $parsedFrame['picturetype']      = $this->APICPictureTypeLookup($frame_picturetype);
1388         $parsedFrame['description']      = $frame_description;
1389         $parsedFrame['data']             = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1390         $parsedFrame['datalength']       = strlen($parsedFrame['data']);
1391 
1392         $parsedFrame['image_mime'] = '';
1393         $imageinfo = array();
1394         $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
1395         if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1396           $parsedFrame['image_mime']       = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
1397           if ($imagechunkcheck[0]) {
1398             $parsedFrame['image_width']  = $imagechunkcheck[0];
1399           }
1400           if ($imagechunkcheck[1]) {
1401             $parsedFrame['image_height'] = $imagechunkcheck[1];
1402           }
1403         }
1404 
1405         do {
1406           if ($this->getid3->option_save_attachments === false) {
1407             // skip entirely
1408             unset($parsedFrame['data']);
1409             break;
1410           }
1411           if ($this->getid3->option_save_attachments === true) {
1412             // great
1413 /*
1414           } elseif (is_int($this->getid3->option_save_attachments)) {
1415             if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1416               // too big, skip
1417               $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
1418               unset($parsedFrame['data']);
1419               break;
1420             }
1421 */
1422           } elseif (is_string($this->getid3->option_save_attachments)) {
1423             $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1424             if (!is_dir($dir) || !is_writable($dir)) {
1425               // cannot write, skip
1426               $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)';
1427               unset($parsedFrame['data']);
1428               break;
1429             }
1430           }
1431           // if we get this far, must be OK
1432           if (is_string($this->getid3->option_save_attachments)) {
1433             $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1434             if (!file_exists($destination_filename) || is_writable($destination_filename)) {
1435               file_put_contents($destination_filename, $parsedFrame['data']);
1436             } else {
1437               $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
1438             }
1439             $parsedFrame['data_filename'] = $destination_filename;
1440             unset($parsedFrame['data']);
1441           } else {
1442             if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1443               if (!isset($info['id3v2']['comments']['picture'])) {
1444                 $info['id3v2']['comments']['picture'] = array();
1445               }
1446               $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']);
1447             }
1448           }
1449         } while (false);
1450       }
1451 
1452     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
1453         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
1454       //   There may be more than one 'GEOB' frame in each tag,
1455       //   but only one with the same content descriptor
1456       // <Header for 'General encapsulated object', ID: 'GEOB'>
1457       // Text encoding          $xx
1458       // MIME type              <text string> $00
1459       // Filename               <text string according to encoding> $00 (00)
1460       // Content description    <text string according to encoding> $00 (00)
1461       // Encapsulated object    <binary data>
1462 
1463       $frame_offset = 0;
1464       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1465       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1466         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1467       }
1468       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1469       $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1470       if (ord($frame_mimetype) === 0) {
1471         $frame_mimetype = '';
1472       }
1473       $frame_offset = $frame_terminatorpos + strlen("\x00");
1474 
1475       $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1476       if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1477         $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1478       }
1479       $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1480       if (ord($frame_filename) === 0) {
1481         $frame_filename = '';
1482       }
1483       $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1484 
1485       $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1486       if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1487         $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1488       }
1489       $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1490       if (ord($frame_description) === 0) {
1491         $frame_description = '';
1492       }
1493       $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1494 
1495       $parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
1496       $parsedFrame['encodingid']  = $frame_textencoding;
1497       $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
1498 
1499       $parsedFrame['mime']        = $frame_mimetype;
1500       $parsedFrame['filename']    = $frame_filename;
1501       $parsedFrame['description'] = $frame_description;
1502       unset($parsedFrame['data']);
1503 
1504 
1505     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
1506         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
1507       //   There may only be one 'PCNT' frame in each tag.
1508       //   When the counter reaches all one's, one byte is inserted in
1509       //   front of the counter thus making the counter eight bits bigger
1510       // <Header for 'Play counter', ID: 'PCNT'>
1511       // Counter        $xx xx xx xx (xx ...)
1512 
1513       $parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);
1514 
1515 
1516     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
1517         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
1518       //   There may be more than one 'POPM' frame in each tag,
1519       //   but only one with the same email address
1520       // <Header for 'Popularimeter', ID: 'POPM'>
1521       // Email to user   <text string> $00
1522       // Rating          $xx
1523       // Counter         $xx xx xx xx (xx ...)
1524 
1525       $frame_offset = 0;
1526       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1527       $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1528       if (ord($frame_emailaddress) === 0) {
1529         $frame_emailaddress = '';
1530       }
1531       $frame_offset = $frame_terminatorpos + strlen("\x00");
1532       $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1533       $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1534       $parsedFrame['email']   = $frame_emailaddress;
1535       $parsedFrame['rating']  = $frame_rating;
1536       unset($parsedFrame['data']);
1537 
1538 
1539     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
1540         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
1541       //   There may only be one 'RBUF' frame in each tag
1542       // <Header for 'Recommended buffer size', ID: 'RBUF'>
1543       // Buffer size               $xx xx xx
1544       // Embedded info flag        %0000000x
1545       // Offset to next tag        $xx xx xx xx
1546 
1547       $frame_offset = 0;
1548       $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1549       $frame_offset += 3;
1550 
1551       $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1552       $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1553       $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1554       unset($parsedFrame['data']);
1555 
1556 
1557     } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
1558       //   There may be more than one 'CRM' frame in a tag,
1559       //   but only one with the same 'owner identifier'
1560       // <Header for 'Encrypted meta frame', ID: 'CRM'>
1561       // Owner identifier      <textstring> $00 (00)
1562       // Content/explanation   <textstring> $00 (00)
1563       // Encrypted datablock   <binary data>
1564 
1565       $frame_offset = 0;
1566       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1567       $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1568       $frame_offset = $frame_terminatorpos + strlen("\x00");
1569 
1570       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1571       $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1572       if (ord($frame_description) === 0) {
1573         $frame_description = '';
1574       }
1575       $frame_offset = $frame_terminatorpos + strlen("\x00");
1576 
1577       $parsedFrame['ownerid']     = $frame_ownerid;
1578       $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1579       $parsedFrame['description'] = $frame_description;
1580       unset($parsedFrame['data']);
1581 
1582 
1583     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
1584         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
1585       //   There may be more than one 'AENC' frames in a tag,
1586       //   but only one with the same 'Owner identifier'
1587       // <Header for 'Audio encryption', ID: 'AENC'>
1588       // Owner identifier   <text string> $00
1589       // Preview start      $xx xx
1590       // Preview length     $xx xx
1591       // Encryption info    <binary data>
1592 
1593       $frame_offset = 0;
1594       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1595       $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1596       if (ord($frame_ownerid) === 0) {
1597         $frame_ownerid == '';
1598       }
1599       $frame_offset = $frame_terminatorpos + strlen("\x00");
1600       $parsedFrame['ownerid'] = $frame_ownerid;
1601       $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1602       $frame_offset += 2;
1603       $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1604       $frame_offset += 2;
1605       $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1606       unset($parsedFrame['data']);
1607 
1608 
1609     } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
1610         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {     // 4.22  LNK  Linked information
1611       //   There may be more than one 'LINK' frame in a tag,
1612       //   but only one with the same contents
1613       // <Header for 'Linked information', ID: 'LINK'>
1614       // ID3v2.3+ => Frame identifier   $xx xx xx xx
1615       // ID3v2.2  => Frame identifier   $xx xx xx
1616       // URL                            <text string> $00
1617       // ID and additional data         <text string(s)>
1618 
1619       $frame_offset = 0;
1620       if ($id3v2_majorversion == 2) {
1621         $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1622         $frame_offset += 3;
1623       } else {
1624         $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1625         $frame_offset += 4;
1626       }
1627 
1628       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1629       $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1630       if (ord($frame_url) === 0) {
1631         $frame_url = '';
1632       }
1633       $frame_offset = $frame_terminatorpos + strlen("\x00");
1634       $parsedFrame['url'] = $frame_url;
1635 
1636       $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1637       if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1638         $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']);
1639       }
1640       unset($parsedFrame['data']);
1641 
1642 
1643     } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
1644       //   There may only be one 'POSS' frame in each tag
1645       // <Head for 'Position synchronisation', ID: 'POSS'>
1646       // Time stamp format         $xx
1647       // Position                  $xx (xx ...)
1648 
1649       $frame_offset = 0;
1650       $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1651       $parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1652       unset($parsedFrame['data']);
1653 
1654 
1655     } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
1656       //   There may be more than one 'Terms of use' frame in a tag,
1657       //   but only one with the same 'Language'
1658       // <Header for 'Terms of use frame', ID: 'USER'>
1659       // Text encoding        $xx
1660       // Language             $xx xx xx
1661       // The actual text      <text string according to encoding>
1662 
1663       $frame_offset = 0;
1664       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1665       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1666         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1667       }
1668       $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1669       $frame_offset += 3;
1670       $parsedFrame['language']     = $frame_language;
1671       $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1672       $parsedFrame['encodingid']   = $frame_textencoding;
1673       $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1674 
1675       $parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
1676       if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1677         $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1678       }
1679       unset($parsedFrame['data']);
1680 
1681 
1682     } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
1683       //   There may only be one 'OWNE' frame in a tag
1684       // <Header for 'Ownership frame', ID: 'OWNE'>
1685       // Text encoding     $xx
1686       // Price paid        <text string> $00
1687       // Date of purch.    <text string>
1688       // Seller            <text string according to encoding>
1689 
1690       $frame_offset = 0;
1691       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1692       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1693         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1694       }
1695       $parsedFrame['encodingid'] = $frame_textencoding;
1696       $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
1697 
1698       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1699       $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1700       $frame_offset = $frame_terminatorpos + strlen("\x00");
1701 
1702       $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1703       $parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1704       $parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);
1705 
1706       $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1707       if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1708         $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1709       }
1710       $frame_offset += 8;
1711 
1712       $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1713       unset($parsedFrame['data']);
1714 
1715 
1716     } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
1717       //   There may be more than one 'commercial frame' in a tag,
1718       //   but no two may be identical
1719       // <Header for 'Commercial frame', ID: 'COMR'>
1720       // Text encoding      $xx
1721       // Price string       <text string> $00
1722       // Valid until        <text string>
1723       // Contact URL        <text string> $00
1724       // Received as        $xx
1725       // Name of seller     <text string according to encoding> $00 (00)
1726       // Description        <text string according to encoding> $00 (00)
1727       // Picture MIME type  <string> $00
1728       // Seller logo        <binary data>
1729 
1730       $frame_offset = 0;
1731       $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1732       if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1733         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1734       }
1735 
1736       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1737       $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1738       $frame_offset = $frame_terminatorpos + strlen("\x00");
1739       $frame_rawpricearray = explode('/', $frame_pricestring);
1740       foreach ($frame_rawpricearray as $key => $val) {
1741         $frame_currencyid = substr($val, 0, 3);
1742         $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1743         $parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3);
1744       }
1745 
1746       $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1747       $frame_offset += 8;
1748 
1749       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1750       $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1751       $frame_offset = $frame_terminatorpos + strlen("\x00");
1752 
1753       $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1754 
1755       $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1756       if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1757         $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1758       }
1759       $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1760       if (ord($frame_sellername) === 0) {
1761         $frame_sellername = '';
1762       }
1763       $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1764 
1765       $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1766       if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1767         $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1768       }
1769       $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1770       if (ord($frame_description) === 0) {
1771         $frame_description = '';
1772       }
1773       $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1774 
1775       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1776       $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1777       $frame_offset = $frame_terminatorpos + strlen("\x00");
1778 
1779       $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1780 
1781       $parsedFrame['encodingid']        = $frame_textencoding;
1782       $parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);
1783 
1784       $parsedFrame['pricevaliduntil']   = $frame_datestring;
1785       $parsedFrame['contacturl']        = $frame_contacturl;
1786       $parsedFrame['receivedasid']      = $frame_receivedasid;
1787       $parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
1788       $parsedFrame['sellername']        = $frame_sellername;
1789       $parsedFrame['description']       = $frame_description;
1790       $parsedFrame['mime']              = $frame_mimetype;
1791       $parsedFrame['logo']              = $frame_sellerlogo;
1792       unset($parsedFrame['data']);
1793 
1794 
1795     } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
1796       //   There may be several 'ENCR' frames in a tag,
1797       //   but only one containing the same symbol
1798       //   and only one containing the same owner identifier
1799       // <Header for 'Encryption method registration', ID: 'ENCR'>
1800       // Owner identifier    <text string> $00
1801       // Method symbol       $xx
1802       // Encryption data     <binary data>
1803 
1804       $frame_offset = 0;
1805       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1806       $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1807       if (ord($frame_ownerid) === 0) {
1808         $frame_ownerid = '';
1809       }
1810       $frame_offset = $frame_terminatorpos + strlen("\x00");
1811 
1812       $parsedFrame['ownerid']      = $frame_ownerid;
1813       $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1814       $parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
1815 
1816 
1817     } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)
1818 
1819       //   There may be several 'GRID' frames in a tag,
1820       //   but only one containing the same symbol
1821       //   and only one containing the same owner identifier
1822       // <Header for 'Group ID registration', ID: 'GRID'>
1823       // Owner identifier      <text string> $00
1824       // Group symbol          $xx
1825       // Group dependent data  <binary data>
1826 
1827       $frame_offset = 0;
1828       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1829       $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1830       if (ord($frame_ownerid) === 0) {
1831         $frame_ownerid = '';
1832       }
1833       $frame_offset = $frame_terminatorpos + strlen("\x00");
1834 
1835       $parsedFrame['ownerid']       = $frame_ownerid;
1836       $parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1837       $parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);
1838 
1839 
1840     } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
1841       //   The tag may contain more than one 'PRIV' frame
1842       //   but only with different contents
1843       // <Header for 'Private frame', ID: 'PRIV'>
1844       // Owner identifier      <text string> $00
1845       // The private data      <binary data>
1846 
1847       $frame_offset = 0;
1848       $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1849       $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1850       if (ord($frame_ownerid) === 0) {
1851         $frame_ownerid = '';
1852       }
1853       $frame_offset = $frame_terminatorpos + strlen("\x00");
1854 
1855       $parsedFrame['ownerid'] = $frame_ownerid;
1856       $parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);
1857 
1858 
1859     } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
1860       //   There may be more than one 'signature frame' in a tag,
1861       //   but no two may be identical
1862       // <Header for 'Signature frame', ID: 'SIGN'>
1863       // Group symbol      $xx
1864       // Signature         <binary data>
1865 
1866       $frame_offset = 0;
1867       $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1868       $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1869 
1870 
1871     } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
1872       //   There may only be one 'seek frame' in a tag
1873       // <Header for 'Seek frame', ID: 'SEEK'>
1874       // Minimum offset to next tag       $xx xx xx xx
1875 
1876       $frame_offset = 0;
1877       $parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1878 
1879 
1880     } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
1881       //   There may only be one 'audio seek point index' frame in a tag
1882       // <Header for 'Seek Point Index', ID: 'ASPI'>
1883       // Indexed data start (S)         $xx xx xx xx
1884       // Indexed data length (L)        $xx xx xx xx
1885       // Number of index points (N)     $xx xx
1886       // Bits per index point (b)       $xx
1887       //   Then for every index point the following data is included:
1888       // Fraction at index (Fi)          $xx (xx)
1889 
1890       $frame_offset = 0;
1891       $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1892       $frame_offset += 4;
1893       $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1894       $frame_offset += 4;
1895       $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1896       $frame_offset += 2;
1897       $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1898       $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1899       for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
1900         $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1901         $frame_offset += $frame_bytesperpoint;
1902       }
1903       unset($parsedFrame['data']);
1904 
1905     } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1906       // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1907       //   There may only be one 'RGAD' frame in a tag
1908       // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1909       // Peak Amplitude                      $xx $xx $xx $xx
1910       // Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
1911       // Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
1912       //   a - name code
1913       //   b - originator code
1914       //   c - sign bit
1915       //   d - replay gain adjustment
1916 
1917       $frame_offset = 0;
1918       $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1919       $frame_offset += 4;
1920       $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1921       $frame_offset += 2;
1922       $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1923       $frame_offset += 2;
1924       $parsedFrame['raw']['track']['name']       = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1925       $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1926       $parsedFrame['raw']['track']['signbit']    = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1927       $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1928       $parsedFrame['raw']['album']['name']       = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1929       $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1930       $parsedFrame['raw']['album']['signbit']    = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
1931       $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
1932       $parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1933       $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1934       $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1935       $parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1936       $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1937       $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1938 
1939       $info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude'];
1940       $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1941       $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1942       $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1943       $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1944 
1945       unset($parsedFrame['data']);
1946 
1947     }
1948 
1949     return true;
1950   }
1951 
1952 
1953   public function DeUnsynchronise($data) {
1954     return str_replace("\xFF\x00", "\xFF", $data);
1955   }
1956 
1957   public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
1958     static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
1959       0x00 => 'No more than 128 frames and 1 MB total tag size',
1960       0x01 => 'No more than 64 frames and 128 KB total tag size',
1961       0x02 => 'No more than 32 frames and 40 KB total tag size',
1962       0x03 => 'No more than 32 frames and 4 KB total tag size',
1963     );
1964     return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
1965   }
1966 
1967   public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
1968     static $LookupExtendedHeaderRestrictionsTextEncodings = array(
1969       0x00 => 'No restrictions',
1970       0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
1971     );
1972     return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
1973   }
1974 
1975   public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
1976     static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
1977       0x00 => 'No restrictions',
1978       0x01 => 'No string is longer than 1024 characters',
1979       0x02 => 'No string is longer than 128 characters',
1980       0x03 => 'No string is longer than 30 characters',
1981     );
1982     return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
1983   }
1984 
1985   public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
1986     static $LookupExtendedHeaderRestrictionsImageEncoding = array(
1987       0x00 => 'No restrictions',
1988       0x01 => 'Images are encoded only with PNG or JPEG',
1989     );
1990     return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
1991   }
1992 
1993   public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
1994     static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
1995       0x00 => 'No restrictions',
1996       0x01 => 'All images are 256x256 pixels or smaller',
1997       0x02 => 'All images are 64x64 pixels or smaller',
1998       0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
1999     );
2000     return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
2001   }
2002 
2003   public function LookupCurrencyUnits($currencyid) {
2004 
2005     $begin = __LINE__;
2006 
2007     /** This is not a comment!
2008 
2009 
2010       AED Dirhams
2011       AFA Afghanis
2012       ALL Leke
2013       AMD Drams
2014       ANG Guilders
2015       AOA Kwanza
2016       ARS Pesos
2017       ATS Schillings
2018       AUD Dollars
2019       AWG Guilders
2020       AZM Manats
2021       BAM Convertible Marka
2022       BBD Dollars
2023       BDT Taka
2024       BEF Francs
2025       BGL Leva
2026       BHD Dinars
2027       BIF Francs
2028       BMD Dollars
2029       BND Dollars
2030       BOB Bolivianos
2031       BRL Brazil Real
2032       BSD Dollars
2033       BTN Ngultrum
2034       BWP Pulas
2035       BYR Rubles
2036       BZD Dollars
2037       CAD Dollars
2038       CDF Congolese Francs
2039       CHF Francs
2040       CLP Pesos
2041       CNY Yuan Renminbi
2042       COP Pesos
2043       CRC Colones
2044       CUP Pesos
2045       CVE Escudos
2046       CYP Pounds
2047       CZK Koruny
2048       DEM Deutsche Marks
2049       DJF Francs
2050       DKK Kroner
2051       DOP Pesos
2052       DZD Algeria Dinars
2053       EEK Krooni
2054       EGP Pounds
2055       ERN Nakfa
2056       ESP Pesetas
2057       ETB Birr
2058       EUR Euro
2059       FIM Markkaa
2060       FJD Dollars
2061       FKP Pounds
2062       FRF Francs
2063       GBP Pounds
2064       GEL Lari
2065       GGP Pounds
2066       GHC Cedis
2067       GIP Pounds
2068       GMD Dalasi
2069       GNF Francs
2070       GRD Drachmae
2071       GTQ Quetzales
2072       GYD Dollars
2073       HKD Dollars
2074       HNL Lempiras
2075       HRK Kuna
2076       HTG Gourdes
2077       HUF Forints
2078       IDR Rupiahs
2079       IEP Pounds
2080       ILS New Shekels
2081       IMP Pounds
2082       INR Rupees
2083       IQD Dinars
2084       IRR Rials
2085       ISK Kronur
2086       ITL Lire
2087       JEP Pounds
2088       JMD Dollars
2089       JOD Dinars
2090       JPY Yen
2091       KES Shillings
2092       KGS Soms
2093       KHR Riels
2094       KMF Francs
2095       KPW Won
2096       KWD Dinars
2097       KYD Dollars
2098       KZT Tenge
2099       LAK Kips
2100       LBP Pounds
2101       LKR Rupees
2102       LRD Dollars
2103       LSL Maloti
2104       LTL Litai
2105       LUF Francs
2106       LVL Lati
2107       LYD Dinars
2108       MAD Dirhams
2109       MDL Lei
2110       MGF Malagasy Francs
2111       MKD Denars
2112       MMK Kyats
2113       MNT Tugriks
2114       MOP Patacas
2115       MRO Ouguiyas
2116       MTL Liri
2117       MUR Rupees
2118       MVR Rufiyaa
2119       MWK Kwachas
2120       MXN Pesos
2121       MYR Ringgits
2122       MZM Meticais
2123       NAD Dollars
2124       NGN Nairas
2125       NIO Gold Cordobas
2126       NLG Guilders
2127       NOK Krone
2128       NPR Nepal Rupees
2129       NZD Dollars
2130       OMR Rials
2131       PAB Balboa
2132       PEN Nuevos Soles
2133       PGK Kina
2134       PHP Pesos
2135       PKR Rupees
2136       PLN Zlotych
2137       PTE Escudos
2138       PYG Guarani
2139       QAR Rials
2140       ROL Lei
2141       RUR Rubles
2142       RWF Rwanda Francs
2143       SAR Riyals
2144       SBD Dollars
2145       SCR Rupees
2146       SDD Dinars
2147       SEK Kronor
2148       SGD Dollars
2149       SHP Pounds
2150       SIT Tolars
2151       SKK Koruny
2152       SLL Leones
2153       SOS Shillings
2154       SPL Luigini
2155       SRG Guilders
2156       STD Dobras
2157       SVC Colones
2158       SYP Pounds
2159       SZL Emalangeni
2160       THB Baht
2161       TJR Rubles
2162       TMM Manats
2163       TND Dinars
2164       TOP Pa'anga
2165       TRL Liras
2166       TTD Dollars
2167       TVD Tuvalu Dollars
2168       TWD New Dollars
2169       TZS Shillings
2170       UAH Hryvnia
2171       UGX Shillings
2172       USD Dollars
2173       UYU Pesos
2174       UZS Sums
2175       VAL Lire
2176       VEB Bolivares
2177       VND Dong
2178       VUV Vatu
2179       WST Tala
2180       XAF Francs
2181       XAG Ounces
2182       XAU Ounces
2183       XCD Dollars
2184       XDR Special Drawing Rights
2185       XPD Ounces
2186       XPF Francs
2187       XPT Ounces
2188       YER Rials
2189       YUM New Dinars
2190       ZAR Rand
2191       ZMK Kwacha
2192       ZWD Zimbabwe Dollars
2193 
2194     */
2195 
2196     return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2197   }
2198 
2199 
2200   public function LookupCurrencyCountry($currencyid) {
2201 
2202     $begin = __LINE__;
2203 
2204     /** This is not a comment!
2205 
2206       AED United Arab Emirates
2207       AFA Afghanistan
2208       ALL Albania
2209       AMD Armenia
2210       ANG Netherlands Antilles
2211       AOA Angola
2212       ARS Argentina
2213       ATS Austria
2214       AUD Australia
2215       AWG Aruba
2216       AZM Azerbaijan
2217       BAM Bosnia and Herzegovina
2218       BBD Barbados
2219       BDT Bangladesh
2220       BEF Belgium
2221       BGL Bulgaria
2222       BHD Bahrain
2223       BIF Burundi
2224       BMD Bermuda
2225       BND Brunei Darussalam
2226       BOB Bolivia
2227       BRL Brazil
2228       BSD Bahamas
2229       BTN Bhutan
2230       BWP Botswana
2231       BYR Belarus
2232       BZD Belize
2233       CAD Canada
2234       CDF Congo/Kinshasa
2235       CHF Switzerland
2236       CLP Chile
2237       CNY China
2238       COP Colombia
2239       CRC Costa Rica
2240       CUP Cuba
2241       CVE Cape Verde
2242       CYP Cyprus
2243       CZK Czech Republic
2244       DEM Germany
2245       DJF Djibouti
2246       DKK Denmark
2247       DOP Dominican Republic
2248       DZD Algeria
2249       EEK Estonia
2250       EGP Egypt
2251       ERN Eritrea
2252       ESP Spain
2253       ETB Ethiopia
2254       EUR Euro Member Countries
2255       FIM Finland
2256       FJD Fiji
2257       FKP Falkland Islands (Malvinas)
2258       FRF France
2259       GBP United Kingdom
2260       GEL Georgia
2261       GGP Guernsey
2262       GHC Ghana
2263       GIP Gibraltar
2264       GMD Gambia
2265       GNF Guinea
2266       GRD Greece
2267       GTQ Guatemala
2268       GYD Guyana
2269       HKD Hong Kong
2270       HNL Honduras
2271       HRK Croatia
2272       HTG Haiti
2273       HUF Hungary
2274       IDR Indonesia
2275       IEP Ireland (Eire)
2276       ILS Israel
2277       IMP Isle of Man
2278       INR India
2279       IQD Iraq
2280       IRR Iran
2281       ISK Iceland
2282       ITL Italy
2283       JEP Jersey
2284       JMD Jamaica
2285       JOD Jordan
2286       JPY Japan
2287       KES Kenya
2288       KGS Kyrgyzstan
2289       KHR Cambodia
2290       KMF Comoros
2291       KPW Korea
2292       KWD Kuwait
2293       KYD Cayman Islands
2294       KZT Kazakstan
2295       LAK Laos
2296       LBP Lebanon
2297       LKR Sri Lanka
2298       LRD Liberia
2299       LSL Lesotho
2300       LTL Lithuania
2301       LUF Luxembourg
2302       LVL Latvia
2303       LYD Libya
2304       MAD Morocco
2305       MDL Moldova
2306       MGF Madagascar
2307       MKD Macedonia
2308       MMK Myanmar (Burma)
2309       MNT Mongolia
2310       MOP Macau
2311       MRO Mauritania
2312       MTL Malta
2313       MUR Mauritius
2314       MVR Maldives (Maldive Islands)
2315       MWK Malawi
2316       MXN Mexico
2317       MYR Malaysia
2318       MZM Mozambique
2319       NAD Namibia
2320       NGN Nigeria
2321       NIO Nicaragua
2322       NLG Netherlands (Holland)
2323       NOK Norway
2324       NPR Nepal
2325       NZD New Zealand
2326       OMR Oman
2327       PAB Panama
2328       PEN Peru
2329       PGK Papua New Guinea
2330       PHP Philippines
2331       PKR Pakistan
2332       PLN Poland
2333       PTE Portugal
2334       PYG Paraguay
2335       QAR Qatar
2336       ROL Romania
2337       RUR Russia
2338       RWF Rwanda
2339       SAR Saudi Arabia
2340       SBD Solomon Islands
2341       SCR Seychelles
2342       SDD Sudan
2343       SEK Sweden
2344       SGD Singapore
2345       SHP Saint Helena
2346       SIT Slovenia
2347       SKK Slovakia
2348       SLL Sierra Leone
2349       SOS Somalia
2350       SPL Seborga
2351       SRG Suriname
2352       STD São Tome and Principe
2353       SVC El Salvador
2354       SYP Syria
2355       SZL Swaziland
2356       THB Thailand
2357       TJR Tajikistan
2358       TMM Turkmenistan
2359       TND Tunisia
2360       TOP Tonga
2361       TRL Turkey
2362       TTD Trinidad and Tobago
2363       TVD Tuvalu
2364       TWD Taiwan
2365       TZS Tanzania
2366       UAH Ukraine
2367       UGX Uganda
2368       USD United States of America
2369       UYU Uruguay
2370       UZS Uzbekistan
2371       VAL Vatican City
2372       VEB Venezuela
2373       VND Viet Nam
2374       VUV Vanuatu
2375       WST Samoa
2376       XAF Communauté Financière Africaine
2377       XAG Silver
2378       XAU Gold
2379       XCD East Caribbean
2380       XDR International Monetary Fund
2381       XPD Palladium
2382       XPF Comptoirs Français du Pacifique
2383       XPT Platinum
2384       YER Yemen
2385       YUM Yugoslavia
2386       ZAR South Africa
2387       ZMK Zambia
2388       ZWD Zimbabwe
2389 
2390     */
2391 
2392     return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2393   }
2394 
2395 
2396 
2397   public static function LanguageLookup($languagecode, $casesensitive=false) {
2398 
2399     if (!$casesensitive) {
2400       $languagecode = strtolower($languagecode);
2401     }
2402 
2403     // http://www.id3.org/id3v2.4.0-structure.txt
2404     // [4.   ID3v2 frame overview]
2405     // The three byte language field, present in several frames, is used to
2406     // describe the language of the frame's content, according to ISO-639-2
2407     // [ISO-639-2]. The language should be represented in lower case. If the
2408     // language is not known the string "XXX" should be used.
2409 
2410 
2411     // ISO 639-2 - http://www.id3.org/iso639-2.html
2412 
2413     $begin = __LINE__;
2414 
2415     /** This is not a comment!
2416 
2417       XXX unknown
2418       xxx unknown
2419       aar Afar
2420       abk Abkhazian
2421       ace Achinese
2422       ach Acoli
2423       ada Adangme
2424       afa Afro-Asiatic (Other)
2425       afh Afrihili
2426       afr Afrikaans
2427       aka Akan
2428       akk Akkadian
2429       alb Albanian
2430       ale Aleut
2431       alg Algonquian Languages
2432       amh Amharic
2433       ang English, Old (ca. 450-1100)
2434       apa Apache Languages
2435       ara Arabic
2436       arc Aramaic
2437       arm Armenian
2438       arn Araucanian
2439       arp Arapaho
2440       art Artificial (Other)
2441       arw Arawak
2442       asm Assamese
2443       ath Athapascan Languages
2444       ava Avaric
2445       ave Avestan
2446       awa Awadhi
2447       aym Aymara
2448       aze Azerbaijani
2449       bad Banda
2450       bai Bamileke Languages
2451       bak Bashkir
2452       bal Baluchi
2453       bam Bambara
2454       ban Balinese
2455       baq Basque
2456       bas Basa
2457       bat Baltic (Other)
2458       bej Beja
2459       bel Byelorussian
2460       bem Bemba
2461       ben Bengali
2462       ber Berber (Other)
2463       bho Bhojpuri
2464       bih Bihari
2465       bik Bikol
2466       bin Bini
2467       bis Bislama
2468       bla Siksika
2469       bnt Bantu (Other)
2470       bod Tibetan
2471       bra Braj
2472       bre Breton
2473       bua Buriat
2474       bug Buginese
2475       bul Bulgarian
2476       bur Burmese
2477       cad Caddo
2478       cai Central American Indian (Other)
2479       car Carib
2480       cat Catalan
2481       cau Caucasian (Other)
2482       ceb Cebuano
2483       cel Celtic (Other)
2484       ces Czech
2485       cha Chamorro
2486       chb Chibcha
2487       che Chechen
2488       chg Chagatai
2489       chi Chinese
2490       chm Mari
2491       chn Chinook jargon
2492       cho Choctaw
2493       chr Cherokee
2494       chu Church Slavic
2495       chv Chuvash
2496       chy Cheyenne
2497       cop Coptic
2498       cor Cornish
2499       cos Corsican
2500       cpe Creoles and Pidgins, English-based (Other)
2501       cpf Creoles and Pidgins, French-based (Other)
2502       cpp Creoles and Pidgins, Portuguese-based (Other)
2503       cre Cree
2504       crp Creoles and Pidgins (Other)
2505       cus Cushitic (Other)
2506       cym Welsh
2507       cze Czech
2508       dak Dakota
2509       dan Danish
2510       del Delaware
2511       deu German
2512       din Dinka
2513       div Divehi
2514       doi Dogri
2515       dra Dravidian (Other)
2516       dua Duala
2517       dum Dutch, Middle (ca. 1050-1350)
2518       dut Dutch
2519       dyu Dyula
2520       dzo Dzongkha
2521       efi Efik
2522       egy Egyptian (Ancient)
2523       eka Ekajuk
2524       ell Greek, Modern (1453-)
2525       elx Elamite
2526       eng English
2527       enm English, Middle (ca. 1100-1500)
2528       epo Esperanto
2529       esk Eskimo (Other)
2530       esl Spanish
2531       est Estonian
2532       eus Basque
2533       ewe Ewe
2534       ewo Ewondo
2535       fan Fang
2536       fao Faroese
2537       fas Persian
2538       fat Fanti
2539       fij Fijian
2540       fin Finnish
2541       fiu Finno-Ugrian (Other)
2542       fon Fon
2543       fra French
2544       fre French
2545       frm French, Middle (ca. 1400-1600)
2546       fro French, Old (842- ca. 1400)
2547       fry Frisian
2548       ful Fulah
2549       gaa Ga
2550       gae Gaelic (Scots)
2551       gai Irish
2552       gay Gayo
2553       gdh Gaelic (Scots)
2554       gem Germanic (Other)
2555       geo Georgian
2556       ger German
2557       gez Geez
2558       gil Gilbertese
2559       glg Gallegan
2560       gmh German, Middle High (ca. 1050-1500)
2561       goh German, Old High (ca. 750-1050)
2562       gon Gondi
2563       got Gothic
2564       grb Grebo
2565       grc Greek, Ancient (to 1453)
2566       gre Greek, Modern (1453-)
2567       grn Guarani
2568       guj Gujarati
2569       hai Haida
2570       hau Hausa
2571       haw Hawaiian
2572       heb Hebrew
2573       her Herero
2574       hil Hiligaynon
2575       him Himachali
2576       hin Hindi
2577       hmo Hiri Motu
2578       hun Hungarian
2579       hup Hupa
2580       hye Armenian
2581       iba Iban
2582       ibo Igbo
2583       ice Icelandic
2584       ijo Ijo
2585       iku Inuktitut
2586       ilo Iloko
2587       ina Interlingua (International Auxiliary language Association)
2588       inc Indic (Other)
2589       ind Indonesian
2590       ine Indo-European (Other)
2591       ine Interlingue
2592       ipk Inupiak
2593       ira Iranian (Other)
2594       iri Irish
2595       iro Iroquoian uages
2596       isl Icelandic
2597       ita Italian
2598       jav Javanese
2599       jaw Javanese
2600       jpn Japanese
2601       jpr Judeo-Persian
2602       jrb Judeo-Arabic
2603       kaa Kara-Kalpak
2604       kab Kabyle
2605       kac Kachin
2606       kal Greenlandic
2607       kam Kamba
2608       kan Kannada
2609       kar Karen
2610       kas Kashmiri
2611       kat Georgian
2612       kau Kanuri
2613       kaw Kawi
2614       kaz Kazakh
2615       kha Khasi
2616       khi Khoisan (Other)
2617       khm Khmer
2618       kho Khotanese
2619       kik Kikuyu
2620       kin Kinyarwanda
2621       kir Kirghiz
2622       kok Konkani
2623       kom Komi
2624       kon Kongo
2625       kor Korean
2626       kpe Kpelle
2627       kro Kru
2628       kru Kurukh
2629       kua Kuanyama
2630       kum Kumyk
2631       kur Kurdish
2632       kus Kusaie
2633       kut Kutenai
2634       lad Ladino
2635       lah Lahnda
2636       lam Lamba
2637       lao Lao
2638       lat Latin
2639       lav Latvian
2640       lez Lezghian
2641       lin Lingala
2642       lit Lithuanian
2643       lol Mongo
2644       loz Lozi
2645       ltz Letzeburgesch
2646       lub Luba-Katanga
2647       lug Ganda
2648       lui Luiseno
2649       lun Lunda
2650       luo Luo (Kenya and Tanzania)
2651       mac Macedonian
2652       mad Madurese
2653       mag Magahi
2654       mah Marshall
2655       mai Maithili
2656       mak Macedonian
2657       mak Makasar
2658       mal Malayalam
2659       man Mandingo
2660       mao Maori
2661       map Austronesian (Other)
2662       mar Marathi
2663       mas Masai
2664       max Manx
2665       may Malay
2666       men Mende
2667       mga Irish, Middle (900 - 1200)
2668       mic Micmac
2669       min Minangkabau
2670       mis Miscellaneous (Other)
2671       mkh Mon-Kmer (Other)
2672       mlg Malagasy
2673       mlt Maltese
2674       mni Manipuri
2675       mno Manobo Languages
2676       moh Mohawk
2677       mol Moldavian
2678       mon Mongolian
2679       mos Mossi
2680       mri Maori
2681       msa Malay
2682       mul Multiple Languages
2683       mun Munda Languages
2684       mus Creek
2685       mwr Marwari
2686       mya Burmese
2687       myn Mayan Languages
2688       nah Aztec
2689       nai North American Indian (Other)
2690       nau Nauru
2691       nav Navajo
2692       nbl Ndebele, South
2693       nde Ndebele, North
2694       ndo Ndongo
2695       nep Nepali
2696       new Newari
2697       nic Niger-Kordofanian (Other)
2698       niu Niuean
2699       nla Dutch
2700       nno Norwegian (Nynorsk)
2701       non Norse, Old
2702       nor Norwegian
2703       nso Sotho, Northern
2704       nub Nubian Languages
2705       nya Nyanja
2706       nym Nyamwezi
2707       nyn Nyankole
2708       nyo Nyoro
2709       nzi Nzima
2710       oci Langue d'Oc (post 1500)
2711       oji Ojibwa
2712       ori Oriya
2713       orm Oromo
2714       osa Osage
2715       oss Ossetic
2716       ota Turkish, Ottoman (1500 - 1928)
2717       oto Otomian Languages
2718       paa Papuan-Australian (Other)
2719       pag Pangasinan
2720       pal Pahlavi
2721       pam Pampanga
2722       pan Panjabi
2723       pap Papiamento
2724       pau Palauan
2725       peo Persian, Old (ca 600 - 400 B.C.)
2726       per Persian
2727       phn Phoenician
2728       pli Pali
2729       pol Polish
2730       pon Ponape
2731       por Portuguese
2732       pra Prakrit uages
2733       pro Provencal, Old (to 1500)
2734       pus Pushto
2735       que Quechua
2736       raj Rajasthani
2737       rar Rarotongan
2738       roa Romance (Other)
2739       roh Rhaeto-Romance
2740       rom Romany
2741       ron Romanian
2742       rum Romanian
2743       run Rundi
2744       rus Russian
2745       sad Sandawe
2746       sag Sango
2747       sah Yakut
2748       sai South American Indian (Other)
2749       sal Salishan Languages
2750       sam Samaritan Aramaic
2751       san Sanskrit
2752       sco Scots
2753       scr Serbo-Croatian
2754       sel Selkup
2755       sem Semitic (Other)
2756       sga Irish, Old (to 900)
2757       shn Shan
2758       sid Sidamo
2759       sin Singhalese
2760       sio Siouan Languages
2761       sit Sino-Tibetan (Other)
2762       sla Slavic (Other)
2763       slk Slovak
2764       slo Slovak
2765       slv Slovenian
2766       smi Sami Languages
2767       smo Samoan
2768       sna Shona
2769       snd Sindhi
2770       sog Sogdian
2771       som Somali
2772       son Songhai
2773       sot Sotho, Southern
2774       spa Spanish
2775       sqi Albanian
2776       srd Sardinian
2777       srr Serer
2778       ssa Nilo-Saharan (Other)
2779       ssw Siswant
2780       ssw Swazi
2781       suk Sukuma
2782       sun Sudanese
2783       sus Susu
2784       sux Sumerian
2785       sve Swedish
2786       swa Swahili
2787       swe Swedish
2788       syr Syriac
2789       tah Tahitian
2790       tam Tamil
2791       tat Tatar
2792       tel Telugu
2793       tem Timne
2794       ter Tereno
2795       tgk Tajik
2796       tgl Tagalog
2797       tha Thai
2798       tib Tibetan
2799       tig Tigre
2800       tir Tigrinya
2801       tiv Tivi
2802       tli Tlingit
2803       tmh Tamashek
2804       tog Tonga (Nyasa)
2805       ton Tonga (Tonga Islands)
2806       tru Truk
2807       tsi Tsimshian
2808       tsn Tswana
2809       tso Tsonga
2810       tuk Turkmen
2811       tum Tumbuka
2812       tur Turkish
2813       tut Altaic (Other)
2814       twi Twi
2815       tyv Tuvinian
2816       uga Ugaritic
2817       uig Uighur
2818       ukr Ukrainian
2819       umb Umbundu
2820       und Undetermined
2821       urd Urdu
2822       uzb Uzbek
2823       vai Vai
2824       ven Venda
2825       vie Vietnamese
2826       vol Volapük
2827       vot Votic
2828       wak Wakashan Languages
2829       wal Walamo
2830       war Waray
2831       was Washo
2832       wel Welsh
2833       wen Sorbian Languages
2834       wol Wolof
2835       xho Xhosa
2836       yao Yao
2837       yap Yap
2838       yid Yiddish
2839       yor Yoruba
2840       zap Zapotec
2841       zen Zenaga
2842       zha Zhuang
2843       zho Chinese
2844       zul Zulu
2845       zun Zuni
2846 
2847     */
2848 
2849     return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
2850   }
2851 
2852 
2853   public static function ETCOEventLookup($index) {
2854     if (($index >= 0x17) && ($index <= 0xDF)) {
2855       return 'reserved for future use';
2856     }
2857     if (($index >= 0xE0) && ($index <= 0xEF)) {
2858       return 'not predefined synch 0-F';
2859     }
2860     if (($index >= 0xF0) && ($index <= 0xFC)) {
2861       return 'reserved for future use';
2862     }
2863 
2864     static $EventLookup = array(
2865       0x00 => 'padding (has no meaning)',
2866       0x01 => 'end of initial silence',
2867       0x02 => 'intro start',
2868       0x03 => 'main part start',
2869       0x04 => 'outro start',
2870       0x05 => 'outro end',
2871       0x06 => 'verse start',
2872       0x07 => 'refrain start',
2873       0x08 => 'interlude start',
2874       0x09 => 'theme start',
2875       0x0A => 'variation start',
2876       0x0B => 'key change',
2877       0x0C => 'time change',
2878       0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
2879       0x0E => 'sustained noise',
2880       0x0F => 'sustained noise end',
2881       0x10 => 'intro end',
2882       0x11 => 'main part end',
2883       0x12 => 'verse end',
2884       0x13 => 'refrain end',
2885       0x14 => 'theme end',
2886       0x15 => 'profanity',
2887       0x16 => 'profanity end',
2888       0xFD => 'audio end (start of silence)',
2889       0xFE => 'audio file ends',
2890       0xFF => 'one more byte of events follows'
2891     );
2892 
2893     return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
2894   }
2895 
2896   public static function SYTLContentTypeLookup($index) {
2897     static $SYTLContentTypeLookup = array(
2898       0x00 => 'other',
2899       0x01 => 'lyrics',
2900       0x02 => 'text transcription',
2901       0x03 => 'movement/part name', // (e.g. 'Adagio')
2902       0x04 => 'events',             // (e.g. 'Don Quijote enters the stage')
2903       0x05 => 'chord',              // (e.g. 'Bb F Fsus')
2904       0x06 => 'trivia/\'pop up\' information',
2905       0x07 => 'URLs to webpages',
2906       0x08 => 'URLs to images'
2907     );
2908 
2909     return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
2910   }
2911 
2912   public static function APICPictureTypeLookup($index, $returnarray=false) {
2913     static $APICPictureTypeLookup = array(
2914       0x00 => 'Other',
2915       0x01 => '32x32 pixels \'file icon\' (PNG only)',
2916       0x02 => 'Other file icon',
2917       0x03 => 'Cover (front)',
2918       0x04 => 'Cover (back)',
2919       0x05 => 'Leaflet page',
2920       0x06 => 'Media (e.g. label side of CD)',
2921       0x07 => 'Lead artist/lead performer/soloist',
2922       0x08 => 'Artist/performer',
2923       0x09 => 'Conductor',
2924       0x0A => 'Band/Orchestra',
2925       0x0B => 'Composer',
2926       0x0C => 'Lyricist/text writer',
2927       0x0D => 'Recording Location',
2928       0x0E => 'During recording',
2929       0x0F => 'During performance',
2930       0x10 => 'Movie/video screen capture',
2931       0x11 => 'A bright coloured fish',
2932       0x12 => 'Illustration',
2933       0x13 => 'Band/artist logotype',
2934       0x14 => 'Publisher/Studio logotype'
2935     );
2936     if ($returnarray) {
2937       return $APICPictureTypeLookup;
2938     }
2939     return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
2940   }
2941 
2942   public static function COMRReceivedAsLookup($index) {
2943     static $COMRReceivedAsLookup = array(
2944       0x00 => 'Other',
2945       0x01 => 'Standard CD album with other songs',
2946       0x02 => 'Compressed audio on CD',
2947       0x03 => 'File over the Internet',
2948       0x04 => 'Stream over the Internet',
2949       0x05 => 'As note sheets',
2950       0x06 => 'As note sheets in a book with other sheets',
2951       0x07 => 'Music on other media',
2952       0x08 => 'Non-musical merchandise'
2953     );
2954 
2955     return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
2956   }
2957 
2958   public static function RVA2ChannelTypeLookup($index) {
2959     static $RVA2ChannelTypeLookup = array(
2960       0x00 => 'Other',
2961       0x01 => 'Master volume',
2962       0x02 => 'Front right',
2963       0x03 => 'Front left',
2964       0x04 => 'Back right',
2965       0x05 => 'Back left',
2966       0x06 => 'Front centre',
2967       0x07 => 'Back centre',
2968       0x08 => 'Subwoofer'
2969     );
2970 
2971     return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
2972   }
2973 
2974   public static function FrameNameLongLookup($framename) {
2975 
2976     $begin = __LINE__;
2977 
2978     /** This is not a comment!
2979 
2980       AENC  Audio encryption
2981       APIC  Attached picture
2982       ASPI  Audio seek point index
2983       BUF Recommended buffer size
2984       CNT Play counter
2985       COM Comments
2986       COMM  Comments
2987       COMR  Commercial frame
2988       CRA Audio encryption
2989       CRM Encrypted meta frame
2990       ENCR  Encryption method registration
2991       EQU Equalisation
2992       EQU2  Equalisation (2)
2993       EQUA  Equalisation
2994       ETC Event timing codes
2995       ETCO  Event timing codes
2996       GEO General encapsulated object
2997       GEOB  General encapsulated object
2998       GRID  Group identification registration
2999       IPL Involved people list
3000       IPLS  Involved people list
3001       LINK  Linked information
3002       LNK Linked information
3003       MCDI  Music CD identifier
3004       MCI Music CD Identifier
3005       MLL MPEG location lookup table
3006       MLLT  MPEG location lookup table
3007       OWNE  Ownership frame
3008       PCNT  Play counter
3009       PIC Attached picture
3010       POP Popularimeter
3011       POPM  Popularimeter
3012       POSS  Position synchronisation frame
3013       PRIV  Private frame
3014       RBUF  Recommended buffer size
3015       REV Reverb
3016       RVA Relative volume adjustment
3017       RVA2  Relative volume adjustment (2)
3018       RVAD  Relative volume adjustment
3019       RVRB  Reverb
3020       SEEK  Seek frame
3021       SIGN  Signature frame
3022       SLT Synchronised lyric/text
3023       STC Synced tempo codes
3024       SYLT  Synchronised lyric/text
3025       SYTC  Synchronised tempo codes
3026       TAL Album/Movie/Show title
3027       TALB  Album/Movie/Show title
3028       TBP BPM (Beats Per Minute)
3029       TBPM  BPM (beats per minute)
3030       TCM Composer
3031       TCMP  Part of a compilation
3032       TCO Content type
3033       TCOM  Composer
3034       TCON  Content type
3035       TCOP  Copyright message
3036       TCP Part of a compilation
3037       TCR Copyright message
3038       TDA Date
3039       TDAT  Date
3040       TDEN  Encoding time
3041       TDLY  Playlist delay
3042       TDOR  Original release time
3043       TDRC  Recording time
3044       TDRL  Release time
3045       TDTG  Tagging time
3046       TDY Playlist delay
3047       TEN Encoded by
3048       TENC  Encoded by
3049       TEXT  Lyricist/Text writer
3050       TFLT  File type
3051       TFT File type
3052       TIM Time
3053       TIME  Time
3054       TIPL  Involved people list
3055       TIT1  Content group description
3056       TIT2  Title/songname/content description
3057       TIT3  Subtitle/Description refinement
3058       TKE Initial key
3059       TKEY  Initial key
3060       TLA Language(s)
3061       TLAN  Language(s)
3062       TLE Length
3063       TLEN  Length
3064       TMCL  Musician credits list
3065       TMED  Media type
3066       TMOO  Mood
3067       TMT Media type
3068       TOA Original artist(s)/performer(s)
3069       TOAL  Original album/movie/show title
3070       TOF Original filename
3071       TOFN  Original filename
3072       TOL Original Lyricist(s)/text writer(s)
3073       TOLY  Original lyricist(s)/text writer(s)
3074       TOPE  Original artist(s)/performer(s)
3075       TOR Original release year
3076       TORY  Original release year
3077       TOT Original album/Movie/Show title
3078       TOWN  File owner/licensee
3079       TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3080       TP2 Band/Orchestra/Accompaniment
3081       TP3 Conductor/Performer refinement
3082       TP4 Interpreted, remixed, or otherwise modified by
3083       TPA Part of a set
3084       TPB Publisher
3085       TPE1  Lead performer(s)/Soloist(s)
3086       TPE2  Band/orchestra/accompaniment
3087       TPE3  Conductor/performer refinement
3088       TPE4  Interpreted, remixed, or otherwise modified by
3089       TPOS  Part of a set
3090       TPRO  Produced notice
3091       TPUB  Publisher
3092       TRC ISRC (International Standard Recording Code)
3093       TRCK  Track number/Position in set
3094       TRD Recording dates
3095       TRDA  Recording dates
3096       TRK Track number/Position in set
3097       TRSN  Internet radio station name
3098       TRSO  Internet radio station owner
3099       TS2 Album-Artist sort order
3100       TSA Album sort order
3101       TSC Composer sort order
3102       TSI Size
3103       TSIZ  Size
3104       TSO2  Album-Artist sort order
3105       TSOA  Album sort order
3106       TSOC  Composer sort order
3107       TSOP  Performer sort order
3108       TSOT  Title sort order
3109       TSP Performer sort order
3110       TSRC  ISRC (international standard recording code)
3111       TSS Software/hardware and settings used for encoding
3112       TSSE  Software/Hardware and settings used for encoding
3113       TSST  Set subtitle
3114       TST Title sort order
3115       TT1 Content group description
3116       TT2 Title/Songname/Content description
3117       TT3 Subtitle/Description refinement
3118       TXT Lyricist/text writer
3119       TXX User defined text information frame
3120       TXXX  User defined text information frame
3121       TYE Year
3122       TYER  Year
3123       UFI Unique file identifier
3124       UFID  Unique file identifier
3125       ULT Unsychronised lyric/text transcription
3126       USER  Terms of use
3127       USLT  Unsynchronised lyric/text transcription
3128       WAF Official audio file webpage
3129       WAR Official artist/performer webpage
3130       WAS Official audio source webpage
3131       WCM Commercial information
3132       WCOM  Commercial information
3133       WCOP  Copyright/Legal information
3134       WCP Copyright/Legal information
3135       WOAF  Official audio file webpage
3136       WOAR  Official artist/performer webpage
3137       WOAS  Official audio source webpage
3138       WORS  Official Internet radio station homepage
3139       WPAY  Payment
3140       WPB Publishers official webpage
3141       WPUB  Publishers official webpage
3142       WXX User defined URL link frame
3143       WXXX  User defined URL link frame
3144       TFEA  Featured Artist
3145       TSTU  Recording Studio
3146       rgad  Replay Gain Adjustment
3147 
3148     */
3149 
3150     return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
3151 
3152     // Last three:
3153     // from Helium2 [www.helium2.com]
3154     // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3155   }
3156 
3157 
3158   public static function FrameNameShortLookup($framename) {
3159 
3160     $begin = __LINE__;
3161 
3162     /** This is not a comment!
3163 
3164       AENC  audio_encryption
3165       APIC  attached_picture
3166       ASPI  audio_seek_point_index
3167       BUF recommended_buffer_size
3168       CNT play_counter
3169       COM comment
3170       COMM  comment
3171       COMR  commercial_frame
3172       CRA audio_encryption
3173       CRM encrypted_meta_frame
3174       ENCR  encryption_method_registration
3175       EQU equalisation
3176       EQU2  equalisation
3177       EQUA  equalisation
3178       ETC event_timing_codes
3179       ETCO  event_timing_codes
3180       GEO general_encapsulated_object
3181       GEOB  general_encapsulated_object
3182       GRID  group_identification_registration
3183       IPL involved_people_list
3184       IPLS  involved_people_list
3185       LINK  linked_information
3186       LNK linked_information
3187       MCDI  music_cd_identifier
3188       MCI music_cd_identifier
3189       MLL mpeg_location_lookup_table
3190       MLLT  mpeg_location_lookup_table
3191       OWNE  ownership_frame
3192       PCNT  play_counter
3193       PIC attached_picture
3194       POP popularimeter
3195       POPM  popularimeter
3196       POSS  position_synchronisation_frame
3197       PRIV  private_frame
3198       RBUF  recommended_buffer_size
3199       REV reverb
3200       RVA relative_volume_adjustment
3201       RVA2  relative_volume_adjustment
3202       RVAD  relative_volume_adjustment
3203       RVRB  reverb
3204       SEEK  seek_frame
3205       SIGN  signature_frame
3206       SLT synchronised_lyric
3207       STC synced_tempo_codes
3208       SYLT  synchronised_lyric
3209       SYTC  synchronised_tempo_codes
3210       TAL album
3211       TALB  album
3212       TBP bpm
3213       TBPM  bpm
3214       TCM composer
3215       TCMP  part_of_a_compilation
3216       TCO genre
3217       TCOM  composer
3218       TCON  genre
3219       TCOP  copyright_message
3220       TCP part_of_a_compilation
3221       TCR copyright_message
3222       TDA date
3223       TDAT  date
3224       TDEN  encoding_time
3225       TDLY  playlist_delay
3226       TDOR  original_release_time
3227       TDRC  recording_time
3228       TDRL  release_time
3229       TDTG  tagging_time
3230       TDY playlist_delay
3231       TEN encoded_by
3232       TENC  encoded_by
3233       TEXT  lyricist
3234       TFLT  file_type
3235       TFT file_type
3236       TIM time
3237       TIME  time
3238       TIPL  involved_people_list
3239       TIT1  content_group_description
3240       TIT2  title
3241       TIT3  subtitle
3242       TKE initial_key
3243       TKEY  initial_key
3244       TLA language
3245       TLAN  language
3246       TLE length
3247       TLEN  length
3248       TMCL  musician_credits_list
3249       TMED  media_type
3250       TMOO  mood
3251       TMT media_type
3252       TOA original_artist
3253       TOAL  original_album
3254       TOF original_filename
3255       TOFN  original_filename
3256       TOL original_lyricist
3257       TOLY  original_lyricist
3258       TOPE  original_artist
3259       TOR original_year
3260       TORY  original_year
3261       TOT original_album
3262       TOWN  file_owner
3263       TP1 artist
3264       TP2 band
3265       TP3 conductor
3266       TP4 remixer
3267       TPA part_of_a_set
3268       TPB publisher
3269       TPE1  artist
3270       TPE2  band
3271       TPE3  conductor
3272       TPE4  remixer
3273       TPOS  part_of_a_set
3274       TPRO  produced_notice
3275       TPUB  publisher
3276       TRC isrc
3277       TRCK  track_number
3278       TRD recording_dates
3279       TRDA  recording_dates
3280       TRK track_number
3281       TRSN  internet_radio_station_name
3282       TRSO  internet_radio_station_owner
3283       TS2 album_artist_sort_order
3284       TSA album_sort_order
3285       TSC composer_sort_order
3286       TSI size
3287       TSIZ  size
3288       TSO2  album_artist_sort_order
3289       TSOA  album_sort_order
3290       TSOC  composer_sort_order
3291       TSOP  performer_sort_order
3292       TSOT  title_sort_order
3293       TSP performer_sort_order
3294       TSRC  isrc
3295       TSS encoder_settings
3296       TSSE  encoder_settings
3297       TSST  set_subtitle
3298       TST title_sort_order
3299       TT1 content_group_description
3300       TT2 title
3301       TT3 subtitle
3302       TXT lyricist
3303       TXX text
3304       TXXX  text
3305       TYE year
3306       TYER  year
3307       UFI unique_file_identifier
3308       UFID  unique_file_identifier
3309       ULT unsychronised_lyric
3310       USER  terms_of_use
3311       USLT  unsynchronised_lyric
3312       WAF url_file
3313       WAR url_artist
3314       WAS url_source
3315       WCM commercial_information
3316       WCOM  commercial_information
3317       WCOP  copyright
3318       WCP copyright
3319       WOAF  url_file
3320       WOAR  url_artist
3321       WOAS  url_source
3322       WORS  url_station
3323       WPAY  url_payment
3324       WPB url_publisher
3325       WPUB  url_publisher
3326       WXX url_user
3327       WXXX  url_user
3328       TFEA  featured_artist
3329       TSTU  recording_studio
3330       rgad  replay_gain_adjustment
3331 
3332     */
3333 
3334     return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
3335   }
3336 
3337   public static function TextEncodingTerminatorLookup($encoding) {
3338     // http://www.id3.org/id3v2.4.0-structure.txt
3339     // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3340     static $TextEncodingTerminatorLookup = array(
3341       0   => "\x00",     // $00  ISO-8859-1. Terminated with $00.
3342       1   => "\x00\x00", // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3343       2   => "\x00\x00", // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3344       3   => "\x00",     // $03  UTF-8 encoded Unicode. Terminated with $00.
3345       255 => "\x00\x00"
3346     );
3347     return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : '');
3348   }
3349 
3350   public static function TextEncodingNameLookup($encoding) {
3351     // http://www.id3.org/id3v2.4.0-structure.txt
3352     // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3353     static $TextEncodingNameLookup = array(
3354       0   => 'ISO-8859-1', // $00  ISO-8859-1. Terminated with $00.
3355       1   => 'UTF-16',     // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3356       2   => 'UTF-16BE',   // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3357       3   => 'UTF-8',      // $03  UTF-8 encoded Unicode. Terminated with $00.
3358       255 => 'UTF-16BE'
3359     );
3360     return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3361   }
3362 
3363   public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3364     switch ($id3v2majorversion) {
3365       case 2:
3366         return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3367         break;
3368 
3369       case 3:
3370       case 4:
3371         return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3372         break;
3373     }
3374     return false;
3375   }
3376 
3377   public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3378     for ($i = 0; $i < strlen($numberstring); $i++) {
3379       if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) {
3380         if (($numberstring{$i} == '.') && $allowdecimal) {
3381           // allowed
3382         } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) {
3383           // allowed
3384         } else {
3385           return false;
3386         }
3387       }
3388     }
3389     return true;
3390   }
3391 
3392   public static function IsValidDateStampString($datestamp) {
3393     if (strlen($datestamp) != 8) {
3394       return false;
3395     }
3396     if (!self::IsANumber($datestamp, false)) {
3397       return false;
3398     }
3399     $year  = substr($datestamp, 0, 4);
3400     $month = substr($datestamp, 4, 2);
3401     $day   = substr($datestamp, 6, 2);
3402     if (($year == 0) || ($month == 0) || ($day == 0)) {
3403       return false;
3404     }
3405     if ($month > 12) {
3406       return false;
3407     }
3408     if ($day > 31) {
3409       return false;
3410     }
3411     if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3412       return false;
3413     }
3414     if (($day > 29) && ($month == 2)) {
3415       return false;
3416     }
3417     return true;
3418   }
3419 
3420   public static function ID3v2HeaderLength($majorversion) {
3421     return (($majorversion == 2) ? 6 : 10);
3422   }
3423 
3424 }
3425