wp/wp-includes/ID3/module.tag.id3v2.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 16 a86126ab1dd4
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
    69 
    69 
    70 		}
    70 		}
    71 
    71 
    72 		if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
    72 		if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
    73 
    73 
    74 			$info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
    74 			$this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
    75 			return false;
    75 			return false;
    76 
    76 
    77 		}
    77 		}
    78 
    78 
    79 		$id3_flags = ord($header{5});
    79 		$id3_flags = ord($header{5});
   239 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
   239 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
   240 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
   240 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
   241 					}
   241 					}
   242 
   242 
   243 					if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
   243 					if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
   244 						$info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
   244 						$this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
   245 					}
   245 					}
   246 				}
   246 				}
   247 
   247 
   248 				$framedataoffset += $extended_header_offset;
   248 				$framedataoffset += $extended_header_offset;
   249 				$framedata = substr($framedata, $extended_header_offset);
   249 				$framedata = substr($framedata, $extended_header_offset);
   258 					$thisfile_id3v2['padding']['valid']  = true;
   258 					$thisfile_id3v2['padding']['valid']  = true;
   259 					for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
   259 					for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
   260 						if ($framedata{$i} != "\x00") {
   260 						if ($framedata{$i} != "\x00") {
   261 							$thisfile_id3v2['padding']['valid'] = false;
   261 							$thisfile_id3v2['padding']['valid'] = false;
   262 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
   262 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
   263 							$info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
   263 							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
   264 							break;
   264 							break;
   265 						}
   265 						}
   266 					}
   266 					}
   267 					break; // skip rest of ID3v2 header
   267 					break; // skip rest of ID3v2 header
   268 				}
   268 				}
   298 						if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
   298 						if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
   299 							// next frame is OK
   299 							// next frame is OK
   300 						} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
   300 						} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
   301 							// MP3ext known broken frames - "ok" for the purposes of this test
   301 							// MP3ext known broken frames - "ok" for the purposes of this test
   302 						} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
   302 						} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
   303 							$info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
   303 							$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');
   304 							$id3v2_majorversion = 3;
   304 							$id3v2_majorversion = 3;
   305 							$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
   305 							$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
   306 						}
   306 						}
   307 					}
   307 					}
   308 
   308 
   320 					$len = strlen($framedata);
   320 					$len = strlen($framedata);
   321 					for ($i = 0; $i < $len; $i++) {
   321 					for ($i = 0; $i < $len; $i++) {
   322 						if ($framedata{$i} != "\x00") {
   322 						if ($framedata{$i} != "\x00") {
   323 							$thisfile_id3v2['padding']['valid'] = false;
   323 							$thisfile_id3v2['padding']['valid'] = false;
   324 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
   324 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
   325 							$info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
   325 							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
   326 							break;
   326 							break;
   327 						}
   327 						}
   328 					}
   328 					}
   329 					break; // skip rest of ID3v2 header
   329 					break; // skip rest of ID3v2 header
   330 				}
   330 				}
   331 
   331 
   332 				if ($frame_name == 'COM ') {
   332 				if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
   333 					$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
   333 					$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.');
   334 					$frame_name = 'COMM';
   334 					$frame_name = $iTunesBrokenFrameNameFixed;
   335 				}
   335 				}
   336 				if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
   336 				if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
   337 
   337 
   338 					unset($parsedFrame);
   338 					unset($parsedFrame);
   339 					$parsedFrame['frame_name']      = $frame_name;
   339 					$parsedFrame['frame_name']      = $frame_name;
   353 
   353 
   354 						if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
   354 						if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
   355 
   355 
   356 							// next frame is valid, just skip the current frame
   356 							// next frame is valid, just skip the current frame
   357 							$framedata = substr($framedata, $frame_size);
   357 							$framedata = substr($framedata, $frame_size);
   358 							$info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
   358 							$this->warning('Next ID3v2 frame is valid, skipping current frame.');
   359 
   359 
   360 						} else {
   360 						} else {
   361 
   361 
   362 							// next frame is invalid too, abort processing
   362 							// next frame is invalid too, abort processing
   363 							//unset($framedata);
   363 							//unset($framedata);
   364 							$framedata = null;
   364 							$framedata = null;
   365 							$info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
   365 							$this->error('Next ID3v2 frame is also invalid, aborting processing.');
   366 
   366 
   367 						}
   367 						}
   368 
   368 
   369 					} elseif ($frame_size == strlen($framedata)) {
   369 					} elseif ($frame_size == strlen($framedata)) {
   370 
   370 
   371 						// this is the last frame, just skip
   371 						// this is the last frame, just skip
   372 						$info['warning'][] = 'This was the last ID3v2 frame.';
   372 						$this->warning('This was the last ID3v2 frame.');
   373 
   373 
   374 					} else {
   374 					} else {
   375 
   375 
   376 						// next frame is invalid too, abort processing
   376 						// next frame is invalid too, abort processing
   377 						//unset($framedata);
   377 						//unset($framedata);
   378 						$framedata = null;
   378 						$framedata = null;
   379 						$info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
   379 						$this->warning('Invalid ID3v2 frame size, aborting.');
   380 
   380 
   381 					}
   381 					}
   382 					if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
   382 					if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
   383 
   383 
   384 						switch ($frame_name) {
   384 						switch ($frame_name) {
   387 							case ' MP3':
   387 							case ' MP3':
   388 							case 'MP3e':
   388 							case 'MP3e':
   389 							case "\x00".'MP':
   389 							case "\x00".'MP':
   390 							case ' MP':
   390 							case ' MP':
   391 							case 'MP3':
   391 							case 'MP3':
   392 								$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
   392 								$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/)"]');
   393 								break;
   393 								break;
   394 
   394 
   395 							default:
   395 							default:
   396 								$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
   396 								$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
   397 								break;
   397 								break;
   398 						}
   398 						}
   399 
   399 
   400 					} elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
   400 					} elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
   401 
   401 
   402 						$info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).';
   402 						$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').')).');
   403 
   403 
   404 					} else {
   404 					} else {
   405 
   405 
   406 						$info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
   406 						$this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');
   407 
   407 
   408 					}
   408 					}
   409 
   409 
   410 				}
   410 				}
   411 				$framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
   411 				$framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
   440 				$thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
   440 				$thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
   441 			}
   441 			}
   442 		} // end footer
   442 		} // end footer
   443 
   443 
   444 		if (isset($thisfile_id3v2['comments']['genre'])) {
   444 		if (isset($thisfile_id3v2['comments']['genre'])) {
       
   445 			$genres = array();
   445 			foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
   446 			foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
   446 				unset($thisfile_id3v2['comments']['genre'][$key]);
   447 				foreach ($this->ParseID3v2GenreString($value) as $genre) {
   447 				$thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
   448 					$genres[] = $genre;
   448 			}
   449 				}
       
   450 			}
       
   451 			$thisfile_id3v2['comments']['genre'] = array_unique($genres);
       
   452 			unset($key, $value, $genres, $genre);
   449 		}
   453 		}
   450 
   454 
   451 		if (isset($thisfile_id3v2['comments']['track'])) {
   455 		if (isset($thisfile_id3v2['comments']['track'])) {
   452 			foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
   456 			foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
   453 				if (strstr($value, '/')) {
   457 				if (strstr($value, '/')) {
   498 	public function ParseID3v2GenreString($genrestring) {
   502 	public function ParseID3v2GenreString($genrestring) {
   499 		// Parse genres into arrays of genreName and genreID
   503 		// Parse genres into arrays of genreName and genreID
   500 		// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
   504 		// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
   501 		// ID3v2.4.x: '21' $00 'Eurodisco' $00
   505 		// ID3v2.4.x: '21' $00 'Eurodisco' $00
   502 		$clean_genres = array();
   506 		$clean_genres = array();
       
   507 
       
   508 		// hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
       
   509 		if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
       
   510 			// note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
       
   511 			// replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
       
   512 			if (preg_match('#/#', $genrestring)) {
       
   513 				$genrestring = str_replace('/', "\x00", $genrestring);
       
   514 				$genrestring = str_replace('Pop'."\x00".'Funk', 'Pop/Funk', $genrestring);
       
   515 				$genrestring = str_replace('Rock'."\x00".'Rock', 'Folk/Rock', $genrestring);
       
   516 			}
       
   517 
       
   518 			// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
       
   519 			if (preg_match('#;#', $genrestring)) {
       
   520 				$genrestring = str_replace(';', "\x00", $genrestring);
       
   521 			}
       
   522 		}
       
   523 
       
   524 
   503 		if (strpos($genrestring, "\x00") === false) {
   525 		if (strpos($genrestring, "\x00") === false) {
   504 			$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
   526 			$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
   505 		}
   527 		}
       
   528 
   506 		$genre_elements = explode("\x00", $genrestring);
   529 		$genre_elements = explode("\x00", $genrestring);
   507 		foreach ($genre_elements as $element) {
   530 		foreach ($genre_elements as $element) {
   508 			$element = trim($element);
   531 			$element = trim($element);
   509 			if ($element) {
   532 			if ($element) {
   510 				if (preg_match('#^[0-9]{1,3}#', $element)) {
   533 				if (preg_match('#^[0-9]{1,3}#', $element)) {
   569 
   592 
   570 			//    Frame-level de-compression
   593 			//    Frame-level de-compression
   571 			if ($parsedFrame['flags']['compression']) {
   594 			if ($parsedFrame['flags']['compression']) {
   572 				$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
   595 				$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
   573 				if (!function_exists('gzuncompress')) {
   596 				if (!function_exists('gzuncompress')) {
   574 					$info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
   597 					$this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
   575 				} else {
   598 				} else {
   576 					if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
   599 					if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
   577 					//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
   600 					//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
   578 						$parsedFrame['data'] = $decompresseddata;
   601 						$parsedFrame['data'] = $decompresseddata;
   579 						unset($decompresseddata);
   602 						unset($decompresseddata);
   580 					} else {
   603 					} else {
   581 						$info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
   604 						$this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
   582 					}
   605 					}
   583 				}
   606 				}
   584 			}
   607 			}
   585 		}
   608 		}
   586 
   609 
   587 		if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
   610 		if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
   588 			if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
   611 			if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
   589 				$info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
   612 				$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');
   590 			}
   613 			}
   591 		}
   614 		}
   592 
   615 
   593 		if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
   616 		if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
   594 
   617 
   599 					break;
   622 					break;
   600 
   623 
   601 				default:
   624 				default:
   602 					break;
   625 					break;
   603 			}
   626 			}
   604 			$info['warning'][] = $warning;
   627 			$this->warning($warning);
   605 
   628 
   606 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
   629 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
   607 			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
   630 			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
   608 			//   There may be more than one 'UFID' frame in a tag,
   631 			//   There may be more than one 'UFID' frame in a tag,
   609 			//   but only one with the same 'Owner identifier'.
   632 			//   but only one with the same 'Owner identifier'.
   623 			// Description       <text string according to encoding> $00 (00)
   646 			// Description       <text string according to encoding> $00 (00)
   624 			// Value             <text string according to encoding>
   647 			// Value             <text string according to encoding>
   625 
   648 
   626 			$frame_offset = 0;
   649 			$frame_offset = 0;
   627 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   650 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   628 
   651 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
   629 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   652 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   630 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
   653 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   631 			}
   654 				$frame_textencoding_terminator = "\x00";
   632 			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
   655 			}
   633 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
   656 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
       
   657 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   634 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   658 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   635 			}
   659 			}
   636 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   660 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   637 			if (ord($frame_description) === 0) {
   661 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
       
   662 				// if description only contains a BOM or terminator then make it blank
   638 				$frame_description = '';
   663 				$frame_description = '';
   639 			}
   664 			}
   640 			$parsedFrame['encodingid']  = $frame_textencoding;
   665 			$parsedFrame['encodingid']  = $frame_textencoding;
   641 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
   666 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
   642 
   667 
   643 			$parsedFrame['description'] = $frame_description;
   668 			$parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description));
   644 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
   669 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
   645 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   670 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   646 				$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
   671 				$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
   647 				if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
   672 				if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
   648 					$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
   673 					$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
   649 				} else {
   674 				} else {
   661 			// Information                  <text string(s) according to encoding>
   686 			// Information                  <text string(s) according to encoding>
   662 
   687 
   663 			$frame_offset = 0;
   688 			$frame_offset = 0;
   664 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   689 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   665 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   690 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   666 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
   691 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   667 			}
   692 			}
   668 
   693 
   669 			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
   694 			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
   670 
   695 
   671 			$parsedFrame['encodingid'] = $frame_textencoding;
   696 			$parsedFrame['encodingid'] = $frame_textencoding;
   715 			// Description       <text string according to encoding> $00 (00)
   740 			// Description       <text string according to encoding> $00 (00)
   716 			// URL               <text string>
   741 			// URL               <text string>
   717 
   742 
   718 			$frame_offset = 0;
   743 			$frame_offset = 0;
   719 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   744 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
       
   745 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
   720 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   746 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   721 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
   747 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   722 			}
   748 				$frame_textencoding_terminator = "\x00";
   723 			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
   749 			}
   724 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
   750 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
       
   751 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   725 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   752 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   726 			}
   753 			}
   727 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   754 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   728 
   755 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
   729 			if (ord($frame_description) === 0) {
   756 				// if description only contains a BOM or terminator then make it blank
   730 				$frame_description = '';
   757 				$frame_description = '';
   731 			}
   758 			}
   732 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
   759 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
   733 
   760 
   734 			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
   761 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator);
   735 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
   762 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   736 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   763 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   737 			}
   764 			}
   738 			if ($frame_terminatorpos) {
   765 			if ($frame_terminatorpos) {
   739 				// there are null bytes after the data - this is not according to spec
   766 				// there are null bytes after the data - this is not according to spec
   740 				// only use data up to first null byte
   767 				// only use data up to first null byte
   778 			// People list strings    <textstrings>
   805 			// People list strings    <textstrings>
   779 
   806 
   780 			$frame_offset = 0;
   807 			$frame_offset = 0;
   781 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   808 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   782 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   809 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   783 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
   810 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   784 			}
   811 			}
   785 			$parsedFrame['encodingid'] = $frame_textencoding;
   812 			$parsedFrame['encodingid'] = $frame_textencoding;
   786 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
   813 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
   787 			$parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
   814 			$parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
   788 
   815 
   954 			// Content descriptor   <text string according to encoding> $00 (00)
   981 			// Content descriptor   <text string according to encoding> $00 (00)
   955 			// Lyrics/text          <full text string according to encoding>
   982 			// Lyrics/text          <full text string according to encoding>
   956 
   983 
   957 			$frame_offset = 0;
   984 			$frame_offset = 0;
   958 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
   985 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
       
   986 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
   959 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   987 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   960 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
   988 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
       
   989 				$frame_textencoding_terminator = "\x00";
   961 			}
   990 			}
   962 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
   991 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
   963 			$frame_offset += 3;
   992 			$frame_offset += 3;
   964 			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
   993 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   965 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
   994 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   966 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   995 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   967 			}
   996 			}
   968 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   997 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   969 			if (ord($frame_description) === 0) {
   998 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
       
   999 				// if description only contains a BOM or terminator then make it blank
   970 				$frame_description = '';
  1000 				$frame_description = '';
   971 			}
  1001 			}
   972 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
  1002 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
   973 
  1003 
   974 			$parsedFrame['encodingid']   = $frame_textencoding;
  1004 			$parsedFrame['encodingid']   = $frame_textencoding;
   975 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
  1005 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
   976 
  1006 
   977 			$parsedFrame['data']         = $parsedFrame['data'];
       
   978 			$parsedFrame['language']     = $frame_language;
  1007 			$parsedFrame['language']     = $frame_language;
   979 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
  1008 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
   980 			$parsedFrame['description']  = $frame_description;
  1009 			$parsedFrame['description']  = $frame_description;
   981 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
  1010 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   982 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
  1011 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
  1000 			//   Sync identifier (terminator to above string)   $00 (00)
  1029 			//   Sync identifier (terminator to above string)   $00 (00)
  1001 			//   Time stamp                                     $xx (xx ...)
  1030 			//   Time stamp                                     $xx (xx ...)
  1002 
  1031 
  1003 			$frame_offset = 0;
  1032 			$frame_offset = 0;
  1004 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1033 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
       
  1034 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
  1005 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1035 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1006 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
  1036 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
       
  1037 				$frame_textencoding_terminator = "\x00";
  1007 			}
  1038 			}
  1008 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
  1039 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
  1009 			$frame_offset += 3;
  1040 			$frame_offset += 3;
  1010 			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1041 			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1011 			$parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1042 			$parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1018 
  1049 
  1019 			$timestampindex = 0;
  1050 			$timestampindex = 0;
  1020 			$frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
  1051 			$frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
  1021 			while (strlen($frame_remainingdata)) {
  1052 			while (strlen($frame_remainingdata)) {
  1022 				$frame_offset = 0;
  1053 				$frame_offset = 0;
  1023 				$frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding));
  1054 				$frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
  1024 				if ($frame_terminatorpos === false) {
  1055 				if ($frame_terminatorpos === false) {
  1025 					$frame_remainingdata = '';
  1056 					$frame_remainingdata = '';
  1026 				} else {
  1057 				} else {
  1027 					if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
  1058 					if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1028 						$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1059 						$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1029 					}
  1060 					}
  1030 					$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
  1061 					$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
  1031 
  1062 
  1032 					$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
  1063 					$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
  1033 					if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
  1064 					if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
  1034 						// timestamp probably omitted for first data item
  1065 						// timestamp probably omitted for first data item
  1035 					} else {
  1066 					} else {
  1036 						$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
  1067 						$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
  1037 						$frame_remainingdata = substr($frame_remainingdata, 4);
  1068 						$frame_remainingdata = substr($frame_remainingdata, 4);
  1052 			// Short content descrip. <text string according to encoding> $00 (00)
  1083 			// Short content descrip. <text string according to encoding> $00 (00)
  1053 			// The actual text        <full text string according to encoding>
  1084 			// The actual text        <full text string according to encoding>
  1054 
  1085 
  1055 			if (strlen($parsedFrame['data']) < 5) {
  1086 			if (strlen($parsedFrame['data']) < 5) {
  1056 
  1087 
  1057 				$info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
  1088 				$this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
  1058 
  1089 
  1059 			} else {
  1090 			} else {
  1060 
  1091 
  1061 				$frame_offset = 0;
  1092 				$frame_offset = 0;
  1062 				$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1093 				$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
       
  1094 				$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
  1063 				if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1095 				if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1064 					$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
  1096 					$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
       
  1097 					$frame_textencoding_terminator = "\x00";
  1065 				}
  1098 				}
  1066 				$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
  1099 				$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
  1067 				$frame_offset += 3;
  1100 				$frame_offset += 3;
  1068 				$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
  1101 				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1069 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
  1102 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1070 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1103 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1071 				}
  1104 				}
  1072 				$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1105 				$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1073 				if (ord($frame_description) === 0) {
  1106 				if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
       
  1107 					// if description only contains a BOM or terminator then make it blank
  1074 					$frame_description = '';
  1108 					$frame_description = '';
  1075 				}
  1109 				}
  1076 				$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
  1110 				$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
  1077 
  1111 
  1078 				$parsedFrame['encodingid']   = $frame_textencoding;
  1112 				$parsedFrame['encodingid']   = $frame_textencoding;
  1079 				$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
  1113 				$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
  1080 
  1114 
  1081 				$parsedFrame['language']     = $frame_language;
  1115 				$parsedFrame['language']     = $frame_language;
  1121 				$parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
  1155 				$parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
  1122 				$parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
  1156 				$parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
  1123 				$frame_offset += 2;
  1157 				$frame_offset += 2;
  1124 				$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
  1158 				$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
  1125 				if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
  1159 				if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
  1126 					$info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
  1160 					$this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
  1127 					break;
  1161 					break;
  1128 				}
  1162 				}
  1129 				$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
  1163 				$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
  1130 				$parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
  1164 				$parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
  1131 				$frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
  1165 				$frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
  1328 			// Description        <text string according to encoding> $00 (00)
  1362 			// Description        <text string according to encoding> $00 (00)
  1329 			// Picture data       <binary data>
  1363 			// Picture data       <binary data>
  1330 
  1364 
  1331 			$frame_offset = 0;
  1365 			$frame_offset = 0;
  1332 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1366 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
       
  1367 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
  1333 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1368 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1334 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
  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";
  1335 			}
  1371 			}
  1336 
  1372 
  1337 			if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
  1373 			if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
  1338 				$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
  1374 				$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
  1339 				if (strtolower($frame_imagetype) == 'ima') {
  1375 				if (strtolower($frame_imagetype) == 'ima') {
  1363 			}
  1399 			}
  1364 
  1400 
  1365 			$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1401 			$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1366 
  1402 
  1367 			if ($frame_offset >= $parsedFrame['datalength']) {
  1403 			if ($frame_offset >= $parsedFrame['datalength']) {
  1368 				$info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
  1404 				$this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
  1369 			} else {
  1405 			} else {
  1370 				$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
  1406 				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1371 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
  1407 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1372 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1408 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1373 				}
  1409 				}
  1374 				$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1410 				$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1375 				if (ord($frame_description) === 0) {
  1411 				if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
       
  1412 					// if description only contains a BOM or terminator then make it blank
  1376 					$frame_description = '';
  1413 					$frame_description = '';
  1377 				}
  1414 				}
  1378 				$parsedFrame['encodingid']       = $frame_textencoding;
  1415 				$parsedFrame['encodingid']       = $frame_textencoding;
  1379 				$parsedFrame['encoding']         = $this->TextEncodingNameLookup($frame_textencoding);
  1416 				$parsedFrame['encoding']         = $this->TextEncodingNameLookup($frame_textencoding);
  1380 
  1417 
  1384 					$parsedFrame['mime']         = $frame_mimetype;
  1421 					$parsedFrame['mime']         = $frame_mimetype;
  1385 				}
  1422 				}
  1386 				$parsedFrame['picturetypeid']    = $frame_picturetype;
  1423 				$parsedFrame['picturetypeid']    = $frame_picturetype;
  1387 				$parsedFrame['picturetype']      = $this->APICPictureTypeLookup($frame_picturetype);
  1424 				$parsedFrame['picturetype']      = $this->APICPictureTypeLookup($frame_picturetype);
  1388 				$parsedFrame['description']      = $frame_description;
  1425 				$parsedFrame['description']      = $frame_description;
  1389 				$parsedFrame['data']             = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
  1426 				$parsedFrame['data']             = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
  1390 				$parsedFrame['datalength']       = strlen($parsedFrame['data']);
  1427 				$parsedFrame['datalength']       = strlen($parsedFrame['data']);
  1391 
  1428 
  1392 				$parsedFrame['image_mime'] = '';
  1429 				$parsedFrame['image_mime'] = '';
  1393 				$imageinfo = array();
  1430 				$imageinfo = array();
  1394 				$imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
  1431 				if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
  1395 				if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
  1432 					if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
  1396 					$parsedFrame['image_mime']       = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
  1433 						$parsedFrame['image_mime']       = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
  1397 					if ($imagechunkcheck[0]) {
  1434 						if ($imagechunkcheck[0]) {
  1398 						$parsedFrame['image_width']  = $imagechunkcheck[0];
  1435 							$parsedFrame['image_width']  = $imagechunkcheck[0];
  1399 					}
  1436 						}
  1400 					if ($imagechunkcheck[1]) {
  1437 						if ($imagechunkcheck[1]) {
  1401 						$parsedFrame['image_height'] = $imagechunkcheck[1];
  1438 							$parsedFrame['image_height'] = $imagechunkcheck[1];
       
  1439 						}
  1402 					}
  1440 					}
  1403 				}
  1441 				}
  1404 
  1442 
  1405 				do {
  1443 				do {
  1406 					if ($this->getid3->option_save_attachments === false) {
  1444 					if ($this->getid3->option_save_attachments === false) {
  1412 						// great
  1450 						// great
  1413 /*
  1451 /*
  1414 					} elseif (is_int($this->getid3->option_save_attachments)) {
  1452 					} elseif (is_int($this->getid3->option_save_attachments)) {
  1415 						if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
  1453 						if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
  1416 							// too big, skip
  1454 							// too big, skip
  1417 							$info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
  1455 							$this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
  1418 							unset($parsedFrame['data']);
  1456 							unset($parsedFrame['data']);
  1419 							break;
  1457 							break;
  1420 						}
  1458 						}
  1421 */
  1459 */
  1422 					} elseif (is_string($this->getid3->option_save_attachments)) {
  1460 					} elseif (is_string($this->getid3->option_save_attachments)) {
  1423 						$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
  1461 						$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
  1424 						if (!is_dir($dir) || !is_writable($dir)) {
  1462 						if (!is_dir($dir) || !getID3::is_writable($dir)) {
  1425 							// cannot write, skip
  1463 							// cannot write, skip
  1426 							$info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)';
  1464 							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
  1427 							unset($parsedFrame['data']);
  1465 							unset($parsedFrame['data']);
  1428 							break;
  1466 							break;
  1429 						}
  1467 						}
  1430 					}
  1468 					}
  1431 					// if we get this far, must be OK
  1469 					// if we get this far, must be OK
  1432 					if (is_string($this->getid3->option_save_attachments)) {
  1470 					if (is_string($this->getid3->option_save_attachments)) {
  1433 						$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
  1471 						$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
  1434 						if (!file_exists($destination_filename) || is_writable($destination_filename)) {
  1472 						if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
  1435 							file_put_contents($destination_filename, $parsedFrame['data']);
  1473 							file_put_contents($destination_filename, $parsedFrame['data']);
  1436 						} else {
  1474 						} else {
  1437 							$info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
  1475 							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
  1438 						}
  1476 						}
  1439 						$parsedFrame['data_filename'] = $destination_filename;
  1477 						$parsedFrame['data_filename'] = $destination_filename;
  1440 						unset($parsedFrame['data']);
  1478 						unset($parsedFrame['data']);
  1441 					} else {
  1479 					} else {
  1442 						if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
  1480 						if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
  1443 							if (!isset($info['id3v2']['comments']['picture'])) {
  1481 							if (!isset($info['id3v2']['comments']['picture'])) {
  1444 								$info['id3v2']['comments']['picture'] = array();
  1482 								$info['id3v2']['comments']['picture'] = array();
  1445 							}
  1483 							}
  1446 							$info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']);
  1484 							$comments_picture_data = array();
       
  1485 							foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
       
  1486 								if (isset($parsedFrame[$picture_key])) {
       
  1487 									$comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
       
  1488 								}
       
  1489 							}
       
  1490 							$info['id3v2']['comments']['picture'][] = $comments_picture_data;
       
  1491 							unset($comments_picture_data);
  1447 						}
  1492 						}
  1448 					}
  1493 					}
  1449 				} while (false);
  1494 				} while (false);
  1450 			}
  1495 			}
  1451 
  1496 
  1460 			// Content description    <text string according to encoding> $00 (00)
  1505 			// Content description    <text string according to encoding> $00 (00)
  1461 			// Encapsulated object    <binary data>
  1506 			// Encapsulated object    <binary data>
  1462 
  1507 
  1463 			$frame_offset = 0;
  1508 			$frame_offset = 0;
  1464 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1509 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
       
  1510 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
  1465 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1511 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1466 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
  1512 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
       
  1513 				$frame_textencoding_terminator = "\x00";
  1467 			}
  1514 			}
  1468 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1515 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1469 			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1516 			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1470 			if (ord($frame_mimetype) === 0) {
  1517 			if (ord($frame_mimetype) === 0) {
  1471 				$frame_mimetype = '';
  1518 				$frame_mimetype = '';
  1472 			}
  1519 			}
  1473 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1520 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1474 
  1521 
  1475 			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
  1522 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1476 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
  1523 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1477 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1524 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1478 			}
  1525 			}
  1479 			$frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1526 			$frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1480 			if (ord($frame_filename) === 0) {
  1527 			if (ord($frame_filename) === 0) {
  1481 				$frame_filename = '';
  1528 				$frame_filename = '';
  1482 			}
  1529 			}
  1483 			$frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
  1530 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
  1484 
  1531 
  1485 			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
  1532 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1486 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
  1533 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1487 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1534 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1488 			}
  1535 			}
  1489 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1536 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1490 			if (ord($frame_description) === 0) {
  1537 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
       
  1538 				// if description only contains a BOM or terminator then make it blank
  1491 				$frame_description = '';
  1539 				$frame_description = '';
  1492 			}
  1540 			}
  1493 			$frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
  1541 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
  1494 
  1542 
  1495 			$parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
  1543 			$parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
  1496 			$parsedFrame['encodingid']  = $frame_textencoding;
  1544 			$parsedFrame['encodingid']  = $frame_textencoding;
  1497 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
  1545 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
  1498 
  1546 
  1567 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1615 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1568 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1616 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1569 
  1617 
  1570 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1618 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1571 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1619 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1572 			if (ord($frame_description) === 0) {
  1620 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
       
  1621 				// if description only contains a BOM or terminator then make it blank
  1573 				$frame_description = '';
  1622 				$frame_description = '';
  1574 			}
  1623 			}
  1575 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1624 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1576 
  1625 
  1577 			$parsedFrame['ownerid']     = $frame_ownerid;
  1626 			$parsedFrame['ownerid']     = $frame_ownerid;
  1592 
  1641 
  1593 			$frame_offset = 0;
  1642 			$frame_offset = 0;
  1594 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1643 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1595 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1644 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1596 			if (ord($frame_ownerid) === 0) {
  1645 			if (ord($frame_ownerid) === 0) {
  1597 				$frame_ownerid == '';
  1646 				$frame_ownerid = '';
  1598 			}
  1647 			}
  1599 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1648 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1600 			$parsedFrame['ownerid'] = $frame_ownerid;
  1649 			$parsedFrame['ownerid'] = $frame_ownerid;
  1601 			$parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
  1650 			$parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
  1602 			$frame_offset += 2;
  1651 			$frame_offset += 2;
  1605 			$parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
  1654 			$parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
  1606 			unset($parsedFrame['data']);
  1655 			unset($parsedFrame['data']);
  1607 
  1656 
  1608 
  1657 
  1609 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
  1658 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
  1610 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {     // 4.22  LNK  Linked information
  1659 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {    // 4.22  LNK  Linked information
  1611 			//   There may be more than one 'LINK' frame in a tag,
  1660 			//   There may be more than one 'LINK' frame in a tag,
  1612 			//   but only one with the same contents
  1661 			//   but only one with the same contents
  1613 			// <Header for 'Linked information', ID: 'LINK'>
  1662 			// <Header for 'Linked information', ID: 'LINK'>
  1614 			// ID3v2.3+ => Frame identifier   $xx xx xx xx
  1663 			// ID3v2.3+ => Frame identifier   $xx xx xx xx
  1615 			// ID3v2.2  => Frame identifier   $xx xx xx
  1664 			// ID3v2.2  => Frame identifier   $xx xx xx
  1633 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1682 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1634 			$parsedFrame['url'] = $frame_url;
  1683 			$parsedFrame['url'] = $frame_url;
  1635 
  1684 
  1636 			$parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
  1685 			$parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
  1637 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
  1686 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
  1638 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']);
  1687 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
  1639 			}
  1688 			}
  1640 			unset($parsedFrame['data']);
  1689 			unset($parsedFrame['data']);
  1641 
  1690 
  1642 
  1691 
  1643 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
  1692 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
  1661 			// The actual text      <text string according to encoding>
  1710 			// The actual text      <text string according to encoding>
  1662 
  1711 
  1663 			$frame_offset = 0;
  1712 			$frame_offset = 0;
  1664 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1713 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1665 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1714 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1666 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
  1715 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
  1667 			}
  1716 			}
  1668 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
  1717 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
  1669 			$frame_offset += 3;
  1718 			$frame_offset += 3;
  1670 			$parsedFrame['language']     = $frame_language;
  1719 			$parsedFrame['language']     = $frame_language;
  1671 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
  1720 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
  1688 			// Seller            <text string according to encoding>
  1737 			// Seller            <text string according to encoding>
  1689 
  1738 
  1690 			$frame_offset = 0;
  1739 			$frame_offset = 0;
  1691 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1740 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1692 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1741 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1693 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
  1742 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
  1694 			}
  1743 			}
  1695 			$parsedFrame['encodingid'] = $frame_textencoding;
  1744 			$parsedFrame['encodingid'] = $frame_textencoding;
  1696 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
  1745 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
  1697 
  1746 
  1698 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1747 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1702 			$parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
  1751 			$parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
  1703 			$parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
  1752 			$parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
  1704 			$parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);
  1753 			$parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);
  1705 
  1754 
  1706 			$parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
  1755 			$parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
  1707 			if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) {
  1756 			if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
  1708 				$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
  1757 				$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
  1709 			}
  1758 			}
  1710 			$frame_offset += 8;
  1759 			$frame_offset += 8;
  1711 
  1760 
  1712 			$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
  1761 			$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
  1727 			// Picture MIME type  <string> $00
  1776 			// Picture MIME type  <string> $00
  1728 			// Seller logo        <binary data>
  1777 			// Seller logo        <binary data>
  1729 
  1778 
  1730 			$frame_offset = 0;
  1779 			$frame_offset = 0;
  1731 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1780 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
       
  1781 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
  1732 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1782 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
  1733 				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
  1783 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
       
  1784 				$frame_textencoding_terminator = "\x00";
  1734 			}
  1785 			}
  1735 
  1786 
  1736 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1787 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1737 			$frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1788 			$frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1738 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1789 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1750 			$frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1801 			$frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1751 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1802 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1752 
  1803 
  1753 			$frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1804 			$frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
  1754 
  1805 
  1755 			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
  1806 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1756 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
  1807 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1757 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1808 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1758 			}
  1809 			}
  1759 			$frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1810 			$frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1760 			if (ord($frame_sellername) === 0) {
  1811 			if (ord($frame_sellername) === 0) {
  1761 				$frame_sellername = '';
  1812 				$frame_sellername = '';
  1762 			}
  1813 			}
  1763 			$frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
  1814 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
  1764 
  1815 
  1765 			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
  1816 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1766 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
  1817 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1767 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1818 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1768 			}
  1819 			}
  1769 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1820 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1770 			if (ord($frame_description) === 0) {
  1821 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
       
  1822 				// if description only contains a BOM or terminator then make it blank
  1771 				$frame_description = '';
  1823 				$frame_description = '';
  1772 			}
  1824 			}
  1773 			$frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
  1825 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
  1774 
  1826 
  1775 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1827 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1776 			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1828 			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1777 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1829 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1778 
  1830 
  1941 			$info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
  1993 			$info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
  1942 			$info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
  1994 			$info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
  1943 			$info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
  1995 			$info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
  1944 
  1996 
  1945 			unset($parsedFrame['data']);
  1997 			unset($parsedFrame['data']);
       
  1998 
       
  1999 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
       
  2000 			// http://id3.org/id3v2-chapters-1.0
       
  2001 			// <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
       
  2002 			// Element ID      <text string> $00
       
  2003 			// Start time      $xx xx xx xx
       
  2004 			// End time        $xx xx xx xx
       
  2005             // Start offset    $xx xx xx xx
       
  2006             // End offset      $xx xx xx xx
       
  2007             // <Optional embedded sub-frames>
       
  2008 
       
  2009 			$frame_offset = 0;
       
  2010 			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
       
  2011 			$frame_offset += strlen($parsedFrame['element_id']."\x00");
       
  2012 			$parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
       
  2013 			$frame_offset += 4;
       
  2014 			$parsedFrame['time_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
       
  2015 			$frame_offset += 4;
       
  2016 			if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
       
  2017 				// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
       
  2018 				$parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
       
  2019 			}
       
  2020 			$frame_offset += 4;
       
  2021 			if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
       
  2022 				// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
       
  2023 				$parsedFrame['offset_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
       
  2024 			}
       
  2025 			$frame_offset += 4;
       
  2026 
       
  2027 			if ($frame_offset < strlen($parsedFrame['data'])) {
       
  2028 				$parsedFrame['subframes'] = array();
       
  2029 				while ($frame_offset < strlen($parsedFrame['data'])) {
       
  2030 					// <Optional embedded sub-frames>
       
  2031 					$subframe = array();
       
  2032 					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
       
  2033 					$frame_offset += 4;
       
  2034 					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
       
  2035 					$frame_offset += 4;
       
  2036 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
       
  2037 					$frame_offset += 2;
       
  2038 					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
       
  2039 						$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)');
       
  2040 						break;
       
  2041 					}
       
  2042 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
       
  2043 					$frame_offset += $subframe['size'];
       
  2044 
       
  2045 					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
       
  2046 					$subframe['text']       =     substr($subframe_rawdata, 1);
       
  2047 					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
       
  2048 					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
       
  2049 					switch (substr($encoding_converted_text, 0, 2)) {
       
  2050 						case "\xFF\xFE":
       
  2051 						case "\xFE\xFF":
       
  2052 							switch (strtoupper($info['id3v2']['encoding'])) {
       
  2053 								case 'ISO-8859-1':
       
  2054 								case 'UTF-8':
       
  2055 									$encoding_converted_text = substr($encoding_converted_text, 2);
       
  2056 									// remove unwanted byte-order-marks
       
  2057 									break;
       
  2058 								default:
       
  2059 									// ignore
       
  2060 									break;
       
  2061 							}
       
  2062 							break;
       
  2063 						default:
       
  2064 							// do not remove BOM
       
  2065 							break;
       
  2066 					}
       
  2067 
       
  2068 					if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
       
  2069 						if ($subframe['name'] == 'TIT2') {
       
  2070 							$parsedFrame['chapter_name']        = $encoding_converted_text;
       
  2071 						} elseif ($subframe['name'] == 'TIT3') {
       
  2072 							$parsedFrame['chapter_description'] = $encoding_converted_text;
       
  2073 						}
       
  2074 						$parsedFrame['subframes'][] = $subframe;
       
  2075 					} else {
       
  2076 						$this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
       
  2077 					}
       
  2078 				}
       
  2079 				unset($subframe_rawdata, $subframe, $encoding_converted_text);
       
  2080 			}
       
  2081 
       
  2082 			$id3v2_chapter_entry = array();
       
  2083 			foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') as $id3v2_chapter_key) {
       
  2084 				if (isset($parsedFrame[$id3v2_chapter_key])) {
       
  2085 					$id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
       
  2086 				}
       
  2087 			}
       
  2088 			if (!isset($info['id3v2']['chapters'])) {
       
  2089 				$info['id3v2']['chapters'] = array();
       
  2090 			}
       
  2091 			$info['id3v2']['chapters'][] = $id3v2_chapter_entry;
       
  2092 			unset($id3v2_chapter_entry, $id3v2_chapter_key);
       
  2093 
       
  2094 
       
  2095 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
       
  2096 			// http://id3.org/id3v2-chapters-1.0
       
  2097 			// <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC">           (10 bytes)
       
  2098 			// Element ID      <text string> $00
       
  2099 			// CTOC flags        %xx
       
  2100 			// Entry count       $xx
       
  2101 			// Child Element ID  <string>$00   /* zero or more child CHAP or CTOC entries */
       
  2102             // <Optional embedded sub-frames>
       
  2103 
       
  2104 			$frame_offset = 0;
       
  2105 			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
       
  2106 			$frame_offset += strlen($parsedFrame['element_id']."\x00");
       
  2107 			$ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
       
  2108 			$frame_offset += 1;
       
  2109 			$parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
       
  2110 			$frame_offset += 1;
       
  2111 
       
  2112 			$terminator_position = null;
       
  2113 			for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
       
  2114 				$terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
       
  2115 				$parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
       
  2116 				$frame_offset = $terminator_position + 1;
       
  2117 			}
       
  2118 
       
  2119 			$parsedFrame['ctoc_flags']['ordered']   = (bool) ($ctoc_flags_raw & 0x01);
       
  2120 			$parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
       
  2121 
       
  2122 			unset($ctoc_flags_raw, $terminator_position);
       
  2123 
       
  2124 			if ($frame_offset < strlen($parsedFrame['data'])) {
       
  2125 				$parsedFrame['subframes'] = array();
       
  2126 				while ($frame_offset < strlen($parsedFrame['data'])) {
       
  2127 					// <Optional embedded sub-frames>
       
  2128 					$subframe = array();
       
  2129 					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
       
  2130 					$frame_offset += 4;
       
  2131 					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
       
  2132 					$frame_offset += 4;
       
  2133 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
       
  2134 					$frame_offset += 2;
       
  2135 					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
       
  2136 						$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)');
       
  2137 						break;
       
  2138 					}
       
  2139 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
       
  2140 					$frame_offset += $subframe['size'];
       
  2141 
       
  2142 					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
       
  2143 					$subframe['text']       =     substr($subframe_rawdata, 1);
       
  2144 					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
       
  2145 					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
       
  2146 					switch (substr($encoding_converted_text, 0, 2)) {
       
  2147 						case "\xFF\xFE":
       
  2148 						case "\xFE\xFF":
       
  2149 							switch (strtoupper($info['id3v2']['encoding'])) {
       
  2150 								case 'ISO-8859-1':
       
  2151 								case 'UTF-8':
       
  2152 									$encoding_converted_text = substr($encoding_converted_text, 2);
       
  2153 									// remove unwanted byte-order-marks
       
  2154 									break;
       
  2155 								default:
       
  2156 									// ignore
       
  2157 									break;
       
  2158 							}
       
  2159 							break;
       
  2160 						default:
       
  2161 							// do not remove BOM
       
  2162 							break;
       
  2163 					}
       
  2164 
       
  2165 					if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
       
  2166 						if ($subframe['name'] == 'TIT2') {
       
  2167 							$parsedFrame['toc_name']        = $encoding_converted_text;
       
  2168 						} elseif ($subframe['name'] == 'TIT3') {
       
  2169 							$parsedFrame['toc_description'] = $encoding_converted_text;
       
  2170 						}
       
  2171 						$parsedFrame['subframes'][] = $subframe;
       
  2172 					} else {
       
  2173 						$this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
       
  2174 					}
       
  2175 				}
       
  2176 				unset($subframe_rawdata, $subframe, $encoding_converted_text);
       
  2177 			}
  1946 
  2178 
  1947 		}
  2179 		}
  1948 
  2180 
  1949 		return true;
  2181 		return true;
  1950 	}
  2182 	}
  3342 			1   => "\x00\x00", // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
  3574 			1   => "\x00\x00", // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
  3343 			2   => "\x00\x00", // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
  3575 			2   => "\x00\x00", // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
  3344 			3   => "\x00",     // $03  UTF-8 encoded Unicode. Terminated with $00.
  3576 			3   => "\x00",     // $03  UTF-8 encoded Unicode. Terminated with $00.
  3345 			255 => "\x00\x00"
  3577 			255 => "\x00\x00"
  3346 		);
  3578 		);
  3347 		return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : '');
  3579 		return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
  3348 	}
  3580 	}
  3349 
  3581 
  3350 	public static function TextEncodingNameLookup($encoding) {
  3582 	public static function TextEncodingNameLookup($encoding) {
  3351 		// http://www.id3.org/id3v2.4.0-structure.txt
  3583 		// http://www.id3.org/id3v2.4.0-structure.txt
  3352 		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
  3584 		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
  3419 
  3651 
  3420 	public static function ID3v2HeaderLength($majorversion) {
  3652 	public static function ID3v2HeaderLength($majorversion) {
  3421 		return (($majorversion == 2) ? 6 : 10);
  3653 		return (($majorversion == 2) ? 6 : 10);
  3422 	}
  3654 	}
  3423 
  3655 
       
  3656 	public static function ID3v22iTunesBrokenFrameName($frame_name) {
       
  3657 		// iTunes (multiple versions) has been known to write ID3v2.3 style frames
       
  3658 		// but use ID3v2.2 frame names, right-padded using either [space] or [null]
       
  3659 		// to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
       
  3660 		// This function will detect and translate the corrupt frame name into ID3v2.3 standard.
       
  3661 		static $ID3v22_iTunes_BrokenFrames = array(
       
  3662 			'BUF' => 'RBUF', // Recommended buffer size
       
  3663 			'CNT' => 'PCNT', // Play counter
       
  3664 			'COM' => 'COMM', // Comments
       
  3665 			'CRA' => 'AENC', // Audio encryption
       
  3666 			'EQU' => 'EQUA', // Equalisation
       
  3667 			'ETC' => 'ETCO', // Event timing codes
       
  3668 			'GEO' => 'GEOB', // General encapsulated object
       
  3669 			'IPL' => 'IPLS', // Involved people list
       
  3670 			'LNK' => 'LINK', // Linked information
       
  3671 			'MCI' => 'MCDI', // Music CD identifier
       
  3672 			'MLL' => 'MLLT', // MPEG location lookup table
       
  3673 			'PIC' => 'APIC', // Attached picture
       
  3674 			'POP' => 'POPM', // Popularimeter
       
  3675 			'REV' => 'RVRB', // Reverb
       
  3676 			'RVA' => 'RVAD', // Relative volume adjustment
       
  3677 			'SLT' => 'SYLT', // Synchronised lyric/text
       
  3678 			'STC' => 'SYTC', // Synchronised tempo codes
       
  3679 			'TAL' => 'TALB', // Album/Movie/Show title
       
  3680 			'TBP' => 'TBPM', // BPM (beats per minute)
       
  3681 			'TCM' => 'TCOM', // Composer
       
  3682 			'TCO' => 'TCON', // Content type
       
  3683 			'TCP' => 'TCMP', // Part of a compilation
       
  3684 			'TCR' => 'TCOP', // Copyright message
       
  3685 			'TDA' => 'TDAT', // Date
       
  3686 			'TDY' => 'TDLY', // Playlist delay
       
  3687 			'TEN' => 'TENC', // Encoded by
       
  3688 			'TFT' => 'TFLT', // File type
       
  3689 			'TIM' => 'TIME', // Time
       
  3690 			'TKE' => 'TKEY', // Initial key
       
  3691 			'TLA' => 'TLAN', // Language(s)
       
  3692 			'TLE' => 'TLEN', // Length
       
  3693 			'TMT' => 'TMED', // Media type
       
  3694 			'TOA' => 'TOPE', // Original artist(s)/performer(s)
       
  3695 			'TOF' => 'TOFN', // Original filename
       
  3696 			'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
       
  3697 			'TOR' => 'TORY', // Original release year
       
  3698 			'TOT' => 'TOAL', // Original album/movie/show title
       
  3699 			'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
       
  3700 			'TP2' => 'TPE2', // Band/orchestra/accompaniment
       
  3701 			'TP3' => 'TPE3', // Conductor/performer refinement
       
  3702 			'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
       
  3703 			'TPA' => 'TPOS', // Part of a set
       
  3704 			'TPB' => 'TPUB', // Publisher
       
  3705 			'TRC' => 'TSRC', // ISRC (international standard recording code)
       
  3706 			'TRD' => 'TRDA', // Recording dates
       
  3707 			'TRK' => 'TRCK', // Track number/Position in set
       
  3708 			'TS2' => 'TSO2', // Album-Artist sort order
       
  3709 			'TSA' => 'TSOA', // Album sort order
       
  3710 			'TSC' => 'TSOC', // Composer sort order
       
  3711 			'TSI' => 'TSIZ', // Size
       
  3712 			'TSP' => 'TSOP', // Performer sort order
       
  3713 			'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
       
  3714 			'TST' => 'TSOT', // Title sort order
       
  3715 			'TT1' => 'TIT1', // Content group description
       
  3716 			'TT2' => 'TIT2', // Title/songname/content description
       
  3717 			'TT3' => 'TIT3', // Subtitle/Description refinement
       
  3718 			'TXT' => 'TEXT', // Lyricist/Text writer
       
  3719 			'TXX' => 'TXXX', // User defined text information frame
       
  3720 			'TYE' => 'TYER', // Year
       
  3721 			'UFI' => 'UFID', // Unique file identifier
       
  3722 			'ULT' => 'USLT', // Unsynchronised lyric/text transcription
       
  3723 			'WAF' => 'WOAF', // Official audio file webpage
       
  3724 			'WAR' => 'WOAR', // Official artist/performer webpage
       
  3725 			'WAS' => 'WOAS', // Official audio source webpage
       
  3726 			'WCM' => 'WCOM', // Commercial information
       
  3727 			'WCP' => 'WCOP', // Copyright/Legal information
       
  3728 			'WPB' => 'WPUB', // Publishers official webpage
       
  3729 			'WXX' => 'WXXX', // User defined URL link frame
       
  3730 		);
       
  3731 		if (strlen($frame_name) == 4) {
       
  3732 			if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) {
       
  3733 				if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) {
       
  3734 					return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)];
       
  3735 				}
       
  3736 			}
       
  3737 		}
       
  3738 		return false;
       
  3739 	}
       
  3740 
  3424 }
  3741 }