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