PageRenderTime 9ms CodeModel.GetById 181ms app.highlight 104ms RepoModel.GetById 47ms app.codeStats 1ms

/Vendor/getid3/getid3.php

https://bitbucket.org/nova-atlantis/simple-server-media-player
PHP | 1803 lines | 1280 code | 271 blank | 252 comment | 243 complexity | bafd73cd94e882b5eda7affa01840cdc MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file