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