wp/wp-includes/ID3/module.tag.id3v2.php
changeset 16 a86126ab1dd4
parent 7 cf61fcea0001
child 19 3d72ae0968f4
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
     1 <?php
     1 <?php
       
     2 
     2 /////////////////////////////////////////////////////////////////
     3 /////////////////////////////////////////////////////////////////
     3 /// getID3() by James Heinrich <info@getid3.org>               //
     4 /// getID3() by James Heinrich <info@getid3.org>               //
     4 //  available at http://getid3.sourceforge.net                 //
     5 //  available at https://github.com/JamesHeinrich/getID3       //
     5 //            or http://www.getid3.org                         //
     6 //            or https://www.getid3.org                        //
     6 //          also https://github.com/JamesHeinrich/getID3       //
     7 //            or http://getid3.sourceforge.net                 //
     7 /////////////////////////////////////////////////////////////////
     8 //  see readme.txt for more details                            //
     8 // See readme.txt for more details                             //
       
     9 /////////////////////////////////////////////////////////////////
     9 /////////////////////////////////////////////////////////////////
    10 ///                                                            //
    10 ///                                                            //
    11 // module.tag.id3v2.php                                        //
    11 // module.tag.id3v2.php                                        //
    12 // module for analyzing ID3v2 tags                             //
    12 // module for analyzing ID3v2 tags                             //
    13 // dependencies: module.tag.id3v1.php                          //
    13 // dependencies: module.tag.id3v1.php                          //
    14 //                                                            ///
    14 //                                                            ///
    15 /////////////////////////////////////////////////////////////////
    15 /////////////////////////////////////////////////////////////////
    16 
    16 
       
    17 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
       
    18 	exit;
       
    19 }
    17 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
    20 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
    18 
    21 
    19 class getid3_id3v2 extends getid3_handler
    22 class getid3_id3v2 extends getid3_handler
    20 {
    23 {
    21 	public $StartingOffset = 0;
    24 	public $StartingOffset = 0;
    22 
    25 
       
    26 	/**
       
    27 	 * @return bool
       
    28 	 */
    23 	public function Analyze() {
    29 	public function Analyze() {
    24 		$info = &$this->getid3->info;
    30 		$info = &$this->getid3->info;
    25 
    31 
    26 		//    Overall tag structure:
    32 		//    Overall tag structure:
    27 		//        +-----------------------------+
    33 		//        +-----------------------------+
    54 
    60 
    55 		$this->fseek($this->StartingOffset);
    61 		$this->fseek($this->StartingOffset);
    56 		$header = $this->fread(10);
    62 		$header = $this->fread(10);
    57 		if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
    63 		if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
    58 
    64 
    59 			$thisfile_id3v2['majorversion'] = ord($header{3});
    65 			$thisfile_id3v2['majorversion'] = ord($header[3]);
    60 			$thisfile_id3v2['minorversion'] = ord($header{4});
    66 			$thisfile_id3v2['minorversion'] = ord($header[4]);
    61 
    67 
    62 			// shortcut
    68 			// shortcut
    63 			$id3v2_majorversion = &$thisfile_id3v2['majorversion'];
    69 			$id3v2_majorversion = &$thisfile_id3v2['majorversion'];
    64 
    70 
    65 		} else {
    71 		} else {
    74 			$this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
    80 			$this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
    75 			return false;
    81 			return false;
    76 
    82 
    77 		}
    83 		}
    78 
    84 
    79 		$id3_flags = ord($header{5});
    85 		$id3_flags = ord($header[5]);
    80 		switch ($id3v2_majorversion) {
    86 		switch ($id3v2_majorversion) {
    81 			case 2:
    87 			case 2:
    82 				// %ab000000 in v2.2
    88 				// %ab000000 in v2.2
    83 				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
    89 				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
    84 				$thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
    90 				$thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
   255 					// insufficient room left in ID3v2 header for actual data - must be padding
   261 					// insufficient room left in ID3v2 header for actual data - must be padding
   256 					$thisfile_id3v2['padding']['start']  = $framedataoffset;
   262 					$thisfile_id3v2['padding']['start']  = $framedataoffset;
   257 					$thisfile_id3v2['padding']['length'] = strlen($framedata);
   263 					$thisfile_id3v2['padding']['length'] = strlen($framedata);
   258 					$thisfile_id3v2['padding']['valid']  = true;
   264 					$thisfile_id3v2['padding']['valid']  = true;
   259 					for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
   265 					for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
   260 						if ($framedata{$i} != "\x00") {
   266 						if ($framedata[$i] != "\x00") {
   261 							$thisfile_id3v2['padding']['valid'] = false;
   267 							$thisfile_id3v2['padding']['valid'] = false;
   262 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
   268 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
   263 							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
   269 							$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;
   270 							break;
   265 						}
   271 						}
   266 					}
   272 					}
   267 					break; // skip rest of ID3v2 header
   273 					break; // skip rest of ID3v2 header
   268 				}
   274 				}
       
   275 				$frame_header = null;
       
   276 				$frame_name   = null;
       
   277 				$frame_size   = null;
       
   278 				$frame_flags  = null;
   269 				if ($id3v2_majorversion == 2) {
   279 				if ($id3v2_majorversion == 2) {
   270 					// Frame ID  $xx xx xx (three characters)
   280 					// Frame ID  $xx xx xx (three characters)
   271 					// Size      $xx xx xx (24-bit integer)
   281 					// Size      $xx xx xx (24-bit integer)
   272 					// Flags     $xx xx
   282 					// Flags     $xx xx
   273 
   283 
   317 					$thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
   327 					$thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
   318 					$thisfile_id3v2['padding']['valid']  = true;
   328 					$thisfile_id3v2['padding']['valid']  = true;
   319 
   329 
   320 					$len = strlen($framedata);
   330 					$len = strlen($framedata);
   321 					for ($i = 0; $i < $len; $i++) {
   331 					for ($i = 0; $i < $len; $i++) {
   322 						if ($framedata{$i} != "\x00") {
   332 						if ($framedata[$i] != "\x00") {
   323 							$thisfile_id3v2['padding']['valid'] = false;
   333 							$thisfile_id3v2['padding']['valid'] = false;
   324 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
   334 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
   325 							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
   335 							$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;
   336 							break;
   327 						}
   337 						}
   425 
   435 
   426 		if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
   436 		if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
   427 			$footer = $this->fread(10);
   437 			$footer = $this->fread(10);
   428 			if (substr($footer, 0, 3) == '3DI') {
   438 			if (substr($footer, 0, 3) == '3DI') {
   429 				$thisfile_id3v2['footer'] = true;
   439 				$thisfile_id3v2['footer'] = true;
   430 				$thisfile_id3v2['majorversion_footer'] = ord($footer{3});
   440 				$thisfile_id3v2['majorversion_footer'] = ord($footer[3]);
   431 				$thisfile_id3v2['minorversion_footer'] = ord($footer{4});
   441 				$thisfile_id3v2['minorversion_footer'] = ord($footer[4]);
   432 			}
   442 			}
   433 			if ($thisfile_id3v2['majorversion_footer'] <= 4) {
   443 			if ($thisfile_id3v2['majorversion_footer'] <= 4) {
   434 				$id3_flags = ord(substr($footer{5}));
   444 				$id3_flags = ord($footer[5]);
   435 				$thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
   445 				$thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
   436 				$thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
   446 				$thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
   437 				$thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
   447 				$thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
   438 				$thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
   448 				$thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
   439 
   449 
   450 			}
   460 			}
   451 			$thisfile_id3v2['comments']['genre'] = array_unique($genres);
   461 			$thisfile_id3v2['comments']['genre'] = array_unique($genres);
   452 			unset($key, $value, $genres, $genre);
   462 			unset($key, $value, $genres, $genre);
   453 		}
   463 		}
   454 
   464 
   455 		if (isset($thisfile_id3v2['comments']['track'])) {
   465 		if (isset($thisfile_id3v2['comments']['track_number'])) {
   456 			foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
   466 			foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) {
   457 				if (strstr($value, '/')) {
   467 				if (strstr($value, '/')) {
   458 					list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
   468 					list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]);
   459 				}
   469 				}
   460 			}
   470 			}
   461 		}
   471 		}
   462 
   472 
   463 		if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
   473 		if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
   496 		}
   506 		}
   497 
   507 
   498 		return true;
   508 		return true;
   499 	}
   509 	}
   500 
   510 
   501 
   511 	/**
       
   512 	 * @param string $genrestring
       
   513 	 *
       
   514 	 * @return array
       
   515 	 */
   502 	public function ParseID3v2GenreString($genrestring) {
   516 	public function ParseID3v2GenreString($genrestring) {
   503 		// Parse genres into arrays of genreName and genreID
   517 		// Parse genres into arrays of genreName and genreID
   504 		// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
   518 		// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
   505 		// ID3v2.4.x: '21' $00 'Eurodisco' $00
   519 		// ID3v2.4.x: '21' $00 'Eurodisco' $00
   506 		$clean_genres = array();
   520 		$clean_genres = array();
   507 
   521 
   508 		// hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
   522 		// 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)) {
   523 		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:
   524 			// 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
   525 			// 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)) {
   526 			if (strpos($genrestring, '/') !== false) {
       
   527 				$LegitimateSlashedGenreList = array(  // https://github.com/JamesHeinrich/getID3/issues/223
       
   528 					'Pop/Funk',    // ID3v1 genre #62 - https://en.wikipedia.org/wiki/ID3#standard
       
   529 					'Cut-up/DJ',   // Discogs - https://www.discogs.com/style/cut-up/dj
       
   530 					'RnB/Swing',   // Discogs - https://www.discogs.com/style/rnb/swing
       
   531 					'Funk / Soul', // Discogs (note spaces) - https://www.discogs.com/genre/funk+%2F+soul
       
   532 				);
   513 				$genrestring = str_replace('/', "\x00", $genrestring);
   533 				$genrestring = str_replace('/', "\x00", $genrestring);
   514 				$genrestring = str_replace('Pop'."\x00".'Funk', 'Pop/Funk', $genrestring);
   534 				foreach ($LegitimateSlashedGenreList as $SlashedGenre) {
   515 				$genrestring = str_replace('Rock'."\x00".'Rock', 'Folk/Rock', $genrestring);
   535 					$genrestring = str_ireplace(str_replace('/', "\x00", $SlashedGenre), $SlashedGenre, $genrestring);
       
   536 				}
   516 			}
   537 			}
   517 
   538 
   518 			// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
   539 			// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
   519 			if (preg_match('#;#', $genrestring)) {
   540 			if (strpos($genrestring, ';') !== false) {
   520 				$genrestring = str_replace(';', "\x00", $genrestring);
   541 				$genrestring = str_replace(';', "\x00", $genrestring);
   521 			}
   542 			}
   522 		}
   543 		}
   523 
   544 
   524 
   545 
   528 
   549 
   529 		$genre_elements = explode("\x00", $genrestring);
   550 		$genre_elements = explode("\x00", $genrestring);
   530 		foreach ($genre_elements as $element) {
   551 		foreach ($genre_elements as $element) {
   531 			$element = trim($element);
   552 			$element = trim($element);
   532 			if ($element) {
   553 			if ($element) {
   533 				if (preg_match('#^[0-9]{1,3}#', $element)) {
   554 				if (preg_match('#^[0-9]{1,3}$#', $element)) {
   534 					$clean_genres[] = getid3_id3v1::LookupGenreName($element);
   555 					$clean_genres[] = getid3_id3v1::LookupGenreName($element);
   535 				} else {
   556 				} else {
   536 					$clean_genres[] = str_replace('((', '(', $element);
   557 					$clean_genres[] = str_replace('((', '(', $element);
   537 				}
   558 				}
   538 			}
   559 			}
   539 		}
   560 		}
   540 		return $clean_genres;
   561 		return $clean_genres;
   541 	}
   562 	}
   542 
   563 
   543 
   564 	/**
       
   565 	 * @param array $parsedFrame
       
   566 	 *
       
   567 	 * @return bool
       
   568 	 */
   544 	public function ParseID3v2Frame(&$parsedFrame) {
   569 	public function ParseID3v2Frame(&$parsedFrame) {
   545 
   570 
   546 		// shortcuts
   571 		// shortcuts
   547 		$info = &$this->getid3->info;
   572 		$info = &$this->getid3->info;
   548 		$id3v2_majorversion = $info['id3v2']['majorversion'];
   573 		$id3v2_majorversion = $info['id3v2']['majorversion'];
   655 			}
   680 			}
   656 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   681 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   657 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   682 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   658 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   683 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   659 			}
   684 			}
   660 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   685 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   661 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
   686 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
   662 				// if description only contains a BOM or terminator then make it blank
       
   663 				$frame_description = '';
       
   664 			}
       
   665 			$parsedFrame['encodingid']  = $frame_textencoding;
   687 			$parsedFrame['encodingid']  = $frame_textencoding;
   666 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
   688 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
   667 
   689 
   668 			$parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description));
   690 			$parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description']));
   669 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
   691 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
       
   692 			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
   670 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   693 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   671 				$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
   694 				$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
   672 				if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
   695 				if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
   673 					$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
   696 					$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
   674 				} else {
   697 				} else {
   676 				}
   699 				}
   677 			}
   700 			}
   678 			//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
   701 			//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
   679 
   702 
   680 
   703 
   681 		} elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
   704 		} elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame
   682 			//   There may only be one text information frame of its kind in an tag.
   705 			//   There may only be one text information frame of its kind in an tag.
   683 			// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
   706 			// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
   684 			// excluding 'TXXX' described in 4.2.6.>
   707 			// excluding 'TXXX' described in 4.2.6.>
   685 			// Text encoding                $xx
   708 			// Text encoding                $xx
   686 			// Information                  <text string(s) according to encoding>
   709 			// Information                  <text string(s) according to encoding>
   690 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   713 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
   691 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   714 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
   692 			}
   715 			}
   693 
   716 
   694 			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
   717 			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
       
   718 			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
   695 
   719 
   696 			$parsedFrame['encodingid'] = $frame_textencoding;
   720 			$parsedFrame['encodingid'] = $frame_textencoding;
   697 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
   721 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
   698 
       
   699 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   722 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
   700 				// ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
   723 				// ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
   701 				// This of course breaks when an artist name contains slash character, e.g. "AC/DC"
   724 				// This of course breaks when an artist name contains slash character, e.g. "AC/DC"
   702 				// MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
   725 				// MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
   703 				// getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
   726 				// getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
   749 			}
   772 			}
   750 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   773 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   751 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   774 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   752 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   775 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   753 			}
   776 			}
   754 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
       
   755 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
       
   756 				// if description only contains a BOM or terminator then make it blank
       
   757 				$frame_description = '';
       
   758 			}
       
   759 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
       
   760 
       
   761 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator);
       
   762 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
       
   763 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
       
   764 			}
       
   765 			if ($frame_terminatorpos) {
       
   766 				// there are null bytes after the data - this is not according to spec
       
   767 				// only use data up to first null byte
       
   768 				$frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
       
   769 			} else {
       
   770 				// no null bytes following data, just use all data
       
   771 				$frame_urldata = (string) $parsedFrame['data'];
       
   772 			}
       
   773 
       
   774 			$parsedFrame['encodingid']  = $frame_textencoding;
   777 			$parsedFrame['encodingid']  = $frame_textencoding;
   775 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
   778 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
   776 
   779 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);           // according to the frame text encoding
   777 			$parsedFrame['url']         = $frame_urldata;
   780 			$parsedFrame['url']         = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1
   778 			$parsedFrame['description'] = $frame_description;
   781 			$parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator);
       
   782 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
       
   783 
   779 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
   784 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
   780 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
   785 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
   781 			}
   786 			}
   782 			unset($parsedFrame['data']);
   787 			unset($parsedFrame['data']);
   783 
   788 
   784 
   789 
   785 		} elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
   790 		} elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames
   786 			//   There may only be one URL link frame of its kind in a tag,
   791 			//   There may only be one URL link frame of its kind in a tag,
   787 			//   except when stated otherwise in the frame description
   792 			//   except when stated otherwise in the frame description
   788 			// <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
   793 			// <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
   789 			// described in 4.3.2.>
   794 			// described in 4.3.2.>
   790 			// URL              <text string>
   795 			// URL              <text string>
   791 
   796 
   792 			$parsedFrame['url'] = trim($parsedFrame['data']);
   797 			$parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1
   793 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
   798 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
   794 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
   799 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
   795 			}
   800 			}
   796 			unset($parsedFrame['data']);
   801 			unset($parsedFrame['data']);
   797 
   802 
   798 
   803 
   799 		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
   804 		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
   811 			}
   816 			}
   812 			$parsedFrame['encodingid'] = $frame_textencoding;
   817 			$parsedFrame['encodingid'] = $frame_textencoding;
   813 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
   818 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
   814 			$parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
   819 			$parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
   815 
   820 
   816 			// http://www.getid3.org/phpBB3/viewtopic.php?t=1369
   821 			// https://www.getid3.org/phpBB3/viewtopic.php?t=1369
   817 			// "this tag typically contains null terminated strings, which are associated in pairs"
   822 			// "this tag typically contains null terminated strings, which are associated in pairs"
   818 			// "there are users that use the tag incorrectly"
   823 			// "there are users that use the tag incorrectly"
   819 			$IPLS_parts = array();
   824 			$IPLS_parts = array();
   820 			if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
   825 			if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
   821 				$IPLS_parts_unsorted = array();
   826 				$IPLS_parts_unsorted = array();
   931 			$parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
   936 			$parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
   932 			$parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
   937 			$parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
   933 			$parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
   938 			$parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
   934 			$parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
   939 			$parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
   935 			$parsedFrame['data'] = substr($parsedFrame['data'], 10);
   940 			$parsedFrame['data'] = substr($parsedFrame['data'], 10);
       
   941 			$deviationbitstream = '';
   936 			while ($frame_offset < strlen($parsedFrame['data'])) {
   942 			while ($frame_offset < strlen($parsedFrame['data'])) {
   937 				$deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
   943 				$deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
   938 			}
   944 			}
   939 			$reference_counter = 0;
   945 			$reference_counter = 0;
   940 			while (strlen($deviationbitstream) > 0) {
   946 			while (strlen($deviationbitstream) > 0) {
   992 			$frame_offset += 3;
   998 			$frame_offset += 3;
   993 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   999 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
   994 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1000 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
   995 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1001 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
   996 			}
  1002 			}
   997 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1003 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
   998 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
  1004 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
   999 				// if description only contains a BOM or terminator then make it blank
       
  1000 				$frame_description = '';
       
  1001 			}
       
  1002 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
  1005 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
       
  1006 			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
  1003 
  1007 
  1004 			$parsedFrame['encodingid']   = $frame_textencoding;
  1008 			$parsedFrame['encodingid']   = $frame_textencoding;
  1005 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
  1009 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
  1006 
  1010 
  1007 			$parsedFrame['language']     = $frame_language;
  1011 			$parsedFrame['language']     = $frame_language;
  1008 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
  1012 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
  1009 			$parsedFrame['description']  = $frame_description;
       
  1010 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
  1013 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
  1011 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
  1014 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
  1012 			}
  1015 			}
  1013 			unset($parsedFrame['data']);
  1016 			unset($parsedFrame['data']);
  1014 
  1017 
  1059 						$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1062 						$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1060 					}
  1063 					}
  1061 					$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
  1064 					$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
  1062 
  1065 
  1063 					$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
  1066 					$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
  1064 					if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
  1067 					if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) {
  1065 						// timestamp probably omitted for first data item
  1068 						// timestamp probably omitted for first data item
  1066 					} else {
  1069 					} else {
  1067 						$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
  1070 						$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
  1068 						$frame_remainingdata = substr($frame_remainingdata, 4);
  1071 						$frame_remainingdata = substr($frame_remainingdata, 4);
  1069 					}
  1072 					}
  1100 				$frame_offset += 3;
  1103 				$frame_offset += 3;
  1101 				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1104 				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1102 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1105 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1103 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1106 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1104 				}
  1107 				}
  1105 				$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1108 				$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1106 				if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
  1109 				$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
  1107 					// if description only contains a BOM or terminator then make it blank
       
  1108 					$frame_description = '';
       
  1109 				}
       
  1110 				$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
  1110 				$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
       
  1111 				$frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator);
  1111 
  1112 
  1112 				$parsedFrame['encodingid']   = $frame_textencoding;
  1113 				$parsedFrame['encodingid']   = $frame_textencoding;
  1113 				$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
  1114 				$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
  1114 
  1115 
  1115 				$parsedFrame['language']     = $frame_language;
  1116 				$parsedFrame['language']     = $frame_language;
  1116 				$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
  1117 				$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
  1117 				$parsedFrame['description']  = $frame_description;
       
  1118 				$parsedFrame['data']         = $frame_text;
  1118 				$parsedFrame['data']         = $frame_text;
  1119 				if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
  1119 				if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
  1120 					$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
  1120 					$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
  1121 					if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
  1121 					if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
  1122 						$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
  1122 						$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
  1405 			} else {
  1405 			} else {
  1406 				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1406 				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1407 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1407 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1408 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1408 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1409 				}
  1409 				}
  1410 				$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1410 				$parsedFrame['description']   = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1411 				if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
  1411 				$parsedFrame['description']   = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
  1412 					// if description only contains a BOM or terminator then make it blank
  1412 				$parsedFrame['encodingid']    = $frame_textencoding;
  1413 					$frame_description = '';
  1413 				$parsedFrame['encoding']      = $this->TextEncodingNameLookup($frame_textencoding);
  1414 				}
       
  1415 				$parsedFrame['encodingid']       = $frame_textencoding;
       
  1416 				$parsedFrame['encoding']         = $this->TextEncodingNameLookup($frame_textencoding);
       
  1417 
  1414 
  1418 				if ($id3v2_majorversion == 2) {
  1415 				if ($id3v2_majorversion == 2) {
  1419 					$parsedFrame['imagetype']    = $frame_imagetype;
  1416 					$parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null;
  1420 				} else {
  1417 				} else {
  1421 					$parsedFrame['mime']         = $frame_mimetype;
  1418 					$parsedFrame['mime']      = isset($frame_mimetype) ? $frame_mimetype : null;
  1422 				}
  1419 				}
  1423 				$parsedFrame['picturetypeid']    = $frame_picturetype;
  1420 				$parsedFrame['picturetypeid'] = $frame_picturetype;
  1424 				$parsedFrame['picturetype']      = $this->APICPictureTypeLookup($frame_picturetype);
  1421 				$parsedFrame['picturetype']   = $this->APICPictureTypeLookup($frame_picturetype);
  1425 				$parsedFrame['description']      = $frame_description;
  1422 				$parsedFrame['data']          = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
  1426 				$parsedFrame['data']             = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
  1423 				$parsedFrame['datalength']    = strlen($parsedFrame['data']);
  1427 				$parsedFrame['datalength']       = strlen($parsedFrame['data']);
  1424 
  1428 
  1425 				$parsedFrame['image_mime']    = '';
  1429 				$parsedFrame['image_mime'] = '';
       
  1430 				$imageinfo = array();
  1426 				$imageinfo = array();
  1431 				if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
  1427 				if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
  1432 					if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
  1428 					if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
  1433 						$parsedFrame['image_mime']       = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
  1429 						$parsedFrame['image_mime']       = image_type_to_mime_type($imagechunkcheck[2]);
  1434 						if ($imagechunkcheck[0]) {
  1430 						if ($imagechunkcheck[0]) {
  1435 							$parsedFrame['image_width']  = $imagechunkcheck[0];
  1431 							$parsedFrame['image_width']  = $imagechunkcheck[0];
  1436 						}
  1432 						}
  1437 						if ($imagechunkcheck[1]) {
  1433 						if ($imagechunkcheck[1]) {
  1438 							$parsedFrame['image_height'] = $imagechunkcheck[1];
  1434 							$parsedFrame['image_height'] = $imagechunkcheck[1];
  1444 					if ($this->getid3->option_save_attachments === false) {
  1440 					if ($this->getid3->option_save_attachments === false) {
  1445 						// skip entirely
  1441 						// skip entirely
  1446 						unset($parsedFrame['data']);
  1442 						unset($parsedFrame['data']);
  1447 						break;
  1443 						break;
  1448 					}
  1444 					}
       
  1445 					$dir = '';
  1449 					if ($this->getid3->option_save_attachments === true) {
  1446 					if ($this->getid3->option_save_attachments === true) {
  1450 						// great
  1447 						// great
  1451 /*
  1448 /*
  1452 					} elseif (is_int($this->getid3->option_save_attachments)) {
  1449 					} elseif (is_int($this->getid3->option_save_attachments)) {
  1453 						if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
  1450 						if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
  1531 
  1528 
  1532 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1529 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1533 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1530 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1534 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1531 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1535 			}
  1532 			}
  1536 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1533 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1537 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
  1534 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
  1538 				// if description only contains a BOM or terminator then make it blank
       
  1539 				$frame_description = '';
       
  1540 			}
       
  1541 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
  1535 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
  1542 
  1536 
  1543 			$parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
  1537 			$parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
  1544 			$parsedFrame['encodingid']  = $frame_textencoding;
  1538 			$parsedFrame['encodingid']  = $frame_textencoding;
  1545 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
  1539 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
  1546 
  1540 
  1547 			$parsedFrame['mime']        = $frame_mimetype;
  1541 			$parsedFrame['mime']        = $frame_mimetype;
  1548 			$parsedFrame['filename']    = $frame_filename;
  1542 			$parsedFrame['filename']    = $frame_filename;
  1549 			$parsedFrame['description'] = $frame_description;
       
  1550 			unset($parsedFrame['data']);
  1543 			unset($parsedFrame['data']);
  1551 
  1544 
  1552 
  1545 
  1553 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
  1546 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
  1554 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
  1547 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
  1614 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1607 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1615 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1608 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1616 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1609 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1617 
  1610 
  1618 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1611 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1619 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1612 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1620 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
  1613 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
  1621 				// if description only contains a BOM or terminator then make it blank
       
  1622 				$frame_description = '';
       
  1623 			}
       
  1624 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1614 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1625 
  1615 
  1626 			$parsedFrame['ownerid']     = $frame_ownerid;
  1616 			$parsedFrame['ownerid']     = $frame_ownerid;
  1627 			$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
  1617 			$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
  1628 			$parsedFrame['description'] = $frame_description;
       
  1629 			unset($parsedFrame['data']);
  1618 			unset($parsedFrame['data']);
  1630 
  1619 
  1631 
  1620 
  1632 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
  1621 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
  1633 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
  1622 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
  1719 			$parsedFrame['language']     = $frame_language;
  1708 			$parsedFrame['language']     = $frame_language;
  1720 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
  1709 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
  1721 			$parsedFrame['encodingid']   = $frame_textencoding;
  1710 			$parsedFrame['encodingid']   = $frame_textencoding;
  1722 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
  1711 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
  1723 
  1712 
  1724 			$parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
  1713 			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
       
  1714 			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
  1725 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
  1715 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
  1726 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
  1716 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
  1727 			}
  1717 			}
  1728 			unset($parsedFrame['data']);
  1718 			unset($parsedFrame['data']);
  1729 
  1719 
  1757 				$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
  1747 				$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
  1758 			}
  1748 			}
  1759 			$frame_offset += 8;
  1749 			$frame_offset += 8;
  1760 
  1750 
  1761 			$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
  1751 			$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
       
  1752 			$parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding));
  1762 			unset($parsedFrame['data']);
  1753 			unset($parsedFrame['data']);
  1763 
  1754 
  1764 
  1755 
  1765 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
  1756 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
  1766 			//   There may be more than one 'commercial frame' in a tag,
  1757 			//   There may be more than one 'commercial frame' in a tag,
  1815 
  1806 
  1816 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1807 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
  1817 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1808 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
  1818 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1809 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  1819 			}
  1810 			}
  1820 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1811 			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1821 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
  1812 			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
  1822 				// if description only contains a BOM or terminator then make it blank
       
  1823 				$frame_description = '';
       
  1824 			}
       
  1825 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
  1813 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
  1826 
  1814 
  1827 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1815 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
  1828 			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1816 			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  1829 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1817 			$frame_offset = $frame_terminatorpos + strlen("\x00");
  1836 			$parsedFrame['pricevaliduntil']   = $frame_datestring;
  1824 			$parsedFrame['pricevaliduntil']   = $frame_datestring;
  1837 			$parsedFrame['contacturl']        = $frame_contacturl;
  1825 			$parsedFrame['contacturl']        = $frame_contacturl;
  1838 			$parsedFrame['receivedasid']      = $frame_receivedasid;
  1826 			$parsedFrame['receivedasid']      = $frame_receivedasid;
  1839 			$parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
  1827 			$parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
  1840 			$parsedFrame['sellername']        = $frame_sellername;
  1828 			$parsedFrame['sellername']        = $frame_sellername;
  1841 			$parsedFrame['description']       = $frame_description;
       
  1842 			$parsedFrame['mime']              = $frame_mimetype;
  1829 			$parsedFrame['mime']              = $frame_mimetype;
  1843 			$parsedFrame['logo']              = $frame_sellerlogo;
  1830 			$parsedFrame['logo']              = $frame_sellerlogo;
  1844 			unset($parsedFrame['data']);
  1831 			unset($parsedFrame['data']);
  1845 
  1832 
  1846 
  1833 
  2000 			// http://id3.org/id3v2-chapters-1.0
  1987 			// http://id3.org/id3v2-chapters-1.0
  2001 			// <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
  1988 			// <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
  2002 			// Element ID      <text string> $00
  1989 			// Element ID      <text string> $00
  2003 			// Start time      $xx xx xx xx
  1990 			// Start time      $xx xx xx xx
  2004 			// End time        $xx xx xx xx
  1991 			// End time        $xx xx xx xx
  2005             // Start offset    $xx xx xx xx
  1992 			// Start offset    $xx xx xx xx
  2006             // End offset      $xx xx xx xx
  1993 			// End offset      $xx xx xx xx
  2007             // <Optional embedded sub-frames>
  1994 			// <Optional embedded sub-frames>
  2008 
  1995 
  2009 			$frame_offset = 0;
  1996 			$frame_offset = 0;
  2010 			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
  1997 			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
  2011 			$frame_offset += strlen($parsedFrame['element_id']."\x00");
  1998 			$frame_offset += strlen($parsedFrame['element_id']."\x00");
  2012 			$parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
  1999 			$parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
  2035 					$frame_offset += 4;
  2022 					$frame_offset += 4;
  2036 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
  2023 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
  2037 					$frame_offset += 2;
  2024 					$frame_offset += 2;
  2038 					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
  2025 					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)');
  2026 						$this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
       
  2027 						break;
       
  2028 					}
       
  2029 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
       
  2030 					$frame_offset += $subframe['size'];
       
  2031 
       
  2032 					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
       
  2033 					$subframe['text']       =     substr($subframe_rawdata, 1);
       
  2034 					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
       
  2035 					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));
       
  2036 					switch (substr($encoding_converted_text, 0, 2)) {
       
  2037 						case "\xFF\xFE":
       
  2038 						case "\xFE\xFF":
       
  2039 							switch (strtoupper($info['id3v2']['encoding'])) {
       
  2040 								case 'ISO-8859-1':
       
  2041 								case 'UTF-8':
       
  2042 									$encoding_converted_text = substr($encoding_converted_text, 2);
       
  2043 									// remove unwanted byte-order-marks
       
  2044 									break;
       
  2045 								default:
       
  2046 									// ignore
       
  2047 									break;
       
  2048 							}
       
  2049 							break;
       
  2050 						default:
       
  2051 							// do not remove BOM
       
  2052 							break;
       
  2053 					}
       
  2054 
       
  2055 					switch ($subframe['name']) {
       
  2056 						case 'TIT2':
       
  2057 							$parsedFrame['chapter_name']        = $encoding_converted_text;
       
  2058 							$parsedFrame['subframes'][] = $subframe;
       
  2059 							break;
       
  2060 						case 'TIT3':
       
  2061 							$parsedFrame['chapter_description'] = $encoding_converted_text;
       
  2062 							$parsedFrame['subframes'][] = $subframe;
       
  2063 							break;
       
  2064 						case 'WXXX':
       
  2065 							list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
       
  2066 							$parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
       
  2067 							$parsedFrame['subframes'][] = $subframe;
       
  2068 							break;
       
  2069 						case 'APIC':
       
  2070 							if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) {
       
  2071 								list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches;
       
  2072 								$subframe['image_mime']   = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime));
       
  2073 								$subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype);
       
  2074 								$subframe['description']  = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description));
       
  2075 								if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) {
       
  2076 									// the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16)
       
  2077 									// the above regex assumes one byte, if it's actually two then strip the second one here
       
  2078 									$subframe_apic_picturedata = substr($subframe_apic_picturedata, 1);
       
  2079 								}
       
  2080 								$subframe['data'] = $subframe_apic_picturedata;
       
  2081 								unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata);
       
  2082 								unset($subframe['text'], $parsedFrame['text']);
       
  2083 								$parsedFrame['subframes'][] = $subframe;
       
  2084 								$parsedFrame['picture_present'] = true;
       
  2085 							} else {
       
  2086 								$this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format');
       
  2087 							}
       
  2088 							break;
       
  2089 						default:
       
  2090 							$this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)');
       
  2091 							break;
       
  2092 					}
       
  2093 				}
       
  2094 				unset($subframe_rawdata, $subframe, $encoding_converted_text);
       
  2095 				unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC
       
  2096 			}
       
  2097 
       
  2098 			$id3v2_chapter_entry = array();
       
  2099 			foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) {
       
  2100 				if (isset($parsedFrame[$id3v2_chapter_key])) {
       
  2101 					$id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
       
  2102 				}
       
  2103 			}
       
  2104 			if (!isset($info['id3v2']['chapters'])) {
       
  2105 				$info['id3v2']['chapters'] = array();
       
  2106 			}
       
  2107 			$info['id3v2']['chapters'][] = $id3v2_chapter_entry;
       
  2108 			unset($id3v2_chapter_entry, $id3v2_chapter_key);
       
  2109 
       
  2110 
       
  2111 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
       
  2112 			// http://id3.org/id3v2-chapters-1.0
       
  2113 			// <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC">           (10 bytes)
       
  2114 			// Element ID      <text string> $00
       
  2115 			// CTOC flags        %xx
       
  2116 			// Entry count       $xx
       
  2117 			// Child Element ID  <string>$00   /* zero or more child CHAP or CTOC entries */
       
  2118 			// <Optional embedded sub-frames>
       
  2119 
       
  2120 			$frame_offset = 0;
       
  2121 			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
       
  2122 			$frame_offset += strlen($parsedFrame['element_id']."\x00");
       
  2123 			$ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
       
  2124 			$frame_offset += 1;
       
  2125 			$parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
       
  2126 			$frame_offset += 1;
       
  2127 
       
  2128 			$terminator_position = null;
       
  2129 			for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
       
  2130 				$terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
       
  2131 				$parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
       
  2132 				$frame_offset = $terminator_position + 1;
       
  2133 			}
       
  2134 
       
  2135 			$parsedFrame['ctoc_flags']['ordered']   = (bool) ($ctoc_flags_raw & 0x01);
       
  2136 			$parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
       
  2137 
       
  2138 			unset($ctoc_flags_raw, $terminator_position);
       
  2139 
       
  2140 			if ($frame_offset < strlen($parsedFrame['data'])) {
       
  2141 				$parsedFrame['subframes'] = array();
       
  2142 				while ($frame_offset < strlen($parsedFrame['data'])) {
       
  2143 					// <Optional embedded sub-frames>
       
  2144 					$subframe = array();
       
  2145 					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
       
  2146 					$frame_offset += 4;
       
  2147 					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
       
  2148 					$frame_offset += 4;
       
  2149 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
       
  2150 					$frame_offset += 2;
       
  2151 					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
       
  2152 						$this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
  2040 						break;
  2153 						break;
  2041 					}
  2154 					}
  2042 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
  2155 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
  2043 					$frame_offset += $subframe['size'];
  2156 					$frame_offset += $subframe['size'];
  2044 
  2157 
  2065 							break;
  2178 							break;
  2066 					}
  2179 					}
  2067 
  2180 
  2068 					if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
  2181 					if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
  2069 						if ($subframe['name'] == 'TIT2') {
  2182 						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;
  2183 							$parsedFrame['toc_name']        = $encoding_converted_text;
  2168 						} elseif ($subframe['name'] == 'TIT3') {
  2184 						} elseif ($subframe['name'] == 'TIT3') {
  2169 							$parsedFrame['toc_description'] = $encoding_converted_text;
  2185 							$parsedFrame['toc_description'] = $encoding_converted_text;
  2170 						}
  2186 						}
  2171 						$parsedFrame['subframes'][] = $subframe;
  2187 						$parsedFrame['subframes'][] = $subframe;
  2179 		}
  2195 		}
  2180 
  2196 
  2181 		return true;
  2197 		return true;
  2182 	}
  2198 	}
  2183 
  2199 
  2184 
  2200 	/**
       
  2201 	 * @param string $data
       
  2202 	 *
       
  2203 	 * @return string
       
  2204 	 */
  2185 	public function DeUnsynchronise($data) {
  2205 	public function DeUnsynchronise($data) {
  2186 		return str_replace("\xFF\x00", "\xFF", $data);
  2206 		return str_replace("\xFF\x00", "\xFF", $data);
  2187 	}
  2207 	}
  2188 
  2208 
       
  2209 	/**
       
  2210 	 * @param int $index
       
  2211 	 *
       
  2212 	 * @return string
       
  2213 	 */
  2189 	public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
  2214 	public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
  2190 		static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
  2215 		static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
  2191 			0x00 => 'No more than 128 frames and 1 MB total tag size',
  2216 			0x00 => 'No more than 128 frames and 1 MB total tag size',
  2192 			0x01 => 'No more than 64 frames and 128 KB total tag size',
  2217 			0x01 => 'No more than 64 frames and 128 KB total tag size',
  2193 			0x02 => 'No more than 32 frames and 40 KB total tag size',
  2218 			0x02 => 'No more than 32 frames and 40 KB total tag size',
  2194 			0x03 => 'No more than 32 frames and 4 KB total tag size',
  2219 			0x03 => 'No more than 32 frames and 4 KB total tag size',
  2195 		);
  2220 		);
  2196 		return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
  2221 		return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
  2197 	}
  2222 	}
  2198 
  2223 
       
  2224 	/**
       
  2225 	 * @param int $index
       
  2226 	 *
       
  2227 	 * @return string
       
  2228 	 */
  2199 	public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
  2229 	public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
  2200 		static $LookupExtendedHeaderRestrictionsTextEncodings = array(
  2230 		static $LookupExtendedHeaderRestrictionsTextEncodings = array(
  2201 			0x00 => 'No restrictions',
  2231 			0x00 => 'No restrictions',
  2202 			0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
  2232 			0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
  2203 		);
  2233 		);
  2204 		return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
  2234 		return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
  2205 	}
  2235 	}
  2206 
  2236 
       
  2237 	/**
       
  2238 	 * @param int $index
       
  2239 	 *
       
  2240 	 * @return string
       
  2241 	 */
  2207 	public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
  2242 	public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
  2208 		static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
  2243 		static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
  2209 			0x00 => 'No restrictions',
  2244 			0x00 => 'No restrictions',
  2210 			0x01 => 'No string is longer than 1024 characters',
  2245 			0x01 => 'No string is longer than 1024 characters',
  2211 			0x02 => 'No string is longer than 128 characters',
  2246 			0x02 => 'No string is longer than 128 characters',
  2212 			0x03 => 'No string is longer than 30 characters',
  2247 			0x03 => 'No string is longer than 30 characters',
  2213 		);
  2248 		);
  2214 		return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
  2249 		return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
  2215 	}
  2250 	}
  2216 
  2251 
       
  2252 	/**
       
  2253 	 * @param int $index
       
  2254 	 *
       
  2255 	 * @return string
       
  2256 	 */
  2217 	public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
  2257 	public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
  2218 		static $LookupExtendedHeaderRestrictionsImageEncoding = array(
  2258 		static $LookupExtendedHeaderRestrictionsImageEncoding = array(
  2219 			0x00 => 'No restrictions',
  2259 			0x00 => 'No restrictions',
  2220 			0x01 => 'Images are encoded only with PNG or JPEG',
  2260 			0x01 => 'Images are encoded only with PNG or JPEG',
  2221 		);
  2261 		);
  2222 		return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
  2262 		return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
  2223 	}
  2263 	}
  2224 
  2264 
       
  2265 	/**
       
  2266 	 * @param int $index
       
  2267 	 *
       
  2268 	 * @return string
       
  2269 	 */
  2225 	public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
  2270 	public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
  2226 		static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
  2271 		static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
  2227 			0x00 => 'No restrictions',
  2272 			0x00 => 'No restrictions',
  2228 			0x01 => 'All images are 256x256 pixels or smaller',
  2273 			0x01 => 'All images are 256x256 pixels or smaller',
  2229 			0x02 => 'All images are 64x64 pixels or smaller',
  2274 			0x02 => 'All images are 64x64 pixels or smaller',
  2230 			0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
  2275 			0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
  2231 		);
  2276 		);
  2232 		return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
  2277 		return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
  2233 	}
  2278 	}
  2234 
  2279 
       
  2280 	/**
       
  2281 	 * @param string $currencyid
       
  2282 	 *
       
  2283 	 * @return string
       
  2284 	 */
  2235 	public function LookupCurrencyUnits($currencyid) {
  2285 	public function LookupCurrencyUnits($currencyid) {
  2236 
  2286 
  2237 		$begin = __LINE__;
  2287 		$begin = __LINE__;
  2238 
  2288 
  2239 		/** This is not a comment!
  2289 		/** This is not a comment!
  2426 		*/
  2476 		*/
  2427 
  2477 
  2428 		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
  2478 		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
  2429 	}
  2479 	}
  2430 
  2480 
  2431 
  2481 	/**
       
  2482 	 * @param string $currencyid
       
  2483 	 *
       
  2484 	 * @return string
       
  2485 	 */
  2432 	public function LookupCurrencyCountry($currencyid) {
  2486 	public function LookupCurrencyCountry($currencyid) {
  2433 
  2487 
  2434 		$begin = __LINE__;
  2488 		$begin = __LINE__;
  2435 
  2489 
  2436 		/** This is not a comment!
  2490 		/** This is not a comment!
  2622 		*/
  2676 		*/
  2623 
  2677 
  2624 		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
  2678 		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
  2625 	}
  2679 	}
  2626 
  2680 
  2627 
  2681 	/**
  2628 
  2682 	 * @param string $languagecode
       
  2683 	 * @param bool   $casesensitive
       
  2684 	 *
       
  2685 	 * @return string
       
  2686 	 */
  2629 	public static function LanguageLookup($languagecode, $casesensitive=false) {
  2687 	public static function LanguageLookup($languagecode, $casesensitive=false) {
  2630 
  2688 
  2631 		if (!$casesensitive) {
  2689 		if (!$casesensitive) {
  2632 			$languagecode = strtolower($languagecode);
  2690 			$languagecode = strtolower($languagecode);
  2633 		}
  2691 		}
  3079 		*/
  3137 		*/
  3080 
  3138 
  3081 		return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
  3139 		return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
  3082 	}
  3140 	}
  3083 
  3141 
  3084 
  3142 	/**
       
  3143 	 * @param int $index
       
  3144 	 *
       
  3145 	 * @return string
       
  3146 	 */
  3085 	public static function ETCOEventLookup($index) {
  3147 	public static function ETCOEventLookup($index) {
  3086 		if (($index >= 0x17) && ($index <= 0xDF)) {
  3148 		if (($index >= 0x17) && ($index <= 0xDF)) {
  3087 			return 'reserved for future use';
  3149 			return 'reserved for future use';
  3088 		}
  3150 		}
  3089 		if (($index >= 0xE0) && ($index <= 0xEF)) {
  3151 		if (($index >= 0xE0) && ($index <= 0xEF)) {
  3123 		);
  3185 		);
  3124 
  3186 
  3125 		return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
  3187 		return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
  3126 	}
  3188 	}
  3127 
  3189 
       
  3190 	/**
       
  3191 	 * @param int $index
       
  3192 	 *
       
  3193 	 * @return string
       
  3194 	 */
  3128 	public static function SYTLContentTypeLookup($index) {
  3195 	public static function SYTLContentTypeLookup($index) {
  3129 		static $SYTLContentTypeLookup = array(
  3196 		static $SYTLContentTypeLookup = array(
  3130 			0x00 => 'other',
  3197 			0x00 => 'other',
  3131 			0x01 => 'lyrics',
  3198 			0x01 => 'lyrics',
  3132 			0x02 => 'text transcription',
  3199 			0x02 => 'text transcription',
  3139 		);
  3206 		);
  3140 
  3207 
  3141 		return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
  3208 		return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
  3142 	}
  3209 	}
  3143 
  3210 
       
  3211 	/**
       
  3212 	 * @param int   $index
       
  3213 	 * @param bool $returnarray
       
  3214 	 *
       
  3215 	 * @return array|string
       
  3216 	 */
  3144 	public static function APICPictureTypeLookup($index, $returnarray=false) {
  3217 	public static function APICPictureTypeLookup($index, $returnarray=false) {
  3145 		static $APICPictureTypeLookup = array(
  3218 		static $APICPictureTypeLookup = array(
  3146 			0x00 => 'Other',
  3219 			0x00 => 'Other',
  3147 			0x01 => '32x32 pixels \'file icon\' (PNG only)',
  3220 			0x01 => '32x32 pixels \'file icon\' (PNG only)',
  3148 			0x02 => 'Other file icon',
  3221 			0x02 => 'Other file icon',
  3169 			return $APICPictureTypeLookup;
  3242 			return $APICPictureTypeLookup;
  3170 		}
  3243 		}
  3171 		return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
  3244 		return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
  3172 	}
  3245 	}
  3173 
  3246 
       
  3247 	/**
       
  3248 	 * @param int $index
       
  3249 	 *
       
  3250 	 * @return string
       
  3251 	 */
  3174 	public static function COMRReceivedAsLookup($index) {
  3252 	public static function COMRReceivedAsLookup($index) {
  3175 		static $COMRReceivedAsLookup = array(
  3253 		static $COMRReceivedAsLookup = array(
  3176 			0x00 => 'Other',
  3254 			0x00 => 'Other',
  3177 			0x01 => 'Standard CD album with other songs',
  3255 			0x01 => 'Standard CD album with other songs',
  3178 			0x02 => 'Compressed audio on CD',
  3256 			0x02 => 'Compressed audio on CD',
  3185 		);
  3263 		);
  3186 
  3264 
  3187 		return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
  3265 		return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
  3188 	}
  3266 	}
  3189 
  3267 
       
  3268 	/**
       
  3269 	 * @param int $index
       
  3270 	 *
       
  3271 	 * @return string
       
  3272 	 */
  3190 	public static function RVA2ChannelTypeLookup($index) {
  3273 	public static function RVA2ChannelTypeLookup($index) {
  3191 		static $RVA2ChannelTypeLookup = array(
  3274 		static $RVA2ChannelTypeLookup = array(
  3192 			0x00 => 'Other',
  3275 			0x00 => 'Other',
  3193 			0x01 => 'Master volume',
  3276 			0x01 => 'Master volume',
  3194 			0x02 => 'Front right',
  3277 			0x02 => 'Front right',
  3201 		);
  3284 		);
  3202 
  3285 
  3203 		return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
  3286 		return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
  3204 	}
  3287 	}
  3205 
  3288 
       
  3289 	/**
       
  3290 	 * @param string $framename
       
  3291 	 *
       
  3292 	 * @return string
       
  3293 	 */
  3206 	public static function FrameNameLongLookup($framename) {
  3294 	public static function FrameNameLongLookup($framename) {
  3207 
  3295 
  3208 		$begin = __LINE__;
  3296 		$begin = __LINE__;
  3209 
  3297 
  3210 		/** This is not a comment!
  3298 		/** This is not a comment!
  3352 			TXXX	User defined text information frame
  3440 			TXXX	User defined text information frame
  3353 			TYE	Year
  3441 			TYE	Year
  3354 			TYER	Year
  3442 			TYER	Year
  3355 			UFI	Unique file identifier
  3443 			UFI	Unique file identifier
  3356 			UFID	Unique file identifier
  3444 			UFID	Unique file identifier
  3357 			ULT	Unsychronised lyric/text transcription
  3445 			ULT	Unsynchronised lyric/text transcription
  3358 			USER	Terms of use
  3446 			USER	Terms of use
  3359 			USLT	Unsynchronised lyric/text transcription
  3447 			USLT	Unsynchronised lyric/text transcription
  3360 			WAF	Official audio file webpage
  3448 			WAF	Official audio file webpage
  3361 			WAR	Official artist/performer webpage
  3449 			WAR	Official artist/performer webpage
  3362 			WAS	Official audio source webpage
  3450 			WAS	Official audio source webpage
  3384 		// Last three:
  3472 		// Last three:
  3385 		// from Helium2 [www.helium2.com]
  3473 		// from Helium2 [www.helium2.com]
  3386 		// from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
  3474 		// from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
  3387 	}
  3475 	}
  3388 
  3476 
  3389 
  3477 	/**
       
  3478 	 * @param string $framename
       
  3479 	 *
       
  3480 	 * @return string
       
  3481 	 */
  3390 	public static function FrameNameShortLookup($framename) {
  3482 	public static function FrameNameShortLookup($framename) {
  3391 
  3483 
  3392 		$begin = __LINE__;
  3484 		$begin = __LINE__;
  3393 
  3485 
  3394 		/** This is not a comment!
  3486 		/** This is not a comment!
  3536 			TXXX	text
  3628 			TXXX	text
  3537 			TYE	year
  3629 			TYE	year
  3538 			TYER	year
  3630 			TYER	year
  3539 			UFI	unique_file_identifier
  3631 			UFI	unique_file_identifier
  3540 			UFID	unique_file_identifier
  3632 			UFID	unique_file_identifier
  3541 			ULT	unsychronised_lyric
  3633 			ULT	unsynchronised_lyric
  3542 			USER	terms_of_use
  3634 			USER	terms_of_use
  3543 			USLT	unsynchronised_lyric
  3635 			USLT	unsynchronised_lyric
  3544 			WAF	url_file
  3636 			WAF	url_file
  3545 			WAR	url_artist
  3637 			WAR	url_artist
  3546 			WAS	url_source
  3638 			WAS	url_source
  3564 		*/
  3656 		*/
  3565 
  3657 
  3566 		return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
  3658 		return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
  3567 	}
  3659 	}
  3568 
  3660 
       
  3661 	/**
       
  3662 	 * @param string $encoding
       
  3663 	 *
       
  3664 	 * @return string
       
  3665 	 */
  3569 	public static function TextEncodingTerminatorLookup($encoding) {
  3666 	public static function TextEncodingTerminatorLookup($encoding) {
  3570 		// http://www.id3.org/id3v2.4.0-structure.txt
  3667 		// http://www.id3.org/id3v2.4.0-structure.txt
  3571 		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
  3668 		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
  3572 		static $TextEncodingTerminatorLookup = array(
  3669 		static $TextEncodingTerminatorLookup = array(
  3573 			0   => "\x00",     // $00  ISO-8859-1. Terminated with $00.
  3670 			0   => "\x00",     // $00  ISO-8859-1. Terminated with $00.
  3577 			255 => "\x00\x00"
  3674 			255 => "\x00\x00"
  3578 		);
  3675 		);
  3579 		return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
  3676 		return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
  3580 	}
  3677 	}
  3581 
  3678 
       
  3679 	/**
       
  3680 	 * @param int $encoding
       
  3681 	 *
       
  3682 	 * @return string
       
  3683 	 */
  3582 	public static function TextEncodingNameLookup($encoding) {
  3684 	public static function TextEncodingNameLookup($encoding) {
  3583 		// http://www.id3.org/id3v2.4.0-structure.txt
  3685 		// http://www.id3.org/id3v2.4.0-structure.txt
  3584 		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
  3686 		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
  3585 		static $TextEncodingNameLookup = array(
  3687 		static $TextEncodingNameLookup = array(
  3586 			0   => 'ISO-8859-1', // $00  ISO-8859-1. Terminated with $00.
  3688 			0   => 'ISO-8859-1', // $00  ISO-8859-1. Terminated with $00.
  3590 			255 => 'UTF-16BE'
  3692 			255 => 'UTF-16BE'
  3591 		);
  3693 		);
  3592 		return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
  3694 		return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
  3593 	}
  3695 	}
  3594 
  3696 
       
  3697 	/**
       
  3698 	 * @param string $string
       
  3699 	 * @param string $terminator
       
  3700 	 *
       
  3701 	 * @return string
       
  3702 	 */
       
  3703 	public static function RemoveStringTerminator($string, $terminator) {
       
  3704 		// Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present.
       
  3705 		// https://github.com/JamesHeinrich/getID3/issues/121
       
  3706 		// https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227
       
  3707 		if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) {
       
  3708 			$string = substr($string, 0, -strlen($terminator));
       
  3709 		}
       
  3710 		return $string;
       
  3711 	}
       
  3712 
       
  3713 	/**
       
  3714 	 * @param string $string
       
  3715 	 *
       
  3716 	 * @return string
       
  3717 	 */
       
  3718 	public static function MakeUTF16emptyStringEmpty($string) {
       
  3719 		if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
       
  3720 			// if string only contains a BOM or terminator then make it actually an empty string
       
  3721 			$string = '';
       
  3722 		}
       
  3723 		return $string;
       
  3724 	}
       
  3725 
       
  3726 	/**
       
  3727 	 * @param string $framename
       
  3728 	 * @param int    $id3v2majorversion
       
  3729 	 *
       
  3730 	 * @return bool|int
       
  3731 	 */
  3595 	public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
  3732 	public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
  3596 		switch ($id3v2majorversion) {
  3733 		switch ($id3v2majorversion) {
  3597 			case 2:
  3734 			case 2:
  3598 				return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
  3735 				return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
  3599 				break;
       
  3600 
  3736 
  3601 			case 3:
  3737 			case 3:
  3602 			case 4:
  3738 			case 4:
  3603 				return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
  3739 				return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
  3604 				break;
       
  3605 		}
  3740 		}
  3606 		return false;
  3741 		return false;
  3607 	}
  3742 	}
  3608 
  3743 
       
  3744 	/**
       
  3745 	 * @param string $numberstring
       
  3746 	 * @param bool   $allowdecimal
       
  3747 	 * @param bool   $allownegative
       
  3748 	 *
       
  3749 	 * @return bool
       
  3750 	 */
  3609 	public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
  3751 	public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
  3610 		for ($i = 0; $i < strlen($numberstring); $i++) {
  3752 		for ($i = 0; $i < strlen($numberstring); $i++) {
  3611 			if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) {
  3753 			if ((chr($numberstring[$i]) < chr('0')) || (chr($numberstring[$i]) > chr('9'))) {
  3612 				if (($numberstring{$i} == '.') && $allowdecimal) {
  3754 				if (($numberstring[$i] == '.') && $allowdecimal) {
  3613 					// allowed
  3755 					// allowed
  3614 				} elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) {
  3756 				} elseif (($numberstring[$i] == '-') && $allownegative && ($i == 0)) {
  3615 					// allowed
  3757 					// allowed
  3616 				} else {
  3758 				} else {
  3617 					return false;
  3759 					return false;
  3618 				}
  3760 				}
  3619 			}
  3761 			}
  3620 		}
  3762 		}
  3621 		return true;
  3763 		return true;
  3622 	}
  3764 	}
  3623 
  3765 
       
  3766 	/**
       
  3767 	 * @param string $datestamp
       
  3768 	 *
       
  3769 	 * @return bool
       
  3770 	 */
  3624 	public static function IsValidDateStampString($datestamp) {
  3771 	public static function IsValidDateStampString($datestamp) {
  3625 		if (strlen($datestamp) != 8) {
  3772 		if (strlen($datestamp) != 8) {
  3626 			return false;
  3773 			return false;
  3627 		}
  3774 		}
  3628 		if (!self::IsANumber($datestamp, false)) {
  3775 		if (!self::IsANumber($datestamp, false)) {
  3647 			return false;
  3794 			return false;
  3648 		}
  3795 		}
  3649 		return true;
  3796 		return true;
  3650 	}
  3797 	}
  3651 
  3798 
       
  3799 	/**
       
  3800 	 * @param int $majorversion
       
  3801 	 *
       
  3802 	 * @return int
       
  3803 	 */
  3652 	public static function ID3v2HeaderLength($majorversion) {
  3804 	public static function ID3v2HeaderLength($majorversion) {
  3653 		return (($majorversion == 2) ? 6 : 10);
  3805 		return (($majorversion == 2) ? 6 : 10);
  3654 	}
  3806 	}
  3655 
  3807 
       
  3808 	/**
       
  3809 	 * @param string $frame_name
       
  3810 	 *
       
  3811 	 * @return string|false
       
  3812 	 */
  3656 	public static function ID3v22iTunesBrokenFrameName($frame_name) {
  3813 	public static function ID3v22iTunesBrokenFrameName($frame_name) {
  3657 		// iTunes (multiple versions) has been known to write ID3v2.3 style frames
  3814 		// 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]
  3815 		// 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.
  3816 		// 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.
  3817 		// This function will detect and translate the corrupt frame name into ID3v2.3 standard.
  3737 		}
  3894 		}
  3738 		return false;
  3895 		return false;
  3739 	}
  3896 	}
  3740 
  3897 
  3741 }
  3898 }
       
  3899