Passed
Branch ticket-41057 (c4f931)
by Stephen
24:38
created

getID3::ProcessAudioStreams()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 7
nc 3
nop 0
dl 0
loc 12
rs 8.2222
c 0
b 0
f 0
1
<?php
2
/////////////////////////////////////////////////////////////////
3
/// getID3() by James Heinrich <[email protected]>               //
4
//  available at http://getid3.sourceforge.net                 //
5
//            or http://www.getid3.org                         //
6
//          also https://github.com/JamesHeinrich/getID3       //
7
/////////////////////////////////////////////////////////////////
8
//                                                             //
9
// Please see readme.txt for more information                  //
10
//                                                            ///
11
/////////////////////////////////////////////////////////////////
12
13
// define a constant rather than looking up every time it is needed
14
if (!defined('GETID3_OS_ISWINDOWS')) {
15
	define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0));
16
}
17
// Get base path of getID3() - ONCE
18
if (!defined('GETID3_INCLUDEPATH')) {
19
	define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
20
}
21
// Workaround Bug #39923 (https://bugs.php.net/bug.php?id=39923)
22
if (!defined('IMG_JPG') && defined('IMAGETYPE_JPEG')) {
23
	define('IMG_JPG', IMAGETYPE_JPEG);
24
}
25
26
// attempt to define temp dir as something flexible but reliable
27
$temp_dir = ini_get('upload_tmp_dir');
28
if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) {
29
	$temp_dir = '';
30
}
31
if (!$temp_dir && 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');
37
if ($open_basedir) {
38
	// e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/"
39
	$temp_dir     = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir);
40
	$open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir);
41
	if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) {
42
		$temp_dir .= DIRECTORY_SEPARATOR;
43
	}
44
	$found_valid_tempdir = false;
45
	$open_basedirs = explode(PATH_SEPARATOR, $open_basedir);
46
	foreach ($open_basedirs as $basedir) {
47
		if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) {
48
			$basedir .= DIRECTORY_SEPARATOR;
49
		}
50
		if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) {
51
			$found_valid_tempdir = true;
52
			break;
53
		}
54
	}
55
	if (!$found_valid_tempdir) {
56
		$temp_dir = '';
57
	}
58
	unset($open_basedirs, $found_valid_tempdir, $basedir);
59
}
60
if (!$temp_dir) {
61
	$temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
62
}
63
// $temp_dir = '/something/else/';  // feel free to override temp dir here if it works better for your system
64
if (!defined('GETID3_TEMP_DIR')) {
65
	define('GETID3_TEMP_DIR', $temp_dir);
66
}
67
unset($open_basedir, $temp_dir);
68
69
// End: Defines
70
71
72
class getID3
73
{
74
	// public: Settings
75
	public $encoding        = 'UTF-8';        // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples:  ISO-8859-1  UTF-8  UTF-16  UTF-16BE
76
	public $encoding_id3v1  = 'ISO-8859-1';   // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252'
77
78
	// public: Optional tag checks - disable for speed.
79
	public $option_tag_id3v1         = true;  // Read and process ID3v1 tags
80
	public $option_tag_id3v2         = true;  // Read and process ID3v2 tags
81
	public $option_tag_lyrics3       = true;  // Read and process Lyrics3 tags
82
	public $option_tag_apetag        = true;  // Read and process APE tags
83
	public $option_tags_process      = true;  // Copy tags to root key 'tags' and encode to $this->encoding
84
	public $option_tags_html         = true;  // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
85
86
	// public: Optional tag/comment calucations
87
	public $option_extra_info        = true;  // Calculate additional info such as bitrate, channelmode etc
88
89
	// public: Optional handling of embedded attachments (e.g. images)
90
	public $option_save_attachments  = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility
91
92
	// public: Optional calculations
93
	public $option_md5_data          = false; // Get MD5 sum of data part - slow
94
	public $option_md5_data_source   = false; // Use MD5 of source file if availble - only FLAC and OptimFROG
95
	public $option_sha1_data         = false; // Get SHA1 sum of data part - slow
96
	public $option_max_2gb_check     = null;  // Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on PHP_INT_MAX)
97
98
	// public: Read buffer size in bytes
99
	public $option_fread_buffer_size = 32768;
100
101
	// Public variables
102
	public $filename;                         // Filename of file being analysed.
103
	public $fp;                               // Filepointer to file being analysed.
104
	public $info;                             // Result array.
105
	public $tempdir = GETID3_TEMP_DIR;
106
	public $memory_limit = 0;
107
108
	// Protected variables
109
	protected $startup_error   = '';
110
	protected $startup_warning = '';
111
112
	const VERSION           = '1.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() {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
120
121
		// Check memory
122
		$this->memory_limit = ini_get('memory_limit');
0 ignored issues
show
Documentation Bug introduced by
The property $memory_limit was declared of type integer, but ini_get('memory_limit') is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
123
		if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) {
124
			// could be stored as "16M" rather than 16777216 for example
125
			$this->memory_limit = $matches[1] * 1048576;
0 ignored issues
show
Documentation Bug introduced by
It seems like $matches[1] * 1048576 can also be of type double. However, the property $memory_limit is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
126
		} elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
127
			// could be stored as "2G" rather than 2147483648 for example
128
			$this->memory_limit = $matches[1] * 1073741824;
0 ignored issues
show
Documentation Bug introduced by
It seems like $matches[1] * 1073741824 can also be of type double. However, the property $memory_limit is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
129
		}
130
		if ($this->memory_limit <= 0) {
131
			// memory limits probably disabled
132
		} elseif ($this->memory_limit <= 4194304) {
133
			$this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini';
134
		} elseif ($this->memory_limit <= 12582912) {
135
			$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';
136
		}
137
138
		// Check safe_mode off
139
		if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
140
			$this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
141
		}
142
143
		if (intval(ini_get('mbstring.func_overload')) > 0) {
144
			$this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.');
145
		}
146
147
		// Check for magic_quotes_runtime
148
		if (function_exists('get_magic_quotes_runtime')) {
149
			if (get_magic_quotes_runtime()) {
150
				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).');
0 ignored issues
show
Bug introduced by
The method startup_error() does not seem to exist on object<getID3>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
151
			}
152
		}
153
154
		// Check for magic_quotes_gpc
155
		if (function_exists('magic_quotes_gpc')) {
156
			if (get_magic_quotes_gpc()) {
157
				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).');
0 ignored issues
show
Bug introduced by
The method startup_error() does not seem to exist on object<getID3>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
158
			}
159
		}
160
161
		// Load support library
162
		if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
163
			$this->startup_error .= 'getid3.lib.php is missing or corrupt';
164
		}
165
166
		if ($this->option_max_2gb_check === null) {
167
			$this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647);
168
		}
169
170
171
		// Needed for Windows only:
172
		// Define locations of helper applications for Shorten, VorbisComment, MetaFLAC
173
		//   as well as other helper functions such as head, tail, md5sum, etc
174
		// This path cannot contain spaces, but the below code will attempt to get the
175
		//   8.3-equivalent path automatically
176
		// IMPORTANT: This path must include the trailing slash
177
		if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) {
178
179
			$helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path
180
181
			if (!is_dir($helperappsdir)) {
182
				$this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist';
183
			} elseif (strpos(realpath($helperappsdir), ' ') !== false) {
184
				$DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir));
185
				$path_so_far = array();
186
				foreach ($DirPieces as $key => $value) {
187
					if (strpos($value, ' ') !== false) {
188
						if (!empty($path_so_far)) {
189
							$commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far));
190
							$dir_listing = `$commandline`;
191
							$lines = explode("\n", $dir_listing);
192
							foreach ($lines as $line) {
193
								$line = trim($line);
194
								if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) {
195
									list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches;
0 ignored issues
show
Unused Code introduced by
The assignment to $dummy is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $date is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $time is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $ampm is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
196
									if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) {
197
										$value = $shortname;
198
									}
199
								}
200
							}
201
						} else {
202
							$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.';
203
						}
204
					}
205
					$path_so_far[] = $value;
206
				}
207
				$helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far);
208
			}
209
			define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR);
210
		}
211
212
		return true;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
213
	}
214
215
	public function version() {
216
		return self::VERSION;
217
	}
218
219
	public function fread_buffer_size() {
220
		return $this->option_fread_buffer_size;
221
	}
222
223
224
	// public: setOption
225
	public function setOption($optArray) {
226
		if (!is_array($optArray) || empty($optArray)) {
227
			return false;
228
		}
229
		foreach ($optArray as $opt => $val) {
230
			if (isset($this->$opt) === false) {
231
				continue;
232
			}
233
			$this->$opt = $val;
234
		}
235
		return true;
236
	}
237
238
239
	public function openfile($filename) {
240
		try {
241
			if (!empty($this->startup_error)) {
242
				throw new getid3_exception($this->startup_error);
243
			}
244
			if (!empty($this->startup_warning)) {
245
				$this->warning($this->startup_warning);
246
			}
247
248
			// init result array and set parameters
249
			$this->filename = $filename;
250
			$this->info = array();
251
			$this->info['GETID3_VERSION']   = $this->version();
252
			$this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false);
253
254
			// remote files not supported
255
			if (preg_match('/^(ht|f)tp:\/\//', $filename)) {
256
				throw new getid3_exception('Remote files are not supported - please copy the file locally first');
257
			}
258
259
			$filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
260
			$filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename);
261
262
			// open local file
263
			//if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see http://www.getid3.org/phpBB3/viewtopic.php?t=1720
264
			if ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) {
265
				// great
266
			} else {
267
				$errormessagelist = array();
268
				if (!is_readable($filename)) {
269
					$errormessagelist[] = '!is_readable';
270
				}
271
				if (!is_file($filename)) {
272
					$errormessagelist[] = '!is_file';
273
				}
274
				if (!file_exists($filename)) {
275
					$errormessagelist[] = '!file_exists';
276
				}
277
				if (empty($errormessagelist)) {
278
					$errormessagelist[] = 'fopen failed';
279
				}
280
				throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')');
281
			}
282
283
			$this->info['filesize'] = filesize($filename);
284
			// set redundant parameters - might be needed in some include file
285
			// filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion
286
			$filename = str_replace('\\', '/', $filename);
287
			$this->info['filepath']     = str_replace('\\', '/', realpath(dirname($filename)));
288
			$this->info['filename']     = getid3_lib::mb_basename($filename);
289
			$this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename'];
290
291
292
			// option_max_2gb_check
293
			if ($this->option_max_2gb_check) {
294
				// PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB)
295
				// filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize
296
				// ftell() returns 0 if seeking to the end is beyond the range of unsigned integer
297
				$fseek = fseek($this->fp, 0, SEEK_END);
298
				if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) ||
299
					($this->info['filesize'] < 0) ||
300
					(ftell($this->fp) < 0)) {
301
						$real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']);
302
303
						if ($real_filesize === false) {
304
							unset($this->info['filesize']);
305
							fclose($this->fp);
306
							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.');
307
						} elseif (getid3_lib::intValueSupported($real_filesize)) {
308
							unset($this->info['filesize']);
309
							fclose($this->fp);
310
							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 [email protected]');
311
						}
312
						$this->info['filesize'] = $real_filesize;
313
						$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.');
314
				}
315
			}
316
317
			// set more parameters
318
			$this->info['avdataoffset']        = 0;
319
			$this->info['avdataend']           = $this->info['filesize'];
320
			$this->info['fileformat']          = '';                // filled in later
321
			$this->info['audio']['dataformat'] = '';                // filled in later, unset if not used
322
			$this->info['video']['dataformat'] = '';                // filled in later, unset if not used
323
			$this->info['tags']                = array();           // filled in later, unset if not used
324
			$this->info['error']               = array();           // filled in later, unset if not used
325
			$this->info['warning']             = array();           // filled in later, unset if not used
326
			$this->info['comments']            = array();           // filled in later, unset if not used
327
			$this->info['encoding']            = $this->encoding;   // required by id3v2 and iso modules - can be unset at the end if desired
328
329
			return true;
330
331
		} catch (Exception $e) {
332
			$this->error($e->getMessage());
333
		}
334
		return false;
335
	}
336
337
	// public: analyze file
338
	public function analyze($filename) {
339
		try {
340
			if (!$this->openfile($filename)) {
341
				return $this->info;
342
			}
343
344
			// Handle tags
345
			foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
346
				$option_tag = 'option_tag_'.$tag_name;
347
				if ($this->$option_tag) {
348
					$this->include_module('tag.'.$tag_name);
349
					try {
350
						$tag_class = 'getid3_'.$tag_name;
351
						$tag = new $tag_class($this);
352
						$tag->Analyze();
353
					}
354
					catch (getid3_exception $e) {
355
						throw $e;
356
					}
357
				}
358
			}
359
			if (isset($this->info['id3v2']['tag_offset_start'])) {
360
				$this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']);
361
			}
362
			foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
363
				if (isset($this->info[$tag_key]['tag_offset_start'])) {
364
					$this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']);
365
				}
366
			}
367
368
			// ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
369
			if (!$this->option_tag_id3v2) {
370
				fseek($this->fp, 0);
371
				$header = fread($this->fp, 10);
372
				if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) {
373
					$this->info['id3v2']['header']        = true;
374
					$this->info['id3v2']['majorversion']  = ord($header{3});
375
					$this->info['id3v2']['minorversion']  = ord($header{4});
376
					$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
377
				}
378
			}
379
380
			// read 32 kb file data
381
			fseek($this->fp, $this->info['avdataoffset']);
382
			$formattest = fread($this->fp, 32774);
383
384
			// determine format
385
			$determined_format = $this->GetFileFormat($formattest, $filename);
386
387
			// unable to determine file format
388
			if (!$determined_format) {
389
				fclose($this->fp);
390
				return $this->error('unable to determine file format');
391
			}
392
393
			// check for illegal ID3 tags
394
			if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) {
395 View Code Duplication
				if ($determined_format['fail_id3'] === 'ERROR') {
396
					fclose($this->fp);
397
					return $this->error('ID3 tags not allowed on this file type.');
398
				} elseif ($determined_format['fail_id3'] === 'WARNING') {
399
					$this->warning('ID3 tags not allowed on this file type.');
400
				}
401
			}
402
403
			// check for illegal APE tags
404
			if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) {
405 View Code Duplication
				if ($determined_format['fail_ape'] === 'ERROR') {
406
					fclose($this->fp);
407
					return $this->error('APE tags not allowed on this file type.');
408
				} elseif ($determined_format['fail_ape'] === 'WARNING') {
409
					$this->warning('APE tags not allowed on this file type.');
410
				}
411
			}
412
413
			// set mime type
414
			$this->info['mime_type'] = $determined_format['mime_type'];
415
416
			// supported format signature pattern detected, but module deleted
417
			if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) {
418
				fclose($this->fp);
419
				return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
420
			}
421
422
			// module requires iconv support
423
			// Check encoding/iconv support
424
			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'))) {
425
				$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. ';
426
				if (GETID3_OS_ISWINDOWS) {
427
					$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';
428
				} else {
429
					$errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch';
430
				}
431
				return $this->error($errormessage);
432
			}
433
434
			// include module
435
			include_once(GETID3_INCLUDEPATH.$determined_format['include']);
436
437
			// instantiate module class
438
			$class_name = 'getid3_'.$determined_format['module'];
439
			if (!class_exists($class_name)) {
440
				return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
441
			}
442
			$class = new $class_name($this);
443
			$class->Analyze();
444
			unset($class);
445
446
			// close file
447
			fclose($this->fp);
448
449
			// process all tags - copy to 'tags' and convert charsets
450
			if ($this->option_tags_process) {
451
				$this->HandleAllTags();
452
			}
453
454
			// perform more calculations
455
			if ($this->option_extra_info) {
456
				$this->ChannelsBitratePlaytimeCalculations();
457
				$this->CalculateCompressionRatioVideo();
458
				$this->CalculateCompressionRatioAudio();
459
				$this->CalculateReplayGain();
460
				$this->ProcessAudioStreams();
461
			}
462
463
			// get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
464
			if ($this->option_md5_data) {
465
				// do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too
466
				if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) {
467
					$this->getHashdata('md5');
468
				}
469
			}
470
471
			// get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
472
			if ($this->option_sha1_data) {
473
				$this->getHashdata('sha1');
474
			}
475
476
			// remove undesired keys
477
			$this->CleanUp();
478
479
		} catch (Exception $e) {
480
			$this->error('Caught exception: '.$e->getMessage());
481
		}
482
483
		// return info array
484
		return $this->info;
485
	}
486
487
488
	// private: error handling
489
	public function error($message) {
490
		$this->CleanUp();
491
		if (!isset($this->info['error'])) {
492
			$this->info['error'] = array();
493
		}
494
		$this->info['error'][] = $message;
495
		return $this->info;
496
	}
497
498
499
	// private: warning handling
500
	public function warning($message) {
501
		$this->info['warning'][] = $message;
502
		return true;
503
	}
504
505
506
	// private: CleanUp
507
	private function CleanUp() {
508
509
		// remove possible empty keys
510
		$AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
511
		foreach ($AVpossibleEmptyKeys as $dummy => $key) {
512 View Code Duplication
			if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) {
513
				unset($this->info['audio'][$key]);
514
			}
515 View Code Duplication
			if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) {
516
				unset($this->info['video'][$key]);
517
			}
518
		}
519
520
		// remove empty root keys
521
		if (!empty($this->info)) {
522
			foreach ($this->info as $key => $value) {
523
				if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) {
524
					unset($this->info[$key]);
525
				}
526
			}
527
		}
528
529
		// remove meaningless entries from unknown-format files
530
		if (empty($this->info['fileformat'])) {
531
			if (isset($this->info['avdataoffset'])) {
532
				unset($this->info['avdataoffset']);
533
			}
534
			if (isset($this->info['avdataend'])) {
535
				unset($this->info['avdataend']);
536
			}
537
		}
538
539
		// remove possible duplicated identical entries
540 View Code Duplication
		if (!empty($this->info['error'])) {
541
			$this->info['error'] = array_values(array_unique($this->info['error']));
542
		}
543 View Code Duplication
		if (!empty($this->info['warning'])) {
544
			$this->info['warning'] = array_values(array_unique($this->info['warning']));
545
		}
546
547
		// remove "global variable" type keys
548
		unset($this->info['php_memory_limit']);
549
550
		return true;
551
	}
552
553
554
	// return array containing information about all supported formats
555
	public function GetFileFormatArray() {
556
		static $format_info = array();
557
		if (empty($format_info)) {
558
			$format_info = array(
559
560
				// Audio formats
561
562
				// AC-3   - audio      - Dolby AC-3 / Dolby Digital
563
				'ac3'  => array(
564
							'pattern'   => '^\x0B\x77',
565
							'group'     => 'audio',
566
							'module'    => 'ac3',
567
							'mime_type' => 'audio/ac3',
568
						),
569
570
				// AAC  - audio       - Advanced Audio Coding (AAC) - ADIF format
571
				'adif' => array(
572
							'pattern'   => '^ADIF',
573
							'group'     => 'audio',
574
							'module'    => 'aac',
575
							'mime_type' => 'application/octet-stream',
576
							'fail_ape'  => 'WARNING',
577
						),
578
579
/*
580
				// AA   - audio       - Audible Audiobook
581
				'aa'   => array(
582
							'pattern'   => '^.{4}\x57\x90\x75\x36',
583
							'group'     => 'audio',
584
							'module'    => 'aa',
585
							'mime_type' => 'audio/audible',
586
						),
587
*/
588
				// AAC  - audio       - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
589
				'adts' => array(
590
							'pattern'   => '^\xFF[\xF0-\xF1\xF8-\xF9]',
591
							'group'     => 'audio',
592
							'module'    => 'aac',
593
							'mime_type' => 'application/octet-stream',
594
							'fail_ape'  => 'WARNING',
595
						),
596
597
598
				// AU   - audio       - NeXT/Sun AUdio (AU)
599
				'au'   => array(
600
							'pattern'   => '^\.snd',
601
							'group'     => 'audio',
602
							'module'    => 'au',
603
							'mime_type' => 'audio/basic',
604
						),
605
606
				// AMR  - audio       - Adaptive Multi Rate
607
				'amr'  => array(
608
							'pattern'   => '^\x23\x21AMR\x0A', // #!AMR[0A]
609
							'group'     => 'audio',
610
							'module'    => 'amr',
611
							'mime_type' => 'audio/amr',
612
						),
613
614
				// AVR  - audio       - Audio Visual Research
615
				'avr'  => array(
616
							'pattern'   => '^2BIT',
617
							'group'     => 'audio',
618
							'module'    => 'avr',
619
							'mime_type' => 'application/octet-stream',
620
						),
621
622
				// BONK - audio       - Bonk v0.9+
623
				'bonk' => array(
624
							'pattern'   => '^\x00(BONK|INFO|META| ID3)',
625
							'group'     => 'audio',
626
							'module'    => 'bonk',
627
							'mime_type' => 'audio/xmms-bonk',
628
						),
629
630
				// DSS  - audio       - Digital Speech Standard
631
				'dss'  => array(
632
							'pattern'   => '^[\x02-\x03]ds[s2]',
633
							'group'     => 'audio',
634
							'module'    => 'dss',
635
							'mime_type' => 'application/octet-stream',
636
						),
637
638
				// DTS  - audio       - Dolby Theatre System
639
				'dts'  => array(
640
							'pattern'   => '^\x7F\xFE\x80\x01',
641
							'group'     => 'audio',
642
							'module'    => 'dts',
643
							'mime_type' => 'audio/dts',
644
						),
645
646
				// FLAC - audio       - Free Lossless Audio Codec
647
				'flac' => array(
648
							'pattern'   => '^fLaC',
649
							'group'     => 'audio',
650
							'module'    => 'flac',
651
							'mime_type' => 'audio/x-flac',
652
						),
653
654
				// LA   - audio       - Lossless Audio (LA)
655
				'la'   => array(
656
							'pattern'   => '^LA0[2-4]',
657
							'group'     => 'audio',
658
							'module'    => 'la',
659
							'mime_type' => 'application/octet-stream',
660
						),
661
662
				// LPAC - audio       - Lossless Predictive Audio Compression (LPAC)
663
				'lpac' => array(
664
							'pattern'   => '^LPAC',
665
							'group'     => 'audio',
666
							'module'    => 'lpac',
667
							'mime_type' => 'application/octet-stream',
668
						),
669
670
				// MIDI - audio       - MIDI (Musical Instrument Digital Interface)
671
				'midi' => array(
672
							'pattern'   => '^MThd',
673
							'group'     => 'audio',
674
							'module'    => 'midi',
675
							'mime_type' => 'audio/midi',
676
						),
677
678
				// MAC  - audio       - Monkey's Audio Compressor
679
				'mac'  => array(
680
							'pattern'   => '^MAC ',
681
							'group'     => 'audio',
682
							'module'    => 'monkey',
683
							'mime_type' => 'application/octet-stream',
684
						),
685
686
// has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available
687
//				// MOD  - audio       - MODule (assorted sub-formats)
688
//				'mod'  => array(
689
//							'pattern'   => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)',
690
//							'group'     => 'audio',
691
//							'module'    => 'mod',
692
//							'option'    => 'mod',
693
//							'mime_type' => 'audio/mod',
694
//						),
695
696
				// MOD  - audio       - MODule (Impulse Tracker)
697
				'it'   => array(
698
							'pattern'   => '^IMPM',
699
							'group'     => 'audio',
700
							'module'    => 'mod',
701
							//'option'    => 'it',
702
							'mime_type' => 'audio/it',
703
						),
704
705
				// MOD  - audio       - MODule (eXtended Module, various sub-formats)
706
				'xm'   => array(
707
							'pattern'   => '^Extended Module',
708
							'group'     => 'audio',
709
							'module'    => 'mod',
710
							//'option'    => 'xm',
711
							'mime_type' => 'audio/xm',
712
						),
713
714
				// MOD  - audio       - MODule (ScreamTracker)
715
				's3m'  => array(
716
							'pattern'   => '^.{44}SCRM',
717
							'group'     => 'audio',
718
							'module'    => 'mod',
719
							//'option'    => 's3m',
720
							'mime_type' => 'audio/s3m',
721
						),
722
723
				// MPC  - audio       - Musepack / MPEGplus
724
				'mpc'  => array(
725
							'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])',
726
							'group'     => 'audio',
727
							'module'    => 'mpc',
728
							'mime_type' => 'audio/x-musepack',
729
						),
730
731
				// MP3  - audio       - MPEG-audio Layer 3 (very similar to AAC-ADTS)
732
				'mp3'  => array(
733
							'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]',
734
							'group'     => 'audio',
735
							'module'    => 'mp3',
736
							'mime_type' => 'audio/mpeg',
737
						),
738
739
				// OFR  - audio       - OptimFROG
740
				'ofr'  => array(
741
							'pattern'   => '^(\*RIFF|OFR)',
742
							'group'     => 'audio',
743
							'module'    => 'optimfrog',
744
							'mime_type' => 'application/octet-stream',
745
						),
746
747
				// RKAU - audio       - RKive AUdio compressor
748
				'rkau' => array(
749
							'pattern'   => '^RKA',
750
							'group'     => 'audio',
751
							'module'    => 'rkau',
752
							'mime_type' => 'application/octet-stream',
753
						),
754
755
				// SHN  - audio       - Shorten
756
				'shn'  => array(
757
							'pattern'   => '^ajkg',
758
							'group'     => 'audio',
759
							'module'    => 'shorten',
760
							'mime_type' => 'audio/xmms-shn',
761
							'fail_id3'  => 'ERROR',
762
							'fail_ape'  => 'ERROR',
763
						),
764
765
				// TTA  - audio       - TTA Lossless Audio Compressor (http://tta.corecodec.org)
766
				'tta'  => array(
767
							'pattern'   => '^TTA',  // could also be '^TTA(\x01|\x02|\x03|2|1)'
768
							'group'     => 'audio',
769
							'module'    => 'tta',
770
							'mime_type' => 'application/octet-stream',
771
						),
772
773
				// VOC  - audio       - Creative Voice (VOC)
774
				'voc'  => array(
775
							'pattern'   => '^Creative Voice File',
776
							'group'     => 'audio',
777
							'module'    => 'voc',
778
							'mime_type' => 'audio/voc',
779
						),
780
781
				// VQF  - audio       - transform-domain weighted interleave Vector Quantization Format (VQF)
782
				'vqf'  => array(
783
							'pattern'   => '^TWIN',
784
							'group'     => 'audio',
785
							'module'    => 'vqf',
786
							'mime_type' => 'application/octet-stream',
787
						),
788
789
				// WV  - audio        - WavPack (v4.0+)
790
				'wv'   => array(
791
							'pattern'   => '^wvpk',
792
							'group'     => 'audio',
793
							'module'    => 'wavpack',
794
							'mime_type' => 'application/octet-stream',
795
						),
796
797
798
				// Audio-Video formats
799
800
				// ASF  - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
801
				'asf'  => array(
802
							'pattern'   => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C',
803
							'group'     => 'audio-video',
804
							'module'    => 'asf',
805
							'mime_type' => 'video/x-ms-asf',
806
							'iconv_req' => false,
807
						),
808
809
				// BINK - audio/video - Bink / Smacker
810
				'bink' => array(
811
							'pattern'   => '^(BIK|SMK)',
812
							'group'     => 'audio-video',
813
							'module'    => 'bink',
814
							'mime_type' => 'application/octet-stream',
815
						),
816
817
				// FLV  - audio/video - FLash Video
818
				'flv' => array(
819
							'pattern'   => '^FLV\x01',
820
							'group'     => 'audio-video',
821
							'module'    => 'flv',
822
							'mime_type' => 'video/x-flv',
823
						),
824
825
				// MKAV - audio/video - Mastroka
826
				'matroska' => array(
827
							'pattern'   => '^\x1A\x45\xDF\xA3',
828
							'group'     => 'audio-video',
829
							'module'    => 'matroska',
830
							'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
831
						),
832
833
				// MPEG - audio/video - MPEG (Moving Pictures Experts Group)
834
				'mpeg' => array(
835
							'pattern'   => '^\x00\x00\x01(\xBA|\xB3)',
836
							'group'     => 'audio-video',
837
							'module'    => 'mpeg',
838
							'mime_type' => 'video/mpeg',
839
						),
840
841
				// NSV  - audio/video - Nullsoft Streaming Video (NSV)
842
				'nsv'  => array(
843
							'pattern'   => '^NSV[sf]',
844
							'group'     => 'audio-video',
845
							'module'    => 'nsv',
846
							'mime_type' => 'application/octet-stream',
847
						),
848
849
				// Ogg  - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
850
				'ogg'  => array(
851
							'pattern'   => '^OggS',
852
							'group'     => 'audio',
853
							'module'    => 'ogg',
854
							'mime_type' => 'application/ogg',
855
							'fail_id3'  => 'WARNING',
856
							'fail_ape'  => 'WARNING',
857
						),
858
859
				// QT   - audio/video - Quicktime
860
				'quicktime' => array(
861
							'pattern'   => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
862
							'group'     => 'audio-video',
863
							'module'    => 'quicktime',
864
							'mime_type' => 'video/quicktime',
865
						),
866
867
				// 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)
868
				'riff' => array(
869
							'pattern'   => '^(RIFF|SDSS|FORM)',
870
							'group'     => 'audio-video',
871
							'module'    => 'riff',
872
							'mime_type' => 'audio/x-wave',
873
							'fail_ape'  => 'WARNING',
874
						),
875
876
				// Real - audio/video - RealAudio, RealVideo
877
				'real' => array(
878
							'pattern'   => '^(\\.RMF|\\.ra)',
879
							'group'     => 'audio-video',
880
							'module'    => 'real',
881
							'mime_type' => 'audio/x-realaudio',
882
						),
883
884
				// SWF - audio/video - ShockWave Flash
885
				'swf' => array(
886
							'pattern'   => '^(F|C)WS',
887
							'group'     => 'audio-video',
888
							'module'    => 'swf',
889
							'mime_type' => 'application/x-shockwave-flash',
890
						),
891
892
				// TS - audio/video - MPEG-2 Transport Stream
893
				'ts' => array(
894
							'pattern'   => '^(\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G".  Check for at least 10 packets matching this pattern
895
							'group'     => 'audio-video',
896
							'module'    => 'ts',
897
							'mime_type' => 'video/MP2T',
898
						),
899
900
901
				// Still-Image formats
902
903
				// BMP  - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
904
				'bmp'  => array(
905
							'pattern'   => '^BM',
906
							'group'     => 'graphic',
907
							'module'    => 'bmp',
908
							'mime_type' => 'image/bmp',
909
							'fail_id3'  => 'ERROR',
910
							'fail_ape'  => 'ERROR',
911
						),
912
913
				// GIF  - still image - Graphics Interchange Format
914
				'gif'  => array(
915
							'pattern'   => '^GIF',
916
							'group'     => 'graphic',
917
							'module'    => 'gif',
918
							'mime_type' => 'image/gif',
919
							'fail_id3'  => 'ERROR',
920
							'fail_ape'  => 'ERROR',
921
						),
922
923
				// JPEG - still image - Joint Photographic Experts Group (JPEG)
924
				'jpg'  => array(
925
							'pattern'   => '^\xFF\xD8\xFF',
926
							'group'     => 'graphic',
927
							'module'    => 'jpg',
928
							'mime_type' => 'image/jpeg',
929
							'fail_id3'  => 'ERROR',
930
							'fail_ape'  => 'ERROR',
931
						),
932
933
				// PCD  - still image - Kodak Photo CD
934
				'pcd'  => array(
935
							'pattern'   => '^.{2048}PCD_IPI\x00',
936
							'group'     => 'graphic',
937
							'module'    => 'pcd',
938
							'mime_type' => 'image/x-photo-cd',
939
							'fail_id3'  => 'ERROR',
940
							'fail_ape'  => 'ERROR',
941
						),
942
943
944
				// PNG  - still image - Portable Network Graphics (PNG)
945
				'png'  => array(
946
							'pattern'   => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A',
947
							'group'     => 'graphic',
948
							'module'    => 'png',
949
							'mime_type' => 'image/png',
950
							'fail_id3'  => 'ERROR',
951
							'fail_ape'  => 'ERROR',
952
						),
953
954
955
				// SVG  - still image - Scalable Vector Graphics (SVG)
956
				'svg'  => array(
957
							'pattern'   => '(<!DOCTYPE svg PUBLIC |xmlns="http:\/\/www\.w3\.org\/2000\/svg")',
958
							'group'     => 'graphic',
959
							'module'    => 'svg',
960
							'mime_type' => 'image/svg+xml',
961
							'fail_id3'  => 'ERROR',
962
							'fail_ape'  => 'ERROR',
963
						),
964
965
966
				// TIFF - still image - Tagged Information File Format (TIFF)
967
				'tiff' => array(
968
							'pattern'   => '^(II\x2A\x00|MM\x00\x2A)',
969
							'group'     => 'graphic',
970
							'module'    => 'tiff',
971
							'mime_type' => 'image/tiff',
972
							'fail_id3'  => 'ERROR',
973
							'fail_ape'  => 'ERROR',
974
						),
975
976
977
				// EFAX - still image - eFax (TIFF derivative)
978
				'efax'  => array(
979
							'pattern'   => '^\xDC\xFE',
980
							'group'     => 'graphic',
981
							'module'    => 'efax',
982
							'mime_type' => 'image/efax',
983
							'fail_id3'  => 'ERROR',
984
							'fail_ape'  => 'ERROR',
985
						),
986
987
988
				// Data formats
989
990
				// ISO  - data        - International Standards Organization (ISO) CD-ROM Image
991
				'iso'  => array(
992
							'pattern'   => '^.{32769}CD001',
993
							'group'     => 'misc',
994
							'module'    => 'iso',
995
							'mime_type' => 'application/octet-stream',
996
							'fail_id3'  => 'ERROR',
997
							'fail_ape'  => 'ERROR',
998
							'iconv_req' => false,
999
						),
1000
1001
				// RAR  - data        - RAR compressed data
1002
				'rar'  => array(
1003
							'pattern'   => '^Rar\!',
1004
							'group'     => 'archive',
1005
							'module'    => 'rar',
1006
							'mime_type' => 'application/octet-stream',
1007
							'fail_id3'  => 'ERROR',
1008
							'fail_ape'  => 'ERROR',
1009
						),
1010
1011
				// SZIP - audio/data  - SZIP compressed data
1012
				'szip' => array(
1013
							'pattern'   => '^SZ\x0A\x04',
1014
							'group'     => 'archive',
1015
							'module'    => 'szip',
1016
							'mime_type' => 'application/octet-stream',
1017
							'fail_id3'  => 'ERROR',
1018
							'fail_ape'  => 'ERROR',
1019
						),
1020
1021
				// TAR  - data        - TAR compressed data
1022
				'tar'  => array(
1023
							'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}',
1024
							'group'     => 'archive',
1025
							'module'    => 'tar',
1026
							'mime_type' => 'application/x-tar',
1027
							'fail_id3'  => 'ERROR',
1028
							'fail_ape'  => 'ERROR',
1029
						),
1030
1031
				// GZIP  - data        - GZIP compressed data
1032
				'gz'  => array(
1033
							'pattern'   => '^\x1F\x8B\x08',
1034
							'group'     => 'archive',
1035
							'module'    => 'gzip',
1036
							'mime_type' => 'application/x-gzip',
1037
							'fail_id3'  => 'ERROR',
1038
							'fail_ape'  => 'ERROR',
1039
						),
1040
1041
				// ZIP  - data         - ZIP compressed data
1042
				'zip'  => array(
1043
							'pattern'   => '^PK\x03\x04',
1044
							'group'     => 'archive',
1045
							'module'    => 'zip',
1046
							'mime_type' => 'application/zip',
1047
							'fail_id3'  => 'ERROR',
1048
							'fail_ape'  => 'ERROR',
1049
						),
1050
1051
1052
				// Misc other formats
1053
1054
				// PAR2 - data        - Parity Volume Set Specification 2.0
1055
				'par2' => array (
1056
							'pattern'   => '^PAR2\x00PKT',
1057
							'group'     => 'misc',
1058
							'module'    => 'par2',
1059
							'mime_type' => 'application/octet-stream',
1060
							'fail_id3'  => 'ERROR',
1061
							'fail_ape'  => 'ERROR',
1062
						),
1063
1064
				// PDF  - data        - Portable Document Format
1065
				'pdf'  => array(
1066
							'pattern'   => '^\x25PDF',
1067
							'group'     => 'misc',
1068
							'module'    => 'pdf',
1069
							'mime_type' => 'application/pdf',
1070
							'fail_id3'  => 'ERROR',
1071
							'fail_ape'  => 'ERROR',
1072
						),
1073
1074
				// MSOFFICE  - data   - ZIP compressed data
1075
				'msoffice' => array(
1076
							'pattern'   => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
1077
							'group'     => 'misc',
1078
							'module'    => 'msoffice',
1079
							'mime_type' => 'application/octet-stream',
1080
							'fail_id3'  => 'ERROR',
1081
							'fail_ape'  => 'ERROR',
1082
						),
1083
1084
				 // CUE  - data       - CUEsheet (index to single-file disc images)
1085
				 'cue' => array(
1086
							'pattern'   => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
1087
							'group'     => 'misc',
1088
							'module'    => 'cue',
1089
							'mime_type' => 'application/octet-stream',
1090
						   ),
1091
1092
			);
1093
		}
1094
1095
		return $format_info;
1096
	}
1097
1098
1099
1100
	public function GetFileFormat(&$filedata, $filename='') {
1101
		// this function will determine the format of a file based on usually
1102
		// the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
1103
		// and in the case of ISO CD image, 6 bytes offset 32kb from the start
1104
		// of the file).
1105
1106
		// Identify file format - loop through $format_info and detect with reg expr
1107
		foreach ($this->GetFileFormatArray() as $format_name => $info) {
1108
			// The /s switch on preg_match() forces preg_match() NOT to treat
1109
			// newline (0x0A) characters as special chars but do a binary match
1110
			if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) {
1111
				$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1112
				return $info;
1113
			}
1114
		}
1115
1116
1117
		if (preg_match('#\.mp[123a]$#i', $filename)) {
1118
			// Too many mp3 encoders on the market put gabage in front of mpeg files
1119
			// use assume format on these if format detection failed
1120
			$GetFileFormatArray = $this->GetFileFormatArray();
1121
			$info = $GetFileFormatArray['mp3'];
1122
			$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1123
			return $info;
1124
		} elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
1125
			// there's not really a useful consistent "magic" at the beginning of .cue files to identify them
1126
			// so until I think of something better, just go by filename if all other format checks fail
1127
			// and verify there's at least one instance of "TRACK xx AUDIO" in the file
1128
			$GetFileFormatArray = $this->GetFileFormatArray();
1129
			$info = $GetFileFormatArray['cue'];
1130
			$info['include']   = 'module.'.$info['group'].'.'.$info['module'].'.php';
1131
			return $info;
1132
		}
1133
1134
		return false;
1135
	}
1136
1137
1138
	// converts array to $encoding charset from $this->encoding
1139
	public function CharConvert(&$array, $encoding) {
1140
1141
		// identical encoding - end here
1142
		if ($encoding == $this->encoding) {
1143
			return;
1144
		}
1145
1146
		// loop thru array
1147
		foreach ($array as $key => $value) {
1148
1149
			// go recursive
1150
			if (is_array($value)) {
1151
				$this->CharConvert($array[$key], $encoding);
1152
			}
1153
1154
			// convert string
1155
			elseif (is_string($value)) {
1156
				$array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value));
1157
			}
1158
		}
1159
	}
1160
1161
1162
	public function HandleAllTags() {
1163
1164
		// key name => array (tag name, character encoding)
1165
		static $tags;
1166
		if (empty($tags)) {
1167
			$tags = array(
1168
				'asf'       => array('asf'           , 'UTF-16LE'),
1169
				'midi'      => array('midi'          , 'ISO-8859-1'),
1170
				'nsv'       => array('nsv'           , 'ISO-8859-1'),
1171
				'ogg'       => array('vorbiscomment' , 'UTF-8'),
1172
				'png'       => array('png'           , 'UTF-8'),
1173
				'tiff'      => array('tiff'          , 'ISO-8859-1'),
1174
				'quicktime' => array('quicktime'     , 'UTF-8'),
1175
				'real'      => array('real'          , 'ISO-8859-1'),
1176
				'vqf'       => array('vqf'           , 'ISO-8859-1'),
1177
				'zip'       => array('zip'           , 'ISO-8859-1'),
1178
				'riff'      => array('riff'          , 'ISO-8859-1'),
1179
				'lyrics3'   => array('lyrics3'       , 'ISO-8859-1'),
1180
				'id3v1'     => array('id3v1'         , $this->encoding_id3v1),
1181
				'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
1182
				'ape'       => array('ape'           , 'UTF-8'),
1183
				'cue'       => array('cue'           , 'ISO-8859-1'),
1184
				'matroska'  => array('matroska'      , 'UTF-8'),
1185
				'flac'      => array('vorbiscomment' , 'UTF-8'),
1186
				'divxtag'   => array('divx'          , 'ISO-8859-1'),
1187
				'iptc'      => array('iptc'          , 'ISO-8859-1'),
1188
			);
1189
		}
1190
1191
		// loop through comments array
1192
		foreach ($tags as $comment_name => $tagname_encoding_array) {
1193
			list($tag_name, $encoding) = $tagname_encoding_array;
1194
1195
			// fill in default encoding type if not already present
1196
			if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) {
1197
				$this->info[$comment_name]['encoding'] = $encoding;
1198
			}
1199
1200
			// copy comments if key name set
1201
			if (!empty($this->info[$comment_name]['comments'])) {
1202
				foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) {
1203
					foreach ($valuearray as $key => $value) {
1204
						if (is_string($value)) {
1205
							$value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
1206
						}
1207
						if ($value) {
1208
							if (!is_numeric($key)) {
1209
								$this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
1210
							} else {
1211
								$this->info['tags'][trim($tag_name)][trim($tag_key)][]     = $value;
1212
							}
1213
						}
1214
					}
1215
					if ($tag_key == 'picture') {
1216
						unset($this->info[$comment_name]['comments'][$tag_key]);
1217
					}
1218
				}
1219
1220
				if (!isset($this->info['tags'][$tag_name])) {
1221
					// comments are set but contain nothing but empty strings, so skip
1222
					continue;
1223
				}
1224
1225
				if ($this->option_tags_html) {
1226
					foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
1227
						$this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $encoding);
1228
					}
1229
				}
1230
1231
				$this->CharConvert($this->info['tags'][$tag_name], $encoding);           // only copy gets converted!
1232
			}
1233
1234
		}
1235
1236
		// pictures can take up a lot of space, and we don't need multiple copies of them
1237
		// let there be a single copy in [comments][picture], and not elsewhere
1238
		if (!empty($this->info['tags'])) {
1239
			$unset_keys = array('tags', 'tags_html');
1240
			foreach ($this->info['tags'] as $tagtype => $tagarray) {
1241
				foreach ($tagarray as $tagname => $tagdata) {
1242
					if ($tagname == 'picture') {
1243
						foreach ($tagdata as $key => $tagarray) {
1244
							$this->info['comments']['picture'][] = $tagarray;
1245
							if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
1246 View Code Duplication
								if (isset($this->info['tags'][$tagtype][$tagname][$key])) {
1247
									unset($this->info['tags'][$tagtype][$tagname][$key]);
1248
								}
1249 View Code Duplication
								if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) {
1250
									unset($this->info['tags_html'][$tagtype][$tagname][$key]);
1251
								}
1252
							}
1253
						}
1254
					}
1255
				}
1256
				foreach ($unset_keys as $unset_key) {
1257
					// remove possible empty keys from (e.g. [tags][id3v2][picture])
1258
					if (empty($this->info[$unset_key][$tagtype]['picture'])) {
1259
						unset($this->info[$unset_key][$tagtype]['picture']);
1260
					}
1261
					if (empty($this->info[$unset_key][$tagtype])) {
1262
						unset($this->info[$unset_key][$tagtype]);
1263
					}
1264
					if (empty($this->info[$unset_key])) {
1265
						unset($this->info[$unset_key]);
1266
					}
1267
				}
1268
				// remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
1269
				if (isset($this->info[$tagtype]['comments']['picture'])) {
1270
					unset($this->info[$tagtype]['comments']['picture']);
1271
				}
1272
				if (empty($this->info[$tagtype]['comments'])) {
1273
					unset($this->info[$tagtype]['comments']);
1274
				}
1275
				if (empty($this->info[$tagtype])) {
1276
					unset($this->info[$tagtype]);
1277
				}
1278
			}
1279
		}
1280
		return true;
1281
	}
1282
1283
	public function getHashdata($algorithm) {
1284
		switch ($algorithm) {
1285
			case 'md5':
1286
			case 'sha1':
1287
				break;
1288
1289
			default:
1290
				return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
1291
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1292
		}
1293
1294
		if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) {
1295
1296
			// We cannot get an identical md5_data value for Ogg files where the comments
1297
			// span more than 1 Ogg page (compared to the same audio data with smaller
1298
			// comments) using the normal getID3() method of MD5'ing the data between the
1299
			// end of the comments and the end of the file (minus any trailing tags),
1300
			// because the page sequence numbers of the pages that the audio data is on
1301
			// do not match. Under normal circumstances, where comments are smaller than
1302
			// the nominal 4-8kB page size, then this is not a problem, but if there are
1303
			// very large comments, the only way around it is to strip off the comment
1304
			// tags with vorbiscomment and MD5 that file.
1305
			// This procedure must be applied to ALL Ogg files, not just the ones with
1306
			// comments larger than 1 page, because the below method simply MD5's the
1307
			// whole file with the comments stripped, not just the portion after the
1308
			// comments block (which is the standard getID3() method.
1309
1310
			// The above-mentioned problem of comments spanning multiple pages and changing
1311
			// page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
1312
			// currently vorbiscomment only works on OggVorbis files.
1313
1314
			if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
1315
1316
				$this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
1317
				$this->info[$algorithm.'_data'] = false;
1318
1319
			} else {
1320
1321
				// Prevent user from aborting script
1322
				$old_abort = ignore_user_abort(true);
1323
1324
				// Create empty file
1325
				$empty = tempnam(GETID3_TEMP_DIR, 'getID3');
1326
				touch($empty);
1327
1328
				// Use vorbiscomment to make temp file without comments
1329
				$temp = tempnam(GETID3_TEMP_DIR, 'getID3');
1330
				$file = $this->info['filenamepath'];
1331
1332
				if (GETID3_OS_ISWINDOWS) {
1333
1334
					if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {
1335
1336
						$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
1337
						$VorbisCommentError = `$commandline`;
1338
1339
					} else {
1340
1341
						$VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;
1342
1343
					}
1344
1345
				} else {
1346
1347
					$commandline = 'vorbiscomment -w -c "'.$empty.'" "'.$file.'" "'.$temp.'" 2>&1';
1348
					$commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
1349
					$VorbisCommentError = `$commandline`;
1350
1351
				}
1352
1353
				if (!empty($VorbisCommentError)) {
1354
1355
					$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;
1356
					$this->info[$algorithm.'_data']  = false;
1357
1358
				} else {
1359
1360
					// Get hash of newly created file
1361
					switch ($algorithm) {
1362
						case 'md5':
1363
							$this->info[$algorithm.'_data'] = md5_file($temp);
1364
							break;
1365
1366
						case 'sha1':
1367
							$this->info[$algorithm.'_data'] = sha1_file($temp);
1368
							break;
1369
					}
1370
				}
1371
1372
				// Clean up
1373
				unlink($empty);
1374
				unlink($temp);
1375
1376
				// Reset abort setting
1377
				ignore_user_abort($old_abort);
1378
1379
			}
1380
1381
		} else {
1382
1383
			if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) {
1384
1385
				// get hash from part of file
1386
				$this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm);
1387
1388
			} else {
1389
1390
				// get hash from whole file
1391
				switch ($algorithm) {
1392
					case 'md5':
1393
						$this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']);
1394
						break;
1395
1396
					case 'sha1':
1397
						$this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']);
1398
						break;
1399
				}
1400
			}
1401
1402
		}
1403
		return true;
1404
	}
1405
1406
1407
	public function ChannelsBitratePlaytimeCalculations() {
1408
1409
		// set channelmode on audio
1410
		if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) {
1411
			// ignore
1412
		} elseif ($this->info['audio']['channels'] == 1) {
1413
			$this->info['audio']['channelmode'] = 'mono';
1414
		} elseif ($this->info['audio']['channels'] == 2) {
1415
			$this->info['audio']['channelmode'] = 'stereo';
1416
		}
1417
1418
		// Calculate combined bitrate - audio + video
1419
		$CombinedBitrate  = 0;
1420
		$CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0);
1421
		$CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0);
1422
		if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) {
1423
			$this->info['bitrate'] = $CombinedBitrate;
1424
		}
1425
		//if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
1426
		//	// for example, VBR MPEG video files cannot determine video bitrate:
1427
		//	// should not set overall bitrate and playtime from audio bitrate only
1428
		//	unset($this->info['bitrate']);
1429
		//}
1430
1431
		// video bitrate undetermined, but calculable
1432
		if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) {
1433
			// if video bitrate not set
1434
			if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) {
1435
				// AND if audio bitrate is set to same as overall bitrate
1436
				if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) {
1437
					// AND if playtime is set
1438
					if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) {
1439
						// AND if AV data offset start/end is known
1440
						// THEN we can calculate the video bitrate
1441
						$this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']);
1442
						$this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate'];
1443
					}
1444
				}
1445
			}
1446
		}
1447
1448 View Code Duplication
		if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) {
1449
			$this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
1450
		}
1451
1452 View Code Duplication
		if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) {
1453
			$this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds'];
1454
		}
1455
		if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) {
1456
			if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) {
1457
				// audio only
1458
				$this->info['audio']['bitrate'] = $this->info['bitrate'];
1459
			} elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) {
1460
				// video only
1461
				$this->info['video']['bitrate'] = $this->info['bitrate'];
1462
			}
1463
		}
1464
1465
		// Set playtime string
1466
		if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
1467
			$this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']);
1468
		}
1469
	}
1470
1471
1472
	public function CalculateCompressionRatioVideo() {
1473
		if (empty($this->info['video'])) {
1474
			return false;
1475
		}
1476
		if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) {
1477
			return false;
1478
		}
1479
		if (empty($this->info['video']['bits_per_sample'])) {
1480
			return false;
1481
		}
1482
1483
		switch ($this->info['video']['dataformat']) {
1484
			case 'bmp':
1485
			case 'gif':
1486
			case 'jpeg':
1487
			case 'jpg':
1488
			case 'png':
1489
			case 'tiff':
1490
				$FrameRate = 1;
1491
				$PlaytimeSeconds = 1;
1492
				$BitrateCompressed = $this->info['filesize'] * 8;
1493
				break;
1494
1495
			default:
1496
				if (!empty($this->info['video']['frame_rate'])) {
1497
					$FrameRate = $this->info['video']['frame_rate'];
1498
				} else {
1499
					return false;
1500
				}
1501
				if (!empty($this->info['playtime_seconds'])) {
1502
					$PlaytimeSeconds = $this->info['playtime_seconds'];
1503
				} else {
1504
					return false;
1505
				}
1506
				if (!empty($this->info['video']['bitrate'])) {
1507
					$BitrateCompressed = $this->info['video']['bitrate'];
1508
				} else {
1509
					return false;
1510
				}
1511
				break;
1512
		}
1513
		$BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;
1514
1515
		$this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
1516
		return true;
1517
	}
1518
1519
1520
	public function CalculateCompressionRatioAudio() {
1521
		if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) {
1522
			return false;
1523
		}
1524
		$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));
1525
1526
		if (!empty($this->info['audio']['streams'])) {
1527
			foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) {
1528
				if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) {
1529
					$this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16));
1530
				}
1531
			}
1532
		}
1533
		return true;
1534
	}
1535
1536
1537
	public function CalculateReplayGain() {
1538
		if (isset($this->info['replay_gain'])) {
1539
			if (!isset($this->info['replay_gain']['reference_volume'])) {
1540
				$this->info['replay_gain']['reference_volume'] = (double) 89.0;
1541
			}
1542
			if (isset($this->info['replay_gain']['track']['adjustment'])) {
1543
				$this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment'];
1544
			}
1545 View Code Duplication
			if (isset($this->info['replay_gain']['album']['adjustment'])) {
1546
				$this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment'];
1547
			}
1548
1549 View Code Duplication
			if (isset($this->info['replay_gain']['track']['peak'])) {
1550
				$this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']);
1551
			}
1552 View Code Duplication
			if (isset($this->info['replay_gain']['album']['peak'])) {
1553
				$this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']);
1554
			}
1555
		}
1556
		return true;
1557
	}
1558
1559
	public function ProcessAudioStreams() {
1560
		if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) {
1561
			if (!isset($this->info['audio']['streams'])) {
1562
				foreach ($this->info['audio'] as $key => $value) {
1563
					if ($key != 'streams') {
1564
						$this->info['audio']['streams'][0][$key] = $value;
1565
					}
1566
				}
1567
			}
1568
		}
1569
		return true;
1570
	}
1571
1572
	public function getid3_tempnam() {
1573
		return tempnam($this->tempdir, 'gI3');
1574
	}
1575
1576
	public function include_module($name) {
1577
		//if (!file_exists($this->include_path.'module.'.$name.'.php')) {
1578
		if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) {
1579
			throw new getid3_exception('Required module.'.$name.'.php is missing.');
1580
		}
1581
		include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php');
1582
		return true;
1583
	}
1584
1585
}
1586
1587
1588
abstract class getid3_handler {
1589
1590
	/**
1591
	* @var getID3
1592
	*/
1593
	protected $getid3;                       // pointer
1594
1595
	protected $data_string_flag     = false; // analyzing filepointer or string
1596
	protected $data_string          = '';    // string to analyze
1597
	protected $data_string_position = 0;     // seek position in string
1598
	protected $data_string_length   = 0;     // string length
1599
1600
	private $dependency_to = null;
1601
1602
1603
	public function __construct(getID3 $getid3, $call_module=null) {
1604
		$this->getid3 = $getid3;
1605
1606
		if ($call_module) {
1607
			$this->dependency_to = str_replace('getid3_', '', $call_module);
1608
		}
1609
	}
1610
1611
1612
	// Analyze from file pointer
1613
	abstract public function Analyze();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
1614
1615
1616
	// Analyze from string instead
1617
	public function AnalyzeString($string) {
1618
		// Enter string mode
1619
		$this->setStringMode($string);
1620
1621
		// Save info
1622
		$saved_avdataoffset = $this->getid3->info['avdataoffset'];
1623
		$saved_avdataend    = $this->getid3->info['avdataend'];
1624
		$saved_filesize     = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call
1625
1626
		// Reset some info
1627
		$this->getid3->info['avdataoffset'] = 0;
1628
		$this->getid3->info['avdataend']    = $this->getid3->info['filesize'] = $this->data_string_length;
1629
1630
		// Analyze
1631
		$this->Analyze();
1632
1633
		// Restore some info
1634
		$this->getid3->info['avdataoffset'] = $saved_avdataoffset;
1635
		$this->getid3->info['avdataend']    = $saved_avdataend;
1636
		$this->getid3->info['filesize']     = $saved_filesize;
1637
1638
		// Exit string mode
1639
		$this->data_string_flag = false;
1640
	}
1641
1642
	public function setStringMode($string) {
1643
		$this->data_string_flag   = true;
1644
		$this->data_string        = $string;
1645
		$this->data_string_length = strlen($string);
1646
	}
1647
1648
	protected function ftell() {
1649
		if ($this->data_string_flag) {
1650
			return $this->data_string_position;
1651
		}
1652
		return ftell($this->getid3->fp);
1653
	}
1654
1655
	protected function fread($bytes) {
1656
		if ($this->data_string_flag) {
1657
			$this->data_string_position += $bytes;
1658
			return substr($this->data_string, $this->data_string_position - $bytes, $bytes);
1659
		}
1660
		$pos = $this->ftell() + $bytes;
1661
		if (!getid3_lib::intValueSupported($pos)) {
1662
			throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10);
1663
		}
1664
		return fread($this->getid3->fp, $bytes);
1665
	}
1666
1667
	protected function fseek($bytes, $whence=SEEK_SET) {
1668
		if ($this->data_string_flag) {
1669
			switch ($whence) {
1670
				case SEEK_SET:
1671
					$this->data_string_position = $bytes;
1672
					break;
1673
1674
				case SEEK_CUR:
1675
					$this->data_string_position += $bytes;
1676
					break;
1677
1678
				case SEEK_END:
1679
					$this->data_string_position = $this->data_string_length + $bytes;
1680
					break;
1681
			}
1682
			return 0;
1683
		} else {
1684
			$pos = $bytes;
1685
			if ($whence == SEEK_CUR) {
1686
				$pos = $this->ftell() + $bytes;
1687
			} elseif ($whence == SEEK_END) {
1688
				$pos = $this->getid3->info['filesize'] + $bytes;
1689
			}
1690
			if (!getid3_lib::intValueSupported($pos)) {
1691
				throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
1692
			}
1693
		}
1694
		return fseek($this->getid3->fp, $bytes, $whence);
1695
	}
1696
1697
	protected function feof() {
1698
		if ($this->data_string_flag) {
1699
			return $this->data_string_position >= $this->data_string_length;
1700
		}
1701
		return feof($this->getid3->fp);
1702
	}
1703
1704
	final protected function isDependencyFor($module) {
1705
		return $this->dependency_to == $module;
1706
	}
1707
1708
	protected function error($text) {
1709
		$this->getid3->info['error'][] = $text;
1710
1711
		return false;
1712
	}
1713
1714
	protected function warning($text) {
1715
		return $this->getid3->warning($text);
1716
	}
1717
1718
	protected function notice($text) {
0 ignored issues
show
Unused Code introduced by
The parameter $text is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1719
		// does nothing for now
1720
	}
1721
1722
	public function saveAttachment($name, $offset, $length, $image_mime=null) {
1723
		try {
1724
1725
			// do not extract at all
1726
			if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) {
1727
1728
				$attachment = null; // do not set any
1729
1730
			// extract to return array
1731
			} elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) {
1732
1733
				$this->fseek($offset);
1734
				$attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory
1735
				if ($attachment === false || strlen($attachment) != $length) {
1736
					throw new Exception('failed to read attachment data');
1737
				}
1738
1739
			// assume directory path is given
1740
			} else {
1741
1742
				// set up destination path
1743
				$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1744
				if (!is_dir($dir) || !is_writable($dir)) { // check supplied directory
1745
					throw new Exception('supplied path ('.$dir.') does not exist, or is not writable');
1746
				}
1747
				$dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : '');
1748
1749
				// create dest file
1750
				if (($fp_dest = fopen($dest, 'wb')) == false) {
1751
					throw new Exception('failed to create file '.$dest);
1752
				}
1753
1754
				// copy data
1755
				$this->fseek($offset);
1756
				$buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size());
1757
				$bytesleft = $length;
1758
				while ($bytesleft > 0) {
1759
					if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) {
1760
						throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space');
1761
					}
1762
					$bytesleft -= $byteswritten;
1763
				}
1764
1765
				fclose($fp_dest);
1766
				$attachment = $dest;
1767
1768
			}
1769
1770
		} catch (Exception $e) {
1771
1772
			// close and remove dest file if created
1773
			if (isset($fp_dest) && is_resource($fp_dest)) {
1774
				fclose($fp_dest);
1775
				unlink($dest);
0 ignored issues
show
Bug introduced by
The variable $dest does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1776
			}
1777
1778
			// do not set any is case of error
1779
			$attachment = null;
1780
			$this->warning('Failed to extract attachment '.$name.': '.$e->getMessage());
1781
1782
		}
1783
1784
		// seek to the end of attachment
1785
		$this->fseek($offset + $length);
1786
1787
		return $attachment;
1788
	}
1789
1790
}
1791
1792
1793
class getid3_exception extends Exception
1794
{
1795
	public $message;
1796
}
1797