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