ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

module.tag.id3v2.php (154849B)


      1 <?php
      2 
      3 /////////////////////////////////////////////////////////////////
      4 /// getID3() by James Heinrich <info@getid3.org>               //
      5 //  available at https://github.com/JamesHeinrich/getID3       //
      6 //            or https://www.getid3.org                        //
      7 //            or http://getid3.sourceforge.net                 //
      8 //  see readme.txt for more details                            //
      9 /////////////////////////////////////////////////////////////////
     10 ///                                                            //
     11 // module.tag.id3v2.php                                        //
     12 // module for analyzing ID3v2 tags                             //
     13 // dependencies: module.tag.id3v1.php                          //
     14 //                                                            ///
     15 /////////////////////////////////////////////////////////////////
     16 
     17 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
     18 	exit;
     19 }
     20 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
     21 
     22 class getid3_id3v2 extends getid3_handler
     23 {
     24 	public $StartingOffset = 0;
     25 
     26 	/**
     27 	 * @return bool
     28 	 */
     29 	public function Analyze() {
     30 		$info = &$this->getid3->info;
     31 
     32 		//    Overall tag structure:
     33 		//        +-----------------------------+
     34 		//        |      Header (10 bytes)      |
     35 		//        +-----------------------------+
     36 		//        |       Extended Header       |
     37 		//        | (variable length, OPTIONAL) |
     38 		//        +-----------------------------+
     39 		//        |   Frames (variable length)  |
     40 		//        +-----------------------------+
     41 		//        |           Padding           |
     42 		//        | (variable length, OPTIONAL) |
     43 		//        +-----------------------------+
     44 		//        | Footer (10 bytes, OPTIONAL) |
     45 		//        +-----------------------------+
     46 
     47 		//    Header
     48 		//        ID3v2/file identifier      "ID3"
     49 		//        ID3v2 version              $04 00
     50 		//        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
     51 		//        ID3v2 size             4 * %0xxxxxxx
     52 
     53 
     54 		// shortcuts
     55 		$info['id3v2']['header'] = true;
     56 		$thisfile_id3v2                  = &$info['id3v2'];
     57 		$thisfile_id3v2['flags']         =  array();
     58 		$thisfile_id3v2_flags            = &$thisfile_id3v2['flags'];
     59 
     60 
     61 		$this->fseek($this->StartingOffset);
     62 		$header = $this->fread(10);
     63 		if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
     64 
     65 			$thisfile_id3v2['majorversion'] = ord($header[3]);
     66 			$thisfile_id3v2['minorversion'] = ord($header[4]);
     67 
     68 			// shortcut
     69 			$id3v2_majorversion = &$thisfile_id3v2['majorversion'];
     70 
     71 		} else {
     72 
     73 			unset($info['id3v2']);
     74 			return false;
     75 
     76 		}
     77 
     78 		if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
     79 
     80 			$this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
     81 			return false;
     82 
     83 		}
     84 
     85 		$id3_flags = ord($header[5]);
     86 		switch ($id3v2_majorversion) {
     87 			case 2:
     88 				// %ab000000 in v2.2
     89 				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
     90 				$thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
     91 				break;
     92 
     93 			case 3:
     94 				// %abc00000 in v2.3
     95 				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
     96 				$thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
     97 				$thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
     98 				break;
     99 
    100 			case 4:
    101 				// %abcd0000 in v2.4
    102 				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
    103 				$thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
    104 				$thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
    105 				$thisfile_id3v2_flags['isfooter']    = (bool) ($id3_flags & 0x10); // d - Footer present
    106 				break;
    107 		}
    108 
    109 		$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
    110 
    111 		$thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
    112 		$thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
    113 
    114 
    115 
    116 		// create 'encoding' key - used by getid3::HandleAllTags()
    117 		// in ID3v2 every field can have it's own encoding type
    118 		// so force everything to UTF-8 so it can be handled consistantly
    119 		$thisfile_id3v2['encoding'] = 'UTF-8';
    120 
    121 
    122 	//    Frames
    123 
    124 	//        All ID3v2 frames consists of one frame header followed by one or more
    125 	//        fields containing the actual information. The header is always 10
    126 	//        bytes and laid out as follows:
    127 	//
    128 	//        Frame ID      $xx xx xx xx  (four characters)
    129 	//        Size      4 * %0xxxxxxx
    130 	//        Flags         $xx xx
    131 
    132 		$sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
    133 		if (!empty($thisfile_id3v2['exthead']['length'])) {
    134 			$sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
    135 		}
    136 		if (!empty($thisfile_id3v2_flags['isfooter'])) {
    137 			$sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
    138 		}
    139 		if ($sizeofframes > 0) {
    140 
    141 			$framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
    142 
    143 			//    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
    144 			if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
    145 				$framedata = $this->DeUnsynchronise($framedata);
    146 			}
    147 			//        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
    148 			//        of on tag level, making it easier to skip frames, increasing the streamability
    149 			//        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
    150 			//        there exists an unsynchronised frame, while the new unsynchronisation flag in
    151 			//        the frame header [S:4.1.2] indicates unsynchronisation.
    152 
    153 
    154 			//$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)
    155 			$framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
    156 
    157 
    158 			//    Extended Header
    159 			if (!empty($thisfile_id3v2_flags['exthead'])) {
    160 				$extended_header_offset = 0;
    161 
    162 				if ($id3v2_majorversion == 3) {
    163 
    164 					// v2.3 definition:
    165 					//Extended header size  $xx xx xx xx   // 32-bit integer
    166 					//Extended Flags        $xx xx
    167 					//     %x0000000 %00000000 // v2.3
    168 					//     x - CRC data present
    169 					//Size of padding       $xx xx xx xx
    170 
    171 					$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
    172 					$extended_header_offset += 4;
    173 
    174 					$thisfile_id3v2['exthead']['flag_bytes'] = 2;
    175 					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
    176 					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
    177 
    178 					$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
    179 
    180 					$thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
    181 					$extended_header_offset += 4;
    182 
    183 					if ($thisfile_id3v2['exthead']['flags']['crc']) {
    184 						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
    185 						$extended_header_offset += 4;
    186 					}
    187 					$extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
    188 
    189 				} elseif ($id3v2_majorversion == 4) {
    190 
    191 					// v2.4 definition:
    192 					//Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
    193 					//Number of flag bytes       $01
    194 					//Extended Flags             $xx
    195 					//     %0bcd0000 // v2.4
    196 					//     b - Tag is an update
    197 					//         Flag data length       $00
    198 					//     c - CRC data present
    199 					//         Flag data length       $05
    200 					//         Total frame CRC    5 * %0xxxxxxx
    201 					//     d - Tag restrictions
    202 					//         Flag data length       $01
    203 
    204 					$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
    205 					$extended_header_offset += 4;
    206 
    207 					$thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
    208 					$extended_header_offset += 1;
    209 
    210 					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
    211 					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
    212 
    213 					$thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
    214 					$thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
    215 					$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
    216 
    217 					if ($thisfile_id3v2['exthead']['flags']['update']) {
    218 						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
    219 						$extended_header_offset += 1;
    220 					}
    221 
    222 					if ($thisfile_id3v2['exthead']['flags']['crc']) {
    223 						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
    224 						$extended_header_offset += 1;
    225 						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
    226 						$extended_header_offset += $ext_header_chunk_length;
    227 					}
    228 
    229 					if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
    230 						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
    231 						$extended_header_offset += 1;
    232 
    233 						// %ppqrrstt
    234 						$restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
    235 						$extended_header_offset += 1;
    236 						$thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
    237 						$thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
    238 						$thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
    239 						$thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
    240 						$thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
    241 
    242 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize']  = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
    243 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc']  = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
    244 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
    245 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
    246 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
    247 					}
    248 
    249 					if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
    250 						$this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
    251 					}
    252 				}
    253 
    254 				$framedataoffset += $extended_header_offset;
    255 				$framedata = substr($framedata, $extended_header_offset);
    256 			} // end extended header
    257 
    258 
    259 			while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
    260 				if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
    261 					// insufficient room left in ID3v2 header for actual data - must be padding
    262 					$thisfile_id3v2['padding']['start']  = $framedataoffset;
    263 					$thisfile_id3v2['padding']['length'] = strlen($framedata);
    264 					$thisfile_id3v2['padding']['valid']  = true;
    265 					for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
    266 						if ($framedata[$i] != "\x00") {
    267 							$thisfile_id3v2['padding']['valid'] = false;
    268 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
    269 							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
    270 							break;
    271 						}
    272 					}
    273 					break; // skip rest of ID3v2 header
    274 				}
    275 				$frame_header = null;
    276 				$frame_name   = null;
    277 				$frame_size   = null;
    278 				$frame_flags  = null;
    279 				if ($id3v2_majorversion == 2) {
    280 					// Frame ID  $xx xx xx (three characters)
    281 					// Size      $xx xx xx (24-bit integer)
    282 					// Flags     $xx xx
    283 
    284 					$frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
    285 					$framedata    = substr($framedata, 6);    // and leave the rest in $framedata
    286 					$frame_name   = substr($frame_header, 0, 3);
    287 					$frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
    288 					$frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
    289 
    290 				} elseif ($id3v2_majorversion > 2) {
    291 
    292 					// Frame ID  $xx xx xx xx (four characters)
    293 					// Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
    294 					// Flags     $xx xx
    295 
    296 					$frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
    297 					$framedata    = substr($framedata, 10);    // and leave the rest in $framedata
    298 
    299 					$frame_name = substr($frame_header, 0, 4);
    300 					if ($id3v2_majorversion == 3) {
    301 						$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
    302 					} else { // ID3v2.4+
    303 						$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
    304 					}
    305 
    306 					if ($frame_size < (strlen($framedata) + 4)) {
    307 						$nextFrameID = substr($framedata, $frame_size, 4);
    308 						if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
    309 							// next frame is OK
    310 						} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
    311 							// MP3ext known broken frames - "ok" for the purposes of this test
    312 						} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
    313 							$this->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');
    314 							$id3v2_majorversion = 3;
    315 							$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
    316 						}
    317 					}
    318 
    319 
    320 					$frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
    321 				}
    322 
    323 				if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
    324 					// padding encountered
    325 
    326 					$thisfile_id3v2['padding']['start']  = $framedataoffset;
    327 					$thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
    328 					$thisfile_id3v2['padding']['valid']  = true;
    329 
    330 					$len = strlen($framedata);
    331 					for ($i = 0; $i < $len; $i++) {
    332 						if ($framedata[$i] != "\x00") {
    333 							$thisfile_id3v2['padding']['valid'] = false;
    334 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
    335 							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
    336 							break;
    337 						}
    338 					}
    339 					break; // skip rest of ID3v2 header
    340 				}
    341 
    342 				if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
    343 					$this->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", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
    344 					$frame_name = $iTunesBrokenFrameNameFixed;
    345 				}
    346 				if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
    347 
    348 					unset($parsedFrame);
    349 					$parsedFrame['frame_name']      = $frame_name;
    350 					$parsedFrame['frame_flags_raw'] = $frame_flags;
    351 					$parsedFrame['data']            = substr($framedata, 0, $frame_size);
    352 					$parsedFrame['datalength']      = getid3_lib::CastAsInt($frame_size);
    353 					$parsedFrame['dataoffset']      = $framedataoffset;
    354 
    355 					$this->ParseID3v2Frame($parsedFrame);
    356 					$thisfile_id3v2[$frame_name][] = $parsedFrame;
    357 
    358 					$framedata = substr($framedata, $frame_size);
    359 
    360 				} else { // invalid frame length or FrameID
    361 
    362 					if ($frame_size <= strlen($framedata)) {
    363 
    364 						if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
    365 
    366 							// next frame is valid, just skip the current frame
    367 							$framedata = substr($framedata, $frame_size);
    368 							$this->warning('Next ID3v2 frame is valid, skipping current frame.');
    369 
    370 						} else {
    371 
    372 							// next frame is invalid too, abort processing
    373 							//unset($framedata);
    374 							$framedata = null;
    375 							$this->error('Next ID3v2 frame is also invalid, aborting processing.');
    376 
    377 						}
    378 
    379 					} elseif ($frame_size == strlen($framedata)) {
    380 
    381 						// this is the last frame, just skip
    382 						$this->warning('This was the last ID3v2 frame.');
    383 
    384 					} else {
    385 
    386 						// next frame is invalid too, abort processing
    387 						//unset($framedata);
    388 						$framedata = null;
    389 						$this->warning('Invalid ID3v2 frame size, aborting.');
    390 
    391 					}
    392 					if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
    393 
    394 						switch ($frame_name) {
    395 							case "\x00\x00".'MP':
    396 							case "\x00".'MP3':
    397 							case ' MP3':
    398 							case 'MP3e':
    399 							case "\x00".'MP':
    400 							case ' MP':
    401 							case 'MP3':
    402 								$this->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/)"]');
    403 								break;
    404 
    405 							default:
    406 								$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
    407 								break;
    408 						}
    409 
    410 					} elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
    411 
    412 						$this->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').')).');
    413 
    414 					} else {
    415 
    416 						$this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');
    417 
    418 					}
    419 
    420 				}
    421 				$framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
    422 
    423 			}
    424 
    425 		}
    426 
    427 
    428 	//    Footer
    429 
    430 	//    The footer is a copy of the header, but with a different identifier.
    431 	//        ID3v2 identifier           "3DI"
    432 	//        ID3v2 version              $04 00
    433 	//        ID3v2 flags                %abcd0000
    434 	//        ID3v2 size             4 * %0xxxxxxx
    435 
    436 		if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
    437 			$footer = $this->fread(10);
    438 			if (substr($footer, 0, 3) == '3DI') {
    439 				$thisfile_id3v2['footer'] = true;
    440 				$thisfile_id3v2['majorversion_footer'] = ord($footer[3]);
    441 				$thisfile_id3v2['minorversion_footer'] = ord($footer[4]);
    442 			}
    443 			if ($thisfile_id3v2['majorversion_footer'] <= 4) {
    444 				$id3_flags = ord($footer[5]);
    445 				$thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
    446 				$thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
    447 				$thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
    448 				$thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
    449 
    450 				$thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
    451 			}
    452 		} // end footer
    453 
    454 		if (isset($thisfile_id3v2['comments']['genre'])) {
    455 			$genres = array();
    456 			foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
    457 				foreach ($this->ParseID3v2GenreString($value) as $genre) {
    458 					$genres[] = $genre;
    459 				}
    460 			}
    461 			$thisfile_id3v2['comments']['genre'] = array_unique($genres);
    462 			unset($key, $value, $genres, $genre);
    463 		}
    464 
    465 		if (isset($thisfile_id3v2['comments']['track_number'])) {
    466 			foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) {
    467 				if (strstr($value, '/')) {
    468 					list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]);
    469 				}
    470 			}
    471 		}
    472 
    473 		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)) {
    474 			$thisfile_id3v2['comments']['year'] = array($matches[1]);
    475 		}
    476 
    477 
    478 		if (!empty($thisfile_id3v2['TXXX'])) {
    479 			// MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
    480 			foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
    481 				switch ($txxx_array['description']) {
    482 					case 'replaygain_track_gain':
    483 						if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
    484 							$info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
    485 						}
    486 						break;
    487 					case 'replaygain_track_peak':
    488 						if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
    489 							$info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
    490 						}
    491 						break;
    492 					case 'replaygain_album_gain':
    493 						if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
    494 							$info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
    495 						}
    496 						break;
    497 				}
    498 			}
    499 		}
    500 
    501 
    502 		// Set avdataoffset
    503 		$info['avdataoffset'] = $thisfile_id3v2['headerlength'];
    504 		if (isset($thisfile_id3v2['footer'])) {
    505 			$info['avdataoffset'] += 10;
    506 		}
    507 
    508 		return true;
    509 	}
    510 
    511 	/**
    512 	 * @param string $genrestring
    513 	 *
    514 	 * @return array
    515 	 */
    516 	public function ParseID3v2GenreString($genrestring) {
    517 		// Parse genres into arrays of genreName and genreID
    518 		// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
    519 		// ID3v2.4.x: '21' $00 'Eurodisco' $00
    520 		$clean_genres = array();
    521 
    522 		// hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
    523 		if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
    524 			// note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
    525 			// replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
    526 			if (strpos($genrestring, '/') !== false) {
    527 				$LegitimateSlashedGenreList = array(  // https://github.com/JamesHeinrich/getID3/issues/223
    528 					'Pop/Funk',    // ID3v1 genre #62 - https://en.wikipedia.org/wiki/ID3#standard
    529 					'Cut-up/DJ',   // Discogs - https://www.discogs.com/style/cut-up/dj
    530 					'RnB/Swing',   // Discogs - https://www.discogs.com/style/rnb/swing
    531 					'Funk / Soul', // Discogs (note spaces) - https://www.discogs.com/genre/funk+%2F+soul
    532 				);
    533 				$genrestring = str_replace('/', "\x00", $genrestring);
    534 				foreach ($LegitimateSlashedGenreList as $SlashedGenre) {
    535 					$genrestring = str_ireplace(str_replace('/', "\x00", $SlashedGenre), $SlashedGenre, $genrestring);
    536 				}
    537 			}
    538 
    539 			// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
    540 			if (strpos($genrestring, ';') !== false) {
    541 				$genrestring = str_replace(';', "\x00", $genrestring);
    542 			}
    543 		}
    544 
    545 
    546 		if (strpos($genrestring, "\x00") === false) {
    547 			$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
    548 		}
    549 
    550 		$genre_elements = explode("\x00", $genrestring);
    551 		foreach ($genre_elements as $element) {
    552 			$element = trim($element);
    553 			if ($element) {
    554 				if (preg_match('#^[0-9]{1,3}$#', $element)) {
    555 					$clean_genres[] = getid3_id3v1::LookupGenreName($element);
    556 				} else {
    557 					$clean_genres[] = str_replace('((', '(', $element);
    558 				}
    559 			}
    560 		}
    561 		return $clean_genres;
    562 	}
    563 
    564 	/**
    565 	 * @param array $parsedFrame
    566 	 *
    567 	 * @return bool
    568 	 */
    569 	public function ParseID3v2Frame(&$parsedFrame) {
    570 
    571 		// shortcuts
    572 		$info = &$this->getid3->info;
    573 		$id3v2_majorversion = $info['id3v2']['majorversion'];
    574 
    575 		$parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
    576 		if (empty($parsedFrame['framenamelong'])) {
    577 			unset($parsedFrame['framenamelong']);
    578 		}
    579 		$parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
    580 		if (empty($parsedFrame['framenameshort'])) {
    581 			unset($parsedFrame['framenameshort']);
    582 		}
    583 
    584 		if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
    585 			if ($id3v2_majorversion == 3) {
    586 				//    Frame Header Flags
    587 				//    %abc00000 %ijk00000
    588 				$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
    589 				$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
    590 				$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
    591 				$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
    592 				$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
    593 				$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
    594 
    595 			} elseif ($id3v2_majorversion == 4) {
    596 				//    Frame Header Flags
    597 				//    %0abc0000 %0h00kmnp
    598 				$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
    599 				$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
    600 				$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
    601 				$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
    602 				$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
    603 				$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
    604 				$parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
    605 				$parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
    606 
    607 				// Frame-level de-unsynchronisation - ID3v2.4
    608 				if ($parsedFrame['flags']['Unsynchronisation']) {
    609 					$parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
    610 				}
    611 
    612 				if ($parsedFrame['flags']['DataLengthIndicator']) {
    613 					$parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
    614 					$parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
    615 				}
    616 			}
    617 
    618 			//    Frame-level de-compression
    619 			if ($parsedFrame['flags']['compression']) {
    620 				$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
    621 				if (!function_exists('gzuncompress')) {
    622 					$this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
    623 				} else {
    624 					if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
    625 					//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
    626 						$parsedFrame['data'] = $decompresseddata;
    627 						unset($decompresseddata);
    628 					} else {
    629 						$this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
    630 					}
    631 				}
    632 			}
    633 		}
    634 
    635 		if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
    636 			if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
    637 				$this->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');
    638 			}
    639 		}
    640 
    641 		if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
    642 
    643 			$warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
    644 			switch ($parsedFrame['frame_name']) {
    645 				case 'WCOM':
    646 					$warning .= ' (this is known to happen with files tagged by RioPort)';
    647 					break;
    648 
    649 				default:
    650 					break;
    651 			}
    652 			$this->warning($warning);
    653 
    654 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
    655 			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
    656 			//   There may be more than one 'UFID' frame in a tag,
    657 			//   but only one with the same 'Owner identifier'.
    658 			// <Header for 'Unique file identifier', ID: 'UFID'>
    659 			// Owner identifier        <text string> $00
    660 			// Identifier              <up to 64 bytes binary data>
    661 			$exploded = explode("\x00", $parsedFrame['data'], 2);
    662 			$parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
    663 			$parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : '');
    664 
    665 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
    666 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
    667 			//   There may be more than one 'TXXX' frame in each tag,
    668 			//   but only one with the same description.
    669 			// <Header for 'User defined text information frame', ID: 'TXXX'>
    670 			// Text encoding     $xx
    671 			// Description       <text string according to encoding> $00 (00)
    672 			// Value             <text string according to encoding>
    673 
    674 			$frame_offset = 0;
    675 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
    676 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
    677 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
    678 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
    679 				$frame_textencoding_terminator = "\x00";
    680 			}
    681 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
    682 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
    683 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
    684 			}
    685 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
    686 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
    687 			$parsedFrame['encodingid']  = $frame_textencoding;
    688 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
    689 
    690 			$parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description']));
    691 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
    692 			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
    693 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
    694 				$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
    695 				if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
    696 					$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
    697 				} else {
    698 					$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
    699 				}
    700 			}
    701 			//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
    702 
    703 
    704 		} elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame
    705 			//   There may only be one text information frame of its kind in an tag.
    706 			// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
    707 			// excluding 'TXXX' described in 4.2.6.>
    708 			// Text encoding                $xx
    709 			// Information                  <text string(s) according to encoding>
    710 
    711 			$frame_offset = 0;
    712 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
    713 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
    714 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
    715 			}
    716 
    717 			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
    718 			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
    719 
    720 			$parsedFrame['encodingid'] = $frame_textencoding;
    721 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
    722 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
    723 				// ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
    724 				// This of course breaks when an artist name contains slash character, e.g. "AC/DC"
    725 				// MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
    726 				// getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
    727 				switch ($parsedFrame['encoding']) {
    728 					case 'UTF-16':
    729 					case 'UTF-16BE':
    730 					case 'UTF-16LE':
    731 						$wordsize = 2;
    732 						break;
    733 					case 'ISO-8859-1':
    734 					case 'UTF-8':
    735 					default:
    736 						$wordsize = 1;
    737 						break;
    738 				}
    739 				$Txxx_elements = array();
    740 				$Txxx_elements_start_offset = 0;
    741 				for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
    742 					if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
    743 						$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
    744 						$Txxx_elements_start_offset = $i + $wordsize;
    745 					}
    746 				}
    747 				$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
    748 				foreach ($Txxx_elements as $Txxx_element) {
    749 					$string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
    750 					if (!empty($string)) {
    751 						$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
    752 					}
    753 				}
    754 				unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
    755 			}
    756 
    757 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
    758 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
    759 			//   There may be more than one 'WXXX' frame in each tag,
    760 			//   but only one with the same description
    761 			// <Header for 'User defined URL link frame', ID: 'WXXX'>
    762 			// Text encoding     $xx
    763 			// Description       <text string according to encoding> $00 (00)
    764 			// URL               <text string>
    765 
    766 			$frame_offset = 0;
    767 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
    768 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
    769 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
    770 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
    771 				$frame_textencoding_terminator = "\x00";
    772 			}
    773 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
    774 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
    775 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
    776 			}
    777 			$parsedFrame['encodingid']  = $frame_textencoding;
    778 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
    779 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);           // according to the frame text encoding
    780 			$parsedFrame['url']         = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1
    781 			$parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator);
    782 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
    783 
    784 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
    785 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
    786 			}
    787 			unset($parsedFrame['data']);
    788 
    789 
    790 		} elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames
    791 			//   There may only be one URL link frame of its kind in a tag,
    792 			//   except when stated otherwise in the frame description
    793 			// <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
    794 			// described in 4.3.2.>
    795 			// URL              <text string>
    796 
    797 			$parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1
    798 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
    799 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
    800 			}
    801 			unset($parsedFrame['data']);
    802 
    803 
    804 		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
    805 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
    806 			// http://id3.org/id3v2.3.0#sec4.4
    807 			//   There may only be one 'IPL' frame in each tag
    808 			// <Header for 'User defined URL link frame', ID: 'IPL'>
    809 			// Text encoding     $xx
    810 			// People list strings    <textstrings>
    811 
    812 			$frame_offset = 0;
    813 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
    814 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
    815 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
    816 			}
    817 			$parsedFrame['encodingid'] = $frame_textencoding;
    818 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
    819 			$parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
    820 
    821 			// https://www.getid3.org/phpBB3/viewtopic.php?t=1369
    822 			// "this tag typically contains null terminated strings, which are associated in pairs"
    823 			// "there are users that use the tag incorrectly"
    824 			$IPLS_parts = array();
    825 			if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
    826 				$IPLS_parts_unsorted = array();
    827 				if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
    828 					// 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
    829 					$thisILPS  = '';
    830 					for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
    831 						$twobytes = substr($parsedFrame['data_raw'], $i, 2);
    832 						if ($twobytes === "\x00\x00") {
    833 							$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
    834 							$thisILPS  = '';
    835 						} else {
    836 							$thisILPS .= $twobytes;
    837 						}
    838 					}
    839 					if (strlen($thisILPS) > 2) { // 2-byte BOM
    840 						$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
    841 					}
    842 				} else {
    843 					// ISO-8859-1 or UTF-8 or other single-byte-null character set
    844 					$IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
    845 				}
    846 				if (count($IPLS_parts_unsorted) == 1) {
    847 					// just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
    848 					foreach ($IPLS_parts_unsorted as $key => $value) {
    849 						$IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
    850 						$position = '';
    851 						foreach ($IPLS_parts_sorted as $person) {
    852 							$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
    853 						}
    854 					}
    855 				} elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
    856 					$position = '';
    857 					$person   = '';
    858 					foreach ($IPLS_parts_unsorted as $key => $value) {
    859 						if (($key % 2) == 0) {
    860 							$position = $value;
    861 						} else {
    862 							$person   = $value;
    863 							$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
    864 							$position = '';
    865 							$person   = '';
    866 						}
    867 					}
    868 				} else {
    869 					foreach ($IPLS_parts_unsorted as $key => $value) {
    870 						$IPLS_parts[] = array($value);
    871 					}
    872 				}
    873 
    874 			} else {
    875 				$IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
    876 			}
    877 			$parsedFrame['data'] = $IPLS_parts;
    878 
    879 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
    880 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
    881 			}
    882 
    883 
    884 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
    885 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
    886 			//   There may only be one 'MCDI' frame in each tag
    887 			// <Header for 'Music CD identifier', ID: 'MCDI'>
    888 			// CD TOC                <binary data>
    889 
    890 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
    891 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
    892 			}
    893 
    894 
    895 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
    896 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
    897 			//   There may only be one 'ETCO' frame in each tag
    898 			// <Header for 'Event timing codes', ID: 'ETCO'>
    899 			// Time stamp format    $xx
    900 			//   Where time stamp format is:
    901 			// $01  (32-bit value) MPEG frames from beginning of file
    902 			// $02  (32-bit value) milliseconds from beginning of file
    903 			//   Followed by a list of key events in the following format:
    904 			// Type of event   $xx
    905 			// Time stamp      $xx (xx ...)
    906 			//   The 'Time stamp' is set to zero if directly at the beginning of the sound
    907 			//   or after the previous event. All events MUST be sorted in chronological order.
    908 
    909 			$frame_offset = 0;
    910 			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
    911 
    912 			while ($frame_offset < strlen($parsedFrame['data'])) {
    913 				$parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1);
    914 				$parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']);
    915 				$parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
    916 				$frame_offset += 4;
    917 			}
    918 			unset($parsedFrame['data']);
    919 
    920 
    921 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
    922 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
    923 			//   There may only be one 'MLLT' frame in each tag
    924 			// <Header for 'Location lookup table', ID: 'MLLT'>
    925 			// MPEG frames between reference  $xx xx
    926 			// Bytes between reference        $xx xx xx
    927 			// Milliseconds between reference $xx xx xx
    928 			// Bits for bytes deviation       $xx
    929 			// Bits for milliseconds dev.     $xx
    930 			//   Then for every reference the following data is included;
    931 			// Deviation in bytes         %xxx....
    932 			// Deviation in milliseconds  %xxx....
    933 
    934 			$frame_offset = 0;
    935 			$parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
    936 			$parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
    937 			$parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
    938 			$parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
    939 			$parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
    940 			$parsedFrame['data'] = substr($parsedFrame['data'], 10);
    941 			$deviationbitstream = '';
    942 			while ($frame_offset < strlen($parsedFrame['data'])) {
    943 				$deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
    944 			}
    945 			$reference_counter = 0;
    946 			while (strlen($deviationbitstream) > 0) {
    947 				$parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
    948 				$parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
    949 				$deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
    950 				$reference_counter++;
    951 			}
    952 			unset($parsedFrame['data']);
    953 
    954 
    955 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
    956 				  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
    957 			//   There may only be one 'SYTC' frame in each tag
    958 			// <Header for 'Synchronised tempo codes', ID: 'SYTC'>
    959 			// Time stamp format   $xx
    960 			// Tempo data          <binary data>
    961 			//   Where time stamp format is:
    962 			// $01  (32-bit value) MPEG frames from beginning of file
    963 			// $02  (32-bit value) milliseconds from beginning of file
    964 
    965 			$frame_offset = 0;
    966 			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
    967 			$timestamp_counter = 0;
    968 			while ($frame_offset < strlen($parsedFrame['data'])) {
    969 				$parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
    970 				if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
    971 					$parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
    972 				}
    973 				$parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
    974 				$frame_offset += 4;
    975 				$timestamp_counter++;
    976 			}
    977 			unset($parsedFrame['data']);
    978 
    979 
    980 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
    981 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
    982 			//   There may be more than one 'Unsynchronised lyrics/text transcription' frame
    983 			//   in each tag, but only one with the same language and content descriptor.
    984 			// <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
    985 			// Text encoding        $xx
    986 			// Language             $xx xx xx
    987 			// Content descriptor   <text string according to encoding> $00 (00)
    988 			// Lyrics/text          <full text string according to encoding>
    989 
    990 			$frame_offset = 0;
    991 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
    992 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
    993 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
    994 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
    995 				$frame_textencoding_terminator = "\x00";
    996 			}
    997 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
    998 			$frame_offset += 3;
    999 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   1000 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   1001 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   1002 			}
   1003 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1004 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
   1005 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
   1006 			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
   1007 
   1008 			$parsedFrame['encodingid']   = $frame_textencoding;
   1009 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
   1010 
   1011 			$parsedFrame['language']     = $frame_language;
   1012 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
   1013 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   1014 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
   1015 			}
   1016 			unset($parsedFrame['data']);
   1017 
   1018 
   1019 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
   1020 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
   1021 			//   There may be more than one 'SYLT' frame in each tag,
   1022 			//   but only one with the same language and content descriptor.
   1023 			// <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
   1024 			// Text encoding        $xx
   1025 			// Language             $xx xx xx
   1026 			// Time stamp format    $xx
   1027 			//   $01  (32-bit value) MPEG frames from beginning of file
   1028 			//   $02  (32-bit value) milliseconds from beginning of file
   1029 			// Content type         $xx
   1030 			// Content descriptor   <text string according to encoding> $00 (00)
   1031 			//   Terminated text to be synced (typically a syllable)
   1032 			//   Sync identifier (terminator to above string)   $00 (00)
   1033 			//   Time stamp                                     $xx (xx ...)
   1034 
   1035 			$frame_offset = 0;
   1036 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1037 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
   1038 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   1039 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   1040 				$frame_textencoding_terminator = "\x00";
   1041 			}
   1042 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
   1043 			$frame_offset += 3;
   1044 			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1045 			$parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1046 			$parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
   1047 			$parsedFrame['encodingid']      = $frame_textencoding;
   1048 			$parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding);
   1049 
   1050 			$parsedFrame['language']        = $frame_language;
   1051 			$parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);
   1052 
   1053 			$timestampindex = 0;
   1054 			$frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
   1055 			while (strlen($frame_remainingdata)) {
   1056 				$frame_offset = 0;
   1057 				$frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
   1058 				if ($frame_terminatorpos === false) {
   1059 					$frame_remainingdata = '';
   1060 				} else {
   1061 					if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   1062 						$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   1063 					}
   1064 					$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
   1065 
   1066 					$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
   1067 					if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) {
   1068 						// timestamp probably omitted for first data item
   1069 					} else {
   1070 						$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
   1071 						$frame_remainingdata = substr($frame_remainingdata, 4);
   1072 					}
   1073 					$timestampindex++;
   1074 				}
   1075 			}
   1076 			unset($parsedFrame['data']);
   1077 
   1078 
   1079 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
   1080 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
   1081 			//   There may be more than one comment frame in each tag,
   1082 			//   but only one with the same language and content descriptor.
   1083 			// <Header for 'Comment', ID: 'COMM'>
   1084 			// Text encoding          $xx
   1085 			// Language               $xx xx xx
   1086 			// Short content descrip. <text string according to encoding> $00 (00)
   1087 			// The actual text        <full text string according to encoding>
   1088 
   1089 			if (strlen($parsedFrame['data']) < 5) {
   1090 
   1091 				$this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
   1092 
   1093 			} else {
   1094 
   1095 				$frame_offset = 0;
   1096 				$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1097 				$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
   1098 				if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   1099 					$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   1100 					$frame_textencoding_terminator = "\x00";
   1101 				}
   1102 				$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
   1103 				$frame_offset += 3;
   1104 				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   1105 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   1106 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   1107 				}
   1108 				$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1109 				$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
   1110 				$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
   1111 				$frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator);
   1112 
   1113 				$parsedFrame['encodingid']   = $frame_textencoding;
   1114 				$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
   1115 
   1116 				$parsedFrame['language']     = $frame_language;
   1117 				$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
   1118 				$parsedFrame['data']         = $frame_text;
   1119 				if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   1120 					$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
   1121 					if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
   1122 						$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
   1123 					} else {
   1124 						$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
   1125 					}
   1126 				}
   1127 
   1128 			}
   1129 
   1130 		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
   1131 			//   There may be more than one 'RVA2' frame in each tag,
   1132 			//   but only one with the same identification string
   1133 			// <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
   1134 			// Identification          <text string> $00
   1135 			//   The 'identification' string is used to identify the situation and/or
   1136 			//   device where this adjustment should apply. The following is then
   1137 			//   repeated for every channel:
   1138 			// Type of channel         $xx
   1139 			// Volume adjustment       $xx xx
   1140 			// Bits representing peak  $xx
   1141 			// Peak volume             $xx (xx ...)
   1142 
   1143 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
   1144 			$frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
   1145 			if (ord($frame_idstring) === 0) {
   1146 				$frame_idstring = '';
   1147 			}
   1148 			$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
   1149 			$parsedFrame['description'] = $frame_idstring;
   1150 			$RVA2channelcounter = 0;
   1151 			while (strlen($frame_remainingdata) >= 5) {
   1152 				$frame_offset = 0;
   1153 				$frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
   1154 				$parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid;
   1155 				$parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
   1156 				$parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
   1157 				$frame_offset += 2;
   1158 				$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
   1159 				if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
   1160 					$this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
   1161 					break;
   1162 				}
   1163 				$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
   1164 				$parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
   1165 				$frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
   1166 				$RVA2channelcounter++;
   1167 			}
   1168 			unset($parsedFrame['data']);
   1169 
   1170 
   1171 		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
   1172 				  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
   1173 			//   There may only be one 'RVA' frame in each tag
   1174 			// <Header for 'Relative volume adjustment', ID: 'RVA'>
   1175 			// ID3v2.2 => Increment/decrement     %000000ba
   1176 			// ID3v2.3 => Increment/decrement     %00fedcba
   1177 			// Bits used for volume descr.        $xx
   1178 			// Relative volume change, right      $xx xx (xx ...) // a
   1179 			// Relative volume change, left       $xx xx (xx ...) // b
   1180 			// Peak volume right                  $xx xx (xx ...)
   1181 			// Peak volume left                   $xx xx (xx ...)
   1182 			//   ID3v2.3 only, optional (not present in ID3v2.2):
   1183 			// Relative volume change, right back $xx xx (xx ...) // c
   1184 			// Relative volume change, left back  $xx xx (xx ...) // d
   1185 			// Peak volume right back             $xx xx (xx ...)
   1186 			// Peak volume left back              $xx xx (xx ...)
   1187 			//   ID3v2.3 only, optional (not present in ID3v2.2):
   1188 			// Relative volume change, center     $xx xx (xx ...) // e
   1189 			// Peak volume center                 $xx xx (xx ...)
   1190 			//   ID3v2.3 only, optional (not present in ID3v2.2):
   1191 			// Relative volume change, bass       $xx xx (xx ...) // f
   1192 			// Peak volume bass                   $xx xx (xx ...)
   1193 
   1194 			$frame_offset = 0;
   1195 			$frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
   1196 			$parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
   1197 			$parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1);
   1198 			$parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1199 			$frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
   1200 			$parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1201 			if ($parsedFrame['incdec']['right'] === false) {
   1202 				$parsedFrame['volumechange']['right'] *= -1;
   1203 			}
   1204 			$frame_offset += $frame_bytesvolume;
   1205 			$parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1206 			if ($parsedFrame['incdec']['left'] === false) {
   1207 				$parsedFrame['volumechange']['left'] *= -1;
   1208 			}
   1209 			$frame_offset += $frame_bytesvolume;
   1210 			$parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1211 			$frame_offset += $frame_bytesvolume;
   1212 			$parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1213 			$frame_offset += $frame_bytesvolume;
   1214 			if ($id3v2_majorversion == 3) {
   1215 				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
   1216 				if (strlen($parsedFrame['data']) > 0) {
   1217 					$parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
   1218 					$parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1);
   1219 					$parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1220 					if ($parsedFrame['incdec']['rightrear'] === false) {
   1221 						$parsedFrame['volumechange']['rightrear'] *= -1;
   1222 					}
   1223 					$frame_offset += $frame_bytesvolume;
   1224 					$parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1225 					if ($parsedFrame['incdec']['leftrear'] === false) {
   1226 						$parsedFrame['volumechange']['leftrear'] *= -1;
   1227 					}
   1228 					$frame_offset += $frame_bytesvolume;
   1229 					$parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1230 					$frame_offset += $frame_bytesvolume;
   1231 					$parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1232 					$frame_offset += $frame_bytesvolume;
   1233 				}
   1234 				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
   1235 				if (strlen($parsedFrame['data']) > 0) {
   1236 					$parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
   1237 					$parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1238 					if ($parsedFrame['incdec']['center'] === false) {
   1239 						$parsedFrame['volumechange']['center'] *= -1;
   1240 					}
   1241 					$frame_offset += $frame_bytesvolume;
   1242 					$parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1243 					$frame_offset += $frame_bytesvolume;
   1244 				}
   1245 				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
   1246 				if (strlen($parsedFrame['data']) > 0) {
   1247 					$parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
   1248 					$parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1249 					if ($parsedFrame['incdec']['bass'] === false) {
   1250 						$parsedFrame['volumechange']['bass'] *= -1;
   1251 					}
   1252 					$frame_offset += $frame_bytesvolume;
   1253 					$parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
   1254 					$frame_offset += $frame_bytesvolume;
   1255 				}
   1256 			}
   1257 			unset($parsedFrame['data']);
   1258 
   1259 
   1260 		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
   1261 			//   There may be more than one 'EQU2' frame in each tag,
   1262 			//   but only one with the same identification string
   1263 			// <Header of 'Equalisation (2)', ID: 'EQU2'>
   1264 			// Interpolation method  $xx
   1265 			//   $00  Band
   1266 			//   $01  Linear
   1267 			// Identification        <text string> $00
   1268 			//   The following is then repeated for every adjustment point
   1269 			// Frequency          $xx xx
   1270 			// Volume adjustment  $xx xx
   1271 
   1272 			$frame_offset = 0;
   1273 			$frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1274 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1275 			$frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1276 			if (ord($frame_idstring) === 0) {
   1277 				$frame_idstring = '';
   1278 			}
   1279 			$parsedFrame['description'] = $frame_idstring;
   1280 			$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
   1281 			while (strlen($frame_remainingdata)) {
   1282 				$frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
   1283 				$parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
   1284 				$frame_remainingdata = substr($frame_remainingdata, 4);
   1285 			}
   1286 			$parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
   1287 			unset($parsedFrame['data']);
   1288 
   1289 
   1290 		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
   1291 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
   1292 			//   There may only be one 'EQUA' frame in each tag
   1293 			// <Header for 'Relative volume adjustment', ID: 'EQU'>
   1294 			// Adjustment bits    $xx
   1295 			//   This is followed by 2 bytes + ('adjustment bits' rounded up to the
   1296 			//   nearest byte) for every equalisation band in the following format,
   1297 			//   giving a frequency range of 0 - 32767Hz:
   1298 			// Increment/decrement   %x (MSB of the Frequency)
   1299 			// Frequency             (lower 15 bits)
   1300 			// Adjustment            $xx (xx ...)
   1301 
   1302 			$frame_offset = 0;
   1303 			$parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
   1304 			$frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
   1305 
   1306 			$frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
   1307 			while (strlen($frame_remainingdata) > 0) {
   1308 				$frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
   1309 				$frame_incdec    = (bool) substr($frame_frequencystr, 0, 1);
   1310 				$frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
   1311 				$parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
   1312 				$parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
   1313 				if ($parsedFrame[$frame_frequency]['incdec'] === false) {
   1314 					$parsedFrame[$frame_frequency]['adjustment'] *= -1;
   1315 				}
   1316 				$frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
   1317 			}
   1318 			unset($parsedFrame['data']);
   1319 
   1320 
   1321 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
   1322 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
   1323 			//   There may only be one 'RVRB' frame in each tag.
   1324 			// <Header for 'Reverb', ID: 'RVRB'>
   1325 			// Reverb left (ms)                 $xx xx
   1326 			// Reverb right (ms)                $xx xx
   1327 			// Reverb bounces, left             $xx
   1328 			// Reverb bounces, right            $xx
   1329 			// Reverb feedback, left to left    $xx
   1330 			// Reverb feedback, left to right   $xx
   1331 			// Reverb feedback, right to right  $xx
   1332 			// Reverb feedback, right to left   $xx
   1333 			// Premix left to right             $xx
   1334 			// Premix right to left             $xx
   1335 
   1336 			$frame_offset = 0;
   1337 			$parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
   1338 			$frame_offset += 2;
   1339 			$parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
   1340 			$frame_offset += 2;
   1341 			$parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1342 			$parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1343 			$parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1344 			$parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1345 			$parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1346 			$parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1347 			$parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1348 			$parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1349 			unset($parsedFrame['data']);
   1350 
   1351 
   1352 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
   1353 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
   1354 			//   There may be several pictures attached to one file,
   1355 			//   each in their individual 'APIC' frame, but only one
   1356 			//   with the same content descriptor
   1357 			// <Header for 'Attached picture', ID: 'APIC'>
   1358 			// Text encoding      $xx
   1359 			// ID3v2.3+ => MIME type          <text string> $00
   1360 			// ID3v2.2  => Image format       $xx xx xx
   1361 			// Picture type       $xx
   1362 			// Description        <text string according to encoding> $00 (00)
   1363 			// Picture data       <binary data>
   1364 
   1365 			$frame_offset = 0;
   1366 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1367 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
   1368 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   1369 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   1370 				$frame_textencoding_terminator = "\x00";
   1371 			}
   1372 
   1373 			if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
   1374 				$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
   1375 				if (strtolower($frame_imagetype) == 'ima') {
   1376 					// complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
   1377 					// MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
   1378 					$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1379 					$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1380 					if (ord($frame_mimetype) === 0) {
   1381 						$frame_mimetype = '';
   1382 					}
   1383 					$frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
   1384 					if ($frame_imagetype == 'JPEG') {
   1385 						$frame_imagetype = 'JPG';
   1386 					}
   1387 					$frame_offset = $frame_terminatorpos + strlen("\x00");
   1388 				} else {
   1389 					$frame_offset += 3;
   1390 				}
   1391 			}
   1392 			if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
   1393 				$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1394 				$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1395 				if (ord($frame_mimetype) === 0) {
   1396 					$frame_mimetype = '';
   1397 				}
   1398 				$frame_offset = $frame_terminatorpos + strlen("\x00");
   1399 			}
   1400 
   1401 			$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1402 
   1403 			if ($frame_offset >= $parsedFrame['datalength']) {
   1404 				$this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
   1405 			} else {
   1406 				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   1407 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   1408 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   1409 				}
   1410 				$parsedFrame['description']   = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1411 				$parsedFrame['description']   = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
   1412 				$parsedFrame['encodingid']    = $frame_textencoding;
   1413 				$parsedFrame['encoding']      = $this->TextEncodingNameLookup($frame_textencoding);
   1414 
   1415 				if ($id3v2_majorversion == 2) {
   1416 					$parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null;
   1417 				} else {
   1418 					$parsedFrame['mime']      = isset($frame_mimetype) ? $frame_mimetype : null;
   1419 				}
   1420 				$parsedFrame['picturetypeid'] = $frame_picturetype;
   1421 				$parsedFrame['picturetype']   = $this->APICPictureTypeLookup($frame_picturetype);
   1422 				$parsedFrame['data']          = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
   1423 				$parsedFrame['datalength']    = strlen($parsedFrame['data']);
   1424 
   1425 				$parsedFrame['image_mime']    = '';
   1426 				$imageinfo = array();
   1427 				if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
   1428 					if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
   1429 						$parsedFrame['image_mime']       = image_type_to_mime_type($imagechunkcheck[2]);
   1430 						if ($imagechunkcheck[0]) {
   1431 							$parsedFrame['image_width']  = $imagechunkcheck[0];
   1432 						}
   1433 						if ($imagechunkcheck[1]) {
   1434 							$parsedFrame['image_height'] = $imagechunkcheck[1];
   1435 						}
   1436 					}
   1437 				}
   1438 
   1439 				do {
   1440 					if ($this->getid3->option_save_attachments === false) {
   1441 						// skip entirely
   1442 						unset($parsedFrame['data']);
   1443 						break;
   1444 					}
   1445 					$dir = '';
   1446 					if ($this->getid3->option_save_attachments === true) {
   1447 						// great
   1448 /*
   1449 					} elseif (is_int($this->getid3->option_save_attachments)) {
   1450 						if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
   1451 							// too big, skip
   1452 							$this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
   1453 							unset($parsedFrame['data']);
   1454 							break;
   1455 						}
   1456 */
   1457 					} elseif (is_string($this->getid3->option_save_attachments)) {
   1458 						$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
   1459 						if (!is_dir($dir) || !getID3::is_writable($dir)) {
   1460 							// cannot write, skip
   1461 							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
   1462 							unset($parsedFrame['data']);
   1463 							break;
   1464 						}
   1465 					}
   1466 					// if we get this far, must be OK
   1467 					if (is_string($this->getid3->option_save_attachments)) {
   1468 						$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
   1469 						if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
   1470 							file_put_contents($destination_filename, $parsedFrame['data']);
   1471 						} else {
   1472 							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
   1473 						}
   1474 						$parsedFrame['data_filename'] = $destination_filename;
   1475 						unset($parsedFrame['data']);
   1476 					} else {
   1477 						if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   1478 							if (!isset($info['id3v2']['comments']['picture'])) {
   1479 								$info['id3v2']['comments']['picture'] = array();
   1480 							}
   1481 							$comments_picture_data = array();
   1482 							foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
   1483 								if (isset($parsedFrame[$picture_key])) {
   1484 									$comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
   1485 								}
   1486 							}
   1487 							$info['id3v2']['comments']['picture'][] = $comments_picture_data;
   1488 							unset($comments_picture_data);
   1489 						}
   1490 					}
   1491 				} while (false);
   1492 			}
   1493 
   1494 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
   1495 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
   1496 			//   There may be more than one 'GEOB' frame in each tag,
   1497 			//   but only one with the same content descriptor
   1498 			// <Header for 'General encapsulated object', ID: 'GEOB'>
   1499 			// Text encoding          $xx
   1500 			// MIME type              <text string> $00
   1501 			// Filename               <text string according to encoding> $00 (00)
   1502 			// Content description    <text string according to encoding> $00 (00)
   1503 			// Encapsulated object    <binary data>
   1504 
   1505 			$frame_offset = 0;
   1506 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1507 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
   1508 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   1509 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   1510 				$frame_textencoding_terminator = "\x00";
   1511 			}
   1512 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1513 			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1514 			if (ord($frame_mimetype) === 0) {
   1515 				$frame_mimetype = '';
   1516 			}
   1517 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1518 
   1519 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   1520 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   1521 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   1522 			}
   1523 			$frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1524 			if (ord($frame_filename) === 0) {
   1525 				$frame_filename = '';
   1526 			}
   1527 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
   1528 
   1529 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   1530 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   1531 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   1532 			}
   1533 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1534 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
   1535 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
   1536 
   1537 			$parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
   1538 			$parsedFrame['encodingid']  = $frame_textencoding;
   1539 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
   1540 
   1541 			$parsedFrame['mime']        = $frame_mimetype;
   1542 			$parsedFrame['filename']    = $frame_filename;
   1543 			unset($parsedFrame['data']);
   1544 
   1545 
   1546 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
   1547 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
   1548 			//   There may only be one 'PCNT' frame in each tag.
   1549 			//   When the counter reaches all one's, one byte is inserted in
   1550 			//   front of the counter thus making the counter eight bits bigger
   1551 			// <Header for 'Play counter', ID: 'PCNT'>
   1552 			// Counter        $xx xx xx xx (xx ...)
   1553 
   1554 			$parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);
   1555 
   1556 
   1557 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
   1558 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
   1559 			//   There may be more than one 'POPM' frame in each tag,
   1560 			//   but only one with the same email address
   1561 			// <Header for 'Popularimeter', ID: 'POPM'>
   1562 			// Email to user   <text string> $00
   1563 			// Rating          $xx
   1564 			// Counter         $xx xx xx xx (xx ...)
   1565 
   1566 			$frame_offset = 0;
   1567 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1568 			$frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1569 			if (ord($frame_emailaddress) === 0) {
   1570 				$frame_emailaddress = '';
   1571 			}
   1572 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1573 			$frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1574 			$parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
   1575 			$parsedFrame['email']   = $frame_emailaddress;
   1576 			$parsedFrame['rating']  = $frame_rating;
   1577 			unset($parsedFrame['data']);
   1578 
   1579 
   1580 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
   1581 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
   1582 			//   There may only be one 'RBUF' frame in each tag
   1583 			// <Header for 'Recommended buffer size', ID: 'RBUF'>
   1584 			// Buffer size               $xx xx xx
   1585 			// Embedded info flag        %0000000x
   1586 			// Offset to next tag        $xx xx xx xx
   1587 
   1588 			$frame_offset = 0;
   1589 			$parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
   1590 			$frame_offset += 3;
   1591 
   1592 			$frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
   1593 			$parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
   1594 			$parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
   1595 			unset($parsedFrame['data']);
   1596 
   1597 
   1598 		} elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
   1599 			//   There may be more than one 'CRM' frame in a tag,
   1600 			//   but only one with the same 'owner identifier'
   1601 			// <Header for 'Encrypted meta frame', ID: 'CRM'>
   1602 			// Owner identifier      <textstring> $00 (00)
   1603 			// Content/explanation   <textstring> $00 (00)
   1604 			// Encrypted datablock   <binary data>
   1605 
   1606 			$frame_offset = 0;
   1607 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1608 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1609 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1610 
   1611 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1612 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1613 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
   1614 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1615 
   1616 			$parsedFrame['ownerid']     = $frame_ownerid;
   1617 			$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
   1618 			unset($parsedFrame['data']);
   1619 
   1620 
   1621 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
   1622 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
   1623 			//   There may be more than one 'AENC' frames in a tag,
   1624 			//   but only one with the same 'Owner identifier'
   1625 			// <Header for 'Audio encryption', ID: 'AENC'>
   1626 			// Owner identifier   <text string> $00
   1627 			// Preview start      $xx xx
   1628 			// Preview length     $xx xx
   1629 			// Encryption info    <binary data>
   1630 
   1631 			$frame_offset = 0;
   1632 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1633 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1634 			if (ord($frame_ownerid) === 0) {
   1635 				$frame_ownerid = '';
   1636 			}
   1637 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1638 			$parsedFrame['ownerid'] = $frame_ownerid;
   1639 			$parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
   1640 			$frame_offset += 2;
   1641 			$parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
   1642 			$frame_offset += 2;
   1643 			$parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
   1644 			unset($parsedFrame['data']);
   1645 
   1646 
   1647 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
   1648 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {    // 4.22  LNK  Linked information
   1649 			//   There may be more than one 'LINK' frame in a tag,
   1650 			//   but only one with the same contents
   1651 			// <Header for 'Linked information', ID: 'LINK'>
   1652 			// ID3v2.3+ => Frame identifier   $xx xx xx xx
   1653 			// ID3v2.2  => Frame identifier   $xx xx xx
   1654 			// URL                            <text string> $00
   1655 			// ID and additional data         <text string(s)>
   1656 
   1657 			$frame_offset = 0;
   1658 			if ($id3v2_majorversion == 2) {
   1659 				$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
   1660 				$frame_offset += 3;
   1661 			} else {
   1662 				$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
   1663 				$frame_offset += 4;
   1664 			}
   1665 
   1666 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1667 			$frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1668 			if (ord($frame_url) === 0) {
   1669 				$frame_url = '';
   1670 			}
   1671 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1672 			$parsedFrame['url'] = $frame_url;
   1673 
   1674 			$parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
   1675 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
   1676 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
   1677 			}
   1678 			unset($parsedFrame['data']);
   1679 
   1680 
   1681 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
   1682 			//   There may only be one 'POSS' frame in each tag
   1683 			// <Head for 'Position synchronisation', ID: 'POSS'>
   1684 			// Time stamp format         $xx
   1685 			// Position                  $xx (xx ...)
   1686 
   1687 			$frame_offset = 0;
   1688 			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1689 			$parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
   1690 			unset($parsedFrame['data']);
   1691 
   1692 
   1693 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
   1694 			//   There may be more than one 'Terms of use' frame in a tag,
   1695 			//   but only one with the same 'Language'
   1696 			// <Header for 'Terms of use frame', ID: 'USER'>
   1697 			// Text encoding        $xx
   1698 			// Language             $xx xx xx
   1699 			// The actual text      <text string according to encoding>
   1700 
   1701 			$frame_offset = 0;
   1702 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1703 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   1704 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   1705 			}
   1706 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
   1707 			$frame_offset += 3;
   1708 			$parsedFrame['language']     = $frame_language;
   1709 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
   1710 			$parsedFrame['encodingid']   = $frame_textencoding;
   1711 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
   1712 
   1713 			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
   1714 			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
   1715 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   1716 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
   1717 			}
   1718 			unset($parsedFrame['data']);
   1719 
   1720 
   1721 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
   1722 			//   There may only be one 'OWNE' frame in a tag
   1723 			// <Header for 'Ownership frame', ID: 'OWNE'>
   1724 			// Text encoding     $xx
   1725 			// Price paid        <text string> $00
   1726 			// Date of purch.    <text string>
   1727 			// Seller            <text string according to encoding>
   1728 
   1729 			$frame_offset = 0;
   1730 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1731 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   1732 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   1733 			}
   1734 			$parsedFrame['encodingid'] = $frame_textencoding;
   1735 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
   1736 
   1737 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1738 			$frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1739 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1740 
   1741 			$parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
   1742 			$parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
   1743 			$parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);
   1744 
   1745 			$parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
   1746 			if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
   1747 				$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
   1748 			}
   1749 			$frame_offset += 8;
   1750 
   1751 			$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
   1752 			$parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding));
   1753 			unset($parsedFrame['data']);
   1754 
   1755 
   1756 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
   1757 			//   There may be more than one 'commercial frame' in a tag,
   1758 			//   but no two may be identical
   1759 			// <Header for 'Commercial frame', ID: 'COMR'>
   1760 			// Text encoding      $xx
   1761 			// Price string       <text string> $00
   1762 			// Valid until        <text string>
   1763 			// Contact URL        <text string> $00
   1764 			// Received as        $xx
   1765 			// Name of seller     <text string according to encoding> $00 (00)
   1766 			// Description        <text string according to encoding> $00 (00)
   1767 			// Picture MIME type  <string> $00
   1768 			// Seller logo        <binary data>
   1769 
   1770 			$frame_offset = 0;
   1771 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1772 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
   1773 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   1774 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   1775 				$frame_textencoding_terminator = "\x00";
   1776 			}
   1777 
   1778 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1779 			$frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1780 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1781 			$frame_rawpricearray = explode('/', $frame_pricestring);
   1782 			foreach ($frame_rawpricearray as $key => $val) {
   1783 				$frame_currencyid = substr($val, 0, 3);
   1784 				$parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
   1785 				$parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3);
   1786 			}
   1787 
   1788 			$frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
   1789 			$frame_offset += 8;
   1790 
   1791 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1792 			$frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1793 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1794 
   1795 			$frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1796 
   1797 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   1798 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   1799 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   1800 			}
   1801 			$frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1802 			if (ord($frame_sellername) === 0) {
   1803 				$frame_sellername = '';
   1804 			}
   1805 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
   1806 
   1807 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   1808 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   1809 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   1810 			}
   1811 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1812 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
   1813 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
   1814 
   1815 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1816 			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1817 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1818 
   1819 			$frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
   1820 
   1821 			$parsedFrame['encodingid']        = $frame_textencoding;
   1822 			$parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);
   1823 
   1824 			$parsedFrame['pricevaliduntil']   = $frame_datestring;
   1825 			$parsedFrame['contacturl']        = $frame_contacturl;
   1826 			$parsedFrame['receivedasid']      = $frame_receivedasid;
   1827 			$parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
   1828 			$parsedFrame['sellername']        = $frame_sellername;
   1829 			$parsedFrame['mime']              = $frame_mimetype;
   1830 			$parsedFrame['logo']              = $frame_sellerlogo;
   1831 			unset($parsedFrame['data']);
   1832 
   1833 
   1834 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
   1835 			//   There may be several 'ENCR' frames in a tag,
   1836 			//   but only one containing the same symbol
   1837 			//   and only one containing the same owner identifier
   1838 			// <Header for 'Encryption method registration', ID: 'ENCR'>
   1839 			// Owner identifier    <text string> $00
   1840 			// Method symbol       $xx
   1841 			// Encryption data     <binary data>
   1842 
   1843 			$frame_offset = 0;
   1844 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1845 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1846 			if (ord($frame_ownerid) === 0) {
   1847 				$frame_ownerid = '';
   1848 			}
   1849 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1850 
   1851 			$parsedFrame['ownerid']      = $frame_ownerid;
   1852 			$parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1853 			$parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
   1854 
   1855 
   1856 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)
   1857 
   1858 			//   There may be several 'GRID' frames in a tag,
   1859 			//   but only one containing the same symbol
   1860 			//   and only one containing the same owner identifier
   1861 			// <Header for 'Group ID registration', ID: 'GRID'>
   1862 			// Owner identifier      <text string> $00
   1863 			// Group symbol          $xx
   1864 			// Group dependent data  <binary data>
   1865 
   1866 			$frame_offset = 0;
   1867 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1868 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1869 			if (ord($frame_ownerid) === 0) {
   1870 				$frame_ownerid = '';
   1871 			}
   1872 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1873 
   1874 			$parsedFrame['ownerid']       = $frame_ownerid;
   1875 			$parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1876 			$parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);
   1877 
   1878 
   1879 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
   1880 			//   The tag may contain more than one 'PRIV' frame
   1881 			//   but only with different contents
   1882 			// <Header for 'Private frame', ID: 'PRIV'>
   1883 			// Owner identifier      <text string> $00
   1884 			// The private data      <binary data>
   1885 
   1886 			$frame_offset = 0;
   1887 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
   1888 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   1889 			if (ord($frame_ownerid) === 0) {
   1890 				$frame_ownerid = '';
   1891 			}
   1892 			$frame_offset = $frame_terminatorpos + strlen("\x00");
   1893 
   1894 			$parsedFrame['ownerid'] = $frame_ownerid;
   1895 			$parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);
   1896 
   1897 
   1898 		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
   1899 			//   There may be more than one 'signature frame' in a tag,
   1900 			//   but no two may be identical
   1901 			// <Header for 'Signature frame', ID: 'SIGN'>
   1902 			// Group symbol      $xx
   1903 			// Signature         <binary data>
   1904 
   1905 			$frame_offset = 0;
   1906 			$parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1907 			$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
   1908 
   1909 
   1910 		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
   1911 			//   There may only be one 'seek frame' in a tag
   1912 			// <Header for 'Seek frame', ID: 'SEEK'>
   1913 			// Minimum offset to next tag       $xx xx xx xx
   1914 
   1915 			$frame_offset = 0;
   1916 			$parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
   1917 
   1918 
   1919 		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
   1920 			//   There may only be one 'audio seek point index' frame in a tag
   1921 			// <Header for 'Seek Point Index', ID: 'ASPI'>
   1922 			// Indexed data start (S)         $xx xx xx xx
   1923 			// Indexed data length (L)        $xx xx xx xx
   1924 			// Number of index points (N)     $xx xx
   1925 			// Bits per index point (b)       $xx
   1926 			//   Then for every index point the following data is included:
   1927 			// Fraction at index (Fi)          $xx (xx)
   1928 
   1929 			$frame_offset = 0;
   1930 			$parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
   1931 			$frame_offset += 4;
   1932 			$parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
   1933 			$frame_offset += 4;
   1934 			$parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
   1935 			$frame_offset += 2;
   1936 			$parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   1937 			$frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
   1938 			for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
   1939 				$parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
   1940 				$frame_offset += $frame_bytesperpoint;
   1941 			}
   1942 			unset($parsedFrame['data']);
   1943 
   1944 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
   1945 			// http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
   1946 			//   There may only be one 'RGAD' frame in a tag
   1947 			// <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
   1948 			// Peak Amplitude                      $xx $xx $xx $xx
   1949 			// Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
   1950 			// Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
   1951 			//   a - name code
   1952 			//   b - originator code
   1953 			//   c - sign bit
   1954 			//   d - replay gain adjustment
   1955 
   1956 			$frame_offset = 0;
   1957 			$parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
   1958 			$frame_offset += 4;
   1959 			$rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
   1960 			$frame_offset += 2;
   1961 			$rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
   1962 			$frame_offset += 2;
   1963 			$parsedFrame['raw']['track']['name']       = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
   1964 			$parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
   1965 			$parsedFrame['raw']['track']['signbit']    = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
   1966 			$parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
   1967 			$parsedFrame['raw']['album']['name']       = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
   1968 			$parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
   1969 			$parsedFrame['raw']['album']['signbit']    = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
   1970 			$parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
   1971 			$parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
   1972 			$parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
   1973 			$parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
   1974 			$parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
   1975 			$parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
   1976 			$parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
   1977 
   1978 			$info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude'];
   1979 			$info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
   1980 			$info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
   1981 			$info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
   1982 			$info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
   1983 
   1984 			unset($parsedFrame['data']);
   1985 
   1986 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
   1987 			// http://id3.org/id3v2-chapters-1.0
   1988 			// <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
   1989 			// Element ID      <text string> $00
   1990 			// Start time      $xx xx xx xx
   1991 			// End time        $xx xx xx xx
   1992 			// Start offset    $xx xx xx xx
   1993 			// End offset      $xx xx xx xx
   1994 			// <Optional embedded sub-frames>
   1995 
   1996 			$frame_offset = 0;
   1997 			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
   1998 			$frame_offset += strlen($parsedFrame['element_id']."\x00");
   1999 			$parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
   2000 			$frame_offset += 4;
   2001 			$parsedFrame['time_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
   2002 			$frame_offset += 4;
   2003 			if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
   2004 				// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
   2005 				$parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
   2006 			}
   2007 			$frame_offset += 4;
   2008 			if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
   2009 				// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
   2010 				$parsedFrame['offset_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
   2011 			}
   2012 			$frame_offset += 4;
   2013 
   2014 			if ($frame_offset < strlen($parsedFrame['data'])) {
   2015 				$parsedFrame['subframes'] = array();
   2016 				while ($frame_offset < strlen($parsedFrame['data'])) {
   2017 					// <Optional embedded sub-frames>
   2018 					$subframe = array();
   2019 					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
   2020 					$frame_offset += 4;
   2021 					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
   2022 					$frame_offset += 4;
   2023 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
   2024 					$frame_offset += 2;
   2025 					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
   2026 						$this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
   2027 						break;
   2028 					}
   2029 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
   2030 					$frame_offset += $subframe['size'];
   2031 
   2032 					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
   2033 					$subframe['text']       =     substr($subframe_rawdata, 1);
   2034 					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
   2035 					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));
   2036 					switch (substr($encoding_converted_text, 0, 2)) {
   2037 						case "\xFF\xFE":
   2038 						case "\xFE\xFF":
   2039 							switch (strtoupper($info['id3v2']['encoding'])) {
   2040 								case 'ISO-8859-1':
   2041 								case 'UTF-8':
   2042 									$encoding_converted_text = substr($encoding_converted_text, 2);
   2043 									// remove unwanted byte-order-marks
   2044 									break;
   2045 								default:
   2046 									// ignore
   2047 									break;
   2048 							}
   2049 							break;
   2050 						default:
   2051 							// do not remove BOM
   2052 							break;
   2053 					}
   2054 
   2055 					switch ($subframe['name']) {
   2056 						case 'TIT2':
   2057 							$parsedFrame['chapter_name']        = $encoding_converted_text;
   2058 							$parsedFrame['subframes'][] = $subframe;
   2059 							break;
   2060 						case 'TIT3':
   2061 							$parsedFrame['chapter_description'] = $encoding_converted_text;
   2062 							$parsedFrame['subframes'][] = $subframe;
   2063 							break;
   2064 						case 'WXXX':
   2065 							list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
   2066 							$parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
   2067 							$parsedFrame['subframes'][] = $subframe;
   2068 							break;
   2069 						case 'APIC':
   2070 							if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) {
   2071 								list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches;
   2072 								$subframe['image_mime']   = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime));
   2073 								$subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype);
   2074 								$subframe['description']  = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description));
   2075 								if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) {
   2076 									// the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16)
   2077 									// the above regex assumes one byte, if it's actually two then strip the second one here
   2078 									$subframe_apic_picturedata = substr($subframe_apic_picturedata, 1);
   2079 								}
   2080 								$subframe['data'] = $subframe_apic_picturedata;
   2081 								unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata);
   2082 								unset($subframe['text'], $parsedFrame['text']);
   2083 								$parsedFrame['subframes'][] = $subframe;
   2084 								$parsedFrame['picture_present'] = true;
   2085 							} else {
   2086 								$this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format');
   2087 							}
   2088 							break;
   2089 						default:
   2090 							$this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)');
   2091 							break;
   2092 					}
   2093 				}
   2094 				unset($subframe_rawdata, $subframe, $encoding_converted_text);
   2095 				unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC
   2096 			}
   2097 
   2098 			$id3v2_chapter_entry = array();
   2099 			foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) {
   2100 				if (isset($parsedFrame[$id3v2_chapter_key])) {
   2101 					$id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
   2102 				}
   2103 			}
   2104 			if (!isset($info['id3v2']['chapters'])) {
   2105 				$info['id3v2']['chapters'] = array();
   2106 			}
   2107 			$info['id3v2']['chapters'][] = $id3v2_chapter_entry;
   2108 			unset($id3v2_chapter_entry, $id3v2_chapter_key);
   2109 
   2110 
   2111 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
   2112 			// http://id3.org/id3v2-chapters-1.0
   2113 			// <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC">           (10 bytes)
   2114 			// Element ID      <text string> $00
   2115 			// CTOC flags        %xx
   2116 			// Entry count       $xx
   2117 			// Child Element ID  <string>$00   /* zero or more child CHAP or CTOC entries */
   2118 			// <Optional embedded sub-frames>
   2119 
   2120 			$frame_offset = 0;
   2121 			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
   2122 			$frame_offset += strlen($parsedFrame['element_id']."\x00");
   2123 			$ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
   2124 			$frame_offset += 1;
   2125 			$parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
   2126 			$frame_offset += 1;
   2127 
   2128 			$terminator_position = null;
   2129 			for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
   2130 				$terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
   2131 				$parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
   2132 				$frame_offset = $terminator_position + 1;
   2133 			}
   2134 
   2135 			$parsedFrame['ctoc_flags']['ordered']   = (bool) ($ctoc_flags_raw & 0x01);
   2136 			$parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
   2137 
   2138 			unset($ctoc_flags_raw, $terminator_position);
   2139 
   2140 			if ($frame_offset < strlen($parsedFrame['data'])) {
   2141 				$parsedFrame['subframes'] = array();
   2142 				while ($frame_offset < strlen($parsedFrame['data'])) {
   2143 					// <Optional embedded sub-frames>
   2144 					$subframe = array();
   2145 					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
   2146 					$frame_offset += 4;
   2147 					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
   2148 					$frame_offset += 4;
   2149 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
   2150 					$frame_offset += 2;
   2151 					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
   2152 						$this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
   2153 						break;
   2154 					}
   2155 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
   2156 					$frame_offset += $subframe['size'];
   2157 
   2158 					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
   2159 					$subframe['text']       =     substr($subframe_rawdata, 1);
   2160 					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
   2161 					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
   2162 					switch (substr($encoding_converted_text, 0, 2)) {
   2163 						case "\xFF\xFE":
   2164 						case "\xFE\xFF":
   2165 							switch (strtoupper($info['id3v2']['encoding'])) {
   2166 								case 'ISO-8859-1':
   2167 								case 'UTF-8':
   2168 									$encoding_converted_text = substr($encoding_converted_text, 2);
   2169 									// remove unwanted byte-order-marks
   2170 									break;
   2171 								default:
   2172 									// ignore
   2173 									break;
   2174 							}
   2175 							break;
   2176 						default:
   2177 							// do not remove BOM
   2178 							break;
   2179 					}
   2180 
   2181 					if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
   2182 						if ($subframe['name'] == 'TIT2') {
   2183 							$parsedFrame['toc_name']        = $encoding_converted_text;
   2184 						} elseif ($subframe['name'] == 'TIT3') {
   2185 							$parsedFrame['toc_description'] = $encoding_converted_text;
   2186 						}
   2187 						$parsedFrame['subframes'][] = $subframe;
   2188 					} else {
   2189 						$this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
   2190 					}
   2191 				}
   2192 				unset($subframe_rawdata, $subframe, $encoding_converted_text);
   2193 			}
   2194 
   2195 		}
   2196 
   2197 		return true;
   2198 	}
   2199 
   2200 	/**
   2201 	 * @param string $data
   2202 	 *
   2203 	 * @return string
   2204 	 */
   2205 	public function DeUnsynchronise($data) {
   2206 		return str_replace("\xFF\x00", "\xFF", $data);
   2207 	}
   2208 
   2209 	/**
   2210 	 * @param int $index
   2211 	 *
   2212 	 * @return string
   2213 	 */
   2214 	public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
   2215 		static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
   2216 			0x00 => 'No more than 128 frames and 1 MB total tag size',
   2217 			0x01 => 'No more than 64 frames and 128 KB total tag size',
   2218 			0x02 => 'No more than 32 frames and 40 KB total tag size',
   2219 			0x03 => 'No more than 32 frames and 4 KB total tag size',
   2220 		);
   2221 		return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
   2222 	}
   2223 
   2224 	/**
   2225 	 * @param int $index
   2226 	 *
   2227 	 * @return string
   2228 	 */
   2229 	public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
   2230 		static $LookupExtendedHeaderRestrictionsTextEncodings = array(
   2231 			0x00 => 'No restrictions',
   2232 			0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
   2233 		);
   2234 		return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
   2235 	}
   2236 
   2237 	/**
   2238 	 * @param int $index
   2239 	 *
   2240 	 * @return string
   2241 	 */
   2242 	public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
   2243 		static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
   2244 			0x00 => 'No restrictions',
   2245 			0x01 => 'No string is longer than 1024 characters',
   2246 			0x02 => 'No string is longer than 128 characters',
   2247 			0x03 => 'No string is longer than 30 characters',
   2248 		);
   2249 		return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
   2250 	}
   2251 
   2252 	/**
   2253 	 * @param int $index
   2254 	 *
   2255 	 * @return string
   2256 	 */
   2257 	public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
   2258 		static $LookupExtendedHeaderRestrictionsImageEncoding = array(
   2259 			0x00 => 'No restrictions',
   2260 			0x01 => 'Images are encoded only with PNG or JPEG',
   2261 		);
   2262 		return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
   2263 	}
   2264 
   2265 	/**
   2266 	 * @param int $index
   2267 	 *
   2268 	 * @return string
   2269 	 */
   2270 	public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
   2271 		static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
   2272 			0x00 => 'No restrictions',
   2273 			0x01 => 'All images are 256x256 pixels or smaller',
   2274 			0x02 => 'All images are 64x64 pixels or smaller',
   2275 			0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
   2276 		);
   2277 		return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
   2278 	}
   2279 
   2280 	/**
   2281 	 * @param string $currencyid
   2282 	 *
   2283 	 * @return string
   2284 	 */
   2285 	public function LookupCurrencyUnits($currencyid) {
   2286 
   2287 		$begin = __LINE__;
   2288 
   2289 		/** This is not a comment!
   2290 
   2291 
   2292 			AED	Dirhams
   2293 			AFA	Afghanis
   2294 			ALL	Leke
   2295 			AMD	Drams
   2296 			ANG	Guilders
   2297 			AOA	Kwanza
   2298 			ARS	Pesos
   2299 			ATS	Schillings
   2300 			AUD	Dollars
   2301 			AWG	Guilders
   2302 			AZM	Manats
   2303 			BAM	Convertible Marka
   2304 			BBD	Dollars
   2305 			BDT	Taka
   2306 			BEF	Francs
   2307 			BGL	Leva
   2308 			BHD	Dinars
   2309 			BIF	Francs
   2310 			BMD	Dollars
   2311 			BND	Dollars
   2312 			BOB	Bolivianos
   2313 			BRL	Brazil Real
   2314 			BSD	Dollars
   2315 			BTN	Ngultrum
   2316 			BWP	Pulas
   2317 			BYR	Rubles
   2318 			BZD	Dollars
   2319 			CAD	Dollars
   2320 			CDF	Congolese Francs
   2321 			CHF	Francs
   2322 			CLP	Pesos
   2323 			CNY	Yuan Renminbi
   2324 			COP	Pesos
   2325 			CRC	Colones
   2326 			CUP	Pesos
   2327 			CVE	Escudos
   2328 			CYP	Pounds
   2329 			CZK	Koruny
   2330 			DEM	Deutsche Marks
   2331 			DJF	Francs
   2332 			DKK	Kroner
   2333 			DOP	Pesos
   2334 			DZD	Algeria Dinars
   2335 			EEK	Krooni
   2336 			EGP	Pounds
   2337 			ERN	Nakfa
   2338 			ESP	Pesetas
   2339 			ETB	Birr
   2340 			EUR	Euro
   2341 			FIM	Markkaa
   2342 			FJD	Dollars
   2343 			FKP	Pounds
   2344 			FRF	Francs
   2345 			GBP	Pounds
   2346 			GEL	Lari
   2347 			GGP	Pounds
   2348 			GHC	Cedis
   2349 			GIP	Pounds
   2350 			GMD	Dalasi
   2351 			GNF	Francs
   2352 			GRD	Drachmae
   2353 			GTQ	Quetzales
   2354 			GYD	Dollars
   2355 			HKD	Dollars
   2356 			HNL	Lempiras
   2357 			HRK	Kuna
   2358 			HTG	Gourdes
   2359 			HUF	Forints
   2360 			IDR	Rupiahs
   2361 			IEP	Pounds
   2362 			ILS	New Shekels
   2363 			IMP	Pounds
   2364 			INR	Rupees
   2365 			IQD	Dinars
   2366 			IRR	Rials
   2367 			ISK	Kronur
   2368 			ITL	Lire
   2369 			JEP	Pounds
   2370 			JMD	Dollars
   2371 			JOD	Dinars
   2372 			JPY	Yen
   2373 			KES	Shillings
   2374 			KGS	Soms
   2375 			KHR	Riels
   2376 			KMF	Francs
   2377 			KPW	Won
   2378 			KWD	Dinars
   2379 			KYD	Dollars
   2380 			KZT	Tenge
   2381 			LAK	Kips
   2382 			LBP	Pounds
   2383 			LKR	Rupees
   2384 			LRD	Dollars
   2385 			LSL	Maloti
   2386 			LTL	Litai
   2387 			LUF	Francs
   2388 			LVL	Lati
   2389 			LYD	Dinars
   2390 			MAD	Dirhams
   2391 			MDL	Lei
   2392 			MGF	Malagasy Francs
   2393 			MKD	Denars
   2394 			MMK	Kyats
   2395 			MNT	Tugriks
   2396 			MOP	Patacas
   2397 			MRO	Ouguiyas
   2398 			MTL	Liri
   2399 			MUR	Rupees
   2400 			MVR	Rufiyaa
   2401 			MWK	Kwachas
   2402 			MXN	Pesos
   2403 			MYR	Ringgits
   2404 			MZM	Meticais
   2405 			NAD	Dollars
   2406 			NGN	Nairas
   2407 			NIO	Gold Cordobas
   2408 			NLG	Guilders
   2409 			NOK	Krone
   2410 			NPR	Nepal Rupees
   2411 			NZD	Dollars
   2412 			OMR	Rials
   2413 			PAB	Balboa
   2414 			PEN	Nuevos Soles
   2415 			PGK	Kina
   2416 			PHP	Pesos
   2417 			PKR	Rupees
   2418 			PLN	Zlotych
   2419 			PTE	Escudos
   2420 			PYG	Guarani
   2421 			QAR	Rials
   2422 			ROL	Lei
   2423 			RUR	Rubles
   2424 			RWF	Rwanda Francs
   2425 			SAR	Riyals
   2426 			SBD	Dollars
   2427 			SCR	Rupees
   2428 			SDD	Dinars
   2429 			SEK	Kronor
   2430 			SGD	Dollars
   2431 			SHP	Pounds
   2432 			SIT	Tolars
   2433 			SKK	Koruny
   2434 			SLL	Leones
   2435 			SOS	Shillings
   2436 			SPL	Luigini
   2437 			SRG	Guilders
   2438 			STD	Dobras
   2439 			SVC	Colones
   2440 			SYP	Pounds
   2441 			SZL	Emalangeni
   2442 			THB	Baht
   2443 			TJR	Rubles
   2444 			TMM	Manats
   2445 			TND	Dinars
   2446 			TOP	Pa'anga
   2447 			TRL	Liras
   2448 			TTD	Dollars
   2449 			TVD	Tuvalu Dollars
   2450 			TWD	New Dollars
   2451 			TZS	Shillings
   2452 			UAH	Hryvnia
   2453 			UGX	Shillings
   2454 			USD	Dollars
   2455 			UYU	Pesos
   2456 			UZS	Sums
   2457 			VAL	Lire
   2458 			VEB	Bolivares
   2459 			VND	Dong
   2460 			VUV	Vatu
   2461 			WST	Tala
   2462 			XAF	Francs
   2463 			XAG	Ounces
   2464 			XAU	Ounces
   2465 			XCD	Dollars
   2466 			XDR	Special Drawing Rights
   2467 			XPD	Ounces
   2468 			XPF	Francs
   2469 			XPT	Ounces
   2470 			YER	Rials
   2471 			YUM	New Dinars
   2472 			ZAR	Rand
   2473 			ZMK	Kwacha
   2474 			ZWD	Zimbabwe Dollars
   2475 
   2476 		*/
   2477 
   2478 		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
   2479 	}
   2480 
   2481 	/**
   2482 	 * @param string $currencyid
   2483 	 *
   2484 	 * @return string
   2485 	 */
   2486 	public function LookupCurrencyCountry($currencyid) {
   2487 
   2488 		$begin = __LINE__;
   2489 
   2490 		/** This is not a comment!
   2491 
   2492 			AED	United Arab Emirates
   2493 			AFA	Afghanistan
   2494 			ALL	Albania
   2495 			AMD	Armenia
   2496 			ANG	Netherlands Antilles
   2497 			AOA	Angola
   2498 			ARS	Argentina
   2499 			ATS	Austria
   2500 			AUD	Australia
   2501 			AWG	Aruba
   2502 			AZM	Azerbaijan
   2503 			BAM	Bosnia and Herzegovina
   2504 			BBD	Barbados
   2505 			BDT	Bangladesh
   2506 			BEF	Belgium
   2507 			BGL	Bulgaria
   2508 			BHD	Bahrain
   2509 			BIF	Burundi
   2510 			BMD	Bermuda
   2511 			BND	Brunei Darussalam
   2512 			BOB	Bolivia
   2513 			BRL	Brazil
   2514 			BSD	Bahamas
   2515 			BTN	Bhutan
   2516 			BWP	Botswana
   2517 			BYR	Belarus
   2518 			BZD	Belize
   2519 			CAD	Canada
   2520 			CDF	Congo/Kinshasa
   2521 			CHF	Switzerland
   2522 			CLP	Chile
   2523 			CNY	China
   2524 			COP	Colombia
   2525 			CRC	Costa Rica
   2526 			CUP	Cuba
   2527 			CVE	Cape Verde
   2528 			CYP	Cyprus
   2529 			CZK	Czech Republic
   2530 			DEM	Germany
   2531 			DJF	Djibouti
   2532 			DKK	Denmark
   2533 			DOP	Dominican Republic
   2534 			DZD	Algeria
   2535 			EEK	Estonia
   2536 			EGP	Egypt
   2537 			ERN	Eritrea
   2538 			ESP	Spain
   2539 			ETB	Ethiopia
   2540 			EUR	Euro Member Countries
   2541 			FIM	Finland
   2542 			FJD	Fiji
   2543 			FKP	Falkland Islands (Malvinas)
   2544 			FRF	France
   2545 			GBP	United Kingdom
   2546 			GEL	Georgia
   2547 			GGP	Guernsey
   2548 			GHC	Ghana
   2549 			GIP	Gibraltar
   2550 			GMD	Gambia
   2551 			GNF	Guinea
   2552 			GRD	Greece
   2553 			GTQ	Guatemala
   2554 			GYD	Guyana
   2555 			HKD	Hong Kong
   2556 			HNL	Honduras
   2557 			HRK	Croatia
   2558 			HTG	Haiti
   2559 			HUF	Hungary
   2560 			IDR	Indonesia
   2561 			IEP	Ireland (Eire)
   2562 			ILS	Israel
   2563 			IMP	Isle of Man
   2564 			INR	India
   2565 			IQD	Iraq
   2566 			IRR	Iran
   2567 			ISK	Iceland
   2568 			ITL	Italy
   2569 			JEP	Jersey
   2570 			JMD	Jamaica
   2571 			JOD	Jordan
   2572 			JPY	Japan
   2573 			KES	Kenya
   2574 			KGS	Kyrgyzstan
   2575 			KHR	Cambodia
   2576 			KMF	Comoros
   2577 			KPW	Korea
   2578 			KWD	Kuwait
   2579 			KYD	Cayman Islands
   2580 			KZT	Kazakstan
   2581 			LAK	Laos
   2582 			LBP	Lebanon
   2583 			LKR	Sri Lanka
   2584 			LRD	Liberia
   2585 			LSL	Lesotho
   2586 			LTL	Lithuania
   2587 			LUF	Luxembourg
   2588 			LVL	Latvia
   2589 			LYD	Libya
   2590 			MAD	Morocco
   2591 			MDL	Moldova
   2592 			MGF	Madagascar
   2593 			MKD	Macedonia
   2594 			MMK	Myanmar (Burma)
   2595 			MNT	Mongolia
   2596 			MOP	Macau
   2597 			MRO	Mauritania
   2598 			MTL	Malta
   2599 			MUR	Mauritius
   2600 			MVR	Maldives (Maldive Islands)
   2601 			MWK	Malawi
   2602 			MXN	Mexico
   2603 			MYR	Malaysia
   2604 			MZM	Mozambique
   2605 			NAD	Namibia
   2606 			NGN	Nigeria
   2607 			NIO	Nicaragua
   2608 			NLG	Netherlands (Holland)
   2609 			NOK	Norway
   2610 			NPR	Nepal
   2611 			NZD	New Zealand
   2612 			OMR	Oman
   2613 			PAB	Panama
   2614 			PEN	Peru
   2615 			PGK	Papua New Guinea
   2616 			PHP	Philippines
   2617 			PKR	Pakistan
   2618 			PLN	Poland
   2619 			PTE	Portugal
   2620 			PYG	Paraguay
   2621 			QAR	Qatar
   2622 			ROL	Romania
   2623 			RUR	Russia
   2624 			RWF	Rwanda
   2625 			SAR	Saudi Arabia
   2626 			SBD	Solomon Islands
   2627 			SCR	Seychelles
   2628 			SDD	Sudan
   2629 			SEK	Sweden
   2630 			SGD	Singapore
   2631 			SHP	Saint Helena
   2632 			SIT	Slovenia
   2633 			SKK	Slovakia
   2634 			SLL	Sierra Leone
   2635 			SOS	Somalia
   2636 			SPL	Seborga
   2637 			SRG	Suriname
   2638 			STD	São Tome and Principe
   2639 			SVC	El Salvador
   2640 			SYP	Syria
   2641 			SZL	Swaziland
   2642 			THB	Thailand
   2643 			TJR	Tajikistan
   2644 			TMM	Turkmenistan
   2645 			TND	Tunisia
   2646 			TOP	Tonga
   2647 			TRL	Turkey
   2648 			TTD	Trinidad and Tobago
   2649 			TVD	Tuvalu
   2650 			TWD	Taiwan
   2651 			TZS	Tanzania
   2652 			UAH	Ukraine
   2653 			UGX	Uganda
   2654 			USD	United States of America
   2655 			UYU	Uruguay
   2656 			UZS	Uzbekistan
   2657 			VAL	Vatican City
   2658 			VEB	Venezuela
   2659 			VND	Viet Nam
   2660 			VUV	Vanuatu
   2661 			WST	Samoa
   2662 			XAF	Communauté Financière Africaine
   2663 			XAG	Silver
   2664 			XAU	Gold
   2665 			XCD	East Caribbean
   2666 			XDR	International Monetary Fund
   2667 			XPD	Palladium
   2668 			XPF	Comptoirs Français du Pacifique
   2669 			XPT	Platinum
   2670 			YER	Yemen
   2671 			YUM	Yugoslavia
   2672 			ZAR	South Africa
   2673 			ZMK	Zambia
   2674 			ZWD	Zimbabwe
   2675 
   2676 		*/
   2677 
   2678 		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
   2679 	}
   2680 
   2681 	/**
   2682 	 * @param string $languagecode
   2683 	 * @param bool   $casesensitive
   2684 	 *
   2685 	 * @return string
   2686 	 */
   2687 	public static function LanguageLookup($languagecode, $casesensitive=false) {
   2688 
   2689 		if (!$casesensitive) {
   2690 			$languagecode = strtolower($languagecode);
   2691 		}
   2692 
   2693 		// http://www.id3.org/id3v2.4.0-structure.txt
   2694 		// [4.   ID3v2 frame overview]
   2695 		// The three byte language field, present in several frames, is used to
   2696 		// describe the language of the frame's content, according to ISO-639-2
   2697 		// [ISO-639-2]. The language should be represented in lower case. If the
   2698 		// language is not known the string "XXX" should be used.
   2699 
   2700 
   2701 		// ISO 639-2 - http://www.id3.org/iso639-2.html
   2702 
   2703 		$begin = __LINE__;
   2704 
   2705 		/** This is not a comment!
   2706 
   2707 			XXX	unknown
   2708 			xxx	unknown
   2709 			aar	Afar
   2710 			abk	Abkhazian
   2711 			ace	Achinese
   2712 			ach	Acoli
   2713 			ada	Adangme
   2714 			afa	Afro-Asiatic (Other)
   2715 			afh	Afrihili
   2716 			afr	Afrikaans
   2717 			aka	Akan
   2718 			akk	Akkadian
   2719 			alb	Albanian
   2720 			ale	Aleut
   2721 			alg	Algonquian Languages
   2722 			amh	Amharic
   2723 			ang	English, Old (ca. 450-1100)
   2724 			apa	Apache Languages
   2725 			ara	Arabic
   2726 			arc	Aramaic
   2727 			arm	Armenian
   2728 			arn	Araucanian
   2729 			arp	Arapaho
   2730 			art	Artificial (Other)
   2731 			arw	Arawak
   2732 			asm	Assamese
   2733 			ath	Athapascan Languages
   2734 			ava	Avaric
   2735 			ave	Avestan
   2736 			awa	Awadhi
   2737 			aym	Aymara
   2738 			aze	Azerbaijani
   2739 			bad	Banda
   2740 			bai	Bamileke Languages
   2741 			bak	Bashkir
   2742 			bal	Baluchi
   2743 			bam	Bambara
   2744 			ban	Balinese
   2745 			baq	Basque
   2746 			bas	Basa
   2747 			bat	Baltic (Other)
   2748 			bej	Beja
   2749 			bel	Byelorussian
   2750 			bem	Bemba
   2751 			ben	Bengali
   2752 			ber	Berber (Other)
   2753 			bho	Bhojpuri
   2754 			bih	Bihari
   2755 			bik	Bikol
   2756 			bin	Bini
   2757 			bis	Bislama
   2758 			bla	Siksika
   2759 			bnt	Bantu (Other)
   2760 			bod	Tibetan
   2761 			bra	Braj
   2762 			bre	Breton
   2763 			bua	Buriat
   2764 			bug	Buginese
   2765 			bul	Bulgarian
   2766 			bur	Burmese
   2767 			cad	Caddo
   2768 			cai	Central American Indian (Other)
   2769 			car	Carib
   2770 			cat	Catalan
   2771 			cau	Caucasian (Other)
   2772 			ceb	Cebuano
   2773 			cel	Celtic (Other)
   2774 			ces	Czech
   2775 			cha	Chamorro
   2776 			chb	Chibcha
   2777 			che	Chechen
   2778 			chg	Chagatai
   2779 			chi	Chinese
   2780 			chm	Mari
   2781 			chn	Chinook jargon
   2782 			cho	Choctaw
   2783 			chr	Cherokee
   2784 			chu	Church Slavic
   2785 			chv	Chuvash
   2786 			chy	Cheyenne
   2787 			cop	Coptic
   2788 			cor	Cornish
   2789 			cos	Corsican
   2790 			cpe	Creoles and Pidgins, English-based (Other)
   2791 			cpf	Creoles and Pidgins, French-based (Other)
   2792 			cpp	Creoles and Pidgins, Portuguese-based (Other)
   2793 			cre	Cree
   2794 			crp	Creoles and Pidgins (Other)
   2795 			cus	Cushitic (Other)
   2796 			cym	Welsh
   2797 			cze	Czech
   2798 			dak	Dakota
   2799 			dan	Danish
   2800 			del	Delaware
   2801 			deu	German
   2802 			din	Dinka
   2803 			div	Divehi
   2804 			doi	Dogri
   2805 			dra	Dravidian (Other)
   2806 			dua	Duala
   2807 			dum	Dutch, Middle (ca. 1050-1350)
   2808 			dut	Dutch
   2809 			dyu	Dyula
   2810 			dzo	Dzongkha
   2811 			efi	Efik
   2812 			egy	Egyptian (Ancient)
   2813 			eka	Ekajuk
   2814 			ell	Greek, Modern (1453-)
   2815 			elx	Elamite
   2816 			eng	English
   2817 			enm	English, Middle (ca. 1100-1500)
   2818 			epo	Esperanto
   2819 			esk	Eskimo (Other)
   2820 			esl	Spanish
   2821 			est	Estonian
   2822 			eus	Basque
   2823 			ewe	Ewe
   2824 			ewo	Ewondo
   2825 			fan	Fang
   2826 			fao	Faroese
   2827 			fas	Persian
   2828 			fat	Fanti
   2829 			fij	Fijian
   2830 			fin	Finnish
   2831 			fiu	Finno-Ugrian (Other)
   2832 			fon	Fon
   2833 			fra	French
   2834 			fre	French
   2835 			frm	French, Middle (ca. 1400-1600)
   2836 			fro	French, Old (842- ca. 1400)
   2837 			fry	Frisian
   2838 			ful	Fulah
   2839 			gaa	Ga
   2840 			gae	Gaelic (Scots)
   2841 			gai	Irish
   2842 			gay	Gayo
   2843 			gdh	Gaelic (Scots)
   2844 			gem	Germanic (Other)
   2845 			geo	Georgian
   2846 			ger	German
   2847 			gez	Geez
   2848 			gil	Gilbertese
   2849 			glg	Gallegan
   2850 			gmh	German, Middle High (ca. 1050-1500)
   2851 			goh	German, Old High (ca. 750-1050)
   2852 			gon	Gondi
   2853 			got	Gothic
   2854 			grb	Grebo
   2855 			grc	Greek, Ancient (to 1453)
   2856 			gre	Greek, Modern (1453-)
   2857 			grn	Guarani
   2858 			guj	Gujarati
   2859 			hai	Haida
   2860 			hau	Hausa
   2861 			haw	Hawaiian
   2862 			heb	Hebrew
   2863 			her	Herero
   2864 			hil	Hiligaynon
   2865 			him	Himachali
   2866 			hin	Hindi
   2867 			hmo	Hiri Motu
   2868 			hun	Hungarian
   2869 			hup	Hupa
   2870 			hye	Armenian
   2871 			iba	Iban
   2872 			ibo	Igbo
   2873 			ice	Icelandic
   2874 			ijo	Ijo
   2875 			iku	Inuktitut
   2876 			ilo	Iloko
   2877 			ina	Interlingua (International Auxiliary language Association)
   2878 			inc	Indic (Other)
   2879 			ind	Indonesian
   2880 			ine	Indo-European (Other)
   2881 			ine	Interlingue
   2882 			ipk	Inupiak
   2883 			ira	Iranian (Other)
   2884 			iri	Irish
   2885 			iro	Iroquoian uages
   2886 			isl	Icelandic
   2887 			ita	Italian
   2888 			jav	Javanese
   2889 			jaw	Javanese
   2890 			jpn	Japanese
   2891 			jpr	Judeo-Persian
   2892 			jrb	Judeo-Arabic
   2893 			kaa	Kara-Kalpak
   2894 			kab	Kabyle
   2895 			kac	Kachin
   2896 			kal	Greenlandic
   2897 			kam	Kamba
   2898 			kan	Kannada
   2899 			kar	Karen
   2900 			kas	Kashmiri
   2901 			kat	Georgian
   2902 			kau	Kanuri
   2903 			kaw	Kawi
   2904 			kaz	Kazakh
   2905 			kha	Khasi
   2906 			khi	Khoisan (Other)
   2907 			khm	Khmer
   2908 			kho	Khotanese
   2909 			kik	Kikuyu
   2910 			kin	Kinyarwanda
   2911 			kir	Kirghiz
   2912 			kok	Konkani
   2913 			kom	Komi
   2914 			kon	Kongo
   2915 			kor	Korean
   2916 			kpe	Kpelle
   2917 			kro	Kru
   2918 			kru	Kurukh
   2919 			kua	Kuanyama
   2920 			kum	Kumyk
   2921 			kur	Kurdish
   2922 			kus	Kusaie
   2923 			kut	Kutenai
   2924 			lad	Ladino
   2925 			lah	Lahnda
   2926 			lam	Lamba
   2927 			lao	Lao
   2928 			lat	Latin
   2929 			lav	Latvian
   2930 			lez	Lezghian
   2931 			lin	Lingala
   2932 			lit	Lithuanian
   2933 			lol	Mongo
   2934 			loz	Lozi
   2935 			ltz	Letzeburgesch
   2936 			lub	Luba-Katanga
   2937 			lug	Ganda
   2938 			lui	Luiseno
   2939 			lun	Lunda
   2940 			luo	Luo (Kenya and Tanzania)
   2941 			mac	Macedonian
   2942 			mad	Madurese
   2943 			mag	Magahi
   2944 			mah	Marshall
   2945 			mai	Maithili
   2946 			mak	Macedonian
   2947 			mak	Makasar
   2948 			mal	Malayalam
   2949 			man	Mandingo
   2950 			mao	Maori
   2951 			map	Austronesian (Other)
   2952 			mar	Marathi
   2953 			mas	Masai
   2954 			max	Manx
   2955 			may	Malay
   2956 			men	Mende
   2957 			mga	Irish, Middle (900 - 1200)
   2958 			mic	Micmac
   2959 			min	Minangkabau
   2960 			mis	Miscellaneous (Other)
   2961 			mkh	Mon-Kmer (Other)
   2962 			mlg	Malagasy
   2963 			mlt	Maltese
   2964 			mni	Manipuri
   2965 			mno	Manobo Languages
   2966 			moh	Mohawk
   2967 			mol	Moldavian
   2968 			mon	Mongolian
   2969 			mos	Mossi
   2970 			mri	Maori
   2971 			msa	Malay
   2972 			mul	Multiple Languages
   2973 			mun	Munda Languages
   2974 			mus	Creek
   2975 			mwr	Marwari
   2976 			mya	Burmese
   2977 			myn	Mayan Languages
   2978 			nah	Aztec
   2979 			nai	North American Indian (Other)
   2980 			nau	Nauru
   2981 			nav	Navajo
   2982 			nbl	Ndebele, South
   2983 			nde	Ndebele, North
   2984 			ndo	Ndongo
   2985 			nep	Nepali
   2986 			new	Newari
   2987 			nic	Niger-Kordofanian (Other)
   2988 			niu	Niuean
   2989 			nla	Dutch
   2990 			nno	Norwegian (Nynorsk)
   2991 			non	Norse, Old
   2992 			nor	Norwegian
   2993 			nso	Sotho, Northern
   2994 			nub	Nubian Languages
   2995 			nya	Nyanja
   2996 			nym	Nyamwezi
   2997 			nyn	Nyankole
   2998 			nyo	Nyoro
   2999 			nzi	Nzima
   3000 			oci	Langue d'Oc (post 1500)
   3001 			oji	Ojibwa
   3002 			ori	Oriya
   3003 			orm	Oromo
   3004 			osa	Osage
   3005 			oss	Ossetic
   3006 			ota	Turkish, Ottoman (1500 - 1928)
   3007 			oto	Otomian Languages
   3008 			paa	Papuan-Australian (Other)
   3009 			pag	Pangasinan
   3010 			pal	Pahlavi
   3011 			pam	Pampanga
   3012 			pan	Panjabi
   3013 			pap	Papiamento
   3014 			pau	Palauan
   3015 			peo	Persian, Old (ca 600 - 400 B.C.)
   3016 			per	Persian
   3017 			phn	Phoenician
   3018 			pli	Pali
   3019 			pol	Polish
   3020 			pon	Ponape
   3021 			por	Portuguese
   3022 			pra	Prakrit uages
   3023 			pro	Provencal, Old (to 1500)
   3024 			pus	Pushto
   3025 			que	Quechua
   3026 			raj	Rajasthani
   3027 			rar	Rarotongan
   3028 			roa	Romance (Other)
   3029 			roh	Rhaeto-Romance
   3030 			rom	Romany
   3031 			ron	Romanian
   3032 			rum	Romanian
   3033 			run	Rundi
   3034 			rus	Russian
   3035 			sad	Sandawe
   3036 			sag	Sango
   3037 			sah	Yakut
   3038 			sai	South American Indian (Other)
   3039 			sal	Salishan Languages
   3040 			sam	Samaritan Aramaic
   3041 			san	Sanskrit
   3042 			sco	Scots
   3043 			scr	Serbo-Croatian
   3044 			sel	Selkup
   3045 			sem	Semitic (Other)
   3046 			sga	Irish, Old (to 900)
   3047 			shn	Shan
   3048 			sid	Sidamo
   3049 			sin	Singhalese
   3050 			sio	Siouan Languages
   3051 			sit	Sino-Tibetan (Other)
   3052 			sla	Slavic (Other)
   3053 			slk	Slovak
   3054 			slo	Slovak
   3055 			slv	Slovenian
   3056 			smi	Sami Languages
   3057 			smo	Samoan
   3058 			sna	Shona
   3059 			snd	Sindhi
   3060 			sog	Sogdian
   3061 			som	Somali
   3062 			son	Songhai
   3063 			sot	Sotho, Southern
   3064 			spa	Spanish
   3065 			sqi	Albanian
   3066 			srd	Sardinian
   3067 			srr	Serer
   3068 			ssa	Nilo-Saharan (Other)
   3069 			ssw	Siswant
   3070 			ssw	Swazi
   3071 			suk	Sukuma
   3072 			sun	Sudanese
   3073 			sus	Susu
   3074 			sux	Sumerian
   3075 			sve	Swedish
   3076 			swa	Swahili
   3077 			swe	Swedish
   3078 			syr	Syriac
   3079 			tah	Tahitian
   3080 			tam	Tamil
   3081 			tat	Tatar
   3082 			tel	Telugu
   3083 			tem	Timne
   3084 			ter	Tereno
   3085 			tgk	Tajik
   3086 			tgl	Tagalog
   3087 			tha	Thai
   3088 			tib	Tibetan
   3089 			tig	Tigre
   3090 			tir	Tigrinya
   3091 			tiv	Tivi
   3092 			tli	Tlingit
   3093 			tmh	Tamashek
   3094 			tog	Tonga (Nyasa)
   3095 			ton	Tonga (Tonga Islands)
   3096 			tru	Truk
   3097 			tsi	Tsimshian
   3098 			tsn	Tswana
   3099 			tso	Tsonga
   3100 			tuk	Turkmen
   3101 			tum	Tumbuka
   3102 			tur	Turkish
   3103 			tut	Altaic (Other)
   3104 			twi	Twi
   3105 			tyv	Tuvinian
   3106 			uga	Ugaritic
   3107 			uig	Uighur
   3108 			ukr	Ukrainian
   3109 			umb	Umbundu
   3110 			und	Undetermined
   3111 			urd	Urdu
   3112 			uzb	Uzbek
   3113 			vai	Vai
   3114 			ven	Venda
   3115 			vie	Vietnamese
   3116 			vol	Volapük
   3117 			vot	Votic
   3118 			wak	Wakashan Languages
   3119 			wal	Walamo
   3120 			war	Waray
   3121 			was	Washo
   3122 			wel	Welsh
   3123 			wen	Sorbian Languages
   3124 			wol	Wolof
   3125 			xho	Xhosa
   3126 			yao	Yao
   3127 			yap	Yap
   3128 			yid	Yiddish
   3129 			yor	Yoruba
   3130 			zap	Zapotec
   3131 			zen	Zenaga
   3132 			zha	Zhuang
   3133 			zho	Chinese
   3134 			zul	Zulu
   3135 			zun	Zuni
   3136 
   3137 		*/
   3138 
   3139 		return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
   3140 	}
   3141 
   3142 	/**
   3143 	 * @param int $index
   3144 	 *
   3145 	 * @return string
   3146 	 */
   3147 	public static function ETCOEventLookup($index) {
   3148 		if (($index >= 0x17) && ($index <= 0xDF)) {
   3149 			return 'reserved for future use';
   3150 		}
   3151 		if (($index >= 0xE0) && ($index <= 0xEF)) {
   3152 			return 'not predefined synch 0-F';
   3153 		}
   3154 		if (($index >= 0xF0) && ($index <= 0xFC)) {
   3155 			return 'reserved for future use';
   3156 		}
   3157 
   3158 		static $EventLookup = array(
   3159 			0x00 => 'padding (has no meaning)',
   3160 			0x01 => 'end of initial silence',
   3161 			0x02 => 'intro start',
   3162 			0x03 => 'main part start',
   3163 			0x04 => 'outro start',
   3164 			0x05 => 'outro end',
   3165 			0x06 => 'verse start',
   3166 			0x07 => 'refrain start',
   3167 			0x08 => 'interlude start',
   3168 			0x09 => 'theme start',
   3169 			0x0A => 'variation start',
   3170 			0x0B => 'key change',
   3171 			0x0C => 'time change',
   3172 			0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
   3173 			0x0E => 'sustained noise',
   3174 			0x0F => 'sustained noise end',
   3175 			0x10 => 'intro end',
   3176 			0x11 => 'main part end',
   3177 			0x12 => 'verse end',
   3178 			0x13 => 'refrain end',
   3179 			0x14 => 'theme end',
   3180 			0x15 => 'profanity',
   3181 			0x16 => 'profanity end',
   3182 			0xFD => 'audio end (start of silence)',
   3183 			0xFE => 'audio file ends',
   3184 			0xFF => 'one more byte of events follows'
   3185 		);
   3186 
   3187 		return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
   3188 	}
   3189 
   3190 	/**
   3191 	 * @param int $index
   3192 	 *
   3193 	 * @return string
   3194 	 */
   3195 	public static function SYTLContentTypeLookup($index) {
   3196 		static $SYTLContentTypeLookup = array(
   3197 			0x00 => 'other',
   3198 			0x01 => 'lyrics',
   3199 			0x02 => 'text transcription',
   3200 			0x03 => 'movement/part name', // (e.g. 'Adagio')
   3201 			0x04 => 'events',             // (e.g. 'Don Quijote enters the stage')
   3202 			0x05 => 'chord',              // (e.g. 'Bb F Fsus')
   3203 			0x06 => 'trivia/\'pop up\' information',
   3204 			0x07 => 'URLs to webpages',
   3205 			0x08 => 'URLs to images'
   3206 		);
   3207 
   3208 		return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
   3209 	}
   3210 
   3211 	/**
   3212 	 * @param int   $index
   3213 	 * @param bool $returnarray
   3214 	 *
   3215 	 * @return array|string
   3216 	 */
   3217 	public static function APICPictureTypeLookup($index, $returnarray=false) {
   3218 		static $APICPictureTypeLookup = array(
   3219 			0x00 => 'Other',
   3220 			0x01 => '32x32 pixels \'file icon\' (PNG only)',
   3221 			0x02 => 'Other file icon',
   3222 			0x03 => 'Cover (front)',
   3223 			0x04 => 'Cover (back)',
   3224 			0x05 => 'Leaflet page',
   3225 			0x06 => 'Media (e.g. label side of CD)',
   3226 			0x07 => 'Lead artist/lead performer/soloist',
   3227 			0x08 => 'Artist/performer',
   3228 			0x09 => 'Conductor',
   3229 			0x0A => 'Band/Orchestra',
   3230 			0x0B => 'Composer',
   3231 			0x0C => 'Lyricist/text writer',
   3232 			0x0D => 'Recording Location',
   3233 			0x0E => 'During recording',
   3234 			0x0F => 'During performance',
   3235 			0x10 => 'Movie/video screen capture',
   3236 			0x11 => 'A bright coloured fish',
   3237 			0x12 => 'Illustration',
   3238 			0x13 => 'Band/artist logotype',
   3239 			0x14 => 'Publisher/Studio logotype'
   3240 		);
   3241 		if ($returnarray) {
   3242 			return $APICPictureTypeLookup;
   3243 		}
   3244 		return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
   3245 	}
   3246 
   3247 	/**
   3248 	 * @param int $index
   3249 	 *
   3250 	 * @return string
   3251 	 */
   3252 	public static function COMRReceivedAsLookup($index) {
   3253 		static $COMRReceivedAsLookup = array(
   3254 			0x00 => 'Other',
   3255 			0x01 => 'Standard CD album with other songs',
   3256 			0x02 => 'Compressed audio on CD',
   3257 			0x03 => 'File over the Internet',
   3258 			0x04 => 'Stream over the Internet',
   3259 			0x05 => 'As note sheets',
   3260 			0x06 => 'As note sheets in a book with other sheets',
   3261 			0x07 => 'Music on other media',
   3262 			0x08 => 'Non-musical merchandise'
   3263 		);
   3264 
   3265 		return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
   3266 	}
   3267 
   3268 	/**
   3269 	 * @param int $index
   3270 	 *
   3271 	 * @return string
   3272 	 */
   3273 	public static function RVA2ChannelTypeLookup($index) {
   3274 		static $RVA2ChannelTypeLookup = array(
   3275 			0x00 => 'Other',
   3276 			0x01 => 'Master volume',
   3277 			0x02 => 'Front right',
   3278 			0x03 => 'Front left',
   3279 			0x04 => 'Back right',
   3280 			0x05 => 'Back left',
   3281 			0x06 => 'Front centre',
   3282 			0x07 => 'Back centre',
   3283 			0x08 => 'Subwoofer'
   3284 		);
   3285 
   3286 		return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
   3287 	}
   3288 
   3289 	/**
   3290 	 * @param string $framename
   3291 	 *
   3292 	 * @return string
   3293 	 */
   3294 	public static function FrameNameLongLookup($framename) {
   3295 
   3296 		$begin = __LINE__;
   3297 
   3298 		/** This is not a comment!
   3299 
   3300 			AENC	Audio encryption
   3301 			APIC	Attached picture
   3302 			ASPI	Audio seek point index
   3303 			BUF	Recommended buffer size
   3304 			CNT	Play counter
   3305 			COM	Comments
   3306 			COMM	Comments
   3307 			COMR	Commercial frame
   3308 			CRA	Audio encryption
   3309 			CRM	Encrypted meta frame
   3310 			ENCR	Encryption method registration
   3311 			EQU	Equalisation
   3312 			EQU2	Equalisation (2)
   3313 			EQUA	Equalisation
   3314 			ETC	Event timing codes
   3315 			ETCO	Event timing codes
   3316 			GEO	General encapsulated object
   3317 			GEOB	General encapsulated object
   3318 			GRID	Group identification registration
   3319 			IPL	Involved people list
   3320 			IPLS	Involved people list
   3321 			LINK	Linked information
   3322 			LNK	Linked information
   3323 			MCDI	Music CD identifier
   3324 			MCI	Music CD Identifier
   3325 			MLL	MPEG location lookup table
   3326 			MLLT	MPEG location lookup table
   3327 			OWNE	Ownership frame
   3328 			PCNT	Play counter
   3329 			PIC	Attached picture
   3330 			POP	Popularimeter
   3331 			POPM	Popularimeter
   3332 			POSS	Position synchronisation frame
   3333 			PRIV	Private frame
   3334 			RBUF	Recommended buffer size
   3335 			REV	Reverb
   3336 			RVA	Relative volume adjustment
   3337 			RVA2	Relative volume adjustment (2)
   3338 			RVAD	Relative volume adjustment
   3339 			RVRB	Reverb
   3340 			SEEK	Seek frame
   3341 			SIGN	Signature frame
   3342 			SLT	Synchronised lyric/text
   3343 			STC	Synced tempo codes
   3344 			SYLT	Synchronised lyric/text
   3345 			SYTC	Synchronised tempo codes
   3346 			TAL	Album/Movie/Show title
   3347 			TALB	Album/Movie/Show title
   3348 			TBP	BPM (Beats Per Minute)
   3349 			TBPM	BPM (beats per minute)
   3350 			TCM	Composer
   3351 			TCMP	Part of a compilation
   3352 			TCO	Content type
   3353 			TCOM	Composer
   3354 			TCON	Content type
   3355 			TCOP	Copyright message
   3356 			TCP	Part of a compilation
   3357 			TCR	Copyright message
   3358 			TDA	Date
   3359 			TDAT	Date
   3360 			TDEN	Encoding time
   3361 			TDLY	Playlist delay
   3362 			TDOR	Original release time
   3363 			TDRC	Recording time
   3364 			TDRL	Release time
   3365 			TDTG	Tagging time
   3366 			TDY	Playlist delay
   3367 			TEN	Encoded by
   3368 			TENC	Encoded by
   3369 			TEXT	Lyricist/Text writer
   3370 			TFLT	File type
   3371 			TFT	File type
   3372 			TIM	Time
   3373 			TIME	Time
   3374 			TIPL	Involved people list
   3375 			TIT1	Content group description
   3376 			TIT2	Title/songname/content description
   3377 			TIT3	Subtitle/Description refinement
   3378 			TKE	Initial key
   3379 			TKEY	Initial key
   3380 			TLA	Language(s)
   3381 			TLAN	Language(s)
   3382 			TLE	Length
   3383 			TLEN	Length
   3384 			TMCL	Musician credits list
   3385 			TMED	Media type
   3386 			TMOO	Mood
   3387 			TMT	Media type
   3388 			TOA	Original artist(s)/performer(s)
   3389 			TOAL	Original album/movie/show title
   3390 			TOF	Original filename
   3391 			TOFN	Original filename
   3392 			TOL	Original Lyricist(s)/text writer(s)
   3393 			TOLY	Original lyricist(s)/text writer(s)
   3394 			TOPE	Original artist(s)/performer(s)
   3395 			TOR	Original release year
   3396 			TORY	Original release year
   3397 			TOT	Original album/Movie/Show title
   3398 			TOWN	File owner/licensee
   3399 			TP1	Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
   3400 			TP2	Band/Orchestra/Accompaniment
   3401 			TP3	Conductor/Performer refinement
   3402 			TP4	Interpreted, remixed, or otherwise modified by
   3403 			TPA	Part of a set
   3404 			TPB	Publisher
   3405 			TPE1	Lead performer(s)/Soloist(s)
   3406 			TPE2	Band/orchestra/accompaniment
   3407 			TPE3	Conductor/performer refinement
   3408 			TPE4	Interpreted, remixed, or otherwise modified by
   3409 			TPOS	Part of a set
   3410 			TPRO	Produced notice
   3411 			TPUB	Publisher
   3412 			TRC	ISRC (International Standard Recording Code)
   3413 			TRCK	Track number/Position in set
   3414 			TRD	Recording dates
   3415 			TRDA	Recording dates
   3416 			TRK	Track number/Position in set
   3417 			TRSN	Internet radio station name
   3418 			TRSO	Internet radio station owner
   3419 			TS2	Album-Artist sort order
   3420 			TSA	Album sort order
   3421 			TSC	Composer sort order
   3422 			TSI	Size
   3423 			TSIZ	Size
   3424 			TSO2	Album-Artist sort order
   3425 			TSOA	Album sort order
   3426 			TSOC	Composer sort order
   3427 			TSOP	Performer sort order
   3428 			TSOT	Title sort order
   3429 			TSP	Performer sort order
   3430 			TSRC	ISRC (international standard recording code)
   3431 			TSS	Software/hardware and settings used for encoding
   3432 			TSSE	Software/Hardware and settings used for encoding
   3433 			TSST	Set subtitle
   3434 			TST	Title sort order
   3435 			TT1	Content group description
   3436 			TT2	Title/Songname/Content description
   3437 			TT3	Subtitle/Description refinement
   3438 			TXT	Lyricist/text writer
   3439 			TXX	User defined text information frame
   3440 			TXXX	User defined text information frame
   3441 			TYE	Year
   3442 			TYER	Year
   3443 			UFI	Unique file identifier
   3444 			UFID	Unique file identifier
   3445 			ULT	Unsynchronised lyric/text transcription
   3446 			USER	Terms of use
   3447 			USLT	Unsynchronised lyric/text transcription
   3448 			WAF	Official audio file webpage
   3449 			WAR	Official artist/performer webpage
   3450 			WAS	Official audio source webpage
   3451 			WCM	Commercial information
   3452 			WCOM	Commercial information
   3453 			WCOP	Copyright/Legal information
   3454 			WCP	Copyright/Legal information
   3455 			WOAF	Official audio file webpage
   3456 			WOAR	Official artist/performer webpage
   3457 			WOAS	Official audio source webpage
   3458 			WORS	Official Internet radio station homepage
   3459 			WPAY	Payment
   3460 			WPB	Publishers official webpage
   3461 			WPUB	Publishers official webpage
   3462 			WXX	User defined URL link frame
   3463 			WXXX	User defined URL link frame
   3464 			TFEA	Featured Artist
   3465 			TSTU	Recording Studio
   3466 			rgad	Replay Gain Adjustment
   3467 
   3468 		*/
   3469 
   3470 		return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
   3471 
   3472 		// Last three:
   3473 		// from Helium2 [www.helium2.com]
   3474 		// from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
   3475 	}
   3476 
   3477 	/**
   3478 	 * @param string $framename
   3479 	 *
   3480 	 * @return string
   3481 	 */
   3482 	public static function FrameNameShortLookup($framename) {
   3483 
   3484 		$begin = __LINE__;
   3485 
   3486 		/** This is not a comment!
   3487 
   3488 			AENC	audio_encryption
   3489 			APIC	attached_picture
   3490 			ASPI	audio_seek_point_index
   3491 			BUF	recommended_buffer_size
   3492 			CNT	play_counter
   3493 			COM	comment
   3494 			COMM	comment
   3495 			COMR	commercial_frame
   3496 			CRA	audio_encryption
   3497 			CRM	encrypted_meta_frame
   3498 			ENCR	encryption_method_registration
   3499 			EQU	equalisation
   3500 			EQU2	equalisation
   3501 			EQUA	equalisation
   3502 			ETC	event_timing_codes
   3503 			ETCO	event_timing_codes
   3504 			GEO	general_encapsulated_object
   3505 			GEOB	general_encapsulated_object
   3506 			GRID	group_identification_registration
   3507 			IPL	involved_people_list
   3508 			IPLS	involved_people_list
   3509 			LINK	linked_information
   3510 			LNK	linked_information
   3511 			MCDI	music_cd_identifier
   3512 			MCI	music_cd_identifier
   3513 			MLL	mpeg_location_lookup_table
   3514 			MLLT	mpeg_location_lookup_table
   3515 			OWNE	ownership_frame
   3516 			PCNT	play_counter
   3517 			PIC	attached_picture
   3518 			POP	popularimeter
   3519 			POPM	popularimeter
   3520 			POSS	position_synchronisation_frame
   3521 			PRIV	private_frame
   3522 			RBUF	recommended_buffer_size
   3523 			REV	reverb
   3524 			RVA	relative_volume_adjustment
   3525 			RVA2	relative_volume_adjustment
   3526 			RVAD	relative_volume_adjustment
   3527 			RVRB	reverb
   3528 			SEEK	seek_frame
   3529 			SIGN	signature_frame
   3530 			SLT	synchronised_lyric
   3531 			STC	synced_tempo_codes
   3532 			SYLT	synchronised_lyric
   3533 			SYTC	synchronised_tempo_codes
   3534 			TAL	album
   3535 			TALB	album
   3536 			TBP	bpm
   3537 			TBPM	bpm
   3538 			TCM	composer
   3539 			TCMP	part_of_a_compilation
   3540 			TCO	genre
   3541 			TCOM	composer
   3542 			TCON	genre
   3543 			TCOP	copyright_message
   3544 			TCP	part_of_a_compilation
   3545 			TCR	copyright_message
   3546 			TDA	date
   3547 			TDAT	date
   3548 			TDEN	encoding_time
   3549 			TDLY	playlist_delay
   3550 			TDOR	original_release_time
   3551 			TDRC	recording_time
   3552 			TDRL	release_time
   3553 			TDTG	tagging_time
   3554 			TDY	playlist_delay
   3555 			TEN	encoded_by
   3556 			TENC	encoded_by
   3557 			TEXT	lyricist
   3558 			TFLT	file_type
   3559 			TFT	file_type
   3560 			TIM	time
   3561 			TIME	time
   3562 			TIPL	involved_people_list
   3563 			TIT1	content_group_description
   3564 			TIT2	title
   3565 			TIT3	subtitle
   3566 			TKE	initial_key
   3567 			TKEY	initial_key
   3568 			TLA	language
   3569 			TLAN	language
   3570 			TLE	length
   3571 			TLEN	length
   3572 			TMCL	musician_credits_list
   3573 			TMED	media_type
   3574 			TMOO	mood
   3575 			TMT	media_type
   3576 			TOA	original_artist
   3577 			TOAL	original_album
   3578 			TOF	original_filename
   3579 			TOFN	original_filename
   3580 			TOL	original_lyricist
   3581 			TOLY	original_lyricist
   3582 			TOPE	original_artist
   3583 			TOR	original_year
   3584 			TORY	original_year
   3585 			TOT	original_album
   3586 			TOWN	file_owner
   3587 			TP1	artist
   3588 			TP2	band
   3589 			TP3	conductor
   3590 			TP4	remixer
   3591 			TPA	part_of_a_set
   3592 			TPB	publisher
   3593 			TPE1	artist
   3594 			TPE2	band
   3595 			TPE3	conductor
   3596 			TPE4	remixer
   3597 			TPOS	part_of_a_set
   3598 			TPRO	produced_notice
   3599 			TPUB	publisher
   3600 			TRC	isrc
   3601 			TRCK	track_number
   3602 			TRD	recording_dates
   3603 			TRDA	recording_dates
   3604 			TRK	track_number
   3605 			TRSN	internet_radio_station_name
   3606 			TRSO	internet_radio_station_owner
   3607 			TS2	album_artist_sort_order
   3608 			TSA	album_sort_order
   3609 			TSC	composer_sort_order
   3610 			TSI	size
   3611 			TSIZ	size
   3612 			TSO2	album_artist_sort_order
   3613 			TSOA	album_sort_order
   3614 			TSOC	composer_sort_order
   3615 			TSOP	performer_sort_order
   3616 			TSOT	title_sort_order
   3617 			TSP	performer_sort_order
   3618 			TSRC	isrc
   3619 			TSS	encoder_settings
   3620 			TSSE	encoder_settings
   3621 			TSST	set_subtitle
   3622 			TST	title_sort_order
   3623 			TT1	content_group_description
   3624 			TT2	title
   3625 			TT3	subtitle
   3626 			TXT	lyricist
   3627 			TXX	text
   3628 			TXXX	text
   3629 			TYE	year
   3630 			TYER	year
   3631 			UFI	unique_file_identifier
   3632 			UFID	unique_file_identifier
   3633 			ULT	unsynchronised_lyric
   3634 			USER	terms_of_use
   3635 			USLT	unsynchronised_lyric
   3636 			WAF	url_file
   3637 			WAR	url_artist
   3638 			WAS	url_source
   3639 			WCM	commercial_information
   3640 			WCOM	commercial_information
   3641 			WCOP	copyright
   3642 			WCP	copyright
   3643 			WOAF	url_file
   3644 			WOAR	url_artist
   3645 			WOAS	url_source
   3646 			WORS	url_station
   3647 			WPAY	url_payment
   3648 			WPB	url_publisher
   3649 			WPUB	url_publisher
   3650 			WXX	url_user
   3651 			WXXX	url_user
   3652 			TFEA	featured_artist
   3653 			TSTU	recording_studio
   3654 			rgad	replay_gain_adjustment
   3655 
   3656 		*/
   3657 
   3658 		return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
   3659 	}
   3660 
   3661 	/**
   3662 	 * @param string $encoding
   3663 	 *
   3664 	 * @return string
   3665 	 */
   3666 	public static function TextEncodingTerminatorLookup($encoding) {
   3667 		// http://www.id3.org/id3v2.4.0-structure.txt
   3668 		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
   3669 		static $TextEncodingTerminatorLookup = array(
   3670 			0   => "\x00",     // $00  ISO-8859-1. Terminated with $00.
   3671 			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.
   3672 			2   => "\x00\x00", // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
   3673 			3   => "\x00",     // $03  UTF-8 encoded Unicode. Terminated with $00.
   3674 			255 => "\x00\x00"
   3675 		);
   3676 		return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
   3677 	}
   3678 
   3679 	/**
   3680 	 * @param int $encoding
   3681 	 *
   3682 	 * @return string
   3683 	 */
   3684 	public static function TextEncodingNameLookup($encoding) {
   3685 		// http://www.id3.org/id3v2.4.0-structure.txt
   3686 		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
   3687 		static $TextEncodingNameLookup = array(
   3688 			0   => 'ISO-8859-1', // $00  ISO-8859-1. Terminated with $00.
   3689 			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.
   3690 			2   => 'UTF-16BE',   // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
   3691 			3   => 'UTF-8',      // $03  UTF-8 encoded Unicode. Terminated with $00.
   3692 			255 => 'UTF-16BE'
   3693 		);
   3694 		return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
   3695 	}
   3696 
   3697 	/**
   3698 	 * @param string $string
   3699 	 * @param string $terminator
   3700 	 *
   3701 	 * @return string
   3702 	 */
   3703 	public static function RemoveStringTerminator($string, $terminator) {
   3704 		// Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present.
   3705 		// https://github.com/JamesHeinrich/getID3/issues/121
   3706 		// https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227
   3707 		if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) {
   3708 			$string = substr($string, 0, -strlen($terminator));
   3709 		}
   3710 		return $string;
   3711 	}
   3712 
   3713 	/**
   3714 	 * @param string $string
   3715 	 *
   3716 	 * @return string
   3717 	 */
   3718 	public static function MakeUTF16emptyStringEmpty($string) {
   3719 		if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
   3720 			// if string only contains a BOM or terminator then make it actually an empty string
   3721 			$string = '';
   3722 		}
   3723 		return $string;
   3724 	}
   3725 
   3726 	/**
   3727 	 * @param string $framename
   3728 	 * @param int    $id3v2majorversion
   3729 	 *
   3730 	 * @return bool|int
   3731 	 */
   3732 	public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
   3733 		switch ($id3v2majorversion) {
   3734 			case 2:
   3735 				return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
   3736 
   3737 			case 3:
   3738 			case 4:
   3739 				return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
   3740 		}
   3741 		return false;
   3742 	}
   3743 
   3744 	/**
   3745 	 * @param string $numberstring
   3746 	 * @param bool   $allowdecimal
   3747 	 * @param bool   $allownegative
   3748 	 *
   3749 	 * @return bool
   3750 	 */
   3751 	public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
   3752 		for ($i = 0; $i < strlen($numberstring); $i++) {
   3753 			if ((chr($numberstring[$i]) < chr('0')) || (chr($numberstring[$i]) > chr('9'))) {
   3754 				if (($numberstring[$i] == '.') && $allowdecimal) {
   3755 					// allowed
   3756 				} elseif (($numberstring[$i] == '-') && $allownegative && ($i == 0)) {
   3757 					// allowed
   3758 				} else {
   3759 					return false;
   3760 				}
   3761 			}
   3762 		}
   3763 		return true;
   3764 	}
   3765 
   3766 	/**
   3767 	 * @param string $datestamp
   3768 	 *
   3769 	 * @return bool
   3770 	 */
   3771 	public static function IsValidDateStampString($datestamp) {
   3772 		if (strlen($datestamp) != 8) {
   3773 			return false;
   3774 		}
   3775 		if (!self::IsANumber($datestamp, false)) {
   3776 			return false;
   3777 		}
   3778 		$year  = substr($datestamp, 0, 4);
   3779 		$month = substr($datestamp, 4, 2);
   3780 		$day   = substr($datestamp, 6, 2);
   3781 		if (($year == 0) || ($month == 0) || ($day == 0)) {
   3782 			return false;
   3783 		}
   3784 		if ($month > 12) {
   3785 			return false;
   3786 		}
   3787 		if ($day > 31) {
   3788 			return false;
   3789 		}
   3790 		if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
   3791 			return false;
   3792 		}
   3793 		if (($day > 29) && ($month == 2)) {
   3794 			return false;
   3795 		}
   3796 		return true;
   3797 	}
   3798 
   3799 	/**
   3800 	 * @param int $majorversion
   3801 	 *
   3802 	 * @return int
   3803 	 */
   3804 	public static function ID3v2HeaderLength($majorversion) {
   3805 		return (($majorversion == 2) ? 6 : 10);
   3806 	}
   3807 
   3808 	/**
   3809 	 * @param string $frame_name
   3810 	 *
   3811 	 * @return string|false
   3812 	 */
   3813 	public static function ID3v22iTunesBrokenFrameName($frame_name) {
   3814 		// iTunes (multiple versions) has been known to write ID3v2.3 style frames
   3815 		// but use ID3v2.2 frame names, right-padded using either [space] or [null]
   3816 		// to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
   3817 		// This function will detect and translate the corrupt frame name into ID3v2.3 standard.
   3818 		static $ID3v22_iTunes_BrokenFrames = array(
   3819 			'BUF' => 'RBUF', // Recommended buffer size
   3820 			'CNT' => 'PCNT', // Play counter
   3821 			'COM' => 'COMM', // Comments
   3822 			'CRA' => 'AENC', // Audio encryption
   3823 			'EQU' => 'EQUA', // Equalisation
   3824 			'ETC' => 'ETCO', // Event timing codes
   3825 			'GEO' => 'GEOB', // General encapsulated object
   3826 			'IPL' => 'IPLS', // Involved people list
   3827 			'LNK' => 'LINK', // Linked information
   3828 			'MCI' => 'MCDI', // Music CD identifier
   3829 			'MLL' => 'MLLT', // MPEG location lookup table
   3830 			'PIC' => 'APIC', // Attached picture
   3831 			'POP' => 'POPM', // Popularimeter
   3832 			'REV' => 'RVRB', // Reverb
   3833 			'RVA' => 'RVAD', // Relative volume adjustment
   3834 			'SLT' => 'SYLT', // Synchronised lyric/text
   3835 			'STC' => 'SYTC', // Synchronised tempo codes
   3836 			'TAL' => 'TALB', // Album/Movie/Show title
   3837 			'TBP' => 'TBPM', // BPM (beats per minute)
   3838 			'TCM' => 'TCOM', // Composer
   3839 			'TCO' => 'TCON', // Content type
   3840 			'TCP' => 'TCMP', // Part of a compilation
   3841 			'TCR' => 'TCOP', // Copyright message
   3842 			'TDA' => 'TDAT', // Date
   3843 			'TDY' => 'TDLY', // Playlist delay
   3844 			'TEN' => 'TENC', // Encoded by
   3845 			'TFT' => 'TFLT', // File type
   3846 			'TIM' => 'TIME', // Time
   3847 			'TKE' => 'TKEY', // Initial key
   3848 			'TLA' => 'TLAN', // Language(s)
   3849 			'TLE' => 'TLEN', // Length
   3850 			'TMT' => 'TMED', // Media type
   3851 			'TOA' => 'TOPE', // Original artist(s)/performer(s)
   3852 			'TOF' => 'TOFN', // Original filename
   3853 			'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
   3854 			'TOR' => 'TORY', // Original release year
   3855 			'TOT' => 'TOAL', // Original album/movie/show title
   3856 			'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
   3857 			'TP2' => 'TPE2', // Band/orchestra/accompaniment
   3858 			'TP3' => 'TPE3', // Conductor/performer refinement
   3859 			'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
   3860 			'TPA' => 'TPOS', // Part of a set
   3861 			'TPB' => 'TPUB', // Publisher
   3862 			'TRC' => 'TSRC', // ISRC (international standard recording code)
   3863 			'TRD' => 'TRDA', // Recording dates
   3864 			'TRK' => 'TRCK', // Track number/Position in set
   3865 			'TS2' => 'TSO2', // Album-Artist sort order
   3866 			'TSA' => 'TSOA', // Album sort order
   3867 			'TSC' => 'TSOC', // Composer sort order
   3868 			'TSI' => 'TSIZ', // Size
   3869 			'TSP' => 'TSOP', // Performer sort order
   3870 			'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
   3871 			'TST' => 'TSOT', // Title sort order
   3872 			'TT1' => 'TIT1', // Content group description
   3873 			'TT2' => 'TIT2', // Title/songname/content description
   3874 			'TT3' => 'TIT3', // Subtitle/Description refinement
   3875 			'TXT' => 'TEXT', // Lyricist/Text writer
   3876 			'TXX' => 'TXXX', // User defined text information frame
   3877 			'TYE' => 'TYER', // Year
   3878 			'UFI' => 'UFID', // Unique file identifier
   3879 			'ULT' => 'USLT', // Unsynchronised lyric/text transcription
   3880 			'WAF' => 'WOAF', // Official audio file webpage
   3881 			'WAR' => 'WOAR', // Official artist/performer webpage
   3882 			'WAS' => 'WOAS', // Official audio source webpage
   3883 			'WCM' => 'WCOM', // Commercial information
   3884 			'WCP' => 'WCOP', // Copyright/Legal information
   3885 			'WPB' => 'WPUB', // Publishers official webpage
   3886 			'WXX' => 'WXXX', // User defined URL link frame
   3887 		);
   3888 		if (strlen($frame_name) == 4) {
   3889 			if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) {
   3890 				if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) {
   3891 					return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)];
   3892 				}
   3893 			}
   3894 		}
   3895 		return false;
   3896 	}
   3897 
   3898 }
   3899