[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Wed May 6 08:20:15 2026 Cross-referenced by PHPXref