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