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