2 /////////////////////////////////////////////////////////////////
3 /// getID3() by James Heinrich <info@getid3.org> //
4 // available at https://github.com/JamesHeinrich/getID3 //
5 // or https://www.getid3.org //
6 // or http://getid3.sourceforge.net //
8 // Please see readme.txt for more information //
10 /////////////////////////////////////////////////////////////////
12 // define a constant rather than looking up every time it is needed
13 if (!defined('GETID3_OS_ISWINDOWS')) {
14 define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS
, 'WIN') === 0));
16 // Get base path of getID3() - ONCE
17 if (!defined('GETID3_INCLUDEPATH')) {
18 define('GETID3_INCLUDEPATH', dirname(__FILE__
).DIRECTORY_SEPARATOR
);
20 // Workaround Bug #39923 (https://bugs.php.net/bug.php?id=39923)
21 if (!defined('IMG_JPG') && defined('IMAGETYPE_JPEG')) {
22 define('IMG_JPG', IMAGETYPE_JPEG
);
24 if (!defined('ENT_SUBSTITUTE')) { // PHP5.3 adds ENT_IGNORE, PHP5.4 adds ENT_SUBSTITUTE
25 define('ENT_SUBSTITUTE', (defined('ENT_IGNORE') ? ENT_IGNORE
: 8));
29 https://www.getid3.org/phpBB3/viewtopic.php?t=2114
30 If you are running into a the problem where filenames with special characters are being handled
31 incorrectly by external helper programs (e.g. metaflac), notably with the special characters removed,
32 and you are passing in the filename in UTF8 (typically via a HTML form), try uncommenting this line:
34 //setlocale(LC_CTYPE, 'en_US.UTF-8');
36 // attempt to define temp dir as something flexible but reliable
37 $temp_dir = ini_get('upload_tmp_dir');
38 if ($temp_dir && (!is_dir($temp_dir) ||
!is_readable($temp_dir))) {
41 if (!$temp_dir && function_exists('sys_get_temp_dir')) { // sys_get_temp_dir added in PHP v5.2.1
42 // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts
43 $temp_dir = sys_get_temp_dir();
45 $temp_dir = @realpath
($temp_dir); // see https://github.com/JamesHeinrich/getID3/pull/10
46 $open_basedir = ini_get('open_basedir');
48 // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/"
49 $temp_dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR
, $temp_dir);
50 $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR
, $open_basedir);
51 if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR
) {
52 $temp_dir .= DIRECTORY_SEPARATOR
;
54 $found_valid_tempdir = false;
55 $open_basedirs = explode(PATH_SEPARATOR
, $open_basedir);
56 foreach ($open_basedirs as $basedir) {
57 if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR
) {
58 $basedir .= DIRECTORY_SEPARATOR
;
60 if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) {
61 $found_valid_tempdir = true;
65 if (!$found_valid_tempdir) {
68 unset($open_basedirs, $found_valid_tempdir, $basedir);
71 $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
73 // $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system
74 if (!defined('GETID3_TEMP_DIR')) {
75 define('GETID3_TEMP_DIR', $temp_dir);
77 unset($open_basedir, $temp_dir);
89 * CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE
93 public $encoding = 'UTF-8';
96 * Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252'
100 public $encoding_id3v1 = 'ISO-8859-1';
103 * ID3v1 should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'Windows-1251' or 'KOI8-R'. If true attempt to detect these encodings, but may return incorrect values for some tags actually in ISO-8859-1 encoding
107 public $encoding_id3v1_autodetect = false;
110 * Optional tag checks - disable for speed.
114 * Read and process ID3v1 tags
118 public $option_tag_id3v1 = true;
121 * Read and process ID3v2 tags
125 public $option_tag_id3v2 = true;
128 * Read and process Lyrics3 tags
132 public $option_tag_lyrics3 = true;
135 * Read and process APE tags
139 public $option_tag_apetag = true;
142 * Copy tags to root key 'tags' and encode to $this->encoding
146 public $option_tags_process = true;
149 * Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
153 public $option_tags_html = true;
156 * Optional tag/comment calculations
160 * Calculate additional info such as bitrate, channelmode etc
164 public $option_extra_info = true;
167 * Optional handling of embedded attachments (e.g. images)
171 * Defaults to true (ATTACHMENTS_INLINE) for backward compatibility
175 public $option_save_attachments = true;
178 * Optional calculations
182 * Get MD5 sum of data part - slow
186 public $option_md5_data = false;
189 * Use MD5 of source file if availble - only FLAC and OptimFROG
193 public $option_md5_data_source = false;
196 * Get SHA1 sum of data part - slow
200 public $option_sha1_data = false;
203 * Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on
208 public $option_max_2gb_check;
211 * Read buffer size in bytes
215 public $option_fread_buffer_size = 32768;
220 * Filename of file being analysed.
227 * Filepointer to file being analysed.
243 public $tempdir = GETID3_TEMP_DIR
;
248 public $memory_limit = 0;
253 protected $startup_error = '';
258 protected $startup_warning = '';
260 const VERSION
= '1.9.20-202006061653';
261 const FREAD_BUFFER_SIZE
= 32768;
263 const ATTACHMENTS_NONE
= false;
264 const ATTACHMENTS_INLINE
= true;
266 public function __construct() {
268 // Check for PHP version
269 $required_php_version = '5.3.0';
270 if (version_compare(PHP_VERSION
, $required_php_version, '<')) {
271 $this->startup_error
.= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION
."\n";
276 $memoryLimit = ini_get('memory_limit');
277 if (preg_match('#([0-9]+) ?M#i', $memoryLimit, $matches)) {
278 // could be stored as "16M" rather than 16777216 for example
279 $memoryLimit = $matches[1] * 1048576;
280 } elseif (preg_match('#([0-9]+) ?G#i', $memoryLimit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
281 // could be stored as "2G" rather than 2147483648 for example
282 $memoryLimit = $matches[1] * 1073741824;
284 $this->memory_limit
= $memoryLimit;
286 if ($this->memory_limit
<= 0) {
287 // memory limits probably disabled
288 } elseif ($this->memory_limit
<= 4194304) {
289 $this->startup_error
.= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'."\n";
290 } elseif ($this->memory_limit
<= 12582912) {
291 $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";
294 // Check safe_mode off
295 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
296 $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
299 if (($mbstring_func_overload = (int) ini_get('mbstring.func_overload')) && ($mbstring_func_overload & 0x02)) {
300 // http://php.net/manual/en/mbstring.overload.php
301 // "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"
302 // 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.
303 $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";
306 // check for magic quotes in PHP < 7.4.0 (when these functions became deprecated)
307 if (version_compare(PHP_VERSION
, '7.4.0', '<')) {
308 // Check for magic_quotes_runtime
309 if (function_exists('get_magic_quotes_runtime')) {
310 if (get_magic_quotes_runtime()) {
311 $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";
314 // Check for magic_quotes_gpc
315 if (function_exists('get_magic_quotes_gpc')) {
316 if (get_magic_quotes_gpc()) {
317 $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";
322 // Load support library
323 if (!include_once(GETID3_INCLUDEPATH
.'getid3.lib.php')) {
324 $this->startup_error
.= 'getid3.lib.php is missing or corrupt'."\n";
327 if ($this->option_max_2gb_check
=== null) {
328 $this->option_max_2gb_check
= (PHP_INT_MAX
<= 2147483647);
332 // Needed for Windows only:
333 // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC
334 // as well as other helper functions such as head, etc
335 // This path cannot contain spaces, but the below code will attempt to get the
336 // 8.3-equivalent path automatically
337 // IMPORTANT: This path must include the trailing slash
338 if (GETID3_OS_ISWINDOWS
&& !defined('GETID3_HELPERAPPSDIR')) {
340 $helperappsdir = GETID3_INCLUDEPATH
.'..'.DIRECTORY_SEPARATOR
.'helperapps'; // must not have any space in this path
342 if (!is_dir($helperappsdir)) {
343 $this->startup_warning
.= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'."\n";
344 } elseif (strpos(realpath($helperappsdir), ' ') !== false) {
345 $DirPieces = explode(DIRECTORY_SEPARATOR
, realpath($helperappsdir));
346 $path_so_far = array();
347 foreach ($DirPieces as $key => $value) {
348 if (strpos($value, ' ') !== false) {
349 if (!empty($path_so_far)) {
350 $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR
, $path_so_far));
351 $dir_listing = `
$commandline`
;
352 $lines = explode("\n", $dir_listing);
353 foreach ($lines as $line) {
355 if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) {
356 list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches;
357 if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) {
363 $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";
366 $path_so_far[] = $value;
368 $helperappsdir = implode(DIRECTORY_SEPARATOR
, $path_so_far);
370 define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR
);
373 if (!empty($this->startup_error
)) {
374 echo $this->startup_error
;
375 throw new getid3_exception($this->startup_error
);
382 public function version() {
383 return self
::VERSION
;
389 public function fread_buffer_size() {
390 return $this->option_fread_buffer_size
;
394 * @param array $optArray
398 public function setOption($optArray) {
399 if (!is_array($optArray) ||
empty($optArray)) {
402 foreach ($optArray as $opt => $val) {
403 if (isset($this->$opt) === false) {
412 * @param string $filename
413 * @param int $filesize
414 * @param resource $fp
418 * @throws getid3_exception
420 public function openfile($filename, $filesize=null, $fp=null) {
422 if (!empty($this->startup_error
)) {
423 throw new getid3_exception($this->startup_error
);
425 if (!empty($this->startup_warning
)) {
426 foreach (explode("\n", $this->startup_warning
) as $startup_warning) {
427 $this->warning($startup_warning);
431 // init result array and set parameters
432 $this->filename
= $filename;
433 $this->info
= array();
434 $this->info
['GETID3_VERSION'] = $this->version();
435 $this->info
['php_memory_limit'] = (($this->memory_limit
> 0) ?
$this->memory_limit
: false);
437 // remote files not supported
438 if (preg_match('#^(ht|f)tp://#', $filename)) {
439 throw new getid3_exception('Remote files are not supported - please copy the file locally first');
442 $filename = str_replace('/', DIRECTORY_SEPARATOR
, $filename);
443 //$filename = preg_replace('#(?<!gs:)('.preg_quote(DIRECTORY_SEPARATOR).'{2,})#', DIRECTORY_SEPARATOR, $filename);
446 //if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see https://www.getid3.org/phpBB3/viewtopic.php?t=1720
447 if (($fp != null) && ((get_resource_type($fp) == 'file') ||
(get_resource_type($fp) == 'stream'))) {
449 } elseif ((is_readable($filename) ||
file_exists($filename)) && is_file($filename) && ($this->fp
= fopen($filename, 'rb'))) {
452 $errormessagelist = array();
453 if (!is_readable($filename)) {
454 $errormessagelist[] = '!is_readable';
456 if (!is_file($filename)) {
457 $errormessagelist[] = '!is_file';
459 if (!file_exists($filename)) {
460 $errormessagelist[] = '!file_exists';
462 if (empty($errormessagelist)) {
463 $errormessagelist[] = 'fopen failed';
465 throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')');
468 $this->info
['filesize'] = (!is_null($filesize) ?
$filesize : filesize($filename));
469 // set redundant parameters - might be needed in some include file
470 // filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion
471 $filename = str_replace('\\', '/', $filename);
472 $this->info
['filepath'] = str_replace('\\', '/', realpath(dirname($filename)));
473 $this->info
['filename'] = getid3_lib
::mb_basename($filename);
474 $this->info
['filenamepath'] = $this->info
['filepath'].'/'.$this->info
['filename'];
476 // set more parameters
477 $this->info
['avdataoffset'] = 0;
478 $this->info
['avdataend'] = $this->info
['filesize'];
479 $this->info
['fileformat'] = ''; // filled in later
480 $this->info
['audio']['dataformat'] = ''; // filled in later, unset if not used
481 $this->info
['video']['dataformat'] = ''; // filled in later, unset if not used
482 $this->info
['tags'] = array(); // filled in later, unset if not used
483 $this->info
['error'] = array(); // filled in later, unset if not used
484 $this->info
['warning'] = array(); // filled in later, unset if not used
485 $this->info
['comments'] = array(); // filled in later, unset if not used
486 $this->info
['encoding'] = $this->encoding
; // required by id3v2 and iso modules - can be unset at the end if desired
488 // option_max_2gb_check
489 if ($this->option_max_2gb_check
) {
490 // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB)
491 // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize
492 // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer
493 $fseek = fseek($this->fp
, 0, SEEK_END
);
494 if (($fseek < 0) ||
(($this->info
['filesize'] != 0) && (ftell($this->fp
) == 0)) ||
495 ($this->info
['filesize'] < 0) ||
496 (ftell($this->fp
) < 0)) {
497 $real_filesize = getid3_lib
::getFileSizeSyscall($this->info
['filenamepath']);
499 if ($real_filesize === false) {
500 unset($this->info
['filesize']);
502 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.');
503 } elseif (getid3_lib
::intValueSupported($real_filesize)) {
504 unset($this->info
['filesize']);
506 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');
508 $this->info
['filesize'] = $real_filesize;
509 $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.');
515 } catch (Exception
$e) {
516 $this->error($e->getMessage());
524 * @param string $filename
525 * @param int $filesize
526 * @param string $original_filename
527 * @param resource $fp
531 public function analyze($filename, $filesize=null, $original_filename='', $fp=null) {
533 if (!$this->openfile($filename, $filesize, $fp)) {
538 foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
539 $option_tag = 'option_tag_'.$tag_name;
540 if ($this->$option_tag) {
541 $this->include_module('tag.'.$tag_name);
543 $tag_class = 'getid3_'.$tag_name;
544 $tag = new $tag_class($this);
547 catch (getid3_exception
$e) {
552 if (isset($this->info
['id3v2']['tag_offset_start'])) {
553 $this->info
['avdataoffset'] = max($this->info
['avdataoffset'], $this->info
['id3v2']['tag_offset_end']);
555 foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
556 if (isset($this->info
[$tag_key]['tag_offset_start'])) {
557 $this->info
['avdataend'] = min($this->info
['avdataend'], $this->info
[$tag_key]['tag_offset_start']);
561 // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
562 if (!$this->option_tag_id3v2
) {
564 $header = fread($this->fp
, 10);
565 if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) {
566 $this->info
['id3v2']['header'] = true;
567 $this->info
['id3v2']['majorversion'] = ord($header[3]);
568 $this->info
['id3v2']['minorversion'] = ord($header[4]);
569 $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
573 // read 32 kb file data
574 fseek($this->fp
, $this->info
['avdataoffset']);
575 $formattest = fread($this->fp
, 32774);
578 $determined_format = $this->GetFileFormat($formattest, ($original_filename ?
$original_filename : $filename));
580 // unable to determine file format
581 if (!$determined_format) {
583 return $this->error('unable to determine file format');
586 // check for illegal ID3 tags
587 if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info
['tags']) ||
in_array('id3v2', $this->info
['tags']))) {
588 if ($determined_format['fail_id3'] === 'ERROR') {
590 return $this->error('ID3 tags not allowed on this file type.');
591 } elseif ($determined_format['fail_id3'] === 'WARNING') {
592 $this->warning('ID3 tags not allowed on this file type.');
596 // check for illegal APE tags
597 if (isset($determined_format['fail_ape']) && in_array('ape', $this->info
['tags'])) {
598 if ($determined_format['fail_ape'] === 'ERROR') {
600 return $this->error('APE tags not allowed on this file type.');
601 } elseif ($determined_format['fail_ape'] === 'WARNING') {
602 $this->warning('APE tags not allowed on this file type.');
607 $this->info
['mime_type'] = $determined_format['mime_type'];
609 // supported format signature pattern detected, but module deleted
610 if (!file_exists(GETID3_INCLUDEPATH
.$determined_format['include'])) {
612 return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
615 // module requires mb_convert_encoding/iconv support
616 // Check encoding/iconv support
617 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'))) {
618 $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. ';
619 if (GETID3_OS_ISWINDOWS
) {
620 $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';
622 $errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch';
624 return $this->error($errormessage);
628 include_once(GETID3_INCLUDEPATH
.$determined_format['include']);
630 // instantiate module class
631 $class_name = 'getid3_'.$determined_format['module'];
632 if (!class_exists($class_name)) {
633 return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
635 $class = new $class_name($this);
642 // process all tags - copy to 'tags' and convert charsets
643 if ($this->option_tags_process
) {
644 $this->HandleAllTags();
647 // perform more calculations
648 if ($this->option_extra_info
) {
649 $this->ChannelsBitratePlaytimeCalculations();
650 $this->CalculateCompressionRatioVideo();
651 $this->CalculateCompressionRatioAudio();
652 $this->CalculateReplayGain();
653 $this->ProcessAudioStreams();
656 // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
657 if ($this->option_md5_data
) {
658 // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too
659 if (!$this->option_md5_data_source ||
empty($this->info
['md5_data_source'])) {
660 $this->getHashdata('md5');
664 // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
665 if ($this->option_sha1_data
) {
666 $this->getHashdata('sha1');
669 // remove undesired keys
672 } catch (Exception
$e) {
673 $this->error('Caught exception: '.$e->getMessage());
684 * @param string $message
688 public function error($message) {
690 if (!isset($this->info
['error'])) {
691 $this->info
['error'] = array();
693 $this->info
['error'][] = $message;
701 * @param string $message
705 public function warning($message) {
706 $this->info
['warning'][] = $message;
714 private function CleanUp() {
716 // remove possible empty keys
717 $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
718 foreach ($AVpossibleEmptyKeys as $dummy => $key) {
719 if (empty($this->info
['audio'][$key]) && isset($this->info
['audio'][$key])) {
720 unset($this->info
['audio'][$key]);
722 if (empty($this->info
['video'][$key]) && isset($this->info
['video'][$key])) {
723 unset($this->info
['video'][$key]);
727 // remove empty root keys
728 if (!empty($this->info
)) {
729 foreach ($this->info
as $key => $value) {
730 if (empty($this->info
[$key]) && ($this->info
[$key] !== 0) && ($this->info
[$key] !== '0')) {
731 unset($this->info
[$key]);
736 // remove meaningless entries from unknown-format files
737 if (empty($this->info
['fileformat'])) {
738 if (isset($this->info
['avdataoffset'])) {
739 unset($this->info
['avdataoffset']);
741 if (isset($this->info
['avdataend'])) {
742 unset($this->info
['avdataend']);
746 // remove possible duplicated identical entries
747 if (!empty($this->info
['error'])) {
748 $this->info
['error'] = array_values(array_unique($this->info
['error']));
750 if (!empty($this->info
['warning'])) {
751 $this->info
['warning'] = array_values(array_unique($this->info
['warning']));
754 // remove "global variable" type keys
755 unset($this->info
['php_memory_limit']);
761 * Return array containing information about all supported formats.
765 public function GetFileFormatArray() {
766 static $format_info = array();
767 if (empty($format_info)) {
768 $format_info = array(
772 // AC-3 - audio - Dolby AC-3 / Dolby Digital
774 'pattern' => '^\\x0B\\x77',
777 'mime_type' => 'audio/ac3',
780 // AAC - audio - Advanced Audio Coding (AAC) - ADIF format
782 'pattern' => '^ADIF',
785 'mime_type' => 'audio/aac',
786 'fail_ape' => 'WARNING',
790 // AA - audio - Audible Audiobook
792 'pattern' => '^.{4}\\x57\\x90\\x75\\x36',
795 'mime_type' => 'audio/audible',
798 // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
800 'pattern' => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]',
803 'mime_type' => 'audio/aac',
804 'fail_ape' => 'WARNING',
808 // AU - audio - NeXT/Sun AUdio (AU)
810 'pattern' => '^\\.snd',
813 'mime_type' => 'audio/basic',
816 // AMR - audio - Adaptive Multi Rate
818 'pattern' => '^\\x23\\x21AMR\\x0A', // #!AMR[0A]
821 'mime_type' => 'audio/amr',
824 // AVR - audio - Audio Visual Research
826 'pattern' => '^2BIT',
829 'mime_type' => 'application/octet-stream',
832 // BONK - audio - Bonk v0.9+
834 'pattern' => '^\\x00(BONK|INFO|META| ID3)',
837 'mime_type' => 'audio/xmms-bonk',
840 // DSF - audio - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital
842 'pattern' => '^DSD ', // including trailing space: 44 53 44 20
845 'mime_type' => 'audio/dsd',
848 // DSS - audio - Digital Speech Standard
850 'pattern' => '^[\\x02-\\x08]ds[s2]',
853 'mime_type' => 'application/octet-stream',
856 // DSDIFF - audio - Direct Stream Digital Interchange File Format
858 'pattern' => '^FRM8',
860 'module' => 'dsdiff',
861 'mime_type' => 'audio/dsd',
864 // DTS - audio - Dolby Theatre System
866 'pattern' => '^\\x7F\\xFE\\x80\\x01',
869 'mime_type' => 'audio/dts',
872 // FLAC - audio - Free Lossless Audio Codec
874 'pattern' => '^fLaC',
877 'mime_type' => 'audio/flac',
880 // LA - audio - Lossless Audio (LA)
882 'pattern' => '^LA0[2-4]',
885 'mime_type' => 'application/octet-stream',
888 // LPAC - audio - Lossless Predictive Audio Compression (LPAC)
890 'pattern' => '^LPAC',
893 'mime_type' => 'application/octet-stream',
896 // MIDI - audio - MIDI (Musical Instrument Digital Interface)
898 'pattern' => '^MThd',
901 'mime_type' => 'audio/midi',
904 // MAC - audio - Monkey's Audio Compressor
906 'pattern' => '^MAC ',
908 'module' => 'monkey',
909 'mime_type' => 'audio/x-monkeys-audio',
912 // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available
913 // // MOD - audio - MODule (assorted sub-formats)
915 // 'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)',
916 // 'group' => 'audio',
917 // 'module' => 'mod',
918 // 'option' => 'mod',
919 // 'mime_type' => 'audio/mod',
922 // MOD - audio - MODule (Impulse Tracker)
924 'pattern' => '^IMPM',
928 'mime_type' => 'audio/it',
931 // MOD - audio - MODule (eXtended Module, various sub-formats)
933 'pattern' => '^Extended Module',
937 'mime_type' => 'audio/xm',
940 // MOD - audio - MODule (ScreamTracker)
942 'pattern' => '^.{44}SCRM',
946 'mime_type' => 'audio/s3m',
949 // MPC - audio - Musepack / MPEGplus
951 '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])',
954 'mime_type' => 'audio/x-musepack',
957 // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS)
959 '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]',
962 'mime_type' => 'audio/mpeg',
965 // OFR - audio - OptimFROG
967 'pattern' => '^(\\*RIFF|OFR)',
969 'module' => 'optimfrog',
970 'mime_type' => 'application/octet-stream',
973 // RKAU - audio - RKive AUdio compressor
978 'mime_type' => 'application/octet-stream',
981 // SHN - audio - Shorten
983 'pattern' => '^ajkg',
985 'module' => 'shorten',
986 'mime_type' => 'audio/xmms-shn',
987 'fail_id3' => 'ERROR',
988 'fail_ape' => 'ERROR',
991 // TAK - audio - Tom's lossless Audio Kompressor
993 'pattern' => '^tBaK',
996 'mime_type' => 'application/octet-stream',
999 // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org)
1001 'pattern' => '^TTA', // could also be '^TTA(\\x01|\\x02|\\x03|2|1)'
1004 'mime_type' => 'application/octet-stream',
1007 // VOC - audio - Creative Voice (VOC)
1009 'pattern' => '^Creative Voice File',
1012 'mime_type' => 'audio/voc',
1015 // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF)
1017 'pattern' => '^TWIN',
1020 'mime_type' => 'application/octet-stream',
1023 // WV - audio - WavPack (v4.0+)
1025 'pattern' => '^wvpk',
1027 'module' => 'wavpack',
1028 'mime_type' => 'application/octet-stream',
1032 // Audio-Video formats
1034 // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
1036 'pattern' => '^\\x30\\x26\\xB2\\x75\\x8E\\x66\\xCF\\x11\\xA6\\xD9\\x00\\xAA\\x00\\x62\\xCE\\x6C',
1037 'group' => 'audio-video',
1039 'mime_type' => 'video/x-ms-asf',
1040 'iconv_req' => false,
1043 // BINK - audio/video - Bink / Smacker
1045 'pattern' => '^(BIK|SMK)',
1046 'group' => 'audio-video',
1048 'mime_type' => 'application/octet-stream',
1051 // FLV - audio/video - FLash Video
1053 'pattern' => '^FLV[\\x01]',
1054 'group' => 'audio-video',
1056 'mime_type' => 'video/x-flv',
1059 // IVF - audio/video - IVF
1061 'pattern' => '^DKIF',
1062 'group' => 'audio-video',
1064 'mime_type' => 'video/x-ivf',
1067 // MKAV - audio/video - Mastroka
1068 'matroska' => array(
1069 'pattern' => '^\\x1A\\x45\\xDF\\xA3',
1070 'group' => 'audio-video',
1071 'module' => 'matroska',
1072 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
1075 // MPEG - audio/video - MPEG (Moving Pictures Experts Group)
1077 'pattern' => '^\\x00\\x00\\x01[\\xB3\\xBA]',
1078 'group' => 'audio-video',
1080 'mime_type' => 'video/mpeg',
1083 // NSV - audio/video - Nullsoft Streaming Video (NSV)
1085 'pattern' => '^NSV[sf]',
1086 'group' => 'audio-video',
1088 'mime_type' => 'application/octet-stream',
1091 // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
1093 'pattern' => '^OggS',
1096 'mime_type' => 'application/ogg',
1097 'fail_id3' => 'WARNING',
1098 'fail_ape' => 'WARNING',
1101 // QT - audio/video - Quicktime
1102 'quicktime' => array(
1103 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
1104 'group' => 'audio-video',
1105 'module' => 'quicktime',
1106 'mime_type' => 'video/quicktime',
1109 // 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)
1111 'pattern' => '^(RIFF|SDSS|FORM)',
1112 'group' => 'audio-video',
1114 'mime_type' => 'audio/wav',
1115 'fail_ape' => 'WARNING',
1118 // Real - audio/video - RealAudio, RealVideo
1120 'pattern' => '^\\.(RMF|ra)',
1121 'group' => 'audio-video',
1123 'mime_type' => 'audio/x-realaudio',
1126 // SWF - audio/video - ShockWave Flash
1128 'pattern' => '^(F|C)WS',
1129 'group' => 'audio-video',
1131 'mime_type' => 'application/x-shockwave-flash',
1134 // TS - audio/video - MPEG-2 Transport Stream
1136 'pattern' => '^(\\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G". Check for at least 10 packets matching this pattern
1137 'group' => 'audio-video',
1139 'mime_type' => 'video/MP2T',
1142 // WTV - audio/video - Windows Recorded TV Show
1144 'pattern' => '^\\xB7\\xD8\\x00\\x20\\x37\\x49\\xDA\\x11\\xA6\\x4E\\x00\\x07\\xE9\\x5E\\xAD\\x8D',
1145 'group' => 'audio-video',
1147 'mime_type' => 'video/x-ms-wtv',
1151 // Still-Image formats
1153 // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
1156 'group' => 'graphic',
1158 'mime_type' => 'image/bmp',
1159 'fail_id3' => 'ERROR',
1160 'fail_ape' => 'ERROR',
1163 // GIF - still image - Graphics Interchange Format
1165 'pattern' => '^GIF',
1166 'group' => 'graphic',
1168 'mime_type' => 'image/gif',
1169 'fail_id3' => 'ERROR',
1170 'fail_ape' => 'ERROR',
1173 // JPEG - still image - Joint Photographic Experts Group (JPEG)
1175 'pattern' => '^\\xFF\\xD8\\xFF',
1176 'group' => 'graphic',
1178 'mime_type' => 'image/jpeg',
1179 'fail_id3' => 'ERROR',
1180 'fail_ape' => 'ERROR',
1183 // PCD - still image - Kodak Photo CD
1185 'pattern' => '^.{2048}PCD_IPI\\x00',
1186 'group' => 'graphic',
1188 'mime_type' => 'image/x-photo-cd',
1189 'fail_id3' => 'ERROR',
1190 'fail_ape' => 'ERROR',
1194 // PNG - still image - Portable Network Graphics (PNG)
1196 'pattern' => '^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A',
1197 'group' => 'graphic',
1199 'mime_type' => 'image/png',
1200 'fail_id3' => 'ERROR',
1201 'fail_ape' => 'ERROR',
1205 // SVG - still image - Scalable Vector Graphics (SVG)
1207 'pattern' => '(<!DOCTYPE svg PUBLIC |xmlns="http://www\\.w3\\.org/2000/svg")',
1208 'group' => 'graphic',
1210 'mime_type' => 'image/svg+xml',
1211 'fail_id3' => 'ERROR',
1212 'fail_ape' => 'ERROR',
1216 // TIFF - still image - Tagged Information File Format (TIFF)
1218 'pattern' => '^(II\\x2A\\x00|MM\\x00\\x2A)',
1219 'group' => 'graphic',
1221 'mime_type' => 'image/tiff',
1222 'fail_id3' => 'ERROR',
1223 'fail_ape' => 'ERROR',
1227 // EFAX - still image - eFax (TIFF derivative)
1229 'pattern' => '^\\xDC\\xFE',
1230 'group' => 'graphic',
1232 'mime_type' => 'image/efax',
1233 'fail_id3' => 'ERROR',
1234 'fail_ape' => 'ERROR',
1240 // ISO - data - International Standards Organization (ISO) CD-ROM Image
1242 'pattern' => '^.{32769}CD001',
1245 'mime_type' => 'application/octet-stream',
1246 'fail_id3' => 'ERROR',
1247 'fail_ape' => 'ERROR',
1248 'iconv_req' => false,
1251 // HPK - data - HPK compressed data
1253 'pattern' => '^BPUL',
1254 'group' => 'archive',
1256 'mime_type' => 'application/octet-stream',
1257 'fail_id3' => 'ERROR',
1258 'fail_ape' => 'ERROR',
1261 // RAR - data - RAR compressed data
1263 'pattern' => '^Rar\\!',
1264 'group' => 'archive',
1266 'mime_type' => 'application/vnd.rar',
1267 'fail_id3' => 'ERROR',
1268 'fail_ape' => 'ERROR',
1271 // SZIP - audio/data - SZIP compressed data
1273 'pattern' => '^SZ\\x0A\\x04',
1274 'group' => 'archive',
1276 'mime_type' => 'application/octet-stream',
1277 'fail_id3' => 'ERROR',
1278 'fail_ape' => 'ERROR',
1281 // TAR - data - TAR compressed data
1283 '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}',
1284 'group' => 'archive',
1286 'mime_type' => 'application/x-tar',
1287 'fail_id3' => 'ERROR',
1288 'fail_ape' => 'ERROR',
1291 // GZIP - data - GZIP compressed data
1293 'pattern' => '^\\x1F\\x8B\\x08',
1294 'group' => 'archive',
1296 'mime_type' => 'application/gzip',
1297 'fail_id3' => 'ERROR',
1298 'fail_ape' => 'ERROR',
1301 // ZIP - data - ZIP compressed data
1303 'pattern' => '^PK\\x03\\x04',
1304 'group' => 'archive',
1306 'mime_type' => 'application/zip',
1307 'fail_id3' => 'ERROR',
1308 'fail_ape' => 'ERROR',
1311 // XZ - data - XZ compressed data
1313 'pattern' => '^\\xFD7zXZ\\x00',
1314 'group' => 'archive',
1316 'mime_type' => 'application/x-xz',
1317 'fail_id3' => 'ERROR',
1318 'fail_ape' => 'ERROR',
1322 // Misc other formats
1324 // PAR2 - data - Parity Volume Set Specification 2.0
1326 'pattern' => '^PAR2\\x00PKT',
1329 'mime_type' => 'application/octet-stream',
1330 'fail_id3' => 'ERROR',
1331 'fail_ape' => 'ERROR',
1334 // PDF - data - Portable Document Format
1336 'pattern' => '^\\x25PDF',
1339 'mime_type' => 'application/pdf',
1340 'fail_id3' => 'ERROR',
1341 'fail_ape' => 'ERROR',
1344 // MSOFFICE - data - ZIP compressed data
1345 'msoffice' => array(
1346 'pattern' => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
1348 'module' => 'msoffice',
1349 'mime_type' => 'application/octet-stream',
1350 'fail_id3' => 'ERROR',
1351 'fail_ape' => 'ERROR',
1354 // CUE - data - CUEsheet (index to single-file disc images)
1356 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
1359 'mime_type' => 'application/octet-stream',
1365 return $format_info;
1369 * @param string $filedata
1370 * @param string $filename
1372 * @return mixed|false
1374 public function GetFileFormat(&$filedata, $filename='') {
1375 // this function will determine the format of a file based on usually
1376 // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
1377 // and in the case of ISO CD image, 6 bytes offset 32kb from the start
1380 // Identify file format - loop through $format_info and detect with reg expr
1381 foreach ($this->GetFileFormatArray() as $format_name => $info) {
1382 // The /s switch on preg_match() forces preg_match() NOT to treat
1383 // newline (0x0A) characters as special chars but do a binary match
1384 if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) {
1385 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1391 if (preg_match('#\\.mp[123a]$#i', $filename)) {
1392 // Too many mp3 encoders on the market put garbage in front of mpeg files
1393 // use assume format on these if format detection failed
1394 $GetFileFormatArray = $this->GetFileFormatArray();
1395 $info = $GetFileFormatArray['mp3'];
1396 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1398 } elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
1399 // there's not really a useful consistent "magic" at the beginning of .cue files to identify them
1400 // so until I think of something better, just go by filename if all other format checks fail
1401 // and verify there's at least one instance of "TRACK xx AUDIO" in the file
1402 $GetFileFormatArray = $this->GetFileFormatArray();
1403 $info = $GetFileFormatArray['cue'];
1404 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1412 * Converts array to $encoding charset from $this->encoding.
1414 * @param array $array
1415 * @param string $encoding
1417 public function CharConvert(&$array, $encoding) {
1419 // identical encoding - end here
1420 if ($encoding == $this->encoding
) {
1425 foreach ($array as $key => $value) {
1428 if (is_array($value)) {
1429 $this->CharConvert($array[$key], $encoding);
1433 elseif (is_string($value)) {
1434 $array[$key] = trim(getid3_lib
::iconv_fallback($encoding, $this->encoding
, $value));
1442 public function HandleAllTags() {
1444 // key name => array (tag name, character encoding)
1448 'asf' => array('asf' , 'UTF-16LE'),
1449 'midi' => array('midi' , 'ISO-8859-1'),
1450 'nsv' => array('nsv' , 'ISO-8859-1'),
1451 'ogg' => array('vorbiscomment' , 'UTF-8'),
1452 'png' => array('png' , 'UTF-8'),
1453 'tiff' => array('tiff' , 'ISO-8859-1'),
1454 'quicktime' => array('quicktime' , 'UTF-8'),
1455 'real' => array('real' , 'ISO-8859-1'),
1456 'vqf' => array('vqf' , 'ISO-8859-1'),
1457 'zip' => array('zip' , 'ISO-8859-1'),
1458 'riff' => array('riff' , 'ISO-8859-1'),
1459 'lyrics3' => array('lyrics3' , 'ISO-8859-1'),
1460 'id3v1' => array('id3v1' , $this->encoding_id3v1
),
1461 '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
1462 'ape' => array('ape' , 'UTF-8'),
1463 'cue' => array('cue' , 'ISO-8859-1'),
1464 'matroska' => array('matroska' , 'UTF-8'),
1465 'flac' => array('vorbiscomment' , 'UTF-8'),
1466 'divxtag' => array('divx' , 'ISO-8859-1'),
1467 'iptc' => array('iptc' , 'ISO-8859-1'),
1468 'dsdiff' => array('dsdiff' , 'ISO-8859-1'),
1472 // loop through comments array
1473 foreach ($tags as $comment_name => $tagname_encoding_array) {
1474 list($tag_name, $encoding) = $tagname_encoding_array;
1476 // fill in default encoding type if not already present
1477 if (isset($this->info
[$comment_name]) && !isset($this->info
[$comment_name]['encoding'])) {
1478 $this->info
[$comment_name]['encoding'] = $encoding;
1481 // copy comments if key name set
1482 if (!empty($this->info
[$comment_name]['comments'])) {
1483 foreach ($this->info
[$comment_name]['comments'] as $tag_key => $valuearray) {
1484 foreach ($valuearray as $key => $value) {
1485 if (is_string($value)) {
1486 $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
1489 if (!is_numeric($key)) {
1490 $this->info
['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
1492 $this->info
['tags'][trim($tag_name)][trim($tag_key)][] = $value;
1496 if ($tag_key == 'picture') {
1497 // pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere
1498 unset($this->info
[$comment_name]['comments'][$tag_key]);
1502 if (!isset($this->info
['tags'][$tag_name])) {
1503 // comments are set but contain nothing but empty strings, so skip
1507 $this->CharConvert($this->info
['tags'][$tag_name], $this->info
[$comment_name]['encoding']); // only copy gets converted!
1509 if ($this->option_tags_html
) {
1510 foreach ($this->info
['tags'][$tag_name] as $tag_key => $valuearray) {
1511 if ($tag_key == 'picture') {
1512 // Do not to try to convert binary picture data to HTML
1513 // https://github.com/JamesHeinrich/getID3/issues/178
1516 $this->info
['tags_html'][$tag_name][$tag_key] = getid3_lib
::recursiveMultiByteCharString2HTML($valuearray, $this->info
[$comment_name]['encoding']);
1524 // pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere
1525 if (!empty($this->info
['tags'])) {
1526 $unset_keys = array('tags', 'tags_html');
1527 foreach ($this->info
['tags'] as $tagtype => $tagarray) {
1528 foreach ($tagarray as $tagname => $tagdata) {
1529 if ($tagname == 'picture') {
1530 foreach ($tagdata as $key => $tagarray) {
1531 $this->info
['comments']['picture'][] = $tagarray;
1532 if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
1533 if (isset($this->info
['tags'][$tagtype][$tagname][$key])) {
1534 unset($this->info
['tags'][$tagtype][$tagname][$key]);
1536 if (isset($this->info
['tags_html'][$tagtype][$tagname][$key])) {
1537 unset($this->info
['tags_html'][$tagtype][$tagname][$key]);
1543 foreach ($unset_keys as $unset_key) {
1544 // remove possible empty keys from (e.g. [tags][id3v2][picture])
1545 if (empty($this->info
[$unset_key][$tagtype]['picture'])) {
1546 unset($this->info
[$unset_key][$tagtype]['picture']);
1548 if (empty($this->info
[$unset_key][$tagtype])) {
1549 unset($this->info
[$unset_key][$tagtype]);
1551 if (empty($this->info
[$unset_key])) {
1552 unset($this->info
[$unset_key]);
1555 // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
1556 if (isset($this->info
[$tagtype]['comments']['picture'])) {
1557 unset($this->info
[$tagtype]['comments']['picture']);
1559 if (empty($this->info
[$tagtype]['comments'])) {
1560 unset($this->info
[$tagtype]['comments']);
1562 if (empty($this->info
[$tagtype])) {
1563 unset($this->info
[$tagtype]);
1571 * Calls getid3_lib::CopyTagsToComments() but passes in the option_tags_html setting from this instance of getID3
1573 * @param array $ThisFileInfo
1577 public function CopyTagsToComments(&$ThisFileInfo) {
1578 return getid3_lib
::CopyTagsToComments($ThisFileInfo, $this->option_tags_html
);
1582 * @param string $algorithm
1584 * @return array|bool
1586 public function getHashdata($algorithm) {
1587 switch ($algorithm) {
1593 return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
1596 if (!empty($this->info
['fileformat']) && !empty($this->info
['dataformat']) && ($this->info
['fileformat'] == 'ogg') && ($this->info
['audio']['dataformat'] == 'vorbis')) {
1598 // We cannot get an identical md5_data value for Ogg files where the comments
1599 // span more than 1 Ogg page (compared to the same audio data with smaller
1600 // comments) using the normal getID3() method of MD5'ing the data between the
1601 // end of the comments and the end of the file (minus any trailing tags),
1602 // because the page sequence numbers of the pages that the audio data is on
1603 // do not match. Under normal circumstances, where comments are smaller than
1604 // the nominal 4-8kB page size, then this is not a problem, but if there are
1605 // very large comments, the only way around it is to strip off the comment
1606 // tags with vorbiscomment and MD5 that file.
1607 // This procedure must be applied to ALL Ogg files, not just the ones with
1608 // comments larger than 1 page, because the below method simply MD5's the
1609 // whole file with the comments stripped, not just the portion after the
1610 // comments block (which is the standard getID3() method.
1612 // The above-mentioned problem of comments spanning multiple pages and changing
1613 // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
1614 // currently vorbiscomment only works on OggVorbis files.
1616 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
1618 $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
1619 $this->info
[$algorithm.'_data'] = false;
1623 // Prevent user from aborting script
1624 $old_abort = ignore_user_abort(true);
1626 // Create empty file
1627 $empty = tempnam(GETID3_TEMP_DIR
, 'getID3');
1630 // Use vorbiscomment to make temp file without comments
1631 $temp = tempnam(GETID3_TEMP_DIR
, 'getID3');
1632 $file = $this->info
['filenamepath'];
1634 if (GETID3_OS_ISWINDOWS
) {
1636 if (file_exists(GETID3_HELPERAPPSDIR
.'vorbiscomment.exe')) {
1638 $commandline = '"'.GETID3_HELPERAPPSDIR
.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
1639 $VorbisCommentError = `
$commandline`
;
1643 $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR
;
1649 $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
1650 $VorbisCommentError = `
$commandline`
;
1654 if (!empty($VorbisCommentError)) {
1656 $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);
1657 $this->info
[$algorithm.'_data'] = false;
1661 // Get hash of newly created file
1662 switch ($algorithm) {
1664 $this->info
[$algorithm.'_data'] = md5_file($temp);
1668 $this->info
[$algorithm.'_data'] = sha1_file($temp);
1677 // Reset abort setting
1678 ignore_user_abort($old_abort);
1684 if (!empty($this->info
['avdataoffset']) ||
(isset($this->info
['avdataend']) && ($this->info
['avdataend'] < $this->info
['filesize']))) {
1686 // get hash from part of file
1687 $this->info
[$algorithm.'_data'] = getid3_lib
::hash_data($this->info
['filenamepath'], $this->info
['avdataoffset'], $this->info
['avdataend'], $algorithm);
1691 // get hash from whole file
1692 switch ($algorithm) {
1694 $this->info
[$algorithm.'_data'] = md5_file($this->info
['filenamepath']);
1698 $this->info
[$algorithm.'_data'] = sha1_file($this->info
['filenamepath']);
1707 public function ChannelsBitratePlaytimeCalculations() {
1709 // set channelmode on audio
1710 if (!empty($this->info
['audio']['channelmode']) ||
!isset($this->info
['audio']['channels'])) {
1712 } elseif ($this->info
['audio']['channels'] == 1) {
1713 $this->info
['audio']['channelmode'] = 'mono';
1714 } elseif ($this->info
['audio']['channels'] == 2) {
1715 $this->info
['audio']['channelmode'] = 'stereo';
1718 // Calculate combined bitrate - audio + video
1719 $CombinedBitrate = 0;
1720 $CombinedBitrate +
= (isset($this->info
['audio']['bitrate']) ?
$this->info
['audio']['bitrate'] : 0);
1721 $CombinedBitrate +
= (isset($this->info
['video']['bitrate']) ?
$this->info
['video']['bitrate'] : 0);
1722 if (($CombinedBitrate > 0) && empty($this->info
['bitrate'])) {
1723 $this->info
['bitrate'] = $CombinedBitrate;
1725 //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
1726 // // for example, VBR MPEG video files cannot determine video bitrate:
1727 // // should not set overall bitrate and playtime from audio bitrate only
1728 // unset($this->info['bitrate']);
1731 // video bitrate undetermined, but calculable
1732 if (isset($this->info
['video']['dataformat']) && $this->info
['video']['dataformat'] && (!isset($this->info
['video']['bitrate']) ||
($this->info
['video']['bitrate'] == 0))) {
1733 // if video bitrate not set
1734 if (isset($this->info
['audio']['bitrate']) && ($this->info
['audio']['bitrate'] > 0) && ($this->info
['audio']['bitrate'] == $this->info
['bitrate'])) {
1735 // AND if audio bitrate is set to same as overall bitrate
1736 if (isset($this->info
['playtime_seconds']) && ($this->info
['playtime_seconds'] > 0)) {
1737 // AND if playtime is set
1738 if (isset($this->info
['avdataend']) && isset($this->info
['avdataoffset'])) {
1739 // AND if AV data offset start/end is known
1740 // THEN we can calculate the video bitrate
1741 $this->info
['bitrate'] = round((($this->info
['avdataend'] - $this->info
['avdataoffset']) * 8) / $this->info
['playtime_seconds']);
1742 $this->info
['video']['bitrate'] = $this->info
['bitrate'] - $this->info
['audio']['bitrate'];
1748 if ((!isset($this->info
['playtime_seconds']) ||
($this->info
['playtime_seconds'] <= 0)) && !empty($this->info
['bitrate'])) {
1749 $this->info
['playtime_seconds'] = (($this->info
['avdataend'] - $this->info
['avdataoffset']) * 8) / $this->info
['bitrate'];
1752 if (!isset($this->info
['bitrate']) && !empty($this->info
['playtime_seconds'])) {
1753 $this->info
['bitrate'] = (($this->info
['avdataend'] - $this->info
['avdataoffset']) * 8) / $this->info
['playtime_seconds'];
1755 if (isset($this->info
['bitrate']) && empty($this->info
['audio']['bitrate']) && empty($this->info
['video']['bitrate'])) {
1756 if (isset($this->info
['audio']['dataformat']) && empty($this->info
['video']['resolution_x'])) {
1758 $this->info
['audio']['bitrate'] = $this->info
['bitrate'];
1759 } elseif (isset($this->info
['video']['resolution_x']) && empty($this->info
['audio']['dataformat'])) {
1761 $this->info
['video']['bitrate'] = $this->info
['bitrate'];
1765 // Set playtime string
1766 if (!empty($this->info
['playtime_seconds']) && empty($this->info
['playtime_string'])) {
1767 $this->info
['playtime_string'] = getid3_lib
::PlaytimeString($this->info
['playtime_seconds']);
1774 public function CalculateCompressionRatioVideo() {
1775 if (empty($this->info
['video'])) {
1778 if (empty($this->info
['video']['resolution_x']) ||
empty($this->info
['video']['resolution_y'])) {
1781 if (empty($this->info
['video']['bits_per_sample'])) {
1785 switch ($this->info
['video']['dataformat']) {
1793 $PlaytimeSeconds = 1;
1794 $BitrateCompressed = $this->info
['filesize'] * 8;
1798 if (!empty($this->info
['video']['frame_rate'])) {
1799 $FrameRate = $this->info
['video']['frame_rate'];
1803 if (!empty($this->info
['playtime_seconds'])) {
1804 $PlaytimeSeconds = $this->info
['playtime_seconds'];
1808 if (!empty($this->info
['video']['bitrate'])) {
1809 $BitrateCompressed = $this->info
['video']['bitrate'];
1815 $BitrateUncompressed = $this->info
['video']['resolution_x'] * $this->info
['video']['resolution_y'] * $this->info
['video']['bits_per_sample'] * $FrameRate;
1817 $this->info
['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
1824 public function CalculateCompressionRatioAudio() {
1825 if (empty($this->info
['audio']['bitrate']) ||
empty($this->info
['audio']['channels']) ||
empty($this->info
['audio']['sample_rate']) ||
!is_numeric($this->info
['audio']['sample_rate'])) {
1828 $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));
1830 if (!empty($this->info
['audio']['streams'])) {
1831 foreach ($this->info
['audio']['streams'] as $streamnumber => $streamdata) {
1832 if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) {
1833 $this->info
['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ?
$streamdata['bits_per_sample'] : 16));
1843 public function CalculateReplayGain() {
1844 if (isset($this->info
['replay_gain'])) {
1845 if (!isset($this->info
['replay_gain']['reference_volume'])) {
1846 $this->info
['replay_gain']['reference_volume'] = 89.0;
1848 if (isset($this->info
['replay_gain']['track']['adjustment'])) {
1849 $this->info
['replay_gain']['track']['volume'] = $this->info
['replay_gain']['reference_volume'] - $this->info
['replay_gain']['track']['adjustment'];
1851 if (isset($this->info
['replay_gain']['album']['adjustment'])) {
1852 $this->info
['replay_gain']['album']['volume'] = $this->info
['replay_gain']['reference_volume'] - $this->info
['replay_gain']['album']['adjustment'];
1855 if (isset($this->info
['replay_gain']['track']['peak'])) {
1856 $this->info
['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib
::RGADamplitude2dB($this->info
['replay_gain']['track']['peak']);
1858 if (isset($this->info
['replay_gain']['album']['peak'])) {
1859 $this->info
['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib
::RGADamplitude2dB($this->info
['replay_gain']['album']['peak']);
1868 public function ProcessAudioStreams() {
1869 if (!empty($this->info
['audio']['bitrate']) ||
!empty($this->info
['audio']['channels']) ||
!empty($this->info
['audio']['sample_rate'])) {
1870 if (!isset($this->info
['audio']['streams'])) {
1871 foreach ($this->info
['audio'] as $key => $value) {
1872 if ($key != 'streams') {
1873 $this->info
['audio']['streams'][0][$key] = $value;
1882 * @return string|bool
1884 public function getid3_tempnam() {
1885 return tempnam($this->tempdir
, 'gI3');
1889 * @param string $name
1893 * @throws getid3_exception
1895 public function include_module($name) {
1896 //if (!file_exists($this->include_path.'module.'.$name.'.php')) {
1897 if (!file_exists(GETID3_INCLUDEPATH
.'module.'.$name.'.php')) {
1898 throw new getid3_exception('Required module.'.$name.'.php is missing.');
1900 include_once(GETID3_INCLUDEPATH
.'module.'.$name.'.php');
1905 * @param string $filename
1909 public static function is_writable ($filename) {
1910 $ret = is_writable($filename);
1912 $perms = fileperms($filename);
1913 $ret = ($perms & 0x0080) ||
($perms & 0x0010) ||
($perms & 0x0002);
1921 abstract class getid3_handler
1927 protected $getid3; // pointer
1930 * Analyzing filepointer or string.
1934 protected $data_string_flag = false;
1937 * String to analyze.
1941 protected $data_string = '';
1944 * Seek position in string.
1948 protected $data_string_position = 0;
1955 protected $data_string_length = 0;
1960 private $dependency_to;
1963 * getid3_handler constructor.
1965 * @param getID3 $getid3
1966 * @param string $call_module
1968 public function __construct(getID3
$getid3, $call_module=null) {
1969 $this->getid3
= $getid3;
1972 $this->dependency_to
= str_replace('getid3_', '', $call_module);
1977 * Analyze from file pointer.
1981 abstract public function Analyze();
1984 * Analyze from string instead.
1986 * @param string $string
1988 public function AnalyzeString($string) {
1989 // Enter string mode
1990 $this->setStringMode($string);
1993 $saved_avdataoffset = $this->getid3
->info
['avdataoffset'];
1994 $saved_avdataend = $this->getid3
->info
['avdataend'];
1995 $saved_filesize = (isset($this->getid3
->info
['filesize']) ?
$this->getid3
->info
['filesize'] : null); // may be not set if called as dependency without openfile() call
1998 $this->getid3
->info
['avdataoffset'] = 0;
1999 $this->getid3
->info
['avdataend'] = $this->getid3
->info
['filesize'] = $this->data_string_length
;
2004 // Restore some info
2005 $this->getid3
->info
['avdataoffset'] = $saved_avdataoffset;
2006 $this->getid3
->info
['avdataend'] = $saved_avdataend;
2007 $this->getid3
->info
['filesize'] = $saved_filesize;
2010 $this->data_string_flag
= false;
2014 * @param string $string
2016 public function setStringMode($string) {
2017 $this->data_string_flag
= true;
2018 $this->data_string
= $string;
2019 $this->data_string_length
= strlen($string);
2025 protected function ftell() {
2026 if ($this->data_string_flag
) {
2027 return $this->data_string_position
;
2029 return ftell($this->getid3
->fp
);
2035 * @return string|false
2037 * @throws getid3_exception
2039 protected function fread($bytes) {
2040 if ($this->data_string_flag
) {
2041 $this->data_string_position +
= $bytes;
2042 return substr($this->data_string
, $this->data_string_position
- $bytes, $bytes);
2044 $pos = $this->ftell() +
$bytes;
2045 if (!getid3_lib
::intValueSupported($pos)) {
2046 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10);
2049 //return fread($this->getid3->fp, $bytes);
2051 * https://www.getid3.org/phpBB3/viewtopic.php?t=1930
2052 * "I found out that the root cause for the problem was how getID3 uses the PHP system function fread().
2053 * It seems to assume that fread() would always return as many bytes as were requested.
2054 * 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.
2055 * The call may return only part of the requested data and a new call is needed to get more."
2059 //if (($this->getid3->memory_limit > 0) && ($bytes > $this->getid3->memory_limit)) {
2060 if (($this->getid3
->memory_limit
> 0) && (($bytes / $this->getid3
->memory_limit
) > 0.99)) { // enable a more-fuzzy match to prevent close misses generating errors like "PHP Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 33554464 bytes)"
2061 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') that is more than available PHP memory ('.$this->getid3
->memory_limit
.')', 10);
2063 $part = fread($this->getid3
->fp
, $bytes);
2064 $partLength = strlen($part);
2065 $bytes -= $partLength;
2067 } while (($bytes > 0) && ($partLength > 0));
2073 * @param int $whence
2077 * @throws getid3_exception
2079 protected function fseek($bytes, $whence=SEEK_SET
) {
2080 if ($this->data_string_flag
) {
2083 $this->data_string_position
= $bytes;
2087 $this->data_string_position +
= $bytes;
2091 $this->data_string_position
= $this->data_string_length +
$bytes;
2097 if ($whence == SEEK_CUR
) {
2098 $pos = $this->ftell() +
$bytes;
2099 } elseif ($whence == SEEK_END
) {
2100 $pos = $this->getid3
->info
['filesize'] +
$bytes;
2102 if (!getid3_lib
::intValueSupported($pos)) {
2103 throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
2106 return fseek($this->getid3
->fp
, $bytes, $whence);
2110 * @return string|false
2112 * @throws getid3_exception
2114 protected function fgets() {
2115 // must be able to handle CR/LF/CRLF but not read more than one lineend
2116 $buffer = ''; // final string we will return
2117 $prevchar = ''; // save previously-read character for end-of-line checking
2118 if ($this->data_string_flag
) {
2120 $thischar = substr($this->data_string
, $this->data_string_position++
, 1);
2121 if (($prevchar == "\r") && ($thischar != "\n")) {
2122 // read one byte too many, back up
2123 $this->data_string_position
--;
2126 $buffer .= $thischar;
2127 if ($thischar == "\n") {
2130 if ($this->data_string_position
>= $this->data_string_length
) {
2134 $prevchar = $thischar;
2139 // Ideally we would just use PHP's fgets() function, however...
2140 // it does not behave consistently with regards to mixed line endings, may be system-dependent
2141 // and breaks entirely when given a file with mixed \r vs \n vs \r\n line endings (e.g. some PDFs)
2142 //return fgets($this->getid3->fp);
2144 $thischar = fgetc($this->getid3
->fp
);
2145 if (($prevchar == "\r") && ($thischar != "\n")) {
2146 // read one byte too many, back up
2147 fseek($this->getid3
->fp
, -1, SEEK_CUR
);
2150 $buffer .= $thischar;
2151 if ($thischar == "\n") {
2154 if (feof($this->getid3
->fp
)) {
2157 $prevchar = $thischar;
2167 protected function feof() {
2168 if ($this->data_string_flag
) {
2169 return $this->data_string_position
>= $this->data_string_length
;
2171 return feof($this->getid3
->fp
);
2175 * @param string $module
2179 final protected function isDependencyFor($module) {
2180 return $this->dependency_to
== $module;
2184 * @param string $text
2188 protected function error($text) {
2189 $this->getid3
->info
['error'][] = $text;
2195 * @param string $text
2199 protected function warning($text) {
2200 return $this->getid3
->warning($text);
2204 * @param string $text
2206 protected function notice($text) {
2207 // does nothing for now
2211 * @param string $name
2212 * @param int $offset
2213 * @param int $length
2214 * @param string $image_mime
2216 * @return string|null
2219 * @throws getid3_exception
2221 public function saveAttachment($name, $offset, $length, $image_mime=null) {
2224 // do not extract at all
2225 if ($this->getid3
->option_save_attachments
=== getID3
::ATTACHMENTS_NONE
) {
2227 $attachment = null; // do not set any
2229 // extract to return array
2230 } elseif ($this->getid3
->option_save_attachments
=== getID3
::ATTACHMENTS_INLINE
) {
2232 $this->fseek($offset);
2233 $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory
2234 if ($attachment === false ||
strlen($attachment) != $length) {
2235 throw new Exception('failed to read attachment data');
2238 // assume directory path is given
2241 // set up destination path
2242 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR
, $this->getid3
->option_save_attachments
), DIRECTORY_SEPARATOR
);
2243 if (!is_dir($dir) ||
!getID3
::is_writable($dir)) { // check supplied directory
2244 throw new Exception('supplied path ('.$dir.') does not exist, or is not writable');
2246 $dest = $dir.DIRECTORY_SEPARATOR
.$name.($image_mime ?
'.'.getid3_lib
::ImageExtFromMime($image_mime) : '');
2249 if (($fp_dest = fopen($dest, 'wb')) == false) {
2250 throw new Exception('failed to create file '.$dest);
2254 $this->fseek($offset);
2255 $buffersize = ($this->data_string_flag ?
$length : $this->getid3
->fread_buffer_size());
2256 $bytesleft = $length;
2257 while ($bytesleft > 0) {
2258 if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false ||
($byteswritten = fwrite($fp_dest, $buffer)) === false ||
($byteswritten === 0)) {
2259 throw new Exception($buffer === false ?
'not enough data to read' : 'failed to write to destination file, may be not enough disk space');
2261 $bytesleft -= $byteswritten;
2265 $attachment = $dest;
2269 } catch (Exception
$e) {
2271 // close and remove dest file if created
2272 if (isset($fp_dest) && is_resource($fp_dest)) {
2276 if (isset($dest) && file_exists($dest)) {
2280 // do not set any is case of error
2282 $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage());
2286 // seek to the end of attachment
2287 $this->fseek($offset +
$length);
2295 class getid3_exception
extends Exception