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