[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Thu May 9 08:20:02 2024 Cross-referenced by PHPXref