| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 ///////////////////////////////////////////////////////////////// 3 /// getID3() by James Heinrich <info@getid3.org> // 4 // available at https://github.com/JamesHeinrich/getID3 // 5 // or https://www.getid3.org // 6 // or http://getid3.sourceforge.net // 7 // // 8 // Please see readme.txt for more information // 9 // /// 10 ///////////////////////////////////////////////////////////////// 11 12 // define a constant rather than looking up every time it is needed 13 if (!defined('GETID3_OS_ISWINDOWS')) { 14 define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0)); 15 } 16 // Get base path of getID3() - ONCE 17 if (!defined('GETID3_INCLUDEPATH')) { 18 define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); 19 } 20 if (!defined('ENT_SUBSTITUTE')) { // PHP5.3 adds ENT_IGNORE, PHP5.4 adds ENT_SUBSTITUTE 21 define('ENT_SUBSTITUTE', (defined('ENT_IGNORE') ? ENT_IGNORE : 8)); 22 } 23 24 /* 25 https://www.getid3.org/phpBB3/viewtopic.php?t=2114 26 If you are running into a the problem where filenames with special characters are being handled 27 incorrectly by external helper programs (e.g. metaflac), notably with the special characters removed, 28 and you are passing in the filename in UTF8 (typically via a HTML form), try uncommenting this line: 29 */ 30 //setlocale(LC_CTYPE, 'en_US.UTF-8'); 31 32 // attempt to define temp dir as something flexible but reliable 33 $temp_dir = ini_get('upload_tmp_dir'); 34 if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { 35 $temp_dir = ''; 36 } 37 if (!$temp_dir && function_exists('sys_get_temp_dir')) { // sys_get_temp_dir added in PHP v5.2.1 38 // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts 39 $temp_dir = sys_get_temp_dir(); 40 } 41 $temp_dir = @realpath($temp_dir); // see https://github.com/JamesHeinrich/getID3/pull/10 42 $open_basedir = ini_get('open_basedir'); 43 if ($open_basedir) { 44 // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/" 45 $temp_dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir); 46 $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir); 47 if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) { 48 $temp_dir .= DIRECTORY_SEPARATOR; 49 } 50 $found_valid_tempdir = false; 51 $open_basedirs = explode(PATH_SEPARATOR, $open_basedir); 52 foreach ($open_basedirs as $basedir) { 53 if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) { 54 $basedir .= DIRECTORY_SEPARATOR; 55 } 56 if (strpos($temp_dir, $basedir) === 0) { 57 $found_valid_tempdir = true; 58 break; 59 } 60 } 61 if (!$found_valid_tempdir) { 62 $temp_dir = ''; 63 } 64 unset($open_basedirs, $found_valid_tempdir, $basedir); 65 } 66 if (!$temp_dir) { 67 $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir 68 } 69 // $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system 70 if (!defined('GETID3_TEMP_DIR')) { 71 define('GETID3_TEMP_DIR', $temp_dir); 72 } 73 unset($open_basedir, $temp_dir); 74 75 // End: Defines 76 77 78 class getID3 79 { 80 /* 81 * Settings 82 */ 83 84 /** 85 * CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE 86 * 87 * @var string 88 */ 89 public $encoding = 'UTF-8'; 90 91 /** 92 * Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252' 93 * 94 * @var string 95 */ 96 public $encoding_id3v1 = 'ISO-8859-1'; 97 98 /** 99 * ID3v1 should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'Windows-1251' or 'KOI8-R'. If true attempt to detect these encodings, but may return incorrect values for some tags actually in ISO-8859-1 encoding 100 * 101 * @var bool 102 */ 103 public $encoding_id3v1_autodetect = false; 104 105 /* 106 * Optional tag checks - disable for speed. 107 */ 108 109 /** 110 * Read and process ID3v1 tags 111 * 112 * @var bool 113 */ 114 public $option_tag_id3v1 = true; 115 116 /** 117 * Read and process ID3v2 tags 118 * 119 * @var bool 120 */ 121 public $option_tag_id3v2 = true; 122 123 /** 124 * Read and process Lyrics3 tags 125 * 126 * @var bool 127 */ 128 public $option_tag_lyrics3 = true; 129 130 /** 131 * Read and process APE tags 132 * 133 * @var bool 134 */ 135 public $option_tag_apetag = true; 136 137 /** 138 * Copy tags to root key 'tags' and encode to $this->encoding 139 * 140 * @var bool 141 */ 142 public $option_tags_process = true; 143 144 /** 145 * Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities 146 * 147 * @var bool 148 */ 149 public $option_tags_html = true; 150 151 /* 152 * Optional tag/comment calculations 153 */ 154 155 /** 156 * Calculate additional info such as bitrate, channelmode etc 157 * 158 * @var bool 159 */ 160 public $option_extra_info = true; 161 162 /* 163 * Optional handling of embedded attachments (e.g. images) 164 */ 165 166 /** 167 * Defaults to true (ATTACHMENTS_INLINE) for backward compatibility 168 * 169 * @var bool|string 170 */ 171 public $option_save_attachments = true; 172 173 /* 174 * Optional calculations 175 */ 176 177 /** 178 * Get MD5 sum of data part - slow 179 * 180 * @var bool 181 */ 182 public $option_md5_data = false; 183 184 /** 185 * Use MD5 of source file if available - only FLAC and OptimFROG 186 * 187 * @var bool 188 */ 189 public $option_md5_data_source = false; 190 191 /** 192 * Get SHA1 sum of data part - slow 193 * 194 * @var bool 195 */ 196 public $option_sha1_data = false; 197 198 /** 199 * Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on 200 * PHP_INT_MAX) 201 * 202 * @var bool|null 203 */ 204 public $option_max_2gb_check; 205 206 /** 207 * Read buffer size in bytes 208 * 209 * @var int 210 */ 211 public $option_fread_buffer_size = 32768; 212 213 214 215 // module-specific options 216 217 /** archive.rar 218 * if true use PHP RarArchive extension, if false (non-extension parsing not yet written in getID3) 219 * 220 * @var bool 221 */ 222 public $options_archive_rar_use_php_rar_extension = true; 223 224 /** archive.gzip 225 * Optional file list - disable for speed. 226 * Decode gzipped files, if possible, and parse recursively (.tar.gz for example). 227 * 228 * @var bool 229 */ 230 public $options_archive_gzip_parse_contents = false; 231 232 /** audio.midi 233 * if false only parse most basic information, much faster for some files but may be inaccurate 234 * 235 * @var bool 236 */ 237 public $options_audio_midi_scanwholefile = true; 238 239 /** audio.mp3 240 * Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, 241 * unrecommended, but may provide data from otherwise-unusable files. 242 * 243 * @var bool 244 */ 245 public $options_audio_mp3_allow_bruteforce = false; 246 247 /** audio.mp3 248 * number of frames to scan to determine if MPEG-audio sequence is valid 249 * Lower this number to 5-20 for faster scanning 250 * Increase this number to 50+ for most accurate detection of valid VBR/CBR mpeg-audio streams 251 * 252 * @var int 253 */ 254 public $options_audio_mp3_mp3_valid_check_frames = 50; 255 256 /** audio.wavpack 257 * Avoid scanning all frames (break after finding ID_RIFF_HEADER and ID_CONFIG_BLOCK, 258 * significantly faster for very large files but other data may be missed 259 * 260 * @var bool 261 */ 262 public $options_audio_wavpack_quick_parsing = false; 263 264 /** audio-video.flv 265 * Break out of the loop if too many frames have been scanned; only scan this 266 * many if meta frame does not contain useful duration. 267 * 268 * @var int 269 */ 270 public $options_audiovideo_flv_max_frames = 100000; 271 272 /** audio-video.matroska 273 * If true, do not return information about CLUSTER chunks, since there's a lot of them 274 * and they're not usually useful [default: TRUE]. 275 * 276 * @var bool 277 */ 278 public $options_audiovideo_matroska_hide_clusters = true; 279 280 /** audio-video.matroska 281 * True to parse the whole file, not only header [default: FALSE]. 282 * 283 * @var bool 284 */ 285 public $options_audiovideo_matroska_parse_whole_file = false; 286 287 /** audio-video.quicktime 288 * return all parsed data from all atoms if true, otherwise just returned parsed metadata 289 * 290 * @var bool 291 */ 292 public $options_audiovideo_quicktime_ReturnAtomData = false; 293 294 /** audio-video.quicktime 295 * return all parsed data from all atoms if true, otherwise just returned parsed metadata 296 * 297 * @var bool 298 */ 299 public $options_audiovideo_quicktime_ParseAllPossibleAtoms = false; 300 301 /** audio-video.swf 302 * return all parsed tags if true, otherwise do not return tags not parsed by getID3 303 * 304 * @var bool 305 */ 306 public $options_audiovideo_swf_ReturnAllTagData = false; 307 308 /** graphic.bmp 309 * return BMP palette 310 * 311 * @var bool 312 */ 313 public $options_graphic_bmp_ExtractPalette = false; 314 315 /** graphic.bmp 316 * return image data 317 * 318 * @var bool 319 */ 320 public $options_graphic_bmp_ExtractData = false; 321 322 /** graphic.png 323 * If data chunk is larger than this do not read it completely (getID3 only needs the first 324 * few dozen bytes for parsing). 325 * 326 * @var int 327 */ 328 public $options_graphic_png_max_data_bytes = 10000000; 329 330 /** misc.pdf 331 * return full details of PDF Cross-Reference Table (XREF) 332 * 333 * @var bool 334 */ 335 public $options_misc_pdf_returnXREF = false; 336 337 /** misc.torrent 338 * Assume all .torrent files are less than 1MB and just read entire thing into memory for easy processing. 339 * Override this value if you need to process files larger than 1MB 340 * 341 * @var int 342 */ 343 public $options_misc_torrent_max_torrent_filesize = 1048576; 344 345 346 347 // Public variables 348 349 /** 350 * Filename of file being analysed. 351 * 352 * @var string 353 */ 354 public $filename; 355 356 /** 357 * Filepointer to file being analysed. 358 * 359 * @var resource 360 */ 361 public $fp; 362 363 /** 364 * Result array. 365 * 366 * @var array 367 */ 368 public $info; 369 370 /** 371 * @var string 372 */ 373 public $tempdir = GETID3_TEMP_DIR; 374 375 /** 376 * @var int 377 */ 378 public $memory_limit = 0; 379 380 /** 381 * @var string 382 */ 383 protected $startup_error = ''; 384 385 /** 386 * @var string 387 */ 388 protected $startup_warning = ''; 389 390 const VERSION = '1.9.24-202509040923'; 391 const FREAD_BUFFER_SIZE = 32768; 392 393 const ATTACHMENTS_NONE = false; 394 const ATTACHMENTS_INLINE = true; 395 396 /** 397 * @throws getid3_exception 398 */ 399 public function __construct() { 400 401 // Check for PHP version 402 $required_php_version = '5.3.0'; 403 if (version_compare(PHP_VERSION, $required_php_version, '<')) { 404 $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION."\n"; 405 return; 406 } 407 408 // Check memory 409 $memoryLimit = ini_get('memory_limit'); 410 if (preg_match('#([0-9]+) ?M#i', $memoryLimit, $matches)) { 411 // could be stored as "16M" rather than 16777216 for example 412 $memoryLimit = (int) $matches[1] * 1048576; 413 } elseif (preg_match('#([0-9]+) ?G#i', $memoryLimit, $matches)) { // The 'G' modifier is available since PHP 5.1.0 414 // could be stored as "2G" rather than 2147483648 for example 415 $memoryLimit = (int) $matches[1] * 1073741824; 416 } 417 $this->memory_limit = $memoryLimit; 418 419 if ($this->memory_limit <= 0) { 420 // memory limits probably disabled 421 } elseif ($this->memory_limit <= 4194304) { 422 $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'."\n"; 423 } elseif ($this->memory_limit <= 12582912) { 424 $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'."\n"; 425 } 426 427 // Check safe_mode off 428 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { 429 $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); 430 } 431 432 if (($mbstring_func_overload = (int) ini_get('mbstring.func_overload')) && ($mbstring_func_overload & 0x02)) { 433 // http://php.net/manual/en/mbstring.overload.php 434 // "mbstring.func_overload in php.ini is a positive value that represents a combination of bitmasks specifying the categories of functions to be overloaded. It should be set to 1 to overload the mail() function. 2 for string functions, 4 for regular expression functions" 435 // getID3 cannot run when string functions are overloaded. It doesn't matter if mail() or ereg* functions are overloaded since getID3 does not use those. 436 $this->startup_error .= 'WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", getID3 cannot run with this setting (bitmask 2 (string functions) cannot be set). Recommended to disable entirely.'."\n"; 437 } 438 439 // check for magic quotes in PHP < 5.4.0 (when these options were removed and getters always return false) 440 if (version_compare(PHP_VERSION, '5.4.0', '<')) { 441 // Check for magic_quotes_runtime 442 if (function_exists('get_magic_quotes_runtime')) { 443 if (get_magic_quotes_runtime()) { // @phpstan-ignore-line 444 $this->startup_error .= 'magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'."\n"; 445 } 446 } 447 // Check for magic_quotes_gpc 448 if (function_exists('get_magic_quotes_gpc')) { 449 if (get_magic_quotes_gpc()) { 450 $this->startup_error .= 'magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'."\n"; 451 } 452 } 453 } 454 455 // Load support library 456 if (!include_once (GETID3_INCLUDEPATH.'getid3.lib.php')) { 457 $this->startup_error .= 'getid3.lib.php is missing or corrupt'."\n"; 458 } 459 460 if ($this->option_max_2gb_check === null) { 461 $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647); 462 } 463 464 465 // Needed for Windows only: 466 // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC 467 // as well as other helper functions such as head, etc 468 // This path cannot contain spaces, but the below code will attempt to get the 469 // 8.3-equivalent path automatically 470 // IMPORTANT: This path must include the trailing slash 471 if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) { 472 473 $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path 474 475 if (!is_dir($helperappsdir)) { 476 $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'."\n"; 477 } elseif (strpos(realpath($helperappsdir), ' ') !== false) { 478 $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir)); 479 $path_so_far = array(); 480 foreach ($DirPieces as $key => $value) { 481 if (strpos($value, ' ') !== false) { 482 if (!empty($path_so_far)) { 483 $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far)); 484 $dir_listing = shell_exec($commandline); 485 $lines = explode("\n", $dir_listing); 486 foreach ($lines as $line) { 487 $line = trim($line); 488 if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) { 489 list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches; 490 if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) { 491 $value = $shortname; 492 } 493 } 494 } 495 } else { 496 $this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'."\n"; 497 } 498 } 499 $path_so_far[] = $value; 500 } 501 $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far); 502 } 503 define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR); 504 } 505 506 if (!empty($this->startup_error)) { 507 echo $this->startup_error; 508 throw new getid3_exception($this->startup_error); 509 } 510 } 511 512 /** 513 * @return string 514 */ 515 public function version() { 516 return self::VERSION; 517 } 518 519 /** 520 * @return int 521 */ 522 public function fread_buffer_size() { 523 return $this->option_fread_buffer_size; 524 } 525 526 /** 527 * @param array $optArray 528 * 529 * @return bool 530 */ 531 public function setOption($optArray) { 532 if (empty($optArray)) { 533 return false; 534 } 535 foreach ($optArray as $opt => $val) { 536 if (isset($this->$opt) === false) { 537 continue; 538 } 539 $this->$opt = $val; 540 } 541 return true; 542 } 543 544 /** 545 * @param string $filename 546 * @param int $filesize 547 * @param resource $fp 548 * 549 * @return bool 550 * 551 * @throws getid3_exception 552 */ 553 public function openfile($filename, $filesize=null, $fp=null) { 554 try { 555 if (!empty($this->startup_error)) { 556 throw new getid3_exception($this->startup_error); 557 } 558 if (!empty($this->startup_warning)) { 559 foreach (explode("\n", $this->startup_warning) as $startup_warning) { 560 $this->warning($startup_warning); 561 } 562 } 563 564 // init result array and set parameters 565 $this->filename = $filename; 566 $this->info = array(); 567 $this->info['GETID3_VERSION'] = $this->version(); 568 $this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false); 569 570 // remote files not supported 571 if (preg_match('#^(ht|f)tps?://#', $filename)) { 572 throw new getid3_exception('Remote files are not supported - please copy the file locally first'); 573 } 574 575 $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); 576 //$filename = preg_replace('#(?<!gs:)('.preg_quote(DIRECTORY_SEPARATOR).'{2,})#', DIRECTORY_SEPARATOR, $filename); 577 578 // open local file 579 //if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see https://www.getid3.org/phpBB3/viewtopic.php?t=1720 580 if (($fp != null) && ((get_resource_type($fp) == 'file') || (get_resource_type($fp) == 'stream'))) { 581 $this->fp = $fp; 582 } elseif ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { 583 // great 584 } else { 585 $errormessagelist = array(); 586 if (!is_readable($filename)) { 587 $errormessagelist[] = '!is_readable'; 588 } 589 if (!is_file($filename)) { 590 $errormessagelist[] = '!is_file'; 591 } 592 if (!file_exists($filename)) { 593 $errormessagelist[] = '!file_exists'; 594 } 595 if (empty($errormessagelist)) { 596 $errormessagelist[] = 'fopen failed'; 597 } 598 throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')'); 599 } 600 601 $this->info['filesize'] = (!is_null($filesize) ? $filesize : filesize($filename)); 602 // set redundant parameters - might be needed in some include file 603 // filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion 604 $filename = str_replace('\\', '/', $filename); 605 $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); 606 $this->info['filename'] = getid3_lib::mb_basename($filename); 607 $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; 608 609 // set more parameters 610 $this->info['avdataoffset'] = 0; 611 $this->info['avdataend'] = $this->info['filesize']; 612 $this->info['fileformat'] = ''; // filled in later 613 $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used 614 $this->info['video']['dataformat'] = ''; // filled in later, unset if not used 615 $this->info['tags'] = array(); // filled in later, unset if not used 616 $this->info['error'] = array(); // filled in later, unset if not used 617 $this->info['warning'] = array(); // filled in later, unset if not used 618 $this->info['comments'] = array(); // filled in later, unset if not used 619 $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired 620 621 // option_max_2gb_check 622 if ($this->option_max_2gb_check) { 623 // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB) 624 // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize 625 // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer 626 $fseek = fseek($this->fp, 0, SEEK_END); 627 if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) || 628 ($this->info['filesize'] < 0) || 629 (ftell($this->fp) < 0)) { 630 $real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']); 631 632 if ($real_filesize === false) { 633 unset($this->info['filesize']); 634 fclose($this->fp); 635 throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.'); 636 } elseif (getid3_lib::intValueSupported($real_filesize)) { 637 unset($this->info['filesize']); 638 fclose($this->fp); 639 throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB, please report to info@getid3.org'); 640 } 641 $this->info['filesize'] = $real_filesize; 642 $this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB) and is not properly supported by PHP.'); 643 } 644 } 645 646 return true; 647 648 } catch (Exception $e) { 649 $this->error($e->getMessage()); 650 } 651 return false; 652 } 653 654 /** 655 * analyze file 656 * 657 * @param string $filename 658 * @param int $filesize 659 * @param string $original_filename 660 * @param resource $fp 661 * 662 * @return array 663 */ 664 public function analyze($filename, $filesize=null, $original_filename='', $fp=null) { 665 try { 666 if (!$this->openfile($filename, $filesize, $fp)) { 667 return $this->info; 668 } 669 670 // Handle tags 671 foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { 672 $option_tag = 'option_tag_'.$tag_name; 673 if ($this->$option_tag) { 674 $this->include_module('tag.'.$tag_name); 675 try { 676 $tag_class = 'getid3_'.$tag_name; 677 $tag = new $tag_class($this); 678 $tag->Analyze(); 679 } 680 catch (getid3_exception $e) { 681 throw $e; 682 } 683 } else { 684 $this->warning('skipping check for '.$tag_name.' tags since option_tag_'.$tag_name.'=FALSE'); 685 } 686 } 687 if (isset($this->info['id3v2']['tag_offset_start'])) { 688 $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']); 689 } 690 foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { 691 if (isset($this->info[$tag_key]['tag_offset_start'])) { 692 $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']); 693 } 694 } 695 696 // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier 697 if (!$this->option_tag_id3v2) { 698 fseek($this->fp, 0); 699 $header = fread($this->fp, 10); 700 if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { 701 $this->info['id3v2']['header'] = true; 702 $this->info['id3v2']['majorversion'] = ord($header[3]); 703 $this->info['id3v2']['minorversion'] = ord($header[4]); 704 $this->info['avdataoffset'] += getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length 705 } 706 } 707 708 // read 32 kb file data 709 fseek($this->fp, $this->info['avdataoffset']); 710 $formattest = fread($this->fp, 32774); 711 712 // determine format 713 $determined_format = $this->GetFileFormat($formattest, ($original_filename ? $original_filename : $filename)); 714 715 // unable to determine file format 716 if (!$determined_format) { 717 fclose($this->fp); 718 return $this->error('unable to determine file format'); 719 } 720 721 // check for illegal ID3 tags 722 if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { 723 if ($determined_format['fail_id3'] === 'ERROR') { 724 fclose($this->fp); 725 return $this->error('ID3 tags not allowed on this file type.'); 726 } elseif ($determined_format['fail_id3'] === 'WARNING') { 727 $this->warning('ID3 tags not allowed on this file type.'); 728 } 729 } 730 731 // check for illegal APE tags 732 if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { 733 if ($determined_format['fail_ape'] === 'ERROR') { 734 fclose($this->fp); 735 return $this->error('APE tags not allowed on this file type.'); 736 } elseif ($determined_format['fail_ape'] === 'WARNING') { 737 $this->warning('APE tags not allowed on this file type.'); 738 } 739 } 740 741 // set mime type 742 $this->info['mime_type'] = $determined_format['mime_type']; 743 744 // supported format signature pattern detected, but module deleted 745 if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { 746 fclose($this->fp); 747 return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); 748 } 749 750 // module requires mb_convert_encoding/iconv support 751 // Check encoding/iconv support 752 if (!empty($determined_format['iconv_req']) && !function_exists('mb_convert_encoding') && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { 753 $errormessage = 'mb_convert_encoding() or iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; 754 if (GETID3_OS_ISWINDOWS) { 755 $errormessage .= 'PHP does not have mb_convert_encoding() or iconv() support. Please enable php_mbstring.dll / php_iconv.dll in php.ini, and copy php_mbstring.dll / iconv.dll from c:/php/dlls to c:/windows/system32'; 756 } else { 757 $errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch'; 758 } 759 return $this->error($errormessage); 760 } 761 762 // include module 763 include_once(GETID3_INCLUDEPATH.$determined_format['include']); 764 765 // instantiate module class 766 $class_name = 'getid3_'.$determined_format['module']; 767 if (!class_exists($class_name)) { 768 return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); 769 } 770 $class = new $class_name($this); 771 772 // set module-specific options 773 foreach (get_object_vars($this) as $getid3_object_vars_key => $getid3_object_vars_value) { 774 if (preg_match('#^options_([^_]+)_([^_]+)_(.+)$#i', $getid3_object_vars_key, $matches)) { 775 list($dummy, $GOVgroup, $GOVmodule, $GOVsetting) = $matches; 776 $GOVgroup = (($GOVgroup == 'audiovideo') ? 'audio-video' : $GOVgroup); // variable names can only contain 0-9a-z_ so standardize here 777 if (($GOVgroup == $determined_format['group']) && ($GOVmodule == $determined_format['module'])) { 778 $class->$GOVsetting = $getid3_object_vars_value; 779 } 780 } 781 } 782 783 $class->Analyze(); 784 unset($class); 785 786 // close file 787 fclose($this->fp); 788 789 // process all tags - copy to 'tags' and convert charsets 790 if ($this->option_tags_process) { 791 $this->HandleAllTags(); 792 } 793 794 // perform more calculations 795 if ($this->option_extra_info) { 796 $this->ChannelsBitratePlaytimeCalculations(); 797 $this->CalculateCompressionRatioVideo(); 798 $this->CalculateCompressionRatioAudio(); 799 $this->CalculateReplayGain(); 800 $this->ProcessAudioStreams(); 801 } 802 803 // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags 804 if ($this->option_md5_data) { 805 // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too 806 if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { 807 $this->getHashdata('md5'); 808 } 809 } 810 811 // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags 812 if ($this->option_sha1_data) { 813 $this->getHashdata('sha1'); 814 } 815 816 // remove undesired keys 817 $this->CleanUp(); 818 819 } catch (Exception $e) { 820 $this->error('Caught exception: '.$e->getMessage()); 821 } 822 823 // return info array 824 return $this->info; 825 } 826 827 828 /** 829 * Error handling. 830 * 831 * @param string $message 832 * 833 * @return array 834 */ 835 public function error($message) { 836 $this->CleanUp(); 837 if (!isset($this->info['error'])) { 838 $this->info['error'] = array(); 839 } 840 $this->info['error'][] = $message; 841 return $this->info; 842 } 843 844 845 /** 846 * Warning handling. 847 * 848 * @param string $message 849 * 850 * @return bool 851 */ 852 public function warning($message) { 853 $this->info['warning'][] = $message; 854 return true; 855 } 856 857 858 /** 859 * @return bool 860 */ 861 private function CleanUp() { 862 863 // remove possible empty keys 864 $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate'); 865 foreach ($AVpossibleEmptyKeys as $dummy => $key) { 866 if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) { 867 unset($this->info['audio'][$key]); 868 } 869 if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) { 870 unset($this->info['video'][$key]); 871 } 872 } 873 874 // remove empty root keys 875 if (!empty($this->info)) { 876 foreach ($this->info as $key => $value) { 877 if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) { 878 unset($this->info[$key]); 879 } 880 } 881 } 882 883 // remove meaningless entries from unknown-format files 884 if (empty($this->info['fileformat'])) { 885 if (isset($this->info['avdataoffset'])) { 886 unset($this->info['avdataoffset']); 887 } 888 if (isset($this->info['avdataend'])) { 889 unset($this->info['avdataend']); 890 } 891 } 892 893 // remove possible duplicated identical entries 894 if (!empty($this->info['error'])) { 895 $this->info['error'] = array_values(array_unique($this->info['error'])); 896 } 897 if (!empty($this->info['warning'])) { 898 $this->info['warning'] = array_values(array_unique($this->info['warning'])); 899 } 900 901 // remove "global variable" type keys 902 unset($this->info['php_memory_limit']); 903 904 return true; 905 } 906 907 /** 908 * Return array containing information about all supported formats. 909 * 910 * @return array 911 */ 912 public function GetFileFormatArray() { 913 static $format_info = array(); 914 if (empty($format_info)) { 915 $format_info = array( 916 917 // Audio formats 918 919 // AC-3 - audio - Dolby AC-3 / Dolby Digital 920 'ac3' => array( 921 'pattern' => '^\\x0B\\x77', 922 'group' => 'audio', 923 'module' => 'ac3', 924 'mime_type' => 'audio/ac3', 925 ), 926 927 // AAC - audio - Advanced Audio Coding (AAC) - ADIF format 928 'adif' => array( 929 'pattern' => '^ADIF', 930 'group' => 'audio', 931 'module' => 'aac', 932 'mime_type' => 'audio/aac', 933 'fail_ape' => 'WARNING', 934 ), 935 936 /* 937 // AA - audio - Audible Audiobook 938 'aa' => array( 939 'pattern' => '^.{4}\\x57\\x90\\x75\\x36', 940 'group' => 'audio', 941 'module' => 'aa', 942 'mime_type' => 'audio/audible', 943 ), 944 */ 945 // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) 946 'adts' => array( 947 'pattern' => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]', 948 'group' => 'audio', 949 'module' => 'aac', 950 'mime_type' => 'audio/aac', 951 'fail_ape' => 'WARNING', 952 ), 953 954 955 // AU - audio - NeXT/Sun AUdio (AU) 956 'au' => array( 957 'pattern' => '^\\.snd', 958 'group' => 'audio', 959 'module' => 'au', 960 'mime_type' => 'audio/basic', 961 ), 962 963 // AMR - audio - Adaptive Multi Rate 964 'amr' => array( 965 'pattern' => '^\\x23\\x21AMR\\x0A', // #!AMR[0A] 966 'group' => 'audio', 967 'module' => 'amr', 968 'mime_type' => 'audio/amr', 969 ), 970 971 // AVR - audio - Audio Visual Research 972 'avr' => array( 973 'pattern' => '^2BIT', 974 'group' => 'audio', 975 'module' => 'avr', 976 'mime_type' => 'application/octet-stream', 977 ), 978 979 // BONK - audio - Bonk v0.9+ 980 'bonk' => array( 981 'pattern' => '^\\x00(BONK|INFO|META| ID3)', 982 'group' => 'audio', 983 'module' => 'bonk', 984 'mime_type' => 'audio/xmms-bonk', 985 ), 986 987 // DSF - audio - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital 988 'dsf' => array( 989 'pattern' => '^DSD ', // including trailing space: 44 53 44 20 990 'group' => 'audio', 991 'module' => 'dsf', 992 'mime_type' => 'audio/dsd', 993 ), 994 995 // DSS - audio - Digital Speech Standard 996 'dss' => array( 997 'pattern' => '^[\\x02-\\x08]ds[s2]', 998 'group' => 'audio', 999 'module' => 'dss', 1000 'mime_type' => 'application/octet-stream', 1001 ), 1002 1003 // DSDIFF - audio - Direct Stream Digital Interchange File Format 1004 'dsdiff' => array( 1005 'pattern' => '^FRM8', 1006 'group' => 'audio', 1007 'module' => 'dsdiff', 1008 'mime_type' => 'audio/dsd', 1009 ), 1010 1011 // DTS - audio - Dolby Theatre System 1012 'dts' => array( 1013 'pattern' => '^\\x7F\\xFE\\x80\\x01', 1014 'group' => 'audio', 1015 'module' => 'dts', 1016 'mime_type' => 'audio/dts', 1017 ), 1018 1019 // FLAC - audio - Free Lossless Audio Codec 1020 'flac' => array( 1021 'pattern' => '^fLaC', 1022 'group' => 'audio', 1023 'module' => 'flac', 1024 'mime_type' => 'audio/flac', 1025 ), 1026 1027 // LA - audio - Lossless Audio (LA) 1028 'la' => array( 1029 'pattern' => '^LA0[2-4]', 1030 'group' => 'audio', 1031 'module' => 'la', 1032 'mime_type' => 'application/octet-stream', 1033 ), 1034 1035 // LPAC - audio - Lossless Predictive Audio Compression (LPAC) 1036 'lpac' => array( 1037 'pattern' => '^LPAC', 1038 'group' => 'audio', 1039 'module' => 'lpac', 1040 'mime_type' => 'application/octet-stream', 1041 ), 1042 1043 // MIDI - audio - MIDI (Musical Instrument Digital Interface) 1044 'midi' => array( 1045 'pattern' => '^MThd', 1046 'group' => 'audio', 1047 'module' => 'midi', 1048 'mime_type' => 'audio/midi', 1049 ), 1050 1051 // MAC - audio - Monkey's Audio Compressor 1052 'mac' => array( 1053 'pattern' => '^MAC ', 1054 'group' => 'audio', 1055 'module' => 'monkey', 1056 'mime_type' => 'audio/x-monkeys-audio', 1057 ), 1058 1059 1060 // MOD - audio - MODule (SoundTracker) 1061 'mod' => array( 1062 //'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)', // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available 1063 'pattern' => '^.{1080}(M\\.K\\.)', 1064 'group' => 'audio', 1065 'module' => 'mod', 1066 'option' => 'mod', 1067 'mime_type' => 'audio/mod', 1068 ), 1069 1070 // MOD - audio - MODule (Impulse Tracker) 1071 'it' => array( 1072 'pattern' => '^IMPM', 1073 'group' => 'audio', 1074 'module' => 'mod', 1075 //'option' => 'it', 1076 'mime_type' => 'audio/it', 1077 ), 1078 1079 // MOD - audio - MODule (eXtended Module, various sub-formats) 1080 'xm' => array( 1081 'pattern' => '^Extended Module', 1082 'group' => 'audio', 1083 'module' => 'mod', 1084 //'option' => 'xm', 1085 'mime_type' => 'audio/xm', 1086 ), 1087 1088 // MOD - audio - MODule (ScreamTracker) 1089 's3m' => array( 1090 'pattern' => '^.{44}SCRM', 1091 'group' => 'audio', 1092 'module' => 'mod', 1093 //'option' => 's3m', 1094 'mime_type' => 'audio/s3m', 1095 ), 1096 1097 // MPC - audio - Musepack / MPEGplus 1098 'mpc' => array( 1099 'pattern' => '^(MPCK|MP\\+)', 1100 'group' => 'audio', 1101 'module' => 'mpc', 1102 'mime_type' => 'audio/x-musepack', 1103 ), 1104 1105 // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS) 1106 'mp3' => array( 1107 'pattern' => '^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\x0B\\x10-\\x1B\\x20-\\x2B\\x30-\\x3B\\x40-\\x4B\\x50-\\x5B\\x60-\\x6B\\x70-\\x7B\\x80-\\x8B\\x90-\\x9B\\xA0-\\xAB\\xB0-\\xBB\\xC0-\\xCB\\xD0-\\xDB\\xE0-\\xEB\\xF0-\\xFB]', 1108 'group' => 'audio', 1109 'module' => 'mp3', 1110 'mime_type' => 'audio/mpeg', 1111 ), 1112 1113 // OFR - audio - OptimFROG 1114 'ofr' => array( 1115 'pattern' => '^(\\*RIFF|OFR)', 1116 'group' => 'audio', 1117 'module' => 'optimfrog', 1118 'mime_type' => 'application/octet-stream', 1119 ), 1120 1121 // RKAU - audio - RKive AUdio compressor 1122 'rkau' => array( 1123 'pattern' => '^RKA', 1124 'group' => 'audio', 1125 'module' => 'rkau', 1126 'mime_type' => 'application/octet-stream', 1127 ), 1128 1129 // SHN - audio - Shorten 1130 'shn' => array( 1131 'pattern' => '^ajkg', 1132 'group' => 'audio', 1133 'module' => 'shorten', 1134 'mime_type' => 'audio/xmms-shn', 1135 'fail_id3' => 'ERROR', 1136 'fail_ape' => 'ERROR', 1137 ), 1138 1139 // TAK - audio - Tom's lossless Audio Kompressor 1140 'tak' => array( 1141 'pattern' => '^tBaK', 1142 'group' => 'audio', 1143 'module' => 'tak', 1144 'mime_type' => 'application/octet-stream', 1145 ), 1146 1147 // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org) 1148 'tta' => array( 1149 'pattern' => '^TTA', // could also be '^TTA(\\x01|\\x02|\\x03|2|1)' 1150 'group' => 'audio', 1151 'module' => 'tta', 1152 'mime_type' => 'application/octet-stream', 1153 ), 1154 1155 // VOC - audio - Creative Voice (VOC) 1156 'voc' => array( 1157 'pattern' => '^Creative Voice File', 1158 'group' => 'audio', 1159 'module' => 'voc', 1160 'mime_type' => 'audio/voc', 1161 ), 1162 1163 // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF) 1164 'vqf' => array( 1165 'pattern' => '^TWIN', 1166 'group' => 'audio', 1167 'module' => 'vqf', 1168 'mime_type' => 'application/octet-stream', 1169 ), 1170 1171 // WV - audio - WavPack (v4.0+) 1172 'wv' => array( 1173 'pattern' => '^wvpk', 1174 'group' => 'audio', 1175 'module' => 'wavpack', 1176 'mime_type' => 'application/octet-stream', 1177 ), 1178 1179 1180 // Audio-Video formats 1181 1182 // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio 1183 'asf' => array( 1184 'pattern' => '^\\x30\\x26\\xB2\\x75\\x8E\\x66\\xCF\\x11\\xA6\\xD9\\x00\\xAA\\x00\\x62\\xCE\\x6C', 1185 'group' => 'audio-video', 1186 'module' => 'asf', 1187 'mime_type' => 'video/x-ms-asf', 1188 'iconv_req' => false, 1189 ), 1190 1191 // BINK - audio/video - Bink / Smacker 1192 'bink' => array( 1193 'pattern' => '^(BIK|SMK)', 1194 'group' => 'audio-video', 1195 'module' => 'bink', 1196 'mime_type' => 'application/octet-stream', 1197 ), 1198 1199 // FLV - audio/video - FLash Video 1200 'flv' => array( 1201 'pattern' => '^FLV[\\x01]', 1202 'group' => 'audio-video', 1203 'module' => 'flv', 1204 'mime_type' => 'video/x-flv', 1205 ), 1206 1207 // IVF - audio/video - IVF 1208 'ivf' => array( 1209 'pattern' => '^DKIF', 1210 'group' => 'audio-video', 1211 'module' => 'ivf', 1212 'mime_type' => 'video/x-ivf', 1213 ), 1214 1215 // MKAV - audio/video - Mastroka 1216 'matroska' => array( 1217 'pattern' => '^\\x1A\\x45\\xDF\\xA3', 1218 'group' => 'audio-video', 1219 'module' => 'matroska', 1220 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska 1221 ), 1222 1223 // MPEG - audio/video - MPEG (Moving Pictures Experts Group) 1224 'mpeg' => array( 1225 'pattern' => '^\\x00\\x00\\x01[\\xB3\\xBA]', 1226 'group' => 'audio-video', 1227 'module' => 'mpeg', 1228 'mime_type' => 'video/mpeg', 1229 ), 1230 1231 // NSV - audio/video - Nullsoft Streaming Video (NSV) 1232 'nsv' => array( 1233 'pattern' => '^NSV[sf]', 1234 'group' => 'audio-video', 1235 'module' => 'nsv', 1236 'mime_type' => 'application/octet-stream', 1237 ), 1238 1239 // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*)) 1240 'ogg' => array( 1241 'pattern' => '^OggS', 1242 'group' => 'audio', 1243 'module' => 'ogg', 1244 'mime_type' => 'application/ogg', 1245 'fail_id3' => 'WARNING', 1246 'fail_ape' => 'WARNING', 1247 ), 1248 1249 // QT - audio/video - Quicktime 1250 'quicktime' => array( 1251 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)', 1252 'group' => 'audio-video', 1253 'module' => 'quicktime', 1254 'mime_type' => 'video/quicktime', 1255 ), 1256 1257 // RIFF - audio/video - Resource Interchange File Format (RIFF) / WAV / AVI / CD-audio / SDSS = renamed variant used by SmartSound QuickTracks (www.smartsound.com) / FORM = Audio Interchange File Format (AIFF) 1258 'riff' => array( 1259 'pattern' => '^(RIFF|SDSS|FORM)', 1260 'group' => 'audio-video', 1261 'module' => 'riff', 1262 'mime_type' => 'audio/wav', 1263 'fail_ape' => 'WARNING', 1264 ), 1265 1266 // Real - audio/video - RealAudio, RealVideo 1267 'real' => array( 1268 'pattern' => '^\\.(RMF|ra)', 1269 'group' => 'audio-video', 1270 'module' => 'real', 1271 'mime_type' => 'audio/x-realaudio', 1272 ), 1273 1274 // SWF - audio/video - ShockWave Flash 1275 'swf' => array( 1276 'pattern' => '^(F|C)WS', 1277 'group' => 'audio-video', 1278 'module' => 'swf', 1279 'mime_type' => 'application/x-shockwave-flash', 1280 ), 1281 1282 // TS - audio/video - MPEG-2 Transport Stream 1283 'ts' => array( 1284 'pattern' => '^(\\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G". Check for at least 10 packets matching this pattern 1285 'group' => 'audio-video', 1286 'module' => 'ts', 1287 'mime_type' => 'video/MP2T', 1288 ), 1289 1290 // WTV - audio/video - Windows Recorded TV Show 1291 'wtv' => array( 1292 'pattern' => '^\\xB7\\xD8\\x00\\x20\\x37\\x49\\xDA\\x11\\xA6\\x4E\\x00\\x07\\xE9\\x5E\\xAD\\x8D', 1293 'group' => 'audio-video', 1294 'module' => 'wtv', 1295 'mime_type' => 'video/x-ms-wtv', 1296 ), 1297 1298 1299 // Still-Image formats 1300 1301 // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4) 1302 'bmp' => array( 1303 'pattern' => '^BM', 1304 'group' => 'graphic', 1305 'module' => 'bmp', 1306 'mime_type' => 'image/bmp', 1307 'fail_id3' => 'ERROR', 1308 'fail_ape' => 'ERROR', 1309 ), 1310 1311 // GIF - still image - Graphics Interchange Format 1312 'gif' => array( 1313 'pattern' => '^GIF', 1314 'group' => 'graphic', 1315 'module' => 'gif', 1316 'mime_type' => 'image/gif', 1317 'fail_id3' => 'ERROR', 1318 'fail_ape' => 'ERROR', 1319 ), 1320 1321 // JPEG - still image - Joint Photographic Experts Group (JPEG) 1322 'jpg' => array( 1323 'pattern' => '^\\xFF\\xD8\\xFF', 1324 'group' => 'graphic', 1325 'module' => 'jpg', 1326 'mime_type' => 'image/jpeg', 1327 'fail_id3' => 'ERROR', 1328 'fail_ape' => 'ERROR', 1329 ), 1330 1331 // PCD - still image - Kodak Photo CD 1332 'pcd' => array( 1333 'pattern' => '^.{2048}PCD_IPI\\x00', 1334 'group' => 'graphic', 1335 'module' => 'pcd', 1336 'mime_type' => 'image/x-photo-cd', 1337 'fail_id3' => 'ERROR', 1338 'fail_ape' => 'ERROR', 1339 ), 1340 1341 1342 // PNG - still image - Portable Network Graphics (PNG) 1343 'png' => array( 1344 'pattern' => '^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A', 1345 'group' => 'graphic', 1346 'module' => 'png', 1347 'mime_type' => 'image/png', 1348 'fail_id3' => 'ERROR', 1349 'fail_ape' => 'ERROR', 1350 ), 1351 1352 1353 // SVG - still image - Scalable Vector Graphics (SVG) 1354 'svg' => array( 1355 'pattern' => '(<!DOCTYPE svg PUBLIC |xmlns="http://www\\.w3\\.org/2000/svg")', 1356 'group' => 'graphic', 1357 'module' => 'svg', 1358 'mime_type' => 'image/svg+xml', 1359 'fail_id3' => 'ERROR', 1360 'fail_ape' => 'ERROR', 1361 ), 1362 1363 1364 // TIFF - still image - Tagged Information File Format (TIFF) 1365 'tiff' => array( 1366 'pattern' => '^(II\\x2A\\x00|MM\\x00\\x2A)', 1367 'group' => 'graphic', 1368 'module' => 'tiff', 1369 'mime_type' => 'image/tiff', 1370 'fail_id3' => 'ERROR', 1371 'fail_ape' => 'ERROR', 1372 ), 1373 1374 1375 // EFAX - still image - eFax (TIFF derivative) 1376 'efax' => array( 1377 'pattern' => '^\\xDC\\xFE', 1378 'group' => 'graphic', 1379 'module' => 'efax', 1380 'mime_type' => 'image/efax', 1381 'fail_id3' => 'ERROR', 1382 'fail_ape' => 'ERROR', 1383 ), 1384 1385 1386 // Data formats 1387 1388 // ISO - data - International Standards Organization (ISO) CD-ROM Image 1389 'iso' => array( 1390 'pattern' => '^.{32769}CD001', 1391 'group' => 'misc', 1392 'module' => 'iso', 1393 'mime_type' => 'application/octet-stream', 1394 'fail_id3' => 'ERROR', 1395 'fail_ape' => 'ERROR', 1396 'iconv_req' => false, 1397 ), 1398 1399 // HPK - data - HPK compressed data 1400 'hpk' => array( 1401 'pattern' => '^BPUL', 1402 'group' => 'archive', 1403 'module' => 'hpk', 1404 'mime_type' => 'application/octet-stream', 1405 'fail_id3' => 'ERROR', 1406 'fail_ape' => 'ERROR', 1407 ), 1408 1409 // RAR - data - RAR compressed data 1410 'rar' => array( 1411 'pattern' => '^Rar\\!', 1412 'group' => 'archive', 1413 'module' => 'rar', 1414 'mime_type' => 'application/vnd.rar', 1415 'fail_id3' => 'ERROR', 1416 'fail_ape' => 'ERROR', 1417 ), 1418 1419 // SZIP - audio/data - SZIP compressed data 1420 'szip' => array( 1421 'pattern' => '^SZ\\x0A\\x04', 1422 'group' => 'archive', 1423 'module' => 'szip', 1424 'mime_type' => 'application/octet-stream', 1425 'fail_id3' => 'ERROR', 1426 'fail_ape' => 'ERROR', 1427 ), 1428 1429 // TAR - data - TAR compressed data 1430 'tar' => array( 1431 'pattern' => '^.{100}[0-9\\x20]{7}\\x00[0-9\\x20]{7}\\x00[0-9\\x20]{7}\\x00[0-9\\x20\\x00]{12}[0-9\\x20\\x00]{12}', 1432 'group' => 'archive', 1433 'module' => 'tar', 1434 'mime_type' => 'application/x-tar', 1435 'fail_id3' => 'ERROR', 1436 'fail_ape' => 'ERROR', 1437 ), 1438 1439 // GZIP - data - GZIP compressed data 1440 'gz' => array( 1441 'pattern' => '^\\x1F\\x8B\\x08', 1442 'group' => 'archive', 1443 'module' => 'gzip', 1444 'mime_type' => 'application/gzip', 1445 'fail_id3' => 'ERROR', 1446 'fail_ape' => 'ERROR', 1447 ), 1448 1449 // ZIP - data - ZIP compressed data 1450 'zip' => array( 1451 'pattern' => '^PK\\x03\\x04', 1452 'group' => 'archive', 1453 'module' => 'zip', 1454 'mime_type' => 'application/zip', 1455 'fail_id3' => 'ERROR', 1456 'fail_ape' => 'ERROR', 1457 ), 1458 1459 // XZ - data - XZ compressed data 1460 'xz' => array( 1461 'pattern' => '^\\xFD7zXZ\\x00', 1462 'group' => 'archive', 1463 'module' => 'xz', 1464 'mime_type' => 'application/x-xz', 1465 'fail_id3' => 'ERROR', 1466 'fail_ape' => 'ERROR', 1467 ), 1468 1469 // XZ - data - XZ compressed data 1470 '7zip' => array( 1471 'pattern' => '^7z\\xBC\\xAF\\x27\\x1C', 1472 'group' => 'archive', 1473 'module' => '7zip', 1474 'mime_type' => 'application/x-7z-compressed', 1475 'fail_id3' => 'ERROR', 1476 'fail_ape' => 'ERROR', 1477 ), 1478 1479 1480 // Misc other formats 1481 1482 // GPX - data - GPS Exchange Format 1483 'gpx' => array ( 1484 'pattern' => '^<\\?xml [^>]+>[\s]*<gpx ', 1485 'group' => 'misc', 1486 'module' => 'gpx', 1487 'mime_type' => 'application/gpx+xml', 1488 'fail_id3' => 'ERROR', 1489 'fail_ape' => 'ERROR', 1490 ), 1491 1492 // PAR2 - data - Parity Volume Set Specification 2.0 1493 'par2' => array ( 1494 'pattern' => '^PAR2\\x00PKT', 1495 'group' => 'misc', 1496 'module' => 'par2', 1497 'mime_type' => 'application/octet-stream', 1498 'fail_id3' => 'ERROR', 1499 'fail_ape' => 'ERROR', 1500 ), 1501 1502 // PDF - data - Portable Document Format 1503 'pdf' => array( 1504 'pattern' => '^\\x25PDF', 1505 'group' => 'misc', 1506 'module' => 'pdf', 1507 'mime_type' => 'application/pdf', 1508 'fail_id3' => 'ERROR', 1509 'fail_ape' => 'ERROR', 1510 ), 1511 1512 // MSOFFICE - data - ZIP compressed data 1513 'msoffice' => array( 1514 'pattern' => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document 1515 'group' => 'misc', 1516 'module' => 'msoffice', 1517 'mime_type' => 'application/octet-stream', 1518 'fail_id3' => 'ERROR', 1519 'fail_ape' => 'ERROR', 1520 ), 1521 1522 // TORRENT - .torrent 1523 'torrent' => array( 1524 'pattern' => '^(d8\\:announce|d7\\:comment)', 1525 'group' => 'misc', 1526 'module' => 'torrent', 1527 'mime_type' => 'application/x-bittorrent', 1528 'fail_id3' => 'ERROR', 1529 'fail_ape' => 'ERROR', 1530 ), 1531 1532 // CUE - data - CUEsheet (index to single-file disc images) 1533 'cue' => array( 1534 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents 1535 'group' => 'misc', 1536 'module' => 'cue', 1537 'mime_type' => 'application/octet-stream', 1538 ), 1539 1540 ); 1541 } 1542 1543 return $format_info; 1544 } 1545 1546 /** 1547 * @param string $filedata 1548 * @param string $filename 1549 * 1550 * @return mixed|false 1551 */ 1552 public function GetFileFormat(&$filedata, $filename='') { 1553 // this function will determine the format of a file based on usually 1554 // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG, 1555 // and in the case of ISO CD image, 6 bytes offset 32kb from the start 1556 // of the file). 1557 1558 // Identify file format - loop through $format_info and detect with reg expr 1559 foreach ($this->GetFileFormatArray() as $format_name => $info) { 1560 // The /s switch on preg_match() forces preg_match() NOT to treat 1561 // newline (0x0A) characters as special chars but do a binary match 1562 if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) { 1563 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1564 return $info; 1565 } 1566 } 1567 1568 1569 if (preg_match('#\\.mp[123a]$#i', $filename)) { 1570 // Too many mp3 encoders on the market put garbage in front of mpeg files 1571 // use assume format on these if format detection failed 1572 $GetFileFormatArray = $this->GetFileFormatArray(); 1573 $info = $GetFileFormatArray['mp3']; 1574 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1575 return $info; 1576 } elseif (preg_match('#\\.mp[cp\\+]$#i', $filename) && preg_match('#[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]#s', $filedata)) { 1577 // old-format (SV4-SV6) Musepack header that has a very loose pattern match and could falsely match other data (e.g. corrupt mp3) 1578 // only enable this pattern check if the filename ends in .mpc/mpp/mp+ 1579 $GetFileFormatArray = $this->GetFileFormatArray(); 1580 $info = $GetFileFormatArray['mpc']; 1581 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1582 return $info; 1583 } elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) { 1584 // there's not really a useful consistent "magic" at the beginning of .cue files to identify them 1585 // so until I think of something better, just go by filename if all other format checks fail 1586 // and verify there's at least one instance of "TRACK xx AUDIO" in the file 1587 $GetFileFormatArray = $this->GetFileFormatArray(); 1588 $info = $GetFileFormatArray['cue']; 1589 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1590 return $info; 1591 } 1592 1593 return false; 1594 } 1595 1596 /** 1597 * Converts array to $encoding charset from $this->encoding. 1598 * 1599 * @param array $array 1600 * @param string $encoding 1601 */ 1602 public function CharConvert(&$array, $encoding) { 1603 1604 // identical encoding - end here 1605 if ($encoding == $this->encoding) { 1606 return; 1607 } 1608 1609 // loop thru array 1610 foreach ($array as $key => $value) { 1611 1612 // go recursive 1613 if (is_array($value)) { 1614 $this->CharConvert($array[$key], $encoding); 1615 } 1616 1617 // convert string 1618 elseif (is_string($value)) { 1619 $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value)); 1620 } 1621 } 1622 } 1623 1624 /** 1625 * @return bool 1626 */ 1627 public function HandleAllTags() { 1628 1629 // key name => array (tag name, character encoding) 1630 static $tags; 1631 if (empty($tags)) { 1632 $tags = array( 1633 'asf' => array('asf' , 'UTF-16LE'), 1634 'midi' => array('midi' , 'ISO-8859-1'), 1635 'nsv' => array('nsv' , 'ISO-8859-1'), 1636 'ogg' => array('vorbiscomment' , 'UTF-8'), 1637 'png' => array('png' , 'UTF-8'), 1638 'tiff' => array('tiff' , 'ISO-8859-1'), 1639 'quicktime' => array('quicktime' , 'UTF-8'), 1640 'real' => array('real' , 'ISO-8859-1'), 1641 'vqf' => array('vqf' , 'ISO-8859-1'), 1642 'zip' => array('zip' , 'ISO-8859-1'), 1643 'riff' => array('riff' , 'ISO-8859-1'), 1644 'lyrics3' => array('lyrics3' , 'ISO-8859-1'), 1645 'id3v1' => array('id3v1' , $this->encoding_id3v1), 1646 'id3v2' => array('id3v2' , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8 1647 'ape' => array('ape' , 'UTF-8'), 1648 'cue' => array('cue' , 'ISO-8859-1'), 1649 'matroska' => array('matroska' , 'UTF-8'), 1650 'flac' => array('vorbiscomment' , 'UTF-8'), 1651 'divxtag' => array('divx' , 'ISO-8859-1'), 1652 'iptc' => array('iptc' , 'ISO-8859-1'), 1653 'dsdiff' => array('dsdiff' , 'ISO-8859-1'), 1654 ); 1655 } 1656 1657 // loop through comments array 1658 foreach ($tags as $comment_name => $tagname_encoding_array) { 1659 list($tag_name, $encoding) = $tagname_encoding_array; 1660 1661 // fill in default encoding type if not already present 1662 if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) { 1663 $this->info[$comment_name]['encoding'] = $encoding; 1664 } 1665 1666 // copy comments if key name set 1667 if (!empty($this->info[$comment_name]['comments'])) { 1668 foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { 1669 foreach ($valuearray as $key => $value) { 1670 if (is_string($value)) { 1671 $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! 1672 } 1673 if (isset($value) && $value !== "") { 1674 if (!is_numeric($key)) { 1675 $this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value; 1676 } else { 1677 $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; 1678 } 1679 } 1680 } 1681 if ($tag_key == 'picture') { 1682 // pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere 1683 unset($this->info[$comment_name]['comments'][$tag_key]); 1684 } 1685 } 1686 1687 if (!isset($this->info['tags'][$tag_name])) { 1688 // comments are set but contain nothing but empty strings, so skip 1689 continue; 1690 } 1691 1692 $this->CharConvert($this->info['tags'][$tag_name], $this->info[$comment_name]['encoding']); // only copy gets converted! 1693 1694 if ($this->option_tags_html) { 1695 foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { 1696 if ($tag_key == 'picture') { 1697 // Do not to try to convert binary picture data to HTML 1698 // https://github.com/JamesHeinrich/getID3/issues/178 1699 continue; 1700 } 1701 $this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']); 1702 } 1703 } 1704 1705 } 1706 1707 } 1708 1709 // pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere 1710 if (!empty($this->info['tags'])) { 1711 $unset_keys = array('tags', 'tags_html'); 1712 foreach ($this->info['tags'] as $tagtype => $tagarray) { 1713 foreach ($tagarray as $tagname => $tagdata) { 1714 if ($tagname == 'picture') { 1715 foreach ($tagdata as $key => $tagarray) { 1716 $this->info['comments']['picture'][] = $tagarray; 1717 if (isset($tagarray['data']) && isset($tagarray['image_mime'])) { 1718 if (isset($this->info['tags'][$tagtype][$tagname][$key])) { 1719 unset($this->info['tags'][$tagtype][$tagname][$key]); 1720 } 1721 if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) { 1722 unset($this->info['tags_html'][$tagtype][$tagname][$key]); 1723 } 1724 } 1725 } 1726 } 1727 } 1728 foreach ($unset_keys as $unset_key) { 1729 // remove possible empty keys from (e.g. [tags][id3v2][picture]) 1730 if (empty($this->info[$unset_key][$tagtype]['picture'])) { 1731 unset($this->info[$unset_key][$tagtype]['picture']); 1732 } 1733 if (empty($this->info[$unset_key][$tagtype])) { 1734 unset($this->info[$unset_key][$tagtype]); 1735 } 1736 if (empty($this->info[$unset_key])) { 1737 unset($this->info[$unset_key]); 1738 } 1739 } 1740 // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture]) 1741 if (isset($this->info[$tagtype]['comments']['picture'])) { 1742 unset($this->info[$tagtype]['comments']['picture']); 1743 } 1744 if (empty($this->info[$tagtype]['comments'])) { 1745 unset($this->info[$tagtype]['comments']); 1746 } 1747 if (empty($this->info[$tagtype])) { 1748 unset($this->info[$tagtype]); 1749 } 1750 } 1751 } 1752 return true; 1753 } 1754 1755 /** 1756 * Calls getid3_lib::CopyTagsToComments() but passes in the option_tags_html setting from this instance of getID3 1757 * 1758 * @param array $ThisFileInfo 1759 * 1760 * @return bool 1761 */ 1762 public function CopyTagsToComments(&$ThisFileInfo) { 1763 return getid3_lib::CopyTagsToComments($ThisFileInfo, $this->option_tags_html); 1764 } 1765 1766 /** 1767 * @param string $algorithm 1768 * 1769 * @return array|bool 1770 */ 1771 public function getHashdata($algorithm) { 1772 switch ($algorithm) { 1773 case 'md5': 1774 case 'sha1': 1775 break; 1776 1777 default: 1778 return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()'); 1779 } 1780 1781 if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) { 1782 1783 // We cannot get an identical md5_data value for Ogg files where the comments 1784 // span more than 1 Ogg page (compared to the same audio data with smaller 1785 // comments) using the normal getID3() method of MD5'ing the data between the 1786 // end of the comments and the end of the file (minus any trailing tags), 1787 // because the page sequence numbers of the pages that the audio data is on 1788 // do not match. Under normal circumstances, where comments are smaller than 1789 // the nominal 4-8kB page size, then this is not a problem, but if there are 1790 // very large comments, the only way around it is to strip off the comment 1791 // tags with vorbiscomment and MD5 that file. 1792 // This procedure must be applied to ALL Ogg files, not just the ones with 1793 // comments larger than 1 page, because the below method simply MD5's the 1794 // whole file with the comments stripped, not just the portion after the 1795 // comments block (which is the standard getID3() method. 1796 1797 // The above-mentioned problem of comments spanning multiple pages and changing 1798 // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but 1799 // currently vorbiscomment only works on OggVorbis files. 1800 1801 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { 1802 1803 $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'); 1804 $this->info[$algorithm.'_data'] = false; 1805 1806 } else { 1807 1808 // Prevent user from aborting script 1809 $old_abort = ignore_user_abort(true); 1810 1811 // Create empty file 1812 $empty = tempnam(GETID3_TEMP_DIR, 'getID3'); 1813 touch($empty); 1814 1815 // Use vorbiscomment to make temp file without comments 1816 $temp = tempnam(GETID3_TEMP_DIR, 'getID3'); 1817 $file = $this->info['filenamepath']; 1818 1819 if (GETID3_OS_ISWINDOWS) { 1820 1821 if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { 1822 1823 $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"'; 1824 $VorbisCommentError = shell_exec($commandline); 1825 1826 } else { 1827 1828 $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; 1829 1830 } 1831 1832 } else { 1833 1834 $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1'; 1835 $VorbisCommentError = shell_exec($commandline); 1836 1837 } 1838 1839 if (!empty($VorbisCommentError)) { 1840 1841 $this->warning('Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError); 1842 $this->info[$algorithm.'_data'] = false; 1843 1844 } else { 1845 1846 // Get hash of newly created file 1847 switch ($algorithm) { 1848 case 'md5': 1849 $this->info[$algorithm.'_data'] = md5_file($temp); 1850 break; 1851 1852 case 'sha1': 1853 $this->info[$algorithm.'_data'] = sha1_file($temp); 1854 break; 1855 } 1856 } 1857 1858 // Clean up 1859 unlink($empty); 1860 unlink($temp); 1861 1862 // Reset abort setting 1863 ignore_user_abort($old_abort); 1864 1865 } 1866 1867 } else { 1868 1869 if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) { 1870 1871 // get hash from part of file 1872 $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm); 1873 1874 } else { 1875 1876 // get hash from whole file 1877 switch ($algorithm) { 1878 case 'md5': 1879 $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']); 1880 break; 1881 1882 case 'sha1': 1883 $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']); 1884 break; 1885 } 1886 } 1887 1888 } 1889 return true; 1890 } 1891 1892 public function ChannelsBitratePlaytimeCalculations() { 1893 1894 // set channelmode on audio 1895 if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) { 1896 // ignore 1897 } elseif ($this->info['audio']['channels'] == 1) { 1898 $this->info['audio']['channelmode'] = 'mono'; 1899 } elseif ($this->info['audio']['channels'] == 2) { 1900 $this->info['audio']['channelmode'] = 'stereo'; 1901 } 1902 1903 // Calculate combined bitrate - audio + video 1904 $CombinedBitrate = 0; 1905 $CombinedBitrate += (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] != 'free') ? $this->info['audio']['bitrate'] : 0); 1906 $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0); 1907 if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) { 1908 $this->info['bitrate'] = $CombinedBitrate; 1909 } 1910 //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) { 1911 // // for example, VBR MPEG video files cannot determine video bitrate: 1912 // // should not set overall bitrate and playtime from audio bitrate only 1913 // unset($this->info['bitrate']); 1914 //} 1915 1916 // video bitrate undetermined, but calculable 1917 if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) { 1918 // if video bitrate not set 1919 if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) { 1920 // AND if audio bitrate is set to same as overall bitrate 1921 if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) { 1922 // AND if playtime is set 1923 if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) { 1924 // AND if AV data offset start/end is known 1925 // THEN we can calculate the video bitrate 1926 $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']); 1927 $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate']; 1928 } 1929 } 1930 } 1931 } 1932 1933 if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) { 1934 $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate']; 1935 } 1936 1937 if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) { 1938 $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']; 1939 } 1940 if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) { 1941 if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) { 1942 // audio only 1943 $this->info['audio']['bitrate'] = $this->info['bitrate']; 1944 } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) { 1945 // video only 1946 $this->info['video']['bitrate'] = $this->info['bitrate']; 1947 } 1948 } 1949 1950 // Set playtime string 1951 if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) { 1952 $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']); 1953 } 1954 } 1955 1956 /** 1957 * @return bool 1958 */ 1959 public function CalculateCompressionRatioVideo() { 1960 if (empty($this->info['video'])) { 1961 return false; 1962 } 1963 if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) { 1964 return false; 1965 } 1966 if (empty($this->info['video']['bits_per_sample'])) { 1967 return false; 1968 } 1969 1970 switch ($this->info['video']['dataformat']) { 1971 case 'bmp': 1972 case 'gif': 1973 case 'jpeg': 1974 case 'jpg': 1975 case 'png': 1976 case 'tiff': 1977 $FrameRate = 1; 1978 $PlaytimeSeconds = 1; 1979 $BitrateCompressed = $this->info['filesize'] * 8; 1980 break; 1981 1982 default: 1983 if (!empty($this->info['video']['frame_rate'])) { 1984 $FrameRate = $this->info['video']['frame_rate']; 1985 } else { 1986 return false; 1987 } 1988 if (!empty($this->info['playtime_seconds'])) { 1989 $PlaytimeSeconds = $this->info['playtime_seconds']; 1990 } else { 1991 return false; 1992 } 1993 if (!empty($this->info['video']['bitrate'])) { 1994 $BitrateCompressed = $this->info['video']['bitrate']; 1995 } else { 1996 return false; 1997 } 1998 break; 1999 } 2000 $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate; 2001 2002 $this->info['video']['compression_ratio'] = getid3_lib::SafeDiv($BitrateCompressed, $BitrateUncompressed, 1); 2003 return true; 2004 } 2005 2006 /** 2007 * @return bool 2008 */ 2009 public function CalculateCompressionRatioAudio() { 2010 if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) { 2011 return false; 2012 } 2013 if ($this->info['audio']['bitrate'] != 'free') { 2014 $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16)); 2015 } 2016 2017 if (!empty($this->info['audio']['streams'])) { 2018 foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) { 2019 if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) { 2020 $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16)); 2021 } 2022 } 2023 } 2024 return true; 2025 } 2026 2027 /** 2028 * @return bool 2029 */ 2030 public function CalculateReplayGain() { 2031 if (isset($this->info['replay_gain'])) { 2032 if (!isset($this->info['replay_gain']['reference_volume'])) { 2033 $this->info['replay_gain']['reference_volume'] = 89.0; 2034 } 2035 if (isset($this->info['replay_gain']['track']['adjustment'])) { 2036 $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; 2037 } 2038 if (isset($this->info['replay_gain']['album']['adjustment'])) { 2039 $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment']; 2040 } 2041 2042 if (isset($this->info['replay_gain']['track']['peak'])) { 2043 $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']); 2044 } 2045 if (isset($this->info['replay_gain']['album']['peak'])) { 2046 $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']); 2047 } 2048 } 2049 return true; 2050 } 2051 2052 /** 2053 * @return bool 2054 */ 2055 public function ProcessAudioStreams() { 2056 if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { 2057 if (!isset($this->info['audio']['streams'])) { 2058 foreach ($this->info['audio'] as $key => $value) { 2059 if ($key != 'streams') { 2060 $this->info['audio']['streams'][0][$key] = $value; 2061 } 2062 } 2063 } 2064 } 2065 return true; 2066 } 2067 2068 /** 2069 * @return string|bool 2070 */ 2071 public function getid3_tempnam() { 2072 return tempnam($this->tempdir, 'gI3'); 2073 } 2074 2075 /** 2076 * @param string $name 2077 * 2078 * @return bool 2079 * 2080 * @throws getid3_exception 2081 */ 2082 public function include_module($name) { 2083 //if (!file_exists($this->include_path.'module.'.$name.'.php')) { 2084 if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { 2085 throw new getid3_exception('Required module.'.$name.'.php is missing.'); 2086 } 2087 include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php'); 2088 return true; 2089 } 2090 2091 /** 2092 * @param string $filename 2093 * 2094 * @return bool 2095 */ 2096 public static function is_writable ($filename) { 2097 $ret = is_writable($filename); 2098 if (!$ret) { 2099 $perms = fileperms($filename); 2100 $ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002); 2101 } 2102 return $ret; 2103 } 2104 2105 } 2106 2107 2108 abstract class getid3_handler 2109 { 2110 2111 /** 2112 * @var getID3 2113 */ 2114 protected $getid3; // pointer 2115 2116 /** 2117 * Analyzing filepointer or string. 2118 * 2119 * @var bool 2120 */ 2121 protected $data_string_flag = false; 2122 2123 /** 2124 * String to analyze. 2125 * 2126 * @var string 2127 */ 2128 protected $data_string = ''; 2129 2130 /** 2131 * Seek position in string. 2132 * 2133 * @var int 2134 */ 2135 protected $data_string_position = 0; 2136 2137 /** 2138 * String length. 2139 * 2140 * @var int 2141 */ 2142 protected $data_string_length = 0; 2143 2144 /** 2145 * @var string 2146 */ 2147 private $dependency_to; 2148 2149 /** 2150 * getid3_handler constructor. 2151 * 2152 * @param getID3 $getid3 2153 * @param string $call_module 2154 */ 2155 public function __construct(getID3 $getid3, $call_module=null) { 2156 $this->getid3 = $getid3; 2157 2158 if ($call_module) { 2159 $this->dependency_to = str_replace('getid3_', '', $call_module); 2160 } 2161 } 2162 2163 /** 2164 * Analyze from file pointer. 2165 * 2166 * @return bool 2167 */ 2168 abstract public function Analyze(); 2169 2170 /** 2171 * Analyze from string instead. 2172 * 2173 * @param string $string 2174 */ 2175 public function AnalyzeString($string) { 2176 // Enter string mode 2177 $this->setStringMode($string); 2178 2179 // Save info 2180 $saved_avdataoffset = $this->getid3->info['avdataoffset']; 2181 $saved_avdataend = $this->getid3->info['avdataend']; 2182 $saved_filesize = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call 2183 2184 // Reset some info 2185 $this->getid3->info['avdataoffset'] = 0; 2186 $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = $this->data_string_length; 2187 2188 // Analyze 2189 $this->Analyze(); 2190 2191 // Restore some info 2192 $this->getid3->info['avdataoffset'] = $saved_avdataoffset; 2193 $this->getid3->info['avdataend'] = $saved_avdataend; 2194 $this->getid3->info['filesize'] = $saved_filesize; 2195 2196 // Exit string mode 2197 $this->data_string_flag = false; 2198 } 2199 2200 /** 2201 * @param string $string 2202 */ 2203 public function setStringMode($string) { 2204 $this->data_string_flag = true; 2205 $this->data_string = $string; 2206 $this->data_string_length = strlen($string); 2207 } 2208 2209 /** 2210 * @phpstan-impure 2211 * 2212 * @return int|bool 2213 */ 2214 protected function ftell() { 2215 if ($this->data_string_flag) { 2216 return $this->data_string_position; 2217 } 2218 return ftell($this->getid3->fp); 2219 } 2220 2221 /** 2222 * @param int $bytes 2223 * 2224 * @phpstan-impure 2225 * 2226 * @return string|false 2227 * 2228 * @throws getid3_exception 2229 */ 2230 protected function fread($bytes) { 2231 if ($this->data_string_flag) { 2232 $this->data_string_position += $bytes; 2233 return substr($this->data_string, $this->data_string_position - $bytes, $bytes); 2234 } 2235 if ($bytes == 0) { 2236 return ''; 2237 } elseif ($bytes < 0) { 2238 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().')', 10); 2239 } 2240 $pos = $this->ftell() + $bytes; 2241 if (!getid3_lib::intValueSupported($pos)) { 2242 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10); 2243 } 2244 2245 //return fread($this->getid3->fp, $bytes); 2246 /* 2247 * https://www.getid3.org/phpBB3/viewtopic.php?t=1930 2248 * "I found out that the root cause for the problem was how getID3 uses the PHP system function fread(). 2249 * It seems to assume that fread() would always return as many bytes as were requested. 2250 * However, according the PHP manual (http://php.net/manual/en/function.fread.php), this is the case only with regular local files, but not e.g. with Linux pipes. 2251 * The call may return only part of the requested data and a new call is needed to get more." 2252 */ 2253 $contents = ''; 2254 do { 2255 //if (($this->getid3->memory_limit > 0) && ($bytes > $this->getid3->memory_limit)) { 2256 if (($this->getid3->memory_limit > 0) && (($bytes / $this->getid3->memory_limit) > 0.99)) { // enable a more-fuzzy match to prevent close misses generating errors like "PHP Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 33554464 bytes)" 2257 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') that is more than available PHP memory ('.$this->getid3->memory_limit.')', 10); 2258 } 2259 $part = fread($this->getid3->fp, $bytes); 2260 $partLength = strlen($part); 2261 $bytes -= $partLength; 2262 $contents .= $part; 2263 } while (($bytes > 0) && ($partLength > 0)); 2264 return $contents; 2265 } 2266 2267 /** 2268 * @param int $bytes 2269 * @param int $whence 2270 * 2271 * @phpstan-impure 2272 * 2273 * @return int 2274 * 2275 * @throws getid3_exception 2276 */ 2277 protected function fseek($bytes, $whence=SEEK_SET) { 2278 if ($this->data_string_flag) { 2279 switch ($whence) { 2280 case SEEK_SET: 2281 $this->data_string_position = $bytes; 2282 break; 2283 2284 case SEEK_CUR: 2285 $this->data_string_position += $bytes; 2286 break; 2287 2288 case SEEK_END: 2289 $this->data_string_position = $this->data_string_length + $bytes; 2290 break; 2291 } 2292 return 0; // fseek returns 0 on success 2293 } 2294 2295 $pos = $bytes; 2296 if ($whence == SEEK_CUR) { 2297 $pos = $this->ftell() + $bytes; 2298 } elseif ($whence == SEEK_END) { 2299 $pos = $this->getid3->info['filesize'] + $bytes; 2300 } 2301 if (!getid3_lib::intValueSupported($pos)) { 2302 throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10); 2303 } 2304 2305 // https://github.com/JamesHeinrich/getID3/issues/327 2306 $result = fseek($this->getid3->fp, $bytes, $whence); 2307 if ($result !== 0) { // fseek returns 0 on success 2308 throw new getid3_exception('cannot fseek('.$pos.'). resource/stream does not appear to support seeking', 10); 2309 } 2310 return $result; 2311 } 2312 2313 /** 2314 * @phpstan-impure 2315 * 2316 * @return string|false 2317 * 2318 * @throws getid3_exception 2319 */ 2320 protected function fgets() { 2321 // must be able to handle CR/LF/CRLF but not read more than one lineend 2322 $buffer = ''; // final string we will return 2323 $prevchar = ''; // save previously-read character for end-of-line checking 2324 if ($this->data_string_flag) { 2325 while (true) { 2326 $thischar = substr($this->data_string, $this->data_string_position++, 1); 2327 if (($prevchar == "\r") && ($thischar != "\n")) { 2328 // read one byte too many, back up 2329 $this->data_string_position--; 2330 break; 2331 } 2332 $buffer .= $thischar; 2333 if ($thischar == "\n") { 2334 break; 2335 } 2336 if ($this->data_string_position >= $this->data_string_length) { 2337 // EOF 2338 break; 2339 } 2340 $prevchar = $thischar; 2341 } 2342 2343 } else { 2344 2345 // Ideally we would just use PHP's fgets() function, however... 2346 // it does not behave consistently with regards to mixed line endings, may be system-dependent 2347 // and breaks entirely when given a file with mixed \r vs \n vs \r\n line endings (e.g. some PDFs) 2348 //return fgets($this->getid3->fp); 2349 while (true) { 2350 $thischar = fgetc($this->getid3->fp); 2351 if (($prevchar == "\r") && ($thischar != "\n")) { 2352 // read one byte too many, back up 2353 fseek($this->getid3->fp, -1, SEEK_CUR); 2354 break; 2355 } 2356 $buffer .= $thischar; 2357 if ($thischar == "\n") { 2358 break; 2359 } 2360 if (feof($this->getid3->fp)) { 2361 break; 2362 } 2363 $prevchar = $thischar; 2364 } 2365 2366 } 2367 return $buffer; 2368 } 2369 2370 /** 2371 * @phpstan-impure 2372 * 2373 * @return bool 2374 */ 2375 protected function feof() { 2376 if ($this->data_string_flag) { 2377 return $this->data_string_position >= $this->data_string_length; 2378 } 2379 return feof($this->getid3->fp); 2380 } 2381 2382 /** 2383 * @param string $module 2384 * 2385 * @return bool 2386 */ 2387 final protected function isDependencyFor($module) { 2388 return $this->dependency_to == $module; 2389 } 2390 2391 /** 2392 * @param string $text 2393 * 2394 * @return bool 2395 */ 2396 protected function error($text) { 2397 $this->getid3->info['error'][] = $text; 2398 2399 return false; 2400 } 2401 2402 /** 2403 * @param string $text 2404 * 2405 * @return bool 2406 */ 2407 protected function warning($text) { 2408 return $this->getid3->warning($text); 2409 } 2410 2411 /** 2412 * @param string $text 2413 */ 2414 protected function notice($text) { 2415 // does nothing for now 2416 } 2417 2418 /** 2419 * @param string $name 2420 * @param int $offset 2421 * @param int $length 2422 * @param string $image_mime 2423 * 2424 * @return string|null 2425 * 2426 * @throws Exception 2427 * @throws getid3_exception 2428 */ 2429 public function saveAttachment($name, $offset, $length, $image_mime=null) { 2430 $fp_dest = null; 2431 $dest = null; 2432 try { 2433 2434 // do not extract at all 2435 if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) { 2436 2437 $attachment = null; // do not set any 2438 2439 // extract to return array 2440 } elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { 2441 2442 $this->fseek($offset); 2443 $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory 2444 if ($attachment === false || strlen($attachment) != $length) { 2445 throw new Exception('failed to read attachment data'); 2446 } 2447 2448 // assume directory path is given 2449 } else { 2450 2451 // set up destination path 2452 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); 2453 if (!is_dir($dir) || !getID3::is_writable($dir)) { // check supplied directory 2454 throw new Exception('supplied path ('.$dir.') does not exist, or is not writable'); 2455 } 2456 $dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : ''); 2457 2458 // create dest file 2459 if (($fp_dest = fopen($dest, 'wb')) == false) { 2460 throw new Exception('failed to create file '.$dest); 2461 } 2462 2463 // copy data 2464 $this->fseek($offset); 2465 $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size()); 2466 $bytesleft = $length; 2467 while ($bytesleft > 0) { 2468 if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) { 2469 throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space'); 2470 } 2471 $bytesleft -= $byteswritten; 2472 } 2473 2474 fclose($fp_dest); 2475 $attachment = $dest; 2476 2477 } 2478 2479 } catch (Exception $e) { 2480 2481 // close and remove dest file if created 2482 if (isset($fp_dest) && is_resource($fp_dest)) { 2483 fclose($fp_dest); 2484 } 2485 2486 if (isset($dest) && file_exists($dest)) { 2487 unlink($dest); 2488 } 2489 2490 // do not set any is case of error 2491 $attachment = null; 2492 $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage()); 2493 2494 } 2495 2496 // seek to the end of attachment 2497 $this->fseek($offset + $length); 2498 2499 return $attachment; 2500 } 2501 2502 } 2503 2504 2505 class getid3_exception extends Exception 2506 { 2507 public $message; 2508 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed May 6 08:20:15 2026 | Cross-referenced by PHPXref |