balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

getid3.php (75114B)


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