[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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


Generated : Fri Oct 10 08:20:03 2025 Cross-referenced by PHPXref