Ampache PHP Cross Reference Groupware Applications

Source: /modules/getid3/getid3.php - 1803 lines - 63782 bytes - Summary - Text - Print

   1  <?php
   2  /////////////////////////////////////////////////////////////////
   3  /// getID3() by James Heinrich <info@getid3.org>               //
   4  //  available at http://getid3.sourceforge.net                 //
   5  //            or http://www.getid3.org                         //
   6  //          also https://github.com/JamesHeinrich/getID3       //
   7  /////////////////////////////////////////////////////////////////
   8  //                                                             //
   9  // Please see readme.txt for more information                  //
  10  //                                                            ///
  11  /////////////////////////////////////////////////////////////////
  12  
  13  // define a constant rather than looking up every time it is needed
  14  if (!defined('GETID3_OS_ISWINDOWS')) {
  15      define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0));
  16  }
  17  // Get base path of getID3() - ONCE
  18  if (!defined('GETID3_INCLUDEPATH')) {
  19      define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
  20  }
  21  // Workaround Bug #39923 (https://bugs.php.net/bug.php?id=39923)
  22  if (!defined('IMG_JPG') && defined('IMAGETYPE_JPEG')) {
  23      define('IMG_JPG', IMAGETYPE_JPEG);
  24  }
  25  
  26  // attempt to define temp dir as something flexible but reliable
  27  $temp_dir = ini_get('upload_tmp_dir');
  28  if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) {
  29      $temp_dir = '';
  30  }
  31  if (!$temp_dir) {
  32      // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts
  33      $temp_dir = sys_get_temp_dir();
  34  }
  35  $temp_dir = @realpath($temp_dir); // see https://github.com/JamesHeinrich/getID3/pull/10
  36  $open_basedir = ini_get('open_basedir');
  37  if ($open_basedir) {
  38      // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/"
  39      $temp_dir     = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir);
  40      $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir);
  41      if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) {
  42          $temp_dir .= DIRECTORY_SEPARATOR;
  43      }
  44      $found_valid_tempdir = false;
  45      $open_basedirs = explode(PATH_SEPARATOR, $open_basedir);
  46      foreach ($open_basedirs as $basedir) {
  47          if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) {
  48              $basedir .= DIRECTORY_SEPARATOR;
  49          }
  50          if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) {
  51              $found_valid_tempdir = true;
  52              break;
  53          }
  54      }
  55      if (!$found_valid_tempdir) {
  56          $temp_dir = '';
  57      }
  58      unset($open_basedirs, $found_valid_tempdir, $basedir);
  59  }
  60  if (!$temp_dir) {
  61      $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
  62  }
  63  // $temp_dir = '/something/else/';  // feel free to override temp dir here if it works better for your system
  64  if (!defined('GETID3_TEMP_DIR')) {
  65      define('GETID3_TEMP_DIR', $temp_dir);
  66  }
  67  unset($open_basedir, $temp_dir);
  68  
  69  // End: Defines
  70  
  71  
  72  class getID3
  73  {
  74      // public: Settings
  75      public $encoding        = 'UTF-8';        // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples:  ISO-8859-1  UTF-8  UTF-16  UTF-16BE
  76      public $encoding_id3v1  = 'ISO-8859-1';   // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252'
  77  
  78      // public: Optional tag checks - disable for speed.
  79      public $option_tag_id3v1         = true;  // Read and process ID3v1 tags
  80      public $option_tag_id3v2         = true;  // Read and process ID3v2 tags
  81      public $option_tag_lyrics3       = true;  // Read and process Lyrics3 tags
  82      public $option_tag_apetag        = true;  // Read and process APE tags
  83      public $option_tags_process      = true;  // Copy tags to root key 'tags' and encode to $this->encoding
  84      public $option_tags_html         = true;  // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
  85  
  86      // public: Optional tag/comment calucations
  87      public $option_extra_info        = true;  // Calculate additional info such as bitrate, channelmode etc
  88  
  89      // public: Optional handling of embedded attachments (e.g. images)
  90      public $option_save_attachments  = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility
  91  
  92      // public: Optional calculations
  93      public $option_md5_data          = false; // Get MD5 sum of data part - slow
  94      public $option_md5_data_source   = false; // Use MD5 of source file if availble - only FLAC and OptimFROG
  95      public $option_sha1_data         = false; // Get SHA1 sum of data part - slow
  96      public $option_max_2gb_check     = null;  // Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on PHP_INT_MAX)
  97  
  98      // public: Read buffer size in bytes
  99      public $option_fread_buffer_size = 32768;
 100  
 101      // Public variables
 102      public $filename;                         // Filename of file being analysed.
 103      public $fp;                               // Filepointer to file being analysed.
 104      public $info;                             // Result array.
 105      public $tempdir = GETID3_TEMP_DIR;
 106      public $memory_limit = 0;
 107  
 108      // Protected variables
 109      protected $startup_error   = '';
 110      protected $startup_warning = '';
 111  
 112      const VERSION           = '1.10.0-20140319';
 113      const FREAD_BUFFER_SIZE = 32768;
 114  
 115      const ATTACHMENTS_NONE   = false;
 116      const ATTACHMENTS_INLINE = true;
 117  
 118      // public: constructor
 119  	public function __construct() {
 120  
 121          // Check for PHP version
 122          $required_php_version = '5.3.0';
 123          if (version_compare(PHP_VERSION, $required_php_version, '<')) {
 124              $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION;
 125              return false;
 126          }
 127  
 128          // Check memory
 129          $this->memory_limit = ini_get('memory_limit');
 130          if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) {
 131              // could be stored as "16M" rather than 16777216 for example
 132              $this->memory_limit = $matches[1] * 1048576;
 133          } elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
 134              // could be stored as "2G" rather than 2147483648 for example
 135              $this->memory_limit = $matches[1] * 1073741824;
 136          }
 137          if ($this->memory_limit <= 0) {
 138              // memory limits probably disabled
 139          } elseif ($this->memory_limit <= 4194304) {
 140              $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini';
 141          } elseif ($this->memory_limit <= 12582912) {
 142              $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';
 143          }
 144  
 145          // Check safe_mode off
 146          if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
 147              $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
 148          }
 149  
 150          if (intval(ini_get('mbstring.func_overload')) > 0) {
 151              $this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.');
 152          }
 153  
 154          // Check for magic_quotes_runtime
 155          if (function_exists('get_magic_quotes_runtime')) {
 156              if (get_magic_quotes_runtime()) {
 157                  return $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).');
 158              }
 159          }
 160  
 161          // Check for magic_quotes_gpc
 162          if (function_exists('magic_quotes_gpc')) {
 163              if (get_magic_quotes_gpc()) {
 164                  return $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).');
 165              }
 166          }
 167  
 168          // Load support library
 169          if (!include_once (GETID3_INCLUDEPATH.'getid3.lib.php')) {
 170              $this->startup_error .= 'getid3.lib.php is missing or corrupt';
 171          }
 172  
 173          if ($this->option_max_2gb_check === null) {
 174              $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647);
 175          }
 176  
 177  
 178          // Needed for Windows only:
 179          // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC
 180          //   as well as other helper functions such as head, tail, md5sum, etc
 181          // This path cannot contain spaces, but the below code will attempt to get the
 182          //   8.3-equivalent path automatically
 183          // IMPORTANT: This path must include the trailing slash
 184          if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) {
 185  
 186              $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path
 187  
 188              if (!is_dir($helperappsdir)) {
 189                  $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist';
 190              } elseif (strpos(realpath($helperappsdir), ' ') !== false) {
 191                  $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir));
 192                  $path_so_far = array();
 193                  foreach ($DirPieces as $key => $value) {
 194                      if (strpos($value, ' ') !== false) {
 195                          if (!empty($path_so_far)) {
 196                              $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far));
 197                              $dir_listing = `$commandline`;
 198                              $lines = explode("\n", $dir_listing);
 199                              foreach ($lines as $line) {
 200                                  $line = trim($line);
 201                                  if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) {
 202                                      list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches;
 203                                      if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) {
 204                                          $value = $shortname;
 205                                      }
 206                                  }
 207                              }
 208                          } else {
 209                              $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.';
 210                          }
 211                      }
 212                      $path_so_far[] = $value;
 213                  }
 214                  $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far);
 215              }
 216              define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR);
 217          }
 218  
 219          return true;
 220      }
 221  
 222  	public function version() {
 223          return self::VERSION;
 224      }
 225  
 226  	public function fread_buffer_size() {
 227          return $this->option_fread_buffer_size;
 228      }
 229  
 230  
 231      // public: setOption
 232  	public function setOption($optArray) {
 233          if (!is_array($optArray) || empty($optArray)) {
 234              return false;
 235          }
 236          foreach ($optArray as $opt => $val) {
 237              if (isset($this->$opt) === false) {
 238                  continue;
 239              }
 240              $this->$opt = $val;
 241          }
 242          return true;
 243      }
 244  
 245  
 246  	public function openfile($filename) {
 247          try {
 248              if (!empty($this->startup_error)) {
 249                  throw new getid3_exception($this->startup_error);
 250              }
 251              if (!empty($this->startup_warning)) {
 252                  $this->warning($this->startup_warning);
 253              }
 254  
 255              // init result array and set parameters
 256              $this->filename = $filename;
 257              $this->info = array();
 258              $this->info['GETID3_VERSION']   = $this->version();
 259              $this->info['php_memory_limit'] = $this->memory_limit;
 260  
 261              // remote files not supported
 262              if (preg_match('/^(ht|f)tp:\/\//', $filename)) {
 263                  throw new getid3_exception('Remote files are not supported - please copy the file locally first');
 264              }
 265  
 266              $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
 267              $filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename);
 268  
 269              // open local file
 270              //if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see http://www.getid3.org/phpBB3/viewtopic.php?t=1720
 271              if ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) {
 272                  // great
 273              } else {
 274                  $errormessagelist = array();
 275                  if (!is_readable($filename)) {
 276                      $errormessagelist[] = '!is_readable';
 277                  }
 278                  if (!is_file($filename)) {
 279                      $errormessagelist[] = '!is_file';
 280                  }
 281                  if (!file_exists($filename)) {
 282                      $errormessagelist[] = '!file_exists';
 283                  }
 284                  if (empty($errormessagelist)) {
 285                      $errormessagelist[] = 'fopen failed';
 286                  }
 287                  throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')');
 288              }
 289  
 290              $this->info['filesize'] = filesize($filename);
 291              // set redundant parameters - might be needed in some include file
 292              // filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion
 293              $filename = str_replace('\\', '/', $filename);
 294              $this->info['filepath']     = str_replace('\\', '/', realpath(dirname($filename)));
 295              $this->info['filename']     = getid3_lib::mb_basename($filename);
 296              $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename'];
 297  
 298  
 299              // option_max_2gb_check
 300              if ($this->option_max_2gb_check) {
 301                  // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB)
 302                  // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize
 303                  // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer
 304                  $fseek = fseek($this->fp, 0, SEEK_END);
 305                  if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) ||
 306                      ($this->info['filesize'] < 0) ||
 307                      (ftell($this->fp) < 0)) {
 308                          $real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']);
 309  
 310                          if ($real_filesize === false) {
 311                              unset($this->info['filesize']);
 312                              fclose($this->fp);
 313                              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.');
 314                          } elseif (getid3_lib::intValueSupported($real_filesize)) {
 315                              unset($this->info['filesize']);
 316                              fclose($this->fp);
 317                              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, 3).'GB, please report to info@getid3.org');
 318                          }
 319                          $this->info['filesize'] = $real_filesize;
 320                          $this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.');
 321                  }
 322              }
 323  
 324              // set more parameters
 325              $this->info['avdataoffset']        = 0;
 326              $this->info['avdataend']           = $this->info['filesize'];
 327              $this->info['fileformat']          = '';                // filled in later
 328              $this->info['audio']['dataformat'] = '';                // filled in later, unset if not used
 329              $this->info['video']['dataformat'] = '';                // filled in later, unset if not used
 330              $this->info['tags']                = array();           // filled in later, unset if not used
 331              $this->info['error']               = array();           // filled in later, unset if not used
 332              $this->info['warning']             = array();           // filled in later, unset if not used
 333              $this->info['comments']            = array();           // filled in later, unset if not used
 334              $this->info['encoding']            = $this->encoding;   // required by id3v2 and iso modules - can be unset at the end if desired
 335  
 336              return true;
 337  
 338          } catch (Exception $e) {
 339              $this->error($e->getMessage());
 340          }
 341          return false;
 342      }
 343  
 344      // public: analyze file
 345  	public function analyze($filename) {
 346          try {
 347              if (!$this->openfile($filename)) {
 348                  return $this->info;
 349              }
 350  
 351              // Handle tags
 352              foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
 353                  $option_tag = 'option_tag_'.$tag_name;
 354                  if ($this->$option_tag) {
 355                      $this->include_module('tag.'.$tag_name);
 356                      try {
 357                          $tag_class = 'getid3_'.$tag_name;
 358                          $tag = new $tag_class($this);
 359                          $tag->Analyze();
 360                      }
 361                      catch (getid3_exception $e) {
 362                          throw $e;
 363                      }
 364                  }
 365              }
 366              if (isset($this->info['id3v2']['tag_offset_start'])) {
 367                  $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']);
 368              }
 369              foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
 370                  if (isset($this->info[$tag_key]['tag_offset_start'])) {
 371                      $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']);
 372                  }
 373              }
 374  
 375              // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
 376              if (!$this->option_tag_id3v2) {
 377                  fseek($this->fp, 0);
 378                  $header = fread($this->fp, 10);
 379                  if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) {
 380                      $this->info['id3v2']['header']        = true;
 381                      $this->info['id3v2']['majorversion']  = ord($header{3});
 382                      $this->info['id3v2']['minorversion']  = ord($header{4});
 383                      $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
 384                  }
 385              }
 386  
 387              // read 32 kb file data
 388              fseek($this->fp, $this->info['avdataoffset']);
 389              $formattest = fread($this->fp, 32774);
 390  
 391              // determine format
 392              $determined_format = $this->GetFileFormat($formattest, $filename);
 393  
 394              // unable to determine file format
 395              if (!$determined_format) {
 396                  fclose($this->fp);
 397                  return $this->error('unable to determine file format');
 398              }
 399  
 400              // check for illegal ID3 tags
 401              if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) {
 402                  if ($determined_format['fail_id3'] === 'ERROR') {
 403                      fclose($this->fp);
 404                      return $this->error('ID3 tags not allowed on this file type.');
 405                  } elseif ($determined_format['fail_id3'] === 'WARNING') {
 406                      $this->warning('ID3 tags not allowed on this file type.');
 407                  }
 408              }
 409  
 410              // check for illegal APE tags
 411              if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) {
 412                  if ($determined_format['fail_ape'] === 'ERROR') {
 413                      fclose($this->fp);
 414                      return $this->error('APE tags not allowed on this file type.');
 415                  } elseif ($determined_format['fail_ape'] === 'WARNING') {
 416                      $this->warning('APE tags not allowed on this file type.');
 417                  }
 418              }
 419  
 420              // set mime type
 421              $this->info['mime_type'] = $determined_format['mime_type'];
 422  
 423              // supported format signature pattern detected, but module deleted
 424              if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) {
 425                  fclose($this->fp);
 426                  return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
 427              }
 428  
 429              // module requires iconv support
 430              // Check encoding/iconv support
 431              if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) {
 432                  $errormessage = '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. ';
 433                  if (GETID3_OS_ISWINDOWS) {
 434                      $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32';
 435                  } else {
 436                      $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch';
 437                  }
 438                  return $this->error($errormessage);
 439              }
 440  
 441              // include module
 442              include_once(GETID3_INCLUDEPATH.$determined_format['include']);
 443  
 444              // instantiate module class
 445              $class_name = 'getid3_'.$determined_format['module'];
 446              if (!class_exists($class_name)) {
 447                  return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
 448              }
 449              $class = new $class_name($this);
 450              $class->Analyze();
 451              unset($class);
 452  
 453              // close file
 454              fclose($this->fp);
 455  
 456              // process all tags - copy to 'tags' and convert charsets
 457              if ($this->option_tags_process) {
 458                  $this->HandleAllTags();
 459              }
 460  
 461              // perform more calculations
 462              if ($this->option_extra_info) {
 463                  $this->ChannelsBitratePlaytimeCalculations();
 464                  $this->CalculateCompressionRatioVideo();
 465                  $this->CalculateCompressionRatioAudio();
 466                  $this->CalculateReplayGain();
 467                  $this->ProcessAudioStreams();
 468              }
 469  
 470              // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
 471              if ($this->option_md5_data) {
 472                  // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too
 473                  if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) {
 474                      $this->getHashdata('md5');
 475                  }
 476              }
 477  
 478              // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
 479              if ($this->option_sha1_data) {
 480                  $this->getHashdata('sha1');
 481              }
 482  
 483              // remove undesired keys
 484              $this->CleanUp();
 485  
 486          } catch (Exception $e) {
 487              $this->error('Caught exception: '.$e->getMessage());
 488          }
 489  
 490          // return info array
 491          return $this->info;
 492      }
 493  
 494  
 495      // private: error handling
 496  	public function error($message) {
 497          $this->CleanUp();
 498          if (!isset($this->info['error'])) {
 499              $this->info['error'] = array();
 500          }
 501          $this->info['error'][] = $message;
 502          return $this->info;
 503      }
 504  
 505  
 506      // private: warning handling
 507  	public function warning($message) {
 508          $this->info['warning'][] = $message;
 509          return true;
 510      }
 511  
 512  
 513      // private: CleanUp
 514  	private function CleanUp() {
 515  
 516          // remove possible empty keys
 517          $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
 518          foreach ($AVpossibleEmptyKeys as $dummy => $key) {
 519              if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) {
 520                  unset($this->info['audio'][$key]);
 521              }
 522              if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) {
 523                  unset($this->info['video'][$key]);
 524              }
 525          }
 526  
 527          // remove empty root keys
 528          if (!empty($this->info)) {
 529              foreach ($this->info as $key => $value) {
 530                  if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) {
 531                      unset($this->info[$key]);
 532                  }
 533              }
 534          }
 535  
 536          // remove meaningless entries from unknown-format files
 537          if (empty($this->info['fileformat'])) {
 538              if (isset($this->info['avdataoffset'])) {
 539                  unset($this->info['avdataoffset']);
 540              }
 541              if (isset($this->info['avdataend'])) {
 542                  unset($this->info['avdataend']);
 543              }
 544          }
 545  
 546          // remove possible duplicated identical entries
 547          if (!empty($this->info['error'])) {
 548              $this->info['error'] = array_values(array_unique($this->info['error']));
 549          }
 550          if (!empty($this->info['warning'])) {
 551              $this->info['warning'] = array_values(array_unique($this->info['warning']));
 552          }
 553  
 554          // remove "global variable" type keys
 555          unset($this->info['php_memory_limit']);
 556  
 557          return true;
 558      }
 559  
 560  
 561      // return array containing information about all supported formats
 562  	public function GetFileFormatArray() {
 563          static $format_info = array();
 564          if (empty($format_info)) {
 565              $format_info = array(
 566  
 567                  // Audio formats
 568  
 569                  // AC-3   - audio      - Dolby AC-3 / Dolby Digital
 570                  'ac3'  => array(
 571                              'pattern'   => '^\x0B\x77',
 572                              'group'     => 'audio',
 573                              'module'    => 'ac3',
 574                              'mime_type' => 'audio/ac3',
 575                          ),
 576  
 577                  // AAC  - audio       - Advanced Audio Coding (AAC) - ADIF format
 578                  'adif' => array(
 579                              'pattern'   => '^ADIF',
 580                              'group'     => 'audio',
 581                              'module'    => 'aac',
 582                              'mime_type' => 'application/octet-stream',
 583                              'fail_ape'  => 'WARNING',
 584                          ),
 585  
 586  /*
 587                  // AA   - audio       - Audible Audiobook
 588                  'aa'   => array(
 589                              'pattern'   => '^.{4}\x57\x90\x75\x36',
 590                              'group'     => 'audio',
 591                              'module'    => 'aa',
 592                              'mime_type' => 'audio/audible',
 593                          ),
 594  */
 595                  // AAC  - audio       - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
 596                  'adts' => array(
 597                              'pattern'   => '^\xFF[\xF0-\xF1\xF8-\xF9]',
 598                              'group'     => 'audio',
 599                              'module'    => 'aac',
 600                              'mime_type' => 'application/octet-stream',
 601                              'fail_ape'  => 'WARNING',
 602                          ),
 603  
 604  
 605                  // AU   - audio       - NeXT/Sun AUdio (AU)
 606                  'au'   => array(
 607                              'pattern'   => '^\.snd',
 608                              'group'     => 'audio',
 609                              'module'    => 'au',
 610                              'mime_type' => 'audio/basic',
 611                          ),
 612  
 613                  // AMR  - audio       - Adaptive Multi Rate
 614                  'amr'  => array(
 615                              'pattern'   => '^\x23\x21AMR\x0A', // #!AMR[0A]
 616                              'group'     => 'audio',
 617                              'module'    => 'amr',
 618                              'mime_type' => 'audio/amr',
 619                          ),
 620  
 621                  // AVR  - audio       - Audio Visual Research
 622                  'avr'  => array(
 623                              'pattern'   => '^2BIT',
 624                              'group'     => 'audio',
 625                              'module'    => 'avr',
 626                              'mime_type' => 'application/octet-stream',
 627                          ),
 628  
 629                  // BONK - audio       - Bonk v0.9+
 630                  'bonk' => array(
 631                              'pattern'   => '^\x00(BONK|INFO|META| ID3)',
 632                              'group'     => 'audio',
 633                              'module'    => 'bonk',
 634                              'mime_type' => 'audio/xmms-bonk',
 635                          ),
 636  
 637                  // DSS  - audio       - Digital Speech Standard
 638                  'dss'  => array(
 639                              'pattern'   => '^[\x02-\x03]ds[s2]',
 640                              'group'     => 'audio',
 641                              'module'    => 'dss',
 642                              'mime_type' => 'application/octet-stream',
 643                          ),
 644  
 645                  // DTS  - audio       - Dolby Theatre System
 646                  'dts'  => array(
 647                              'pattern'   => '^\x7F\xFE\x80\x01',
 648                              'group'     => 'audio',
 649                              'module'    => 'dts',
 650                              'mime_type' => 'audio/dts',
 651                          ),
 652  
 653                  // FLAC - audio       - Free Lossless Audio Codec
 654                  'flac' => array(
 655                              'pattern'   => '^fLaC',
 656                              'group'     => 'audio',
 657                              'module'    => 'flac',
 658                              'mime_type' => 'audio/x-flac',
 659                          ),
 660  
 661                  // LA   - audio       - Lossless Audio (LA)
 662                  'la'   => array(
 663                              'pattern'   => '^LA0[2-4]',
 664                              'group'     => 'audio',
 665                              'module'    => 'la',
 666                              'mime_type' => 'application/octet-stream',
 667                          ),
 668  
 669                  // LPAC - audio       - Lossless Predictive Audio Compression (LPAC)
 670                  'lpac' => array(
 671                              'pattern'   => '^LPAC',
 672                              'group'     => 'audio',
 673                              'module'    => 'lpac',
 674                              'mime_type' => 'application/octet-stream',
 675                          ),
 676  
 677                  // MIDI - audio       - MIDI (Musical Instrument Digital Interface)
 678                  'midi' => array(
 679                              'pattern'   => '^MThd',
 680                              'group'     => 'audio',
 681                              'module'    => 'midi',
 682                              'mime_type' => 'audio/midi',
 683                          ),
 684  
 685                  // MAC  - audio       - Monkey's Audio Compressor
 686                  'mac'  => array(
 687                              'pattern'   => '^MAC ',
 688                              'group'     => 'audio',
 689                              'module'    => 'monkey',
 690                              'mime_type' => 'application/octet-stream',
 691                          ),
 692  
 693  // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available
 694  //                // MOD  - audio       - MODule (assorted sub-formats)
 695  //                'mod'  => array(
 696  //                            'pattern'   => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)',
 697  //                            'group'     => 'audio',
 698  //                            'module'    => 'mod',
 699  //                            'option'    => 'mod',
 700  //                            'mime_type' => 'audio/mod',
 701  //                        ),
 702  
 703                  // MOD  - audio       - MODule (Impulse Tracker)
 704                  'it'   => array(
 705                              'pattern'   => '^IMPM',
 706                              'group'     => 'audio',
 707                              'module'    => 'mod',
 708                              //'option'    => 'it',
 709                              'mime_type' => 'audio/it',
 710                          ),
 711  
 712                  // MOD  - audio       - MODule (eXtended Module, various sub-formats)
 713                  'xm'   => array(
 714                              'pattern'   => '^Extended Module',
 715                              'group'     => 'audio',
 716                              'module'    => 'mod',
 717                              //'option'    => 'xm',
 718                              'mime_type' => 'audio/xm',
 719                          ),
 720  
 721                  // MOD  - audio       - MODule (ScreamTracker)
 722                  's3m'  => array(
 723                              'pattern'   => '^.{44}SCRM',
 724                              'group'     => 'audio',
 725                              'module'    => 'mod',
 726                              //'option'    => 's3m',
 727                              'mime_type' => 'audio/s3m',
 728                          ),
 729  
 730                  // MPC  - audio       - Musepack / MPEGplus
 731                  'mpc'  => array(
 732                              'pattern'   => '^(MPCK|MP\+|[\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])',
 733                              'group'     => 'audio',
 734                              'module'    => 'mpc',
 735                              'mime_type' => 'audio/x-musepack',
 736                          ),
 737  
 738                  // MP3  - audio       - MPEG-audio Layer 3 (very similar to AAC-ADTS)
 739                  'mp3'  => array(
 740                              '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]',
 741                              'group'     => 'audio',
 742                              'module'    => 'mp3',
 743                              'mime_type' => 'audio/mpeg',
 744                          ),
 745  
 746                  // OFR  - audio       - OptimFROG
 747                  'ofr'  => array(
 748                              'pattern'   => '^(\*RIFF|OFR)',
 749                              'group'     => 'audio',
 750                              'module'    => 'optimfrog',
 751                              'mime_type' => 'application/octet-stream',
 752                          ),
 753  
 754                  // RKAU - audio       - RKive AUdio compressor
 755                  'rkau' => array(
 756                              'pattern'   => '^RKA',
 757                              'group'     => 'audio',
 758                              'module'    => 'rkau',
 759                              'mime_type' => 'application/octet-stream',
 760                          ),
 761  
 762                  // SHN  - audio       - Shorten
 763                  'shn'  => array(
 764                              'pattern'   => '^ajkg',
 765                              'group'     => 'audio',
 766                              'module'    => 'shorten',
 767                              'mime_type' => 'audio/xmms-shn',
 768                              'fail_id3'  => 'ERROR',
 769                              'fail_ape'  => 'ERROR',
 770                          ),
 771  
 772                  // TTA  - audio       - TTA Lossless Audio Compressor (http://tta.corecodec.org)
 773                  'tta'  => array(
 774                              'pattern'   => '^TTA',  // could also be '^TTA(\x01|\x02|\x03|2|1)'
 775                              'group'     => 'audio',
 776                              'module'    => 'tta',
 777                              'mime_type' => 'application/octet-stream',
 778                          ),
 779  
 780                  // VOC  - audio       - Creative Voice (VOC)
 781                  'voc'  => array(
 782                              'pattern'   => '^Creative Voice File',
 783                              'group'     => 'audio',
 784                              'module'    => 'voc',
 785                              'mime_type' => 'audio/voc',
 786                          ),
 787  
 788                  // VQF  - audio       - transform-domain weighted interleave Vector Quantization Format (VQF)
 789                  'vqf'  => array(
 790                              'pattern'   => '^TWIN',
 791                              'group'     => 'audio',
 792                              'module'    => 'vqf',
 793                              'mime_type' => 'application/octet-stream',
 794                          ),
 795  
 796                  // WV  - audio        - WavPack (v4.0+)
 797                  'wv'   => array(
 798                              'pattern'   => '^wvpk',
 799                              'group'     => 'audio',
 800                              'module'    => 'wavpack',
 801                              'mime_type' => 'application/octet-stream',
 802                          ),
 803  
 804  
 805                  // Audio-Video formats
 806  
 807                  // ASF  - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
 808                  'asf'  => array(
 809                              'pattern'   => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C',
 810                              'group'     => 'audio-video',
 811                              'module'    => 'asf',
 812                              'mime_type' => 'video/x-ms-asf',
 813                              'iconv_req' => false,
 814                          ),
 815  
 816                  // BINK - audio/video - Bink / Smacker
 817                  'bink' => array(
 818                              'pattern'   => '^(BIK|SMK)',
 819                              'group'     => 'audio-video',
 820                              'module'    => 'bink',
 821                              'mime_type' => 'application/octet-stream',
 822                          ),
 823  
 824                  // FLV  - audio/video - FLash Video
 825                  'flv' => array(
 826                              'pattern'   => '^FLV\x01',
 827                              'group'     => 'audio-video',
 828                              'module'    => 'flv',
 829                              'mime_type' => 'video/x-flv',
 830                          ),
 831  
 832                  // MKAV - audio/video - Mastroka
 833                  'matroska' => array(
 834                              'pattern'   => '^\x1A\x45\xDF\xA3',
 835                              'group'     => 'audio-video',
 836                              'module'    => 'matroska',
 837                              'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
 838                          ),
 839  
 840                  // MPEG - audio/video - MPEG (Moving Pictures Experts Group)
 841                  'mpeg' => array(
 842                              'pattern'   => '^\x00\x00\x01(\xBA|\xB3)',
 843                              'group'     => 'audio-video',
 844                              'module'    => 'mpeg',
 845                              'mime_type' => 'video/mpeg',
 846                          ),
 847  
 848                  // NSV  - audio/video - Nullsoft Streaming Video (NSV)
 849                  'nsv'  => array(
 850                              'pattern'   => '^NSV[sf]',
 851                              'group'     => 'audio-video',
 852                              'module'    => 'nsv',
 853                              'mime_type' => 'application/octet-stream',
 854                          ),
 855  
 856                  // Ogg  - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
 857                  'ogg'  => array(
 858                              'pattern'   => '^OggS',
 859                              'group'     => 'audio',
 860                              'module'    => 'ogg',
 861                              'mime_type' => 'application/ogg',
 862                              'fail_id3'  => 'WARNING',
 863                              'fail_ape'  => 'WARNING',
 864                          ),
 865  
 866                  // QT   - audio/video - Quicktime
 867                  'quicktime' => array(
 868                              'pattern'   => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
 869                              'group'     => 'audio-video',
 870                              'module'    => 'quicktime',
 871                              'mime_type' => 'video/quicktime',
 872                          ),
 873  
 874                  // 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)
 875                  'riff' => array(
 876                              'pattern'   => '^(RIFF|SDSS|FORM)',
 877                              'group'     => 'audio-video',
 878                              'module'    => 'riff',
 879                              'mime_type' => 'audio/x-wave',
 880                              'fail_ape'  => 'WARNING',
 881                          ),
 882  
 883                  // Real - audio/video - RealAudio, RealVideo
 884                  'real' => array(
 885                              'pattern'   => '^(\\.RMF|\\.ra)',
 886                              'group'     => 'audio-video',
 887                              'module'    => 'real',
 888                              'mime_type' => 'audio/x-realaudio',
 889                          ),
 890  
 891                  // SWF - audio/video - ShockWave Flash
 892                  'swf' => array(
 893                              'pattern'   => '^(F|C)WS',
 894                              'group'     => 'audio-video',
 895                              'module'    => 'swf',
 896                              'mime_type' => 'application/x-shockwave-flash',
 897                          ),
 898  
 899                  // TS - audio/video - MPEG-2 Transport Stream
 900                  'ts' => array(
 901                              'pattern'   => '^(\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G".  Check for at least 10 packets matching this pattern
 902                              'group'     => 'audio-video',
 903                              'module'    => 'ts',
 904                              'mime_type' => 'video/MP2T',
 905                          ),
 906  
 907  
 908                  // Still-Image formats
 909  
 910                  // BMP  - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
 911                  'bmp'  => array(
 912                              'pattern'   => '^BM',
 913                              'group'     => 'graphic',
 914                              'module'    => 'bmp',
 915                              'mime_type' => 'image/bmp',
 916                              'fail_id3'  => 'ERROR',
 917                              'fail_ape'  => 'ERROR',
 918                          ),
 919  
 920                  // GIF  - still image - Graphics Interchange Format
 921                  'gif'  => array(
 922                              'pattern'   => '^GIF',
 923                              'group'     => 'graphic',
 924                              'module'    => 'gif',
 925                              'mime_type' => 'image/gif',
 926                              'fail_id3'  => 'ERROR',
 927                              'fail_ape'  => 'ERROR',
 928                          ),
 929  
 930                  // JPEG - still image - Joint Photographic Experts Group (JPEG)
 931                  'jpg'  => array(
 932                              'pattern'   => '^\xFF\xD8\xFF',
 933                              'group'     => 'graphic',
 934                              'module'    => 'jpg',
 935                              'mime_type' => 'image/jpeg',
 936                              'fail_id3'  => 'ERROR',
 937                              'fail_ape'  => 'ERROR',
 938                          ),
 939  
 940                  // PCD  - still image - Kodak Photo CD
 941                  'pcd'  => array(
 942                              'pattern'   => '^.{2048}PCD_IPI\x00',
 943                              'group'     => 'graphic',
 944                              'module'    => 'pcd',
 945                              'mime_type' => 'image/x-photo-cd',
 946                              'fail_id3'  => 'ERROR',
 947                              'fail_ape'  => 'ERROR',
 948                          ),
 949  
 950  
 951                  // PNG  - still image - Portable Network Graphics (PNG)
 952                  'png'  => array(
 953                              'pattern'   => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A',
 954                              'group'     => 'graphic',
 955                              'module'    => 'png',
 956                              'mime_type' => 'image/png',
 957                              'fail_id3'  => 'ERROR',
 958                              'fail_ape'  => 'ERROR',
 959                          ),
 960  
 961  
 962                  // SVG  - still image - Scalable Vector Graphics (SVG)
 963                  'svg'  => array(
 964                              'pattern'   => '(<!DOCTYPE svg PUBLIC |xmlns="http:\/\/www\.w3\.org\/2000\/svg")',
 965                              'group'     => 'graphic',
 966                              'module'    => 'svg',
 967                              'mime_type' => 'image/svg+xml',
 968                              'fail_id3'  => 'ERROR',
 969                              'fail_ape'  => 'ERROR',
 970                          ),
 971  
 972  
 973                  // TIFF - still image - Tagged Information File Format (TIFF)
 974                  'tiff' => array(
 975                              'pattern'   => '^(II\x2A\x00|MM\x00\x2A)',
 976                              'group'     => 'graphic',
 977                              'module'    => 'tiff',
 978                              'mime_type' => 'image/tiff',
 979                              'fail_id3'  => 'ERROR',
 980                              'fail_ape'  => 'ERROR',
 981                          ),
 982  
 983  
 984                  // EFAX - still image - eFax (TIFF derivative)
 985                  'efax'  => array(
 986                              'pattern'   => '^\xDC\xFE',
 987                              'group'     => 'graphic',
 988                              'module'    => 'efax',
 989                              'mime_type' => 'image/efax',
 990                              'fail_id3'  => 'ERROR',
 991                              'fail_ape'  => 'ERROR',
 992                          ),
 993  
 994  
 995                  // Data formats
 996  
 997                  // ISO  - data        - International Standards Organization (ISO) CD-ROM Image
 998                  'iso'  => array(
 999                              'pattern'   => '^.{32769}CD001',
1000                              'group'     => 'misc',
1001                              'module'    => 'iso',
1002                              'mime_type' => 'application/octet-stream',
1003                              'fail_id3'  => 'ERROR',
1004                              'fail_ape'  => 'ERROR',
1005                              'iconv_req' => false,
1006                          ),
1007  
1008                  // RAR  - data        - RAR compressed data
1009                  'rar'  => array(
1010                              'pattern'   => '^Rar\!',
1011                              'group'     => 'archive',
1012                              'module'    => 'rar',
1013                              'mime_type' => 'application/octet-stream',
1014                              'fail_id3'  => 'ERROR',
1015                              'fail_ape'  => 'ERROR',
1016                          ),
1017  
1018                  // SZIP - audio/data  - SZIP compressed data
1019                  'szip' => array(
1020                              'pattern'   => '^SZ\x0A\x04',
1021                              'group'     => 'archive',
1022                              'module'    => 'szip',
1023                              'mime_type' => 'application/octet-stream',
1024                              'fail_id3'  => 'ERROR',
1025                              'fail_ape'  => 'ERROR',
1026                          ),
1027  
1028                  // TAR  - data        - TAR compressed data
1029                  'tar'  => array(
1030                              '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}',
1031                              'group'     => 'archive',
1032                              'module'    => 'tar',
1033                              'mime_type' => 'application/x-tar',
1034                              'fail_id3'  => 'ERROR',
1035                              'fail_ape'  => 'ERROR',
1036                          ),
1037  
1038                  // GZIP  - data        - GZIP compressed data
1039                  'gz'  => array(
1040                              'pattern'   => '^\x1F\x8B\x08',
1041                              'group'     => 'archive',
1042                              'module'    => 'gzip',
1043                              'mime_type' => 'application/x-gzip',
1044                              'fail_id3'  => 'ERROR',
1045                              'fail_ape'  => 'ERROR',
1046                          ),
1047  
1048                  // ZIP  - data         - ZIP compressed data
1049                  'zip'  => array(
1050                              'pattern'   => '^PK\x03\x04',
1051                              'group'     => 'archive',
1052                              'module'    => 'zip',
1053                              'mime_type' => 'application/zip',
1054                              'fail_id3'  => 'ERROR',
1055                              'fail_ape'  => 'ERROR',
1056                          ),
1057  
1058  
1059                  // Misc other formats
1060  
1061                  // PAR2 - data        - Parity Volume Set Specification 2.0
1062                  'par2' => array (
1063                              'pattern'   => '^PAR2\x00PKT',
1064                              'group'     => 'misc',
1065                              'module'    => 'par2',
1066                              'mime_type' => 'application/octet-stream',
1067                              'fail_id3'  => 'ERROR',
1068                              'fail_ape'  => 'ERROR',
1069                          ),
1070  
1071                  // PDF  - data        - Portable Document Format
1072                  'pdf'  => array(
1073                              'pattern'   => '^\x25PDF',
1074                              'group'     => 'misc',
1075                              'module'    => 'pdf',
1076                              'mime_type' => 'application/pdf',
1077                              'fail_id3'  => 'ERROR',
1078                              'fail_ape'  => 'ERROR',
1079                          ),
1080  
1081                  // MSOFFICE  - data   - ZIP compressed data
1082                  'msoffice' => array(
1083                              'pattern'   => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
1084                              'group'     => 'misc',
1085                              'module'    => 'msoffice',
1086                              'mime_type' => 'application/octet-stream',
1087                              'fail_id3'  => 'ERROR',
1088                              'fail_ape'  => 'ERROR',
1089                          ),
1090  
1091                   // CUE  - data       - CUEsheet (index to single-file disc images)
1092                   'cue' => array(
1093                              'pattern'   => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
1094                              'group'     => 'misc',
1095                              'module'    => 'cue',
1096                              'mime_type' => 'application/octet-stream',
1097                             ),
1098  
1099              );
1100          }
1101  
1102          return $format_info;
1103      }
1104  
1105  
1106  
1107  	public function GetFileFormat(&$filedata, $filename='') {
1108          // this function will determine the format of a file based on usually
1109          // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
1110          // and in the case of ISO CD image, 6 bytes offset 32kb from the start
1111          // of the file).
1112  
1113          // Identify file format - loop through $format_info and detect with reg expr
1114          foreach ($this->GetFileFormatArray() as $format_name => $info) {
1115              // The /s switch on preg_match() forces preg_match() NOT to treat
1116              // newline (0x0A) characters as special chars but do a binary match
1117              if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) {
1118                  $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1119                  return $info;
1120              }
1121          }
1122  
1123  
1124          if (preg_match('#\.mp[123a]$#i', $filename)) {
1125              // Too many mp3 encoders on the market put gabage in front of mpeg files
1126              // use assume format on these if format detection failed
1127              $GetFileFormatArray = $this->GetFileFormatArray();
1128              $info = $GetFileFormatArray['mp3'];
1129              $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1130              return $info;
1131          } elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
1132              // there's not really a useful consistent "magic" at the beginning of .cue files to identify them
1133              // so until I think of something better, just go by filename if all other format checks fail
1134              // and verify there's at least one instance of "TRACK xx AUDIO" in the file
1135              $GetFileFormatArray = $this->GetFileFormatArray();
1136              $info = $GetFileFormatArray['cue'];
1137              $info['include']   = 'module.'.$info['group'].'.'.$info['module'].'.php';
1138              return $info;
1139          }
1140  
1141          return false;
1142      }
1143  
1144  
1145      // converts array to $encoding charset from $this->encoding
1146  	public function CharConvert(&$array, $encoding) {
1147  
1148          // identical encoding - end here
1149          if ($encoding == $this->encoding) {
1150              return;
1151          }
1152  
1153          // loop thru array
1154          foreach ($array as $key => $value) {
1155  
1156              // go recursive
1157              if (is_array($value)) {
1158                  $this->CharConvert($array[$key], $encoding);
1159              }
1160  
1161              // convert string
1162              elseif (is_string($value)) {
1163                  $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value));
1164              }
1165          }
1166      }
1167  
1168  
1169  	public function HandleAllTags() {
1170  
1171          // key name => array (tag name, character encoding)
1172          static $tags;
1173          if (empty($tags)) {
1174              $tags = array(
1175                  'asf'       => array('asf'           , 'UTF-16LE'),
1176                  'midi'      => array('midi'          , 'ISO-8859-1'),
1177                  'nsv'       => array('nsv'           , 'ISO-8859-1'),
1178                  'ogg'       => array('vorbiscomment' , 'UTF-8'),
1179                  'png'       => array('png'           , 'UTF-8'),
1180                  'tiff'      => array('tiff'          , 'ISO-8859-1'),
1181                  'quicktime' => array('quicktime'     , 'UTF-8'),
1182                  'real'      => array('real'          , 'ISO-8859-1'),
1183                  'vqf'       => array('vqf'           , 'ISO-8859-1'),
1184                  'zip'       => array('zip'           , 'ISO-8859-1'),
1185                  'riff'      => array('riff'          , 'ISO-8859-1'),
1186                  'lyrics3'   => array('lyrics3'       , 'ISO-8859-1'),
1187                  'id3v1'     => array('id3v1'         , $this->encoding_id3v1),
1188                  '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
1189                  'ape'       => array('ape'           , 'UTF-8'),
1190                  'cue'       => array('cue'           , 'ISO-8859-1'),
1191                  'matroska'  => array('matroska'      , 'UTF-8'),
1192                  'flac'      => array('vorbiscomment' , 'UTF-8'),
1193                  'divxtag'   => array('divx'          , 'ISO-8859-1'),
1194                  'iptc'      => array('iptc'          , 'ISO-8859-1'),
1195              );
1196          }
1197  
1198          // loop through comments array
1199          foreach ($tags as $comment_name => $tagname_encoding_array) {
1200              list($tag_name, $encoding) = $tagname_encoding_array;
1201  
1202              // fill in default encoding type if not already present
1203              if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) {
1204                  $this->info[$comment_name]['encoding'] = $encoding;
1205              }
1206  
1207              // copy comments if key name set
1208              if (!empty($this->info[$comment_name]['comments'])) {
1209                  foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) {
1210                      foreach ($valuearray as $key => $value) {
1211                          if (is_string($value)) {
1212                              $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
1213                          }
1214                          if ($value) {
1215                              if (!is_numeric($key)) {
1216                                  $this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
1217                              } else {
1218                                  $this->info['tags'][trim($tag_name)][trim($tag_key)][]     = $value;
1219                              }
1220                          }
1221                      }
1222                      if ($tag_key == 'picture') {
1223                          unset($this->info[$comment_name]['comments'][$tag_key]);
1224                      }
1225                  }
1226  
1227                  if (!isset($this->info['tags'][$tag_name])) {
1228                      // comments are set but contain nothing but empty strings, so skip
1229                      continue;
1230                  }
1231  
1232                  if ($this->option_tags_html) {
1233                      foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
1234                          $this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $encoding);
1235                      }
1236                  }
1237  
1238                  $this->CharConvert($this->info['tags'][$tag_name], $encoding);           // only copy gets converted!
1239              }
1240  
1241          }
1242  
1243          // pictures can take up a lot of space, and we don't need multiple copies of them
1244          // let there be a single copy in [comments][picture], and not elsewhere
1245          if (!empty($this->info['tags'])) {
1246              $unset_keys = array('tags', 'tags_html');
1247              foreach ($this->info['tags'] as $tagtype => $tagarray) {
1248                  foreach ($tagarray as $tagname => $tagdata) {
1249                      if ($tagname == 'picture') {
1250                          foreach ($tagdata as $key => $tagarray) {
1251                              $this->info['comments']['picture'][] = $tagarray;
1252                              if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
1253                                  if (isset($this->info['tags'][$tagtype][$tagname][$key])) {
1254                                      unset($this->info['tags'][$tagtype][$tagname][$key]);
1255                                  }
1256                                  if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) {
1257                                      unset($this->info['tags_html'][$tagtype][$tagname][$key]);
1258                                  }
1259                              }
1260                          }
1261                      }
1262                  }
1263                  foreach ($unset_keys as $unset_key) {
1264                      // remove possible empty keys from (e.g. [tags][id3v2][picture])
1265                      if (empty($this->info[$unset_key][$tagtype]['picture'])) {
1266                          unset($this->info[$unset_key][$tagtype]['picture']);
1267                      }
1268                      if (empty($this->info[$unset_key][$tagtype])) {
1269                          unset($this->info[$unset_key][$tagtype]);
1270                      }
1271                      if (empty($this->info[$unset_key])) {
1272                          unset($this->info[$unset_key]);
1273                      }
1274                  }
1275                  // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
1276                  if (isset($this->info[$tagtype]['comments']['picture'])) {
1277                      unset($this->info[$tagtype]['comments']['picture']);
1278                  }
1279                  if (empty($this->info[$tagtype]['comments'])) {
1280                      unset($this->info[$tagtype]['comments']);
1281                  }
1282                  if (empty($this->info[$tagtype])) {
1283                      unset($this->info[$tagtype]);
1284                  }
1285              }
1286          }
1287          return true;
1288      }
1289  
1290  	public function getHashdata($algorithm) {
1291          switch ($algorithm) {
1292              case 'md5':
1293              case 'sha1':
1294                  break;
1295  
1296              default:
1297                  return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
1298                  break;
1299          }
1300  
1301          if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) {
1302  
1303              // We cannot get an identical md5_data value for Ogg files where the comments
1304              // span more than 1 Ogg page (compared to the same audio data with smaller
1305              // comments) using the normal getID3() method of MD5'ing the data between the
1306              // end of the comments and the end of the file (minus any trailing tags),
1307              // because the page sequence numbers of the pages that the audio data is on
1308              // do not match. Under normal circumstances, where comments are smaller than
1309              // the nominal 4-8kB page size, then this is not a problem, but if there are
1310              // very large comments, the only way around it is to strip off the comment
1311              // tags with vorbiscomment and MD5 that file.
1312              // This procedure must be applied to ALL Ogg files, not just the ones with
1313              // comments larger than 1 page, because the below method simply MD5's the
1314              // whole file with the comments stripped, not just the portion after the
1315              // comments block (which is the standard getID3() method.
1316  
1317              // The above-mentioned problem of comments spanning multiple pages and changing
1318              // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
1319              // currently vorbiscomment only works on OggVorbis files.
1320  
1321              if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
1322  
1323                  $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
1324                  $this->info[$algorithm.'_data'] = false;
1325  
1326              } else {
1327  
1328                  // Prevent user from aborting script
1329                  $old_abort = ignore_user_abort(true);
1330  
1331                  // Create empty file
1332                  $empty = tempnam(GETID3_TEMP_DIR, 'getID3');
1333                  touch($empty);
1334  
1335                  // Use vorbiscomment to make temp file without comments
1336                  $temp = tempnam(GETID3_TEMP_DIR, 'getID3');
1337                  $file = $this->info['filenamepath'];
1338  
1339                  if (GETID3_OS_ISWINDOWS) {
1340  
1341                      if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {
1342  
1343                          $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
1344                          $VorbisCommentError = `$commandline`;
1345  
1346                      } else {
1347  
1348                          $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;
1349  
1350                      }
1351  
1352                  } else {
1353  
1354                      $commandline = 'vorbiscomment -w -c "'.$empty.'" "'.$file.'" "'.$temp.'" 2>&1';
1355                      $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
1356                      $VorbisCommentError = `$commandline`;
1357  
1358                  }
1359  
1360                  if (!empty($VorbisCommentError)) {
1361  
1362                      $this->info['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;
1363                      $this->info[$algorithm.'_data']  = false;
1364  
1365                  } else {
1366  
1367                      // Get hash of newly created file
1368                      switch ($algorithm) {
1369                          case 'md5':
1370                              $this->info[$algorithm.'_data'] = md5_file($temp);
1371                              break;
1372  
1373                          case 'sha1':
1374                              $this->info[$algorithm.'_data'] = sha1_file($temp);
1375                              break;
1376                      }
1377                  }
1378  
1379                  // Clean up
1380                  unlink($empty);
1381                  unlink($temp);
1382  
1383                  // Reset abort setting
1384                  ignore_user_abort($old_abort);
1385  
1386              }
1387  
1388          } else {
1389  
1390              if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) {
1391  
1392                  // get hash from part of file
1393                  $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm);
1394  
1395              } else {
1396  
1397                  // get hash from whole file
1398                  switch ($algorithm) {
1399                      case 'md5':
1400                          $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']);
1401                          break;
1402  
1403                      case 'sha1':
1404                          $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']);
1405                          break;
1406                  }
1407              }
1408  
1409          }
1410          return true;
1411      }
1412  
1413  
1414  	public function ChannelsBitratePlaytimeCalculations() {
1415  
1416          // set channelmode on audio
1417          if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) {
1418              // ignore
1419          } elseif ($this->info['audio']['channels'] == 1) {
1420              $this->info['audio']['channelmode'] = 'mono';
1421          } elseif ($this->info['audio']['channels'] == 2) {
1422              $this->info['audio']['channelmode'] = 'stereo';
1423          }
1424  
1425          // Calculate combined bitrate - audio + video
1426          $CombinedBitrate  = 0;
1427          $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0);
1428          $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0);
1429          if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) {
1430              $this->info['bitrate'] = $CombinedBitrate;
1431          }
1432          //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
1433          //    // for example, VBR MPEG video files cannot determine video bitrate:
1434          //    // should not set overall bitrate and playtime from audio bitrate only
1435          //    unset($this->info['bitrate']);
1436          //}
1437  
1438          // video bitrate undetermined, but calculable
1439          if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) {
1440              // if video bitrate not set
1441              if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) {
1442                  // AND if audio bitrate is set to same as overall bitrate
1443                  if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) {
1444                      // AND if playtime is set
1445                      if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) {
1446                          // AND if AV data offset start/end is known
1447                          // THEN we can calculate the video bitrate
1448                          $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']);
1449                          $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate'];
1450                      }
1451                  }
1452              }
1453          }
1454  
1455          if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) {
1456              $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
1457          }
1458  
1459          if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) {
1460              $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds'];
1461          }
1462          if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) {
1463              if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) {
1464                  // audio only
1465                  $this->info['audio']['bitrate'] = $this->info['bitrate'];
1466              } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) {
1467                  // video only
1468                  $this->info['video']['bitrate'] = $this->info['bitrate'];
1469              }
1470          }
1471  
1472          // Set playtime string
1473          if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
1474              $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']);
1475          }
1476      }
1477  
1478  
1479  	public function CalculateCompressionRatioVideo() {
1480          if (empty($this->info['video'])) {
1481              return false;
1482          }
1483          if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) {
1484              return false;
1485          }
1486          if (empty($this->info['video']['bits_per_sample'])) {
1487              return false;
1488          }
1489  
1490          switch ($this->info['video']['dataformat']) {
1491              case 'bmp':
1492              case 'gif':
1493              case 'jpeg':
1494              case 'jpg':
1495              case 'png':
1496              case 'tiff':
1497                  $FrameRate = 1;
1498                  $PlaytimeSeconds = 1;
1499                  $BitrateCompressed = $this->info['filesize'] * 8;
1500                  break;
1501  
1502              default:
1503                  if (!empty($this->info['video']['frame_rate'])) {
1504                      $FrameRate = $this->info['video']['frame_rate'];
1505                  } else {
1506                      return false;
1507                  }
1508                  if (!empty($this->info['playtime_seconds'])) {
1509                      $PlaytimeSeconds = $this->info['playtime_seconds'];
1510                  } else {
1511                      return false;
1512                  }
1513                  if (!empty($this->info['video']['bitrate'])) {
1514                      $BitrateCompressed = $this->info['video']['bitrate'];
1515                  } else {
1516                      return false;
1517                  }
1518                  break;
1519          }
1520          $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;
1521  
1522          $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
1523          return true;
1524      }
1525  
1526  
1527  	public function CalculateCompressionRatioAudio() {
1528          if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) {
1529              return false;
1530          }
1531          $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));
1532  
1533          if (!empty($this->info['audio']['streams'])) {
1534              foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) {
1535                  if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) {
1536                      $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16));
1537                  }
1538              }
1539          }
1540          return true;
1541      }
1542  
1543  
1544  	public function CalculateReplayGain() {
1545          if (isset($this->info['replay_gain'])) {
1546              if (!isset($this->info['replay_gain']['reference_volume'])) {
1547                  $this->info['replay_gain']['reference_volume'] = (double) 89.0;
1548              }
1549              if (isset($this->info['replay_gain']['track']['adjustment'])) {
1550                  $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment'];
1551              }
1552              if (isset($this->info['replay_gain']['album']['adjustment'])) {
1553                  $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment'];
1554              }
1555  
1556              if (isset($this->info['replay_gain']['track']['peak'])) {
1557                  $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']);
1558              }
1559              if (isset($this->info['replay_gain']['album']['peak'])) {
1560                  $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']);
1561              }
1562          }
1563          return true;
1564      }
1565  
1566  	public function ProcessAudioStreams() {
1567          if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) {
1568              if (!isset($this->info['audio']['streams'])) {
1569                  foreach ($this->info['audio'] as $key => $value) {
1570                      if ($key != 'streams') {
1571                          $this->info['audio']['streams'][0][$key] = $value;
1572                      }
1573                  }
1574              }
1575          }
1576          return true;
1577      }
1578  
1579  	public function getid3_tempnam() {
1580          return tempnam($this->tempdir, 'gI3');
1581      }
1582  
1583  	public function include_module($name) {
1584          //if (!file_exists($this->include_path.'module.'.$name.'.php')) {
1585          if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) {
1586              throw new getid3_exception('Required module.'.$name.'.php is missing.');
1587          }
1588          include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php');
1589          return true;
1590      }
1591  
1592  }
1593  
1594  
1595  abstract class getid3_handler {
1596  
1597      /**
1598      * @var getID3
1599      */
1600      protected $getid3;                       // pointer
1601  
1602      protected $data_string_flag     = false; // analyzing filepointer or string
1603      protected $data_string          = '';    // string to analyze
1604      protected $data_string_position = 0;     // seek position in string
1605      protected $data_string_length   = 0;     // string length
1606  
1607      private $dependency_to = null;
1608  
1609  
1610  	public function __construct(getID3 $getid3, $call_module=null) {
1611          $this->getid3 = $getid3;
1612  
1613          if ($call_module) {
1614              $this->dependency_to = str_replace('getid3_', '', $call_module);
1615          }
1616      }
1617  
1618  
1619      // Analyze from file pointer
1620      abstract public function Analyze();
1621  
1622  
1623      // Analyze from string instead
1624  	public function AnalyzeString($string) {
1625          // Enter string mode
1626          $this->setStringMode($string);
1627  
1628          // Save info
1629          $saved_avdataoffset = $this->getid3->info['avdataoffset'];
1630          $saved_avdataend    = $this->getid3->info['avdataend'];
1631          $saved_filesize     = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call
1632  
1633          // Reset some info
1634          $this->getid3->info['avdataoffset'] = 0;
1635          $this->getid3->info['avdataend']    = $this->getid3->info['filesize'] = $this->data_string_length;
1636  
1637          // Analyze
1638          $this->Analyze();
1639  
1640          // Restore some info
1641          $this->getid3->info['avdataoffset'] = $saved_avdataoffset;
1642          $this->getid3->info['avdataend']    = $saved_avdataend;
1643          $this->getid3->info['filesize']     = $saved_filesize;
1644  
1645          // Exit string mode
1646          $this->data_string_flag = false;
1647      }
1648  
1649  	public function setStringMode($string) {
1650          $this->data_string_flag   = true;
1651          $this->data_string        = $string;
1652          $this->data_string_length = strlen($string);
1653      }
1654  
1655  	protected function ftell() {
1656          if ($this->data_string_flag) {
1657              return $this->data_string_position;
1658          }
1659          return ftell($this->getid3->fp);
1660      }
1661  
1662  	protected function fread($bytes) {
1663          if ($this->data_string_flag) {
1664              $this->data_string_position += $bytes;
1665              return substr($this->data_string, $this->data_string_position - $bytes, $bytes);
1666          }
1667          $pos = $this->ftell() + $bytes;
1668          if (!getid3_lib::intValueSupported($pos)) {
1669              throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10);
1670          }
1671          return fread($this->getid3->fp, $bytes);
1672      }
1673  
1674  	protected function fseek($bytes, $whence=SEEK_SET) {
1675          if ($this->data_string_flag) {
1676              switch ($whence) {
1677                  case SEEK_SET:
1678                      $this->data_string_position = $bytes;
1679                      break;
1680  
1681                  case SEEK_CUR:
1682                      $this->data_string_position += $bytes;
1683                      break;
1684  
1685                  case SEEK_END:
1686                      $this->data_string_position = $this->data_string_length + $bytes;
1687                      break;
1688              }
1689              return 0;
1690          } else {
1691              $pos = $bytes;
1692              if ($whence == SEEK_CUR) {
1693                  $pos = $this->ftell() + $bytes;
1694              } elseif ($whence == SEEK_END) {
1695                  $pos = $this->getid3->info['filesize'] + $bytes;
1696              }
1697              if (!getid3_lib::intValueSupported($pos)) {
1698                  throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
1699              }
1700          }
1701          return fseek($this->getid3->fp, $bytes, $whence);
1702      }
1703  
1704  	protected function feof() {
1705          if ($this->data_string_flag) {
1706              return $this->data_string_position >= $this->data_string_length;
1707          }
1708          return feof($this->getid3->fp);
1709      }
1710  
1711  	final protected function isDependencyFor($module) {
1712          return $this->dependency_to == $module;
1713      }
1714  
1715  	protected function error($text) {
1716          $this->getid3->info['error'][] = $text;
1717  
1718          return false;
1719      }
1720  
1721  	protected function warning($text) {
1722          return $this->getid3->warning($text);
1723      }
1724  
1725  	protected function notice($text) {
1726          // does nothing for now
1727      }
1728  
1729  	public function saveAttachment($name, $offset, $length, $image_mime=null) {
1730          try {
1731  
1732              // do not extract at all
1733              if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) {
1734  
1735                  $attachment = null; // do not set any
1736  
1737              // extract to return array
1738              } elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) {
1739  
1740                  $this->fseek($offset);
1741                  $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory
1742                  if ($attachment === false || strlen($attachment) != $length) {
1743                      throw new Exception('failed to read attachment data');
1744                  }
1745  
1746              // assume directory path is given
1747              } else {
1748  
1749                  // set up destination path
1750                  $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1751                  if (!is_dir($dir) || !is_writable($dir)) { // check supplied directory
1752                      throw new Exception('supplied path ('.$dir.') does not exist, or is not writable');
1753                  }
1754                  $dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : '');
1755  
1756                  // create dest file
1757                  if (($fp_dest = fopen($dest, 'wb')) == false) {
1758                      throw new Exception('failed to create file '.$dest);
1759                  }
1760  
1761                  // copy data
1762                  $this->fseek($offset);
1763                  $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size());
1764                  $bytesleft = $length;
1765                  while ($bytesleft > 0) {
1766                      if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) {
1767                          throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space');
1768                      }
1769                      $bytesleft -= $byteswritten;
1770                  }
1771  
1772                  fclose($fp_dest);
1773                  $attachment = $dest;
1774  
1775              }
1776  
1777          } catch (Exception $e) {
1778  
1779              // close and remove dest file if created
1780              if (isset($fp_dest) && is_resource($fp_dest)) {
1781                  fclose($fp_dest);
1782                  unlink($dest);
1783              }
1784  
1785              // do not set any is case of error
1786              $attachment = null;
1787              $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage());
1788  
1789          }
1790  
1791          // seek to the end of attachment
1792          $this->fseek($offset + $length);
1793  
1794          return $attachment;
1795      }
1796  
1797  }
1798  
1799  
1800  class getid3_exception extends Exception
1801  {
1802      public $message;
1803  }

title

Description

title

Description

title

Description

title

title

Body