[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ID3/ -> getid3.php (source)

   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  }


Generated: Sat Nov 28 01:00:03 2020 Cross-referenced by PHPXref 0.7.1