wp/wp-includes/ID3/module.tag.id3v1.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.id3v1.php                                        //
    11 // module.tag.id3v1.php                                        //
    12 // module for analyzing ID3v1 tags                             //
    12 // module for analyzing ID3v1 tags                             //
    13 // dependencies: NONE                                          //
    13 // dependencies: NONE                                          //
    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 
    20 
    18 class getid3_id3v1 extends getid3_handler
    21 class getid3_id3v1 extends getid3_handler
    19 {
    22 {
    20 
    23 	/**
       
    24 	 * @return bool
       
    25 	 */
    21 	public function Analyze() {
    26 	public function Analyze() {
    22 		$info = &$this->getid3->info;
    27 		$info = &$this->getid3->info;
    23 
    28 
    24 		if (!getid3_lib::intValueSupported($info['filesize'])) {
    29 		if (!getid3_lib::intValueSupported($info['filesize'])) {
    25 			$this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
    30 			$this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
    41 			$ParsedID3v1['comment'] =                 substr($id3v1tag,  97, 30);  // can't remove nulls yet, track detection depends on them
    46 			$ParsedID3v1['comment'] =                 substr($id3v1tag,  97, 30);  // can't remove nulls yet, track detection depends on them
    42 			$ParsedID3v1['genreid'] =             ord(substr($id3v1tag, 127,  1));
    47 			$ParsedID3v1['genreid'] =             ord(substr($id3v1tag, 127,  1));
    43 
    48 
    44 			// If second-last byte of comment field is null and last byte of comment field is non-null
    49 			// If second-last byte of comment field is null and last byte of comment field is non-null
    45 			// then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
    50 			// then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
    46 			if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
    51 			if (($id3v1tag[125] === "\x00") && ($id3v1tag[126] !== "\x00")) {
    47 				$ParsedID3v1['track']   = ord(substr($ParsedID3v1['comment'], 29,  1));
    52 				$ParsedID3v1['track_number'] = ord(substr($ParsedID3v1['comment'], 29,  1));
    48 				$ParsedID3v1['comment'] =     substr($ParsedID3v1['comment'],  0, 28);
    53 				$ParsedID3v1['comment']      =     substr($ParsedID3v1['comment'],  0, 28);
    49 			}
    54 			}
    50 			$ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
    55 			$ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
    51 
    56 
    52 			$ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
    57 			$ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
    53 			if (!empty($ParsedID3v1['genre'])) {
    58 			if (!empty($ParsedID3v1['genre'])) {
    58 			}
    63 			}
    59 
    64 
    60 			foreach ($ParsedID3v1 as $key => $value) {
    65 			foreach ($ParsedID3v1 as $key => $value) {
    61 				$ParsedID3v1['comments'][$key][0] = $value;
    66 				$ParsedID3v1['comments'][$key][0] = $value;
    62 			}
    67 			}
    63 			// ID3v1 encoding detection hack START
    68 			$ID3v1encoding = $this->getid3->encoding_id3v1;
    64 			// ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets
    69 			if ($this->getid3->encoding_id3v1_autodetect) {
    65 			// Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess
    70 				// ID3v1 encoding detection hack START
    66 			$ID3v1encoding = 'ISO-8859-1';
    71 				// ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets
    67 			foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) {
    72 				// Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess
    68 				foreach ($valuearray as $key => $value) {
    73 				foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) {
    69 					if (preg_match('#^[\\x00-\\x40\\xA8\\B8\\x80-\\xFF]+$#', $value)) {
    74 					foreach ($valuearray as $key => $value) {
    70 						foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
    75 						if (preg_match('#^[\\x00-\\x40\\x80-\\xFF]+$#', $value) && !ctype_digit((string) $value)) { // check for strings with only characters above chr(128) and punctuation/numbers, but not just numeric strings (e.g. track numbers or years)
    71 							if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) {
    76 							foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
    72 								$ID3v1encoding = $id3v1_bad_encoding;
    77 								if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) {
    73 								break 3;
    78 									$ID3v1encoding = $id3v1_bad_encoding;
    74 							} elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) {
    79 									$this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
    75 								$ID3v1encoding = $id3v1_bad_encoding;
    80 									break 3;
    76 								break 3;
    81 								} elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) {
       
    82 									$ID3v1encoding = $id3v1_bad_encoding;
       
    83 									$this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
       
    84 									break 3;
       
    85 								}
    77 							}
    86 							}
    78 						}
    87 						}
    79 					}
    88 					}
    80 				}
    89 				}
    81 			}
    90 				// ID3v1 encoding detection hack END
    82 			// ID3v1 encoding detection hack END
    91 			}
    83 
    92 
    84 			// ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
    93 			// ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
    85 			$GoodFormatID3v1tag = $this->GenerateID3v1Tag(
    94 			$GoodFormatID3v1tag = $this->GenerateID3v1Tag(
    86 											$ParsedID3v1['title'],
    95 											$ParsedID3v1['title'],
    87 											$ParsedID3v1['artist'],
    96 											$ParsedID3v1['artist'],
    88 											$ParsedID3v1['album'],
    97 											$ParsedID3v1['album'],
    89 											$ParsedID3v1['year'],
    98 											$ParsedID3v1['year'],
    90 											(isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
    99 											(isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
    91 											$ParsedID3v1['comment'],
   100 											$ParsedID3v1['comment'],
    92 											(!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
   101 											(!empty($ParsedID3v1['track_number']) ? $ParsedID3v1['track_number'] : ''));
    93 			$ParsedID3v1['padding_valid'] = true;
   102 			$ParsedID3v1['padding_valid'] = true;
    94 			if ($id3v1tag !== $GoodFormatID3v1tag) {
   103 			if ($id3v1tag !== $GoodFormatID3v1tag) {
    95 				$ParsedID3v1['padding_valid'] = false;
   104 				$ParsedID3v1['padding_valid'] = false;
    96 				$this->warning('Some ID3v1 fields do not use NULL characters for padding');
   105 				$this->warning('Some ID3v1 fields do not use NULL characters for padding');
    97 			}
   106 			}
   122 		}
   131 		}
   123 
   132 
   124 		return true;
   133 		return true;
   125 	}
   134 	}
   126 
   135 
       
   136 	/**
       
   137 	 * @param string $str
       
   138 	 *
       
   139 	 * @return string
       
   140 	 */
   127 	public static function cutfield($str) {
   141 	public static function cutfield($str) {
   128 		return trim(substr($str, 0, strcspn($str, "\x00")));
   142 		return trim(substr($str, 0, strcspn($str, "\x00")));
   129 	}
   143 	}
   130 
   144 
       
   145 	/**
       
   146 	 * @param bool $allowSCMPXextended
       
   147 	 *
       
   148 	 * @return string[]
       
   149 	 */
   131 	public static function ArrayOfGenres($allowSCMPXextended=false) {
   150 	public static function ArrayOfGenres($allowSCMPXextended=false) {
   132 		static $GenreLookup = array(
   151 		static $GenreLookup = array(
   133 			0    => 'Blues',
   152 			0    => 'Blues',
   134 			1    => 'Classic Rock',
   153 			1    => 'Classic Rock',
   135 			2    => 'Country',
   154 			2    => 'Country',
   310 		}
   329 		}
   311 
   330 
   312 		return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
   331 		return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
   313 	}
   332 	}
   314 
   333 
       
   334 	/**
       
   335 	 * @param string $genreid
       
   336 	 * @param bool   $allowSCMPXextended
       
   337 	 *
       
   338 	 * @return string|false
       
   339 	 */
   315 	public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
   340 	public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
   316 		switch ($genreid) {
   341 		switch ($genreid) {
   317 			case 'RX':
   342 			case 'RX':
   318 			case 'CR':
   343 			case 'CR':
   319 				break;
   344 				break;
   326 		}
   351 		}
   327 		$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
   352 		$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
   328 		return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
   353 		return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
   329 	}
   354 	}
   330 
   355 
       
   356 	/**
       
   357 	 * @param string $genre
       
   358 	 * @param bool   $allowSCMPXextended
       
   359 	 *
       
   360 	 * @return string|false
       
   361 	 */
   331 	public static function LookupGenreID($genre, $allowSCMPXextended=false) {
   362 	public static function LookupGenreID($genre, $allowSCMPXextended=false) {
   332 		$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
   363 		$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
   333 		$LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
   364 		$LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
   334 		foreach ($GenreLookup as $key => $value) {
   365 		foreach ($GenreLookup as $key => $value) {
   335 			if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
   366 			if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
   337 			}
   368 			}
   338 		}
   369 		}
   339 		return false;
   370 		return false;
   340 	}
   371 	}
   341 
   372 
       
   373 	/**
       
   374 	 * @param string $OriginalGenre
       
   375 	 *
       
   376 	 * @return string|false
       
   377 	 */
   342 	public static function StandardiseID3v1GenreName($OriginalGenre) {
   378 	public static function StandardiseID3v1GenreName($OriginalGenre) {
   343 		if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
   379 		if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
   344 			return self::LookupGenreName($GenreID);
   380 			return self::LookupGenreName($GenreID);
   345 		}
   381 		}
   346 		return $OriginalGenre;
   382 		return $OriginalGenre;
   347 	}
   383 	}
   348 
   384 
       
   385 	/**
       
   386 	 * @param string     $title
       
   387 	 * @param string     $artist
       
   388 	 * @param string     $album
       
   389 	 * @param string     $year
       
   390 	 * @param int        $genreid
       
   391 	 * @param string     $comment
       
   392 	 * @param int|string $track
       
   393 	 *
       
   394 	 * @return string
       
   395 	 */
   349 	public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
   396 	public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
   350 		$ID3v1Tag  = 'TAG';
   397 		$ID3v1Tag  = 'TAG';
   351 		$ID3v1Tag .= str_pad(trim(substr($title,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
   398 		$ID3v1Tag .= str_pad(trim(substr($title,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
   352 		$ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
   399 		$ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
   353 		$ID3v1Tag .= str_pad(trim(substr($album,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
   400 		$ID3v1Tag .= str_pad(trim(substr($album,  0, 30)), 30, "\x00", STR_PAD_RIGHT);