ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
GetId3Core.php
Go to the documentation of this file.
1<?php
2
3namespace GetId3;
4
7
10// available at http://getid3.sourceforge.net //
11// or http://www.getid3.org //
13// //
14// Please see readme.txt for more information //
15// ///
17
26{
27 // public: Settings
28 public $encoding = 'UTF-8'; // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE
29 public $encoding_id3v1 = 'ISO-8859-1'; // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252'
30 // public: Optional tag checks - disable for speed.
31 public $option_tag_id3v1 = true; // Read and process ID3v1 tags
32 public $option_tag_id3v2 = true; // Read and process ID3v2 tags
33 public $option_tag_lyrics3 = true; // Read and process Lyrics3 tags
34 public $option_tag_apetag = true; // Read and process APE tags
35 public $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding
36 public $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
37 // public: Optional tag/comment calucations
38 public $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc
39 // public: Optional handling of embedded attachments (e.g. images)
40 public $option_save_attachments = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility
41 // public: Optional calculations
42 public $option_md5_data = false; // Get MD5 sum of data part - slow
43 public $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG
44 public $option_sha1_data = false; // Get SHA1 sum of data part - slow
45 public $option_max_2gb_check = null; // Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on PHP_INT_MAX)
46 // public: Read buffer size in bytes
47 public $option_fread_buffer_size = 32768;
48 // Public variables
49 public $filename; // Filename of file being analysed.
50 public $fp; // Filepointer to file being analysed.
51 public $info; // Result array.
52 // Protected variables
53 protected $startup_error = '';
54 protected $startup_warning = '';
55 protected $memory_limit = 0;
56 public $tempdir;
57 protected static $TempDir; // $TempDir = '/something/else/'; // feel free to override temp dir here if it works better for your system
58 protected static $IncludePath;
59 protected static $EnvironmentIsWindows;
60 protected static $HelperAppsDir;
61
62 const VERSION = '1.9.4-20120530';
63 const FREAD_BUFFER_SIZE = 32768;
64 const ATTACHMENTS_NONE = false;
65 const ATTACHMENTS_INLINE = true;
66
71 public function __construct()
72 {
73 $this->tempdir = self::getTempDir();
74 // Check for PHP version
75 $required_php_version = '5.0.5';
76 if (version_compare(PHP_VERSION, $required_php_version, '<')) {
77 $this->startup_error .= 'getID3() requires PHP v' . $required_php_version . ' or higher - you are running v' . PHP_VERSION;
78
79 return false;
80 }
81
82 // Check memory
83 $this->memory_limit = ini_get('memory_limit');
84 if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) {
85 // could be stored as "16M" rather than 16777216 for example
86 $this->memory_limit = $matches[1] * 1048576;
87 } elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
88 // could be stored as "2G" rather than 2147483648 for example
89 $this->memory_limit = $matches[1] * 1073741824;
90 }
91 if ($this->memory_limit <= 0) {
92 // memory limits probably disabled
93 } elseif ($this->memory_limit <= 4194304) {
94 $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini';
95 } elseif ($this->memory_limit <= 12582912) {
96 $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';
97 }
98
99 // Check safe_mode off
100 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
101 $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
102 }
103
104 if (intval(ini_get('mbstring.func_overload')) > 0) {
105 $this->warning('WARNING: php.ini contains "mbstring.func_overload = ' . ini_get('mbstring.func_overload') . '", this may break things.');
106 }
107
108 // Check for magic_quotes_runtime
109 if (function_exists('get_magic_quotes_runtime')) {
110 if (get_magic_quotes_runtime()) {
111 return $this->startup_error('magic_quotes_runtime must be disabled before running GetId3Core(). Surround GetId3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).');
112 }
113 }
114
115 // Check for magic_quotes_gpc
116 if (function_exists('magic_quotes_gpc')) {
117 if (get_magic_quotes_gpc()) {
118 return $this->startup_error('magic_quotes_gpc must be disabled before running GetId3Core(). Surround GetId3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).');
119 }
120 }
121
122 // Check support library
123 if (!class_exists('GetId3\\Lib\\Helper')) {
124 $this->startup_error .= str_replace('\\', DIRECTORY_SEPARATOR, 'GetId3\\Lib\\Helper') . '.php is missing or corrupt';
125 }
126
127 if ($this->option_max_2gb_check === null) {
128 $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647);
129 }
130
131 $this->setHelperAppsDir();
132
133 return true;
134 }
135
139 protected function setHelperAppsDir()
140 {
141 // Needed for Windows only:
142 // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC
143 // as well as other helper functions such as head, tail, md5sum, etc
144 // This path cannot contain spaces, but the below code will attempt to get the
145 // 8.3-equivalent path automatically
146 // IMPORTANT: This path must include the trailing slash
147 if (self::$EnvironmentIsWindows && null === self::$HelperAppsDir) {
148
149 $helperappsdir = self::$IncludePath . 'Resources' . DIRECTORY_SEPARATOR . 'helperapps'; // must not have any space in this path
150
151 if (!is_dir($helperappsdir)) {
152 $this->startup_warning .= '"' . $helperappsdir . '" cannot be defined as self::getHelperAppsDir() because it does not exist';
153 } elseif (strpos(realpath($helperappsdir), ' ') !== false) {
154 $DirPieces = explode(DIRECTORY_SEPARATOR,
155 realpath($helperappsdir));
156 $path_so_far = array();
157 foreach ($DirPieces as $key => $value) {
158 if (strpos($value, ' ') !== false) {
159 if (!empty($path_so_far)) {
160 $commandline = 'dir /x ' . escapeshellarg(implode(DIRECTORY_SEPARATOR,
161 $path_so_far));
162 $dir_listing = `$commandline`;
163 $lines = explode("\n", $dir_listing);
164 foreach ($lines as $line) {
165 $line = trim($line);
166 if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#',
167 $line, $matches)) {
168 list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches;
169 if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) {
170 $value = $shortname;
171 }
172 }
173 }
174 } else {
175 $this->startup_warning .= 'self::getHelperAppsDir() 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.';
176 }
177 }
178 $path_so_far[] = $value;
179 }
180 $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far);
181 }
182 self::$HelperAppsDir = $helperappsdir . DIRECTORY_SEPARATOR;
183 }
184 }
185
186 public static function getHelperAppsDir()
187 {
188 return self::$HelperAppsDir;
189 }
190
195 public function version()
196 {
197 return self::VERSION;
198 }
199
204 public function fread_buffer_size()
205 {
206 return $this->option_fread_buffer_size;
207 }
208
214 public function setOption($optArray)
215 {
216 if (!is_array($optArray) || empty($optArray)) {
217 return false;
218 }
219 foreach ($optArray as $opt => $val) {
220 if (isset($this->$opt) === false) {
221 continue;
222 }
223 $this->$opt = $val;
224 }
225
226 return true;
227 }
228
235 public function openfile($filename)
236 {
237 try {
238 if (!empty($this->startup_error)) {
239 throw new DefaultException($this->startup_error);
240 }
241 if (!empty($this->startup_warning)) {
242 $this->warning($this->startup_warning);
243 }
244
245 // init result array and set parameters
246 $this->filename = $filename;
247 $this->info = array();
248 $this->info['GETID3_VERSION'] = $this->version();
249 $this->info['php_memory_limit'] = $this->memory_limit;
250
251 // remote files not supported
252 if (preg_match('/^(ht|f)tp:\/\//', $filename)) {
253 throw new DefaultException('Remote files are not supported - please copy the file locally first');
254 }
255
256 $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
257 $filename = preg_replace('#(.+)' . preg_quote(DIRECTORY_SEPARATOR) . '{2,}#U',
258 '\1' . DIRECTORY_SEPARATOR,
259 $filename);
260
261 // open local file
262 if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename,
263 'rb'))) {
264 // great
265 } else {
266 throw new DefaultException('Could not open "' . $filename . '" (does not exist, or is not a file)');
267 }
268
269 $this->info['filesize'] = filesize($filename);
270 // set redundant parameters - might be needed in some include file
271 $this->info['filename'] = basename($filename);
272 $this->info['filepath'] = str_replace('\\', '/',
273 realpath(dirname($filename)));
274 $this->info['filenamepath'] = $this->info['filepath'] . '/' . $this->info['filename'];
275
276 // option_max_2gb_check
277 if ($this->option_max_2gb_check) {
278 // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB)
279 // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize
280 // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer
281 $fseek = fseek($this->fp, 0, SEEK_END);
282 if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) ||
283 ($this->info['filesize'] < 0) ||
284 (ftell($this->fp) < 0)) {
285 $real_filesize = false;
286 if (self::$EnvironmentIsWindows) {
287 $commandline = 'dir /-C "' . str_replace('/',
288 DIRECTORY_SEPARATOR,
289 $filename) . '"';
290 $dir_output = `$commandline`;
291 if (preg_match('#1 File\‍(s\‍)[ ]+([0-9]+) bytes#i',
292 $dir_output, $matches)) {
293 $real_filesize = (float) $matches[1];
294 }
295 } else {
296 $commandline = 'ls -o -g -G --time-style=long-iso ' . escapeshellarg($filename);
297 $dir_output = `$commandline`;
298 if (preg_match('#([0-9]+) ([0-9]{4}-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}) ' . str_replace('#',
299 '\\#',
300 preg_quote($filename)) . '$#',
301 $dir_output,
302 $matches)) {
303 $real_filesize = (float) $matches[1];
304 }
305 }
306 if ($real_filesize === false) {
307 unset($this->info['filesize']);
308 fclose($this->fp);
309 throw new DefaultException('Unable to determine actual filesize. File is most likely larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB and is not supported by PHP.');
310 } elseif (Helper::intValueSupported($real_filesize)) {
311 unset($this->info['filesize']);
312 fclose($this->fp);
313 throw new DefaultException('PHP seems to think the file is larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB, but filesystem reports it as ' . number_format($real_filesize,
314 3) . 'GB, please report to info@getid3.org');
315 }
316 $this->info['filesize'] = $real_filesize;
317 $this->error('File is larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB (filesystem reports it as ' . number_format($real_filesize,
318 3) . 'GB) and is not properly supported by PHP.');
319 }
320 }
321
322 // set more parameters
323 $this->info['avdataoffset'] = 0;
324 $this->info['avdataend'] = $this->info['filesize'];
325 $this->info['fileformat'] = ''; // filled in later
326 $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used
327 $this->info['video']['dataformat'] = ''; // filled in later, unset if not used
328 $this->info['tags'] = array(); // filled in later, unset if not used
329 $this->info['error'] = array(); // filled in later, unset if not used
330 $this->info['warning'] = array(); // filled in later, unset if not used
331 $this->info['comments'] = array(); // filled in later, unset if not used
332 $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired
333
334 return true;
335 } catch (DefaultException $e) {
336 $this->error($e->getMessage());
337 }
338
339 return false;
340 }
341
348 public function analyze($filename)
349 {
350 try {
351 if (!$this->openfile($filename)) {
352 return $this->info;
353 }
354
355 // Handle tags
356 foreach (array('id3v2' => 'id3v2', 'id3v1' => 'id3v1', 'apetag' => 'ape', 'lyrics3' => 'lyrics3') as $tag_name => $tag_key) {
357 $option_tag = 'option_tag_' . $tag_name;
358 if ($this->$option_tag) {
359 try {
360 $tag_class = 'GetId3\\Module\\Tag\\' . ucfirst($tag_name);
361 $tag = new $tag_class($this);
362 $tag->analyze();
363 } catch (DefaultException $e) {
364 throw $e;
365 }
366 }
367 }
368 if (isset($this->info['id3v2']['tag_offset_start'])) {
369 $this->info['avdataoffset'] = max($this->info['avdataoffset'],
370 $this->info['id3v2']['tag_offset_end']);
371 }
372 foreach (array('id3v1' => 'id3v1', 'apetag' => 'ape', 'lyrics3' => 'lyrics3') as $tag_name => $tag_key) {
373 if (isset($this->info[$tag_key]['tag_offset_start'])) {
374 $this->info['avdataend'] = min($this->info['avdataend'],
375 $this->info[$tag_key]['tag_offset_start']);
376 }
377 }
378
379 // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
380 if (!$this->option_tag_id3v2) {
381 fseek($this->fp, 0, SEEK_SET);
382 $header = fread($this->fp, 10);
383 if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) {
384 $this->info['id3v2']['header'] = true;
385 $this->info['id3v2']['majorversion'] = ord($header{3});
386 $this->info['id3v2']['minorversion'] = ord($header{4});
387 $this->info['avdataoffset'] += Helper::BigEndian2Int(substr($header,
388 6,
389 4),
390 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
391 }
392 }
393
394 // read 32 kb file data
395 fseek($this->fp, $this->info['avdataoffset'], SEEK_SET);
396 $formattest = fread($this->fp, 32774);
397
398 // determine format
399 $determined_format = $this->GetFileFormat($formattest, $filename);
400
401 // unable to determine file format
402 if (!$determined_format) {
403 fclose($this->fp);
404
405 return $this->error('unable to determine file format');
406 }
407
408 // check for illegal ID3 tags
409 if (isset($determined_format['fail_id3']) && (in_array('id3v1',
410 $this->info['tags']) || in_array('id3v2',
411 $this->info['tags']))) {
412 if ($determined_format['fail_id3'] === 'ERROR') {
413 fclose($this->fp);
414
415 return $this->error('ID3 tags not allowed on this file type.');
416 } elseif ($determined_format['fail_id3'] === 'WARNING') {
417 $this->warning('ID3 tags not allowed on this file type.');
418 }
419 }
420
421 // check for illegal APE tags
422 if (isset($determined_format['fail_ape']) && in_array('ape',
423 $this->info['tags'])) {
424 if ($determined_format['fail_ape'] === 'ERROR') {
425 fclose($this->fp);
426
427 return $this->error('APE tags not allowed on this file type.');
428 } elseif ($determined_format['fail_ape'] === 'WARNING') {
429 $this->warning('APE tags not allowed on this file type.');
430 }
431 }
432
433 // set mime type
434 $this->info['mime_type'] = $determined_format['mime_type'];
435
436 // supported format signature pattern detected, but module deleted
437 if (!class_exists($determined_format['class'])) {
438 fclose($this->fp);
439
440 return $this->error('Format not supported, module "' . $determined_format['include'] . '" was removed.');
441 }
442
443 // module requires iconv support
444 // Check encoding/iconv support
445 if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->encoding,
446 array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) {
447 $errormessage = '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. ';
448 if (self::$EnvironmentIsWindows) {
449 $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32';
450 } else {
451 $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch';
452 }
453
454 return $this->error($errormessage);
455 }
456
457 // instantiate module class
458 $class_name = 'GetId3\\Module\\' . Helper::toCamelCase($determined_format['group'], '-', true) . '\\' . ucfirst($determined_format['module']);
459 if (!class_exists($class_name)) {
460 return $this->error('Format not supported, module "' . $determined_format['include'] . '" is corrupt.');
461 }
462 //if (isset($determined_format['option'])) {
463 // //$class = new $class_name($this->fp, $this->info, $determined_format['option']);
464 //} else {
465 //$class = new $class_name($this->fp, $this->info);
466 $class = new $class_name($this);
467 //}
468
469 if (!empty($determined_format['set_inline_attachments'])) {
470 $class->inline_attachments = $this->option_save_attachments;
471 }
472 $class->analyze();
473
474 unset($class);
475
476 // close file
477 fclose($this->fp);
478
479 // process all tags - copy to 'tags' and convert charsets
480 if ($this->option_tags_process) {
481 $this->HandleAllTags();
482 }
483
484 // perform more calculations
485 if ($this->option_extra_info) {
486 $this->ChannelsBitratePlaytimeCalculations();
487 $this->CalculateCompressionRatioVideo();
488 $this->CalculateCompressionRatioAudio();
489 $this->CalculateReplayGain();
490 $this->ProcessAudioStreams();
491 }
492
493 // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
494 if ($this->option_md5_data) {
495 // do not cald md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too
496 if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) {
497 $this->getHashdata('md5');
498 }
499 }
500
501 // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
502 if ($this->option_sha1_data) {
503 $this->getHashdata('sha1');
504 }
505
506 // remove undesired keys
507 $this->CleanUp();
508 } catch (\Exception $e) {
509 $this->error('Caught exception: ' . $e->getMessage());
510 }
511
512 // return info array
513 return $this->info;
514 }
515
521 private function error($message)
522 {
523 $this->CleanUp();
524 if (!isset($this->info['error'])) {
525 $this->info['error'] = array();
526 }
527 $this->info['error'][] = $message;
528
529 return $this->info;
530 }
531
537 private function warning($message)
538 {
539 $this->info['warning'][] = $message;
540
541 return true;
542 }
543
548 private function CleanUp()
549 {
550
551 // remove possible empty keys
552 $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
553 foreach ($AVpossibleEmptyKeys as $dummy => $key) {
554 if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) {
555 unset($this->info['audio'][$key]);
556 }
557 if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) {
558 unset($this->info['video'][$key]);
559 }
560 }
561
562 // remove empty root keys
563 if (!empty($this->info)) {
564 foreach ($this->info as $key => $value) {
565 if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) {
566 unset($this->info[$key]);
567 }
568 }
569 }
570
571 // remove meaningless entries from unknown-format files
572 if (empty($this->info['fileformat'])) {
573 if (isset($this->info['avdataoffset'])) {
574 unset($this->info['avdataoffset']);
575 }
576 if (isset($this->info['avdataend'])) {
577 unset($this->info['avdataend']);
578 }
579 }
580
581 // remove possible duplicated identical entries
582 if (!empty($this->info['error'])) {
583 $this->info['error'] = array_values(array_unique($this->info['error']));
584 }
585 if (!empty($this->info['warning'])) {
586 $this->info['warning'] = array_values(array_unique($this->info['warning']));
587 }
588
589 // remove "global variable" type keys
590 unset($this->info['php_memory_limit']);
591
592 return true;
593 }
594
600 public function GetFileFormatArray()
601 {
602 static $format_info = array();
603 if (empty($format_info)) {
604 $format_info = array(
605 // Audio formats
606 // AC-3 - audio - Dolby AC-3 / Dolby Digital
607 'ac3' => array(
608 'pattern' => '^\x0B\x77',
609 'group' => 'audio',
610 'module' => 'ac3',
611 'mime_type' => 'audio/ac3',
612 ),
613 // AAC - audio - Advanced Audio Coding (AAC) - ADIF format
614 'adif' => array(
615 'pattern' => '^ADIF',
616 'group' => 'audio',
617 'module' => 'aac',
618 'mime_type' => 'application/octet-stream',
619 'fail_ape' => 'WARNING',
620 ),
621 // AA - audio - Audible Audiobook
622 'aa' => array(
623 'pattern' => '^.{4}\x57\x90\x75\x36',
624 'group' => 'audio',
625 'module' => 'aa',
626 'mime_type' => 'audio/audible',
627 ),
628 // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
629 'adts' => array(
630 'pattern' => '^\xFF[\xF0-\xF1\xF8-\xF9]',
631 'group' => 'audio',
632 'module' => 'aac',
633 'mime_type' => 'application/octet-stream',
634 'fail_ape' => 'WARNING',
635 ),
636 // AU - audio - NeXT/Sun AUdio (AU)
637 'au' => array(
638 'pattern' => '^\.snd',
639 'group' => 'audio',
640 'module' => 'au',
641 'mime_type' => 'audio/basic',
642 ),
643 // AVR - audio - Audio Visual Research
644 'avr' => array(
645 'pattern' => '^2BIT',
646 'group' => 'audio',
647 'module' => 'avr',
648 'mime_type' => 'application/octet-stream',
649 ),
650 // BONK - audio - Bonk v0.9+
651 'bonk' => array(
652 'pattern' => '^\x00(BONK|INFO|META| ID3)',
653 'group' => 'audio',
654 'module' => 'bonk',
655 'mime_type' => 'audio/xmms-bonk',
656 ),
657 // DSS - audio - Digital Speech Standard
658 'dss' => array(
659 'pattern' => '^[\x02-\x03]dss',
660 'group' => 'audio',
661 'module' => 'dss',
662 'mime_type' => 'application/octet-stream',
663 ),
664 // DTS - audio - Dolby Theatre System
665 'dts' => array(
666 'pattern' => '^\x7F\xFE\x80\x01',
667 'group' => 'audio',
668 'module' => 'dts',
669 'mime_type' => 'audio/dts',
670 ),
671 // FLAC - audio - Free Lossless Audio Codec
672 'flac' => array(
673 'pattern' => '^fLaC',
674 'group' => 'audio',
675 'module' => 'flac',
676 'mime_type' => 'audio/x-flac',
677 'set_inline_attachments' => true,
678 ),
679 // LA - audio - Lossless Audio (LA)
680 'la' => array(
681 'pattern' => '^LA0[2-4]',
682 'group' => 'audio',
683 'module' => 'la',
684 'mime_type' => 'application/octet-stream',
685 ),
686 // LPAC - audio - Lossless Predictive Audio Compression (LPAC)
687 'lpac' => array(
688 'pattern' => '^LPAC',
689 'group' => 'audio',
690 'module' => 'lpac',
691 'mime_type' => 'application/octet-stream',
692 ),
693 // MIDI - audio - MIDI (Musical Instrument Digital Interface)
694 'midi' => array(
695 'pattern' => '^MThd',
696 'group' => 'audio',
697 'module' => 'midi',
698 'mime_type' => 'audio/midi',
699 ),
700 // MAC - audio - Monkey's Audio Compressor
701 'mac' => array(
702 'pattern' => '^MAC ',
703 'group' => 'audio',
704 'module' => 'monkey',
705 'mime_type' => 'application/octet-stream',
706 ),
707// has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available
708// // MOD - audio - MODule (assorted sub-formats)
709// 'mod' => array(
710// 'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)',
711// 'group' => 'audio',
712// 'module' => 'mod',
713// 'option' => 'mod',
714// 'mime_type' => 'audio/mod',
715// ),
716 // MOD - audio - MODule (Impulse Tracker)
717 'it' => array(
718 'pattern' => '^IMPM',
719 'group' => 'audio',
720 'module' => 'mod',
721 //'option' => 'it',
722 'mime_type' => 'audio/it',
723 ),
724 // MOD - audio - MODule (eXtended Module, various sub-formats)
725 'xm' => array(
726 'pattern' => '^Extended Module',
727 'group' => 'audio',
728 'module' => 'mod',
729 //'option' => 'xm',
730 'mime_type' => 'audio/xm',
731 ),
732 // MOD - audio - MODule (ScreamTracker)
733 's3m' => array(
734 'pattern' => '^.{44}SCRM',
735 'group' => 'audio',
736 'module' => 'mod',
737 //'option' => 's3m',
738 'mime_type' => 'audio/s3m',
739 ),
740 // MPC - audio - Musepack / MPEGplus
741 'mpc' => array(
742 'pattern' => '^(MPCK|MP\+|[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0])',
743 'group' => 'audio',
744 'module' => 'mpc',
745 'mime_type' => 'audio/x-musepack',
746 ),
747 // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS)
748 'mp3' => array(
749 '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]',
750 'group' => 'audio',
751 'module' => 'mp3',
752 'mime_type' => 'audio/mpeg',
753 ),
754 // OFR - audio - OptimFROG
755 'ofr' => array(
756 'pattern' => '^(\*RIFF|OFR)',
757 'group' => 'audio',
758 'module' => 'optimfrog',
759 'mime_type' => 'application/octet-stream',
760 ),
761 // RKAU - audio - RKive AUdio compressor
762 'rkau' => array(
763 'pattern' => '^RKA',
764 'group' => 'audio',
765 'module' => 'rkau',
766 'mime_type' => 'application/octet-stream',
767 ),
768 // SHN - audio - Shorten
769 'shn' => array(
770 'pattern' => '^ajkg',
771 'group' => 'audio',
772 'module' => 'shorten',
773 'mime_type' => 'audio/xmms-shn',
774 'fail_id3' => 'ERROR',
775 'fail_ape' => 'ERROR',
776 ),
777 // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org)
778 'tta' => array(
779 'pattern' => '^TTA', // could also be '^TTA(\x01|\x02|\x03|2|1)'
780 'group' => 'audio',
781 'module' => 'tta',
782 'mime_type' => 'application/octet-stream',
783 ),
784 // VOC - audio - Creative Voice (VOC)
785 'voc' => array(
786 'pattern' => '^Creative Voice File',
787 'group' => 'audio',
788 'module' => 'voc',
789 'mime_type' => 'audio/voc',
790 ),
791 // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF)
792 'vqf' => array(
793 'pattern' => '^TWIN',
794 'group' => 'audio',
795 'module' => 'vqf',
796 'mime_type' => 'application/octet-stream',
797 ),
798 // WV - audio - WavPack (v4.0+)
799 'wv' => array(
800 'pattern' => '^wvpk',
801 'group' => 'audio',
802 'module' => 'wavpack',
803 'mime_type' => 'application/octet-stream',
804 ),
805 // Audio-Video formats
806 // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
807 'asf' => array(
808 'pattern' => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C',
809 'group' => 'audio-video',
810 'module' => 'asf',
811 'mime_type' => 'video/x-ms-asf',
812 'iconv_req' => false,
813 ),
814 // BINK - audio/video - Bink / Smacker
815 'bink' => array(
816 'pattern' => '^(BIK|SMK)',
817 'group' => 'audio-video',
818 'module' => 'bink',
819 'mime_type' => 'application/octet-stream',
820 ),
821 // FLV - audio/video - FLash Video
822 'flv' => array(
823 'pattern' => '^FLV\x01',
824 'group' => 'audio-video',
825 'module' => 'flv',
826 'mime_type' => 'video/x-flv',
827 ),
828 // MKAV - audio/video - Mastroka
829 'matroska' => array(
830 'pattern' => '^\x1A\x45\xDF\xA3',
831 'group' => 'audio-video',
832 'module' => 'matroska',
833 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
834 'set_inline_attachments' => true,
835 ),
836 // MPEG - audio/video - MPEG (Moving Pictures Experts Group)
837 'mpeg' => array(
838 'pattern' => '^\x00\x00\x01(\xBA|\xB3)',
839 'group' => 'audio-video',
840 'module' => 'mpeg',
841 'mime_type' => 'video/mpeg',
842 ),
843 // NSV - audio/video - Nullsoft Streaming Video (NSV)
844 'nsv' => array(
845 'pattern' => '^NSV[sf]',
846 'group' => 'audio-video',
847 'module' => 'nsv',
848 'mime_type' => 'application/octet-stream',
849 ),
850 // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
851 'ogg' => array(
852 'pattern' => '^OggS',
853 'group' => 'audio',
854 'module' => 'ogg',
855 'mime_type' => 'application/ogg',
856 'fail_id3' => 'WARNING',
857 'fail_ape' => 'WARNING',
858 'set_inline_attachments' => true,
859 ),
860 // QT - audio/video - Quicktime
861 'quicktime' => array(
862 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
863 'group' => 'audio-video',
864 'module' => 'quicktime',
865 'mime_type' => 'video/quicktime',
866 ),
867 // 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)
868 'riff' => array(
869 'pattern' => '^(RIFF|SDSS|FORM)',
870 'group' => 'audio-video',
871 'module' => 'riff',
872 'mime_type' => 'audio/x-wave',
873 'fail_ape' => 'WARNING',
874 ),
875 // Real - audio/video - RealAudio, RealVideo
876 'real' => array(
877 'pattern' => '^(\\.RMF|\\.ra)',
878 'group' => 'audio-video',
879 'module' => 'real',
880 'mime_type' => 'audio/x-realaudio',
881 ),
882 // SWF - audio/video - ShockWave Flash
883 'swf' => array(
884 'pattern' => '^(F|C)WS',
885 'group' => 'audio-video',
886 'module' => 'swf',
887 'mime_type' => 'application/x-shockwave-flash',
888 ),
889 // TS - audio/video - MPEG-2 Transport Stream
890 'ts' => array(
891 'pattern' => '^\x47',
892 'group' => 'audio-video',
893 'module' => 'ts',
894 'mime_type' => 'video/MP2T',
895 ),
896 // Still-Image formats
897 // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
898 'bmp' => array(
899 'pattern' => '^BM',
900 'group' => 'graphic',
901 'module' => 'bmp',
902 'mime_type' => 'image/bmp',
903 'fail_id3' => 'ERROR',
904 'fail_ape' => 'ERROR',
905 ),
906 // GIF - still image - Graphics Interchange Format
907 'gif' => array(
908 'pattern' => '^GIF',
909 'group' => 'graphic',
910 'module' => 'gif',
911 'mime_type' => 'image/gif',
912 'fail_id3' => 'ERROR',
913 'fail_ape' => 'ERROR',
914 ),
915 // JPEG - still image - Joint Photographic Experts Group (JPEG)
916 'jpg' => array(
917 'pattern' => '^\xFF\xD8\xFF',
918 'group' => 'graphic',
919 'module' => 'jpg',
920 'mime_type' => 'image/jpeg',
921 'fail_id3' => 'ERROR',
922 'fail_ape' => 'ERROR',
923 ),
924 // PCD - still image - Kodak Photo CD
925 'pcd' => array(
926 'pattern' => '^.{2048}PCD_IPI\x00',
927 'group' => 'graphic',
928 'module' => 'pcd',
929 'mime_type' => 'image/x-photo-cd',
930 'fail_id3' => 'ERROR',
931 'fail_ape' => 'ERROR',
932 ),
933 // PNG - still image - Portable Network Graphics (PNG)
934 'png' => array(
935 'pattern' => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A',
936 'group' => 'graphic',
937 'module' => 'png',
938 'mime_type' => 'image/png',
939 'fail_id3' => 'ERROR',
940 'fail_ape' => 'ERROR',
941 ),
942 // SVG - still image - Scalable Vector Graphics (SVG)
943 'svg' => array(
944 'pattern' => '(<!DOCTYPE svg PUBLIC |xmlns="http:\/\/www\.w3\.org\/2000\/svg")',
945 'group' => 'graphic',
946 'module' => 'svg',
947 'mime_type' => 'image/svg+xml',
948 'fail_id3' => 'ERROR',
949 'fail_ape' => 'ERROR',
950 ),
951 // TIFF - still image - Tagged Information File Format (TIFF)
952 'tiff' => array(
953 'pattern' => '^(II\x2A\x00|MM\x00\x2A)',
954 'group' => 'graphic',
955 'module' => 'tiff',
956 'mime_type' => 'image/tiff',
957 'fail_id3' => 'ERROR',
958 'fail_ape' => 'ERROR',
959 ),
960 // EFAX - still image - eFax (TIFF derivative)
961 'efax' => array(
962 'pattern' => '^\xDC\xFE',
963 'group' => 'graphic',
964 'module' => 'efax',
965 'mime_type' => 'image/efax',
966 'fail_id3' => 'ERROR',
967 'fail_ape' => 'ERROR',
968 ),
969 // Data formats
970 // ISO - data - International Standards Organization (ISO) CD-ROM Image
971 'iso' => array(
972 'pattern' => '^.{32769}CD001',
973 'group' => 'misc',
974 'module' => 'iso',
975 'mime_type' => 'application/octet-stream',
976 'fail_id3' => 'ERROR',
977 'fail_ape' => 'ERROR',
978 'iconv_req' => false,
979 ),
980 // RAR - data - RAR compressed data
981 'rar' => array(
982 'pattern' => '^Rar\!',
983 'group' => 'archive',
984 'module' => 'rar',
985 'mime_type' => 'application/octet-stream',
986 'fail_id3' => 'ERROR',
987 'fail_ape' => 'ERROR',
988 ),
989 // SZIP - audio/data - SZIP compressed data
990 'szip' => array(
991 'pattern' => '^SZ\x0A\x04',
992 'group' => 'archive',
993 'module' => 'szip',
994 'mime_type' => 'application/octet-stream',
995 'fail_id3' => 'ERROR',
996 'fail_ape' => 'ERROR',
997 ),
998 // TAR - data - TAR compressed data
999 'tar' => array(
1000 '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}',
1001 'group' => 'archive',
1002 'module' => 'tar',
1003 'mime_type' => 'application/x-tar',
1004 'fail_id3' => 'ERROR',
1005 'fail_ape' => 'ERROR',
1006 ),
1007 // GZIP - data - GZIP compressed data
1008 'gz' => array(
1009 'pattern' => '^\x1F\x8B\x08',
1010 'group' => 'archive',
1011 'module' => 'gzip',
1012 'mime_type' => 'application/x-gzip',
1013 'fail_id3' => 'ERROR',
1014 'fail_ape' => 'ERROR',
1015 ),
1016 // ZIP - data - ZIP compressed data
1017 'zip' => array(
1018 'pattern' => '^PK\x03\x04',
1019 'group' => 'archive',
1020 'module' => 'zip',
1021 'mime_type' => 'application/zip',
1022 'fail_id3' => 'ERROR',
1023 'fail_ape' => 'ERROR',
1024 ),
1025 // Misc other formats
1026 // PAR2 - data - Parity Volume Set Specification 2.0
1027 'par2' => array(
1028 'pattern' => '^PAR2\x00PKT',
1029 'group' => 'misc',
1030 'module' => 'par2',
1031 'mime_type' => 'application/octet-stream',
1032 'fail_id3' => 'ERROR',
1033 'fail_ape' => 'ERROR',
1034 ),
1035 // PDF - data - Portable Document Format
1036 'pdf' => array(
1037 'pattern' => '^\x25PDF',
1038 'group' => 'misc',
1039 'module' => 'pdf',
1040 'mime_type' => 'application/pdf',
1041 'fail_id3' => 'ERROR',
1042 'fail_ape' => 'ERROR',
1043 ),
1044 // MSOFFICE - data - ZIP compressed data
1045 'msoffice' => array(
1046 'pattern' => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
1047 'group' => 'misc',
1048 'module' => 'msoffice',
1049 'mime_type' => 'application/octet-stream',
1050 'fail_id3' => 'ERROR',
1051 'fail_ape' => 'ERROR',
1052 ),
1053 // CUE - data - CUEsheet (index to single-file disc images)
1054 'cue' => array(
1055 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
1056 'group' => 'misc',
1057 'module' => 'cue',
1058 'mime_type' => 'application/octet-stream',
1059 ),
1060 );
1061 }
1062
1063 return $format_info;
1064 }
1065
1072 public function GetFileFormat(&$filedata, $filename = '')
1073 {
1074 // this function will determine the format of a file based on usually
1075 // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
1076 // and in the case of ISO CD image, 6 bytes offset 32kb from the start
1077 // of the file).
1078 // Identify file format - loop through $format_info and detect with reg expr
1079 $GetFileFormatArray = $this->GetFileFormatArray();
1080 foreach ($GetFileFormatArray as $format_name => $info) {
1081 // The /s switch on preg_match() forces preg_match() NOT to treat
1082 // newline (0x0A) characters as special chars but do a binary match
1083 if (!empty($info['pattern'])
1084 && preg_match('#' . $info['pattern'] . '#s', $filedata)
1085 ) {
1086 $info['class'] = 'GetId3\\Module\\' . Helper::toCamelCase($info['group'], '-', true) . '\\' . ucfirst($info['module']);
1087 $info['include'] = str_replace('\\', DIRECTORY_SEPARATOR, $info['class']) . '.php';
1088
1089 return $info;
1090 }
1091 }
1092
1093 if (preg_match('#\.mp[123a]$#i', $filename)) {
1094 // Too many mp3 encoders on the market put gabage in front of mpeg files
1095 // use assume format on these if format detection failed
1096 $info = $GetFileFormatArray['mp3'];
1097 $info['class'] = 'GetId3\\Module\\' . Helper::toCamelCase($info['group'], '-', true) . '\\' . ucfirst($info['module']);
1098 $info['include'] = str_replace('\\', DIRECTORY_SEPARATOR, $info['class']) . '.php';
1099
1100 return $info;
1101 } elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#',
1102 $filedata)) {
1103 // there's not really a useful consistent "magic" at the beginning of .cue files to identify them
1104 // so until I think of something better, just go by filename if all other format checks fail
1105 // and verify there's at least one instance of "TRACK xx AUDIO" in the file
1106 $info = $GetFileFormatArray['cue'];
1107 $info['class'] = 'GetId3\\Module\\' . Helper::toCamelCase($info['group'], '-', true) . '\\' . ucfirst($info['module']);
1108 $info['include'] = str_replace('\\', DIRECTORY_SEPARATOR, $info['class']) . '.php';
1109
1110 return $info;
1111 }
1112
1113 return false;
1114 }
1115
1122 public function CharConvert(&$array, $encoding)
1123 {
1124
1125 // identical encoding - end here
1126 if ($encoding == $this->encoding) {
1127 return;
1128 }
1129
1130 // loop thru array
1131 foreach ($array as $key => $value) {
1132
1133 // go recursive
1134 if (is_array($value)) {
1135 $this->CharConvert($array[$key], $encoding);
1136 }
1137
1138 // convert string
1139 elseif (is_string($value)) {
1140 $array[$key] = trim(Helper::iconv_fallback($encoding,
1141 $this->encoding,
1142 $value));
1143 }
1144 }
1145 }
1146
1152 public function HandleAllTags()
1153 {
1154
1155 // key name => array (tag name, character encoding)
1156 static $tags;
1157 if (empty($tags)) {
1158 $tags = array(
1159 'asf' => array('asf', 'UTF-16LE'),
1160 'midi' => array('midi', 'ISO-8859-1'),
1161 'nsv' => array('nsv', 'ISO-8859-1'),
1162 'ogg' => array('vorbiscomment', 'UTF-8'),
1163 'png' => array('png', 'UTF-8'),
1164 'tiff' => array('tiff', 'ISO-8859-1'),
1165 'quicktime' => array('quicktime', 'UTF-8'),
1166 'real' => array('real', 'ISO-8859-1'),
1167 'vqf' => array('vqf', 'ISO-8859-1'),
1168 'zip' => array('zip', 'ISO-8859-1'),
1169 'riff' => array('riff', 'ISO-8859-1'),
1170 'lyrics3' => array('lyrics3', 'ISO-8859-1'),
1171 'id3v1' => array('id3v1', $this->encoding_id3v1),
1172 'id3v2' => array('id3v2', 'UTF-8'), // not according to the specs (every frame can have a different encoding), but GetId3Core() force-converts all encodings to UTF-8
1173 'ape' => array('ape', 'UTF-8'),
1174 'cue' => array('cue', 'ISO-8859-1'),
1175 'matroska' => array('matroska', 'UTF-8'),
1176 'flac' => array('vorbiscomment', 'UTF-8'),
1177 );
1178 }
1179
1180 // loop through comments array
1181 foreach ($tags as $comment_name => $tagname_encoding_array) {
1182 list($tag_name, $encoding) = $tagname_encoding_array;
1183
1184 // fill in default encoding type if not already present
1185 if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) {
1186 $this->info[$comment_name]['encoding'] = $encoding;
1187 }
1188
1189 // copy comments if key name set
1190 if (!empty($this->info[$comment_name]['comments'])) {
1191 foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) {
1192 foreach ($valuearray as $key => $value) {
1193 if (is_string($value)) {
1194 $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
1195 }
1196 if ($value) {
1197 $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value;
1198 }
1199 }
1200 if ($tag_key == 'picture') {
1201 unset($this->info[$comment_name]['comments'][$tag_key]);
1202 }
1203 }
1204
1205 if (!isset($this->info['tags'][$tag_name])) {
1206 // comments are set but contain nothing but empty strings, so skip
1207 continue;
1208 }
1209
1210 if ($this->option_tags_html) {
1211 foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
1212 foreach ($valuearray as $key => $value) {
1213 if (is_string($value)) {
1214 //$this->info['tags_html'][$tag_name][$tag_key][$key] = GetId3_lib::MultiByteCharString2HTML($value, $encoding);
1215 $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('&#0;',
1216 '',
1217 trim(Helper::MultiByteCharString2HTML($value,
1218 $encoding)));
1219 } else {
1220 $this->info['tags_html'][$tag_name][$tag_key][$key] = $value;
1221 }
1222 }
1223 }
1224 }
1225
1226 $this->CharConvert($this->info['tags'][$tag_name], $encoding); // only copy gets converted!
1227 }
1228 }
1229
1230 // pictures can take up a lot of space, and we don't need multiple copies of them
1231 // let there be a single copy in [comments][picture], and not elsewhere
1232 if (!empty($this->info['tags'])) {
1233 $unset_keys = array('tags', 'tags_html');
1234 foreach ($this->info['tags'] as $tagtype => $tagarray) {
1235 foreach ($tagarray as $tagname => $tagdata) {
1236 if ($tagname == 'picture') {
1237 foreach ($tagdata as $key => $tagarray) {
1238 $this->info['comments']['picture'][] = $tagarray;
1239 if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
1240 if (isset($this->info['tags'][$tagtype][$tagname][$key])) {
1241 unset($this->info['tags'][$tagtype][$tagname][$key]);
1242 }
1243 if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) {
1244 unset($this->info['tags_html'][$tagtype][$tagname][$key]);
1245 }
1246 }
1247 }
1248 }
1249 }
1250 foreach ($unset_keys as $unset_key) {
1251 // remove possible empty keys from (e.g. [tags][id3v2][picture])
1252 if (empty($this->info[$unset_key][$tagtype]['picture'])) {
1253 unset($this->info[$unset_key][$tagtype]['picture']);
1254 }
1255 if (empty($this->info[$unset_key][$tagtype])) {
1256 unset($this->info[$unset_key][$tagtype]);
1257 }
1258 if (empty($this->info[$unset_key])) {
1259 unset($this->info[$unset_key]);
1260 }
1261 }
1262 // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
1263 if (isset($this->info[$tagtype]['comments']['picture'])) {
1264 unset($this->info[$tagtype]['comments']['picture']);
1265 }
1266 if (empty($this->info[$tagtype]['comments'])) {
1267 unset($this->info[$tagtype]['comments']);
1268 }
1269 if (empty($this->info[$tagtype])) {
1270 unset($this->info[$tagtype]);
1271 }
1272 }
1273 }
1274
1275 return true;
1276 }
1277
1283 public function getHashdata($algorithm)
1284 {
1285 switch ($algorithm) {
1286 case 'md5':
1287 case 'sha1':
1288 break;
1289
1290 default:
1291 return $this->error('bad algorithm "' . $algorithm . '" in getHashdata()');
1292 break;
1293 }
1294
1295 if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) {
1296
1297 // We cannot get an identical md5_data value for Ogg files where the comments
1298 // span more than 1 Ogg page (compared to the same audio data with smaller
1299 // comments) using the normal GetId3Core() method of MD5'ing the data between the
1300 // end of the comments and the end of the file (minus any trailing tags),
1301 // because the page sequence numbers of the pages that the audio data is on
1302 // do not match. Under normal circumstances, where comments are smaller than
1303 // the nominal 4-8kB page size, then this is not a problem, but if there are
1304 // very large comments, the only way around it is to strip off the comment
1305 // tags with vorbiscomment and MD5 that file.
1306 // This procedure must be applied to ALL Ogg files, not just the ones with
1307 // comments larger than 1 page, because the below method simply MD5's the
1308 // whole file with the comments stripped, not just the portion after the
1309 // comments block (which is the standard GetId3Core() method.
1310 // The above-mentioned problem of comments spanning multiple pages and changing
1311 // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
1312 // currently vorbiscomment only works on OggVorbis files.
1313
1314 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
1315
1316 $this->warning('Failed making system call to vorbiscomment.exe - ' . $algorithm . '_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
1317 $this->info[$algorithm . '_data'] = false;
1318 } else {
1319
1320 // Prevent user from aborting script
1321 $old_abort = ignore_user_abort(true);
1322
1323 // Create empty file
1324 $empty = tempnam(self::getTempDir(), 'getID3');
1325 touch($empty);
1326
1327 // Use vorbiscomment to make temp file without comments
1328 $temp = tempnam(self::getTempDir(), 'getID3');
1329 $file = $this->info['filenamepath'];
1330
1331 if (self::$EnvironmentIsWindows) {
1332
1333 if (file_exists(self::getHelperAppsDir() . 'vorbiscomment.exe')) {
1334
1335 $commandline = '"' . self::getHelperAppsDir() . 'vorbiscomment.exe" -w -c "' . $empty . '" "' . $file . '" "' . $temp . '"';
1336 $VorbisCommentError = `$commandline`;
1337 } else {
1338
1339 $VorbisCommentError = 'vorbiscomment.exe not found in ' . self::getHelperAppsDir();
1340 }
1341 } else {
1342
1343 $commandline = 'vorbiscomment -w -c "' . $empty . '" "' . $file . '" "' . $temp . '" 2>&1';
1344 $commandline = 'vorbiscomment -w -c ' . escapeshellarg($empty) . ' ' . escapeshellarg($file) . ' ' . escapeshellarg($temp) . ' 2>&1';
1345 $VorbisCommentError = `$commandline`;
1346 }
1347
1348 if (!empty($VorbisCommentError)) {
1349
1350 $this->info['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 GetId3Core() directory. Error returned: ' . $VorbisCommentError;
1351 $this->info[$algorithm . '_data'] = false;
1352 } else {
1353
1354 // Get hash of newly created file
1355 switch ($algorithm) {
1356 case 'md5':
1357 $this->info[$algorithm . '_data'] = md5_file($temp);
1358 break;
1359
1360 case 'sha1':
1361 $this->info[$algorithm . '_data'] = sha1_file($temp);
1362 break;
1363 }
1364 }
1365
1366 // Clean up
1367 unlink($empty);
1368 unlink($temp);
1369
1370 // Reset abort setting
1371 ignore_user_abort($old_abort);
1372 }
1373 } else {
1374
1375 if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) {
1376
1377 // get hash from part of file
1378 $this->info[$algorithm . '_data'] = Helper::hash_data($this->info['filenamepath'],
1379 $this->info['avdataoffset'],
1380 $this->info['avdataend'],
1381 $algorithm);
1382 } else {
1383
1384 // get hash from whole file
1385 switch ($algorithm) {
1386 case 'md5':
1387 $this->info[$algorithm . '_data'] = md5_file($this->info['filenamepath']);
1388 break;
1389
1390 case 'sha1':
1391 $this->info[$algorithm . '_data'] = sha1_file($this->info['filenamepath']);
1392 break;
1393 }
1394 }
1395 }
1396
1397 return true;
1398 }
1399
1404 {
1405
1406 // set channelmode on audio
1407 if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) {
1408 // ignore
1409 } elseif ($this->info['audio']['channels'] == 1) {
1410 $this->info['audio']['channelmode'] = 'mono';
1411 } elseif ($this->info['audio']['channels'] == 2) {
1412 $this->info['audio']['channelmode'] = 'stereo';
1413 }
1414
1415 // Calculate combined bitrate - audio + video
1416 $CombinedBitrate = 0;
1417 $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0);
1418 $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0);
1419 if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) {
1420 $this->info['bitrate'] = $CombinedBitrate;
1421 }
1422 //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
1423 // // for example, VBR MPEG video files cannot determine video bitrate:
1424 // // should not set overall bitrate and playtime from audio bitrate only
1425 // unset($this->info['bitrate']);
1426 //}
1427 // video bitrate undetermined, but calculable
1428 if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) {
1429 // if video bitrate not set
1430 if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) {
1431 // AND if audio bitrate is set to same as overall bitrate
1432 if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) {
1433 // AND if playtime is set
1434 if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) {
1435 // AND if AV data offset start/end is known
1436 // THEN we can calculate the video bitrate
1437 $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']);
1438 $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate'];
1439 }
1440 }
1441 }
1442 }
1443
1444 if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) {
1445 $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
1446 }
1447
1448 if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) {
1449 $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds'];
1450 }
1451 if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) {
1452 if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) {
1453 // audio only
1454 $this->info['audio']['bitrate'] = $this->info['bitrate'];
1455 } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) {
1456 // video only
1457 $this->info['video']['bitrate'] = $this->info['bitrate'];
1458 }
1459 }
1460
1461 // Set playtime string
1462 if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
1463 $this->info['playtime_string'] = Helper::PlaytimeString($this->info['playtime_seconds']);
1464 }
1465 }
1466
1472 {
1473 if (empty($this->info['video'])) {
1474 return false;
1475 }
1476 if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) {
1477 return false;
1478 }
1479 if (empty($this->info['video']['bits_per_sample'])) {
1480 return false;
1481 }
1482
1483 switch ($this->info['video']['dataformat']) {
1484 case 'bmp':
1485 case 'gif':
1486 case 'jpeg':
1487 case 'jpg':
1488 case 'png':
1489 case 'tiff':
1490 $FrameRate = 1;
1491 $PlaytimeSeconds = 1;
1492 $BitrateCompressed = $this->info['filesize'] * 8;
1493 break;
1494
1495 default:
1496 if (!empty($this->info['video']['frame_rate'])) {
1497 $FrameRate = $this->info['video']['frame_rate'];
1498 } else {
1499 return false;
1500 }
1501 if (!empty($this->info['playtime_seconds'])) {
1502 $PlaytimeSeconds = $this->info['playtime_seconds'];
1503 } else {
1504 return false;
1505 }
1506 if (!empty($this->info['video']['bitrate'])) {
1507 $BitrateCompressed = $this->info['video']['bitrate'];
1508 } else {
1509 return false;
1510 }
1511 break;
1512 }
1513 $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;
1514
1515 $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
1516
1517 return true;
1518 }
1519
1525 {
1526 if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate'])) {
1527 return false;
1528 }
1529 $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));
1530
1531 if (!empty($this->info['audio']['streams'])) {
1532 foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) {
1533 if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) {
1534 $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16));
1535 }
1536 }
1537 }
1538
1539 return true;
1540 }
1541
1546 public function CalculateReplayGain()
1547 {
1548 if (isset($this->info['replay_gain'])) {
1549 if (!isset($this->info['replay_gain']['reference_volume'])) {
1550 $this->info['replay_gain']['reference_volume'] = (double) 89.0;
1551 }
1552 if (isset($this->info['replay_gain']['track']['adjustment'])) {
1553 $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment'];
1554 }
1555 if (isset($this->info['replay_gain']['album']['adjustment'])) {
1556 $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment'];
1557 }
1558
1559 if (isset($this->info['replay_gain']['track']['peak'])) {
1560 $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - Helper::RGADamplitude2dB($this->info['replay_gain']['track']['peak']);
1561 }
1562 if (isset($this->info['replay_gain']['album']['peak'])) {
1563 $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - Helper::RGADamplitude2dB($this->info['replay_gain']['album']['peak']);
1564 }
1565 }
1566
1567 return true;
1568 }
1569
1574 public function ProcessAudioStreams()
1575 {
1576 if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) {
1577 if (!isset($this->info['audio']['streams'])) {
1578 foreach ($this->info['audio'] as $key => $value) {
1579 if ($key != 'streams') {
1580 $this->info['audio']['streams'][0][$key] = $value;
1581 }
1582 }
1583 }
1584 }
1585
1586 return true;
1587 }
1588
1593 public function GetId3_tempnam()
1594 {
1595 return tempnam($this->tempdir, 'gI3');
1596 }
1597
1601 public static function getTempDir()
1602 {
1603 if (null === self::$TempDir) {
1604 $temp_dir = ini_get('upload_tmp_dir');
1605 if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) {
1606 $temp_dir = '';
1607 }
1608 if (!$temp_dir && function_exists('sys_get_temp_dir')) {
1609 // PHP v5.2.1+
1610 // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts
1611 $temp_dir = sys_get_temp_dir();
1612 }
1613 $temp_dir = realpath($temp_dir);
1614 $open_basedir = ini_get('open_basedir');
1615
1616 if ($open_basedir) {
1617 // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/"
1618 $temp_dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR,
1619 $temp_dir);
1620 $open_basedir = str_replace(array('/', '\\'),
1621 DIRECTORY_SEPARATOR, $open_basedir);
1622 if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) {
1623 $temp_dir .= DIRECTORY_SEPARATOR;
1624 }
1625 $found_valid_tempdir = false;
1626 $open_basedirs = explode(':', $open_basedir);
1627 foreach ($open_basedirs as $basedir) {
1628 if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) {
1629 $basedir .= DIRECTORY_SEPARATOR;
1630 }
1631 if (preg_match('#^' . preg_quote($basedir) . '#', $temp_dir)) {
1632 $found_valid_tempdir = true;
1633 break;
1634 }
1635 }
1636 if (!$found_valid_tempdir) {
1637 $temp_dir = '';
1638 }
1639 unset($open_basedirs, $found_valid_tempdir, $basedir);
1640 }
1641 if (!$temp_dir) {
1642 $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
1643 }
1644 self::$TempDir = $temp_dir;
1645 unset($open_basedir, $temp_dir);
1646 }
1647
1648 return self::$TempDir;
1649 }
1650
1655 public static function environmentIsWindows()
1656 {
1657 // define a static property rather than looking up every time it is needed
1658 if (null === self::$EnvironmentIsWindows) {
1659 self::$EnvironmentIsWindows = strtolower(substr(PHP_OS, 0, 3)) == 'win';
1660 }
1661
1662 return self::$EnvironmentIsWindows;
1663 }
1664
1668 public static function getIncludePath()
1669 {
1670 // Get base path of GetId3Core() - ONCE
1671 if (null === self::$IncludePath) {
1672 foreach (get_included_files() as $val) {
1673 if (basename($val) == 'getid3.php') {
1674 self::$IncludePath = dirname($val) . DIRECTORY_SEPARATOR;
1675 break;
1676 }
1677 }
1678 }
1679
1680 return self::$IncludePath;
1681 }
1682}
An exception for terminatinating execution or to throw for unit testing.
GetId3() by James Heinrich info@getid3.org //.
Definition: GetId3Core.php:26
HandleAllTags()
@staticvar array $tags
setOption($optArray)
public: setOption
Definition: GetId3Core.php:214
GetFileFormat(&$filedata, $filename='')
CleanUp()
private: CleanUp
Definition: GetId3Core.php:548
ChannelsBitratePlaytimeCalculations()
GetFileFormatArray()
@staticvar array $format_info
Definition: GetId3Core.php:600
openfile($filename)
Definition: GetId3Core.php:235
static getIncludePath()
error($message)
private: error handling
Definition: GetId3Core.php:521
static environmentIsWindows()
__construct()
public: constructor
Definition: GetId3Core.php:71
static $HelperAppsDir
Definition: GetId3Core.php:60
CharConvert(&$array, $encoding)
warning($message)
private: warning handling
Definition: GetId3Core.php:537
static getHelperAppsDir()
Definition: GetId3Core.php:186
static $EnvironmentIsWindows
Definition: GetId3Core.php:59
analyze($filename)
public: analyze file
Definition: GetId3Core.php:348
getHashdata($algorithm)
GetId3() by James Heinrich info@getid3.org //.
Definition: Helper.php:27
error($a_errmsg)
set error message @access public
$header
$info
Definition: example_052.php:80
if(!file_exists("$old.txt")) if( $old===$new) if(file_exists("$new.txt")) $file