[ 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.23-202310190849'; 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 = $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 = $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()) { // @phpstan-ignore-line 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 (!is_array($optArray) || 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 } 684 } 685 if (isset($this->info['id3v2']['tag_offset_start'])) { 686 $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']); 687 } 688 foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { 689 if (isset($this->info[$tag_key]['tag_offset_start'])) { 690 $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']); 691 } 692 } 693 694 // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier 695 if (!$this->option_tag_id3v2) { 696 fseek($this->fp, 0); 697 $header = fread($this->fp, 10); 698 if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { 699 $this->info['id3v2']['header'] = true; 700 $this->info['id3v2']['majorversion'] = ord($header[3]); 701 $this->info['id3v2']['minorversion'] = ord($header[4]); 702 $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 703 } 704 } 705 706 // read 32 kb file data 707 fseek($this->fp, $this->info['avdataoffset']); 708 $formattest = fread($this->fp, 32774); 709 710 // determine format 711 $determined_format = $this->GetFileFormat($formattest, ($original_filename ? $original_filename : $filename)); 712 713 // unable to determine file format 714 if (!$determined_format) { 715 fclose($this->fp); 716 return $this->error('unable to determine file format'); 717 } 718 719 // check for illegal ID3 tags 720 if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { 721 if ($determined_format['fail_id3'] === 'ERROR') { 722 fclose($this->fp); 723 return $this->error('ID3 tags not allowed on this file type.'); 724 } elseif ($determined_format['fail_id3'] === 'WARNING') { 725 $this->warning('ID3 tags not allowed on this file type.'); 726 } 727 } 728 729 // check for illegal APE tags 730 if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { 731 if ($determined_format['fail_ape'] === 'ERROR') { 732 fclose($this->fp); 733 return $this->error('APE tags not allowed on this file type.'); 734 } elseif ($determined_format['fail_ape'] === 'WARNING') { 735 $this->warning('APE tags not allowed on this file type.'); 736 } 737 } 738 739 // set mime type 740 $this->info['mime_type'] = $determined_format['mime_type']; 741 742 // supported format signature pattern detected, but module deleted 743 if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { 744 fclose($this->fp); 745 return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); 746 } 747 748 // module requires mb_convert_encoding/iconv support 749 // Check encoding/iconv support 750 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'))) { 751 $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. '; 752 if (GETID3_OS_ISWINDOWS) { 753 $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'; 754 } else { 755 $errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch'; 756 } 757 return $this->error($errormessage); 758 } 759 760 // include module 761 include_once(GETID3_INCLUDEPATH.$determined_format['include']); 762 763 // instantiate module class 764 $class_name = 'getid3_'.$determined_format['module']; 765 if (!class_exists($class_name)) { 766 return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); 767 } 768 $class = new $class_name($this); 769 770 // set module-specific options 771 foreach (get_object_vars($this) as $getid3_object_vars_key => $getid3_object_vars_value) { 772 if (preg_match('#^options_([^_]+)_([^_]+)_(.+)$#i', $getid3_object_vars_key, $matches)) { 773 list($dummy, $GOVgroup, $GOVmodule, $GOVsetting) = $matches; 774 $GOVgroup = (($GOVgroup == 'audiovideo') ? 'audio-video' : $GOVgroup); // variable names can only contain 0-9a-z_ so standardize here 775 if (($GOVgroup == $determined_format['group']) && ($GOVmodule == $determined_format['module'])) { 776 $class->$GOVsetting = $getid3_object_vars_value; 777 } 778 } 779 } 780 781 $class->Analyze(); 782 unset($class); 783 784 // close file 785 fclose($this->fp); 786 787 // process all tags - copy to 'tags' and convert charsets 788 if ($this->option_tags_process) { 789 $this->HandleAllTags(); 790 } 791 792 // perform more calculations 793 if ($this->option_extra_info) { 794 $this->ChannelsBitratePlaytimeCalculations(); 795 $this->CalculateCompressionRatioVideo(); 796 $this->CalculateCompressionRatioAudio(); 797 $this->CalculateReplayGain(); 798 $this->ProcessAudioStreams(); 799 } 800 801 // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags 802 if ($this->option_md5_data) { 803 // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too 804 if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { 805 $this->getHashdata('md5'); 806 } 807 } 808 809 // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags 810 if ($this->option_sha1_data) { 811 $this->getHashdata('sha1'); 812 } 813 814 // remove undesired keys 815 $this->CleanUp(); 816 817 } catch (Exception $e) { 818 $this->error('Caught exception: '.$e->getMessage()); 819 } 820 821 // return info array 822 return $this->info; 823 } 824 825 826 /** 827 * Error handling. 828 * 829 * @param string $message 830 * 831 * @return array 832 */ 833 public function error($message) { 834 $this->CleanUp(); 835 if (!isset($this->info['error'])) { 836 $this->info['error'] = array(); 837 } 838 $this->info['error'][] = $message; 839 return $this->info; 840 } 841 842 843 /** 844 * Warning handling. 845 * 846 * @param string $message 847 * 848 * @return bool 849 */ 850 public function warning($message) { 851 $this->info['warning'][] = $message; 852 return true; 853 } 854 855 856 /** 857 * @return bool 858 */ 859 private function CleanUp() { 860 861 // remove possible empty keys 862 $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate'); 863 foreach ($AVpossibleEmptyKeys as $dummy => $key) { 864 if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) { 865 unset($this->info['audio'][$key]); 866 } 867 if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) { 868 unset($this->info['video'][$key]); 869 } 870 } 871 872 // remove empty root keys 873 if (!empty($this->info)) { 874 foreach ($this->info as $key => $value) { 875 if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) { 876 unset($this->info[$key]); 877 } 878 } 879 } 880 881 // remove meaningless entries from unknown-format files 882 if (empty($this->info['fileformat'])) { 883 if (isset($this->info['avdataoffset'])) { 884 unset($this->info['avdataoffset']); 885 } 886 if (isset($this->info['avdataend'])) { 887 unset($this->info['avdataend']); 888 } 889 } 890 891 // remove possible duplicated identical entries 892 if (!empty($this->info['error'])) { 893 $this->info['error'] = array_values(array_unique($this->info['error'])); 894 } 895 if (!empty($this->info['warning'])) { 896 $this->info['warning'] = array_values(array_unique($this->info['warning'])); 897 } 898 899 // remove "global variable" type keys 900 unset($this->info['php_memory_limit']); 901 902 return true; 903 } 904 905 /** 906 * Return array containing information about all supported formats. 907 * 908 * @return array 909 */ 910 public function GetFileFormatArray() { 911 static $format_info = array(); 912 if (empty($format_info)) { 913 $format_info = array( 914 915 // Audio formats 916 917 // AC-3 - audio - Dolby AC-3 / Dolby Digital 918 'ac3' => array( 919 'pattern' => '^\\x0B\\x77', 920 'group' => 'audio', 921 'module' => 'ac3', 922 'mime_type' => 'audio/ac3', 923 ), 924 925 // AAC - audio - Advanced Audio Coding (AAC) - ADIF format 926 'adif' => array( 927 'pattern' => '^ADIF', 928 'group' => 'audio', 929 'module' => 'aac', 930 'mime_type' => 'audio/aac', 931 'fail_ape' => 'WARNING', 932 ), 933 934 /* 935 // AA - audio - Audible Audiobook 936 'aa' => array( 937 'pattern' => '^.{4}\\x57\\x90\\x75\\x36', 938 'group' => 'audio', 939 'module' => 'aa', 940 'mime_type' => 'audio/audible', 941 ), 942 */ 943 // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) 944 'adts' => array( 945 'pattern' => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]', 946 'group' => 'audio', 947 'module' => 'aac', 948 'mime_type' => 'audio/aac', 949 'fail_ape' => 'WARNING', 950 ), 951 952 953 // AU - audio - NeXT/Sun AUdio (AU) 954 'au' => array( 955 'pattern' => '^\\.snd', 956 'group' => 'audio', 957 'module' => 'au', 958 'mime_type' => 'audio/basic', 959 ), 960 961 // AMR - audio - Adaptive Multi Rate 962 'amr' => array( 963 'pattern' => '^\\x23\\x21AMR\\x0A', // #!AMR[0A] 964 'group' => 'audio', 965 'module' => 'amr', 966 'mime_type' => 'audio/amr', 967 ), 968 969 // AVR - audio - Audio Visual Research 970 'avr' => array( 971 'pattern' => '^2BIT', 972 'group' => 'audio', 973 'module' => 'avr', 974 'mime_type' => 'application/octet-stream', 975 ), 976 977 // BONK - audio - Bonk v0.9+ 978 'bonk' => array( 979 'pattern' => '^\\x00(BONK|INFO|META| ID3)', 980 'group' => 'audio', 981 'module' => 'bonk', 982 'mime_type' => 'audio/xmms-bonk', 983 ), 984 985 // DSF - audio - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital 986 'dsf' => array( 987 'pattern' => '^DSD ', // including trailing space: 44 53 44 20 988 'group' => 'audio', 989 'module' => 'dsf', 990 'mime_type' => 'audio/dsd', 991 ), 992 993 // DSS - audio - Digital Speech Standard 994 'dss' => array( 995 'pattern' => '^[\\x02-\\x08]ds[s2]', 996 'group' => 'audio', 997 'module' => 'dss', 998 'mime_type' => 'application/octet-stream', 999 ), 1000 1001 // DSDIFF - audio - Direct Stream Digital Interchange File Format 1002 'dsdiff' => array( 1003 'pattern' => '^FRM8', 1004 'group' => 'audio', 1005 'module' => 'dsdiff', 1006 'mime_type' => 'audio/dsd', 1007 ), 1008 1009 // DTS - audio - Dolby Theatre System 1010 'dts' => array( 1011 'pattern' => '^\\x7F\\xFE\\x80\\x01', 1012 'group' => 'audio', 1013 'module' => 'dts', 1014 'mime_type' => 'audio/dts', 1015 ), 1016 1017 // FLAC - audio - Free Lossless Audio Codec 1018 'flac' => array( 1019 'pattern' => '^fLaC', 1020 'group' => 'audio', 1021 'module' => 'flac', 1022 'mime_type' => 'audio/flac', 1023 ), 1024 1025 // LA - audio - Lossless Audio (LA) 1026 'la' => array( 1027 'pattern' => '^LA0[2-4]', 1028 'group' => 'audio', 1029 'module' => 'la', 1030 'mime_type' => 'application/octet-stream', 1031 ), 1032 1033 // LPAC - audio - Lossless Predictive Audio Compression (LPAC) 1034 'lpac' => array( 1035 'pattern' => '^LPAC', 1036 'group' => 'audio', 1037 'module' => 'lpac', 1038 'mime_type' => 'application/octet-stream', 1039 ), 1040 1041 // MIDI - audio - MIDI (Musical Instrument Digital Interface) 1042 'midi' => array( 1043 'pattern' => '^MThd', 1044 'group' => 'audio', 1045 'module' => 'midi', 1046 'mime_type' => 'audio/midi', 1047 ), 1048 1049 // MAC - audio - Monkey's Audio Compressor 1050 'mac' => array( 1051 'pattern' => '^MAC ', 1052 'group' => 'audio', 1053 'module' => 'monkey', 1054 'mime_type' => 'audio/x-monkeys-audio', 1055 ), 1056 1057 1058 // MOD - audio - MODule (SoundTracker) 1059 'mod' => array( 1060 //'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 1061 'pattern' => '^.{1080}(M\\.K\\.)', 1062 'group' => 'audio', 1063 'module' => 'mod', 1064 'option' => 'mod', 1065 'mime_type' => 'audio/mod', 1066 ), 1067 1068 // MOD - audio - MODule (Impulse Tracker) 1069 'it' => array( 1070 'pattern' => '^IMPM', 1071 'group' => 'audio', 1072 'module' => 'mod', 1073 //'option' => 'it', 1074 'mime_type' => 'audio/it', 1075 ), 1076 1077 // MOD - audio - MODule (eXtended Module, various sub-formats) 1078 'xm' => array( 1079 'pattern' => '^Extended Module', 1080 'group' => 'audio', 1081 'module' => 'mod', 1082 //'option' => 'xm', 1083 'mime_type' => 'audio/xm', 1084 ), 1085 1086 // MOD - audio - MODule (ScreamTracker) 1087 's3m' => array( 1088 'pattern' => '^.{44}SCRM', 1089 'group' => 'audio', 1090 'module' => 'mod', 1091 //'option' => 's3m', 1092 'mime_type' => 'audio/s3m', 1093 ), 1094 1095 // MPC - audio - Musepack / MPEGplus 1096 'mpc' => array( 1097 'pattern' => '^(MPCK|MP\\+)', 1098 'group' => 'audio', 1099 'module' => 'mpc', 1100 'mime_type' => 'audio/x-musepack', 1101 ), 1102 1103 // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS) 1104 'mp3' => array( 1105 '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]', 1106 'group' => 'audio', 1107 'module' => 'mp3', 1108 'mime_type' => 'audio/mpeg', 1109 ), 1110 1111 // OFR - audio - OptimFROG 1112 'ofr' => array( 1113 'pattern' => '^(\\*RIFF|OFR)', 1114 'group' => 'audio', 1115 'module' => 'optimfrog', 1116 'mime_type' => 'application/octet-stream', 1117 ), 1118 1119 // RKAU - audio - RKive AUdio compressor 1120 'rkau' => array( 1121 'pattern' => '^RKA', 1122 'group' => 'audio', 1123 'module' => 'rkau', 1124 'mime_type' => 'application/octet-stream', 1125 ), 1126 1127 // SHN - audio - Shorten 1128 'shn' => array( 1129 'pattern' => '^ajkg', 1130 'group' => 'audio', 1131 'module' => 'shorten', 1132 'mime_type' => 'audio/xmms-shn', 1133 'fail_id3' => 'ERROR', 1134 'fail_ape' => 'ERROR', 1135 ), 1136 1137 // TAK - audio - Tom's lossless Audio Kompressor 1138 'tak' => array( 1139 'pattern' => '^tBaK', 1140 'group' => 'audio', 1141 'module' => 'tak', 1142 'mime_type' => 'application/octet-stream', 1143 ), 1144 1145 // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org) 1146 'tta' => array( 1147 'pattern' => '^TTA', // could also be '^TTA(\\x01|\\x02|\\x03|2|1)' 1148 'group' => 'audio', 1149 'module' => 'tta', 1150 'mime_type' => 'application/octet-stream', 1151 ), 1152 1153 // VOC - audio - Creative Voice (VOC) 1154 'voc' => array( 1155 'pattern' => '^Creative Voice File', 1156 'group' => 'audio', 1157 'module' => 'voc', 1158 'mime_type' => 'audio/voc', 1159 ), 1160 1161 // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF) 1162 'vqf' => array( 1163 'pattern' => '^TWIN', 1164 'group' => 'audio', 1165 'module' => 'vqf', 1166 'mime_type' => 'application/octet-stream', 1167 ), 1168 1169 // WV - audio - WavPack (v4.0+) 1170 'wv' => array( 1171 'pattern' => '^wvpk', 1172 'group' => 'audio', 1173 'module' => 'wavpack', 1174 'mime_type' => 'application/octet-stream', 1175 ), 1176 1177 1178 // Audio-Video formats 1179 1180 // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio 1181 'asf' => array( 1182 'pattern' => '^\\x30\\x26\\xB2\\x75\\x8E\\x66\\xCF\\x11\\xA6\\xD9\\x00\\xAA\\x00\\x62\\xCE\\x6C', 1183 'group' => 'audio-video', 1184 'module' => 'asf', 1185 'mime_type' => 'video/x-ms-asf', 1186 'iconv_req' => false, 1187 ), 1188 1189 // BINK - audio/video - Bink / Smacker 1190 'bink' => array( 1191 'pattern' => '^(BIK|SMK)', 1192 'group' => 'audio-video', 1193 'module' => 'bink', 1194 'mime_type' => 'application/octet-stream', 1195 ), 1196 1197 // FLV - audio/video - FLash Video 1198 'flv' => array( 1199 'pattern' => '^FLV[\\x01]', 1200 'group' => 'audio-video', 1201 'module' => 'flv', 1202 'mime_type' => 'video/x-flv', 1203 ), 1204 1205 // IVF - audio/video - IVF 1206 'ivf' => array( 1207 'pattern' => '^DKIF', 1208 'group' => 'audio-video', 1209 'module' => 'ivf', 1210 'mime_type' => 'video/x-ivf', 1211 ), 1212 1213 // MKAV - audio/video - Mastroka 1214 'matroska' => array( 1215 'pattern' => '^\\x1A\\x45\\xDF\\xA3', 1216 'group' => 'audio-video', 1217 'module' => 'matroska', 1218 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska 1219 ), 1220 1221 // MPEG - audio/video - MPEG (Moving Pictures Experts Group) 1222 'mpeg' => array( 1223 'pattern' => '^\\x00\\x00\\x01[\\xB3\\xBA]', 1224 'group' => 'audio-video', 1225 'module' => 'mpeg', 1226 'mime_type' => 'video/mpeg', 1227 ), 1228 1229 // NSV - audio/video - Nullsoft Streaming Video (NSV) 1230 'nsv' => array( 1231 'pattern' => '^NSV[sf]', 1232 'group' => 'audio-video', 1233 'module' => 'nsv', 1234 'mime_type' => 'application/octet-stream', 1235 ), 1236 1237 // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*)) 1238 'ogg' => array( 1239 'pattern' => '^OggS', 1240 'group' => 'audio', 1241 'module' => 'ogg', 1242 'mime_type' => 'application/ogg', 1243 'fail_id3' => 'WARNING', 1244 'fail_ape' => 'WARNING', 1245 ), 1246 1247 // QT - audio/video - Quicktime 1248 'quicktime' => array( 1249 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)', 1250 'group' => 'audio-video', 1251 'module' => 'quicktime', 1252 'mime_type' => 'video/quicktime', 1253 ), 1254 1255 // 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) 1256 'riff' => array( 1257 'pattern' => '^(RIFF|SDSS|FORM)', 1258 'group' => 'audio-video', 1259 'module' => 'riff', 1260 'mime_type' => 'audio/wav', 1261 'fail_ape' => 'WARNING', 1262 ), 1263 1264 // Real - audio/video - RealAudio, RealVideo 1265 'real' => array( 1266 'pattern' => '^\\.(RMF|ra)', 1267 'group' => 'audio-video', 1268 'module' => 'real', 1269 'mime_type' => 'audio/x-realaudio', 1270 ), 1271 1272 // SWF - audio/video - ShockWave Flash 1273 'swf' => array( 1274 'pattern' => '^(F|C)WS', 1275 'group' => 'audio-video', 1276 'module' => 'swf', 1277 'mime_type' => 'application/x-shockwave-flash', 1278 ), 1279 1280 // TS - audio/video - MPEG-2 Transport Stream 1281 'ts' => array( 1282 'pattern' => '^(\\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G". Check for at least 10 packets matching this pattern 1283 'group' => 'audio-video', 1284 'module' => 'ts', 1285 'mime_type' => 'video/MP2T', 1286 ), 1287 1288 // WTV - audio/video - Windows Recorded TV Show 1289 'wtv' => array( 1290 'pattern' => '^\\xB7\\xD8\\x00\\x20\\x37\\x49\\xDA\\x11\\xA6\\x4E\\x00\\x07\\xE9\\x5E\\xAD\\x8D', 1291 'group' => 'audio-video', 1292 'module' => 'wtv', 1293 'mime_type' => 'video/x-ms-wtv', 1294 ), 1295 1296 1297 // Still-Image formats 1298 1299 // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4) 1300 'bmp' => array( 1301 'pattern' => '^BM', 1302 'group' => 'graphic', 1303 'module' => 'bmp', 1304 'mime_type' => 'image/bmp', 1305 'fail_id3' => 'ERROR', 1306 'fail_ape' => 'ERROR', 1307 ), 1308 1309 // GIF - still image - Graphics Interchange Format 1310 'gif' => array( 1311 'pattern' => '^GIF', 1312 'group' => 'graphic', 1313 'module' => 'gif', 1314 'mime_type' => 'image/gif', 1315 'fail_id3' => 'ERROR', 1316 'fail_ape' => 'ERROR', 1317 ), 1318 1319 // JPEG - still image - Joint Photographic Experts Group (JPEG) 1320 'jpg' => array( 1321 'pattern' => '^\\xFF\\xD8\\xFF', 1322 'group' => 'graphic', 1323 'module' => 'jpg', 1324 'mime_type' => 'image/jpeg', 1325 'fail_id3' => 'ERROR', 1326 'fail_ape' => 'ERROR', 1327 ), 1328 1329 // PCD - still image - Kodak Photo CD 1330 'pcd' => array( 1331 'pattern' => '^.{2048}PCD_IPI\\x00', 1332 'group' => 'graphic', 1333 'module' => 'pcd', 1334 'mime_type' => 'image/x-photo-cd', 1335 'fail_id3' => 'ERROR', 1336 'fail_ape' => 'ERROR', 1337 ), 1338 1339 1340 // PNG - still image - Portable Network Graphics (PNG) 1341 'png' => array( 1342 'pattern' => '^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A', 1343 'group' => 'graphic', 1344 'module' => 'png', 1345 'mime_type' => 'image/png', 1346 'fail_id3' => 'ERROR', 1347 'fail_ape' => 'ERROR', 1348 ), 1349 1350 1351 // SVG - still image - Scalable Vector Graphics (SVG) 1352 'svg' => array( 1353 'pattern' => '(<!DOCTYPE svg PUBLIC |xmlns="http://www\\.w3\\.org/2000/svg")', 1354 'group' => 'graphic', 1355 'module' => 'svg', 1356 'mime_type' => 'image/svg+xml', 1357 'fail_id3' => 'ERROR', 1358 'fail_ape' => 'ERROR', 1359 ), 1360 1361 1362 // TIFF - still image - Tagged Information File Format (TIFF) 1363 'tiff' => array( 1364 'pattern' => '^(II\\x2A\\x00|MM\\x00\\x2A)', 1365 'group' => 'graphic', 1366 'module' => 'tiff', 1367 'mime_type' => 'image/tiff', 1368 'fail_id3' => 'ERROR', 1369 'fail_ape' => 'ERROR', 1370 ), 1371 1372 1373 // EFAX - still image - eFax (TIFF derivative) 1374 'efax' => array( 1375 'pattern' => '^\\xDC\\xFE', 1376 'group' => 'graphic', 1377 'module' => 'efax', 1378 'mime_type' => 'image/efax', 1379 'fail_id3' => 'ERROR', 1380 'fail_ape' => 'ERROR', 1381 ), 1382 1383 1384 // Data formats 1385 1386 // ISO - data - International Standards Organization (ISO) CD-ROM Image 1387 'iso' => array( 1388 'pattern' => '^.{32769}CD001', 1389 'group' => 'misc', 1390 'module' => 'iso', 1391 'mime_type' => 'application/octet-stream', 1392 'fail_id3' => 'ERROR', 1393 'fail_ape' => 'ERROR', 1394 'iconv_req' => false, 1395 ), 1396 1397 // HPK - data - HPK compressed data 1398 'hpk' => array( 1399 'pattern' => '^BPUL', 1400 'group' => 'archive', 1401 'module' => 'hpk', 1402 'mime_type' => 'application/octet-stream', 1403 'fail_id3' => 'ERROR', 1404 'fail_ape' => 'ERROR', 1405 ), 1406 1407 // RAR - data - RAR compressed data 1408 'rar' => array( 1409 'pattern' => '^Rar\\!', 1410 'group' => 'archive', 1411 'module' => 'rar', 1412 'mime_type' => 'application/vnd.rar', 1413 'fail_id3' => 'ERROR', 1414 'fail_ape' => 'ERROR', 1415 ), 1416 1417 // SZIP - audio/data - SZIP compressed data 1418 'szip' => array( 1419 'pattern' => '^SZ\\x0A\\x04', 1420 'group' => 'archive', 1421 'module' => 'szip', 1422 'mime_type' => 'application/octet-stream', 1423 'fail_id3' => 'ERROR', 1424 'fail_ape' => 'ERROR', 1425 ), 1426 1427 // TAR - data - TAR compressed data 1428 'tar' => array( 1429 '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}', 1430 'group' => 'archive', 1431 'module' => 'tar', 1432 'mime_type' => 'application/x-tar', 1433 'fail_id3' => 'ERROR', 1434 'fail_ape' => 'ERROR', 1435 ), 1436 1437 // GZIP - data - GZIP compressed data 1438 'gz' => array( 1439 'pattern' => '^\\x1F\\x8B\\x08', 1440 'group' => 'archive', 1441 'module' => 'gzip', 1442 'mime_type' => 'application/gzip', 1443 'fail_id3' => 'ERROR', 1444 'fail_ape' => 'ERROR', 1445 ), 1446 1447 // ZIP - data - ZIP compressed data 1448 'zip' => array( 1449 'pattern' => '^PK\\x03\\x04', 1450 'group' => 'archive', 1451 'module' => 'zip', 1452 'mime_type' => 'application/zip', 1453 'fail_id3' => 'ERROR', 1454 'fail_ape' => 'ERROR', 1455 ), 1456 1457 // XZ - data - XZ compressed data 1458 'xz' => array( 1459 'pattern' => '^\\xFD7zXZ\\x00', 1460 'group' => 'archive', 1461 'module' => 'xz', 1462 'mime_type' => 'application/x-xz', 1463 'fail_id3' => 'ERROR', 1464 'fail_ape' => 'ERROR', 1465 ), 1466 1467 // XZ - data - XZ compressed data 1468 '7zip' => array( 1469 'pattern' => '^7z\\xBC\\xAF\\x27\\x1C', 1470 'group' => 'archive', 1471 'module' => '7zip', 1472 'mime_type' => 'application/x-7z-compressed', 1473 'fail_id3' => 'ERROR', 1474 'fail_ape' => 'ERROR', 1475 ), 1476 1477 1478 // Misc other formats 1479 1480 // PAR2 - data - Parity Volume Set Specification 2.0 1481 'par2' => array ( 1482 'pattern' => '^PAR2\\x00PKT', 1483 'group' => 'misc', 1484 'module' => 'par2', 1485 'mime_type' => 'application/octet-stream', 1486 'fail_id3' => 'ERROR', 1487 'fail_ape' => 'ERROR', 1488 ), 1489 1490 // PDF - data - Portable Document Format 1491 'pdf' => array( 1492 'pattern' => '^\\x25PDF', 1493 'group' => 'misc', 1494 'module' => 'pdf', 1495 'mime_type' => 'application/pdf', 1496 'fail_id3' => 'ERROR', 1497 'fail_ape' => 'ERROR', 1498 ), 1499 1500 // MSOFFICE - data - ZIP compressed data 1501 'msoffice' => array( 1502 'pattern' => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document 1503 'group' => 'misc', 1504 'module' => 'msoffice', 1505 'mime_type' => 'application/octet-stream', 1506 'fail_id3' => 'ERROR', 1507 'fail_ape' => 'ERROR', 1508 ), 1509 1510 // TORRENT - .torrent 1511 'torrent' => array( 1512 'pattern' => '^(d8\\:announce|d7\\:comment)', 1513 'group' => 'misc', 1514 'module' => 'torrent', 1515 'mime_type' => 'application/x-bittorrent', 1516 'fail_id3' => 'ERROR', 1517 'fail_ape' => 'ERROR', 1518 ), 1519 1520 // CUE - data - CUEsheet (index to single-file disc images) 1521 'cue' => array( 1522 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents 1523 'group' => 'misc', 1524 'module' => 'cue', 1525 'mime_type' => 'application/octet-stream', 1526 ), 1527 1528 ); 1529 } 1530 1531 return $format_info; 1532 } 1533 1534 /** 1535 * @param string $filedata 1536 * @param string $filename 1537 * 1538 * @return mixed|false 1539 */ 1540 public function GetFileFormat(&$filedata, $filename='') { 1541 // this function will determine the format of a file based on usually 1542 // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG, 1543 // and in the case of ISO CD image, 6 bytes offset 32kb from the start 1544 // of the file). 1545 1546 // Identify file format - loop through $format_info and detect with reg expr 1547 foreach ($this->GetFileFormatArray() as $format_name => $info) { 1548 // The /s switch on preg_match() forces preg_match() NOT to treat 1549 // newline (0x0A) characters as special chars but do a binary match 1550 if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) { 1551 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1552 return $info; 1553 } 1554 } 1555 1556 1557 if (preg_match('#\\.mp[123a]$#i', $filename)) { 1558 // Too many mp3 encoders on the market put garbage in front of mpeg files 1559 // use assume format on these if format detection failed 1560 $GetFileFormatArray = $this->GetFileFormatArray(); 1561 $info = $GetFileFormatArray['mp3']; 1562 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1563 return $info; 1564 } 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)) { 1565 // old-format (SV4-SV6) Musepack header that has a very loose pattern match and could falsely match other data (e.g. corrupt mp3) 1566 // only enable this pattern check if the filename ends in .mpc/mpp/mp+ 1567 $GetFileFormatArray = $this->GetFileFormatArray(); 1568 $info = $GetFileFormatArray['mpc']; 1569 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1570 return $info; 1571 } elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) { 1572 // there's not really a useful consistent "magic" at the beginning of .cue files to identify them 1573 // so until I think of something better, just go by filename if all other format checks fail 1574 // and verify there's at least one instance of "TRACK xx AUDIO" in the file 1575 $GetFileFormatArray = $this->GetFileFormatArray(); 1576 $info = $GetFileFormatArray['cue']; 1577 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1578 return $info; 1579 } 1580 1581 return false; 1582 } 1583 1584 /** 1585 * Converts array to $encoding charset from $this->encoding. 1586 * 1587 * @param array $array 1588 * @param string $encoding 1589 */ 1590 public function CharConvert(&$array, $encoding) { 1591 1592 // identical encoding - end here 1593 if ($encoding == $this->encoding) { 1594 return; 1595 } 1596 1597 // loop thru array 1598 foreach ($array as $key => $value) { 1599 1600 // go recursive 1601 if (is_array($value)) { 1602 $this->CharConvert($array[$key], $encoding); 1603 } 1604 1605 // convert string 1606 elseif (is_string($value)) { 1607 $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value)); 1608 } 1609 } 1610 } 1611 1612 /** 1613 * @return bool 1614 */ 1615 public function HandleAllTags() { 1616 1617 // key name => array (tag name, character encoding) 1618 static $tags; 1619 if (empty($tags)) { 1620 $tags = array( 1621 'asf' => array('asf' , 'UTF-16LE'), 1622 'midi' => array('midi' , 'ISO-8859-1'), 1623 'nsv' => array('nsv' , 'ISO-8859-1'), 1624 'ogg' => array('vorbiscomment' , 'UTF-8'), 1625 'png' => array('png' , 'UTF-8'), 1626 'tiff' => array('tiff' , 'ISO-8859-1'), 1627 'quicktime' => array('quicktime' , 'UTF-8'), 1628 'real' => array('real' , 'ISO-8859-1'), 1629 'vqf' => array('vqf' , 'ISO-8859-1'), 1630 'zip' => array('zip' , 'ISO-8859-1'), 1631 'riff' => array('riff' , 'ISO-8859-1'), 1632 'lyrics3' => array('lyrics3' , 'ISO-8859-1'), 1633 'id3v1' => array('id3v1' , $this->encoding_id3v1), 1634 '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 1635 'ape' => array('ape' , 'UTF-8'), 1636 'cue' => array('cue' , 'ISO-8859-1'), 1637 'matroska' => array('matroska' , 'UTF-8'), 1638 'flac' => array('vorbiscomment' , 'UTF-8'), 1639 'divxtag' => array('divx' , 'ISO-8859-1'), 1640 'iptc' => array('iptc' , 'ISO-8859-1'), 1641 'dsdiff' => array('dsdiff' , 'ISO-8859-1'), 1642 ); 1643 } 1644 1645 // loop through comments array 1646 foreach ($tags as $comment_name => $tagname_encoding_array) { 1647 list($tag_name, $encoding) = $tagname_encoding_array; 1648 1649 // fill in default encoding type if not already present 1650 if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) { 1651 $this->info[$comment_name]['encoding'] = $encoding; 1652 } 1653 1654 // copy comments if key name set 1655 if (!empty($this->info[$comment_name]['comments'])) { 1656 foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { 1657 foreach ($valuearray as $key => $value) { 1658 if (is_string($value)) { 1659 $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! 1660 } 1661 if (isset($value) && $value !== "") { 1662 if (!is_numeric($key)) { 1663 $this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value; 1664 } else { 1665 $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; 1666 } 1667 } 1668 } 1669 if ($tag_key == 'picture') { 1670 // 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 1671 unset($this->info[$comment_name]['comments'][$tag_key]); 1672 } 1673 } 1674 1675 if (!isset($this->info['tags'][$tag_name])) { 1676 // comments are set but contain nothing but empty strings, so skip 1677 continue; 1678 } 1679 1680 $this->CharConvert($this->info['tags'][$tag_name], $this->info[$comment_name]['encoding']); // only copy gets converted! 1681 1682 if ($this->option_tags_html) { 1683 foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { 1684 if ($tag_key == 'picture') { 1685 // Do not to try to convert binary picture data to HTML 1686 // https://github.com/JamesHeinrich/getID3/issues/178 1687 continue; 1688 } 1689 $this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']); 1690 } 1691 } 1692 1693 } 1694 1695 } 1696 1697 // 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 1698 if (!empty($this->info['tags'])) { 1699 $unset_keys = array('tags', 'tags_html'); 1700 foreach ($this->info['tags'] as $tagtype => $tagarray) { 1701 foreach ($tagarray as $tagname => $tagdata) { 1702 if ($tagname == 'picture') { 1703 foreach ($tagdata as $key => $tagarray) { 1704 $this->info['comments']['picture'][] = $tagarray; 1705 if (isset($tagarray['data']) && isset($tagarray['image_mime'])) { 1706 if (isset($this->info['tags'][$tagtype][$tagname][$key])) { 1707 unset($this->info['tags'][$tagtype][$tagname][$key]); 1708 } 1709 if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) { 1710 unset($this->info['tags_html'][$tagtype][$tagname][$key]); 1711 } 1712 } 1713 } 1714 } 1715 } 1716 foreach ($unset_keys as $unset_key) { 1717 // remove possible empty keys from (e.g. [tags][id3v2][picture]) 1718 if (empty($this->info[$unset_key][$tagtype]['picture'])) { 1719 unset($this->info[$unset_key][$tagtype]['picture']); 1720 } 1721 if (empty($this->info[$unset_key][$tagtype])) { 1722 unset($this->info[$unset_key][$tagtype]); 1723 } 1724 if (empty($this->info[$unset_key])) { 1725 unset($this->info[$unset_key]); 1726 } 1727 } 1728 // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture]) 1729 if (isset($this->info[$tagtype]['comments']['picture'])) { 1730 unset($this->info[$tagtype]['comments']['picture']); 1731 } 1732 if (empty($this->info[$tagtype]['comments'])) { 1733 unset($this->info[$tagtype]['comments']); 1734 } 1735 if (empty($this->info[$tagtype])) { 1736 unset($this->info[$tagtype]); 1737 } 1738 } 1739 } 1740 return true; 1741 } 1742 1743 /** 1744 * Calls getid3_lib::CopyTagsToComments() but passes in the option_tags_html setting from this instance of getID3 1745 * 1746 * @param array $ThisFileInfo 1747 * 1748 * @return bool 1749 */ 1750 public function CopyTagsToComments(&$ThisFileInfo) { 1751 return getid3_lib::CopyTagsToComments($ThisFileInfo, $this->option_tags_html); 1752 } 1753 1754 /** 1755 * @param string $algorithm 1756 * 1757 * @return array|bool 1758 */ 1759 public function getHashdata($algorithm) { 1760 switch ($algorithm) { 1761 case 'md5': 1762 case 'sha1': 1763 break; 1764 1765 default: 1766 return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()'); 1767 } 1768 1769 if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) { 1770 1771 // We cannot get an identical md5_data value for Ogg files where the comments 1772 // span more than 1 Ogg page (compared to the same audio data with smaller 1773 // comments) using the normal getID3() method of MD5'ing the data between the 1774 // end of the comments and the end of the file (minus any trailing tags), 1775 // because the page sequence numbers of the pages that the audio data is on 1776 // do not match. Under normal circumstances, where comments are smaller than 1777 // the nominal 4-8kB page size, then this is not a problem, but if there are 1778 // very large comments, the only way around it is to strip off the comment 1779 // tags with vorbiscomment and MD5 that file. 1780 // This procedure must be applied to ALL Ogg files, not just the ones with 1781 // comments larger than 1 page, because the below method simply MD5's the 1782 // whole file with the comments stripped, not just the portion after the 1783 // comments block (which is the standard getID3() method. 1784 1785 // The above-mentioned problem of comments spanning multiple pages and changing 1786 // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but 1787 // currently vorbiscomment only works on OggVorbis files. 1788 1789 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { 1790 1791 $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'); 1792 $this->info[$algorithm.'_data'] = false; 1793 1794 } else { 1795 1796 // Prevent user from aborting script 1797 $old_abort = ignore_user_abort(true); 1798 1799 // Create empty file 1800 $empty = tempnam(GETID3_TEMP_DIR, 'getID3'); 1801 touch($empty); 1802 1803 // Use vorbiscomment to make temp file without comments 1804 $temp = tempnam(GETID3_TEMP_DIR, 'getID3'); 1805 $file = $this->info['filenamepath']; 1806 1807 if (GETID3_OS_ISWINDOWS) { 1808 1809 if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { 1810 1811 $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"'; 1812 $VorbisCommentError = shell_exec($commandline); 1813 1814 } else { 1815 1816 $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; 1817 1818 } 1819 1820 } else { 1821 1822 $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1'; 1823 $VorbisCommentError = shell_exec($commandline); 1824 1825 } 1826 1827 if (!empty($VorbisCommentError)) { 1828 1829 $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); 1830 $this->info[$algorithm.'_data'] = false; 1831 1832 } else { 1833 1834 // Get hash of newly created file 1835 switch ($algorithm) { 1836 case 'md5': 1837 $this->info[$algorithm.'_data'] = md5_file($temp); 1838 break; 1839 1840 case 'sha1': 1841 $this->info[$algorithm.'_data'] = sha1_file($temp); 1842 break; 1843 } 1844 } 1845 1846 // Clean up 1847 unlink($empty); 1848 unlink($temp); 1849 1850 // Reset abort setting 1851 ignore_user_abort($old_abort); 1852 1853 } 1854 1855 } else { 1856 1857 if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) { 1858 1859 // get hash from part of file 1860 $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm); 1861 1862 } else { 1863 1864 // get hash from whole file 1865 switch ($algorithm) { 1866 case 'md5': 1867 $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']); 1868 break; 1869 1870 case 'sha1': 1871 $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']); 1872 break; 1873 } 1874 } 1875 1876 } 1877 return true; 1878 } 1879 1880 public function ChannelsBitratePlaytimeCalculations() { 1881 1882 // set channelmode on audio 1883 if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) { 1884 // ignore 1885 } elseif ($this->info['audio']['channels'] == 1) { 1886 $this->info['audio']['channelmode'] = 'mono'; 1887 } elseif ($this->info['audio']['channels'] == 2) { 1888 $this->info['audio']['channelmode'] = 'stereo'; 1889 } 1890 1891 // Calculate combined bitrate - audio + video 1892 $CombinedBitrate = 0; 1893 $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0); 1894 $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0); 1895 if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) { 1896 $this->info['bitrate'] = $CombinedBitrate; 1897 } 1898 //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) { 1899 // // for example, VBR MPEG video files cannot determine video bitrate: 1900 // // should not set overall bitrate and playtime from audio bitrate only 1901 // unset($this->info['bitrate']); 1902 //} 1903 1904 // video bitrate undetermined, but calculable 1905 if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) { 1906 // if video bitrate not set 1907 if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) { 1908 // AND if audio bitrate is set to same as overall bitrate 1909 if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) { 1910 // AND if playtime is set 1911 if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) { 1912 // AND if AV data offset start/end is known 1913 // THEN we can calculate the video bitrate 1914 $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']); 1915 $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate']; 1916 } 1917 } 1918 } 1919 } 1920 1921 if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) { 1922 $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate']; 1923 } 1924 1925 if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) { 1926 $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']; 1927 } 1928 if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) { 1929 if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) { 1930 // audio only 1931 $this->info['audio']['bitrate'] = $this->info['bitrate']; 1932 } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) { 1933 // video only 1934 $this->info['video']['bitrate'] = $this->info['bitrate']; 1935 } 1936 } 1937 1938 // Set playtime string 1939 if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) { 1940 $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']); 1941 } 1942 } 1943 1944 /** 1945 * @return bool 1946 */ 1947 public function CalculateCompressionRatioVideo() { 1948 if (empty($this->info['video'])) { 1949 return false; 1950 } 1951 if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) { 1952 return false; 1953 } 1954 if (empty($this->info['video']['bits_per_sample'])) { 1955 return false; 1956 } 1957 1958 switch ($this->info['video']['dataformat']) { 1959 case 'bmp': 1960 case 'gif': 1961 case 'jpeg': 1962 case 'jpg': 1963 case 'png': 1964 case 'tiff': 1965 $FrameRate = 1; 1966 $PlaytimeSeconds = 1; 1967 $BitrateCompressed = $this->info['filesize'] * 8; 1968 break; 1969 1970 default: 1971 if (!empty($this->info['video']['frame_rate'])) { 1972 $FrameRate = $this->info['video']['frame_rate']; 1973 } else { 1974 return false; 1975 } 1976 if (!empty($this->info['playtime_seconds'])) { 1977 $PlaytimeSeconds = $this->info['playtime_seconds']; 1978 } else { 1979 return false; 1980 } 1981 if (!empty($this->info['video']['bitrate'])) { 1982 $BitrateCompressed = $this->info['video']['bitrate']; 1983 } else { 1984 return false; 1985 } 1986 break; 1987 } 1988 $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate; 1989 1990 $this->info['video']['compression_ratio'] = getid3_lib::SafeDiv($BitrateCompressed, $BitrateUncompressed, 1); 1991 return true; 1992 } 1993 1994 /** 1995 * @return bool 1996 */ 1997 public function CalculateCompressionRatioAudio() { 1998 if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) { 1999 return false; 2000 } 2001 $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)); 2002 2003 if (!empty($this->info['audio']['streams'])) { 2004 foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) { 2005 if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) { 2006 $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16)); 2007 } 2008 } 2009 } 2010 return true; 2011 } 2012 2013 /** 2014 * @return bool 2015 */ 2016 public function CalculateReplayGain() { 2017 if (isset($this->info['replay_gain'])) { 2018 if (!isset($this->info['replay_gain']['reference_volume'])) { 2019 $this->info['replay_gain']['reference_volume'] = 89.0; 2020 } 2021 if (isset($this->info['replay_gain']['track']['adjustment'])) { 2022 $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; 2023 } 2024 if (isset($this->info['replay_gain']['album']['adjustment'])) { 2025 $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment']; 2026 } 2027 2028 if (isset($this->info['replay_gain']['track']['peak'])) { 2029 $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']); 2030 } 2031 if (isset($this->info['replay_gain']['album']['peak'])) { 2032 $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']); 2033 } 2034 } 2035 return true; 2036 } 2037 2038 /** 2039 * @return bool 2040 */ 2041 public function ProcessAudioStreams() { 2042 if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { 2043 if (!isset($this->info['audio']['streams'])) { 2044 foreach ($this->info['audio'] as $key => $value) { 2045 if ($key != 'streams') { 2046 $this->info['audio']['streams'][0][$key] = $value; 2047 } 2048 } 2049 } 2050 } 2051 return true; 2052 } 2053 2054 /** 2055 * @return string|bool 2056 */ 2057 public function getid3_tempnam() { 2058 return tempnam($this->tempdir, 'gI3'); 2059 } 2060 2061 /** 2062 * @param string $name 2063 * 2064 * @return bool 2065 * 2066 * @throws getid3_exception 2067 */ 2068 public function include_module($name) { 2069 //if (!file_exists($this->include_path.'module.'.$name.'.php')) { 2070 if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { 2071 throw new getid3_exception('Required module.'.$name.'.php is missing.'); 2072 } 2073 include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php'); 2074 return true; 2075 } 2076 2077 /** 2078 * @param string $filename 2079 * 2080 * @return bool 2081 */ 2082 public static function is_writable ($filename) { 2083 $ret = is_writable($filename); 2084 if (!$ret) { 2085 $perms = fileperms($filename); 2086 $ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002); 2087 } 2088 return $ret; 2089 } 2090 2091 } 2092 2093 2094 abstract class getid3_handler 2095 { 2096 2097 /** 2098 * @var getID3 2099 */ 2100 protected $getid3; // pointer 2101 2102 /** 2103 * Analyzing filepointer or string. 2104 * 2105 * @var bool 2106 */ 2107 protected $data_string_flag = false; 2108 2109 /** 2110 * String to analyze. 2111 * 2112 * @var string 2113 */ 2114 protected $data_string = ''; 2115 2116 /** 2117 * Seek position in string. 2118 * 2119 * @var int 2120 */ 2121 protected $data_string_position = 0; 2122 2123 /** 2124 * String length. 2125 * 2126 * @var int 2127 */ 2128 protected $data_string_length = 0; 2129 2130 /** 2131 * @var string 2132 */ 2133 private $dependency_to; 2134 2135 /** 2136 * getid3_handler constructor. 2137 * 2138 * @param getID3 $getid3 2139 * @param string $call_module 2140 */ 2141 public function __construct(getID3 $getid3, $call_module=null) { 2142 $this->getid3 = $getid3; 2143 2144 if ($call_module) { 2145 $this->dependency_to = str_replace('getid3_', '', $call_module); 2146 } 2147 } 2148 2149 /** 2150 * Analyze from file pointer. 2151 * 2152 * @return bool 2153 */ 2154 abstract public function Analyze(); 2155 2156 /** 2157 * Analyze from string instead. 2158 * 2159 * @param string $string 2160 */ 2161 public function AnalyzeString($string) { 2162 // Enter string mode 2163 $this->setStringMode($string); 2164 2165 // Save info 2166 $saved_avdataoffset = $this->getid3->info['avdataoffset']; 2167 $saved_avdataend = $this->getid3->info['avdataend']; 2168 $saved_filesize = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call 2169 2170 // Reset some info 2171 $this->getid3->info['avdataoffset'] = 0; 2172 $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = $this->data_string_length; 2173 2174 // Analyze 2175 $this->Analyze(); 2176 2177 // Restore some info 2178 $this->getid3->info['avdataoffset'] = $saved_avdataoffset; 2179 $this->getid3->info['avdataend'] = $saved_avdataend; 2180 $this->getid3->info['filesize'] = $saved_filesize; 2181 2182 // Exit string mode 2183 $this->data_string_flag = false; 2184 } 2185 2186 /** 2187 * @param string $string 2188 */ 2189 public function setStringMode($string) { 2190 $this->data_string_flag = true; 2191 $this->data_string = $string; 2192 $this->data_string_length = strlen($string); 2193 } 2194 2195 /** 2196 * @phpstan-impure 2197 * 2198 * @return int|bool 2199 */ 2200 protected function ftell() { 2201 if ($this->data_string_flag) { 2202 return $this->data_string_position; 2203 } 2204 return ftell($this->getid3->fp); 2205 } 2206 2207 /** 2208 * @param int $bytes 2209 * 2210 * @phpstan-impure 2211 * 2212 * @return string|false 2213 * 2214 * @throws getid3_exception 2215 */ 2216 protected function fread($bytes) { 2217 if ($this->data_string_flag) { 2218 $this->data_string_position += $bytes; 2219 return substr($this->data_string, $this->data_string_position - $bytes, $bytes); 2220 } 2221 if ($bytes == 0) { 2222 return ''; 2223 } elseif ($bytes < 0) { 2224 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().')', 10); 2225 } 2226 $pos = $this->ftell() + $bytes; 2227 if (!getid3_lib::intValueSupported($pos)) { 2228 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10); 2229 } 2230 2231 //return fread($this->getid3->fp, $bytes); 2232 /* 2233 * https://www.getid3.org/phpBB3/viewtopic.php?t=1930 2234 * "I found out that the root cause for the problem was how getID3 uses the PHP system function fread(). 2235 * It seems to assume that fread() would always return as many bytes as were requested. 2236 * 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. 2237 * The call may return only part of the requested data and a new call is needed to get more." 2238 */ 2239 $contents = ''; 2240 do { 2241 //if (($this->getid3->memory_limit > 0) && ($bytes > $this->getid3->memory_limit)) { 2242 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)" 2243 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') that is more than available PHP memory ('.$this->getid3->memory_limit.')', 10); 2244 } 2245 $part = fread($this->getid3->fp, $bytes); 2246 $partLength = strlen($part); 2247 $bytes -= $partLength; 2248 $contents .= $part; 2249 } while (($bytes > 0) && ($partLength > 0)); 2250 return $contents; 2251 } 2252 2253 /** 2254 * @param int $bytes 2255 * @param int $whence 2256 * 2257 * @phpstan-impure 2258 * 2259 * @return int 2260 * 2261 * @throws getid3_exception 2262 */ 2263 protected function fseek($bytes, $whence=SEEK_SET) { 2264 if ($this->data_string_flag) { 2265 switch ($whence) { 2266 case SEEK_SET: 2267 $this->data_string_position = $bytes; 2268 break; 2269 2270 case SEEK_CUR: 2271 $this->data_string_position += $bytes; 2272 break; 2273 2274 case SEEK_END: 2275 $this->data_string_position = $this->data_string_length + $bytes; 2276 break; 2277 } 2278 return 0; // fseek returns 0 on success 2279 } 2280 2281 $pos = $bytes; 2282 if ($whence == SEEK_CUR) { 2283 $pos = $this->ftell() + $bytes; 2284 } elseif ($whence == SEEK_END) { 2285 $pos = $this->getid3->info['filesize'] + $bytes; 2286 } 2287 if (!getid3_lib::intValueSupported($pos)) { 2288 throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10); 2289 } 2290 2291 // https://github.com/JamesHeinrich/getID3/issues/327 2292 $result = fseek($this->getid3->fp, $bytes, $whence); 2293 if ($result !== 0) { // fseek returns 0 on success 2294 throw new getid3_exception('cannot fseek('.$pos.'). resource/stream does not appear to support seeking', 10); 2295 } 2296 return $result; 2297 } 2298 2299 /** 2300 * @phpstan-impure 2301 * 2302 * @return string|false 2303 * 2304 * @throws getid3_exception 2305 */ 2306 protected function fgets() { 2307 // must be able to handle CR/LF/CRLF but not read more than one lineend 2308 $buffer = ''; // final string we will return 2309 $prevchar = ''; // save previously-read character for end-of-line checking 2310 if ($this->data_string_flag) { 2311 while (true) { 2312 $thischar = substr($this->data_string, $this->data_string_position++, 1); 2313 if (($prevchar == "\r") && ($thischar != "\n")) { 2314 // read one byte too many, back up 2315 $this->data_string_position--; 2316 break; 2317 } 2318 $buffer .= $thischar; 2319 if ($thischar == "\n") { 2320 break; 2321 } 2322 if ($this->data_string_position >= $this->data_string_length) { 2323 // EOF 2324 break; 2325 } 2326 $prevchar = $thischar; 2327 } 2328 2329 } else { 2330 2331 // Ideally we would just use PHP's fgets() function, however... 2332 // it does not behave consistently with regards to mixed line endings, may be system-dependent 2333 // and breaks entirely when given a file with mixed \r vs \n vs \r\n line endings (e.g. some PDFs) 2334 //return fgets($this->getid3->fp); 2335 while (true) { 2336 $thischar = fgetc($this->getid3->fp); 2337 if (($prevchar == "\r") && ($thischar != "\n")) { 2338 // read one byte too many, back up 2339 fseek($this->getid3->fp, -1, SEEK_CUR); 2340 break; 2341 } 2342 $buffer .= $thischar; 2343 if ($thischar == "\n") { 2344 break; 2345 } 2346 if (feof($this->getid3->fp)) { 2347 break; 2348 } 2349 $prevchar = $thischar; 2350 } 2351 2352 } 2353 return $buffer; 2354 } 2355 2356 /** 2357 * @phpstan-impure 2358 * 2359 * @return bool 2360 */ 2361 protected function feof() { 2362 if ($this->data_string_flag) { 2363 return $this->data_string_position >= $this->data_string_length; 2364 } 2365 return feof($this->getid3->fp); 2366 } 2367 2368 /** 2369 * @param string $module 2370 * 2371 * @return bool 2372 */ 2373 final protected function isDependencyFor($module) { 2374 return $this->dependency_to == $module; 2375 } 2376 2377 /** 2378 * @param string $text 2379 * 2380 * @return bool 2381 */ 2382 protected function error($text) { 2383 $this->getid3->info['error'][] = $text; 2384 2385 return false; 2386 } 2387 2388 /** 2389 * @param string $text 2390 * 2391 * @return bool 2392 */ 2393 protected function warning($text) { 2394 return $this->getid3->warning($text); 2395 } 2396 2397 /** 2398 * @param string $text 2399 */ 2400 protected function notice($text) { 2401 // does nothing for now 2402 } 2403 2404 /** 2405 * @param string $name 2406 * @param int $offset 2407 * @param int $length 2408 * @param string $image_mime 2409 * 2410 * @return string|null 2411 * 2412 * @throws Exception 2413 * @throws getid3_exception 2414 */ 2415 public function saveAttachment($name, $offset, $length, $image_mime=null) { 2416 $fp_dest = null; 2417 $dest = null; 2418 try { 2419 2420 // do not extract at all 2421 if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) { 2422 2423 $attachment = null; // do not set any 2424 2425 // extract to return array 2426 } elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { 2427 2428 $this->fseek($offset); 2429 $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory 2430 if ($attachment === false || strlen($attachment) != $length) { 2431 throw new Exception('failed to read attachment data'); 2432 } 2433 2434 // assume directory path is given 2435 } else { 2436 2437 // set up destination path 2438 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); 2439 if (!is_dir($dir) || !getID3::is_writable($dir)) { // check supplied directory 2440 throw new Exception('supplied path ('.$dir.') does not exist, or is not writable'); 2441 } 2442 $dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : ''); 2443 2444 // create dest file 2445 if (($fp_dest = fopen($dest, 'wb')) == false) { 2446 throw new Exception('failed to create file '.$dest); 2447 } 2448 2449 // copy data 2450 $this->fseek($offset); 2451 $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size()); 2452 $bytesleft = $length; 2453 while ($bytesleft > 0) { 2454 if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) { 2455 throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space'); 2456 } 2457 $bytesleft -= $byteswritten; 2458 } 2459 2460 fclose($fp_dest); 2461 $attachment = $dest; 2462 2463 } 2464 2465 } catch (Exception $e) { 2466 2467 // close and remove dest file if created 2468 if (isset($fp_dest) && is_resource($fp_dest)) { 2469 fclose($fp_dest); 2470 } 2471 2472 if (isset($dest) && file_exists($dest)) { 2473 unlink($dest); 2474 } 2475 2476 // do not set any is case of error 2477 $attachment = null; 2478 $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage()); 2479 2480 } 2481 2482 // seek to the end of attachment 2483 $this->fseek($offset + $length); 2484 2485 return $attachment; 2486 } 2487 2488 } 2489 2490 2491 class getid3_exception extends Exception 2492 { 2493 public $message; 2494 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Fri Oct 10 08:20:03 2025 | Cross-referenced by PHPXref |