[ 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.26-202609042051';
 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 = (int) $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 = (int) $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()) {
 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 (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                  } else {
 684                      $this->warning('skipping check for '.$tag_name.' tags since option_tag_'.$tag_name.'=FALSE');
 685                  }
 686              }
 687              if (isset($this->info['id3v2']['tag_offset_start'])) {
 688                  $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']);
 689              }
 690              foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
 691                  if (isset($this->info[$tag_key]['tag_offset_start'])) {
 692                      $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']);
 693                  }
 694              }
 695  
 696              // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
 697              if (!$this->option_tag_id3v2) {
 698                  fseek($this->fp, 0);
 699                  $header = fread($this->fp, 10);
 700                  if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) {
 701                      $this->info['id3v2']['header']        = true;
 702                      $this->info['id3v2']['majorversion']  = ord($header[3]);
 703                      $this->info['id3v2']['minorversion']  = ord($header[4]);
 704                      $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
 705                  }
 706              }
 707  
 708              // read 32 kb file data
 709              fseek($this->fp, $this->info['avdataoffset']);
 710              $formattest = fread($this->fp, 32774);
 711  
 712              // determine format
 713              $determined_format = $this->GetFileFormat($formattest, ($original_filename ? $original_filename : $filename));
 714  
 715              // unable to determine file format
 716              if (!$determined_format) {
 717                  fclose($this->fp);
 718                  return $this->error('unable to determine file format');
 719              }
 720  
 721              // check for illegal ID3 tags
 722              if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) {
 723                  if ($determined_format['fail_id3'] === 'ERROR') {
 724                      fclose($this->fp);
 725                      return $this->error('ID3 tags not allowed on this file type.');
 726                  } elseif ($determined_format['fail_id3'] === 'WARNING') {
 727                      $this->warning('ID3 tags not allowed on this file type.');
 728                  }
 729              }
 730  
 731              // check for illegal APE tags
 732              if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) {
 733                  if ($determined_format['fail_ape'] === 'ERROR') {
 734                      fclose($this->fp);
 735                      return $this->error('APE tags not allowed on this file type.');
 736                  } elseif ($determined_format['fail_ape'] === 'WARNING') {
 737                      $this->warning('APE tags not allowed on this file type.');
 738                  }
 739              }
 740  
 741              // set mime type
 742              $this->info['mime_type'] = $determined_format['mime_type'];
 743  
 744              // supported format signature pattern detected, but module deleted
 745              if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) {
 746                  fclose($this->fp);
 747                  return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
 748              }
 749  
 750              // module requires mb_convert_encoding/iconv support
 751              // Check encoding/iconv support
 752              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'))) {
 753                  $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. ';
 754                  if (GETID3_OS_ISWINDOWS) {
 755                      $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';
 756                  } else {
 757                      $errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch';
 758                  }
 759                  return $this->error($errormessage);
 760              }
 761  
 762              // include module
 763              include_once(GETID3_INCLUDEPATH.$determined_format['include']);
 764  
 765              // instantiate module class
 766              $class_name = 'getid3_'.$determined_format['module'];
 767              if (!class_exists($class_name)) {
 768                  return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
 769              }
 770              $class = new $class_name($this);
 771  
 772              // set module-specific options
 773              foreach (get_object_vars($this) as $getid3_object_vars_key => $getid3_object_vars_value) {
 774                  if (preg_match('#^options_([^_]+)_([^_]+)_(.+)$#i', $getid3_object_vars_key, $matches)) {
 775                      list($dummy, $GOVgroup, $GOVmodule, $GOVsetting) = $matches;
 776                      $GOVgroup = (($GOVgroup == 'audiovideo') ? 'audio-video' : $GOVgroup); // variable names can only contain 0-9a-z_ so standardize here
 777                      if (($GOVgroup == $determined_format['group']) && ($GOVmodule == $determined_format['module'])) {
 778                          $class->$GOVsetting = $getid3_object_vars_value;
 779                      }
 780                  }
 781              }
 782  
 783              $class->Analyze();
 784              unset($class);
 785  
 786              // close file
 787              fclose($this->fp);
 788  
 789              // process all tags - copy to 'tags' and convert charsets
 790              if ($this->option_tags_process) {
 791                  $this->HandleAllTags();
 792              }
 793  
 794              // perform more calculations
 795              if ($this->option_extra_info) {
 796                  $this->ChannelsBitratePlaytimeCalculations();
 797                  $this->CalculateCompressionRatioVideo();
 798                  $this->CalculateCompressionRatioAudio();
 799                  $this->CalculateReplayGain();
 800                  $this->ProcessAudioStreams();
 801              }
 802  
 803              // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
 804              if ($this->option_md5_data) {
 805                  // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too
 806                  if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) {
 807                      $this->getHashdata('md5');
 808                  }
 809              }
 810  
 811              // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
 812              if ($this->option_sha1_data) {
 813                  $this->getHashdata('sha1');
 814              }
 815  
 816              // remove undesired keys
 817              $this->CleanUp();
 818  
 819          } catch (Exception $e) {
 820              $this->error('Caught exception: '.$e->getMessage());
 821          }
 822  
 823          // return info array
 824          return $this->info;
 825      }
 826  
 827  
 828      /**
 829       * Error handling.
 830       *
 831       * @param string $message
 832       *
 833       * @return array
 834       */
 835  	public function error($message) {
 836          $this->CleanUp();
 837          if (!isset($this->info['error'])) {
 838              $this->info['error'] = array();
 839          }
 840          $this->info['error'][] = $message;
 841          return $this->info;
 842      }
 843  
 844  
 845      /**
 846       * Warning handling.
 847       *
 848       * @param string $message
 849       *
 850       * @return bool
 851       */
 852  	public function warning($message) {
 853          $this->info['warning'][] = $message;
 854          return true;
 855      }
 856  
 857  
 858      /**
 859       * @return bool
 860       */
 861  	private function CleanUp() {
 862  
 863          // remove possible empty keys
 864          $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
 865          foreach ($AVpossibleEmptyKeys as $dummy => $key) {
 866              if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) {
 867                  unset($this->info['audio'][$key]);
 868              }
 869              if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) {
 870                  unset($this->info['video'][$key]);
 871              }
 872          }
 873  
 874          // remove empty root keys
 875          if (!empty($this->info)) {
 876              foreach ($this->info as $key => $value) {
 877                  if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) {
 878                      unset($this->info[$key]);
 879                  }
 880              }
 881          }
 882  
 883          // remove meaningless entries from unknown-format files
 884          if (empty($this->info['fileformat'])) {
 885              if (isset($this->info['avdataoffset'])) {
 886                  unset($this->info['avdataoffset']);
 887              }
 888              if (isset($this->info['avdataend'])) {
 889                  unset($this->info['avdataend']);
 890              }
 891          }
 892  
 893          // remove possible duplicated identical entries
 894          if (!empty($this->info['error'])) {
 895              $this->info['error'] = array_values(array_unique($this->info['error']));
 896          }
 897          if (!empty($this->info['warning'])) {
 898              $this->info['warning'] = array_values(array_unique($this->info['warning']));
 899          }
 900  
 901          // remove "global variable" type keys
 902          unset($this->info['php_memory_limit']);
 903  
 904          return true;
 905      }
 906  
 907      /**
 908       * Return array containing information about all supported formats.
 909       *
 910       * @return array
 911       */
 912  	public function GetFileFormatArray() {
 913          static $format_info = array();
 914          if (empty($format_info)) {
 915              $format_info = array(
 916  
 917                  // Audio formats
 918  
 919                  // AC-3   - audio      - Dolby AC-3 / Dolby Digital
 920                  'ac3'  => array(
 921                              'pattern'   => '^\\x0B\\x77',
 922                              'group'     => 'audio',
 923                              'module'    => 'ac3',
 924                              'mime_type' => 'audio/ac3',
 925                          ),
 926  
 927                  // AAC  - audio       - Advanced Audio Coding (AAC) - ADIF format
 928                  'adif' => array(
 929                              'pattern'   => '^ADIF',
 930                              'group'     => 'audio',
 931                              'module'    => 'aac',
 932                              'mime_type' => 'audio/aac',
 933                              'fail_ape'  => 'WARNING',
 934                          ),
 935  
 936  /*
 937                  // AA   - audio       - Audible Audiobook
 938                  'aa'   => array(
 939                              'pattern'   => '^.{4}\\x57\\x90\\x75\\x36',
 940                              'group'     => 'audio',
 941                              'module'    => 'aa',
 942                              'mime_type' => 'audio/audible',
 943                          ),
 944  */
 945                  // AAC  - audio       - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
 946                  'adts' => array(
 947                              'pattern'   => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]',
 948                              'group'     => 'audio',
 949                              'module'    => 'aac',
 950                              'mime_type' => 'audio/aac',
 951                              'fail_ape'  => 'WARNING',
 952                          ),
 953  
 954  
 955                  // AU   - audio       - NeXT/Sun AUdio (AU)
 956                  'au'   => array(
 957                              'pattern'   => '^\\.snd',
 958                              'group'     => 'audio',
 959                              'module'    => 'au',
 960                              'mime_type' => 'audio/basic',
 961                          ),
 962  
 963                  // AMR  - audio       - Adaptive Multi Rate
 964                  'amr'  => array(
 965                              'pattern'   => '^\\x23\\x21AMR\\x0A', // #!AMR[0A]
 966                              'group'     => 'audio',
 967                              'module'    => 'amr',
 968                              'mime_type' => 'audio/amr',
 969                          ),
 970  
 971                  // AVR  - audio       - Audio Visual Research
 972                  'avr'  => array(
 973                              'pattern'   => '^2BIT',
 974                              'group'     => 'audio',
 975                              'module'    => 'avr',
 976                              'mime_type' => 'application/octet-stream',
 977                          ),
 978  
 979                  // BONK - audio       - Bonk v0.9+
 980                  'bonk' => array(
 981                              'pattern'   => '^\\x00(BONK|INFO|META| ID3)',
 982                              'group'     => 'audio',
 983                              'module'    => 'bonk',
 984                              'mime_type' => 'audio/xmms-bonk',
 985                          ),
 986  
 987                  // DSF  - audio       - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital
 988                  'dsf'  => array(
 989                              'pattern'   => '^DSD ',  // including trailing space: 44 53 44 20
 990                              'group'     => 'audio',
 991                              'module'    => 'dsf',
 992                              'mime_type' => 'audio/dsd',
 993                          ),
 994  
 995                  // DSS  - audio       - Digital Speech Standard
 996                  'dss'  => array(
 997                              'pattern'   => '^[\\x02-\\x08]ds[s2]',
 998                              'group'     => 'audio',
 999                              'module'    => 'dss',
1000                              'mime_type' => 'application/octet-stream',
1001                          ),
1002  
1003                  // DSDIFF - audio     - Direct Stream Digital Interchange File Format
1004                  'dsdiff' => array(
1005                              'pattern'   => '^FRM8',
1006                              'group'     => 'audio',
1007                              'module'    => 'dsdiff',
1008                              'mime_type' => 'audio/dsd',
1009                          ),
1010  
1011                  // DTS  - audio       - Dolby Theatre System
1012                  'dts'  => array(
1013                              'pattern'   => '^\\x7F\\xFE\\x80\\x01',
1014                              'group'     => 'audio',
1015                              'module'    => 'dts',
1016                              'mime_type' => 'audio/dts',
1017                          ),
1018  
1019                  // FLAC - audio       - Free Lossless Audio Codec
1020                  'flac' => array(
1021                              'pattern'   => '^fLaC',
1022                              'group'     => 'audio',
1023                              'module'    => 'flac',
1024                              'mime_type' => 'audio/flac',
1025                          ),
1026  
1027                  // LA   - audio       - Lossless Audio (LA)
1028                  'la'   => array(
1029                              'pattern'   => '^LA0[2-4]',
1030                              'group'     => 'audio',
1031                              'module'    => 'la',
1032                              'mime_type' => 'application/octet-stream',
1033                          ),
1034  
1035                  // LPAC - audio       - Lossless Predictive Audio Compression (LPAC)
1036                  'lpac' => array(
1037                              'pattern'   => '^LPAC',
1038                              'group'     => 'audio',
1039                              'module'    => 'lpac',
1040                              'mime_type' => 'application/octet-stream',
1041                          ),
1042  
1043                  // MIDI - audio       - MIDI (Musical Instrument Digital Interface)
1044                  'midi' => array(
1045                              'pattern'   => '^MThd',
1046                              'group'     => 'audio',
1047                              'module'    => 'midi',
1048                              'mime_type' => 'audio/midi',
1049                          ),
1050  
1051                  // MAC  - audio       - Monkey's Audio Compressor
1052                  'mac'  => array(
1053                              'pattern'   => '^MAC ',
1054                              'group'     => 'audio',
1055                              'module'    => 'monkey',
1056                              'mime_type' => 'audio/x-monkeys-audio',
1057                          ),
1058  
1059  
1060                  // MOD  - audio       - MODule (SoundTracker)
1061                  'mod'  => array(
1062                              //'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
1063                              'pattern'   => '^.{1080}(M\\.K\\.)',
1064                              'group'     => 'audio',
1065                              'module'    => 'mod',
1066                              'option'    => 'mod',
1067                              'mime_type' => 'audio/mod',
1068                          ),
1069  
1070                  // MOD  - audio       - MODule (Impulse Tracker)
1071                  'it'   => array(
1072                              'pattern'   => '^IMPM',
1073                              'group'     => 'audio',
1074                              'module'    => 'mod',
1075                              //'option'    => 'it',
1076                              'mime_type' => 'audio/it',
1077                          ),
1078  
1079                  // MOD  - audio       - MODule (eXtended Module, various sub-formats)
1080                  'xm'   => array(
1081                              'pattern'   => '^Extended Module',
1082                              'group'     => 'audio',
1083                              'module'    => 'mod',
1084                              //'option'    => 'xm',
1085                              'mime_type' => 'audio/xm',
1086                          ),
1087  
1088                  // MOD  - audio       - MODule (ScreamTracker)
1089                  's3m'  => array(
1090                              'pattern'   => '^.{44}SCRM',
1091                              'group'     => 'audio',
1092                              'module'    => 'mod',
1093                              //'option'    => 's3m',
1094                              'mime_type' => 'audio/s3m',
1095                          ),
1096  
1097                  // MPC  - audio       - Musepack / MPEGplus
1098                  'mpc'  => array(
1099                              'pattern'   => '^(MPCK|MP\\+)',
1100                              'group'     => 'audio',
1101                              'module'    => 'mpc',
1102                              'mime_type' => 'audio/x-musepack',
1103                          ),
1104  
1105                  // MP3  - audio       - MPEG-audio Layer 3 (very similar to AAC-ADTS)
1106                  'mp3'  => array(
1107                              '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]',
1108                              'group'     => 'audio',
1109                              'module'    => 'mp3',
1110                              'mime_type' => 'audio/mpeg',
1111                          ),
1112  
1113                  // OFR  - audio       - OptimFROG
1114                  'ofr'  => array(
1115                              'pattern'   => '^(\\*RIFF|OFR)',
1116                              'group'     => 'audio',
1117                              'module'    => 'optimfrog',
1118                              'mime_type' => 'application/octet-stream',
1119                          ),
1120  
1121                  // RKAU - audio       - RKive AUdio compressor
1122                  'rkau' => array(
1123                              'pattern'   => '^RKA',
1124                              'group'     => 'audio',
1125                              'module'    => 'rkau',
1126                              'mime_type' => 'application/octet-stream',
1127                          ),
1128  
1129                  // SHN  - audio       - Shorten
1130                  'shn'  => array(
1131                              'pattern'   => '^ajkg',
1132                              'group'     => 'audio',
1133                              'module'    => 'shorten',
1134                              'mime_type' => 'audio/xmms-shn',
1135                              'fail_id3'  => 'ERROR',
1136                              'fail_ape'  => 'ERROR',
1137                          ),
1138  
1139                  // TAK  - audio       - Tom's lossless Audio Kompressor
1140                  'tak'  => array(
1141                              'pattern'   => '^tBaK',
1142                              'group'     => 'audio',
1143                              'module'    => 'tak',
1144                              'mime_type' => 'application/octet-stream',
1145                          ),
1146  
1147                  // TTA  - audio       - TTA Lossless Audio Compressor (http://tta.corecodec.org)
1148                  'tta'  => array(
1149                              'pattern'   => '^TTA',  // could also be '^TTA(\\x01|\\x02|\\x03|2|1)'
1150                              'group'     => 'audio',
1151                              'module'    => 'tta',
1152                              'mime_type' => 'application/octet-stream',
1153                          ),
1154  
1155                  // VOC  - audio       - Creative Voice (VOC)
1156                  'voc'  => array(
1157                              'pattern'   => '^Creative Voice File',
1158                              'group'     => 'audio',
1159                              'module'    => 'voc',
1160                              'mime_type' => 'audio/voc',
1161                          ),
1162  
1163                  // VQF  - audio       - transform-domain weighted interleave Vector Quantization Format (VQF)
1164                  'vqf'  => array(
1165                              'pattern'   => '^TWIN',
1166                              'group'     => 'audio',
1167                              'module'    => 'vqf',
1168                              'mime_type' => 'application/octet-stream',
1169                          ),
1170  
1171                  // WV  - audio        - WavPack (v4.0+)
1172                  'wv'   => array(
1173                              'pattern'   => '^wvpk',
1174                              'group'     => 'audio',
1175                              'module'    => 'wavpack',
1176                              'mime_type' => 'application/octet-stream',
1177                          ),
1178  
1179  
1180                  // Audio-Video formats
1181  
1182                  // ASF  - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
1183                  'asf'  => array(
1184                              'pattern'   => '^\\x30\\x26\\xB2\\x75\\x8E\\x66\\xCF\\x11\\xA6\\xD9\\x00\\xAA\\x00\\x62\\xCE\\x6C',
1185                              'group'     => 'audio-video',
1186                              'module'    => 'asf',
1187                              'mime_type' => 'video/x-ms-asf',
1188                              'iconv_req' => false,
1189                          ),
1190  
1191                  // BINK - audio/video - Bink / Smacker
1192                  'bink' => array(
1193                              'pattern'   => '^(BIK|SMK)',
1194                              'group'     => 'audio-video',
1195                              'module'    => 'bink',
1196                              'mime_type' => 'application/octet-stream',
1197                          ),
1198  
1199                  // FLV  - audio/video - FLash Video
1200                  'flv' => array(
1201                              'pattern'   => '^FLV[\\x01]',
1202                              'group'     => 'audio-video',
1203                              'module'    => 'flv',
1204                              'mime_type' => 'video/x-flv',
1205                          ),
1206  
1207                  // IVF - audio/video - IVF
1208                  'ivf' => array(
1209                              'pattern'   => '^DKIF',
1210                              'group'     => 'audio-video',
1211                              'module'    => 'ivf',
1212                              'mime_type' => 'video/x-ivf',
1213                          ),
1214  
1215                  // MKAV - audio/video - Mastroka
1216                  'matroska' => array(
1217                              'pattern'   => '^\\x1A\\x45\\xDF\\xA3',
1218                              'group'     => 'audio-video',
1219                              'module'    => 'matroska',
1220                              'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
1221                          ),
1222  
1223                  // MPEG - audio/video - MPEG (Moving Pictures Experts Group)
1224                  'mpeg' => array(
1225                              'pattern'   => '^\\x00\\x00\\x01[\\xB3\\xBA]',
1226                              'group'     => 'audio-video',
1227                              'module'    => 'mpeg',
1228                              'mime_type' => 'video/mpeg',
1229                          ),
1230  
1231                  // NSV  - audio/video - Nullsoft Streaming Video (NSV)
1232                  'nsv'  => array(
1233                              'pattern'   => '^NSV[sf]',
1234                              'group'     => 'audio-video',
1235                              'module'    => 'nsv',
1236                              'mime_type' => 'application/octet-stream',
1237                          ),
1238  
1239                  // Ogg  - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
1240                  'ogg'  => array(
1241                              'pattern'   => '^OggS',
1242                              'group'     => 'audio',
1243                              'module'    => 'ogg',
1244                              'mime_type' => 'application/ogg',
1245                              'fail_id3'  => 'WARNING',
1246                              'fail_ape'  => 'WARNING',
1247                          ),
1248  
1249                  // QT   - audio/video - Quicktime
1250                  'quicktime' => array(
1251                              'pattern'   => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
1252                              'group'     => 'audio-video',
1253                              'module'    => 'quicktime',
1254                              'mime_type' => 'video/quicktime',
1255                          ),
1256  
1257                  // 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)
1258                  'riff' => array(
1259                              'pattern'   => '^(RIFF|SDSS|FORM)',
1260                              'group'     => 'audio-video',
1261                              'module'    => 'riff',
1262                              'mime_type' => 'audio/wav',
1263                              'fail_ape'  => 'WARNING',
1264                          ),
1265  
1266                  // Real - audio/video - RealAudio, RealVideo
1267                  'real' => array(
1268                              'pattern'   => '^\\.(RMF|ra)',
1269                              'group'     => 'audio-video',
1270                              'module'    => 'real',
1271                              'mime_type' => 'audio/x-realaudio',
1272                          ),
1273  
1274                  // SWF - audio/video - ShockWave Flash
1275                  'swf' => array(
1276                              'pattern'   => '^(F|C)WS',
1277                              'group'     => 'audio-video',
1278                              'module'    => 'swf',
1279                              'mime_type' => 'application/x-shockwave-flash',
1280                          ),
1281  
1282                  // TS - audio/video - MPEG-2 Transport Stream
1283                  'ts' => array(
1284                              'pattern'   => '^(\\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G".  Check for at least 10 packets matching this pattern
1285                              'group'     => 'audio-video',
1286                              'module'    => 'ts',
1287                              'mime_type' => 'video/MP2T',
1288                          ),
1289  
1290                  // WTV - audio/video - Windows Recorded TV Show
1291                  'wtv' => array(
1292                              'pattern'   => '^\\xB7\\xD8\\x00\\x20\\x37\\x49\\xDA\\x11\\xA6\\x4E\\x00\\x07\\xE9\\x5E\\xAD\\x8D',
1293                              'group'     => 'audio-video',
1294                              'module'    => 'wtv',
1295                              'mime_type' => 'video/x-ms-wtv',
1296                          ),
1297  
1298  
1299                  // Still-Image formats
1300  
1301                  // BMP  - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
1302                  'bmp'  => array(
1303                              'pattern'   => '^BM',
1304                              'group'     => 'graphic',
1305                              'module'    => 'bmp',
1306                              'mime_type' => 'image/bmp',
1307                              'fail_id3'  => 'ERROR',
1308                              'fail_ape'  => 'ERROR',
1309                          ),
1310  
1311                  // GIF  - still image - Graphics Interchange Format
1312                  'gif'  => array(
1313                              'pattern'   => '^GIF',
1314                              'group'     => 'graphic',
1315                              'module'    => 'gif',
1316                              'mime_type' => 'image/gif',
1317                              'fail_id3'  => 'ERROR',
1318                              'fail_ape'  => 'ERROR',
1319                          ),
1320  
1321                  // JPEG - still image - Joint Photographic Experts Group (JPEG)
1322                  'jpg'  => array(
1323                              'pattern'   => '^\\xFF\\xD8\\xFF',
1324                              'group'     => 'graphic',
1325                              'module'    => 'jpg',
1326                              'mime_type' => 'image/jpeg',
1327                              'fail_id3'  => 'ERROR',
1328                              'fail_ape'  => 'ERROR',
1329                          ),
1330  
1331                  // JXL  - still image - JPEG XL (bare codestream)
1332                  'jpegxl'  => array(
1333                              'pattern'   => '^\\xFF\\x0A',
1334                              'group'     => 'graphic',
1335                              'module'    => 'jpegxl',
1336                              'mime_type' => 'image/jxl',
1337                              'fail_id3'  => 'ERROR',
1338                              'fail_ape'  => 'ERROR',
1339                          ),
1340  
1341                  // JXL  - still image - JPEG XL (ISO BMFF container)
1342                  'jpegxlbmff'  => array(
1343                              'pattern'   => '^\\x00\\x00\\x00\\x0CJXL\\x20\\x0D\\x0A\\x87\\x0A',
1344                              'group'     => 'graphic',
1345                              'module'    => 'jpegxl',
1346                              'mime_type' => 'image/jxl',
1347                              'fail_id3'  => 'ERROR',
1348                              'fail_ape'  => 'ERROR',
1349                          ),
1350  
1351                  // PCD  - still image - Kodak Photo CD
1352                  'pcd'  => array(
1353                              'pattern'   => '^.{2048}PCD_IPI\\x00',
1354                              'group'     => 'graphic',
1355                              'module'    => 'pcd',
1356                              'mime_type' => 'image/x-photo-cd',
1357                              'fail_id3'  => 'ERROR',
1358                              'fail_ape'  => 'ERROR',
1359                          ),
1360  
1361  
1362                  // PNG  - still image - Portable Network Graphics (PNG)
1363                  'png'  => array(
1364                              'pattern'   => '^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A',
1365                              'group'     => 'graphic',
1366                              'module'    => 'png',
1367                              'mime_type' => 'image/png',
1368                              'fail_id3'  => 'ERROR',
1369                              'fail_ape'  => 'ERROR',
1370                          ),
1371  
1372  
1373                  // SVG  - still image - Scalable Vector Graphics (SVG)
1374                  'svg'  => array(
1375                              'pattern'   => '(<!DOCTYPE svg PUBLIC |xmlns="http://www\\.w3\\.org/2000/svg")',
1376                              'group'     => 'graphic',
1377                              'module'    => 'svg',
1378                              'mime_type' => 'image/svg+xml',
1379                              'fail_id3'  => 'ERROR',
1380                              'fail_ape'  => 'ERROR',
1381                          ),
1382  
1383  
1384                  // TIFF - still image - Tagged Information File Format (TIFF)
1385                  'tiff' => array(
1386                              'pattern'   => '^(II\\x2A\\x00|MM\\x00\\x2A)',
1387                              'group'     => 'graphic',
1388                              'module'    => 'tiff',
1389                              'mime_type' => 'image/tiff',
1390                              'fail_id3'  => 'ERROR',
1391                              'fail_ape'  => 'ERROR',
1392                          ),
1393  
1394  
1395                  // EFAX - still image - eFax (TIFF derivative)
1396                  'efax'  => array(
1397                              'pattern'   => '^\\xDC\\xFE',
1398                              'group'     => 'graphic',
1399                              'module'    => 'efax',
1400                              'mime_type' => 'image/efax',
1401                              'fail_id3'  => 'ERROR',
1402                              'fail_ape'  => 'ERROR',
1403                          ),
1404  
1405  
1406                  // Data formats
1407  
1408                  // ISO  - data        - International Standards Organization (ISO) CD-ROM Image
1409                  'iso'  => array(
1410                              'pattern'   => '^.{32769}CD001',
1411                              'group'     => 'misc',
1412                              'module'    => 'iso',
1413                              'mime_type' => 'application/octet-stream',
1414                              'fail_id3'  => 'ERROR',
1415                              'fail_ape'  => 'ERROR',
1416                              'iconv_req' => false,
1417                          ),
1418  
1419                  // HPK  - data        - HPK compressed data
1420                  'hpk'  => array(
1421                              'pattern'   => '^BPUL',
1422                              'group'     => 'archive',
1423                              'module'    => 'hpk',
1424                              'mime_type' => 'application/octet-stream',
1425                              'fail_id3'  => 'ERROR',
1426                              'fail_ape'  => 'ERROR',
1427                          ),
1428  
1429                  // RAR  - data        - RAR compressed data
1430                  'rar'  => array(
1431                              'pattern'   => '^Rar\\!',
1432                              'group'     => 'archive',
1433                              'module'    => 'rar',
1434                              'mime_type' => 'application/vnd.rar',
1435                              'fail_id3'  => 'ERROR',
1436                              'fail_ape'  => 'ERROR',
1437                          ),
1438  
1439                  // SZIP - audio/data  - SZIP compressed data
1440                  'szip' => array(
1441                              'pattern'   => '^SZ\\x0A\\x04',
1442                              'group'     => 'archive',
1443                              'module'    => 'szip',
1444                              'mime_type' => 'application/octet-stream',
1445                              'fail_id3'  => 'ERROR',
1446                              'fail_ape'  => 'ERROR',
1447                          ),
1448  
1449                  // TAR  - data        - TAR compressed data
1450                  'tar'  => array(
1451                              '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}',
1452                              'group'     => 'archive',
1453                              'module'    => 'tar',
1454                              'mime_type' => 'application/x-tar',
1455                              'fail_id3'  => 'ERROR',
1456                              'fail_ape'  => 'ERROR',
1457                          ),
1458  
1459                  // GZIP  - data        - GZIP compressed data
1460                  'gz'  => array(
1461                              'pattern'   => '^\\x1F\\x8B\\x08',
1462                              'group'     => 'archive',
1463                              'module'    => 'gzip',
1464                              'mime_type' => 'application/gzip',
1465                              'fail_id3'  => 'ERROR',
1466                              'fail_ape'  => 'ERROR',
1467                          ),
1468  
1469                  // ZIP  - data         - ZIP compressed data
1470                  'zip'  => array(
1471                              'pattern'   => '^PK\\x03\\x04',
1472                              'group'     => 'archive',
1473                              'module'    => 'zip',
1474                              'mime_type' => 'application/zip',
1475                              'fail_id3'  => 'ERROR',
1476                              'fail_ape'  => 'ERROR',
1477                          ),
1478  
1479                  // XZ   - data         - XZ compressed data
1480                  'xz'  => array(
1481                              'pattern'   => '^\\xFD7zXZ\\x00',
1482                              'group'     => 'archive',
1483                              'module'    => 'xz',
1484                              'mime_type' => 'application/x-xz',
1485                              'fail_id3'  => 'ERROR',
1486                              'fail_ape'  => 'ERROR',
1487                          ),
1488  
1489                  // XZ   - data         - XZ compressed data
1490                  '7zip'  => array(
1491                              'pattern'   => '^7z\\xBC\\xAF\\x27\\x1C',
1492                              'group'     => 'archive',
1493                              'module'    => '7zip',
1494                              'mime_type' => 'application/x-7z-compressed',
1495                              'fail_id3'  => 'ERROR',
1496                              'fail_ape'  => 'ERROR',
1497                          ),
1498  
1499  
1500                  // Misc other formats
1501  
1502                  // GPX - data         - GPS Exchange Format
1503                  'gpx' => array (
1504                              'pattern'   => '^<\\?xml [^>]+>[\s]*<gpx ',
1505                              'group'     => 'misc',
1506                              'module'    => 'gpx',
1507                              'mime_type' => 'application/gpx+xml',
1508                              'fail_id3'  => 'ERROR',
1509                              'fail_ape'  => 'ERROR',
1510                          ),
1511  
1512                  // PAR2 - data        - Parity Volume Set Specification 2.0
1513                  'par2' => array (
1514                              'pattern'   => '^PAR2\\x00PKT',
1515                              'group'     => 'misc',
1516                              'module'    => 'par2',
1517                              'mime_type' => 'application/octet-stream',
1518                              'fail_id3'  => 'ERROR',
1519                              'fail_ape'  => 'ERROR',
1520                          ),
1521  
1522                  // PDF  - data        - Portable Document Format
1523                  'pdf'  => array(
1524                              'pattern'   => '^\\x25PDF',
1525                              'group'     => 'misc',
1526                              'module'    => 'pdf',
1527                              'mime_type' => 'application/pdf',
1528                              'fail_id3'  => 'ERROR',
1529                              'fail_ape'  => 'ERROR',
1530                          ),
1531  
1532                  // MSOFFICE  - data   - ZIP compressed data
1533                  'msoffice' => array(
1534                              'pattern'   => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
1535                              'group'     => 'misc',
1536                              'module'    => 'msoffice',
1537                              'mime_type' => 'application/octet-stream',
1538                              'fail_id3'  => 'ERROR',
1539                              'fail_ape'  => 'ERROR',
1540                          ),
1541  
1542                  // TORRENT             - .torrent
1543                  'torrent' => array(
1544                              'pattern'   => '^(d8\\:announce|d7\\:comment)',
1545                              'group'     => 'misc',
1546                              'module'    => 'torrent',
1547                              'mime_type' => 'application/x-bittorrent',
1548                              'fail_id3'  => 'ERROR',
1549                              'fail_ape'  => 'ERROR',
1550                          ),
1551  
1552                   // CUE  - data       - CUEsheet (index to single-file disc images)
1553                   'cue' => array(
1554                              'pattern'   => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
1555                              'group'     => 'misc',
1556                              'module'    => 'cue',
1557                              'mime_type' => 'application/octet-stream',
1558                             ),
1559  
1560              );
1561          }
1562  
1563          return $format_info;
1564      }
1565  
1566      /**
1567       * @param string $filedata
1568       * @param string $filename
1569       *
1570       * @return mixed|false
1571       */
1572  	public function GetFileFormat(&$filedata, $filename='') {
1573          // this function will determine the format of a file based on usually
1574          // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
1575          // and in the case of ISO CD image, 6 bytes offset 32kb from the start
1576          // of the file).
1577  
1578          // Identify file format - loop through $format_info and detect with reg expr
1579          foreach ($this->GetFileFormatArray() as $format_name => $info) {
1580              // The /s switch on preg_match() forces preg_match() NOT to treat
1581              // newline (0x0A) characters as special chars but do a binary match
1582              if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) {
1583                  $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1584                  return $info;
1585              }
1586          }
1587  
1588  
1589          if (preg_match('#\\.mp[123a]$#i', $filename)) {
1590              // Too many mp3 encoders on the market put garbage in front of mpeg files
1591              // use assume format on these if format detection failed
1592              $GetFileFormatArray = $this->GetFileFormatArray();
1593              $info = $GetFileFormatArray['mp3'];
1594              $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1595              return $info;
1596          } 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)) {
1597              // old-format (SV4-SV6) Musepack header that has a very loose pattern match and could falsely match other data (e.g. corrupt mp3)
1598              // only enable this pattern check if the filename ends in .mpc/mpp/mp+
1599              $GetFileFormatArray = $this->GetFileFormatArray();
1600              $info = $GetFileFormatArray['mpc'];
1601              $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1602              return $info;
1603          } elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
1604              // there's not really a useful consistent "magic" at the beginning of .cue files to identify them
1605              // so until I think of something better, just go by filename if all other format checks fail
1606              // and verify there's at least one instance of "TRACK xx AUDIO" in the file
1607              $GetFileFormatArray = $this->GetFileFormatArray();
1608              $info = $GetFileFormatArray['cue'];
1609              $info['include']   = 'module.'.$info['group'].'.'.$info['module'].'.php';
1610              return $info;
1611          }
1612  
1613          return false;
1614      }
1615  
1616      /**
1617       * Converts array to $encoding charset from $this->encoding.
1618       *
1619       * @param array  $array
1620       * @param string $encoding
1621       */
1622  	public function CharConvert(&$array, $encoding) {
1623  
1624          // identical encoding - end here
1625          if ($encoding == $this->encoding) {
1626              return;
1627          }
1628  
1629          // loop thru array
1630          foreach ($array as $key => $value) {
1631  
1632              // go recursive
1633              if (is_array($value)) {
1634                  $this->CharConvert($array[$key], $encoding);
1635              }
1636  
1637              // convert string
1638              elseif (is_string($value)) {
1639                  $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value));
1640              }
1641          }
1642      }
1643  
1644      /**
1645       * @return bool
1646       */
1647  	public function HandleAllTags() {
1648  
1649          // key name => array (tag name, character encoding)
1650          static $tags;
1651          if (empty($tags)) {
1652              $tags = array(
1653                  'asf'       => array('asf'           , 'UTF-16LE'),
1654                  'midi'      => array('midi'          , 'ISO-8859-1'),
1655                  'nsv'       => array('nsv'           , 'ISO-8859-1'),
1656                  'ogg'       => array('vorbiscomment' , 'UTF-8'),
1657                  'png'       => array('png'           , 'UTF-8'),
1658                  'tiff'      => array('tiff'          , 'ISO-8859-1'),
1659                  'quicktime' => array('quicktime'     , 'UTF-8'),
1660                  'real'      => array('real'          , 'ISO-8859-1'),
1661                  'vqf'       => array('vqf'           , 'ISO-8859-1'),
1662                  'zip'       => array('zip'           , 'ISO-8859-1'),
1663                  'riff'      => array('riff'          , 'ISO-8859-1'),
1664                  'lyrics3'   => array('lyrics3'       , 'ISO-8859-1'),
1665                  'id3v1'     => array('id3v1'         , $this->encoding_id3v1),
1666                  '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
1667                  'ape'       => array('ape'           , 'UTF-8'),
1668                  'cue'       => array('cue'           , 'ISO-8859-1'),
1669                  'matroska'  => array('matroska'      , 'UTF-8'),
1670                  'flac'      => array('vorbiscomment' , 'UTF-8'),
1671                  'divxtag'   => array('divx'          , 'ISO-8859-1'),
1672                  'iptc'      => array('iptc'          , 'ISO-8859-1'),
1673                  'dsdiff'    => array('dsdiff'        , 'ISO-8859-1'),
1674              );
1675          }
1676  
1677          // loop through comments array
1678          foreach ($tags as $comment_name => $tagname_encoding_array) {
1679              list($tag_name, $encoding) = $tagname_encoding_array;
1680  
1681              // fill in default encoding type if not already present
1682              if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) {
1683                  $this->info[$comment_name]['encoding'] = $encoding;
1684              }
1685  
1686              // copy comments if key name set
1687              if (!empty($this->info[$comment_name]['comments'])) {
1688                  foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) {
1689                      foreach ($valuearray as $key => $value) {
1690                          if (is_string($value)) {
1691                              $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
1692                          }
1693                          if (isset($value) && $value !== "") {
1694                              if (!is_numeric($key)) {
1695                                  $this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
1696                              } else {
1697                                  $this->info['tags'][trim($tag_name)][trim($tag_key)][]     = $value;
1698                              }
1699                          }
1700                      }
1701                      if ($tag_key == 'picture') {
1702                          // 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
1703                          unset($this->info[$comment_name]['comments'][$tag_key]);
1704                      }
1705                  }
1706  
1707                  if (!isset($this->info['tags'][$tag_name])) {
1708                      // comments are set but contain nothing but empty strings, so skip
1709                      continue;
1710                  }
1711  
1712                  $this->CharConvert($this->info['tags'][$tag_name], $this->info[$comment_name]['encoding']);           // only copy gets converted!
1713  
1714                  if ($this->option_tags_html) {
1715                      foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
1716                          if ($tag_key == 'picture') {
1717                              // Do not to try to convert binary picture data to HTML
1718                              // https://github.com/JamesHeinrich/getID3/issues/178
1719                              continue;
1720                          }
1721                          $this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']);
1722                      }
1723                  }
1724  
1725              }
1726  
1727          }
1728  
1729          // 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
1730          if (!empty($this->info['tags'])) {
1731              $unset_keys = array('tags', 'tags_html');
1732              foreach ($this->info['tags'] as $tagtype => $tagarray) {
1733                  foreach ($tagarray as $tagname => $tagdata) {
1734                      if ($tagname == 'picture') {
1735                          foreach ($tagdata as $key => $tagarray) {
1736                              $this->info['comments']['picture'][] = $tagarray;
1737                              if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
1738                                  if (isset($this->info['tags'][$tagtype][$tagname][$key])) {
1739                                      unset($this->info['tags'][$tagtype][$tagname][$key]);
1740                                  }
1741                                  if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) {
1742                                      unset($this->info['tags_html'][$tagtype][$tagname][$key]);
1743                                  }
1744                              }
1745                          }
1746                      }
1747                  }
1748                  foreach ($unset_keys as $unset_key) {
1749                      // remove possible empty keys from (e.g. [tags][id3v2][picture])
1750                      if (empty($this->info[$unset_key][$tagtype]['picture'])) {
1751                          unset($this->info[$unset_key][$tagtype]['picture']);
1752                      }
1753                      if (empty($this->info[$unset_key][$tagtype])) {
1754                          unset($this->info[$unset_key][$tagtype]);
1755                      }
1756                      if (empty($this->info[$unset_key])) {
1757                          unset($this->info[$unset_key]);
1758                      }
1759                  }
1760                  // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
1761                  if (isset($this->info[$tagtype]['comments']['picture'])) {
1762                      unset($this->info[$tagtype]['comments']['picture']);
1763                  }
1764                  if (empty($this->info[$tagtype]['comments'])) {
1765                      unset($this->info[$tagtype]['comments']);
1766                  }
1767                  if (empty($this->info[$tagtype])) {
1768                      unset($this->info[$tagtype]);
1769                  }
1770              }
1771          }
1772          return true;
1773      }
1774  
1775      /**
1776       * Calls getid3_lib::CopyTagsToComments() but passes in the option_tags_html setting from this instance of getID3
1777       *
1778       * @param array $ThisFileInfo
1779       *
1780       * @return bool
1781       */
1782  	public function CopyTagsToComments(&$ThisFileInfo) {
1783          return getid3_lib::CopyTagsToComments($ThisFileInfo, $this->option_tags_html);
1784      }
1785  
1786      /**
1787       * @param string $algorithm
1788       *
1789       * @return array|bool
1790       */
1791  	public function getHashdata($algorithm) {
1792          switch ($algorithm) {
1793              case 'md5':
1794              case 'sha1':
1795                  break;
1796  
1797              default:
1798                  return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
1799          }
1800  
1801          if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) {
1802  
1803              // We cannot get an identical md5_data value for Ogg files where the comments
1804              // span more than 1 Ogg page (compared to the same audio data with smaller
1805              // comments) using the normal getID3() method of MD5'ing the data between the
1806              // end of the comments and the end of the file (minus any trailing tags),
1807              // because the page sequence numbers of the pages that the audio data is on
1808              // do not match. Under normal circumstances, where comments are smaller than
1809              // the nominal 4-8kB page size, then this is not a problem, but if there are
1810              // very large comments, the only way around it is to strip off the comment
1811              // tags with vorbiscomment and MD5 that file.
1812              // This procedure must be applied to ALL Ogg files, not just the ones with
1813              // comments larger than 1 page, because the below method simply MD5's the
1814              // whole file with the comments stripped, not just the portion after the
1815              // comments block (which is the standard getID3() method.
1816  
1817              // The above-mentioned problem of comments spanning multiple pages and changing
1818              // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
1819              // currently vorbiscomment only works on OggVorbis files.
1820  
1821              if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
1822  
1823                  $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
1824                  $this->info[$algorithm.'_data'] = false;
1825  
1826              } else {
1827  
1828                  // Prevent user from aborting script
1829                  $old_abort = ignore_user_abort(true);
1830  
1831                  // Create empty file
1832                  $empty = tempnam(GETID3_TEMP_DIR, 'getID3');
1833                  touch($empty);
1834  
1835                  // Use vorbiscomment to make temp file without comments
1836                  $temp = tempnam(GETID3_TEMP_DIR, 'getID3');
1837                  $file = $this->info['filenamepath'];
1838  
1839                  if (GETID3_OS_ISWINDOWS) {
1840  
1841                      if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {
1842  
1843                          $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp);
1844                          $VorbisCommentError = shell_exec($commandline);
1845  
1846                      } else {
1847  
1848                          $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;
1849  
1850                      }
1851  
1852                  } else {
1853  
1854                      $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
1855                      $VorbisCommentError = shell_exec($commandline);
1856  
1857                  }
1858  
1859                  if (!empty($VorbisCommentError)) {
1860  
1861                      $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);
1862                      $this->info[$algorithm.'_data'] = false;
1863  
1864                  } else {
1865  
1866                      // Get hash of newly created file
1867                      switch ($algorithm) {
1868                          case 'md5':
1869                              $this->info[$algorithm.'_data'] = md5_file($temp);
1870                              break;
1871  
1872                          case 'sha1':
1873                              $this->info[$algorithm.'_data'] = sha1_file($temp);
1874                              break;
1875                      }
1876                  }
1877  
1878                  // Clean up
1879                  unlink($empty);
1880                  unlink($temp);
1881  
1882                  // Reset abort setting
1883                  ignore_user_abort($old_abort);
1884  
1885              }
1886  
1887          } else {
1888  
1889              if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) {
1890  
1891                  // get hash from part of file
1892                  $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm);
1893  
1894              } else {
1895  
1896                  // get hash from whole file
1897                  switch ($algorithm) {
1898                      case 'md5':
1899                          $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']);
1900                          break;
1901  
1902                      case 'sha1':
1903                          $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']);
1904                          break;
1905                  }
1906              }
1907  
1908          }
1909          return true;
1910      }
1911  
1912  	public function ChannelsBitratePlaytimeCalculations() {
1913  
1914          // set channelmode on audio
1915          if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) {
1916              // ignore
1917          } elseif ($this->info['audio']['channels'] == 1) {
1918              $this->info['audio']['channelmode'] = 'mono';
1919          } elseif ($this->info['audio']['channels'] == 2) {
1920              $this->info['audio']['channelmode'] = 'stereo';
1921          }
1922  
1923          // Calculate combined bitrate - audio + video
1924          $CombinedBitrate  = 0;
1925          $CombinedBitrate += (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] != 'free') ? $this->info['audio']['bitrate'] : 0);
1926          $CombinedBitrate += (isset($this->info['video']['bitrate'])                                                ? $this->info['video']['bitrate'] : 0);
1927          if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) {
1928              $this->info['bitrate'] = $CombinedBitrate;
1929          }
1930          //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
1931          //    // for example, VBR MPEG video files cannot determine video bitrate:
1932          //    // should not set overall bitrate and playtime from audio bitrate only
1933          //    unset($this->info['bitrate']);
1934          //}
1935  
1936          // video bitrate undetermined, but calculable
1937          if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) {
1938              // if video bitrate not set
1939              if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) {
1940                  // AND if audio bitrate is set to same as overall bitrate
1941                  if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) {
1942                      // AND if playtime is set
1943                      if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) {
1944                          // AND if AV data offset start/end is known
1945                          // THEN we can calculate the video bitrate
1946                          $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']);
1947                          $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate'];
1948                      }
1949                  }
1950              }
1951          }
1952  
1953          if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) {
1954              $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
1955          }
1956  
1957          if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) {
1958              $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds'];
1959          }
1960          if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) {
1961              if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) {
1962                  // audio only
1963                  $this->info['audio']['bitrate'] = $this->info['bitrate'];
1964              } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) {
1965                  // video only
1966                  $this->info['video']['bitrate'] = $this->info['bitrate'];
1967              }
1968          }
1969  
1970          // Set playtime string
1971          if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
1972              $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']);
1973          }
1974  
1975          // Look up codec name if fourcc is set but codec is not
1976          if (!empty($this->info['video']['fourcc']) && !isset($this->info['video']['codec'])) {
1977              $this->include_module('audio-video.riff');
1978              $this->info['video']['codec'] = getid3_riff::fourccLookup($this->info['video']['fourcc']);
1979          }
1980      }
1981  
1982      /**
1983       * @return bool
1984       */
1985  	public function CalculateCompressionRatioVideo() {
1986          if (empty($this->info['video'])) {
1987              return false;
1988          }
1989          if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) {
1990              return false;
1991          }
1992          if (empty($this->info['video']['bits_per_sample'])) {
1993              return false;
1994          }
1995  
1996          switch ($this->info['video']['dataformat']) {
1997              case 'bmp':
1998              case 'gif':
1999              case 'jpeg':
2000              case 'jpg':
2001              case 'png':
2002              case 'tiff':
2003                  $FrameRate = 1;
2004                  $PlaytimeSeconds = 1;
2005                  $BitrateCompressed = $this->info['filesize'] * 8;
2006                  break;
2007  
2008              default:
2009                  if (!empty($this->info['video']['frame_rate'])) {
2010                      $FrameRate = $this->info['video']['frame_rate'];
2011                  } else {
2012                      return false;
2013                  }
2014                  if (!empty($this->info['playtime_seconds'])) {
2015                      $PlaytimeSeconds = $this->info['playtime_seconds'];
2016                  } else {
2017                      return false;
2018                  }
2019                  if (!empty($this->info['video']['bitrate'])) {
2020                      $BitrateCompressed = $this->info['video']['bitrate'];
2021                  } else {
2022                      return false;
2023                  }
2024                  break;
2025          }
2026          $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;
2027  
2028          $this->info['video']['compression_ratio'] = getid3_lib::SafeDiv($BitrateCompressed, $BitrateUncompressed, 1);
2029          return true;
2030      }
2031  
2032      /**
2033       * @return bool
2034       */
2035  	public function CalculateCompressionRatioAudio() {
2036          if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) {
2037              return false;
2038          }
2039          if ($this->info['audio']['bitrate'] != 'free') {
2040              $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));
2041          }
2042  
2043          if (!empty($this->info['audio']['streams'])) {
2044              foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) {
2045                  if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) {
2046                      $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16));
2047                  }
2048              }
2049          }
2050          return true;
2051      }
2052  
2053      /**
2054       * @return bool
2055       */
2056  	public function CalculateReplayGain() {
2057          if (isset($this->info['replay_gain'])) {
2058              if (!isset($this->info['replay_gain']['reference_volume'])) {
2059                  $this->info['replay_gain']['reference_volume'] = 89.0;
2060              }
2061              if (isset($this->info['replay_gain']['track']['adjustment'])) {
2062                  $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment'];
2063              }
2064              if (isset($this->info['replay_gain']['album']['adjustment'])) {
2065                  $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment'];
2066              }
2067  
2068              if (isset($this->info['replay_gain']['track']['peak'])) {
2069                  $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']);
2070              }
2071              if (isset($this->info['replay_gain']['album']['peak'])) {
2072                  $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']);
2073              }
2074          }
2075          return true;
2076      }
2077  
2078      /**
2079       * @return bool
2080       */
2081  	public function ProcessAudioStreams() {
2082          if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) {
2083              if (!isset($this->info['audio']['streams'])) {
2084                  foreach ($this->info['audio'] as $key => $value) {
2085                      if ($key != 'streams') {
2086                          $this->info['audio']['streams'][0][$key] = $value;
2087                      }
2088                  }
2089              }
2090          }
2091          return true;
2092      }
2093  
2094      /**
2095       * @return string|bool
2096       */
2097  	public function getid3_tempnam() {
2098          return tempnam($this->tempdir, 'gI3');
2099      }
2100  
2101      /**
2102       * @param string $name
2103       *
2104       * @return bool
2105       *
2106       * @throws getid3_exception
2107       */
2108  	public function include_module($name) {
2109          //if (!file_exists($this->include_path.'module.'.$name.'.php')) {
2110          if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) {
2111              throw new getid3_exception('Required module.'.$name.'.php is missing.');
2112          }
2113          include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php');
2114          return true;
2115      }
2116  
2117      /**
2118       * @param string $filename
2119       *
2120       * @return bool
2121       */
2122  	public static function is_writable ($filename) {
2123          $ret = is_writable($filename);
2124          if (!$ret) {
2125              $perms = fileperms($filename);
2126              $ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002);
2127          }
2128          return $ret;
2129      }
2130  
2131  }
2132  
2133  
2134  abstract class getid3_handler
2135  {
2136  
2137      /**
2138      * @var getID3
2139      */
2140      protected $getid3;                       // pointer
2141  
2142      /**
2143       * Analyzing filepointer or string.
2144       *
2145       * @var bool
2146       */
2147      protected $data_string_flag     = false;
2148  
2149      /**
2150       * String to analyze.
2151       *
2152       * @var string
2153       */
2154      protected $data_string          = '';
2155  
2156      /**
2157       * Seek position in string.
2158       *
2159       * @var int
2160       */
2161      protected $data_string_position = 0;
2162  
2163      /**
2164       * String length.
2165       *
2166       * @var int
2167       */
2168      protected $data_string_length   = 0;
2169  
2170      /**
2171       * @var string
2172       */
2173      private $dependency_to;
2174  
2175      /**
2176       * getid3_handler constructor.
2177       *
2178       * @param getID3 $getid3
2179       * @param string $call_module
2180       */
2181  	public function __construct(getID3 $getid3, $call_module=null) {
2182          $this->getid3 = $getid3;
2183  
2184          if ($call_module) {
2185              $this->dependency_to = str_replace('getid3_', '', $call_module);
2186          }
2187      }
2188  
2189      /**
2190       * Analyze from file pointer.
2191       *
2192       * @return bool
2193       */
2194      abstract public function Analyze();
2195  
2196      /**
2197       * Analyze from string instead.
2198       *
2199       * @param string $string
2200       */
2201  	public function AnalyzeString($string) {
2202          // Enter string mode
2203          $this->setStringMode($string);
2204  
2205          // Save info
2206          $saved_avdataoffset = $this->getid3->info['avdataoffset'];
2207          $saved_avdataend    = $this->getid3->info['avdataend'];
2208          $saved_filesize     = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call
2209  
2210          // Reset some info
2211          $this->getid3->info['avdataoffset'] = 0;
2212          $this->getid3->info['avdataend']    = $this->getid3->info['filesize'] = $this->data_string_length;
2213  
2214          // Analyze
2215          $this->Analyze();
2216  
2217          // Restore some info
2218          $this->getid3->info['avdataoffset'] = $saved_avdataoffset;
2219          $this->getid3->info['avdataend']    = $saved_avdataend;
2220          $this->getid3->info['filesize']     = $saved_filesize;
2221  
2222          // Exit string mode
2223          $this->data_string_flag = false;
2224      }
2225  
2226      /**
2227       * @param string $string
2228       */
2229  	public function setStringMode($string) {
2230          $this->data_string_flag   = true;
2231          $this->data_string        = $string;
2232          $this->data_string_length = strlen($string);
2233      }
2234  
2235      /**
2236       * @phpstan-impure
2237       *
2238       * @return int|bool
2239       */
2240  	protected function ftell() {
2241          if ($this->data_string_flag) {
2242              return $this->data_string_position;
2243          }
2244          return ftell($this->getid3->fp);
2245      }
2246  
2247      /**
2248       * @param int $bytes
2249       *
2250       * @phpstan-impure
2251       *
2252       * @return string|false
2253       *
2254       * @throws getid3_exception
2255       */
2256  	protected function fread($bytes) {
2257          if ($this->data_string_flag) {
2258              $this->data_string_position += $bytes;
2259              return substr($this->data_string, $this->data_string_position - $bytes, $bytes);
2260          }
2261          if ($bytes == 0) {
2262              return '';
2263          } elseif ($bytes < 0) {
2264              throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().')', 10);
2265          }
2266          $pos = $this->ftell() + $bytes;
2267          if (!getid3_lib::intValueSupported($pos)) {
2268              throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10);
2269          }
2270  
2271          //return fread($this->getid3->fp, $bytes);
2272          /*
2273          * https://www.getid3.org/phpBB3/viewtopic.php?t=1930
2274          * "I found out that the root cause for the problem was how getID3 uses the PHP system function fread().
2275          * It seems to assume that fread() would always return as many bytes as were requested.
2276          * 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.
2277          * The call may return only part of the requested data and a new call is needed to get more."
2278          */
2279          $contents = '';
2280          do {
2281              //if (($this->getid3->memory_limit > 0) && ($bytes > $this->getid3->memory_limit)) {
2282              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)"
2283                  throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') that is more than available PHP memory ('.$this->getid3->memory_limit.')', 10);
2284              }
2285              $part = fread($this->getid3->fp, $bytes);
2286              $partLength  = strlen($part);
2287              $bytes      -= $partLength;
2288              $contents   .= $part;
2289          } while (($bytes > 0) && ($partLength > 0));
2290          return $contents;
2291      }
2292  
2293      /**
2294       * @param int $bytes
2295       * @param int $whence
2296       *
2297       * @phpstan-impure
2298       *
2299       * @return int
2300       *
2301       * @throws getid3_exception
2302       */
2303  	protected function fseek($bytes, $whence=SEEK_SET) {
2304          if ($this->data_string_flag) {
2305              switch ($whence) {
2306                  case SEEK_SET:
2307                      $this->data_string_position = $bytes;
2308                      break;
2309  
2310                  case SEEK_CUR:
2311                      $this->data_string_position += $bytes;
2312                      break;
2313  
2314                  case SEEK_END:
2315                      $this->data_string_position = $this->data_string_length + $bytes;
2316                      break;
2317              }
2318              return 0; // fseek returns 0 on success
2319          }
2320  
2321          $pos = $bytes;
2322          if ($whence == SEEK_CUR) {
2323              $pos = $this->ftell() + $bytes;
2324          } elseif ($whence == SEEK_END) {
2325              $pos = $this->getid3->info['filesize'] + $bytes;
2326          }
2327          if (!getid3_lib::intValueSupported($pos)) {
2328              throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
2329          }
2330  
2331          // https://github.com/JamesHeinrich/getID3/issues/327
2332          $result = fseek($this->getid3->fp, $bytes, $whence);
2333          if ($result !== 0) { // fseek returns 0 on success
2334              throw new getid3_exception('cannot fseek('.$pos.'). resource/stream does not appear to support seeking', 10);
2335          }
2336          return $result;
2337      }
2338  
2339      /**
2340       * @phpstan-impure
2341       *
2342       * @return string|false
2343       *
2344       * @throws getid3_exception
2345       */
2346  	protected function fgets() {
2347          // must be able to handle CR/LF/CRLF but not read more than one lineend
2348          $buffer   = ''; // final string we will return
2349          $prevchar = ''; // save previously-read character for end-of-line checking
2350          if ($this->data_string_flag) {
2351              while (true) {
2352                  $thischar = substr($this->data_string, $this->data_string_position++, 1);
2353                  if (($prevchar == "\r") && ($thischar != "\n")) {
2354                      // read one byte too many, back up
2355                      $this->data_string_position--;
2356                      break;
2357                  }
2358                  $buffer .= $thischar;
2359                  if ($thischar == "\n") {
2360                      break;
2361                  }
2362                  if ($this->data_string_position >= $this->data_string_length) {
2363                      // EOF
2364                      break;
2365                  }
2366                  $prevchar = $thischar;
2367              }
2368  
2369          } else {
2370  
2371              // Ideally we would just use PHP's fgets() function, however...
2372              // it does not behave consistently with regards to mixed line endings, may be system-dependent
2373              // and breaks entirely when given a file with mixed \r vs \n vs \r\n line endings (e.g. some PDFs)
2374              //return fgets($this->getid3->fp);
2375              while (true) {
2376                  $thischar = fgetc($this->getid3->fp);
2377                  if (($prevchar == "\r") && ($thischar != "\n")) {
2378                      // read one byte too many, back up
2379                      fseek($this->getid3->fp, -1, SEEK_CUR);
2380                      break;
2381                  }
2382                  $buffer .= $thischar;
2383                  if ($thischar == "\n") {
2384                      break;
2385                  }
2386                  if (feof($this->getid3->fp)) {
2387                      break;
2388                  }
2389                  $prevchar = $thischar;
2390              }
2391  
2392          }
2393          return $buffer;
2394      }
2395  
2396      /**
2397       * @phpstan-impure
2398       *
2399       * @return bool
2400       */
2401  	protected function feof() {
2402          if ($this->data_string_flag) {
2403              return $this->data_string_position >= $this->data_string_length;
2404          }
2405          return feof($this->getid3->fp);
2406      }
2407  
2408      /**
2409       * @param string $module
2410       *
2411       * @return bool
2412       */
2413  	final protected function isDependencyFor($module) {
2414          return $this->dependency_to == $module;
2415      }
2416  
2417      /**
2418       * @param string $text
2419       *
2420       * @return bool
2421       */
2422  	protected function error($text) {
2423          $this->getid3->info['error'][] = $text;
2424  
2425          return false;
2426      }
2427  
2428      /**
2429       * @param string $text
2430       *
2431       * @return bool
2432       */
2433  	protected function warning($text) {
2434          return $this->getid3->warning($text);
2435      }
2436  
2437      /**
2438       * @param string $text
2439       */
2440  	protected function notice($text) {
2441          // does nothing for now
2442      }
2443  
2444      /**
2445       * @param string $name
2446       * @param int    $offset
2447       * @param int    $length
2448       * @param string $image_mime
2449       *
2450       * @return string|null
2451       *
2452       * @throws Exception
2453       * @throws getid3_exception
2454       */
2455  	public function saveAttachment($name, $offset, $length, $image_mime=null) {
2456          $fp_dest = null;
2457          $dest = null;
2458          try {
2459  
2460              // do not extract at all
2461              if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) {
2462  
2463                  $attachment = null; // do not set any
2464  
2465              // extract to return array
2466              } elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) {
2467  
2468                  $this->fseek($offset);
2469                  $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory
2470                  if ($attachment === false || strlen($attachment) != $length) {
2471                      throw new Exception('failed to read attachment data');
2472                  }
2473  
2474              // assume directory path is given
2475              } else {
2476  
2477                  // set up destination path
2478                  $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
2479                  if (!is_dir($dir) || !getID3::is_writable($dir)) { // check supplied directory
2480                      throw new Exception('supplied path ('.$dir.') does not exist, or is not writable');
2481                  }
2482                  $dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : '');
2483  
2484                  // create dest file
2485                  if (($fp_dest = fopen($dest, 'wb')) == false) {
2486                      throw new Exception('failed to create file '.$dest);
2487                  }
2488  
2489                  // copy data
2490                  $this->fseek($offset);
2491                  $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size());
2492                  $bytesleft = $length;
2493                  while ($bytesleft > 0) {
2494                      if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) {
2495                          throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space');
2496                      }
2497                      $bytesleft -= $byteswritten;
2498                  }
2499  
2500                  fclose($fp_dest);
2501                  $attachment = $dest;
2502  
2503              }
2504  
2505          } catch (Exception $e) {
2506  
2507              // close and remove dest file if created
2508              if (isset($fp_dest) && is_resource($fp_dest)) {
2509                  fclose($fp_dest);
2510              }
2511  
2512              if (isset($dest) && file_exists($dest)) {
2513                  unlink($dest);
2514              }
2515  
2516              // do not set any is case of error
2517              $attachment = null;
2518              $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage());
2519  
2520          }
2521  
2522          // seek to the end of attachment
2523          $this->fseek($offset + $length);
2524  
2525          return $attachment;
2526      }
2527  
2528  }
2529  
2530  
2531  class getid3_exception extends Exception
2532  {
2533      public $message;
2534  }


Generated : Fri Sep 11 08:20:31 2026 Cross-referenced by PHPXref