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 //
9 // Please see readme.txt for more information //
11 /////////////////////////////////////////////////////////////////
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));
17 // Get base path of getID3() - ONCE
18 if (!defined('GETID3_INCLUDEPATH')) {
19 define('GETID3_INCLUDEPATH', dirname(__FILE__
).DIRECTORY_SEPARATOR
);
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
);
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));
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:
35 //setlocale(LC_CTYPE, 'en_US.UTF-8');
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))) {
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();
46 $temp_dir = @realpath
($temp_dir); // see https://github.com/JamesHeinrich/getID3/pull/10
47 $open_basedir = ini_get('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
;
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
;
61 if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) {
62 $found_valid_tempdir = true;
66 if (!$found_valid_tempdir) {
69 unset($open_basedirs, $found_valid_tempdir, $basedir);
72 $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
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);
78 unset($open_basedir, $temp_dir);
90 * CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE
94 public $encoding = 'UTF-8';
97 * Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252'
101 public $encoding_id3v1 = 'ISO-8859-1';
104 * Optional tag checks - disable for speed.
108 * Read and process ID3v1 tags
112 public $option_tag_id3v1 = true;
115 * Read and process ID3v2 tags
119 public $option_tag_id3v2 = true;
122 * Read and process Lyrics3 tags
126 public $option_tag_lyrics3 = true;
129 * Read and process APE tags
133 public $option_tag_apetag = true;
136 * Copy tags to root key 'tags' and encode to $this->encoding
140 public $option_tags_process = true;
143 * Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
147 public $option_tags_html = true;
150 * Optional tag/comment calculations
154 * Calculate additional info such as bitrate, channelmode etc
158 public $option_extra_info = true;
161 * Optional handling of embedded attachments (e.g. images)
165 * Defaults to true (ATTACHMENTS_INLINE) for backward compatibility
169 public $option_save_attachments = true;
172 * Optional calculations
176 * Get MD5 sum of data part - slow
180 public $option_md5_data = false;
183 * Use MD5 of source file if availble - only FLAC and OptimFROG
187 public $option_md5_data_source = false;
190 * Get SHA1 sum of data part - slow
194 public $option_sha1_data = false;
197 * Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on
202 public $option_max_2gb_check;
205 * Read buffer size in bytes
209 public $option_fread_buffer_size = 32768;
214 * Filename of file being analysed.
221 * Filepointer to file being analysed.
237 public $tempdir = GETID3_TEMP_DIR
;
242 public $memory_limit = 0;
247 protected $startup_error = '';
252 protected $startup_warning = '';
254 const VERSION
= '1.9.16-201810171314';
255 const FREAD_BUFFER_SIZE
= 32768;
257 const ATTACHMENTS_NONE
= false;
258 const ATTACHMENTS_INLINE
= true;
260 public function __construct() {
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";
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;
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";
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.');
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";
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";
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";
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";
317 if ($this->option_max_2gb_check
=== null) {
318 $this->option_max_2gb_check
= (PHP_INT_MAX
<= 2147483647);
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')) {
330 $helperappsdir = GETID3_INCLUDEPATH
.'..'.DIRECTORY_SEPARATOR
.'helperapps'; // must not have any space in this path
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) {
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))) {
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";
356 $path_so_far[] = $value;
358 $helperappsdir = implode(DIRECTORY_SEPARATOR
, $path_so_far);
360 define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR
);
363 if (!empty($this->startup_error
)) {
364 echo $this->startup_error
;
365 throw new getid3_exception($this->startup_error
);
372 public function version() {
373 return self
::VERSION
;
379 public function fread_buffer_size() {
380 return $this->option_fread_buffer_size
;
384 * @param array $optArray
388 public function setOption($optArray) {
389 if (!is_array($optArray) ||
empty($optArray)) {
392 foreach ($optArray as $opt => $val) {
393 if (isset($this->$opt) === false) {
402 * @param string $filename
403 * @param int $filesize
407 * @throws getid3_exception
409 public function openfile($filename, $filesize=null) {
411 if (!empty($this->startup_error
)) {
412 throw new getid3_exception($this->startup_error
);
414 if (!empty($this->startup_warning
)) {
415 foreach (explode("\n", $this->startup_warning
) as $startup_warning) {
416 $this->warning($startup_warning);
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);
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');
431 $filename = str_replace('/', DIRECTORY_SEPARATOR
, $filename);
432 //$filename = preg_replace('#(?<!gs:)('.preg_quote(DIRECTORY_SEPARATOR).'{2,})#', DIRECTORY_SEPARATOR, $filename);
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'))) {
439 $errormessagelist = array();
440 if (!is_readable($filename)) {
441 $errormessagelist[] = '!is_readable';
443 if (!is_file($filename)) {
444 $errormessagelist[] = '!is_file';
446 if (!file_exists($filename)) {
447 $errormessagelist[] = '!file_exists';
449 if (empty($errormessagelist)) {
450 $errormessagelist[] = 'fopen failed';
452 throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')');
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'];
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
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']);
486 if ($real_filesize === false) {
487 unset($this->info
['filesize']);
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']);
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');
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.');
502 } catch (Exception
$e) {
503 $this->error($e->getMessage());
511 * @param string $filename
512 * @param int $filesize
513 * @param string $original_filename
517 public function analyze($filename, $filesize=null, $original_filename='') {
519 if (!$this->openfile($filename, $filesize)) {
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);
529 $tag_class = 'getid3_'.$tag_name;
530 $tag = new $tag_class($this);
533 catch (getid3_exception
$e) {
538 if (isset($this->info
['id3v2']['tag_offset_start'])) {
539 $this->info
['avdataoffset'] = max($this->info
['avdataoffset'], $this->info
['id3v2']['tag_offset_end']);
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']);
547 // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
548 if (!$this->option_tag_id3v2
) {
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
559 // read 32 kb file data
560 fseek($this->fp
, $this->info
['avdataoffset']);
561 $formattest = fread($this->fp
, 32774);
564 $determined_format = $this->GetFileFormat($formattest, ($original_filename ?
$original_filename : $filename));
566 // unable to determine file format
567 if (!$determined_format) {
569 return $this->error('unable to determine file format');
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') {
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.');
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') {
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.');
593 $this->info
['mime_type'] = $determined_format['mime_type'];
595 // supported format signature pattern detected, but module deleted
596 if (!file_exists(GETID3_INCLUDEPATH
.$determined_format['include'])) {
598 return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
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';
608 $errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch';
610 return $this->error($errormessage);
614 include_once(GETID3_INCLUDEPATH
.$determined_format['include']);
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.');
621 $class = new $class_name($this);
628 // process all tags - copy to 'tags' and convert charsets
629 if ($this->option_tags_process
) {
630 $this->HandleAllTags();
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();
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');
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');
655 // remove undesired keys
658 } catch (Exception
$e) {
659 $this->error('Caught exception: '.$e->getMessage());
670 * @param string $message
674 public function error($message) {
676 if (!isset($this->info
['error'])) {
677 $this->info
['error'] = array();
679 $this->info
['error'][] = $message;
687 * @param string $message
691 public function warning($message) {
692 $this->info
['warning'][] = $message;
700 private function CleanUp() {
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]);
708 if (empty($this->info
['video'][$key]) && isset($this->info
['video'][$key])) {
709 unset($this->info
['video'][$key]);
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]);
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']);
727 if (isset($this->info
['avdataend'])) {
728 unset($this->info
['avdataend']);
732 // remove possible duplicated identical entries
733 if (!empty($this->info
['error'])) {
734 $this->info
['error'] = array_values(array_unique($this->info
['error']));
736 if (!empty($this->info
['warning'])) {
737 $this->info
['warning'] = array_values(array_unique($this->info
['warning']));
740 // remove "global variable" type keys
741 unset($this->info
['php_memory_limit']);
747 * Return array containing information about all supported formats.
751 public function GetFileFormatArray() {
752 static $format_info = array();
753 if (empty($format_info)) {
754 $format_info = array(
758 // AC-3 - audio - Dolby AC-3 / Dolby Digital
760 'pattern' => '^\\x0B\\x77',
763 'mime_type' => 'audio/ac3',
766 // AAC - audio - Advanced Audio Coding (AAC) - ADIF format
768 'pattern' => '^ADIF',
771 'mime_type' => 'audio/aac',
772 'fail_ape' => 'WARNING',
776 // AA - audio - Audible Audiobook
778 'pattern' => '^.{4}\\x57\\x90\\x75\\x36',
781 'mime_type' => 'audio/audible',
784 // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
786 'pattern' => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]',
789 'mime_type' => 'audio/aac',
790 'fail_ape' => 'WARNING',
794 // AU - audio - NeXT/Sun AUdio (AU)
796 'pattern' => '^\\.snd',
799 'mime_type' => 'audio/basic',
802 // AMR - audio - Adaptive Multi Rate
804 'pattern' => '^\\x23\\x21AMR\\x0A', // #!AMR[0A]
807 'mime_type' => 'audio/amr',
810 // AVR - audio - Audio Visual Research
812 'pattern' => '^2BIT',
815 'mime_type' => 'application/octet-stream',
818 // BONK - audio - Bonk v0.9+
820 'pattern' => '^\\x00(BONK|INFO|META| ID3)',
823 'mime_type' => 'audio/xmms-bonk',
826 // DSF - audio - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital
828 'pattern' => '^DSD ', // including trailing space: 44 53 44 20
831 'mime_type' => 'audio/dsd',
834 // DSS - audio - Digital Speech Standard
836 'pattern' => '^[\\x02-\\x06]ds[s2]',
839 'mime_type' => 'application/octet-stream',
842 // DTS - audio - Dolby Theatre System
844 'pattern' => '^\\x7F\\xFE\\x80\\x01',
847 'mime_type' => 'audio/dts',
850 // FLAC - audio - Free Lossless Audio Codec
852 'pattern' => '^fLaC',
855 'mime_type' => 'audio/flac',
858 // LA - audio - Lossless Audio (LA)
860 'pattern' => '^LA0[2-4]',
863 'mime_type' => 'application/octet-stream',
866 // LPAC - audio - Lossless Predictive Audio Compression (LPAC)
868 'pattern' => '^LPAC',
871 'mime_type' => 'application/octet-stream',
874 // MIDI - audio - MIDI (Musical Instrument Digital Interface)
876 'pattern' => '^MThd',
879 'mime_type' => 'audio/midi',
882 // MAC - audio - Monkey's Audio Compressor
884 'pattern' => '^MAC ',
886 'module' => 'monkey',
887 'mime_type' => 'audio/x-monkeys-audio',
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)
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',
900 // MOD - audio - MODule (Impulse Tracker)
902 'pattern' => '^IMPM',
906 'mime_type' => 'audio/it',
909 // MOD - audio - MODule (eXtended Module, various sub-formats)
911 'pattern' => '^Extended Module',
915 'mime_type' => 'audio/xm',
918 // MOD - audio - MODule (ScreamTracker)
920 'pattern' => '^.{44}SCRM',
924 'mime_type' => 'audio/s3m',
927 // MPC - audio - Musepack / MPEGplus
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])',
932 'mime_type' => 'audio/x-musepack',
935 // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS)
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]',
940 'mime_type' => 'audio/mpeg',
943 // OFR - audio - OptimFROG
945 'pattern' => '^(\\*RIFF|OFR)',
947 'module' => 'optimfrog',
948 'mime_type' => 'application/octet-stream',
951 // RKAU - audio - RKive AUdio compressor
956 'mime_type' => 'application/octet-stream',
959 // SHN - audio - Shorten
961 'pattern' => '^ajkg',
963 'module' => 'shorten',
964 'mime_type' => 'audio/xmms-shn',
965 'fail_id3' => 'ERROR',
966 'fail_ape' => 'ERROR',
969 // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org)
971 'pattern' => '^TTA', // could also be '^TTA(\\x01|\\x02|\\x03|2|1)'
974 'mime_type' => 'application/octet-stream',
977 // VOC - audio - Creative Voice (VOC)
979 'pattern' => '^Creative Voice File',
982 'mime_type' => 'audio/voc',
985 // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF)
987 'pattern' => '^TWIN',
990 'mime_type' => 'application/octet-stream',
993 // WV - audio - WavPack (v4.0+)
995 'pattern' => '^wvpk',
997 'module' => 'wavpack',
998 'mime_type' => 'application/octet-stream',
1002 // Audio-Video formats
1004 // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
1006 'pattern' => '^\\x30\\x26\\xB2\\x75\\x8E\\x66\\xCF\\x11\\xA6\\xD9\\x00\\xAA\\x00\\x62\\xCE\\x6C',
1007 'group' => 'audio-video',
1009 'mime_type' => 'video/x-ms-asf',
1010 'iconv_req' => false,
1013 // BINK - audio/video - Bink / Smacker
1015 'pattern' => '^(BIK|SMK)',
1016 'group' => 'audio-video',
1018 'mime_type' => 'application/octet-stream',
1021 // FLV - audio/video - FLash Video
1023 'pattern' => '^FLV[\\x01]',
1024 'group' => 'audio-video',
1026 'mime_type' => 'video/x-flv',
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
1037 // MPEG - audio/video - MPEG (Moving Pictures Experts Group)
1039 'pattern' => '^\\x00\\x00\\x01[\\xB3\\xBA]',
1040 'group' => 'audio-video',
1042 'mime_type' => 'video/mpeg',
1045 // NSV - audio/video - Nullsoft Streaming Video (NSV)
1047 'pattern' => '^NSV[sf]',
1048 'group' => 'audio-video',
1050 'mime_type' => 'application/octet-stream',
1053 // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
1055 'pattern' => '^OggS',
1058 'mime_type' => 'application/ogg',
1059 'fail_id3' => 'WARNING',
1060 'fail_ape' => 'WARNING',
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',
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)
1073 'pattern' => '^(RIFF|SDSS|FORM)',
1074 'group' => 'audio-video',
1076 'mime_type' => 'audio/wav',
1077 'fail_ape' => 'WARNING',
1080 // Real - audio/video - RealAudio, RealVideo
1082 'pattern' => '^\\.(RMF|ra)',
1083 'group' => 'audio-video',
1085 'mime_type' => 'audio/x-realaudio',
1088 // SWF - audio/video - ShockWave Flash
1090 'pattern' => '^(F|C)WS',
1091 'group' => 'audio-video',
1093 'mime_type' => 'application/x-shockwave-flash',
1096 // TS - audio/video - MPEG-2 Transport Stream
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',
1101 'mime_type' => 'video/MP2T',
1105 // Still-Image formats
1107 // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
1110 'group' => 'graphic',
1112 'mime_type' => 'image/bmp',
1113 'fail_id3' => 'ERROR',
1114 'fail_ape' => 'ERROR',
1117 // GIF - still image - Graphics Interchange Format
1119 'pattern' => '^GIF',
1120 'group' => 'graphic',
1122 'mime_type' => 'image/gif',
1123 'fail_id3' => 'ERROR',
1124 'fail_ape' => 'ERROR',
1127 // JPEG - still image - Joint Photographic Experts Group (JPEG)
1129 'pattern' => '^\\xFF\\xD8\\xFF',
1130 'group' => 'graphic',
1132 'mime_type' => 'image/jpeg',
1133 'fail_id3' => 'ERROR',
1134 'fail_ape' => 'ERROR',
1137 // PCD - still image - Kodak Photo CD
1139 'pattern' => '^.{2048}PCD_IPI\\x00',
1140 'group' => 'graphic',
1142 'mime_type' => 'image/x-photo-cd',
1143 'fail_id3' => 'ERROR',
1144 'fail_ape' => 'ERROR',
1148 // PNG - still image - Portable Network Graphics (PNG)
1150 'pattern' => '^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A',
1151 'group' => 'graphic',
1153 'mime_type' => 'image/png',
1154 'fail_id3' => 'ERROR',
1155 'fail_ape' => 'ERROR',
1159 // SVG - still image - Scalable Vector Graphics (SVG)
1161 'pattern' => '(<!DOCTYPE svg PUBLIC |xmlns="http://www\\.w3\\.org/2000/svg")',
1162 'group' => 'graphic',
1164 'mime_type' => 'image/svg+xml',
1165 'fail_id3' => 'ERROR',
1166 'fail_ape' => 'ERROR',
1170 // TIFF - still image - Tagged Information File Format (TIFF)
1172 'pattern' => '^(II\\x2A\\x00|MM\\x00\\x2A)',
1173 'group' => 'graphic',
1175 'mime_type' => 'image/tiff',
1176 'fail_id3' => 'ERROR',
1177 'fail_ape' => 'ERROR',
1181 // EFAX - still image - eFax (TIFF derivative)
1183 'pattern' => '^\\xDC\\xFE',
1184 'group' => 'graphic',
1186 'mime_type' => 'image/efax',
1187 'fail_id3' => 'ERROR',
1188 'fail_ape' => 'ERROR',
1194 // ISO - data - International Standards Organization (ISO) CD-ROM Image
1196 'pattern' => '^.{32769}CD001',
1199 'mime_type' => 'application/octet-stream',
1200 'fail_id3' => 'ERROR',
1201 'fail_ape' => 'ERROR',
1202 'iconv_req' => false,
1205 // RAR - data - RAR compressed data
1207 'pattern' => '^Rar\\!',
1208 'group' => 'archive',
1210 'mime_type' => 'application/octet-stream',
1211 'fail_id3' => 'ERROR',
1212 'fail_ape' => 'ERROR',
1215 // SZIP - audio/data - SZIP compressed data
1217 'pattern' => '^SZ\\x0A\\x04',
1218 'group' => 'archive',
1220 'mime_type' => 'application/octet-stream',
1221 'fail_id3' => 'ERROR',
1222 'fail_ape' => 'ERROR',
1225 // TAR - data - TAR compressed data
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',
1230 'mime_type' => 'application/x-tar',
1231 'fail_id3' => 'ERROR',
1232 'fail_ape' => 'ERROR',
1235 // GZIP - data - GZIP compressed data
1237 'pattern' => '^\\x1F\\x8B\\x08',
1238 'group' => 'archive',
1240 'mime_type' => 'application/gzip',
1241 'fail_id3' => 'ERROR',
1242 'fail_ape' => 'ERROR',
1245 // ZIP - data - ZIP compressed data
1247 'pattern' => '^PK\\x03\\x04',
1248 'group' => 'archive',
1250 'mime_type' => 'application/zip',
1251 'fail_id3' => 'ERROR',
1252 'fail_ape' => 'ERROR',
1256 // Misc other formats
1258 // PAR2 - data - Parity Volume Set Specification 2.0
1260 'pattern' => '^PAR2\\x00PKT',
1263 'mime_type' => 'application/octet-stream',
1264 'fail_id3' => 'ERROR',
1265 'fail_ape' => 'ERROR',
1268 // PDF - data - Portable Document Format
1270 'pattern' => '^\\x25PDF',
1273 'mime_type' => 'application/pdf',
1274 'fail_id3' => 'ERROR',
1275 'fail_ape' => 'ERROR',
1278 // MSOFFICE - data - ZIP compressed data
1279 'msoffice' => array(
1280 'pattern' => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
1282 'module' => 'msoffice',
1283 'mime_type' => 'application/octet-stream',
1284 'fail_id3' => 'ERROR',
1285 'fail_ape' => 'ERROR',
1288 // CUE - data - CUEsheet (index to single-file disc images)
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
1293 'mime_type' => 'application/octet-stream',
1299 return $format_info;
1303 * @param string $filedata
1304 * @param string $filename
1306 * @return mixed|false
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
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';
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';
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';
1346 * Converts array to $encoding charset from $this->encoding.
1348 * @param array $array
1349 * @param string $encoding
1351 public function CharConvert(&$array, $encoding) {
1353 // identical encoding - end here
1354 if ($encoding == $this->encoding
) {
1359 foreach ($array as $key => $value) {
1362 if (is_array($value)) {
1363 $this->CharConvert($array[$key], $encoding);
1367 elseif (is_string($value)) {
1368 $array[$key] = trim(getid3_lib
::iconv_fallback($encoding, $this->encoding
, $value));
1376 public function HandleAllTags() {
1378 // key name => array (tag name, character encoding)
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'),
1405 // loop through comments array
1406 foreach ($tags as $comment_name => $tagname_encoding_array) {
1407 list($tag_name, $encoding) = $tagname_encoding_array;
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;
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!
1422 if (!is_numeric($key)) {
1423 $this->info
['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
1425 $this->info
['tags'][trim($tag_name)][trim($tag_key)][] = $value;
1429 if ($tag_key == 'picture') {
1430 unset($this->info
[$comment_name]['comments'][$tag_key]);
1434 if (!isset($this->info
['tags'][$tag_name])) {
1435 // comments are set but contain nothing but empty strings, so skip
1439 $this->CharConvert($this->info
['tags'][$tag_name], $this->info
[$comment_name]['encoding']); // only copy gets converted!
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']);
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]);
1464 if (isset($this->info
['tags_html'][$tagtype][$tagname][$key])) {
1465 unset($this->info
['tags_html'][$tagtype][$tagname][$key]);
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']);
1476 if (empty($this->info
[$unset_key][$tagtype])) {
1477 unset($this->info
[$unset_key][$tagtype]);
1479 if (empty($this->info
[$unset_key])) {
1480 unset($this->info
[$unset_key]);
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']);
1487 if (empty($this->info
[$tagtype]['comments'])) {
1488 unset($this->info
[$tagtype]['comments']);
1490 if (empty($this->info
[$tagtype])) {
1491 unset($this->info
[$tagtype]);
1499 * @param string $algorithm
1501 * @return array|bool
1503 public function getHashdata($algorithm) {
1504 switch ($algorithm) {
1510 return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
1514 if (!empty($this->info
['fileformat']) && !empty($this->info
['dataformat']) && ($this->info
['fileformat'] == 'ogg') && ($this->info
['audio']['dataformat'] == 'vorbis')) {
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.
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.
1534 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
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;
1541 // Prevent user from aborting script
1542 $old_abort = ignore_user_abort(true);
1544 // Create empty file
1545 $empty = tempnam(GETID3_TEMP_DIR
, 'getID3');
1548 // Use vorbiscomment to make temp file without comments
1549 $temp = tempnam(GETID3_TEMP_DIR
, 'getID3');
1550 $file = $this->info
['filenamepath'];
1552 if (GETID3_OS_ISWINDOWS
) {
1554 if (file_exists(GETID3_HELPERAPPSDIR
.'vorbiscomment.exe')) {
1556 $commandline = '"'.GETID3_HELPERAPPSDIR
.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
1557 $VorbisCommentError = `
$commandline`
;
1561 $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR
;
1567 $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
1568 $VorbisCommentError = `
$commandline`
;
1572 if (!empty($VorbisCommentError)) {
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;
1579 // Get hash of newly created file
1580 switch ($algorithm) {
1582 $this->info
[$algorithm.'_data'] = md5_file($temp);
1586 $this->info
[$algorithm.'_data'] = sha1_file($temp);
1595 // Reset abort setting
1596 ignore_user_abort($old_abort);
1602 if (!empty($this->info
['avdataoffset']) ||
(isset($this->info
['avdataend']) && ($this->info
['avdataend'] < $this->info
['filesize']))) {
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);
1609 // get hash from whole file
1610 switch ($algorithm) {
1612 $this->info
[$algorithm.'_data'] = md5_file($this->info
['filenamepath']);
1616 $this->info
[$algorithm.'_data'] = sha1_file($this->info
['filenamepath']);
1625 public function ChannelsBitratePlaytimeCalculations() {
1627 // set channelmode on audio
1628 if (!empty($this->info
['audio']['channelmode']) ||
!isset($this->info
['audio']['channels'])) {
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';
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;
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']);
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'];
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'];
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'];
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'])) {
1676 $this->info
['audio']['bitrate'] = $this->info
['bitrate'];
1677 } elseif (isset($this->info
['video']['resolution_x']) && empty($this->info
['audio']['dataformat'])) {
1679 $this->info
['video']['bitrate'] = $this->info
['bitrate'];
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']);
1692 public function CalculateCompressionRatioVideo() {
1693 if (empty($this->info
['video'])) {
1696 if (empty($this->info
['video']['resolution_x']) ||
empty($this->info
['video']['resolution_y'])) {
1699 if (empty($this->info
['video']['bits_per_sample'])) {
1703 switch ($this->info
['video']['dataformat']) {
1711 $PlaytimeSeconds = 1;
1712 $BitrateCompressed = $this->info
['filesize'] * 8;
1716 if (!empty($this->info
['video']['frame_rate'])) {
1717 $FrameRate = $this->info
['video']['frame_rate'];
1721 if (!empty($this->info
['playtime_seconds'])) {
1722 $PlaytimeSeconds = $this->info
['playtime_seconds'];
1726 if (!empty($this->info
['video']['bitrate'])) {
1727 $BitrateCompressed = $this->info
['video']['bitrate'];
1733 $BitrateUncompressed = $this->info
['video']['resolution_x'] * $this->info
['video']['resolution_y'] * $this->info
['video']['bits_per_sample'] * $FrameRate;
1735 $this->info
['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
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'])) {
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));
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));
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;
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'];
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'];
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']);
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']);
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;
1800 * @return string|bool
1802 public function getid3_tempnam() {
1803 return tempnam($this->tempdir
, 'gI3');
1807 * @param string $name
1811 * @throws getid3_exception
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.');
1818 include_once(GETID3_INCLUDEPATH
.'module.'.$name.'.php');
1823 * @param string $filename
1827 public static function is_writable ($filename) {
1828 $ret = is_writable($filename);
1831 $perms = fileperms($filename);
1832 $ret = ($perms & 0x0080) ||
($perms & 0x0010) ||
($perms & 0x0002);
1841 abstract class getid3_handler
1847 protected $getid3; // pointer
1850 * Analyzing filepointer or string.
1854 protected $data_string_flag = false;
1857 * String to analyze.
1861 protected $data_string = '';
1864 * Seek position in string.
1868 protected $data_string_position = 0;
1875 protected $data_string_length = 0;
1880 private $dependency_to;
1883 * getid3_handler constructor.
1885 * @param getID3 $getid3
1886 * @param string $call_module
1888 public function __construct(getID3
$getid3, $call_module=null) {
1889 $this->getid3
= $getid3;
1892 $this->dependency_to
= str_replace('getid3_', '', $call_module);
1897 * Analyze from file pointer.
1901 abstract public function Analyze();
1904 * Analyze from string instead.
1906 * @param string $string
1908 public function AnalyzeString($string) {
1909 // Enter string mode
1910 $this->setStringMode($string);
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
1918 $this->getid3
->info
['avdataoffset'] = 0;
1919 $this->getid3
->info
['avdataend'] = $this->getid3
->info
['filesize'] = $this->data_string_length
;
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;
1930 $this->data_string_flag
= false;
1934 * @param string $string
1936 public function setStringMode($string) {
1937 $this->data_string_flag
= true;
1938 $this->data_string
= $string;
1939 $this->data_string_length
= strlen($string);
1945 protected function ftell() {
1946 if ($this->data_string_flag
) {
1947 return $this->data_string_position
;
1949 return ftell($this->getid3
->fp
);
1955 * @return string|false
1957 * @throws getid3_exception
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);
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);
1969 //return fread($this->getid3->fp, $bytes);
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."
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);
1982 $part = fread($this->getid3
->fp
, $bytes);
1983 $partLength = strlen($part);
1984 $bytes -= $partLength;
1986 } while (($bytes > 0) && ($partLength > 0));
1992 * @param int $whence
1996 * @throws getid3_exception
1998 protected function fseek($bytes, $whence=SEEK_SET
) {
1999 if ($this->data_string_flag
) {
2002 $this->data_string_position
= $bytes;
2006 $this->data_string_position +
= $bytes;
2010 $this->data_string_position
= $this->data_string_length +
$bytes;
2016 if ($whence == SEEK_CUR
) {
2017 $pos = $this->ftell() +
$bytes;
2018 } elseif ($whence == SEEK_END
) {
2019 $pos = $this->getid3
->info
['filesize'] +
$bytes;
2021 if (!getid3_lib
::intValueSupported($pos)) {
2022 throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
2025 return fseek($this->getid3
->fp
, $bytes, $whence);
2031 protected function feof() {
2032 if ($this->data_string_flag
) {
2033 return $this->data_string_position
>= $this->data_string_length
;
2035 return feof($this->getid3
->fp
);
2039 * @param string $module
2043 final protected function isDependencyFor($module) {
2044 return $this->dependency_to
== $module;
2048 * @param string $text
2052 protected function error($text) {
2053 $this->getid3
->info
['error'][] = $text;
2059 * @param string $text
2063 protected function warning($text) {
2064 return $this->getid3
->warning($text);
2068 * @param string $text
2070 protected function notice($text) {
2071 // does nothing for now
2075 * @param string $name
2076 * @param int $offset
2077 * @param int $length
2078 * @param string $image_mime
2080 * @return string|null
2083 * @throws getid3_exception
2085 public function saveAttachment($name, $offset, $length, $image_mime=null) {
2088 // do not extract at all
2089 if ($this->getid3
->option_save_attachments
=== getID3
::ATTACHMENTS_NONE
) {
2091 $attachment = null; // do not set any
2093 // extract to return array
2094 } elseif ($this->getid3
->option_save_attachments
=== getID3
::ATTACHMENTS_INLINE
) {
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');
2102 // assume directory path is given
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');
2110 $dest = $dir.DIRECTORY_SEPARATOR
.$name.($image_mime ?
'.'.getid3_lib
::ImageExtFromMime($image_mime) : '');
2113 if (($fp_dest = fopen($dest, 'wb')) == false) {
2114 throw new Exception('failed to create file '.$dest);
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');
2125 $bytesleft -= $byteswritten;
2129 $attachment = $dest;
2133 } catch (Exception
$e) {
2135 // close and remove dest file if created
2136 if (isset($fp_dest) && is_resource($fp_dest)) {
2140 if (isset($dest) && file_exists($dest)) {
2144 // do not set any is case of error
2146 $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage());
2150 // seek to the end of attachment
2151 $this->fseek($offset +
$length);
2159 class getid3_exception
extends Exception