[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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


Generated: Tue Mar 19 01:00:02 2024 Cross-referenced by PHPXref 0.7.1