source: spip-zone/_core_/plugins/medias/lib/getid3/getid3.php @ 113161

Last change on this file since 113161 was 113161, checked in by spip.franck@…, 5 months ago

Mise à jour de la lib getid en 1.9.16, nous étions en 1.9.14
https://github.com/JamesHeinrich/getID3/blob/master/changelog.txt

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