| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 3 ///////////////////////////////////////////////////////////////// 4 /// getID3() by James Heinrich <info@getid3.org> // 5 // available at https://github.com/JamesHeinrich/getID3 // 6 // or https://www.getid3.org // 7 // or http://getid3.sourceforge.net // 8 // see readme.txt for more details // 9 ///////////////////////////////////////////////////////////////// 10 // // 11 // module.tag.id3v1.php // 12 // module for analyzing ID3v1 tags // 13 // dependencies: NONE // 14 // /// 15 ///////////////////////////////////////////////////////////////// 16 17 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers 18 exit; 19 } 20 21 class getid3_id3v1 extends getid3_handler 22 { 23 /** 24 * @return bool 25 */ 26 public function Analyze() { 27 $info = &$this->getid3->info; 28 29 if (!getid3_lib::intValueSupported($info['filesize'])) { 30 $this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'); 31 return false; 32 } elseif ($info['filesize'] < 128) { 33 $this->warning('Unable to check for ID3v1 because file is too small'); 34 return false; 35 } 36 37 if($info['filesize'] < 256) { 38 $this->fseek(-128, SEEK_END); 39 $preid3v1 = ''; 40 $id3v1tag = $this->fread(128); 41 } else { 42 $this->fseek(-256, SEEK_END); 43 $preid3v1 = $this->fread(128); 44 $id3v1tag = $this->fread(128); 45 } 46 47 48 if (substr($id3v1tag, 0, 3) == 'TAG') { 49 50 $info['avdataend'] = $info['filesize'] - 128; 51 52 $ParsedID3v1 = array(); 53 $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30)); 54 $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30)); 55 $ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30)); 56 $ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4)); 57 $ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them 58 $ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1)); 59 60 // If second-last byte of comment field is null and last byte of comment field is non-null 61 // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number 62 if (($id3v1tag[125] === "\x00") && ($id3v1tag[126] !== "\x00")) { 63 $ParsedID3v1['track_number'] = ord(substr($ParsedID3v1['comment'], 29, 1)); 64 $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28); 65 } 66 $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']); 67 68 $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']); 69 if (!empty($ParsedID3v1['genre'])) { 70 unset($ParsedID3v1['genreid']); 71 } 72 if (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown')) { 73 unset($ParsedID3v1['genre']); 74 } 75 76 foreach ($ParsedID3v1 as $key => $value) { 77 $ParsedID3v1['comments'][$key][0] = $value; 78 } 79 $ID3v1encoding = $this->getid3->encoding_id3v1; 80 if ($this->getid3->encoding_id3v1_autodetect) { 81 // ID3v1 encoding detection hack START 82 // 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 83 // 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 84 foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) { 85 foreach ($valuearray as $key => $value) { 86 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) 87 foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) { 88 if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) { 89 $ID3v1encoding = $id3v1_bad_encoding; 90 $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key); 91 break 3; 92 } elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) { 93 $ID3v1encoding = $id3v1_bad_encoding; 94 $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key); 95 break 3; 96 } 97 } 98 } 99 } 100 } 101 // ID3v1 encoding detection hack END 102 } 103 104 // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces 105 $GoodFormatID3v1tag = $this->GenerateID3v1Tag( 106 $ParsedID3v1['title'], 107 $ParsedID3v1['artist'], 108 $ParsedID3v1['album'], 109 $ParsedID3v1['year'], 110 (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false), 111 $ParsedID3v1['comment'], 112 (!empty($ParsedID3v1['track_number']) ? $ParsedID3v1['track_number'] : '')); 113 $ParsedID3v1['padding_valid'] = true; 114 if ($id3v1tag !== $GoodFormatID3v1tag) { 115 $ParsedID3v1['padding_valid'] = false; 116 $this->warning('Some ID3v1 fields do not use NULL characters for padding'); 117 } 118 119 $ParsedID3v1['tag_offset_end'] = $info['filesize']; 120 $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128; 121 122 $info['id3v1'] = $ParsedID3v1; 123 $info['id3v1']['encoding'] = $ID3v1encoding; 124 } 125 126 if (substr($preid3v1, 0, 3) == 'TAG') { 127 // The way iTunes handles tags is, well, brain-damaged. 128 // It completely ignores v1 if ID3v2 is present. 129 // This goes as far as adding a new v1 tag *even if there already is one* 130 131 // A suspected double-ID3v1 tag has been detected, but it could be that 132 // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag 133 if (substr($preid3v1, 96, 8) == 'APETAGEX') { 134 // an APE tag footer was found before the last ID3v1, assume false "TAG" synch 135 } elseif (substr($preid3v1, 119, 6) == 'LYRICS') { 136 // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch 137 } else { 138 // APE and Lyrics3 footers not found - assume double ID3v1 139 $this->warning('Duplicate ID3v1 tag detected - this has been known to happen with iTunes'); 140 $info['avdataend'] -= 128; 141 } 142 } 143 144 return true; 145 } 146 147 /** 148 * @param string $str 149 * 150 * @return string 151 */ 152 public static function cutfield($str) { 153 return trim(substr($str, 0, strcspn($str, "\x00"))); 154 } 155 156 /** 157 * @param bool $allowSCMPXextended 158 * 159 * @return string[] 160 */ 161 public static function ArrayOfGenres($allowSCMPXextended=false) { 162 static $GenreLookup = array( 163 0 => 'Blues', 164 1 => 'Classic Rock', 165 2 => 'Country', 166 3 => 'Dance', 167 4 => 'Disco', 168 5 => 'Funk', 169 6 => 'Grunge', 170 7 => 'Hip-Hop', 171 8 => 'Jazz', 172 9 => 'Metal', 173 10 => 'New Age', 174 11 => 'Oldies', 175 12 => 'Other', 176 13 => 'Pop', 177 14 => 'R&B', 178 15 => 'Rap', 179 16 => 'Reggae', 180 17 => 'Rock', 181 18 => 'Techno', 182 19 => 'Industrial', 183 20 => 'Alternative', 184 21 => 'Ska', 185 22 => 'Death Metal', 186 23 => 'Pranks', 187 24 => 'Soundtrack', 188 25 => 'Euro-Techno', 189 26 => 'Ambient', 190 27 => 'Trip-Hop', 191 28 => 'Vocal', 192 29 => 'Jazz+Funk', 193 30 => 'Fusion', 194 31 => 'Trance', 195 32 => 'Classical', 196 33 => 'Instrumental', 197 34 => 'Acid', 198 35 => 'House', 199 36 => 'Game', 200 37 => 'Sound Clip', 201 38 => 'Gospel', 202 39 => 'Noise', 203 40 => 'Alt. Rock', 204 41 => 'Bass', 205 42 => 'Soul', 206 43 => 'Punk', 207 44 => 'Space', 208 45 => 'Meditative', 209 46 => 'Instrumental Pop', 210 47 => 'Instrumental Rock', 211 48 => 'Ethnic', 212 49 => 'Gothic', 213 50 => 'Darkwave', 214 51 => 'Techno-Industrial', 215 52 => 'Electronic', 216 53 => 'Pop-Folk', 217 54 => 'Eurodance', 218 55 => 'Dream', 219 56 => 'Southern Rock', 220 57 => 'Comedy', 221 58 => 'Cult', 222 59 => 'Gangsta Rap', 223 60 => 'Top 40', 224 61 => 'Christian Rap', 225 62 => 'Pop/Funk', 226 63 => 'Jungle', 227 64 => 'Native American', 228 65 => 'Cabaret', 229 66 => 'New Wave', 230 67 => 'Psychedelic', 231 68 => 'Rave', 232 69 => 'Showtunes', 233 70 => 'Trailer', 234 71 => 'Lo-Fi', 235 72 => 'Tribal', 236 73 => 'Acid Punk', 237 74 => 'Acid Jazz', 238 75 => 'Polka', 239 76 => 'Retro', 240 77 => 'Musical', 241 78 => 'Rock & Roll', 242 79 => 'Hard Rock', 243 80 => 'Folk', 244 81 => 'Folk/Rock', 245 82 => 'National Folk', 246 83 => 'Swing', 247 84 => 'Fast-Fusion', 248 85 => 'Bebob', 249 86 => 'Latin', 250 87 => 'Revival', 251 88 => 'Celtic', 252 89 => 'Bluegrass', 253 90 => 'Avantgarde', 254 91 => 'Gothic Rock', 255 92 => 'Progressive Rock', 256 93 => 'Psychedelic Rock', 257 94 => 'Symphonic Rock', 258 95 => 'Slow Rock', 259 96 => 'Big Band', 260 97 => 'Chorus', 261 98 => 'Easy Listening', 262 99 => 'Acoustic', 263 100 => 'Humour', 264 101 => 'Speech', 265 102 => 'Chanson', 266 103 => 'Opera', 267 104 => 'Chamber Music', 268 105 => 'Sonata', 269 106 => 'Symphony', 270 107 => 'Booty Bass', 271 108 => 'Primus', 272 109 => 'Porn Groove', 273 110 => 'Satire', 274 111 => 'Slow Jam', 275 112 => 'Club', 276 113 => 'Tango', 277 114 => 'Samba', 278 115 => 'Folklore', 279 116 => 'Ballad', 280 117 => 'Power Ballad', 281 118 => 'Rhythmic Soul', 282 119 => 'Freestyle', 283 120 => 'Duet', 284 121 => 'Punk Rock', 285 122 => 'Drum Solo', 286 123 => 'A Cappella', 287 124 => 'Euro-House', 288 125 => 'Dance Hall', 289 126 => 'Goa', 290 127 => 'Drum & Bass', 291 128 => 'Club-House', 292 129 => 'Hardcore', 293 130 => 'Terror', 294 131 => 'Indie', 295 132 => 'BritPop', 296 133 => 'Negerpunk', 297 134 => 'Polsk Punk', 298 135 => 'Beat', 299 136 => 'Christian Gangsta Rap', 300 137 => 'Heavy Metal', 301 138 => 'Black Metal', 302 139 => 'Crossover', 303 140 => 'Contemporary Christian', 304 141 => 'Christian Rock', 305 142 => 'Merengue', 306 143 => 'Salsa', 307 144 => 'Thrash Metal', 308 145 => 'Anime', 309 146 => 'JPop', 310 147 => 'Synthpop', 311 148 => 'Abstract', 312 149 => 'Art Rock', 313 150 => 'Baroque', 314 151 => 'Bhangra', 315 152 => 'Big Beat', 316 153 => 'Breakbeat', 317 154 => 'Chillout', 318 155 => 'Downtempo', 319 156 => 'Dub', 320 157 => 'EBM', 321 158 => 'Eclectic', 322 159 => 'Electro', 323 160 => 'Electroclash', 324 161 => 'Emo', 325 162 => 'Experimental', 326 163 => 'Garage', 327 164 => 'Global', 328 165 => 'IDM', 329 166 => 'Illbient', 330 167 => 'Industro-Goth', 331 168 => 'Jam Band', 332 169 => 'Krautrock', 333 170 => 'Leftfield', 334 171 => 'Lounge', 335 172 => 'Math Rock', 336 173 => 'New Romantic', 337 174 => 'Nu-Breakz', 338 175 => 'Post-Punk', 339 176 => 'Post-Rock', 340 177 => 'Psytrance', 341 178 => 'Shoegaze', 342 179 => 'Space Rock', 343 180 => 'Trop Rock', 344 181 => 'World Music', 345 182 => 'Neoclassical', 346 183 => 'Audiobook', 347 184 => 'Audio Theatre', 348 185 => 'Neue Deutsche Welle', 349 186 => 'Podcast', 350 187 => 'Indie-Rock', 351 188 => 'G-Funk', 352 189 => 'Dubstep', 353 190 => 'Garage Rock', 354 191 => 'Psybient', 355 356 255 => 'Unknown', 357 358 'CR' => 'Cover', 359 'RX' => 'Remix' 360 ); 361 362 static $GenreLookupSCMPX = array(); 363 if ($allowSCMPXextended && empty($GenreLookupSCMPX)) { 364 $GenreLookupSCMPX = $GenreLookup; 365 // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended 366 // Extended ID3v1 genres invented by SCMPX 367 // Note that 255 "Japanese Anime" conflicts with standard "Unknown" 368 $GenreLookupSCMPX[240] = 'Sacred'; 369 $GenreLookupSCMPX[241] = 'Northern Europe'; 370 $GenreLookupSCMPX[242] = 'Irish & Scottish'; 371 $GenreLookupSCMPX[243] = 'Scotland'; 372 $GenreLookupSCMPX[244] = 'Ethnic Europe'; 373 $GenreLookupSCMPX[245] = 'Enka'; 374 $GenreLookupSCMPX[246] = 'Children\'s Song'; 375 $GenreLookupSCMPX[247] = 'Japanese Sky'; 376 $GenreLookupSCMPX[248] = 'Japanese Heavy Rock'; 377 $GenreLookupSCMPX[249] = 'Japanese Doom Rock'; 378 $GenreLookupSCMPX[250] = 'Japanese J-POP'; 379 $GenreLookupSCMPX[251] = 'Japanese Seiyu'; 380 $GenreLookupSCMPX[252] = 'Japanese Ambient Techno'; 381 $GenreLookupSCMPX[253] = 'Japanese Moemoe'; 382 $GenreLookupSCMPX[254] = 'Japanese Tokusatsu'; 383 //$GenreLookupSCMPX[255] = 'Japanese Anime'; 384 } 385 386 return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup); 387 } 388 389 /** 390 * @param string $genreid 391 * @param bool $allowSCMPXextended 392 * 393 * @return string|false 394 */ 395 public static function LookupGenreName($genreid, $allowSCMPXextended=true) { 396 switch ($genreid) { 397 case 'RX': 398 case 'CR': 399 break; 400 default: 401 if (!is_numeric($genreid)) { 402 return false; 403 } 404 $genreid = intval($genreid); // to handle 3 or '3' or '03' 405 break; 406 } 407 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); 408 return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); 409 } 410 411 /** 412 * @param string $genre 413 * @param bool $allowSCMPXextended 414 * 415 * @return string|false 416 */ 417 public static function LookupGenreID($genre, $allowSCMPXextended=false) { 418 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); 419 $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre)); 420 foreach ($GenreLookup as $key => $value) { 421 if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { 422 return $key; 423 } 424 } 425 return false; 426 } 427 428 /** 429 * @param string $OriginalGenre 430 * 431 * @return string|false 432 */ 433 public static function StandardiseID3v1GenreName($OriginalGenre) { 434 if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) { 435 return self::LookupGenreName($GenreID); 436 } 437 return $OriginalGenre; 438 } 439 440 /** 441 * @param string $title 442 * @param string $artist 443 * @param string $album 444 * @param string $year 445 * @param int $genreid 446 * @param string $comment 447 * @param int|string $track 448 * 449 * @return string 450 */ 451 public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { 452 $ID3v1Tag = 'TAG'; 453 $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT); 454 $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT); 455 $ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT); 456 $ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT); 457 if (!empty($track) && ($track > 0) && ($track <= 255)) { 458 $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT); 459 $ID3v1Tag .= "\x00"; 460 if (gettype($track) == 'string') { 461 $track = (int) $track; 462 } 463 $ID3v1Tag .= chr($track); 464 } else { 465 $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT); 466 } 467 if (($genreid < 0) || ($genreid > 147)) { 468 $genreid = 255; // 'unknown' genre 469 } 470 switch (gettype($genreid)) { 471 case 'string': 472 case 'integer': 473 $ID3v1Tag .= chr(intval($genreid)); 474 break; 475 default: 476 $ID3v1Tag .= chr(255); // 'unknown' genre 477 break; 478 } 479 480 return $ID3v1Tag; 481 } 482 483 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Thu Jun 18 08:20:10 2026 | Cross-referenced by PHPXref |