[ Index ] |
PHP Cross Reference of WordPress |
[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 availble - 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.21-202109171300'; 391 const FREAD_BUFFER_SIZE = 32768; 392 393 const ATTACHMENTS_NONE = false; 394 const ATTACHMENTS_INLINE = true; 395 396 public function __construct() { 397 398 // Check for PHP version 399 $required_php_version = '5.3.0'; 400 if (version_compare(PHP_VERSION, $required_php_version, '<')) { 401 $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION."\n"; 402 return; 403 } 404 405 // Check memory 406 $memoryLimit = ini_get('memory_limit'); 407 if (preg_match('#([0-9]+) ?M#i', $memoryLimit, $matches)) { 408 // could be stored as "16M" rather than 16777216 for example 409 $memoryLimit = $matches[1] * 1048576; 410 } elseif (preg_match('#([0-9]+) ?G#i', $memoryLimit, $matches)) { // The 'G' modifier is available since PHP 5.1.0 411 // could be stored as "2G" rather than 2147483648 for example 412 $memoryLimit = $matches[1] * 1073741824; 413 } 414 $this->memory_limit = $memoryLimit; 415 416 if ($this->memory_limit <= 0) { 417 // memory limits probably disabled 418 } elseif ($this->memory_limit <= 4194304) { 419 $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'."\n"; 420 } elseif ($this->memory_limit <= 12582912) { 421 $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"; 422 } 423 424 // Check safe_mode off 425 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved 426 $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); 427 } 428 429 // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated 430 if (($mbstring_func_overload = (int) ini_get('mbstring.func_overload')) && ($mbstring_func_overload & 0x02)) { 431 // http://php.net/manual/en/mbstring.overload.php 432 // "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" 433 // 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. 434 // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated 435 $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"; 436 } 437 438 // check for magic quotes in PHP < 7.4.0 (when these functions became deprecated) 439 if (version_compare(PHP_VERSION, '7.4.0', '<')) { 440 // Check for magic_quotes_runtime 441 if (function_exists('get_magic_quotes_runtime')) { 442 // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.get_magic_quotes_runtimeDeprecated 443 if (get_magic_quotes_runtime()) { 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 // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.get_magic_quotes_gpcDeprecated 450 if (get_magic_quotes_gpc()) { 451 $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"; 452 } 453 } 454 } 455 456 // Load support library 457 if (!include_once (GETID3_INCLUDEPATH.'getid3.lib.php')) { 458 $this->startup_error .= 'getid3.lib.php is missing or corrupt'."\n"; 459 } 460 461 if ($this->option_max_2gb_check === null) { 462 $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647); 463 } 464 465 466 // Needed for Windows only: 467 // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC 468 // as well as other helper functions such as head, etc 469 // This path cannot contain spaces, but the below code will attempt to get the 470 // 8.3-equivalent path automatically 471 // IMPORTANT: This path must include the trailing slash 472 if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) { 473 474 $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path 475 476 if (!is_dir($helperappsdir)) { 477 $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'."\n"; 478 } elseif (strpos(realpath($helperappsdir), ' ') !== false) { 479 $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir)); 480 $path_so_far = array(); 481 foreach ($DirPieces as $key => $value) { 482 if (strpos($value, ' ') !== false) { 483 if (!empty($path_so_far)) { 484 $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far)); 485 $dir_listing = `$commandline`; 486 $lines = explode("\n", $dir_listing); 487 foreach ($lines as $line) { 488 $line = trim($line); 489 if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) { 490 list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches; 491 if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) { 492 $value = $shortname; 493 } 494 } 495 } 496 } else { 497 $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"; 498 } 499 } 500 $path_so_far[] = $value; 501 } 502 $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far); 503 } 504 define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR); 505 } 506 507 if (!empty($this->startup_error)) { 508 echo $this->startup_error; 509 throw new getid3_exception($this->startup_error); 510 } 511 } 512 513 /** 514 * @return string 515 */ 516 public function version() { 517 return self::VERSION; 518 } 519 520 /** 521 * @return int 522 */ 523 public function fread_buffer_size() { 524 return $this->option_fread_buffer_size; 525 } 526 527 /** 528 * @param array $optArray 529 * 530 * @return bool 531 */ 532 public function setOption($optArray) { 533 if (!is_array($optArray) || empty($optArray)) { 534 return false; 535 } 536 foreach ($optArray as $opt => $val) { 537 if (isset($this->$opt) === false) { 538 continue; 539 } 540 $this->$opt = $val; 541 } 542 return true; 543 } 544 545 /** 546 * @param string $filename 547 * @param int $filesize 548 * @param resource $fp 549 * 550 * @return bool 551 * 552 * @throws getid3_exception 553 */ 554 public function openfile($filename, $filesize=null, $fp=null) { 555 try { 556 if (!empty($this->startup_error)) { 557 throw new getid3_exception($this->startup_error); 558 } 559 if (!empty($this->startup_warning)) { 560 foreach (explode("\n", $this->startup_warning) as $startup_warning) { 561 $this->warning($startup_warning); 562 } 563 } 564 565 // init result array and set parameters 566 $this->filename = $filename; 567 $this->info = array(); 568 $this->info['GETID3_VERSION'] = $this->version(); 569 $this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false); 570 571 // remote files not supported 572 if (preg_match('#^(ht|f)tp://#', $filename)) { 573 throw new getid3_exception('Remote files are not supported - please copy the file locally first'); 574 } 575 576 $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); 577 //$filename = preg_replace('#(?<!gs:)('.preg_quote(DIRECTORY_SEPARATOR).'{2,})#', DIRECTORY_SEPARATOR, $filename); 578 579 // open local file 580 //if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see https://www.getid3.org/phpBB3/viewtopic.php?t=1720 581 if (($fp != null) && ((get_resource_type($fp) == 'file') || (get_resource_type($fp) == 'stream'))) { 582 $this->fp = $fp; 583 } elseif ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { 584 // great 585 } else { 586 $errormessagelist = array(); 587 if (!is_readable($filename)) { 588 $errormessagelist[] = '!is_readable'; 589 } 590 if (!is_file($filename)) { 591 $errormessagelist[] = '!is_file'; 592 } 593 if (!file_exists($filename)) { 594 $errormessagelist[] = '!file_exists'; 595 } 596 if (empty($errormessagelist)) { 597 $errormessagelist[] = 'fopen failed'; 598 } 599 throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')'); 600 } 601 602 $this->info['filesize'] = (!is_null($filesize) ? $filesize : filesize($filename)); 603 // set redundant parameters - might be needed in some include file 604 // filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion 605 $filename = str_replace('\\', '/', $filename); 606 $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); 607 $this->info['filename'] = getid3_lib::mb_basename($filename); 608 $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; 609 610 // set more parameters 611 $this->info['avdataoffset'] = 0; 612 $this->info['avdataend'] = $this->info['filesize']; 613 $this->info['fileformat'] = ''; // filled in later 614 $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used 615 $this->info['video']['dataformat'] = ''; // filled in later, unset if not used 616 $this->info['tags'] = array(); // filled in later, unset if not used 617 $this->info['error'] = array(); // filled in later, unset if not used 618 $this->info['warning'] = array(); // filled in later, unset if not used 619 $this->info['comments'] = array(); // filled in later, unset if not used 620 $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired 621 622 // option_max_2gb_check 623 if ($this->option_max_2gb_check) { 624 // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB) 625 // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize 626 // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer 627 $fseek = fseek($this->fp, 0, SEEK_END); 628 if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) || 629 ($this->info['filesize'] < 0) || 630 (ftell($this->fp) < 0)) { 631 $real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']); 632 633 if ($real_filesize === false) { 634 unset($this->info['filesize']); 635 fclose($this->fp); 636 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.'); 637 } elseif (getid3_lib::intValueSupported($real_filesize)) { 638 unset($this->info['filesize']); 639 fclose($this->fp); 640 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'); 641 } 642 $this->info['filesize'] = $real_filesize; 643 $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.'); 644 } 645 } 646 647 return true; 648 649 } catch (Exception $e) { 650 $this->error($e->getMessage()); 651 } 652 return false; 653 } 654 655 /** 656 * analyze file 657 * 658 * @param string $filename 659 * @param int $filesize 660 * @param string $original_filename 661 * @param resource $fp 662 * 663 * @return array 664 */ 665 public function analyze($filename, $filesize=null, $original_filename='', $fp=null) { 666 try { 667 if (!$this->openfile($filename, $filesize, $fp)) { 668 return $this->info; 669 } 670 671 // Handle tags 672 foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { 673 $option_tag = 'option_tag_'.$tag_name; 674 if ($this->$option_tag) { 675 $this->include_module('tag.'.$tag_name); 676 try { 677 $tag_class = 'getid3_'.$tag_name; 678 $tag = new $tag_class($this); 679 $tag->Analyze(); 680 } 681 catch (getid3_exception $e) { 682 throw $e; 683 } 684 } 685 } 686 if (isset($this->info['id3v2']['tag_offset_start'])) { 687 $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']); 688 } 689 foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { 690 if (isset($this->info[$tag_key]['tag_offset_start'])) { 691 $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']); 692 } 693 } 694 695 // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier 696 if (!$this->option_tag_id3v2) { 697 fseek($this->fp, 0); 698 $header = fread($this->fp, 10); 699 if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { 700 $this->info['id3v2']['header'] = true; 701 $this->info['id3v2']['majorversion'] = ord($header[3]); 702 $this->info['id3v2']['minorversion'] = ord($header[4]); 703 $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 704 } 705 } 706 707 // read 32 kb file data 708 fseek($this->fp, $this->info['avdataoffset']); 709 $formattest = fread($this->fp, 32774); 710 711 // determine format 712 $determined_format = $this->GetFileFormat($formattest, ($original_filename ? $original_filename : $filename)); 713 714 // unable to determine file format 715 if (!$determined_format) { 716 fclose($this->fp); 717 return $this->error('unable to determine file format'); 718 } 719 720 // check for illegal ID3 tags 721 if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { 722 if ($determined_format['fail_id3'] === 'ERROR') { 723 fclose($this->fp); 724 return $this->error('ID3 tags not allowed on this file type.'); 725 } elseif ($determined_format['fail_id3'] === 'WARNING') { 726 $this->warning('ID3 tags not allowed on this file type.'); 727 } 728 } 729 730 // check for illegal APE tags 731 if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { 732 if ($determined_format['fail_ape'] === 'ERROR') { 733 fclose($this->fp); 734 return $this->error('APE tags not allowed on this file type.'); 735 } elseif ($determined_format['fail_ape'] === 'WARNING') { 736 $this->warning('APE tags not allowed on this file type.'); 737 } 738 } 739 740 // set mime type 741 $this->info['mime_type'] = $determined_format['mime_type']; 742 743 // supported format signature pattern detected, but module deleted 744 if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { 745 fclose($this->fp); 746 return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); 747 } 748 749 // module requires mb_convert_encoding/iconv support 750 // Check encoding/iconv support 751 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'))) { 752 $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. '; 753 if (GETID3_OS_ISWINDOWS) { 754 $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'; 755 } else { 756 $errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch'; 757 } 758 return $this->error($errormessage); 759 } 760 761 // include module 762 include_once(GETID3_INCLUDEPATH.$determined_format['include']); 763 764 // instantiate module class 765 $class_name = 'getid3_'.$determined_format['module']; 766 if (!class_exists($class_name)) { 767 return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); 768 } 769 $class = new $class_name($this); 770 771 // set module-specific options 772 foreach (get_object_vars($this) as $getid3_object_vars_key => $getid3_object_vars_value) { 773 if (preg_match('#^options_([^_]+)_([^_]+)_(.+)$#i', $getid3_object_vars_key, $matches)) { 774 list($dummy, $GOVgroup, $GOVmodule, $GOVsetting) = $matches; 775 $GOVgroup = (($GOVgroup == 'audiovideo') ? 'audio-video' : $GOVgroup); // variable names can only contain 0-9a-z_ so standardize here 776 if (($GOVgroup == $determined_format['group']) && ($GOVmodule == $determined_format['module'])) { 777 $class->$GOVsetting = $getid3_object_vars_value; 778 } 779 } 780 } 781 782 $class->Analyze(); 783 unset($class); 784 785 // close file 786 fclose($this->fp); 787 788 // process all tags - copy to 'tags' and convert charsets 789 if ($this->option_tags_process) { 790 $this->HandleAllTags(); 791 } 792 793 // perform more calculations 794 if ($this->option_extra_info) { 795 $this->ChannelsBitratePlaytimeCalculations(); 796 $this->CalculateCompressionRatioVideo(); 797 $this->CalculateCompressionRatioAudio(); 798 $this->CalculateReplayGain(); 799 $this->ProcessAudioStreams(); 800 } 801 802 // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags 803 if ($this->option_md5_data) { 804 // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too 805 if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { 806 $this->getHashdata('md5'); 807 } 808 } 809 810 // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags 811 if ($this->option_sha1_data) { 812 $this->getHashdata('sha1'); 813 } 814 815 // remove undesired keys 816 $this->CleanUp(); 817 818 } catch (Exception $e) { 819 $this->error('Caught exception: '.$e->getMessage()); 820 } 821 822 // return info array 823 return $this->info; 824 } 825 826 827 /** 828 * Error handling. 829 * 830 * @param string $message 831 * 832 * @return array 833 */ 834 public function error($message) { 835 $this->CleanUp(); 836 if (!isset($this->info['error'])) { 837 $this->info['error'] = array(); 838 } 839 $this->info['error'][] = $message; 840 return $this->info; 841 } 842 843 844 /** 845 * Warning handling. 846 * 847 * @param string $message 848 * 849 * @return bool 850 */ 851 public function warning($message) { 852 $this->info['warning'][] = $message; 853 return true; 854 } 855 856 857 /** 858 * @return bool 859 */ 860 private function CleanUp() { 861 862 // remove possible empty keys 863 $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate'); 864 foreach ($AVpossibleEmptyKeys as $dummy => $key) { 865 if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) { 866 unset($this->info['audio'][$key]); 867 } 868 if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) { 869 unset($this->info['video'][$key]); 870 } 871 } 872 873 // remove empty root keys 874 if (!empty($this->info)) { 875 foreach ($this->info as $key => $value) { 876 if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) { 877 unset($this->info[$key]); 878 } 879 } 880 } 881 882 // remove meaningless entries from unknown-format files 883 if (empty($this->info['fileformat'])) { 884 if (isset($this->info['avdataoffset'])) { 885 unset($this->info['avdataoffset']); 886 } 887 if (isset($this->info['avdataend'])) { 888 unset($this->info['avdataend']); 889 } 890 } 891 892 // remove possible duplicated identical entries 893 if (!empty($this->info['error'])) { 894 $this->info['error'] = array_values(array_unique($this->info['error'])); 895 } 896 if (!empty($this->info['warning'])) { 897 $this->info['warning'] = array_values(array_unique($this->info['warning'])); 898 } 899 900 // remove "global variable" type keys 901 unset($this->info['php_memory_limit']); 902 903 return true; 904 } 905 906 /** 907 * Return array containing information about all supported formats. 908 * 909 * @return array 910 */ 911 public function GetFileFormatArray() { 912 static $format_info = array(); 913 if (empty($format_info)) { 914 $format_info = array( 915 916 // Audio formats 917 918 // AC-3 - audio - Dolby AC-3 / Dolby Digital 919 'ac3' => array( 920 'pattern' => '^\\x0B\\x77', 921 'group' => 'audio', 922 'module' => 'ac3', 923 'mime_type' => 'audio/ac3', 924 ), 925 926 // AAC - audio - Advanced Audio Coding (AAC) - ADIF format 927 'adif' => array( 928 'pattern' => '^ADIF', 929 'group' => 'audio', 930 'module' => 'aac', 931 'mime_type' => 'audio/aac', 932 'fail_ape' => 'WARNING', 933 ), 934 935 /* 936 // AA - audio - Audible Audiobook 937 'aa' => array( 938 'pattern' => '^.{4}\\x57\\x90\\x75\\x36', 939 'group' => 'audio', 940 'module' => 'aa', 941 'mime_type' => 'audio/audible', 942 ), 943 */ 944 // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) 945 'adts' => array( 946 'pattern' => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]', 947 'group' => 'audio', 948 'module' => 'aac', 949 'mime_type' => 'audio/aac', 950 'fail_ape' => 'WARNING', 951 ), 952 953 954 // AU - audio - NeXT/Sun AUdio (AU) 955 'au' => array( 956 'pattern' => '^\\.snd', 957 'group' => 'audio', 958 'module' => 'au', 959 'mime_type' => 'audio/basic', 960 ), 961 962 // AMR - audio - Adaptive Multi Rate 963 'amr' => array( 964 'pattern' => '^\\x23\\x21AMR\\x0A', // #!AMR[0A] 965 'group' => 'audio', 966 'module' => 'amr', 967 'mime_type' => 'audio/amr', 968 ), 969 970 // AVR - audio - Audio Visual Research 971 'avr' => array( 972 'pattern' => '^2BIT', 973 'group' => 'audio', 974 'module' => 'avr', 975 'mime_type' => 'application/octet-stream', 976 ), 977 978 // BONK - audio - Bonk v0.9+ 979 'bonk' => array( 980 'pattern' => '^\\x00(BONK|INFO|META| ID3)', 981 'group' => 'audio', 982 'module' => 'bonk', 983 'mime_type' => 'audio/xmms-bonk', 984 ), 985 986 // DSF - audio - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital 987 'dsf' => array( 988 'pattern' => '^DSD ', // including trailing space: 44 53 44 20 989 'group' => 'audio', 990 'module' => 'dsf', 991 'mime_type' => 'audio/dsd', 992 ), 993 994 // DSS - audio - Digital Speech Standard 995 'dss' => array( 996 'pattern' => '^[\\x02-\\x08]ds[s2]', 997 'group' => 'audio', 998 'module' => 'dss', 999 'mime_type' => 'application/octet-stream', 1000 ), 1001 1002 // DSDIFF - audio - Direct Stream Digital Interchange File Format 1003 'dsdiff' => array( 1004 'pattern' => '^FRM8', 1005 'group' => 'audio', 1006 'module' => 'dsdiff', 1007 'mime_type' => 'audio/dsd', 1008 ), 1009 1010 // DTS - audio - Dolby Theatre System 1011 'dts' => array( 1012 'pattern' => '^\\x7F\\xFE\\x80\\x01', 1013 'group' => 'audio', 1014 'module' => 'dts', 1015 'mime_type' => 'audio/dts', 1016 ), 1017 1018 // FLAC - audio - Free Lossless Audio Codec 1019 'flac' => array( 1020 'pattern' => '^fLaC', 1021 'group' => 'audio', 1022 'module' => 'flac', 1023 'mime_type' => 'audio/flac', 1024 ), 1025 1026 // LA - audio - Lossless Audio (LA) 1027 'la' => array( 1028 'pattern' => '^LA0[2-4]', 1029 'group' => 'audio', 1030 'module' => 'la', 1031 'mime_type' => 'application/octet-stream', 1032 ), 1033 1034 // LPAC - audio - Lossless Predictive Audio Compression (LPAC) 1035 'lpac' => array( 1036 'pattern' => '^LPAC', 1037 'group' => 'audio', 1038 'module' => 'lpac', 1039 'mime_type' => 'application/octet-stream', 1040 ), 1041 1042 // MIDI - audio - MIDI (Musical Instrument Digital Interface) 1043 'midi' => array( 1044 'pattern' => '^MThd', 1045 'group' => 'audio', 1046 'module' => 'midi', 1047 'mime_type' => 'audio/midi', 1048 ), 1049 1050 // MAC - audio - Monkey's Audio Compressor 1051 'mac' => array( 1052 'pattern' => '^MAC ', 1053 'group' => 'audio', 1054 'module' => 'monkey', 1055 'mime_type' => 'audio/x-monkeys-audio', 1056 ), 1057 1058 // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available 1059 // // MOD - audio - MODule (assorted sub-formats) 1060 // 'mod' => array( 1061 // 'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)', 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\\+|[\\x00\\x01\\x10\\x11\\x40\\x41\\x50\\x51\\x80\\x81\\x90\\x91\\xC0\\xC1\\xD0\\xD1][\\x20-\\x37][\\x00\\x20\\x40\\x60\\x80\\xA0\\xC0\\xE0])', 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 1468 // Misc other formats 1469 1470 // PAR2 - data - Parity Volume Set Specification 2.0 1471 'par2' => array ( 1472 'pattern' => '^PAR2\\x00PKT', 1473 'group' => 'misc', 1474 'module' => 'par2', 1475 'mime_type' => 'application/octet-stream', 1476 'fail_id3' => 'ERROR', 1477 'fail_ape' => 'ERROR', 1478 ), 1479 1480 // PDF - data - Portable Document Format 1481 'pdf' => array( 1482 'pattern' => '^\\x25PDF', 1483 'group' => 'misc', 1484 'module' => 'pdf', 1485 'mime_type' => 'application/pdf', 1486 'fail_id3' => 'ERROR', 1487 'fail_ape' => 'ERROR', 1488 ), 1489 1490 // MSOFFICE - data - ZIP compressed data 1491 'msoffice' => array( 1492 'pattern' => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document 1493 'group' => 'misc', 1494 'module' => 'msoffice', 1495 'mime_type' => 'application/octet-stream', 1496 'fail_id3' => 'ERROR', 1497 'fail_ape' => 'ERROR', 1498 ), 1499 1500 // TORRENT - .torrent 1501 'torrent' => array( 1502 'pattern' => '^(d8\\:announce|d7\\:comment)', 1503 'group' => 'misc', 1504 'module' => 'torrent', 1505 'mime_type' => 'application/x-bittorrent', 1506 'fail_id3' => 'ERROR', 1507 'fail_ape' => 'ERROR', 1508 ), 1509 1510 // CUE - data - CUEsheet (index to single-file disc images) 1511 'cue' => array( 1512 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents 1513 'group' => 'misc', 1514 'module' => 'cue', 1515 'mime_type' => 'application/octet-stream', 1516 ), 1517 1518 ); 1519 } 1520 1521 return $format_info; 1522 } 1523 1524 /** 1525 * @param string $filedata 1526 * @param string $filename 1527 * 1528 * @return mixed|false 1529 */ 1530 public function GetFileFormat(&$filedata, $filename='') { 1531 // this function will determine the format of a file based on usually 1532 // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG, 1533 // and in the case of ISO CD image, 6 bytes offset 32kb from the start 1534 // of the file). 1535 1536 // Identify file format - loop through $format_info and detect with reg expr 1537 foreach ($this->GetFileFormatArray() as $format_name => $info) { 1538 // The /s switch on preg_match() forces preg_match() NOT to treat 1539 // newline (0x0A) characters as special chars but do a binary match 1540 if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) { 1541 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1542 return $info; 1543 } 1544 } 1545 1546 1547 if (preg_match('#\\.mp[123a]$#i', $filename)) { 1548 // Too many mp3 encoders on the market put garbage in front of mpeg files 1549 // use assume format on these if format detection failed 1550 $GetFileFormatArray = $this->GetFileFormatArray(); 1551 $info = $GetFileFormatArray['mp3']; 1552 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1553 return $info; 1554 } elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) { 1555 // there's not really a useful consistent "magic" at the beginning of .cue files to identify them 1556 // so until I think of something better, just go by filename if all other format checks fail 1557 // and verify there's at least one instance of "TRACK xx AUDIO" in the file 1558 $GetFileFormatArray = $this->GetFileFormatArray(); 1559 $info = $GetFileFormatArray['cue']; 1560 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1561 return $info; 1562 } 1563 1564 return false; 1565 } 1566 1567 /** 1568 * Converts array to $encoding charset from $this->encoding. 1569 * 1570 * @param array $array 1571 * @param string $encoding 1572 */ 1573 public function CharConvert(&$array, $encoding) { 1574 1575 // identical encoding - end here 1576 if ($encoding == $this->encoding) { 1577 return; 1578 } 1579 1580 // loop thru array 1581 foreach ($array as $key => $value) { 1582 1583 // go recursive 1584 if (is_array($value)) { 1585 $this->CharConvert($array[$key], $encoding); 1586 } 1587 1588 // convert string 1589 elseif (is_string($value)) { 1590 $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value)); 1591 } 1592 } 1593 } 1594 1595 /** 1596 * @return bool 1597 */ 1598 public function HandleAllTags() { 1599 1600 // key name => array (tag name, character encoding) 1601 static $tags; 1602 if (empty($tags)) { 1603 $tags = array( 1604 'asf' => array('asf' , 'UTF-16LE'), 1605 'midi' => array('midi' , 'ISO-8859-1'), 1606 'nsv' => array('nsv' , 'ISO-8859-1'), 1607 'ogg' => array('vorbiscomment' , 'UTF-8'), 1608 'png' => array('png' , 'UTF-8'), 1609 'tiff' => array('tiff' , 'ISO-8859-1'), 1610 'quicktime' => array('quicktime' , 'UTF-8'), 1611 'real' => array('real' , 'ISO-8859-1'), 1612 'vqf' => array('vqf' , 'ISO-8859-1'), 1613 'zip' => array('zip' , 'ISO-8859-1'), 1614 'riff' => array('riff' , 'ISO-8859-1'), 1615 'lyrics3' => array('lyrics3' , 'ISO-8859-1'), 1616 'id3v1' => array('id3v1' , $this->encoding_id3v1), 1617 '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 1618 'ape' => array('ape' , 'UTF-8'), 1619 'cue' => array('cue' , 'ISO-8859-1'), 1620 'matroska' => array('matroska' , 'UTF-8'), 1621 'flac' => array('vorbiscomment' , 'UTF-8'), 1622 'divxtag' => array('divx' , 'ISO-8859-1'), 1623 'iptc' => array('iptc' , 'ISO-8859-1'), 1624 'dsdiff' => array('dsdiff' , 'ISO-8859-1'), 1625 ); 1626 } 1627 1628 // loop through comments array 1629 foreach ($tags as $comment_name => $tagname_encoding_array) { 1630 list($tag_name, $encoding) = $tagname_encoding_array; 1631 1632 // fill in default encoding type if not already present 1633 if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) { 1634 $this->info[$comment_name]['encoding'] = $encoding; 1635 } 1636 1637 // copy comments if key name set 1638 if (!empty($this->info[$comment_name]['comments'])) { 1639 foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { 1640 foreach ($valuearray as $key => $value) { 1641 if (is_string($value)) { 1642 $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! 1643 } 1644 if (isset($value) && $value !== "") { 1645 if (!is_numeric($key)) { 1646 $this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value; 1647 } else { 1648 $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; 1649 } 1650 } 1651 } 1652 if ($tag_key == 'picture') { 1653 // 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 1654 unset($this->info[$comment_name]['comments'][$tag_key]); 1655 } 1656 } 1657 1658 if (!isset($this->info['tags'][$tag_name])) { 1659 // comments are set but contain nothing but empty strings, so skip 1660 continue; 1661 } 1662 1663 $this->CharConvert($this->info['tags'][$tag_name], $this->info[$comment_name]['encoding']); // only copy gets converted! 1664 1665 if ($this->option_tags_html) { 1666 foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { 1667 if ($tag_key == 'picture') { 1668 // Do not to try to convert binary picture data to HTML 1669 // https://github.com/JamesHeinrich/getID3/issues/178 1670 continue; 1671 } 1672 $this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']); 1673 } 1674 } 1675 1676 } 1677 1678 } 1679 1680 // 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 1681 if (!empty($this->info['tags'])) { 1682 $unset_keys = array('tags', 'tags_html'); 1683 foreach ($this->info['tags'] as $tagtype => $tagarray) { 1684 foreach ($tagarray as $tagname => $tagdata) { 1685 if ($tagname == 'picture') { 1686 foreach ($tagdata as $key => $tagarray) { 1687 $this->info['comments']['picture'][] = $tagarray; 1688 if (isset($tagarray['data']) && isset($tagarray['image_mime'])) { 1689 if (isset($this->info['tags'][$tagtype][$tagname][$key])) { 1690 unset($this->info['tags'][$tagtype][$tagname][$key]); 1691 } 1692 if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) { 1693 unset($this->info['tags_html'][$tagtype][$tagname][$key]); 1694 } 1695 } 1696 } 1697 } 1698 } 1699 foreach ($unset_keys as $unset_key) { 1700 // remove possible empty keys from (e.g. [tags][id3v2][picture]) 1701 if (empty($this->info[$unset_key][$tagtype]['picture'])) { 1702 unset($this->info[$unset_key][$tagtype]['picture']); 1703 } 1704 if (empty($this->info[$unset_key][$tagtype])) { 1705 unset($this->info[$unset_key][$tagtype]); 1706 } 1707 if (empty($this->info[$unset_key])) { 1708 unset($this->info[$unset_key]); 1709 } 1710 } 1711 // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture]) 1712 if (isset($this->info[$tagtype]['comments']['picture'])) { 1713 unset($this->info[$tagtype]['comments']['picture']); 1714 } 1715 if (empty($this->info[$tagtype]['comments'])) { 1716 unset($this->info[$tagtype]['comments']); 1717 } 1718 if (empty($this->info[$tagtype])) { 1719 unset($this->info[$tagtype]); 1720 } 1721 } 1722 } 1723 return true; 1724 } 1725 1726 /** 1727 * Calls getid3_lib::CopyTagsToComments() but passes in the option_tags_html setting from this instance of getID3 1728 * 1729 * @param array $ThisFileInfo 1730 * 1731 * @return bool 1732 */ 1733 public function CopyTagsToComments(&$ThisFileInfo) { 1734 return getid3_lib::CopyTagsToComments($ThisFileInfo, $this->option_tags_html); 1735 } 1736 1737 /** 1738 * @param string $algorithm 1739 * 1740 * @return array|bool 1741 */ 1742 public function getHashdata($algorithm) { 1743 switch ($algorithm) { 1744 case 'md5': 1745 case 'sha1': 1746 break; 1747 1748 default: 1749 return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()'); 1750 } 1751 1752 if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) { 1753 1754 // We cannot get an identical md5_data value for Ogg files where the comments 1755 // span more than 1 Ogg page (compared to the same audio data with smaller 1756 // comments) using the normal getID3() method of MD5'ing the data between the 1757 // end of the comments and the end of the file (minus any trailing tags), 1758 // because the page sequence numbers of the pages that the audio data is on 1759 // do not match. Under normal circumstances, where comments are smaller than 1760 // the nominal 4-8kB page size, then this is not a problem, but if there are 1761 // very large comments, the only way around it is to strip off the comment 1762 // tags with vorbiscomment and MD5 that file. 1763 // This procedure must be applied to ALL Ogg files, not just the ones with 1764 // comments larger than 1 page, because the below method simply MD5's the 1765 // whole file with the comments stripped, not just the portion after the 1766 // comments block (which is the standard getID3() method. 1767 1768 // The above-mentioned problem of comments spanning multiple pages and changing 1769 // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but 1770 // currently vorbiscomment only works on OggVorbis files. 1771 1772 // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved 1773 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { 1774 1775 $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'); 1776 $this->info[$algorithm.'_data'] = false; 1777 1778 } else { 1779 1780 // Prevent user from aborting script 1781 $old_abort = ignore_user_abort(true); 1782 1783 // Create empty file 1784 $empty = tempnam(GETID3_TEMP_DIR, 'getID3'); 1785 touch($empty); 1786 1787 // Use vorbiscomment to make temp file without comments 1788 $temp = tempnam(GETID3_TEMP_DIR, 'getID3'); 1789 $file = $this->info['filenamepath']; 1790 1791 if (GETID3_OS_ISWINDOWS) { 1792 1793 if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { 1794 1795 $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"'; 1796 $VorbisCommentError = `$commandline`; 1797 1798 } else { 1799 1800 $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; 1801 1802 } 1803 1804 } else { 1805 1806 $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1'; 1807 $VorbisCommentError = `$commandline`; 1808 1809 } 1810 1811 if (!empty($VorbisCommentError)) { 1812 1813 $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); 1814 $this->info[$algorithm.'_data'] = false; 1815 1816 } else { 1817 1818 // Get hash of newly created file 1819 switch ($algorithm) { 1820 case 'md5': 1821 $this->info[$algorithm.'_data'] = md5_file($temp); 1822 break; 1823 1824 case 'sha1': 1825 $this->info[$algorithm.'_data'] = sha1_file($temp); 1826 break; 1827 } 1828 } 1829 1830 // Clean up 1831 unlink($empty); 1832 unlink($temp); 1833 1834 // Reset abort setting 1835 ignore_user_abort($old_abort); 1836 1837 } 1838 1839 } else { 1840 1841 if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) { 1842 1843 // get hash from part of file 1844 $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm); 1845 1846 } else { 1847 1848 // get hash from whole file 1849 switch ($algorithm) { 1850 case 'md5': 1851 $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']); 1852 break; 1853 1854 case 'sha1': 1855 $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']); 1856 break; 1857 } 1858 } 1859 1860 } 1861 return true; 1862 } 1863 1864 public function ChannelsBitratePlaytimeCalculations() { 1865 1866 // set channelmode on audio 1867 if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) { 1868 // ignore 1869 } elseif ($this->info['audio']['channels'] == 1) { 1870 $this->info['audio']['channelmode'] = 'mono'; 1871 } elseif ($this->info['audio']['channels'] == 2) { 1872 $this->info['audio']['channelmode'] = 'stereo'; 1873 } 1874 1875 // Calculate combined bitrate - audio + video 1876 $CombinedBitrate = 0; 1877 $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0); 1878 $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0); 1879 if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) { 1880 $this->info['bitrate'] = $CombinedBitrate; 1881 } 1882 //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) { 1883 // // for example, VBR MPEG video files cannot determine video bitrate: 1884 // // should not set overall bitrate and playtime from audio bitrate only 1885 // unset($this->info['bitrate']); 1886 //} 1887 1888 // video bitrate undetermined, but calculable 1889 if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) { 1890 // if video bitrate not set 1891 if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) { 1892 // AND if audio bitrate is set to same as overall bitrate 1893 if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) { 1894 // AND if playtime is set 1895 if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) { 1896 // AND if AV data offset start/end is known 1897 // THEN we can calculate the video bitrate 1898 $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']); 1899 $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate']; 1900 } 1901 } 1902 } 1903 } 1904 1905 if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) { 1906 $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate']; 1907 } 1908 1909 if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) { 1910 $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']; 1911 } 1912 if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) { 1913 if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) { 1914 // audio only 1915 $this->info['audio']['bitrate'] = $this->info['bitrate']; 1916 } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) { 1917 // video only 1918 $this->info['video']['bitrate'] = $this->info['bitrate']; 1919 } 1920 } 1921 1922 // Set playtime string 1923 if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) { 1924 $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']); 1925 } 1926 } 1927 1928 /** 1929 * @return bool 1930 */ 1931 public function CalculateCompressionRatioVideo() { 1932 if (empty($this->info['video'])) { 1933 return false; 1934 } 1935 if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) { 1936 return false; 1937 } 1938 if (empty($this->info['video']['bits_per_sample'])) { 1939 return false; 1940 } 1941 1942 switch ($this->info['video']['dataformat']) { 1943 case 'bmp': 1944 case 'gif': 1945 case 'jpeg': 1946 case 'jpg': 1947 case 'png': 1948 case 'tiff': 1949 $FrameRate = 1; 1950 $PlaytimeSeconds = 1; 1951 $BitrateCompressed = $this->info['filesize'] * 8; 1952 break; 1953 1954 default: 1955 if (!empty($this->info['video']['frame_rate'])) { 1956 $FrameRate = $this->info['video']['frame_rate']; 1957 } else { 1958 return false; 1959 } 1960 if (!empty($this->info['playtime_seconds'])) { 1961 $PlaytimeSeconds = $this->info['playtime_seconds']; 1962 } else { 1963 return false; 1964 } 1965 if (!empty($this->info['video']['bitrate'])) { 1966 $BitrateCompressed = $this->info['video']['bitrate']; 1967 } else { 1968 return false; 1969 } 1970 break; 1971 } 1972 $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate; 1973 1974 $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed; 1975 return true; 1976 } 1977 1978 /** 1979 * @return bool 1980 */ 1981 public function CalculateCompressionRatioAudio() { 1982 if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) { 1983 return false; 1984 } 1985 $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)); 1986 1987 if (!empty($this->info['audio']['streams'])) { 1988 foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) { 1989 if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) { 1990 $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16)); 1991 } 1992 } 1993 } 1994 return true; 1995 } 1996 1997 /** 1998 * @return bool 1999 */ 2000 public function CalculateReplayGain() { 2001 if (isset($this->info['replay_gain'])) { 2002 if (!isset($this->info['replay_gain']['reference_volume'])) { 2003 $this->info['replay_gain']['reference_volume'] = 89.0; 2004 } 2005 if (isset($this->info['replay_gain']['track']['adjustment'])) { 2006 $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; 2007 } 2008 if (isset($this->info['replay_gain']['album']['adjustment'])) { 2009 $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment']; 2010 } 2011 2012 if (isset($this->info['replay_gain']['track']['peak'])) { 2013 $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']); 2014 } 2015 if (isset($this->info['replay_gain']['album']['peak'])) { 2016 $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']); 2017 } 2018 } 2019 return true; 2020 } 2021 2022 /** 2023 * @return bool 2024 */ 2025 public function ProcessAudioStreams() { 2026 if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { 2027 if (!isset($this->info['audio']['streams'])) { 2028 foreach ($this->info['audio'] as $key => $value) { 2029 if ($key != 'streams') { 2030 $this->info['audio']['streams'][0][$key] = $value; 2031 } 2032 } 2033 } 2034 } 2035 return true; 2036 } 2037 2038 /** 2039 * @return string|bool 2040 */ 2041 public function getid3_tempnam() { 2042 return tempnam($this->tempdir, 'gI3'); 2043 } 2044 2045 /** 2046 * @param string $name 2047 * 2048 * @return bool 2049 * 2050 * @throws getid3_exception 2051 */ 2052 public function include_module($name) { 2053 //if (!file_exists($this->include_path.'module.'.$name.'.php')) { 2054 if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { 2055 throw new getid3_exception('Required module.'.$name.'.php is missing.'); 2056 } 2057 include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php'); 2058 return true; 2059 } 2060 2061 /** 2062 * @param string $filename 2063 * 2064 * @return bool 2065 */ 2066 public static function is_writable ($filename) { 2067 $ret = is_writable($filename); 2068 if (!$ret) { 2069 $perms = fileperms($filename); 2070 $ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002); 2071 } 2072 return $ret; 2073 } 2074 2075 } 2076 2077 2078 abstract class getid3_handler 2079 { 2080 2081 /** 2082 * @var getID3 2083 */ 2084 protected $getid3; // pointer 2085 2086 /** 2087 * Analyzing filepointer or string. 2088 * 2089 * @var bool 2090 */ 2091 protected $data_string_flag = false; 2092 2093 /** 2094 * String to analyze. 2095 * 2096 * @var string 2097 */ 2098 protected $data_string = ''; 2099 2100 /** 2101 * Seek position in string. 2102 * 2103 * @var int 2104 */ 2105 protected $data_string_position = 0; 2106 2107 /** 2108 * String length. 2109 * 2110 * @var int 2111 */ 2112 protected $data_string_length = 0; 2113 2114 /** 2115 * @var string 2116 */ 2117 private $dependency_to; 2118 2119 /** 2120 * getid3_handler constructor. 2121 * 2122 * @param getID3 $getid3 2123 * @param string $call_module 2124 */ 2125 public function __construct(getID3 $getid3, $call_module=null) { 2126 $this->getid3 = $getid3; 2127 2128 if ($call_module) { 2129 $this->dependency_to = str_replace('getid3_', '', $call_module); 2130 } 2131 } 2132 2133 /** 2134 * Analyze from file pointer. 2135 * 2136 * @return bool 2137 */ 2138 abstract public function Analyze(); 2139 2140 /** 2141 * Analyze from string instead. 2142 * 2143 * @param string $string 2144 */ 2145 public function AnalyzeString($string) { 2146 // Enter string mode 2147 $this->setStringMode($string); 2148 2149 // Save info 2150 $saved_avdataoffset = $this->getid3->info['avdataoffset']; 2151 $saved_avdataend = $this->getid3->info['avdataend']; 2152 $saved_filesize = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call 2153 2154 // Reset some info 2155 $this->getid3->info['avdataoffset'] = 0; 2156 $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = $this->data_string_length; 2157 2158 // Analyze 2159 $this->Analyze(); 2160 2161 // Restore some info 2162 $this->getid3->info['avdataoffset'] = $saved_avdataoffset; 2163 $this->getid3->info['avdataend'] = $saved_avdataend; 2164 $this->getid3->info['filesize'] = $saved_filesize; 2165 2166 // Exit string mode 2167 $this->data_string_flag = false; 2168 } 2169 2170 /** 2171 * @param string $string 2172 */ 2173 public function setStringMode($string) { 2174 $this->data_string_flag = true; 2175 $this->data_string = $string; 2176 $this->data_string_length = strlen($string); 2177 } 2178 2179 /** 2180 * @return int|bool 2181 */ 2182 protected function ftell() { 2183 if ($this->data_string_flag) { 2184 return $this->data_string_position; 2185 } 2186 return ftell($this->getid3->fp); 2187 } 2188 2189 /** 2190 * @param int $bytes 2191 * 2192 * @return string|false 2193 * 2194 * @throws getid3_exception 2195 */ 2196 protected function fread($bytes) { 2197 if ($this->data_string_flag) { 2198 $this->data_string_position += $bytes; 2199 return substr($this->data_string, $this->data_string_position - $bytes, $bytes); 2200 } 2201 $pos = $this->ftell() + $bytes; 2202 if (!getid3_lib::intValueSupported($pos)) { 2203 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10); 2204 } 2205 2206 //return fread($this->getid3->fp, $bytes); 2207 /* 2208 * https://www.getid3.org/phpBB3/viewtopic.php?t=1930 2209 * "I found out that the root cause for the problem was how getID3 uses the PHP system function fread(). 2210 * It seems to assume that fread() would always return as many bytes as were requested. 2211 * 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. 2212 * The call may return only part of the requested data and a new call is needed to get more." 2213 */ 2214 $contents = ''; 2215 do { 2216 //if (($this->getid3->memory_limit > 0) && ($bytes > $this->getid3->memory_limit)) { 2217 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)" 2218 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') that is more than available PHP memory ('.$this->getid3->memory_limit.')', 10); 2219 } 2220 $part = fread($this->getid3->fp, $bytes); 2221 $partLength = strlen($part); 2222 $bytes -= $partLength; 2223 $contents .= $part; 2224 } while (($bytes > 0) && ($partLength > 0)); 2225 return $contents; 2226 } 2227 2228 /** 2229 * @param int $bytes 2230 * @param int $whence 2231 * 2232 * @return int 2233 * 2234 * @throws getid3_exception 2235 */ 2236 protected function fseek($bytes, $whence=SEEK_SET) { 2237 if ($this->data_string_flag) { 2238 switch ($whence) { 2239 case SEEK_SET: 2240 $this->data_string_position = $bytes; 2241 break; 2242 2243 case SEEK_CUR: 2244 $this->data_string_position += $bytes; 2245 break; 2246 2247 case SEEK_END: 2248 $this->data_string_position = $this->data_string_length + $bytes; 2249 break; 2250 } 2251 return 0; // fseek returns 0 on success 2252 } 2253 2254 $pos = $bytes; 2255 if ($whence == SEEK_CUR) { 2256 $pos = $this->ftell() + $bytes; 2257 } elseif ($whence == SEEK_END) { 2258 $pos = $this->getid3->info['filesize'] + $bytes; 2259 } 2260 if (!getid3_lib::intValueSupported($pos)) { 2261 throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10); 2262 } 2263 2264 // https://github.com/JamesHeinrich/getID3/issues/327 2265 $result = fseek($this->getid3->fp, $bytes, $whence); 2266 if ($result !== 0) { // fseek returns 0 on success 2267 throw new getid3_exception('cannot fseek('.$pos.'). resource/stream does not appear to support seeking', 10); 2268 } 2269 return $result; 2270 } 2271 2272 /** 2273 * @return string|false 2274 * 2275 * @throws getid3_exception 2276 */ 2277 protected function fgets() { 2278 // must be able to handle CR/LF/CRLF but not read more than one lineend 2279 $buffer = ''; // final string we will return 2280 $prevchar = ''; // save previously-read character for end-of-line checking 2281 if ($this->data_string_flag) { 2282 while (true) { 2283 $thischar = substr($this->data_string, $this->data_string_position++, 1); 2284 if (($prevchar == "\r") && ($thischar != "\n")) { 2285 // read one byte too many, back up 2286 $this->data_string_position--; 2287 break; 2288 } 2289 $buffer .= $thischar; 2290 if ($thischar == "\n") { 2291 break; 2292 } 2293 if ($this->data_string_position >= $this->data_string_length) { 2294 // EOF 2295 break; 2296 } 2297 $prevchar = $thischar; 2298 } 2299 2300 } else { 2301 2302 // Ideally we would just use PHP's fgets() function, however... 2303 // it does not behave consistently with regards to mixed line endings, may be system-dependent 2304 // and breaks entirely when given a file with mixed \r vs \n vs \r\n line endings (e.g. some PDFs) 2305 //return fgets($this->getid3->fp); 2306 while (true) { 2307 $thischar = fgetc($this->getid3->fp); 2308 if (($prevchar == "\r") && ($thischar != "\n")) { 2309 // read one byte too many, back up 2310 fseek($this->getid3->fp, -1, SEEK_CUR); 2311 break; 2312 } 2313 $buffer .= $thischar; 2314 if ($thischar == "\n") { 2315 break; 2316 } 2317 if (feof($this->getid3->fp)) { 2318 break; 2319 } 2320 $prevchar = $thischar; 2321 } 2322 2323 } 2324 return $buffer; 2325 } 2326 2327 /** 2328 * @return bool 2329 */ 2330 protected function feof() { 2331 if ($this->data_string_flag) { 2332 return $this->data_string_position >= $this->data_string_length; 2333 } 2334 return feof($this->getid3->fp); 2335 } 2336 2337 /** 2338 * @param string $module 2339 * 2340 * @return bool 2341 */ 2342 final protected function isDependencyFor($module) { 2343 return $this->dependency_to == $module; 2344 } 2345 2346 /** 2347 * @param string $text 2348 * 2349 * @return bool 2350 */ 2351 protected function error($text) { 2352 $this->getid3->info['error'][] = $text; 2353 2354 return false; 2355 } 2356 2357 /** 2358 * @param string $text 2359 * 2360 * @return bool 2361 */ 2362 protected function warning($text) { 2363 return $this->getid3->warning($text); 2364 } 2365 2366 /** 2367 * @param string $text 2368 */ 2369 protected function notice($text) { 2370 // does nothing for now 2371 } 2372 2373 /** 2374 * @param string $name 2375 * @param int $offset 2376 * @param int $length 2377 * @param string $image_mime 2378 * 2379 * @return string|null 2380 * 2381 * @throws Exception 2382 * @throws getid3_exception 2383 */ 2384 public function saveAttachment($name, $offset, $length, $image_mime=null) { 2385 $fp_dest = null; 2386 $dest = null; 2387 try { 2388 2389 // do not extract at all 2390 if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) { 2391 2392 $attachment = null; // do not set any 2393 2394 // extract to return array 2395 } elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { 2396 2397 $this->fseek($offset); 2398 $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory 2399 if ($attachment === false || strlen($attachment) != $length) { 2400 throw new Exception('failed to read attachment data'); 2401 } 2402 2403 // assume directory path is given 2404 } else { 2405 2406 // set up destination path 2407 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); 2408 if (!is_dir($dir) || !getID3::is_writable($dir)) { // check supplied directory 2409 throw new Exception('supplied path ('.$dir.') does not exist, or is not writable'); 2410 } 2411 $dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : ''); 2412 2413 // create dest file 2414 if (($fp_dest = fopen($dest, 'wb')) == false) { 2415 throw new Exception('failed to create file '.$dest); 2416 } 2417 2418 // copy data 2419 $this->fseek($offset); 2420 $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size()); 2421 $bytesleft = $length; 2422 while ($bytesleft > 0) { 2423 if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) { 2424 throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space'); 2425 } 2426 $bytesleft -= $byteswritten; 2427 } 2428 2429 fclose($fp_dest); 2430 $attachment = $dest; 2431 2432 } 2433 2434 } catch (Exception $e) { 2435 2436 // close and remove dest file if created 2437 if (isset($fp_dest) && is_resource($fp_dest)) { 2438 fclose($fp_dest); 2439 } 2440 2441 if (isset($dest) && file_exists($dest)) { 2442 unlink($dest); 2443 } 2444 2445 // do not set any is case of error 2446 $attachment = null; 2447 $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage()); 2448 2449 } 2450 2451 // seek to the end of attachment 2452 $this->fseek($offset + $length); 2453 2454 return $attachment; 2455 } 2456 2457 } 2458 2459 2460 class getid3_exception extends Exception 2461 { 2462 public $message; 2463 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Dec 22 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |