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