Completed
Push — vendor/getid3 ( f84e24...1ec141 )
by Pauli
03:18
created

getID3::include_module()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 8
rs 9.4285
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
if (!defined('ENT_SUBSTITUTE')) { // PHP5.3 adds ENT_IGNORE, PHP5.4 adds ENT_SUBSTITUTE
26
	define('ENT_SUBSTITUTE', (defined('ENT_IGNORE') ? ENT_IGNORE : 8));
27
}
28
29
/*
30
http://www.getid3.org/phpBB3/viewtopic.php?t=2114
31
If you are running into a the problem where filenames with special characters are being handled
32
incorrectly by external helper programs (e.g. metaflac), notably with the special characters removed,
33
and you are passing in the filename in UTF8 (typically via a HTML form), try uncommenting this line:
34
*/
35
//setlocale(LC_CTYPE, 'en_US.UTF-8');
36
37
// attempt to define temp dir as something flexible but reliable
38
$temp_dir = ini_get('upload_tmp_dir');
39
if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) {
40
	$temp_dir = '';
41
}
42
if (!$temp_dir && function_exists('sys_get_temp_dir')) { // sys_get_temp_dir added in PHP v5.2.1
43
	// sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts
44
	$temp_dir = sys_get_temp_dir();
45
}
46
$temp_dir = @realpath($temp_dir); // see https://github.com/JamesHeinrich/getID3/pull/10
47
$open_basedir = ini_get('open_basedir');
48
if ($open_basedir) {
49
	// e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/"
50
	$temp_dir     = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir);
51
	$open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir);
52
	if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) {
53
		$temp_dir .= DIRECTORY_SEPARATOR;
54
	}
55
	$found_valid_tempdir = false;
56
	$open_basedirs = explode(PATH_SEPARATOR, $open_basedir);
57
	foreach ($open_basedirs as $basedir) {
58
		if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) {
59
			$basedir .= DIRECTORY_SEPARATOR;
60
		}
61
		if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) {
62
			$found_valid_tempdir = true;
63
			break;
64
		}
65
	}
66
	if (!$found_valid_tempdir) {
67
		$temp_dir = '';
68
	}
69
	unset($open_basedirs, $found_valid_tempdir, $basedir);
70
}
71
if (!$temp_dir) {
72
	$temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
73
}
74
// $temp_dir = '/something/else/';  // feel free to override temp dir here if it works better for your system
75
if (!defined('GETID3_TEMP_DIR')) {
76
	define('GETID3_TEMP_DIR', $temp_dir);
77
}
78
unset($open_basedir, $temp_dir);
79
80
// End: Defines
81
82
83
class getID3
84
{
85
	// public: Settings
86
	public $encoding        = 'UTF-8';        // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples:  ISO-8859-1  UTF-8  UTF-16  UTF-16BE
87
	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'
88
89
	// public: Optional tag checks - disable for speed.
90
	public $option_tag_id3v1         = true;  // Read and process ID3v1 tags
91
	public $option_tag_id3v2         = true;  // Read and process ID3v2 tags
92
	public $option_tag_lyrics3       = true;  // Read and process Lyrics3 tags
93
	public $option_tag_apetag        = true;  // Read and process APE tags
94
	public $option_tags_process      = true;  // Copy tags to root key 'tags' and encode to $this->encoding
95
	public $option_tags_html         = true;  // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
96
97
	// public: Optional tag/comment calucations
98
	public $option_extra_info        = true;  // Calculate additional info such as bitrate, channelmode etc
99
100
	// public: Optional handling of embedded attachments (e.g. images)
101
	public $option_save_attachments  = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility
102
103
	// public: Optional calculations
104
	public $option_md5_data          = false; // Get MD5 sum of data part - slow
105
	public $option_md5_data_source   = false; // Use MD5 of source file if availble - only FLAC and OptimFROG
106
	public $option_sha1_data         = false; // Get SHA1 sum of data part - slow
107
	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)
108
109
	// public: Read buffer size in bytes
110
	public $option_fread_buffer_size = 32768;
111
112
	// Public variables
113
	public $filename;                         // Filename of file being analysed.
114
	public $fp;                               // Filepointer to file being analysed.
115
	public $info;                             // Result array.
116
	public $tempdir = GETID3_TEMP_DIR;
117
	public $memory_limit = 0;
118
119
	// Protected variables
120
	protected $startup_error   = '';
121
	protected $startup_warning = '';
122
123
	const VERSION           = '1.9.15-201709291043';
124
	const FREAD_BUFFER_SIZE = 32768;
125
126
	const ATTACHMENTS_NONE   = false;
127
	const ATTACHMENTS_INLINE = true;
128
129
	// public: constructor
130
	public function __construct() {
131
132
		// Check for PHP version
133
		$required_php_version = '5.3.0';
134
		if (version_compare(PHP_VERSION, $required_php_version, '<')) {
135
			$this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION."\n";
136
			return false;
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...
137
		}
138
139
		// Check memory
140
		$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...
141
		if (preg_match('#([0-9]+) ?M#i', $this->memory_limit, $matches)) {
142
			// could be stored as "16M" rather than 16777216 for example
143
			$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...
144
		} elseif (preg_match('#([0-9]+) ?G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
145
			// could be stored as "2G" rather than 2147483648 for example
146
			$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...
147
		}
148
		if ($this->memory_limit <= 0) {
149
			// memory limits probably disabled
150
		} elseif ($this->memory_limit <= 4194304) {
151
			$this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'."\n";
152
		} elseif ($this->memory_limit <= 12582912) {
153
			$this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'."\n";
154
		}
155
156
		// Check safe_mode off
157
		if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
158
			$this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
159
		}
160
161
		if (($mbstring_func_overload = ini_get('mbstring.func_overload')) && ($mbstring_func_overload & 0x02)) {
162
			// http://php.net/manual/en/mbstring.overload.php
163
			// "mbstring.func_overload in php.ini is a positive value that represents a combination of bitmasks specifying the categories of functions to be overloaded. It should be set to 1 to overload the mail() function. 2 for string functions, 4 for regular expression functions"
164
			// getID3 cannot run when string functions are overloaded. It doesn't matter if mail() or ereg* functions are overloaded since getID3 does not use those.
165
			$this->startup_error .= 'WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", getID3 cannot run with this setting (bitmask 2 (string functions) cannot be set). Recommended to disable entirely.'."\n";
166
		}
167
168
		// Check for magic_quotes_runtime
169
		if (function_exists('get_magic_quotes_runtime')) {
170
			if (get_magic_quotes_runtime()) {
171
				$this->startup_error .= 'magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'."\n";
172
			}
173
		}
174
175
		// Check for magic_quotes_gpc
176
		if (function_exists('magic_quotes_gpc')) {
177
			if (get_magic_quotes_gpc()) {
178
				$this->startup_error .= 'magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'."\n";
179
			}
180
		}
181
182
		// Load support library
183
		if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
184
			$this->startup_error .= 'getid3.lib.php is missing or corrupt'."\n";
185
		}
186
187
		if ($this->option_max_2gb_check === null) {
188
			$this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647);
189
		}
190
191
192
		// Needed for Windows only:
193
		// Define locations of helper applications for Shorten, VorbisComment, MetaFLAC
194
		//   as well as other helper functions such as head, tail, md5sum, etc
195
		// This path cannot contain spaces, but the below code will attempt to get the
196
		//   8.3-equivalent path automatically
197
		// IMPORTANT: This path must include the trailing slash
198
		if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) {
199
200
			$helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path
201
202
			if (!is_dir($helperappsdir)) {
203
				$this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'."\n";
204
			} elseif (strpos(realpath($helperappsdir), ' ') !== false) {
205
				$DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir));
206
				$path_so_far = array();
207
				foreach ($DirPieces as $key => $value) {
208
					if (strpos($value, ' ') !== false) {
209
						if (!empty($path_so_far)) {
210
							$commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far));
211
							$dir_listing = `$commandline`;
212
							$lines = explode("\n", $dir_listing);
213
							foreach ($lines as $line) {
214
								$line = trim($line);
215
								if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) {
216
									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...
217
									if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) {
218
										$value = $shortname;
219
									}
220
								}
221
							}
222
						} else {
223
							$this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'."\n";
224
						}
225
					}
226
					$path_so_far[] = $value;
227
				}
228
				$helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far);
229
			}
230
			define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR);
231
		}
232
233
		if (!empty($this->startup_error)) {
234
			echo $this->startup_error;
235
			throw new getid3_exception($this->startup_error);
236
		}
237
238
		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...
239
	}
240
241
	public function version() {
242
		return self::VERSION;
243
	}
244
245
	public function fread_buffer_size() {
246
		return $this->option_fread_buffer_size;
247
	}
248
249
250
	// public: setOption
251
	public function setOption($optArray) {
252
		if (!is_array($optArray) || empty($optArray)) {
253
			return false;
254
		}
255
		foreach ($optArray as $opt => $val) {
256
			if (isset($this->$opt) === false) {
257
				continue;
258
			}
259
			$this->$opt = $val;
260
		}
261
		return true;
262
	}
263
264
265
	public function openfile($filename, $filesize=null) {
266
		try {
267
			if (!empty($this->startup_error)) {
268
				throw new getid3_exception($this->startup_error);
269
			}
270
			if (!empty($this->startup_warning)) {
271
				foreach (explode("\n", $this->startup_warning) as $startup_warning) {
272
					$this->warning($startup_warning);
273
				}
274
			}
275
276
			// init result array and set parameters
277
			$this->filename = $filename;
278
			$this->info = array();
279
			$this->info['GETID3_VERSION']   = $this->version();
280
			$this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false);
281
282
			// remote files not supported
283
			if (preg_match('#^(ht|f)tp://#', $filename)) {
284
				throw new getid3_exception('Remote files are not supported - please copy the file locally first');
285
			}
286
287
			$filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
288
			$filename = preg_replace('#(?<!gs:)('.preg_quote(DIRECTORY_SEPARATOR).'{2,})#', DIRECTORY_SEPARATOR, $filename);
289
290
			// open local file
291
			//if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see http://www.getid3.org/phpBB3/viewtopic.php?t=1720
292
			if ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) {
293
				// great
294
			} else {
295
				$errormessagelist = array();
296
				if (!is_readable($filename)) {
297
					$errormessagelist[] = '!is_readable';
298
				}
299
				if (!is_file($filename)) {
300
					$errormessagelist[] = '!is_file';
301
				}
302
				if (!file_exists($filename)) {
303
					$errormessagelist[] = '!file_exists';
304
				}
305
				if (empty($errormessagelist)) {
306
					$errormessagelist[] = 'fopen failed';
307
				}
308
				throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')');
309
			}
310
311
			$this->info['filesize'] = (!is_null($filesize) ? $filesize : filesize($filename));
312
			// set redundant parameters - might be needed in some include file
313
			// filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion
314
			$filename = str_replace('\\', '/', $filename);
315
			$this->info['filepath']     = str_replace('\\', '/', realpath(dirname($filename)));
316
			$this->info['filename']     = getid3_lib::mb_basename($filename);
317
			$this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename'];
318
319
			// set more parameters
320
			$this->info['avdataoffset']        = 0;
321
			$this->info['avdataend']           = $this->info['filesize'];
322
			$this->info['fileformat']          = '';                // filled in later
323
			$this->info['audio']['dataformat'] = '';                // filled in later, unset if not used
324
			$this->info['video']['dataformat'] = '';                // filled in later, unset if not used
325
			$this->info['tags']                = array();           // filled in later, unset if not used
326
			$this->info['error']               = array();           // filled in later, unset if not used
327
			$this->info['warning']             = array();           // filled in later, unset if not used
328
			$this->info['comments']            = array();           // filled in later, unset if not used
329
			$this->info['encoding']            = $this->encoding;   // required by id3v2 and iso modules - can be unset at the end if desired
330
331
			// option_max_2gb_check
332
			if ($this->option_max_2gb_check) {
333
				// PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB)
334
				// filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize
335
				// ftell() returns 0 if seeking to the end is beyond the range of unsigned integer
336
				$fseek = fseek($this->fp, 0, SEEK_END);
337
				if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) ||
338
					($this->info['filesize'] < 0) ||
339
					(ftell($this->fp) < 0)) {
340
						$real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']);
341
342
						if ($real_filesize === false) {
343
							unset($this->info['filesize']);
344
							fclose($this->fp);
345
							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.');
346
						} elseif (getid3_lib::intValueSupported($real_filesize)) {
347
							unset($this->info['filesize']);
348
							fclose($this->fp);
349
							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]');
350
						}
351
						$this->info['filesize'] = $real_filesize;
352
						$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.');
353
				}
354
			}
355
356
			return true;
357
358
		} catch (Exception $e) {
359
			$this->error($e->getMessage());
360
		}
361
		return false;
362
	}
363
364
	// public: analyze file
365
	public function analyze($filename, $filesize=null, $original_filename='') {
366
		try {
367
			if (!$this->openfile($filename, $filesize)) {
368
				return $this->info;
369
			}
370
371
			// Handle tags
372
			foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
373
				$option_tag = 'option_tag_'.$tag_name;
374
				if ($this->$option_tag) {
375
					$this->include_module('tag.'.$tag_name);
376
					try {
377
						$tag_class = 'getid3_'.$tag_name;
378
						$tag = new $tag_class($this);
379
						$tag->Analyze();
380
					}
381
					catch (getid3_exception $e) {
382
						throw $e;
383
					}
384
				}
385
			}
386
			if (isset($this->info['id3v2']['tag_offset_start'])) {
387
				$this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']);
388
			}
389
			foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
390
				if (isset($this->info[$tag_key]['tag_offset_start'])) {
391
					$this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']);
392
				}
393
			}
394
395
			// ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
396
			if (!$this->option_tag_id3v2) {
397
				fseek($this->fp, 0);
398
				$header = fread($this->fp, 10);
399
				if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) {
400
					$this->info['id3v2']['header']        = true;
401
					$this->info['id3v2']['majorversion']  = ord($header{3});
402
					$this->info['id3v2']['minorversion']  = ord($header{4});
403
					$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
0 ignored issues
show
Documentation introduced by
1 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
404
				}
405
			}
406
407
			// read 32 kb file data
408
			fseek($this->fp, $this->info['avdataoffset']);
409
			$formattest = fread($this->fp, 32774);
410
411
			// determine format
412
			$determined_format = $this->GetFileFormat($formattest, ($original_filename ? $original_filename : $filename));
413
414
			// unable to determine file format
415
			if (!$determined_format) {
416
				fclose($this->fp);
417
				return $this->error('unable to determine file format');
418
			}
419
420
			// check for illegal ID3 tags
421
			if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) {
422 View Code Duplication
				if ($determined_format['fail_id3'] === 'ERROR') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
423
					fclose($this->fp);
424
					return $this->error('ID3 tags not allowed on this file type.');
425
				} elseif ($determined_format['fail_id3'] === 'WARNING') {
426
					$this->warning('ID3 tags not allowed on this file type.');
427
				}
428
			}
429
430
			// check for illegal APE tags
431
			if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) {
432 View Code Duplication
				if ($determined_format['fail_ape'] === 'ERROR') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
433
					fclose($this->fp);
434
					return $this->error('APE tags not allowed on this file type.');
435
				} elseif ($determined_format['fail_ape'] === 'WARNING') {
436
					$this->warning('APE tags not allowed on this file type.');
437
				}
438
			}
439
440
			// set mime type
441
			$this->info['mime_type'] = $determined_format['mime_type'];
442
443
			// supported format signature pattern detected, but module deleted
444
			if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) {
445
				fclose($this->fp);
446
				return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
447
			}
448
449
			// module requires mb_convert_encoding/iconv support
450
			// Check encoding/iconv support
451
			if (!empty($determined_format['iconv_req']) && !function_exists('mb_convert_encoding') && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) {
452
				$errormessage = 'mb_convert_encoding() or iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. ';
453
				if (GETID3_OS_ISWINDOWS) {
454
					$errormessage .= 'PHP does not have mb_convert_encoding() or iconv() support. Please enable php_mbstring.dll / php_iconv.dll in php.ini, and copy php_mbstring.dll / iconv.dll from c:/php/dlls to c:/windows/system32';
455
				} else {
456
					$errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch';
457
				}
458
				return $this->error($errormessage);
459
			}
460
461
			// include module
462
			include_once(GETID3_INCLUDEPATH.$determined_format['include']);
463
464
			// instantiate module class
465
			$class_name = 'getid3_'.$determined_format['module'];
466
			if (!class_exists($class_name)) {
467
				return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
468
			}
469
			$class = new $class_name($this);
470
			$class->Analyze();
471
			unset($class);
472
473
			// close file
474
			fclose($this->fp);
475
476
			// process all tags - copy to 'tags' and convert charsets
477
			if ($this->option_tags_process) {
478
				$this->HandleAllTags();
479
			}
480
481
			// perform more calculations
482
			if ($this->option_extra_info) {
483
				$this->ChannelsBitratePlaytimeCalculations();
484
				$this->CalculateCompressionRatioVideo();
485
				$this->CalculateCompressionRatioAudio();
486
				$this->CalculateReplayGain();
487
				$this->ProcessAudioStreams();
488
			}
489
490
			// get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
491
			if ($this->option_md5_data) {
492
				// do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too
493
				if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) {
494
					$this->getHashdata('md5');
495
				}
496
			}
497
498
			// get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
499
			if ($this->option_sha1_data) {
500
				$this->getHashdata('sha1');
501
			}
502
503
			// remove undesired keys
504
			$this->CleanUp();
505
506
		} catch (Exception $e) {
507
			$this->error('Caught exception: '.$e->getMessage());
508
		}
509
510
		// return info array
511
		return $this->info;
512
	}
513
514
515
	// private: error handling
516
	public function error($message) {
517
		$this->CleanUp();
518
		if (!isset($this->info['error'])) {
519
			$this->info['error'] = array();
520
		}
521
		$this->info['error'][] = $message;
522
		return $this->info;
523
	}
524
525
526
	// private: warning handling
527
	public function warning($message) {
528
		$this->info['warning'][] = $message;
529
		return true;
530
	}
531
532
533
	// private: CleanUp
534
	private function CleanUp() {
535
536
		// remove possible empty keys
537
		$AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
538
		foreach ($AVpossibleEmptyKeys as $dummy => $key) {
539 View Code Duplication
			if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
540
				unset($this->info['audio'][$key]);
541
			}
542 View Code Duplication
			if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
543
				unset($this->info['video'][$key]);
544
			}
545
		}
546
547
		// remove empty root keys
548
		if (!empty($this->info)) {
549
			foreach ($this->info as $key => $value) {
550
				if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) {
551
					unset($this->info[$key]);
552
				}
553
			}
554
		}
555
556
		// remove meaningless entries from unknown-format files
557
		if (empty($this->info['fileformat'])) {
558
			if (isset($this->info['avdataoffset'])) {
559
				unset($this->info['avdataoffset']);
560
			}
561
			if (isset($this->info['avdataend'])) {
562
				unset($this->info['avdataend']);
563
			}
564
		}
565
566
		// remove possible duplicated identical entries
567 View Code Duplication
		if (!empty($this->info['error'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
568
			$this->info['error'] = array_values(array_unique($this->info['error']));
569
		}
570 View Code Duplication
		if (!empty($this->info['warning'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
571
			$this->info['warning'] = array_values(array_unique($this->info['warning']));
572
		}
573
574
		// remove "global variable" type keys
575
		unset($this->info['php_memory_limit']);
576
577
		return true;
578
	}
579
580
581
	// return array containing information about all supported formats
582
	public function GetFileFormatArray() {
583
		static $format_info = array();
584
		if (empty($format_info)) {
585
			$format_info = array(
586
587
				// Audio formats
588
589
				// AC-3   - audio      - Dolby AC-3 / Dolby Digital
590
				'ac3'  => array(
591
							'pattern'   => '^\\x0B\\x77',
592
							'group'     => 'audio',
593
							'module'    => 'ac3',
594
							'mime_type' => 'audio/ac3',
595
						),
596
597
				// AAC  - audio       - Advanced Audio Coding (AAC) - ADIF format
598
				'adif' => array(
599
							'pattern'   => '^ADIF',
600
							'group'     => 'audio',
601
							'module'    => 'aac',
602
							'mime_type' => 'audio/aac',
603
							'fail_ape'  => 'WARNING',
604
						),
605
606
/*
607
				// AA   - audio       - Audible Audiobook
608
				'aa'   => array(
609
							'pattern'   => '^.{4}\\x57\\x90\\x75\\x36',
610
							'group'     => 'audio',
611
							'module'    => 'aa',
612
							'mime_type' => 'audio/audible',
613
						),
614
*/
615
				// AAC  - audio       - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
616
				'adts' => array(
617
							'pattern'   => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]',
618
							'group'     => 'audio',
619
							'module'    => 'aac',
620
							'mime_type' => 'audio/aac',
621
							'fail_ape'  => 'WARNING',
622
						),
623
624
625
				// AU   - audio       - NeXT/Sun AUdio (AU)
626
				'au'   => array(
627
							'pattern'   => '^\\.snd',
628
							'group'     => 'audio',
629
							'module'    => 'au',
630
							'mime_type' => 'audio/basic',
631
						),
632
633
				// AMR  - audio       - Adaptive Multi Rate
634
				'amr'  => array(
635
							'pattern'   => '^\\x23\\x21AMR\\x0A', // #!AMR[0A]
636
							'group'     => 'audio',
637
							'module'    => 'amr',
638
							'mime_type' => 'audio/amr',
639
						),
640
641
				// AVR  - audio       - Audio Visual Research
642
				'avr'  => array(
643
							'pattern'   => '^2BIT',
644
							'group'     => 'audio',
645
							'module'    => 'avr',
646
							'mime_type' => 'application/octet-stream',
647
						),
648
649
				// BONK - audio       - Bonk v0.9+
650
				'bonk' => array(
651
							'pattern'   => '^\\x00(BONK|INFO|META| ID3)',
652
							'group'     => 'audio',
653
							'module'    => 'bonk',
654
							'mime_type' => 'audio/xmms-bonk',
655
						),
656
657
				// DSF  - audio       - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital
658
				'dsf'  => array(
659
							'pattern'   => '^DSD ',  // including trailing space: 44 53 44 20
660
							'group'     => 'audio',
661
							'module'    => 'dsf',
662
							'mime_type' => 'audio/dsd',
663
						),
664
665
				// DSS  - audio       - Digital Speech Standard
666
				'dss'  => array(
667
							'pattern'   => '^[\\x02-\\x06]ds[s2]',
668
							'group'     => 'audio',
669
							'module'    => 'dss',
670
							'mime_type' => 'application/octet-stream',
671
						),
672
673
				// DTS  - audio       - Dolby Theatre System
674
				'dts'  => array(
675
							'pattern'   => '^\\x7F\\xFE\\x80\\x01',
676
							'group'     => 'audio',
677
							'module'    => 'dts',
678
							'mime_type' => 'audio/dts',
679
						),
680
681
				// FLAC - audio       - Free Lossless Audio Codec
682
				'flac' => array(
683
							'pattern'   => '^fLaC',
684
							'group'     => 'audio',
685
							'module'    => 'flac',
686
							'mime_type' => 'audio/x-flac',
687
						),
688
689
				// LA   - audio       - Lossless Audio (LA)
690
				'la'   => array(
691
							'pattern'   => '^LA0[2-4]',
692
							'group'     => 'audio',
693
							'module'    => 'la',
694
							'mime_type' => 'application/octet-stream',
695
						),
696
697
				// LPAC - audio       - Lossless Predictive Audio Compression (LPAC)
698
				'lpac' => array(
699
							'pattern'   => '^LPAC',
700
							'group'     => 'audio',
701
							'module'    => 'lpac',
702
							'mime_type' => 'application/octet-stream',
703
						),
704
705
				// MIDI - audio       - MIDI (Musical Instrument Digital Interface)
706
				'midi' => array(
707
							'pattern'   => '^MThd',
708
							'group'     => 'audio',
709
							'module'    => 'midi',
710
							'mime_type' => 'audio/midi',
711
						),
712
713
				// MAC  - audio       - Monkey's Audio Compressor
714
				'mac'  => array(
715
							'pattern'   => '^MAC ',
716
							'group'     => 'audio',
717
							'module'    => 'monkey',
718
							'mime_type' => 'audio/x-monkeys-audio',
719
						),
720
721
// has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available
722
//				// MOD  - audio       - MODule (assorted sub-formats)
723
//				'mod'  => array(
724
//							'pattern'   => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)',
725
//							'group'     => 'audio',
726
//							'module'    => 'mod',
727
//							'option'    => 'mod',
728
//							'mime_type' => 'audio/mod',
729
//						),
730
731
				// MOD  - audio       - MODule (Impulse Tracker)
732
				'it'   => array(
733
							'pattern'   => '^IMPM',
734
							'group'     => 'audio',
735
							'module'    => 'mod',
736
							//'option'    => 'it',
737
							'mime_type' => 'audio/it',
738
						),
739
740
				// MOD  - audio       - MODule (eXtended Module, various sub-formats)
741
				'xm'   => array(
742
							'pattern'   => '^Extended Module',
743
							'group'     => 'audio',
744
							'module'    => 'mod',
745
							//'option'    => 'xm',
746
							'mime_type' => 'audio/xm',
747
						),
748
749
				// MOD  - audio       - MODule (ScreamTracker)
750
				's3m'  => array(
751
							'pattern'   => '^.{44}SCRM',
752
							'group'     => 'audio',
753
							'module'    => 'mod',
754
							//'option'    => 's3m',
755
							'mime_type' => 'audio/s3m',
756
						),
757
758
				// MPC  - audio       - Musepack / MPEGplus
759
				'mpc'  => array(
760
							'pattern'   => '^(MPCK|MP\\+|[\\x00\\x01\\x10\\x11\\x40\\x41\\x50\\x51\\x80\\x81\\x90\\x91\\xC0\\xC1\\xD0\\xD1][\\x20-\\x37][\\x00\\x20\\x40\\x60\\x80\\xA0\\xC0\\xE0])',
761
							'group'     => 'audio',
762
							'module'    => 'mpc',
763
							'mime_type' => 'audio/x-musepack',
764
						),
765
766
				// MP3  - audio       - MPEG-audio Layer 3 (very similar to AAC-ADTS)
767
				'mp3'  => array(
768
							'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]',
769
							'group'     => 'audio',
770
							'module'    => 'mp3',
771
							'mime_type' => 'audio/mpeg',
772
						),
773
774
				// OFR  - audio       - OptimFROG
775
				'ofr'  => array(
776
							'pattern'   => '^(\\*RIFF|OFR)',
777
							'group'     => 'audio',
778
							'module'    => 'optimfrog',
779
							'mime_type' => 'application/octet-stream',
780
						),
781
782
				// RKAU - audio       - RKive AUdio compressor
783
				'rkau' => array(
784
							'pattern'   => '^RKA',
785
							'group'     => 'audio',
786
							'module'    => 'rkau',
787
							'mime_type' => 'application/octet-stream',
788
						),
789
790
				// SHN  - audio       - Shorten
791
				'shn'  => array(
792
							'pattern'   => '^ajkg',
793
							'group'     => 'audio',
794
							'module'    => 'shorten',
795
							'mime_type' => 'audio/xmms-shn',
796
							'fail_id3'  => 'ERROR',
797
							'fail_ape'  => 'ERROR',
798
						),
799
800
				// TTA  - audio       - TTA Lossless Audio Compressor (http://tta.corecodec.org)
801
				'tta'  => array(
802
							'pattern'   => '^TTA',  // could also be '^TTA(\\x01|\\x02|\\x03|2|1)'
803
							'group'     => 'audio',
804
							'module'    => 'tta',
805
							'mime_type' => 'application/octet-stream',
806
						),
807
808
				// VOC  - audio       - Creative Voice (VOC)
809
				'voc'  => array(
810
							'pattern'   => '^Creative Voice File',
811
							'group'     => 'audio',
812
							'module'    => 'voc',
813
							'mime_type' => 'audio/voc',
814
						),
815
816
				// VQF  - audio       - transform-domain weighted interleave Vector Quantization Format (VQF)
817
				'vqf'  => array(
818
							'pattern'   => '^TWIN',
819
							'group'     => 'audio',
820
							'module'    => 'vqf',
821
							'mime_type' => 'application/octet-stream',
822
						),
823
824
				// WV  - audio        - WavPack (v4.0+)
825
				'wv'   => array(
826
							'pattern'   => '^wvpk',
827
							'group'     => 'audio',
828
							'module'    => 'wavpack',
829
							'mime_type' => 'application/octet-stream',
830
						),
831
832
833
				// Audio-Video formats
834
835
				// ASF  - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
836
				'asf'  => array(
837
							'pattern'   => '^\\x30\\x26\\xB2\\x75\\x8E\\x66\\xCF\\x11\\xA6\\xD9\\x00\\xAA\\x00\\x62\\xCE\\x6C',
838
							'group'     => 'audio-video',
839
							'module'    => 'asf',
840
							'mime_type' => 'video/x-ms-asf',
841
							'iconv_req' => false,
842
						),
843
844
				// BINK - audio/video - Bink / Smacker
845
				'bink' => array(
846
							'pattern'   => '^(BIK|SMK)',
847
							'group'     => 'audio-video',
848
							'module'    => 'bink',
849
							'mime_type' => 'application/octet-stream',
850
						),
851
852
				// FLV  - audio/video - FLash Video
853
				'flv' => array(
854
							'pattern'   => '^FLV[\\x01]',
855
							'group'     => 'audio-video',
856
							'module'    => 'flv',
857
							'mime_type' => 'video/x-flv',
858
						),
859
860
				// MKAV - audio/video - Mastroka
861
				'matroska' => array(
862
							'pattern'   => '^\\x1A\\x45\\xDF\\xA3',
863
							'group'     => 'audio-video',
864
							'module'    => 'matroska',
865
							'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
866
						),
867
868
				// MPEG - audio/video - MPEG (Moving Pictures Experts Group)
869
				'mpeg' => array(
870
							'pattern'   => '^\\x00\\x00\\x01[\\xB3\\xBA]',
871
							'group'     => 'audio-video',
872
							'module'    => 'mpeg',
873
							'mime_type' => 'video/mpeg',
874
						),
875
876
				// NSV  - audio/video - Nullsoft Streaming Video (NSV)
877
				'nsv'  => array(
878
							'pattern'   => '^NSV[sf]',
879
							'group'     => 'audio-video',
880
							'module'    => 'nsv',
881
							'mime_type' => 'application/octet-stream',
882
						),
883
884
				// Ogg  - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
885
				'ogg'  => array(
886
							'pattern'   => '^OggS',
887
							'group'     => 'audio',
888
							'module'    => 'ogg',
889
							'mime_type' => 'application/ogg',
890
							'fail_id3'  => 'WARNING',
891
							'fail_ape'  => 'WARNING',
892
						),
893
894
				// QT   - audio/video - Quicktime
895
				'quicktime' => array(
896
							'pattern'   => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
897
							'group'     => 'audio-video',
898
							'module'    => 'quicktime',
899
							'mime_type' => 'video/quicktime',
900
						),
901
902
				// 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)
903
				'riff' => array(
904
							'pattern'   => '^(RIFF|SDSS|FORM)',
905
							'group'     => 'audio-video',
906
							'module'    => 'riff',
907
							'mime_type' => 'audio/x-wav',
908
							'fail_ape'  => 'WARNING',
909
						),
910
911
				// Real - audio/video - RealAudio, RealVideo
912
				'real' => array(
913
							'pattern'   => '^\\.(RMF|ra)',
914
							'group'     => 'audio-video',
915
							'module'    => 'real',
916
							'mime_type' => 'audio/x-realaudio',
917
						),
918
919
				// SWF - audio/video - ShockWave Flash
920
				'swf' => array(
921
							'pattern'   => '^(F|C)WS',
922
							'group'     => 'audio-video',
923
							'module'    => 'swf',
924
							'mime_type' => 'application/x-shockwave-flash',
925
						),
926
927
				// TS - audio/video - MPEG-2 Transport Stream
928
				'ts' => array(
929
							'pattern'   => '^(\\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G".  Check for at least 10 packets matching this pattern
930
							'group'     => 'audio-video',
931
							'module'    => 'ts',
932
							'mime_type' => 'video/MP2T',
933
						),
934
935
936
				// Still-Image formats
937
938
				// BMP  - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
939
				'bmp'  => array(
940
							'pattern'   => '^BM',
941
							'group'     => 'graphic',
942
							'module'    => 'bmp',
943
							'mime_type' => 'image/bmp',
944
							'fail_id3'  => 'ERROR',
945
							'fail_ape'  => 'ERROR',
946
						),
947
948
				// GIF  - still image - Graphics Interchange Format
949
				'gif'  => array(
950
							'pattern'   => '^GIF',
951
							'group'     => 'graphic',
952
							'module'    => 'gif',
953
							'mime_type' => 'image/gif',
954
							'fail_id3'  => 'ERROR',
955
							'fail_ape'  => 'ERROR',
956
						),
957
958
				// JPEG - still image - Joint Photographic Experts Group (JPEG)
959
				'jpg'  => array(
960
							'pattern'   => '^\\xFF\\xD8\\xFF',
961
							'group'     => 'graphic',
962
							'module'    => 'jpg',
963
							'mime_type' => 'image/jpeg',
964
							'fail_id3'  => 'ERROR',
965
							'fail_ape'  => 'ERROR',
966
						),
967
968
				// PCD  - still image - Kodak Photo CD
969
				'pcd'  => array(
970
							'pattern'   => '^.{2048}PCD_IPI\\x00',
971
							'group'     => 'graphic',
972
							'module'    => 'pcd',
973
							'mime_type' => 'image/x-photo-cd',
974
							'fail_id3'  => 'ERROR',
975
							'fail_ape'  => 'ERROR',
976
						),
977
978
979
				// PNG  - still image - Portable Network Graphics (PNG)
980
				'png'  => array(
981
							'pattern'   => '^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A',
982
							'group'     => 'graphic',
983
							'module'    => 'png',
984
							'mime_type' => 'image/png',
985
							'fail_id3'  => 'ERROR',
986
							'fail_ape'  => 'ERROR',
987
						),
988
989
990
				// SVG  - still image - Scalable Vector Graphics (SVG)
991
				'svg'  => array(
992
							'pattern'   => '(<!DOCTYPE svg PUBLIC |xmlns="http://www\\.w3\\.org/2000/svg")',
993
							'group'     => 'graphic',
994
							'module'    => 'svg',
995
							'mime_type' => 'image/svg+xml',
996
							'fail_id3'  => 'ERROR',
997
							'fail_ape'  => 'ERROR',
998
						),
999
1000
1001
				// TIFF - still image - Tagged Information File Format (TIFF)
1002
				'tiff' => array(
1003
							'pattern'   => '^(II\\x2A\\x00|MM\\x00\\x2A)',
1004
							'group'     => 'graphic',
1005
							'module'    => 'tiff',
1006
							'mime_type' => 'image/tiff',
1007
							'fail_id3'  => 'ERROR',
1008
							'fail_ape'  => 'ERROR',
1009
						),
1010
1011
1012
				// EFAX - still image - eFax (TIFF derivative)
1013
				'efax'  => array(
1014
							'pattern'   => '^\\xDC\\xFE',
1015
							'group'     => 'graphic',
1016
							'module'    => 'efax',
1017
							'mime_type' => 'image/efax',
1018
							'fail_id3'  => 'ERROR',
1019
							'fail_ape'  => 'ERROR',
1020
						),
1021
1022
1023
				// Data formats
1024
1025
				// ISO  - data        - International Standards Organization (ISO) CD-ROM Image
1026
				'iso'  => array(
1027
							'pattern'   => '^.{32769}CD001',
1028
							'group'     => 'misc',
1029
							'module'    => 'iso',
1030
							'mime_type' => 'application/octet-stream',
1031
							'fail_id3'  => 'ERROR',
1032
							'fail_ape'  => 'ERROR',
1033
							'iconv_req' => false,
1034
						),
1035
1036
				// RAR  - data        - RAR compressed data
1037
				'rar'  => array(
1038
							'pattern'   => '^Rar\\!',
1039
							'group'     => 'archive',
1040
							'module'    => 'rar',
1041
							'mime_type' => 'application/octet-stream',
1042
							'fail_id3'  => 'ERROR',
1043
							'fail_ape'  => 'ERROR',
1044
						),
1045
1046
				// SZIP - audio/data  - SZIP compressed data
1047
				'szip' => array(
1048
							'pattern'   => '^SZ\\x0A\\x04',
1049
							'group'     => 'archive',
1050
							'module'    => 'szip',
1051
							'mime_type' => 'application/octet-stream',
1052
							'fail_id3'  => 'ERROR',
1053
							'fail_ape'  => 'ERROR',
1054
						),
1055
1056
				// TAR  - data        - TAR compressed data
1057
				'tar'  => array(
1058
							'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}',
1059
							'group'     => 'archive',
1060
							'module'    => 'tar',
1061
							'mime_type' => 'application/x-tar',
1062
							'fail_id3'  => 'ERROR',
1063
							'fail_ape'  => 'ERROR',
1064
						),
1065
1066
				// GZIP  - data        - GZIP compressed data
1067
				'gz'  => array(
1068
							'pattern'   => '^\\x1F\\x8B\\x08',
1069
							'group'     => 'archive',
1070
							'module'    => 'gzip',
1071
							'mime_type' => 'application/x-gzip',
1072
							'fail_id3'  => 'ERROR',
1073
							'fail_ape'  => 'ERROR',
1074
						),
1075
1076
				// ZIP  - data         - ZIP compressed data
1077
				'zip'  => array(
1078
							'pattern'   => '^PK\\x03\\x04',
1079
							'group'     => 'archive',
1080
							'module'    => 'zip',
1081
							'mime_type' => 'application/zip',
1082
							'fail_id3'  => 'ERROR',
1083
							'fail_ape'  => 'ERROR',
1084
						),
1085
1086
1087
				// Misc other formats
1088
1089
				// PAR2 - data        - Parity Volume Set Specification 2.0
1090
				'par2' => array (
1091
							'pattern'   => '^PAR2\\x00PKT',
1092
							'group'     => 'misc',
1093
							'module'    => 'par2',
1094
							'mime_type' => 'application/octet-stream',
1095
							'fail_id3'  => 'ERROR',
1096
							'fail_ape'  => 'ERROR',
1097
						),
1098
1099
				// PDF  - data        - Portable Document Format
1100
				'pdf'  => array(
1101
							'pattern'   => '^\\x25PDF',
1102
							'group'     => 'misc',
1103
							'module'    => 'pdf',
1104
							'mime_type' => 'application/pdf',
1105
							'fail_id3'  => 'ERROR',
1106
							'fail_ape'  => 'ERROR',
1107
						),
1108
1109
				// MSOFFICE  - data   - ZIP compressed data
1110
				'msoffice' => array(
1111
							'pattern'   => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
1112
							'group'     => 'misc',
1113
							'module'    => 'msoffice',
1114
							'mime_type' => 'application/octet-stream',
1115
							'fail_id3'  => 'ERROR',
1116
							'fail_ape'  => 'ERROR',
1117
						),
1118
1119
				 // CUE  - data       - CUEsheet (index to single-file disc images)
1120
				 'cue' => array(
1121
							'pattern'   => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
1122
							'group'     => 'misc',
1123
							'module'    => 'cue',
1124
							'mime_type' => 'application/octet-stream',
1125
						   ),
1126
1127
			);
1128
		}
1129
1130
		return $format_info;
1131
	}
1132
1133
1134
1135
	public function GetFileFormat(&$filedata, $filename='') {
1136
		// this function will determine the format of a file based on usually
1137
		// the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
1138
		// and in the case of ISO CD image, 6 bytes offset 32kb from the start
1139
		// of the file).
1140
1141
		// Identify file format - loop through $format_info and detect with reg expr
1142
		foreach ($this->GetFileFormatArray() as $format_name => $info) {
1143
			// The /s switch on preg_match() forces preg_match() NOT to treat
1144
			// newline (0x0A) characters as special chars but do a binary match
1145
			if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) {
1146
				$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1147
				return $info;
1148
			}
1149
		}
1150
1151
1152
		if (preg_match('#\\.mp[123a]$#i', $filename)) {
1153
			// Too many mp3 encoders on the market put gabage in front of mpeg files
1154
			// use assume format on these if format detection failed
1155
			$GetFileFormatArray = $this->GetFileFormatArray();
1156
			$info = $GetFileFormatArray['mp3'];
1157
			$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1158
			return $info;
1159
		} elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
1160
			// there's not really a useful consistent "magic" at the beginning of .cue files to identify them
1161
			// so until I think of something better, just go by filename if all other format checks fail
1162
			// and verify there's at least one instance of "TRACK xx AUDIO" in the file
1163
			$GetFileFormatArray = $this->GetFileFormatArray();
1164
			$info = $GetFileFormatArray['cue'];
1165
			$info['include']   = 'module.'.$info['group'].'.'.$info['module'].'.php';
1166
			return $info;
1167
		}
1168
1169
		return false;
1170
	}
1171
1172
1173
	// converts array to $encoding charset from $this->encoding
1174
	public function CharConvert(&$array, $encoding) {
1175
1176
		// identical encoding - end here
1177
		if ($encoding == $this->encoding) {
1178
			return;
1179
		}
1180
1181
		// loop thru array
1182
		foreach ($array as $key => $value) {
1183
1184
			// go recursive
1185
			if (is_array($value)) {
1186
				$this->CharConvert($array[$key], $encoding);
1187
			}
1188
1189
			// convert string
1190
			elseif (is_string($value)) {
1191
				$array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value));
1192
			}
1193
		}
1194
	}
1195
1196
1197
	public function HandleAllTags() {
1198
1199
		// key name => array (tag name, character encoding)
1200
		static $tags;
1201
		if (empty($tags)) {
1202
			$tags = array(
1203
				'asf'       => array('asf'           , 'UTF-16LE'),
1204
				'midi'      => array('midi'          , 'ISO-8859-1'),
1205
				'nsv'       => array('nsv'           , 'ISO-8859-1'),
1206
				'ogg'       => array('vorbiscomment' , 'UTF-8'),
1207
				'png'       => array('png'           , 'UTF-8'),
1208
				'tiff'      => array('tiff'          , 'ISO-8859-1'),
1209
				'quicktime' => array('quicktime'     , 'UTF-8'),
1210
				'real'      => array('real'          , 'ISO-8859-1'),
1211
				'vqf'       => array('vqf'           , 'ISO-8859-1'),
1212
				'zip'       => array('zip'           , 'ISO-8859-1'),
1213
				'riff'      => array('riff'          , 'ISO-8859-1'),
1214
				'lyrics3'   => array('lyrics3'       , 'ISO-8859-1'),
1215
				'id3v1'     => array('id3v1'         , $this->encoding_id3v1),
1216
				'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
1217
				'ape'       => array('ape'           , 'UTF-8'),
1218
				'cue'       => array('cue'           , 'ISO-8859-1'),
1219
				'matroska'  => array('matroska'      , 'UTF-8'),
1220
				'flac'      => array('vorbiscomment' , 'UTF-8'),
1221
				'divxtag'   => array('divx'          , 'ISO-8859-1'),
1222
				'iptc'      => array('iptc'          , 'ISO-8859-1'),
1223
			);
1224
		}
1225
1226
		// loop through comments array
1227
		foreach ($tags as $comment_name => $tagname_encoding_array) {
1228
			list($tag_name, $encoding) = $tagname_encoding_array;
1229
1230
			// fill in default encoding type if not already present
1231
			if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) {
1232
				$this->info[$comment_name]['encoding'] = $encoding;
1233
			}
1234
1235
			// copy comments if key name set
1236
			if (!empty($this->info[$comment_name]['comments'])) {
1237
				foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) {
1238
					foreach ($valuearray as $key => $value) {
1239
						if (is_string($value)) {
1240
							$value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
1241
						}
1242
						if ($value) {
1243
							if (!is_numeric($key)) {
1244
								$this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
1245
							} else {
1246
								$this->info['tags'][trim($tag_name)][trim($tag_key)][]     = $value;
1247
							}
1248
						}
1249
					}
1250
					if ($tag_key == 'picture') {
1251
						unset($this->info[$comment_name]['comments'][$tag_key]);
1252
					}
1253
				}
1254
1255
				if (!isset($this->info['tags'][$tag_name])) {
1256
					// comments are set but contain nothing but empty strings, so skip
1257
					continue;
1258
				}
1259
1260
				$this->CharConvert($this->info['tags'][$tag_name], $this->info[$comment_name]['encoding']);           // only copy gets converted!
1261
1262
				if ($this->option_tags_html) {
1263
					foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
1264
						$this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']);
1265
					}
1266
				}
1267
1268
			}
1269
1270
		}
1271
1272
		// pictures can take up a lot of space, and we don't need multiple copies of them
1273
		// let there be a single copy in [comments][picture], and not elsewhere
1274
		if (!empty($this->info['tags'])) {
1275
			$unset_keys = array('tags', 'tags_html');
1276
			foreach ($this->info['tags'] as $tagtype => $tagarray) {
1277
				foreach ($tagarray as $tagname => $tagdata) {
1278
					if ($tagname == 'picture') {
1279
						foreach ($tagdata as $key => $tagarray) {
1280
							$this->info['comments']['picture'][] = $tagarray;
1281
							if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
1282 View Code Duplication
								if (isset($this->info['tags'][$tagtype][$tagname][$key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1283
									unset($this->info['tags'][$tagtype][$tagname][$key]);
1284
								}
1285 View Code Duplication
								if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1286
									unset($this->info['tags_html'][$tagtype][$tagname][$key]);
1287
								}
1288
							}
1289
						}
1290
					}
1291
				}
1292
				foreach ($unset_keys as $unset_key) {
1293
					// remove possible empty keys from (e.g. [tags][id3v2][picture])
1294
					if (empty($this->info[$unset_key][$tagtype]['picture'])) {
1295
						unset($this->info[$unset_key][$tagtype]['picture']);
1296
					}
1297
					if (empty($this->info[$unset_key][$tagtype])) {
1298
						unset($this->info[$unset_key][$tagtype]);
1299
					}
1300
					if (empty($this->info[$unset_key])) {
1301
						unset($this->info[$unset_key]);
1302
					}
1303
				}
1304
				// remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
1305
				if (isset($this->info[$tagtype]['comments']['picture'])) {
1306
					unset($this->info[$tagtype]['comments']['picture']);
1307
				}
1308
				if (empty($this->info[$tagtype]['comments'])) {
1309
					unset($this->info[$tagtype]['comments']);
1310
				}
1311
				if (empty($this->info[$tagtype])) {
1312
					unset($this->info[$tagtype]);
1313
				}
1314
			}
1315
		}
1316
		return true;
1317
	}
1318
1319
	public function getHashdata($algorithm) {
1320
		switch ($algorithm) {
1321
			case 'md5':
1322
			case 'sha1':
1323
				break;
1324
1325
			default:
1326
				return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
1327
				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...
1328
		}
1329
1330
		if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) {
1331
1332
			// We cannot get an identical md5_data value for Ogg files where the comments
1333
			// span more than 1 Ogg page (compared to the same audio data with smaller
1334
			// comments) using the normal getID3() method of MD5'ing the data between the
1335
			// end of the comments and the end of the file (minus any trailing tags),
1336
			// because the page sequence numbers of the pages that the audio data is on
1337
			// do not match. Under normal circumstances, where comments are smaller than
1338
			// the nominal 4-8kB page size, then this is not a problem, but if there are
1339
			// very large comments, the only way around it is to strip off the comment
1340
			// tags with vorbiscomment and MD5 that file.
1341
			// This procedure must be applied to ALL Ogg files, not just the ones with
1342
			// comments larger than 1 page, because the below method simply MD5's the
1343
			// whole file with the comments stripped, not just the portion after the
1344
			// comments block (which is the standard getID3() method.
1345
1346
			// The above-mentioned problem of comments spanning multiple pages and changing
1347
			// page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
1348
			// currently vorbiscomment only works on OggVorbis files.
1349
1350
			if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
1351
1352
				$this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
1353
				$this->info[$algorithm.'_data'] = false;
1354
1355
			} else {
1356
1357
				// Prevent user from aborting script
1358
				$old_abort = ignore_user_abort(true);
1359
1360
				// Create empty file
1361
				$empty = tempnam(GETID3_TEMP_DIR, 'getID3');
1362
				touch($empty);
1363
1364
				// Use vorbiscomment to make temp file without comments
1365
				$temp = tempnam(GETID3_TEMP_DIR, 'getID3');
1366
				$file = $this->info['filenamepath'];
1367
1368
				if (GETID3_OS_ISWINDOWS) {
1369
1370
					if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {
1371
1372
						$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
1373
						$VorbisCommentError = `$commandline`;
1374
1375
					} else {
1376
1377
						$VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;
1378
1379
					}
1380
1381
				} else {
1382
1383
					$commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
1384
					$VorbisCommentError = `$commandline`;
1385
1386
				}
1387
1388
				if (!empty($VorbisCommentError)) {
1389
1390
					$this->warning('Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError);
1391
					$this->info[$algorithm.'_data'] = false;
1392
1393
				} else {
1394
1395
					// Get hash of newly created file
1396
					switch ($algorithm) {
1397
						case 'md5':
1398
							$this->info[$algorithm.'_data'] = md5_file($temp);
1399
							break;
1400
1401
						case 'sha1':
1402
							$this->info[$algorithm.'_data'] = sha1_file($temp);
1403
							break;
1404
					}
1405
				}
1406
1407
				// Clean up
1408
				unlink($empty);
1409
				unlink($temp);
1410
1411
				// Reset abort setting
1412
				ignore_user_abort($old_abort);
1413
1414
			}
1415
1416
		} else {
1417
1418
			if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) {
1419
1420
				// get hash from part of file
1421
				$this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm);
1422
1423
			} else {
1424
1425
				// get hash from whole file
1426
				switch ($algorithm) {
1427
					case 'md5':
1428
						$this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']);
1429
						break;
1430
1431
					case 'sha1':
1432
						$this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']);
1433
						break;
1434
				}
1435
			}
1436
1437
		}
1438
		return true;
1439
	}
1440
1441
1442
	public function ChannelsBitratePlaytimeCalculations() {
1443
1444
		// set channelmode on audio
1445
		if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) {
1446
			// ignore
1447
		} elseif ($this->info['audio']['channels'] == 1) {
1448
			$this->info['audio']['channelmode'] = 'mono';
1449
		} elseif ($this->info['audio']['channels'] == 2) {
1450
			$this->info['audio']['channelmode'] = 'stereo';
1451
		}
1452
1453
		// Calculate combined bitrate - audio + video
1454
		$CombinedBitrate  = 0;
1455
		$CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0);
1456
		$CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0);
1457
		if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) {
1458
			$this->info['bitrate'] = $CombinedBitrate;
1459
		}
1460
		//if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
1461
		//	// for example, VBR MPEG video files cannot determine video bitrate:
1462
		//	// should not set overall bitrate and playtime from audio bitrate only
1463
		//	unset($this->info['bitrate']);
1464
		//}
1465
1466
		// video bitrate undetermined, but calculable
1467
		if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) {
1468
			// if video bitrate not set
1469
			if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) {
1470
				// AND if audio bitrate is set to same as overall bitrate
1471
				if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) {
1472
					// AND if playtime is set
1473
					if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) {
1474
						// AND if AV data offset start/end is known
1475
						// THEN we can calculate the video bitrate
1476
						$this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']);
1477
						$this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate'];
1478
					}
1479
				}
1480
			}
1481
		}
1482
1483 View Code Duplication
		if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1484
			$this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
1485
		}
1486
1487 View Code Duplication
		if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1488
			$this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds'];
1489
		}
1490
		if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) {
1491
			if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) {
1492
				// audio only
1493
				$this->info['audio']['bitrate'] = $this->info['bitrate'];
1494
			} elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) {
1495
				// video only
1496
				$this->info['video']['bitrate'] = $this->info['bitrate'];
1497
			}
1498
		}
1499
1500
		// Set playtime string
1501
		if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
1502
			$this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']);
1503
		}
1504
	}
1505
1506
1507
	public function CalculateCompressionRatioVideo() {
1508
		if (empty($this->info['video'])) {
1509
			return false;
1510
		}
1511
		if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) {
1512
			return false;
1513
		}
1514
		if (empty($this->info['video']['bits_per_sample'])) {
1515
			return false;
1516
		}
1517
1518
		switch ($this->info['video']['dataformat']) {
1519
			case 'bmp':
1520
			case 'gif':
1521
			case 'jpeg':
1522
			case 'jpg':
1523
			case 'png':
1524 View Code Duplication
			case 'tiff':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1525
				$FrameRate = 1;
1526
				$PlaytimeSeconds = 1;
0 ignored issues
show
Unused Code introduced by
$PlaytimeSeconds is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1527
				$BitrateCompressed = $this->info['filesize'] * 8;
1528
				break;
1529
1530
			default:
1531
				if (!empty($this->info['video']['frame_rate'])) {
1532
					$FrameRate = $this->info['video']['frame_rate'];
1533
				} else {
1534
					return false;
1535
				}
1536
				if (!empty($this->info['playtime_seconds'])) {
1537
					$PlaytimeSeconds = $this->info['playtime_seconds'];
0 ignored issues
show
Unused Code introduced by
$PlaytimeSeconds is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1538
				} else {
1539
					return false;
1540
				}
1541
				if (!empty($this->info['video']['bitrate'])) {
1542
					$BitrateCompressed = $this->info['video']['bitrate'];
1543
				} else {
1544
					return false;
1545
				}
1546
				break;
1547
		}
1548
		$BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;
1549
1550
		$this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
1551
		return true;
1552
	}
1553
1554
1555
	public function CalculateCompressionRatioAudio() {
1556
		if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) {
1557
			return false;
1558
		}
1559
		$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));
1560
1561
		if (!empty($this->info['audio']['streams'])) {
1562
			foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) {
1563
				if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) {
1564
					$this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16));
1565
				}
1566
			}
1567
		}
1568
		return true;
1569
	}
1570
1571
1572
	public function CalculateReplayGain() {
1573
		if (isset($this->info['replay_gain'])) {
1574
			if (!isset($this->info['replay_gain']['reference_volume'])) {
1575
				$this->info['replay_gain']['reference_volume'] = (double) 89.0;
1576
			}
1577
			if (isset($this->info['replay_gain']['track']['adjustment'])) {
1578
				$this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment'];
1579
			}
1580 View Code Duplication
			if (isset($this->info['replay_gain']['album']['adjustment'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1581
				$this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment'];
1582
			}
1583
1584 View Code Duplication
			if (isset($this->info['replay_gain']['track']['peak'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1585
				$this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']);
1586
			}
1587 View Code Duplication
			if (isset($this->info['replay_gain']['album']['peak'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1588
				$this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']);
1589
			}
1590
		}
1591
		return true;
1592
	}
1593
1594
	public function ProcessAudioStreams() {
1595
		if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) {
1596
			if (!isset($this->info['audio']['streams'])) {
1597
				foreach ($this->info['audio'] as $key => $value) {
1598
					if ($key != 'streams') {
1599
						$this->info['audio']['streams'][0][$key] = $value;
1600
					}
1601
				}
1602
			}
1603
		}
1604
		return true;
1605
	}
1606
1607
	public function getid3_tempnam() {
1608
		return tempnam($this->tempdir, 'gI3');
1609
	}
1610
1611
	public function include_module($name) {
1612
		//if (!file_exists($this->include_path.'module.'.$name.'.php')) {
1613
		if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) {
1614
			throw new getid3_exception('Required module.'.$name.'.php is missing.');
1615
		}
1616
		include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php');
1617
		return true;
1618
	}
1619
1620
    public static function is_writable ($filename) {
1621
        $ret = is_writable($filename);
1622
1623
        if (!$ret) {
1624
            $perms = fileperms($filename);
1625
            $ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002);
1626
        }
1627
1628
        return $ret;
1629
    }
1630
1631
}
1632
1633
1634
abstract class getid3_handler {
1635
1636
	/**
1637
	* @var getID3
1638
	*/
1639
	protected $getid3;                       // pointer
1640
1641
	protected $data_string_flag     = false; // analyzing filepointer or string
1642
	protected $data_string          = '';    // string to analyze
1643
	protected $data_string_position = 0;     // seek position in string
1644
	protected $data_string_length   = 0;     // string length
1645
1646
	private $dependency_to = null;
1647
1648
1649
	public function __construct(getID3 $getid3, $call_module=null) {
1650
		$this->getid3 = $getid3;
1651
1652
		if ($call_module) {
1653
			$this->dependency_to = str_replace('getid3_', '', $call_module);
1654
		}
1655
	}
1656
1657
1658
	// Analyze from file pointer
1659
	abstract public function Analyze();
1660
1661
1662
	// Analyze from string instead
1663
	public function AnalyzeString($string) {
1664
		// Enter string mode
1665
		$this->setStringMode($string);
1666
1667
		// Save info
1668
		$saved_avdataoffset = $this->getid3->info['avdataoffset'];
1669
		$saved_avdataend    = $this->getid3->info['avdataend'];
1670
		$saved_filesize     = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call
1671
1672
		// Reset some info
1673
		$this->getid3->info['avdataoffset'] = 0;
1674
		$this->getid3->info['avdataend']    = $this->getid3->info['filesize'] = $this->data_string_length;
1675
1676
		// Analyze
1677
		$this->Analyze();
1678
1679
		// Restore some info
1680
		$this->getid3->info['avdataoffset'] = $saved_avdataoffset;
1681
		$this->getid3->info['avdataend']    = $saved_avdataend;
1682
		$this->getid3->info['filesize']     = $saved_filesize;
1683
1684
		// Exit string mode
1685
		$this->data_string_flag = false;
1686
	}
1687
1688
	public function setStringMode($string) {
1689
		$this->data_string_flag   = true;
1690
		$this->data_string        = $string;
1691
		$this->data_string_length = strlen($string);
1692
	}
1693
1694
	protected function ftell() {
1695
		if ($this->data_string_flag) {
1696
			return $this->data_string_position;
1697
		}
1698
		return ftell($this->getid3->fp);
1699
	}
1700
1701
	protected function fread($bytes) {
1702
		if ($this->data_string_flag) {
1703
			$this->data_string_position += $bytes;
1704
			return substr($this->data_string, $this->data_string_position - $bytes, $bytes);
1705
		}
1706
		$pos = $this->ftell() + $bytes;
1707
		if (!getid3_lib::intValueSupported($pos)) {
1708
			throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10);
1709
		}
1710
1711
		//return fread($this->getid3->fp, $bytes);
1712
		/*
1713
		* http://www.getid3.org/phpBB3/viewtopic.php?t=1930
1714
		* "I found out that the root cause for the problem was how getID3 uses the PHP system function fread().
1715
		* It seems to assume that fread() would always return as many bytes as were requested.
1716
		* However, according the PHP manual (http://php.net/manual/en/function.fread.php), this is the case only with regular local files, but not e.g. with Linux pipes.
1717
		* The call may return only part of the requested data and a new call is needed to get more."
1718
		*/
1719
		$contents = '';
1720
		do {
1721
			$part = fread($this->getid3->fp, $bytes);
1722
			$partLength  = strlen($part);
1723
			$bytes      -= $partLength;
1724
			$contents   .= $part;
1725
		} while (($bytes > 0) && ($partLength > 0));
1726
		return $contents;
1727
	}
1728
1729
	protected function fseek($bytes, $whence=SEEK_SET) {
1730
		if ($this->data_string_flag) {
1731
			switch ($whence) {
1732
				case SEEK_SET:
1733
					$this->data_string_position = $bytes;
1734
					break;
1735
1736
				case SEEK_CUR:
1737
					$this->data_string_position += $bytes;
1738
					break;
1739
1740
				case SEEK_END:
1741
					$this->data_string_position = $this->data_string_length + $bytes;
1742
					break;
1743
			}
1744
			return 0;
1745
		} else {
1746
			$pos = $bytes;
1747
			if ($whence == SEEK_CUR) {
1748
				$pos = $this->ftell() + $bytes;
1749
			} elseif ($whence == SEEK_END) {
1750
				$pos = $this->getid3->info['filesize'] + $bytes;
1751
			}
1752
			if (!getid3_lib::intValueSupported($pos)) {
1753
				throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
1754
			}
1755
		}
1756
		return fseek($this->getid3->fp, $bytes, $whence);
1757
	}
1758
1759
	protected function feof() {
1760
		if ($this->data_string_flag) {
1761
			return $this->data_string_position >= $this->data_string_length;
1762
		}
1763
		return feof($this->getid3->fp);
1764
	}
1765
1766
	final protected function isDependencyFor($module) {
1767
		return $this->dependency_to == $module;
1768
	}
1769
1770
	protected function error($text) {
1771
		$this->getid3->info['error'][] = $text;
1772
1773
		return false;
1774
	}
1775
1776
	protected function warning($text) {
1777
		return $this->getid3->warning($text);
1778
	}
1779
1780
	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...
1781
		// does nothing for now
1782
	}
1783
1784
	public function saveAttachment($name, $offset, $length, $image_mime=null) {
1785
		try {
1786
1787
			// do not extract at all
1788
			if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) {
1789
1790
				$attachment = null; // do not set any
1791
1792
			// extract to return array
1793
			} elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) {
1794
1795
				$this->fseek($offset);
1796
				$attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory
1797
				if ($attachment === false || strlen($attachment) != $length) {
1798
					throw new Exception('failed to read attachment data');
1799
				}
1800
1801
			// assume directory path is given
1802
			} else {
1803
1804
				// set up destination path
1805
				$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1806
				if (!is_dir($dir) || !getID3::is_writable($dir)) { // check supplied directory
1807
					throw new Exception('supplied path ('.$dir.') does not exist, or is not writable');
1808
				}
1809
				$dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : '');
1810
1811
				// create dest file
1812
				if (($fp_dest = fopen($dest, 'wb')) == false) {
1813
					throw new Exception('failed to create file '.$dest);
1814
				}
1815
1816
				// copy data
1817
				$this->fseek($offset);
1818
				$buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size());
1819
				$bytesleft = $length;
1820
				while ($bytesleft > 0) {
1821
					if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) {
1822
						throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space');
1823
					}
1824
					$bytesleft -= $byteswritten;
1825
				}
1826
1827
				fclose($fp_dest);
1828
				$attachment = $dest;
1829
1830
			}
1831
1832
		} catch (Exception $e) {
1833
1834
			// close and remove dest file if created
1835
			if (isset($fp_dest) && is_resource($fp_dest)) {
1836
				fclose($fp_dest);
1837
				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...
1838
			}
1839
1840
			// do not set any is case of error
1841
			$attachment = null;
1842
			$this->warning('Failed to extract attachment '.$name.': '.$e->getMessage());
1843
1844
		}
1845
1846
		// seek to the end of attachment
1847
		$this->fseek($offset + $length);
1848
1849
		return $attachment;
1850
	}
1851
1852
}
1853
1854
1855
class getid3_exception extends Exception
1856
{
1857
	public $message;
1858
}
1859