Ticket #23673: 23673.diff
File 23673.diff, 939.7 KB (added by , 12 years ago) |
---|
-
wp-admin/includes/image.php
diff --git wp-admin/includes/image.php wp-admin/includes/image.php index a842287..34e726c 100644
function wp_generate_attachment_metadata( $attachment_id, $file ) { 116 116 if ( $image_meta ) 117 117 $metadata['image_meta'] = $image_meta; 118 118 119 } elseif ( preg_match( '#^video/#', get_post_mime_type( $attachment ) ) ) { 120 $metadata = wp_read_video_metadata( $file ); 121 } elseif ( preg_match( '#^audio/#', get_post_mime_type( $attachment ) ) ) { 122 $metadata = wp_read_audio_metadata( $file ); 119 123 } 120 124 121 125 return apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id ); -
new file wp-includes/ID3/class-getid3.php
diff --git wp-includes/ID3/class-getid3.php wp-includes/ID3/class-getid3.php new file mode 100644 index 0000000..8c18163
- + 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 ///////////////////////////////////////////////////////////////// 7 // // 8 // Please see readme.txt for more information // 9 // /// 10 ///////////////////////////////////////////////////////////////// 11 12 // define a constant rather than looking up every time it is needed 13 if (!defined('GETID3_OS_ISWINDOWS')) { 14 define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0)); 15 } 16 // Get base path of getID3() - ONCE 17 if (!defined('GETID3_INCLUDEPATH')) { 18 define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); 19 } 20 21 // attempt to define temp dir as something flexible but reliable 22 $temp_dir = ini_get('upload_tmp_dir'); 23 if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { 24 $temp_dir = ''; 25 } 26 if (!$temp_dir && function_exists('sys_get_temp_dir')) { 27 // PHP v5.2.1+ 28 // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts 29 $temp_dir = sys_get_temp_dir(); 30 } 31 $temp_dir = realpath($temp_dir); 32 $open_basedir = ini_get('open_basedir'); 33 if ($open_basedir) { 34 // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/" 35 $temp_dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir); 36 $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir); 37 if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) { 38 $temp_dir .= DIRECTORY_SEPARATOR; 39 } 40 $found_valid_tempdir = false; 41 $open_basedirs = explode(PATH_SEPARATOR, $open_basedir); 42 foreach ($open_basedirs as $basedir) { 43 if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) { 44 $basedir .= DIRECTORY_SEPARATOR; 45 } 46 if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) { 47 $found_valid_tempdir = true; 48 break; 49 } 50 } 51 if (!$found_valid_tempdir) { 52 $temp_dir = ''; 53 } 54 unset($open_basedirs, $found_valid_tempdir, $basedir); 55 } 56 if (!$temp_dir) { 57 $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir 58 } 59 // $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system 60 define('GETID3_TEMP_DIR', $temp_dir); 61 unset($open_basedir, $temp_dir); 62 63 // End: Defines 64 65 66 class getID3 67 { 68 // public: Settings 69 public $encoding = 'UTF-8'; // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE 70 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' 71 72 // public: Optional tag checks - disable for speed. 73 public $option_tag_id3v1 = true; // Read and process ID3v1 tags 74 public $option_tag_id3v2 = true; // Read and process ID3v2 tags 75 public $option_tag_lyrics3 = true; // Read and process Lyrics3 tags 76 public $option_tag_apetag = true; // Read and process APE tags 77 public $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding 78 public $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities 79 80 // public: Optional tag/comment calucations 81 public $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc 82 83 // public: Optional handling of embedded attachments (e.g. images) 84 public $option_save_attachments = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility 85 86 // public: Optional calculations 87 public $option_md5_data = false; // Get MD5 sum of data part - slow 88 public $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG 89 public $option_sha1_data = false; // Get SHA1 sum of data part - slow 90 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) 91 92 // public: Read buffer size in bytes 93 public $option_fread_buffer_size = 32768; 94 95 // Public variables 96 public $filename; // Filename of file being analysed. 97 public $fp; // Filepointer to file being analysed. 98 public $info; // Result array. 99 public $tempdir = GETID3_TEMP_DIR; 100 101 // Protected variables 102 protected $startup_error = ''; 103 protected $startup_warning = ''; 104 protected $memory_limit = 0; 105 106 const VERSION = '1.9.5-20130220'; 107 const FREAD_BUFFER_SIZE = 32768; 108 109 const ATTACHMENTS_NONE = false; 110 const ATTACHMENTS_INLINE = true; 111 112 // public: constructor 113 public function __construct() { 114 115 // Check for PHP version 116 $required_php_version = '5.0.5'; 117 if (version_compare(PHP_VERSION, $required_php_version, '<')) { 118 $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION; 119 return false; 120 } 121 122 // Check memory 123 $this->memory_limit = ini_get('memory_limit'); 124 if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) { 125 // could be stored as "16M" rather than 16777216 for example 126 $this->memory_limit = $matches[1] * 1048576; 127 } elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0 128 // could be stored as "2G" rather than 2147483648 for example 129 $this->memory_limit = $matches[1] * 1073741824; 130 } 131 if ($this->memory_limit <= 0) { 132 // memory limits probably disabled 133 } elseif ($this->memory_limit <= 4194304) { 134 $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'; 135 } elseif ($this->memory_limit <= 12582912) { 136 $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'; 137 } 138 139 // Check safe_mode off 140 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { 141 $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); 142 } 143 144 if (intval(ini_get('mbstring.func_overload')) > 0) { 145 $this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.'); 146 } 147 148 // Check for magic_quotes_runtime 149 if (function_exists('get_magic_quotes_runtime')) { 150 if (get_magic_quotes_runtime()) { 151 return $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).'); 152 } 153 } 154 155 // Check for magic_quotes_gpc 156 if (function_exists('magic_quotes_gpc')) { 157 if (get_magic_quotes_gpc()) { 158 return $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).'); 159 } 160 } 161 162 // Load support library 163 if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { 164 $this->startup_error .= 'getid3.lib.php is missing or corrupt'; 165 } 166 167 if ($this->option_max_2gb_check === null) { 168 $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647); 169 } 170 171 172 // Needed for Windows only: 173 // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC 174 // as well as other helper functions such as head, tail, md5sum, etc 175 // This path cannot contain spaces, but the below code will attempt to get the 176 // 8.3-equivalent path automatically 177 // IMPORTANT: This path must include the trailing slash 178 if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) { 179 180 $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path 181 182 if (!is_dir($helperappsdir)) { 183 $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'; 184 } elseif (strpos(realpath($helperappsdir), ' ') !== false) { 185 $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir)); 186 $path_so_far = array(); 187 foreach ($DirPieces as $key => $value) { 188 if (strpos($value, ' ') !== false) { 189 if (!empty($path_so_far)) { 190 $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far)); 191 $dir_listing = `$commandline`; 192 $lines = explode("\n", $dir_listing); 193 foreach ($lines as $line) { 194 $line = trim($line); 195 if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) { 196 list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches; 197 if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) { 198 $value = $shortname; 199 } 200 } 201 } 202 } else { 203 $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.'; 204 } 205 } 206 $path_so_far[] = $value; 207 } 208 $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far); 209 } 210 define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR); 211 } 212 213 return true; 214 } 215 216 public function version() { 217 return self::VERSION; 218 } 219 220 public function fread_buffer_size() { 221 return $this->option_fread_buffer_size; 222 } 223 224 // public: setOption 225 public function setOption($optArray) { 226 if (!is_array($optArray) || empty($optArray)) { 227 return false; 228 } 229 foreach ($optArray as $opt => $val) { 230 if (isset($this->$opt) === false) { 231 continue; 232 } 233 $this->$opt = $val; 234 } 235 return true; 236 } 237 238 public function openfile($filename) { 239 try { 240 if (!empty($this->startup_error)) { 241 throw new getid3_exception($this->startup_error); 242 } 243 if (!empty($this->startup_warning)) { 244 $this->warning($this->startup_warning); 245 } 246 247 // init result array and set parameters 248 $this->filename = $filename; 249 $this->info = array(); 250 $this->info['GETID3_VERSION'] = $this->version(); 251 $this->info['php_memory_limit'] = $this->memory_limit; 252 253 // remote files not supported 254 if (preg_match('/^(ht|f)tp:\/\//', $filename)) { 255 throw new getid3_exception('Remote files are not supported - please copy the file locally first'); 256 } 257 258 $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); 259 $filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename); 260 261 // open local file 262 if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { 263 // great 264 } else { 265 throw new getid3_exception('Could not open "'.$filename.'" (does not exist, or is not a file)'); 266 } 267 268 $this->info['filesize'] = filesize($filename); 269 // set redundant parameters - might be needed in some include file 270 $this->info['filename'] = basename($filename); 271 $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); 272 $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; 273 274 275 // option_max_2gb_check 276 if ($this->option_max_2gb_check) { 277 // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB) 278 // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize 279 // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer 280 $fseek = fseek($this->fp, 0, SEEK_END); 281 if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) || 282 ($this->info['filesize'] < 0) || 283 (ftell($this->fp) < 0)) { 284 $real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']); 285 286 if ($real_filesize === false) { 287 unset($this->info['filesize']); 288 fclose($this->fp); 289 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.'); 290 } elseif (getid3_lib::intValueSupported($real_filesize)) { 291 unset($this->info['filesize']); 292 fclose($this->fp); 293 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'); 294 } 295 $this->info['filesize'] = $real_filesize; 296 $this->error('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.'); 297 } 298 } 299 300 // set more parameters 301 $this->info['avdataoffset'] = 0; 302 $this->info['avdataend'] = $this->info['filesize']; 303 $this->info['fileformat'] = ''; // filled in later 304 $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used 305 $this->info['video']['dataformat'] = ''; // filled in later, unset if not used 306 $this->info['tags'] = array(); // filled in later, unset if not used 307 $this->info['error'] = array(); // filled in later, unset if not used 308 $this->info['warning'] = array(); // filled in later, unset if not used 309 $this->info['comments'] = array(); // filled in later, unset if not used 310 $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired 311 312 return true; 313 314 } catch (Exception $e) { 315 $this->error($e->getMessage()); 316 } 317 return false; 318 } 319 320 // public: analyze file 321 public function analyze($filename) { 322 try { 323 if (!$this->openfile($filename)) { 324 return $this->info; 325 } 326 327 // Handle tags 328 foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { 329 $option_tag = 'option_tag_'.$tag_name; 330 if ($this->$option_tag) { 331 $this->include_module('tag.'.$tag_name); 332 try { 333 $tag_class = 'getid3_'.$tag_name; 334 $tag = new $tag_class($this); 335 $tag->Analyze(); 336 } 337 catch (getid3_exception $e) { 338 throw $e; 339 } 340 } 341 } 342 if (isset($this->info['id3v2']['tag_offset_start'])) { 343 $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']); 344 } 345 foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { 346 if (isset($this->info[$tag_key]['tag_offset_start'])) { 347 $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']); 348 } 349 } 350 351 // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier 352 if (!$this->option_tag_id3v2) { 353 fseek($this->fp, 0, SEEK_SET); 354 $header = fread($this->fp, 10); 355 if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { 356 $this->info['id3v2']['header'] = true; 357 $this->info['id3v2']['majorversion'] = ord($header{3}); 358 $this->info['id3v2']['minorversion'] = ord($header{4}); 359 $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 360 } 361 } 362 363 // read 32 kb file data 364 fseek($this->fp, $this->info['avdataoffset'], SEEK_SET); 365 $formattest = fread($this->fp, 32774); 366 367 // determine format 368 $determined_format = $this->GetFileFormat($formattest, $filename); 369 370 // unable to determine file format 371 if (!$determined_format) { 372 fclose($this->fp); 373 return $this->error('unable to determine file format'); 374 } 375 376 // check for illegal ID3 tags 377 if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { 378 if ($determined_format['fail_id3'] === 'ERROR') { 379 fclose($this->fp); 380 return $this->error('ID3 tags not allowed on this file type.'); 381 } elseif ($determined_format['fail_id3'] === 'WARNING') { 382 $this->warning('ID3 tags not allowed on this file type.'); 383 } 384 } 385 386 // check for illegal APE tags 387 if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { 388 if ($determined_format['fail_ape'] === 'ERROR') { 389 fclose($this->fp); 390 return $this->error('APE tags not allowed on this file type.'); 391 } elseif ($determined_format['fail_ape'] === 'WARNING') { 392 $this->warning('APE tags not allowed on this file type.'); 393 } 394 } 395 396 // set mime type 397 $this->info['mime_type'] = $determined_format['mime_type']; 398 399 // supported format signature pattern detected, but module deleted 400 if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { 401 fclose($this->fp); 402 return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); 403 } 404 405 // module requires iconv support 406 // Check encoding/iconv support 407 if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { 408 $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. '; 409 if (GETID3_OS_ISWINDOWS) { 410 $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'; 411 } else { 412 $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch'; 413 } 414 return $this->error($errormessage); 415 } 416 417 // include module 418 include_once(GETID3_INCLUDEPATH.$determined_format['include']); 419 420 // instantiate module class 421 $class_name = 'getid3_'.$determined_format['module']; 422 if (!class_exists($class_name)) { 423 return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); 424 } 425 $class = new $class_name($this); 426 $class->Analyze(); 427 unset($class); 428 429 // close file 430 fclose($this->fp); 431 432 // process all tags - copy to 'tags' and convert charsets 433 if ($this->option_tags_process) { 434 $this->HandleAllTags(); 435 } 436 437 // perform more calculations 438 if ($this->option_extra_info) { 439 $this->ChannelsBitratePlaytimeCalculations(); 440 $this->CalculateCompressionRatioVideo(); 441 $this->CalculateCompressionRatioAudio(); 442 $this->CalculateReplayGain(); 443 $this->ProcessAudioStreams(); 444 } 445 446 // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags 447 if ($this->option_md5_data) { 448 // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too 449 if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { 450 $this->getHashdata('md5'); 451 } 452 } 453 454 // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags 455 if ($this->option_sha1_data) { 456 $this->getHashdata('sha1'); 457 } 458 459 // remove undesired keys 460 $this->CleanUp(); 461 462 } catch (Exception $e) { 463 $this->error('Caught exception: '.$e->getMessage()); 464 } 465 466 // return info array 467 return $this->info; 468 } 469 470 471 // private: error handling 472 public function error($message) { 473 $this->CleanUp(); 474 if (!isset($this->info['error'])) { 475 $this->info['error'] = array(); 476 } 477 $this->info['error'][] = $message; 478 return $this->info; 479 } 480 481 482 // private: warning handling 483 public function warning($message) { 484 $this->info['warning'][] = $message; 485 return true; 486 } 487 488 489 // private: CleanUp 490 private function CleanUp() { 491 492 // remove possible empty keys 493 $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate'); 494 foreach ($AVpossibleEmptyKeys as $dummy => $key) { 495 if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) { 496 unset($this->info['audio'][$key]); 497 } 498 if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) { 499 unset($this->info['video'][$key]); 500 } 501 } 502 503 // remove empty root keys 504 if (!empty($this->info)) { 505 foreach ($this->info as $key => $value) { 506 if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) { 507 unset($this->info[$key]); 508 } 509 } 510 } 511 512 // remove meaningless entries from unknown-format files 513 if (empty($this->info['fileformat'])) { 514 if (isset($this->info['avdataoffset'])) { 515 unset($this->info['avdataoffset']); 516 } 517 if (isset($this->info['avdataend'])) { 518 unset($this->info['avdataend']); 519 } 520 } 521 522 // remove possible duplicated identical entries 523 if (!empty($this->info['error'])) { 524 $this->info['error'] = array_values(array_unique($this->info['error'])); 525 } 526 if (!empty($this->info['warning'])) { 527 $this->info['warning'] = array_values(array_unique($this->info['warning'])); 528 } 529 530 // remove "global variable" type keys 531 unset($this->info['php_memory_limit']); 532 533 return true; 534 } 535 536 537 // return array containing information about all supported formats 538 public function GetFileFormatArray() { 539 static $format_info = array(); 540 if (empty($format_info)) { 541 $format_info = array( 542 543 // Audio formats 544 545 // AC-3 - audio - Dolby AC-3 / Dolby Digital 546 'ac3' => array( 547 'pattern' => '^\x0B\x77', 548 'group' => 'audio', 549 'module' => 'ac3', 550 'mime_type' => 'audio/ac3', 551 ), 552 553 // AAC - audio - Advanced Audio Coding (AAC) - ADIF format 554 'adif' => array( 555 'pattern' => '^ADIF', 556 'group' => 'audio', 557 'module' => 'aac', 558 'mime_type' => 'application/octet-stream', 559 'fail_ape' => 'WARNING', 560 ), 561 562 /* 563 // AA - audio - Audible Audiobook 564 'aa' => array( 565 'pattern' => '^.{4}\x57\x90\x75\x36', 566 'group' => 'audio', 567 'module' => 'aa', 568 'mime_type' => 'audio/audible', 569 ), 570 */ 571 // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) 572 'adts' => array( 573 'pattern' => '^\xFF[\xF0-\xF1\xF8-\xF9]', 574 'group' => 'audio', 575 'module' => 'aac', 576 'mime_type' => 'application/octet-stream', 577 'fail_ape' => 'WARNING', 578 ), 579 580 581 // AU - audio - NeXT/Sun AUdio (AU) 582 'au' => array( 583 'pattern' => '^\.snd', 584 'group' => 'audio', 585 'module' => 'au', 586 'mime_type' => 'audio/basic', 587 ), 588 589 // AVR - audio - Audio Visual Research 590 'avr' => array( 591 'pattern' => '^2BIT', 592 'group' => 'audio', 593 'module' => 'avr', 594 'mime_type' => 'application/octet-stream', 595 ), 596 597 // BONK - audio - Bonk v0.9+ 598 'bonk' => array( 599 'pattern' => '^\x00(BONK|INFO|META| ID3)', 600 'group' => 'audio', 601 'module' => 'bonk', 602 'mime_type' => 'audio/xmms-bonk', 603 ), 604 605 // DSS - audio - Digital Speech Standard 606 'dss' => array( 607 'pattern' => '^[\x02-\x03]ds[s2]', 608 'group' => 'audio', 609 'module' => 'dss', 610 'mime_type' => 'application/octet-stream', 611 ), 612 613 // DTS - audio - Dolby Theatre System 614 'dts' => array( 615 'pattern' => '^\x7F\xFE\x80\x01', 616 'group' => 'audio', 617 'module' => 'dts', 618 'mime_type' => 'audio/dts', 619 ), 620 621 // FLAC - audio - Free Lossless Audio Codec 622 'flac' => array( 623 'pattern' => '^fLaC', 624 'group' => 'audio', 625 'module' => 'flac', 626 'mime_type' => 'audio/x-flac', 627 ), 628 629 // LA - audio - Lossless Audio (LA) 630 'la' => array( 631 'pattern' => '^LA0[2-4]', 632 'group' => 'audio', 633 'module' => 'la', 634 'mime_type' => 'application/octet-stream', 635 ), 636 637 // LPAC - audio - Lossless Predictive Audio Compression (LPAC) 638 'lpac' => array( 639 'pattern' => '^LPAC', 640 'group' => 'audio', 641 'module' => 'lpac', 642 'mime_type' => 'application/octet-stream', 643 ), 644 645 // MIDI - audio - MIDI (Musical Instrument Digital Interface) 646 'midi' => array( 647 'pattern' => '^MThd', 648 'group' => 'audio', 649 'module' => 'midi', 650 'mime_type' => 'audio/midi', 651 ), 652 653 // MAC - audio - Monkey's Audio Compressor 654 'mac' => array( 655 'pattern' => '^MAC ', 656 'group' => 'audio', 657 'module' => 'monkey', 658 'mime_type' => 'application/octet-stream', 659 ), 660 661 // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available 662 // // MOD - audio - MODule (assorted sub-formats) 663 // 'mod' => array( 664 // 'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)', 665 // 'group' => 'audio', 666 // 'module' => 'mod', 667 // 'option' => 'mod', 668 // 'mime_type' => 'audio/mod', 669 // ), 670 671 // MOD - audio - MODule (Impulse Tracker) 672 'it' => array( 673 'pattern' => '^IMPM', 674 'group' => 'audio', 675 'module' => 'mod', 676 //'option' => 'it', 677 'mime_type' => 'audio/it', 678 ), 679 680 // MOD - audio - MODule (eXtended Module, various sub-formats) 681 'xm' => array( 682 'pattern' => '^Extended Module', 683 'group' => 'audio', 684 'module' => 'mod', 685 //'option' => 'xm', 686 'mime_type' => 'audio/xm', 687 ), 688 689 // MOD - audio - MODule (ScreamTracker) 690 's3m' => array( 691 'pattern' => '^.{44}SCRM', 692 'group' => 'audio', 693 'module' => 'mod', 694 //'option' => 's3m', 695 'mime_type' => 'audio/s3m', 696 ), 697 698 // MPC - audio - Musepack / MPEGplus 699 'mpc' => array( 700 '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])', 701 'group' => 'audio', 702 'module' => 'mpc', 703 'mime_type' => 'audio/x-musepack', 704 ), 705 706 // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS) 707 'mp3' => array( 708 '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]', 709 'group' => 'audio', 710 'module' => 'mp3', 711 'mime_type' => 'audio/mpeg', 712 ), 713 714 // OFR - audio - OptimFROG 715 'ofr' => array( 716 'pattern' => '^(\*RIFF|OFR)', 717 'group' => 'audio', 718 'module' => 'optimfrog', 719 'mime_type' => 'application/octet-stream', 720 ), 721 722 // RKAU - audio - RKive AUdio compressor 723 'rkau' => array( 724 'pattern' => '^RKA', 725 'group' => 'audio', 726 'module' => 'rkau', 727 'mime_type' => 'application/octet-stream', 728 ), 729 730 // SHN - audio - Shorten 731 'shn' => array( 732 'pattern' => '^ajkg', 733 'group' => 'audio', 734 'module' => 'shorten', 735 'mime_type' => 'audio/xmms-shn', 736 'fail_id3' => 'ERROR', 737 'fail_ape' => 'ERROR', 738 ), 739 740 // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org) 741 'tta' => array( 742 'pattern' => '^TTA', // could also be '^TTA(\x01|\x02|\x03|2|1)' 743 'group' => 'audio', 744 'module' => 'tta', 745 'mime_type' => 'application/octet-stream', 746 ), 747 748 // VOC - audio - Creative Voice (VOC) 749 'voc' => array( 750 'pattern' => '^Creative Voice File', 751 'group' => 'audio', 752 'module' => 'voc', 753 'mime_type' => 'audio/voc', 754 ), 755 756 // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF) 757 'vqf' => array( 758 'pattern' => '^TWIN', 759 'group' => 'audio', 760 'module' => 'vqf', 761 'mime_type' => 'application/octet-stream', 762 ), 763 764 // WV - audio - WavPack (v4.0+) 765 'wv' => array( 766 'pattern' => '^wvpk', 767 'group' => 'audio', 768 'module' => 'wavpack', 769 'mime_type' => 'application/octet-stream', 770 ), 771 772 773 // Audio-Video formats 774 775 // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio 776 'asf' => array( 777 'pattern' => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C', 778 'group' => 'audio-video', 779 'module' => 'asf', 780 'mime_type' => 'video/x-ms-asf', 781 'iconv_req' => false, 782 ), 783 784 // BINK - audio/video - Bink / Smacker 785 'bink' => array( 786 'pattern' => '^(BIK|SMK)', 787 'group' => 'audio-video', 788 'module' => 'bink', 789 'mime_type' => 'application/octet-stream', 790 ), 791 792 // FLV - audio/video - FLash Video 793 'flv' => array( 794 'pattern' => '^FLV\x01', 795 'group' => 'audio-video', 796 'module' => 'flv', 797 'mime_type' => 'video/x-flv', 798 ), 799 800 // MKAV - audio/video - Mastroka 801 'matroska' => array( 802 'pattern' => '^\x1A\x45\xDF\xA3', 803 'group' => 'audio-video', 804 'module' => 'matroska', 805 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska 806 ), 807 808 // MPEG - audio/video - MPEG (Moving Pictures Experts Group) 809 'mpeg' => array( 810 'pattern' => '^\x00\x00\x01(\xBA|\xB3)', 811 'group' => 'audio-video', 812 'module' => 'mpeg', 813 'mime_type' => 'video/mpeg', 814 ), 815 816 // NSV - audio/video - Nullsoft Streaming Video (NSV) 817 'nsv' => array( 818 'pattern' => '^NSV[sf]', 819 'group' => 'audio-video', 820 'module' => 'nsv', 821 'mime_type' => 'application/octet-stream', 822 ), 823 824 // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*)) 825 'ogg' => array( 826 'pattern' => '^OggS', 827 'group' => 'audio', 828 'module' => 'ogg', 829 'mime_type' => 'application/ogg', 830 'fail_id3' => 'WARNING', 831 'fail_ape' => 'WARNING', 832 ), 833 834 // QT - audio/video - Quicktime 835 'quicktime' => array( 836 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)', 837 'group' => 'audio-video', 838 'module' => 'quicktime', 839 'mime_type' => 'video/quicktime', 840 ), 841 842 // 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) 843 'riff' => array( 844 'pattern' => '^(RIFF|SDSS|FORM)', 845 'group' => 'audio-video', 846 'module' => 'riff', 847 'mime_type' => 'audio/x-wave', 848 'fail_ape' => 'WARNING', 849 ), 850 851 // Real - audio/video - RealAudio, RealVideo 852 'real' => array( 853 'pattern' => '^(\\.RMF|\\.ra)', 854 'group' => 'audio-video', 855 'module' => 'real', 856 'mime_type' => 'audio/x-realaudio', 857 ), 858 859 // SWF - audio/video - ShockWave Flash 860 'swf' => array( 861 'pattern' => '^(F|C)WS', 862 'group' => 'audio-video', 863 'module' => 'swf', 864 'mime_type' => 'application/x-shockwave-flash', 865 ), 866 867 // TS - audio/video - MPEG-2 Transport Stream 868 'ts' => array( 869 'pattern' => '^(\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G". Check for at least 10 packets matching this pattern 870 'group' => 'audio-video', 871 'module' => 'ts', 872 'mime_type' => 'video/MP2T', 873 ), 874 875 876 // Still-Image formats 877 878 // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4) 879 'bmp' => array( 880 'pattern' => '^BM', 881 'group' => 'graphic', 882 'module' => 'bmp', 883 'mime_type' => 'image/bmp', 884 'fail_id3' => 'ERROR', 885 'fail_ape' => 'ERROR', 886 ), 887 888 // GIF - still image - Graphics Interchange Format 889 'gif' => array( 890 'pattern' => '^GIF', 891 'group' => 'graphic', 892 'module' => 'gif', 893 'mime_type' => 'image/gif', 894 'fail_id3' => 'ERROR', 895 'fail_ape' => 'ERROR', 896 ), 897 898 // JPEG - still image - Joint Photographic Experts Group (JPEG) 899 'jpg' => array( 900 'pattern' => '^\xFF\xD8\xFF', 901 'group' => 'graphic', 902 'module' => 'jpg', 903 'mime_type' => 'image/jpeg', 904 'fail_id3' => 'ERROR', 905 'fail_ape' => 'ERROR', 906 ), 907 908 // PCD - still image - Kodak Photo CD 909 'pcd' => array( 910 'pattern' => '^.{2048}PCD_IPI\x00', 911 'group' => 'graphic', 912 'module' => 'pcd', 913 'mime_type' => 'image/x-photo-cd', 914 'fail_id3' => 'ERROR', 915 'fail_ape' => 'ERROR', 916 ), 917 918 919 // PNG - still image - Portable Network Graphics (PNG) 920 'png' => array( 921 'pattern' => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A', 922 'group' => 'graphic', 923 'module' => 'png', 924 'mime_type' => 'image/png', 925 'fail_id3' => 'ERROR', 926 'fail_ape' => 'ERROR', 927 ), 928 929 930 // SVG - still image - Scalable Vector Graphics (SVG) 931 'svg' => array( 932 'pattern' => '(<!DOCTYPE svg PUBLIC |xmlns="http:\/\/www\.w3\.org\/2000\/svg")', 933 'group' => 'graphic', 934 'module' => 'svg', 935 'mime_type' => 'image/svg+xml', 936 'fail_id3' => 'ERROR', 937 'fail_ape' => 'ERROR', 938 ), 939 940 941 // TIFF - still image - Tagged Information File Format (TIFF) 942 'tiff' => array( 943 'pattern' => '^(II\x2A\x00|MM\x00\x2A)', 944 'group' => 'graphic', 945 'module' => 'tiff', 946 'mime_type' => 'image/tiff', 947 'fail_id3' => 'ERROR', 948 'fail_ape' => 'ERROR', 949 ), 950 951 952 // EFAX - still image - eFax (TIFF derivative) 953 'efax' => array( 954 'pattern' => '^\xDC\xFE', 955 'group' => 'graphic', 956 'module' => 'efax', 957 'mime_type' => 'image/efax', 958 'fail_id3' => 'ERROR', 959 'fail_ape' => 'ERROR', 960 ), 961 962 963 // Data formats 964 965 // ISO - data - International Standards Organization (ISO) CD-ROM Image 966 'iso' => array( 967 'pattern' => '^.{32769}CD001', 968 'group' => 'misc', 969 'module' => 'iso', 970 'mime_type' => 'application/octet-stream', 971 'fail_id3' => 'ERROR', 972 'fail_ape' => 'ERROR', 973 'iconv_req' => false, 974 ), 975 976 // RAR - data - RAR compressed data 977 'rar' => array( 978 'pattern' => '^Rar\!', 979 'group' => 'archive', 980 'module' => 'rar', 981 'mime_type' => 'application/octet-stream', 982 'fail_id3' => 'ERROR', 983 'fail_ape' => 'ERROR', 984 ), 985 986 // SZIP - audio/data - SZIP compressed data 987 'szip' => array( 988 'pattern' => '^SZ\x0A\x04', 989 'group' => 'archive', 990 'module' => 'szip', 991 'mime_type' => 'application/octet-stream', 992 'fail_id3' => 'ERROR', 993 'fail_ape' => 'ERROR', 994 ), 995 996 // TAR - data - TAR compressed data 997 'tar' => array( 998 '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}', 999 'group' => 'archive', 1000 'module' => 'tar', 1001 'mime_type' => 'application/x-tar', 1002 'fail_id3' => 'ERROR', 1003 'fail_ape' => 'ERROR', 1004 ), 1005 1006 // GZIP - data - GZIP compressed data 1007 'gz' => array( 1008 'pattern' => '^\x1F\x8B\x08', 1009 'group' => 'archive', 1010 'module' => 'gzip', 1011 'mime_type' => 'application/x-gzip', 1012 'fail_id3' => 'ERROR', 1013 'fail_ape' => 'ERROR', 1014 ), 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 1026 1027 // Misc other formats 1028 1029 // PAR2 - data - Parity Volume Set Specification 2.0 1030 'par2' => array ( 1031 'pattern' => '^PAR2\x00PKT', 1032 'group' => 'misc', 1033 'module' => 'par2', 1034 'mime_type' => 'application/octet-stream', 1035 'fail_id3' => 'ERROR', 1036 'fail_ape' => 'ERROR', 1037 ), 1038 1039 // PDF - data - Portable Document Format 1040 'pdf' => array( 1041 'pattern' => '^\x25PDF', 1042 'group' => 'misc', 1043 'module' => 'pdf', 1044 'mime_type' => 'application/pdf', 1045 'fail_id3' => 'ERROR', 1046 'fail_ape' => 'ERROR', 1047 ), 1048 1049 // MSOFFICE - data - ZIP compressed data 1050 'msoffice' => array( 1051 'pattern' => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document 1052 'group' => 'misc', 1053 'module' => 'msoffice', 1054 'mime_type' => 'application/octet-stream', 1055 'fail_id3' => 'ERROR', 1056 'fail_ape' => 'ERROR', 1057 ), 1058 1059 // CUE - data - CUEsheet (index to single-file disc images) 1060 'cue' => array( 1061 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents 1062 'group' => 'misc', 1063 'module' => 'cue', 1064 'mime_type' => 'application/octet-stream', 1065 ), 1066 1067 ); 1068 } 1069 1070 return $format_info; 1071 } 1072 1073 1074 1075 public function GetFileFormat(&$filedata, $filename='') { 1076 // this function will determine the format of a file based on usually 1077 // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG, 1078 // and in the case of ISO CD image, 6 bytes offset 32kb from the start 1079 // of the file). 1080 1081 // Identify file format - loop through $format_info and detect with reg expr 1082 foreach ($this->GetFileFormatArray() as $format_name => $info) { 1083 // The /s switch on preg_match() forces preg_match() NOT to treat 1084 // newline (0x0A) characters as special chars but do a binary match 1085 if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) { 1086 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1087 return $info; 1088 } 1089 } 1090 1091 1092 if (preg_match('#\.mp[123a]$#i', $filename)) { 1093 // Too many mp3 encoders on the market put gabage in front of mpeg files 1094 // use assume format on these if format detection failed 1095 $GetFileFormatArray = $this->GetFileFormatArray(); 1096 $info = $GetFileFormatArray['mp3']; 1097 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1098 return $info; 1099 } elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) { 1100 // there's not really a useful consistent "magic" at the beginning of .cue files to identify them 1101 // so until I think of something better, just go by filename if all other format checks fail 1102 // and verify there's at least one instance of "TRACK xx AUDIO" in the file 1103 $GetFileFormatArray = $this->GetFileFormatArray(); 1104 $info = $GetFileFormatArray['cue']; 1105 $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 1106 return $info; 1107 } 1108 1109 return false; 1110 } 1111 1112 1113 // converts array to $encoding charset from $this->encoding 1114 public function CharConvert(&$array, $encoding) { 1115 1116 // identical encoding - end here 1117 if ($encoding == $this->encoding) { 1118 return; 1119 } 1120 1121 // loop thru array 1122 foreach ($array as $key => $value) { 1123 1124 // go recursive 1125 if (is_array($value)) { 1126 $this->CharConvert($array[$key], $encoding); 1127 } 1128 1129 // convert string 1130 elseif (is_string($value)) { 1131 $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value)); 1132 } 1133 } 1134 } 1135 1136 1137 public function HandleAllTags() { 1138 1139 // key name => array (tag name, character encoding) 1140 static $tags; 1141 if (empty($tags)) { 1142 $tags = array( 1143 'asf' => array('asf' , 'UTF-16LE'), 1144 'midi' => array('midi' , 'ISO-8859-1'), 1145 'nsv' => array('nsv' , 'ISO-8859-1'), 1146 'ogg' => array('vorbiscomment' , 'UTF-8'), 1147 'png' => array('png' , 'UTF-8'), 1148 'tiff' => array('tiff' , 'ISO-8859-1'), 1149 'quicktime' => array('quicktime' , 'UTF-8'), 1150 'real' => array('real' , 'ISO-8859-1'), 1151 'vqf' => array('vqf' , 'ISO-8859-1'), 1152 'zip' => array('zip' , 'ISO-8859-1'), 1153 'riff' => array('riff' , 'ISO-8859-1'), 1154 'lyrics3' => array('lyrics3' , 'ISO-8859-1'), 1155 'id3v1' => array('id3v1' , $this->encoding_id3v1), 1156 '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 1157 'ape' => array('ape' , 'UTF-8'), 1158 'cue' => array('cue' , 'ISO-8859-1'), 1159 'matroska' => array('matroska' , 'UTF-8'), 1160 'flac' => array('vorbiscomment' , 'UTF-8'), 1161 'divxtag' => array('divx' , 'ISO-8859-1'), 1162 ); 1163 } 1164 1165 // loop through comments array 1166 foreach ($tags as $comment_name => $tagname_encoding_array) { 1167 list($tag_name, $encoding) = $tagname_encoding_array; 1168 1169 // fill in default encoding type if not already present 1170 if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) { 1171 $this->info[$comment_name]['encoding'] = $encoding; 1172 } 1173 1174 // copy comments if key name set 1175 if (!empty($this->info[$comment_name]['comments'])) { 1176 foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { 1177 foreach ($valuearray as $key => $value) { 1178 if (is_string($value)) { 1179 $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! 1180 } 1181 if ($value) { 1182 $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; 1183 } 1184 } 1185 if ($tag_key == 'picture') { 1186 unset($this->info[$comment_name]['comments'][$tag_key]); 1187 } 1188 } 1189 1190 if (!isset($this->info['tags'][$tag_name])) { 1191 // comments are set but contain nothing but empty strings, so skip 1192 continue; 1193 } 1194 1195 if ($this->option_tags_html) { 1196 foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { 1197 foreach ($valuearray as $key => $value) { 1198 if (is_string($value)) { 1199 //$this->info['tags_html'][$tag_name][$tag_key][$key] = getid3_lib::MultiByteCharString2HTML($value, $encoding); 1200 $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('�', '', trim(getid3_lib::MultiByteCharString2HTML($value, $encoding))); 1201 } else { 1202 $this->info['tags_html'][$tag_name][$tag_key][$key] = $value; 1203 } 1204 } 1205 } 1206 } 1207 1208 $this->CharConvert($this->info['tags'][$tag_name], $encoding); // only copy gets converted! 1209 } 1210 1211 } 1212 1213 // pictures can take up a lot of space, and we don't need multiple copies of them 1214 // let there be a single copy in [comments][picture], and not elsewhere 1215 if (!empty($this->info['tags'])) { 1216 $unset_keys = array('tags', 'tags_html'); 1217 foreach ($this->info['tags'] as $tagtype => $tagarray) { 1218 foreach ($tagarray as $tagname => $tagdata) { 1219 if ($tagname == 'picture') { 1220 foreach ($tagdata as $key => $tagarray) { 1221 $this->info['comments']['picture'][] = $tagarray; 1222 if (isset($tagarray['data']) && isset($tagarray['image_mime'])) { 1223 if (isset($this->info['tags'][$tagtype][$tagname][$key])) { 1224 unset($this->info['tags'][$tagtype][$tagname][$key]); 1225 } 1226 if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) { 1227 unset($this->info['tags_html'][$tagtype][$tagname][$key]); 1228 } 1229 } 1230 } 1231 } 1232 } 1233 foreach ($unset_keys as $unset_key) { 1234 // remove possible empty keys from (e.g. [tags][id3v2][picture]) 1235 if (empty($this->info[$unset_key][$tagtype]['picture'])) { 1236 unset($this->info[$unset_key][$tagtype]['picture']); 1237 } 1238 if (empty($this->info[$unset_key][$tagtype])) { 1239 unset($this->info[$unset_key][$tagtype]); 1240 } 1241 if (empty($this->info[$unset_key])) { 1242 unset($this->info[$unset_key]); 1243 } 1244 } 1245 // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture]) 1246 if (isset($this->info[$tagtype]['comments']['picture'])) { 1247 unset($this->info[$tagtype]['comments']['picture']); 1248 } 1249 if (empty($this->info[$tagtype]['comments'])) { 1250 unset($this->info[$tagtype]['comments']); 1251 } 1252 if (empty($this->info[$tagtype])) { 1253 unset($this->info[$tagtype]); 1254 } 1255 } 1256 } 1257 return true; 1258 } 1259 1260 1261 public function getHashdata($algorithm) { 1262 switch ($algorithm) { 1263 case 'md5': 1264 case 'sha1': 1265 break; 1266 1267 default: 1268 return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()'); 1269 break; 1270 } 1271 1272 if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) { 1273 1274 // We cannot get an identical md5_data value for Ogg files where the comments 1275 // span more than 1 Ogg page (compared to the same audio data with smaller 1276 // comments) using the normal getID3() method of MD5'ing the data between the 1277 // end of the comments and the end of the file (minus any trailing tags), 1278 // because the page sequence numbers of the pages that the audio data is on 1279 // do not match. Under normal circumstances, where comments are smaller than 1280 // the nominal 4-8kB page size, then this is not a problem, but if there are 1281 // very large comments, the only way around it is to strip off the comment 1282 // tags with vorbiscomment and MD5 that file. 1283 // This procedure must be applied to ALL Ogg files, not just the ones with 1284 // comments larger than 1 page, because the below method simply MD5's the 1285 // whole file with the comments stripped, not just the portion after the 1286 // comments block (which is the standard getID3() method. 1287 1288 // The above-mentioned problem of comments spanning multiple pages and changing 1289 // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but 1290 // currently vorbiscomment only works on OggVorbis files. 1291 1292 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { 1293 1294 $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'); 1295 $this->info[$algorithm.'_data'] = false; 1296 1297 } else { 1298 1299 // Prevent user from aborting script 1300 $old_abort = ignore_user_abort(true); 1301 1302 // Create empty file 1303 $empty = tempnam(GETID3_TEMP_DIR, 'getID3'); 1304 touch($empty); 1305 1306 // Use vorbiscomment to make temp file without comments 1307 $temp = tempnam(GETID3_TEMP_DIR, 'getID3'); 1308 $file = $this->info['filenamepath']; 1309 1310 if (GETID3_OS_ISWINDOWS) { 1311 1312 if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { 1313 1314 $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"'; 1315 $VorbisCommentError = `$commandline`; 1316 1317 } else { 1318 1319 $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; 1320 1321 } 1322 1323 } else { 1324 1325 $commandline = 'vorbiscomment -w -c "'.$empty.'" "'.$file.'" "'.$temp.'" 2>&1'; 1326 $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1'; 1327 $VorbisCommentError = `$commandline`; 1328 1329 } 1330 1331 if (!empty($VorbisCommentError)) { 1332 1333 $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 getID3() directory. Error returned: '.$VorbisCommentError; 1334 $this->info[$algorithm.'_data'] = false; 1335 1336 } else { 1337 1338 // Get hash of newly created file 1339 switch ($algorithm) { 1340 case 'md5': 1341 $this->info[$algorithm.'_data'] = md5_file($temp); 1342 break; 1343 1344 case 'sha1': 1345 $this->info[$algorithm.'_data'] = sha1_file($temp); 1346 break; 1347 } 1348 } 1349 1350 // Clean up 1351 unlink($empty); 1352 unlink($temp); 1353 1354 // Reset abort setting 1355 ignore_user_abort($old_abort); 1356 1357 } 1358 1359 } else { 1360 1361 if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) { 1362 1363 // get hash from part of file 1364 $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm); 1365 1366 } else { 1367 1368 // get hash from whole file 1369 switch ($algorithm) { 1370 case 'md5': 1371 $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']); 1372 break; 1373 1374 case 'sha1': 1375 $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']); 1376 break; 1377 } 1378 } 1379 1380 } 1381 return true; 1382 } 1383 1384 1385 public function ChannelsBitratePlaytimeCalculations() { 1386 1387 // set channelmode on audio 1388 if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) { 1389 // ignore 1390 } elseif ($this->info['audio']['channels'] == 1) { 1391 $this->info['audio']['channelmode'] = 'mono'; 1392 } elseif ($this->info['audio']['channels'] == 2) { 1393 $this->info['audio']['channelmode'] = 'stereo'; 1394 } 1395 1396 // Calculate combined bitrate - audio + video 1397 $CombinedBitrate = 0; 1398 $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0); 1399 $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0); 1400 if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) { 1401 $this->info['bitrate'] = $CombinedBitrate; 1402 } 1403 //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) { 1404 // // for example, VBR MPEG video files cannot determine video bitrate: 1405 // // should not set overall bitrate and playtime from audio bitrate only 1406 // unset($this->info['bitrate']); 1407 //} 1408 1409 // video bitrate undetermined, but calculable 1410 if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) { 1411 // if video bitrate not set 1412 if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) { 1413 // AND if audio bitrate is set to same as overall bitrate 1414 if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) { 1415 // AND if playtime is set 1416 if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) { 1417 // AND if AV data offset start/end is known 1418 // THEN we can calculate the video bitrate 1419 $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']); 1420 $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate']; 1421 } 1422 } 1423 } 1424 } 1425 1426 if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) { 1427 $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate']; 1428 } 1429 1430 if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) { 1431 $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']; 1432 } 1433 if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) { 1434 if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) { 1435 // audio only 1436 $this->info['audio']['bitrate'] = $this->info['bitrate']; 1437 } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) { 1438 // video only 1439 $this->info['video']['bitrate'] = $this->info['bitrate']; 1440 } 1441 } 1442 1443 // Set playtime string 1444 if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) { 1445 $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']); 1446 } 1447 } 1448 1449 1450 public function CalculateCompressionRatioVideo() { 1451 if (empty($this->info['video'])) { 1452 return false; 1453 } 1454 if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) { 1455 return false; 1456 } 1457 if (empty($this->info['video']['bits_per_sample'])) { 1458 return false; 1459 } 1460 1461 switch ($this->info['video']['dataformat']) { 1462 case 'bmp': 1463 case 'gif': 1464 case 'jpeg': 1465 case 'jpg': 1466 case 'png': 1467 case 'tiff': 1468 $FrameRate = 1; 1469 $PlaytimeSeconds = 1; 1470 $BitrateCompressed = $this->info['filesize'] * 8; 1471 break; 1472 1473 default: 1474 if (!empty($this->info['video']['frame_rate'])) { 1475 $FrameRate = $this->info['video']['frame_rate']; 1476 } else { 1477 return false; 1478 } 1479 if (!empty($this->info['playtime_seconds'])) { 1480 $PlaytimeSeconds = $this->info['playtime_seconds']; 1481 } else { 1482 return false; 1483 } 1484 if (!empty($this->info['video']['bitrate'])) { 1485 $BitrateCompressed = $this->info['video']['bitrate']; 1486 } else { 1487 return false; 1488 } 1489 break; 1490 } 1491 $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate; 1492 1493 $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed; 1494 return true; 1495 } 1496 1497 1498 public function CalculateCompressionRatioAudio() { 1499 if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) { 1500 return false; 1501 } 1502 $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)); 1503 1504 if (!empty($this->info['audio']['streams'])) { 1505 foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) { 1506 if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) { 1507 $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16)); 1508 } 1509 } 1510 } 1511 return true; 1512 } 1513 1514 1515 public function CalculateReplayGain() { 1516 if (isset($this->info['replay_gain'])) { 1517 if (!isset($this->info['replay_gain']['reference_volume'])) { 1518 $this->info['replay_gain']['reference_volume'] = (double) 89.0; 1519 } 1520 if (isset($this->info['replay_gain']['track']['adjustment'])) { 1521 $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; 1522 } 1523 if (isset($this->info['replay_gain']['album']['adjustment'])) { 1524 $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment']; 1525 } 1526 1527 if (isset($this->info['replay_gain']['track']['peak'])) { 1528 $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']); 1529 } 1530 if (isset($this->info['replay_gain']['album']['peak'])) { 1531 $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']); 1532 } 1533 } 1534 return true; 1535 } 1536 1537 public function ProcessAudioStreams() { 1538 if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { 1539 if (!isset($this->info['audio']['streams'])) { 1540 foreach ($this->info['audio'] as $key => $value) { 1541 if ($key != 'streams') { 1542 $this->info['audio']['streams'][0][$key] = $value; 1543 } 1544 } 1545 } 1546 } 1547 return true; 1548 } 1549 1550 public function getid3_tempnam() { 1551 return tempnam($this->tempdir, 'gI3'); 1552 } 1553 1554 public function include_module($name) { 1555 //if (!file_exists($this->include_path.'module.'.$name.'.php')) { 1556 if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { 1557 throw new getid3_exception('Required module.'.$name.'.php is missing.'); 1558 } 1559 include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php'); 1560 return true; 1561 } 1562 1563 } 1564 1565 1566 abstract class getid3_handler 1567 { 1568 protected $getid3; // pointer 1569 1570 protected $data_string_flag = false; // analyzing filepointer or string 1571 protected $data_string = ''; // string to analyze 1572 protected $data_string_position = 0; // seek position in string 1573 protected $data_string_length = 0; // string length 1574 1575 private $dependency_to = null; 1576 1577 1578 public function __construct(getID3 $getid3, $call_module=null) { 1579 $this->getid3 = $getid3; 1580 1581 if ($call_module) { 1582 $this->dependency_to = str_replace('getid3_', '', $call_module); 1583 } 1584 } 1585 1586 1587 // Analyze from file pointer 1588 abstract public function Analyze(); 1589 1590 1591 // Analyze from string instead 1592 public function AnalyzeString($string) { 1593 // Enter string mode 1594 $this->setStringMode($string); 1595 1596 // Save info 1597 $saved_avdataoffset = $this->getid3->info['avdataoffset']; 1598 $saved_avdataend = $this->getid3->info['avdataend']; 1599 $saved_filesize = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call 1600 1601 // Reset some info 1602 $this->getid3->info['avdataoffset'] = 0; 1603 $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = $this->data_string_length; 1604 1605 // Analyze 1606 $this->Analyze(); 1607 1608 // Restore some info 1609 $this->getid3->info['avdataoffset'] = $saved_avdataoffset; 1610 $this->getid3->info['avdataend'] = $saved_avdataend; 1611 $this->getid3->info['filesize'] = $saved_filesize; 1612 1613 // Exit string mode 1614 $this->data_string_flag = false; 1615 } 1616 1617 public function setStringMode($string) { 1618 $this->data_string_flag = true; 1619 $this->data_string = $string; 1620 $this->data_string_length = strlen($string); 1621 } 1622 1623 protected function ftell() { 1624 if ($this->data_string_flag) { 1625 return $this->data_string_position; 1626 } 1627 return ftell($this->getid3->fp); 1628 } 1629 1630 protected function fread($bytes) { 1631 if ($this->data_string_flag) { 1632 $this->data_string_position += $bytes; 1633 return substr($this->data_string, $this->data_string_position - $bytes, $bytes); 1634 } 1635 $pos = $this->ftell() + $bytes; 1636 if (!getid3_lib::intValueSupported($pos)) { 1637 throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10); 1638 } 1639 return fread($this->getid3->fp, $bytes); 1640 } 1641 1642 protected function fseek($bytes, $whence=SEEK_SET) { 1643 if ($this->data_string_flag) { 1644 switch ($whence) { 1645 case SEEK_SET: 1646 $this->data_string_position = $bytes; 1647 break; 1648 1649 case SEEK_CUR: 1650 $this->data_string_position += $bytes; 1651 break; 1652 1653 case SEEK_END: 1654 $this->data_string_position = $this->data_string_length + $bytes; 1655 break; 1656 } 1657 return 0; 1658 } else { 1659 $pos = $bytes; 1660 if ($whence == SEEK_CUR) { 1661 $pos = $this->ftell() + $bytes; 1662 } elseif ($whence == SEEK_END) { 1663 $pos = $this->info['filesize'] + $bytes; 1664 } 1665 if (!getid3_lib::intValueSupported($pos)) { 1666 throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10); 1667 } 1668 } 1669 return fseek($this->getid3->fp, $bytes, $whence); 1670 } 1671 1672 protected function feof() { 1673 if ($this->data_string_flag) { 1674 return $this->data_string_position >= $this->data_string_length; 1675 } 1676 return feof($this->getid3->fp); 1677 } 1678 1679 final protected function isDependencyFor($module) { 1680 return $this->dependency_to == $module; 1681 } 1682 1683 protected function error($text) 1684 { 1685 $this->getid3->info['error'][] = $text; 1686 1687 return false; 1688 } 1689 1690 protected function warning($text) 1691 { 1692 return $this->getid3->warning($text); 1693 } 1694 1695 protected function notice($text) 1696 { 1697 // does nothing for now 1698 } 1699 1700 public function saveAttachment($name, $offset, $length, $image_mime=null) { 1701 try { 1702 1703 // do not extract at all 1704 if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) { 1705 1706 $attachment = null; // do not set any 1707 1708 // extract to return array 1709 } elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { 1710 1711 $this->fseek($offset); 1712 $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory 1713 if ($attachment === false || strlen($attachment) != $length) { 1714 throw new Exception('failed to read attachment data'); 1715 } 1716 1717 // assume directory path is given 1718 } else { 1719 1720 // set up destination path 1721 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); 1722 if (!is_dir($dir) || !is_writable($dir)) { // check supplied directory 1723 throw new Exception('supplied path ('.$dir.') does not exist, or is not writable'); 1724 } 1725 $dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : ''); 1726 1727 // create dest file 1728 if (($fp_dest = fopen($dest, 'wb')) == false) { 1729 throw new Exception('failed to create file '.$dest); 1730 } 1731 1732 // copy data 1733 $this->fseek($offset); 1734 $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size()); 1735 $bytesleft = $length; 1736 while ($bytesleft > 0) { 1737 if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) { 1738 throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space'); 1739 } 1740 $bytesleft -= $byteswritten; 1741 } 1742 1743 fclose($fp_dest); 1744 $attachment = $dest; 1745 1746 } 1747 1748 } catch (Exception $e) { 1749 1750 // close and remove dest file if created 1751 if (isset($fp_dest) && is_resource($fp_dest)) { 1752 fclose($fp_dest); 1753 unlink($dest); 1754 } 1755 1756 // do not set any is case of error 1757 $attachment = null; 1758 $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage()); 1759 1760 } 1761 1762 // seek to the end of attachment 1763 $this->fseek($offset + $length); 1764 1765 return $attachment; 1766 } 1767 } 1768 1769 class getid3_exception extends Exception { 1770 public $message; 1771 } 1772 No newline at end of file -
new file wp-includes/ID3/getid3.lib.php
diff --git wp-includes/ID3/getid3.lib.php wp-includes/ID3/getid3.lib.php new file mode 100644 index 0000000..f0b9c94
- + 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 ///////////////////////////////////////////////////////////////// 7 // // 8 // getid3.lib.php - part of getID3() // 9 // See readme.txt for more details // 10 // /// 11 ///////////////////////////////////////////////////////////////// 12 13 14 class getid3_lib 15 { 16 17 public static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') { 18 $returnstring = ''; 19 for ($i = 0; $i < strlen($string); $i++) { 20 if ($hex) { 21 $returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT); 22 } else { 23 $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : '¤'); 24 } 25 if ($spaces) { 26 $returnstring .= ' '; 27 } 28 } 29 if (!empty($htmlencoding)) { 30 if ($htmlencoding === true) { 31 $htmlencoding = 'UTF-8'; // prior to getID3 v1.9.0 the function's 4th parameter was boolean 32 } 33 $returnstring = htmlentities($returnstring, ENT_QUOTES, $htmlencoding); 34 } 35 return $returnstring; 36 } 37 38 public static function trunc($floatnumber) { 39 // truncates a floating-point number at the decimal point 40 // returns int (if possible, otherwise float) 41 if ($floatnumber >= 1) { 42 $truncatednumber = floor($floatnumber); 43 } elseif ($floatnumber <= -1) { 44 $truncatednumber = ceil($floatnumber); 45 } else { 46 $truncatednumber = 0; 47 } 48 if (self::intValueSupported($truncatednumber)) { 49 $truncatednumber = (int) $truncatednumber; 50 } 51 return $truncatednumber; 52 } 53 54 55 public static function safe_inc(&$variable, $increment=1) { 56 if (isset($variable)) { 57 $variable += $increment; 58 } else { 59 $variable = $increment; 60 } 61 return true; 62 } 63 64 public static function CastAsInt($floatnum) { 65 // convert to float if not already 66 $floatnum = (float) $floatnum; 67 68 // convert a float to type int, only if possible 69 if (self::trunc($floatnum) == $floatnum) { 70 // it's not floating point 71 if (self::intValueSupported($floatnum)) { 72 // it's within int range 73 $floatnum = (int) $floatnum; 74 } 75 } 76 return $floatnum; 77 } 78 79 public static function intValueSupported($num) { 80 // check if integers are 64-bit 81 static $hasINT64 = null; 82 if ($hasINT64 === null) { // 10x faster than is_null() 83 $hasINT64 = is_int(pow(2, 31)); // 32-bit int are limited to (2^31)-1 84 if (!$hasINT64 && !defined('PHP_INT_MIN')) { 85 define('PHP_INT_MIN', ~PHP_INT_MAX); 86 } 87 } 88 // if integers are 64-bit - no other check required 89 if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) { 90 return true; 91 } 92 return false; 93 } 94 95 public static function DecimalizeFraction($fraction) { 96 list($numerator, $denominator) = explode('/', $fraction); 97 return $numerator / ($denominator ? $denominator : 1); 98 } 99 100 101 public static function DecimalBinary2Float($binarynumerator) { 102 $numerator = self::Bin2Dec($binarynumerator); 103 $denominator = self::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator))); 104 return ($numerator / $denominator); 105 } 106 107 108 public static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { 109 // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html 110 if (strpos($binarypointnumber, '.') === false) { 111 $binarypointnumber = '0.'.$binarypointnumber; 112 } elseif ($binarypointnumber{0} == '.') { 113 $binarypointnumber = '0'.$binarypointnumber; 114 } 115 $exponent = 0; 116 while (($binarypointnumber{0} != '1') || (substr($binarypointnumber, 1, 1) != '.')) { 117 if (substr($binarypointnumber, 1, 1) == '.') { 118 $exponent--; 119 $binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3); 120 } else { 121 $pointpos = strpos($binarypointnumber, '.'); 122 $exponent += ($pointpos - 1); 123 $binarypointnumber = str_replace('.', '', $binarypointnumber); 124 $binarypointnumber = $binarypointnumber{0}.'.'.substr($binarypointnumber, 1); 125 } 126 } 127 $binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT); 128 return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent); 129 } 130 131 132 public static function Float2BinaryDecimal($floatvalue) { 133 // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html 134 $maxbits = 128; // to how many bits of precision should the calculations be taken? 135 $intpart = self::trunc($floatvalue); 136 $floatpart = abs($floatvalue - $intpart); 137 $pointbitstring = ''; 138 while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) { 139 $floatpart *= 2; 140 $pointbitstring .= (string) self::trunc($floatpart); 141 $floatpart -= self::trunc($floatpart); 142 } 143 $binarypointnumber = decbin($intpart).'.'.$pointbitstring; 144 return $binarypointnumber; 145 } 146 147 148 public static function Float2String($floatvalue, $bits) { 149 // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html 150 switch ($bits) { 151 case 32: 152 $exponentbits = 8; 153 $fractionbits = 23; 154 break; 155 156 case 64: 157 $exponentbits = 11; 158 $fractionbits = 52; 159 break; 160 161 default: 162 return false; 163 break; 164 } 165 if ($floatvalue >= 0) { 166 $signbit = '0'; 167 } else { 168 $signbit = '1'; 169 } 170 $normalizedbinary = self::NormalizeBinaryPoint(self::Float2BinaryDecimal($floatvalue), $fractionbits); 171 $biasedexponent = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent 172 $exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT); 173 $fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT); 174 175 return self::BigEndian2String(self::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false); 176 } 177 178 179 public static function LittleEndian2Float($byteword) { 180 return self::BigEndian2Float(strrev($byteword)); 181 } 182 183 184 public static function BigEndian2Float($byteword) { 185 // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic 186 // http://www.psc.edu/general/software/packages/ieee/ieee.html 187 // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html 188 189 $bitword = self::BigEndian2Bin($byteword); 190 if (!$bitword) { 191 return 0; 192 } 193 $signbit = $bitword{0}; 194 195 switch (strlen($byteword) * 8) { 196 case 32: 197 $exponentbits = 8; 198 $fractionbits = 23; 199 break; 200 201 case 64: 202 $exponentbits = 11; 203 $fractionbits = 52; 204 break; 205 206 case 80: 207 // 80-bit Apple SANE format 208 // http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ 209 $exponentstring = substr($bitword, 1, 15); 210 $isnormalized = intval($bitword{16}); 211 $fractionstring = substr($bitword, 17, 63); 212 $exponent = pow(2, self::Bin2Dec($exponentstring) - 16383); 213 $fraction = $isnormalized + self::DecimalBinary2Float($fractionstring); 214 $floatvalue = $exponent * $fraction; 215 if ($signbit == '1') { 216 $floatvalue *= -1; 217 } 218 return $floatvalue; 219 break; 220 221 default: 222 return false; 223 break; 224 } 225 $exponentstring = substr($bitword, 1, $exponentbits); 226 $fractionstring = substr($bitword, $exponentbits + 1, $fractionbits); 227 $exponent = self::Bin2Dec($exponentstring); 228 $fraction = self::Bin2Dec($fractionstring); 229 230 if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) { 231 // Not a Number 232 $floatvalue = false; 233 } elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) { 234 if ($signbit == '1') { 235 $floatvalue = '-infinity'; 236 } else { 237 $floatvalue = '+infinity'; 238 } 239 } elseif (($exponent == 0) && ($fraction == 0)) { 240 if ($signbit == '1') { 241 $floatvalue = -0; 242 } else { 243 $floatvalue = 0; 244 } 245 $floatvalue = ($signbit ? 0 : -0); 246 } elseif (($exponent == 0) && ($fraction != 0)) { 247 // These are 'unnormalized' values 248 $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * self::DecimalBinary2Float($fractionstring); 249 if ($signbit == '1') { 250 $floatvalue *= -1; 251 } 252 } elseif ($exponent != 0) { 253 $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + self::DecimalBinary2Float($fractionstring)); 254 if ($signbit == '1') { 255 $floatvalue *= -1; 256 } 257 } 258 return (float) $floatvalue; 259 } 260 261 262 public static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { 263 $intvalue = 0; 264 $bytewordlen = strlen($byteword); 265 if ($bytewordlen == 0) { 266 return false; 267 } 268 for ($i = 0; $i < $bytewordlen; $i++) { 269 if ($synchsafe) { // disregard MSB, effectively 7-bit bytes 270 //$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); // faster, but runs into problems past 2^31 on 32-bit systems 271 $intvalue += (ord($byteword{$i}) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7); 272 } else { 273 $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i)); 274 } 275 } 276 if ($signed && !$synchsafe) { 277 // synchsafe ints are not allowed to be signed 278 if ($bytewordlen <= PHP_INT_SIZE) { 279 $signMaskBit = 0x80 << (8 * ($bytewordlen - 1)); 280 if ($intvalue & $signMaskBit) { 281 $intvalue = 0 - ($intvalue & ($signMaskBit - 1)); 282 } 283 } else { 284 throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits ('.strlen($byteword).') in self::BigEndian2Int()'); 285 break; 286 } 287 } 288 return self::CastAsInt($intvalue); 289 } 290 291 292 public static function LittleEndian2Int($byteword, $signed=false) { 293 return self::BigEndian2Int(strrev($byteword), false, $signed); 294 } 295 296 297 public static function BigEndian2Bin($byteword) { 298 $binvalue = ''; 299 $bytewordlen = strlen($byteword); 300 for ($i = 0; $i < $bytewordlen; $i++) { 301 $binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT); 302 } 303 return $binvalue; 304 } 305 306 307 public static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { 308 if ($number < 0) { 309 throw new Exception('ERROR: self::BigEndian2String() does not support negative numbers'); 310 } 311 $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF); 312 $intstring = ''; 313 if ($signed) { 314 if ($minbytes > PHP_INT_SIZE) { 315 throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits in self::BigEndian2String()'); 316 } 317 $number = $number & (0x80 << (8 * ($minbytes - 1))); 318 } 319 while ($number != 0) { 320 $quotient = ($number / ($maskbyte + 1)); 321 $intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring; 322 $number = floor($quotient); 323 } 324 return str_pad($intstring, $minbytes, "\x00", STR_PAD_LEFT); 325 } 326 327 328 public static function Dec2Bin($number) { 329 while ($number >= 256) { 330 $bytes[] = (($number / 256) - (floor($number / 256))) * 256; 331 $number = floor($number / 256); 332 } 333 $bytes[] = $number; 334 $binstring = ''; 335 for ($i = 0; $i < count($bytes); $i++) { 336 $binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring; 337 } 338 return $binstring; 339 } 340 341 342 public static function Bin2Dec($binstring, $signed=false) { 343 $signmult = 1; 344 if ($signed) { 345 if ($binstring{0} == '1') { 346 $signmult = -1; 347 } 348 $binstring = substr($binstring, 1); 349 } 350 $decvalue = 0; 351 for ($i = 0; $i < strlen($binstring); $i++) { 352 $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i); 353 } 354 return self::CastAsInt($decvalue * $signmult); 355 } 356 357 358 public static function Bin2String($binstring) { 359 // return 'hi' for input of '0110100001101001' 360 $string = ''; 361 $binstringreversed = strrev($binstring); 362 for ($i = 0; $i < strlen($binstringreversed); $i += 8) { 363 $string = chr(self::Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string; 364 } 365 return $string; 366 } 367 368 369 public static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { 370 $intstring = ''; 371 while ($number > 0) { 372 if ($synchsafe) { 373 $intstring = $intstring.chr($number & 127); 374 $number >>= 7; 375 } else { 376 $intstring = $intstring.chr($number & 255); 377 $number >>= 8; 378 } 379 } 380 return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT); 381 } 382 383 384 public static function array_merge_clobber($array1, $array2) { 385 // written by kcØhireability*com 386 // taken from http://www.php.net/manual/en/function.array-merge-recursive.php 387 if (!is_array($array1) || !is_array($array2)) { 388 return false; 389 } 390 $newarray = $array1; 391 foreach ($array2 as $key => $val) { 392 if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { 393 $newarray[$key] = self::array_merge_clobber($newarray[$key], $val); 394 } else { 395 $newarray[$key] = $val; 396 } 397 } 398 return $newarray; 399 } 400 401 402 public static function array_merge_noclobber($array1, $array2) { 403 if (!is_array($array1) || !is_array($array2)) { 404 return false; 405 } 406 $newarray = $array1; 407 foreach ($array2 as $key => $val) { 408 if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { 409 $newarray[$key] = self::array_merge_noclobber($newarray[$key], $val); 410 } elseif (!isset($newarray[$key])) { 411 $newarray[$key] = $val; 412 } 413 } 414 return $newarray; 415 } 416 417 418 public static function ksort_recursive(&$theArray) { 419 ksort($theArray); 420 foreach ($theArray as $key => $value) { 421 if (is_array($value)) { 422 self::ksort_recursive($theArray[$key]); 423 } 424 } 425 return true; 426 } 427 428 public static function fileextension($filename, $numextensions=1) { 429 if (strstr($filename, '.')) { 430 $reversedfilename = strrev($filename); 431 $offset = 0; 432 for ($i = 0; $i < $numextensions; $i++) { 433 $offset = strpos($reversedfilename, '.', $offset + 1); 434 if ($offset === false) { 435 return ''; 436 } 437 } 438 return strrev(substr($reversedfilename, 0, $offset)); 439 } 440 return ''; 441 } 442 443 444 public static function PlaytimeString($seconds) { 445 $sign = (($seconds < 0) ? '-' : ''); 446 $seconds = round(abs($seconds)); 447 $H = (int) floor( $seconds / 3600); 448 $M = (int) floor(($seconds - (3600 * $H) ) / 60); 449 $S = (int) round( $seconds - (3600 * $H) - (60 * $M) ); 450 return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT); 451 } 452 453 454 public static function DateMac2Unix($macdate) { 455 // Macintosh timestamp: seconds since 00:00h January 1, 1904 456 // UNIX timestamp: seconds since 00:00h January 1, 1970 457 return self::CastAsInt($macdate - 2082844800); 458 } 459 460 461 public static function FixedPoint8_8($rawdata) { 462 return self::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (self::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); 463 } 464 465 466 public static function FixedPoint16_16($rawdata) { 467 return self::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (self::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); 468 } 469 470 471 public static function FixedPoint2_30($rawdata) { 472 $binarystring = self::BigEndian2Bin($rawdata); 473 return self::Bin2Dec(substr($binarystring, 0, 2)) + (float) (self::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30)); 474 } 475 476 477 public static function CreateDeepArray($ArrayPath, $Separator, $Value) { 478 // assigns $Value to a nested array path: 479 // $foo = self::CreateDeepArray('/path/to/my', '/', 'file.txt') 480 // is the same as: 481 // $foo = array('path'=>array('to'=>'array('my'=>array('file.txt')))); 482 // or 483 // $foo['path']['to']['my'] = 'file.txt'; 484 $ArrayPath = ltrim($ArrayPath, $Separator); 485 if (($pos = strpos($ArrayPath, $Separator)) !== false) { 486 $ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value); 487 } else { 488 $ReturnedArray[$ArrayPath] = $Value; 489 } 490 return $ReturnedArray; 491 } 492 493 public static function array_max($arraydata, $returnkey=false) { 494 $maxvalue = false; 495 $maxkey = false; 496 foreach ($arraydata as $key => $value) { 497 if (!is_array($value)) { 498 if ($value > $maxvalue) { 499 $maxvalue = $value; 500 $maxkey = $key; 501 } 502 } 503 } 504 return ($returnkey ? $maxkey : $maxvalue); 505 } 506 507 public static function array_min($arraydata, $returnkey=false) { 508 $minvalue = false; 509 $minkey = false; 510 foreach ($arraydata as $key => $value) { 511 if (!is_array($value)) { 512 if ($value > $minvalue) { 513 $minvalue = $value; 514 $minkey = $key; 515 } 516 } 517 } 518 return ($returnkey ? $minkey : $minvalue); 519 } 520 521 public static function XML2array($XMLstring) { 522 if (function_exists('simplexml_load_string')) { 523 if (function_exists('get_object_vars')) { 524 $XMLobject = simplexml_load_string($XMLstring); 525 return self::SimpleXMLelement2array($XMLobject); 526 } 527 } 528 return false; 529 } 530 531 public static function SimpleXMLelement2array($XMLobject) { 532 if (!is_object($XMLobject) && !is_array($XMLobject)) { 533 return $XMLobject; 534 } 535 $XMLarray = (is_object($XMLobject) ? get_object_vars($XMLobject) : $XMLobject); 536 foreach ($XMLarray as $key => $value) { 537 $XMLarray[$key] = self::SimpleXMLelement2array($value); 538 } 539 return $XMLarray; 540 } 541 542 543 // Allan Hansen <ahØartemis*dk> 544 // self::md5_data() - returns md5sum for a file from startuing position to absolute end position 545 public static function hash_data($file, $offset, $end, $algorithm) { 546 static $tempdir = ''; 547 if (!self::intValueSupported($end)) { 548 return false; 549 } 550 switch ($algorithm) { 551 case 'md5': 552 $hash_function = 'md5_file'; 553 $unix_call = 'md5sum'; 554 $windows_call = 'md5sum.exe'; 555 $hash_length = 32; 556 break; 557 558 case 'sha1': 559 $hash_function = 'sha1_file'; 560 $unix_call = 'sha1sum'; 561 $windows_call = 'sha1sum.exe'; 562 $hash_length = 40; 563 break; 564 565 default: 566 throw new Exception('Invalid algorithm ('.$algorithm.') in self::hash_data()'); 567 break; 568 } 569 $size = $end - $offset; 570 while (true) { 571 if (GETID3_OS_ISWINDOWS) { 572 573 // It seems that sha1sum.exe for Windows only works on physical files, does not accept piped data 574 // Fall back to create-temp-file method: 575 if ($algorithm == 'sha1') { 576 break; 577 } 578 579 $RequiredFiles = array('cygwin1.dll', 'head.exe', 'tail.exe', $windows_call); 580 foreach ($RequiredFiles as $required_file) { 581 if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { 582 // helper apps not available - fall back to old method 583 break 2; 584 } 585 } 586 $commandline = GETID3_HELPERAPPSDIR.'head.exe -c '.$end.' '.escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $file)).' | '; 587 $commandline .= GETID3_HELPERAPPSDIR.'tail.exe -c '.$size.' | '; 588 $commandline .= GETID3_HELPERAPPSDIR.$windows_call; 589 590 } else { 591 592 $commandline = 'head -c'.$end.' '.escapeshellarg($file).' | '; 593 $commandline .= 'tail -c'.$size.' | '; 594 $commandline .= $unix_call; 595 596 } 597 if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { 598 //throw new Exception('PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm'); 599 break; 600 } 601 return substr(`$commandline`, 0, $hash_length); 602 } 603 604 if (empty($tempdir)) { 605 // yes this is ugly, feel free to suggest a better way 606 require_once(dirname(__FILE__).'/getid3.php'); 607 $getid3_temp = new getID3(); 608 $tempdir = $getid3_temp->tempdir; 609 unset($getid3_temp); 610 } 611 // try to create a temporary file in the system temp directory - invalid dirname should force to system temp dir 612 if (($data_filename = tempnam($tempdir, 'gI3')) === false) { 613 // can't find anywhere to create a temp file, just fail 614 return false; 615 } 616 617 // Init 618 $result = false; 619 620 // copy parts of file 621 try { 622 self::CopyFileParts($file, $data_filename, $offset, $end - $offset); 623 $result = $hash_function($data_filename); 624 } catch (Exception $e) { 625 throw new Exception('self::CopyFileParts() failed in getid_lib::hash_data(): '.$e->getMessage()); 626 } 627 unlink($data_filename); 628 return $result; 629 } 630 631 public static function CopyFileParts($filename_source, $filename_dest, $offset, $length) { 632 if (!self::intValueSupported($offset + $length)) { 633 throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit'); 634 } 635 if (is_readable($filename_source) && is_file($filename_source) && ($fp_src = fopen($filename_source, 'rb'))) { 636 if (($fp_dest = fopen($filename_dest, 'wb'))) { 637 if (fseek($fp_src, $offset, SEEK_SET) == 0) { 638 $byteslefttowrite = $length; 639 while (($byteslefttowrite > 0) && ($buffer = fread($fp_src, min($byteslefttowrite, getID3::FREAD_BUFFER_SIZE)))) { 640 $byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite); 641 $byteslefttowrite -= $byteswritten; 642 } 643 return true; 644 } else { 645 throw new Exception('failed to seek to offset '.$offset.' in '.$filename_source); 646 } 647 fclose($fp_dest); 648 } else { 649 throw new Exception('failed to create file for writing '.$filename_dest); 650 } 651 fclose($fp_src); 652 } else { 653 throw new Exception('failed to open file for reading '.$filename_source); 654 } 655 return false; 656 } 657 658 public static function iconv_fallback_int_utf8($charval) { 659 if ($charval < 128) { 660 // 0bbbbbbb 661 $newcharstring = chr($charval); 662 } elseif ($charval < 2048) { 663 // 110bbbbb 10bbbbbb 664 $newcharstring = chr(($charval >> 6) | 0xC0); 665 $newcharstring .= chr(($charval & 0x3F) | 0x80); 666 } elseif ($charval < 65536) { 667 // 1110bbbb 10bbbbbb 10bbbbbb 668 $newcharstring = chr(($charval >> 12) | 0xE0); 669 $newcharstring .= chr(($charval >> 6) | 0xC0); 670 $newcharstring .= chr(($charval & 0x3F) | 0x80); 671 } else { 672 // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb 673 $newcharstring = chr(($charval >> 18) | 0xF0); 674 $newcharstring .= chr(($charval >> 12) | 0xC0); 675 $newcharstring .= chr(($charval >> 6) | 0xC0); 676 $newcharstring .= chr(($charval & 0x3F) | 0x80); 677 } 678 return $newcharstring; 679 } 680 681 // ISO-8859-1 => UTF-8 682 public static function iconv_fallback_iso88591_utf8($string, $bom=false) { 683 if (function_exists('utf8_encode')) { 684 return utf8_encode($string); 685 } 686 // utf8_encode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support) 687 $newcharstring = ''; 688 if ($bom) { 689 $newcharstring .= "\xEF\xBB\xBF"; 690 } 691 for ($i = 0; $i < strlen($string); $i++) { 692 $charval = ord($string{$i}); 693 $newcharstring .= self::iconv_fallback_int_utf8($charval); 694 } 695 return $newcharstring; 696 } 697 698 // ISO-8859-1 => UTF-16BE 699 public static function iconv_fallback_iso88591_utf16be($string, $bom=false) { 700 $newcharstring = ''; 701 if ($bom) { 702 $newcharstring .= "\xFE\xFF"; 703 } 704 for ($i = 0; $i < strlen($string); $i++) { 705 $newcharstring .= "\x00".$string{$i}; 706 } 707 return $newcharstring; 708 } 709 710 // ISO-8859-1 => UTF-16LE 711 public static function iconv_fallback_iso88591_utf16le($string, $bom=false) { 712 $newcharstring = ''; 713 if ($bom) { 714 $newcharstring .= "\xFF\xFE"; 715 } 716 for ($i = 0; $i < strlen($string); $i++) { 717 $newcharstring .= $string{$i}."\x00"; 718 } 719 return $newcharstring; 720 } 721 722 // ISO-8859-1 => UTF-16LE (BOM) 723 public static function iconv_fallback_iso88591_utf16($string) { 724 return self::iconv_fallback_iso88591_utf16le($string, true); 725 } 726 727 // UTF-8 => ISO-8859-1 728 public static function iconv_fallback_utf8_iso88591($string) { 729 if (function_exists('utf8_decode')) { 730 return utf8_decode($string); 731 } 732 // utf8_decode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support) 733 $newcharstring = ''; 734 $offset = 0; 735 $stringlength = strlen($string); 736 while ($offset < $stringlength) { 737 if ((ord($string{$offset}) | 0x07) == 0xF7) { 738 // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb 739 $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & 740 ((ord($string{($offset + 1)}) & 0x3F) << 12) & 741 ((ord($string{($offset + 2)}) & 0x3F) << 6) & 742 (ord($string{($offset + 3)}) & 0x3F); 743 $offset += 4; 744 } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { 745 // 1110bbbb 10bbbbbb 10bbbbbb 746 $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & 747 ((ord($string{($offset + 1)}) & 0x3F) << 6) & 748 (ord($string{($offset + 2)}) & 0x3F); 749 $offset += 3; 750 } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { 751 // 110bbbbb 10bbbbbb 752 $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & 753 (ord($string{($offset + 1)}) & 0x3F); 754 $offset += 2; 755 } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { 756 // 0bbbbbbb 757 $charval = ord($string{$offset}); 758 $offset += 1; 759 } else { 760 // error? throw some kind of warning here? 761 $charval = false; 762 $offset += 1; 763 } 764 if ($charval !== false) { 765 $newcharstring .= (($charval < 256) ? chr($charval) : '?'); 766 } 767 } 768 return $newcharstring; 769 } 770 771 // UTF-8 => UTF-16BE 772 public static function iconv_fallback_utf8_utf16be($string, $bom=false) { 773 $newcharstring = ''; 774 if ($bom) { 775 $newcharstring .= "\xFE\xFF"; 776 } 777 $offset = 0; 778 $stringlength = strlen($string); 779 while ($offset < $stringlength) { 780 if ((ord($string{$offset}) | 0x07) == 0xF7) { 781 // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb 782 $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & 783 ((ord($string{($offset + 1)}) & 0x3F) << 12) & 784 ((ord($string{($offset + 2)}) & 0x3F) << 6) & 785 (ord($string{($offset + 3)}) & 0x3F); 786 $offset += 4; 787 } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { 788 // 1110bbbb 10bbbbbb 10bbbbbb 789 $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & 790 ((ord($string{($offset + 1)}) & 0x3F) << 6) & 791 (ord($string{($offset + 2)}) & 0x3F); 792 $offset += 3; 793 } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { 794 // 110bbbbb 10bbbbbb 795 $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & 796 (ord($string{($offset + 1)}) & 0x3F); 797 $offset += 2; 798 } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { 799 // 0bbbbbbb 800 $charval = ord($string{$offset}); 801 $offset += 1; 802 } else { 803 // error? throw some kind of warning here? 804 $charval = false; 805 $offset += 1; 806 } 807 if ($charval !== false) { 808 $newcharstring .= (($charval < 65536) ? self::BigEndian2String($charval, 2) : "\x00".'?'); 809 } 810 } 811 return $newcharstring; 812 } 813 814 // UTF-8 => UTF-16LE 815 public static function iconv_fallback_utf8_utf16le($string, $bom=false) { 816 $newcharstring = ''; 817 if ($bom) { 818 $newcharstring .= "\xFF\xFE"; 819 } 820 $offset = 0; 821 $stringlength = strlen($string); 822 while ($offset < $stringlength) { 823 if ((ord($string{$offset}) | 0x07) == 0xF7) { 824 // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb 825 $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & 826 ((ord($string{($offset + 1)}) & 0x3F) << 12) & 827 ((ord($string{($offset + 2)}) & 0x3F) << 6) & 828 (ord($string{($offset + 3)}) & 0x3F); 829 $offset += 4; 830 } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { 831 // 1110bbbb 10bbbbbb 10bbbbbb 832 $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & 833 ((ord($string{($offset + 1)}) & 0x3F) << 6) & 834 (ord($string{($offset + 2)}) & 0x3F); 835 $offset += 3; 836 } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { 837 // 110bbbbb 10bbbbbb 838 $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & 839 (ord($string{($offset + 1)}) & 0x3F); 840 $offset += 2; 841 } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { 842 // 0bbbbbbb 843 $charval = ord($string{$offset}); 844 $offset += 1; 845 } else { 846 // error? maybe throw some warning here? 847 $charval = false; 848 $offset += 1; 849 } 850 if ($charval !== false) { 851 $newcharstring .= (($charval < 65536) ? self::LittleEndian2String($charval, 2) : '?'."\x00"); 852 } 853 } 854 return $newcharstring; 855 } 856 857 // UTF-8 => UTF-16LE (BOM) 858 public static function iconv_fallback_utf8_utf16($string) { 859 return self::iconv_fallback_utf8_utf16le($string, true); 860 } 861 862 // UTF-16BE => UTF-8 863 public static function iconv_fallback_utf16be_utf8($string) { 864 if (substr($string, 0, 2) == "\xFE\xFF") { 865 // strip BOM 866 $string = substr($string, 2); 867 } 868 $newcharstring = ''; 869 for ($i = 0; $i < strlen($string); $i += 2) { 870 $charval = self::BigEndian2Int(substr($string, $i, 2)); 871 $newcharstring .= self::iconv_fallback_int_utf8($charval); 872 } 873 return $newcharstring; 874 } 875 876 // UTF-16LE => UTF-8 877 public static function iconv_fallback_utf16le_utf8($string) { 878 if (substr($string, 0, 2) == "\xFF\xFE") { 879 // strip BOM 880 $string = substr($string, 2); 881 } 882 $newcharstring = ''; 883 for ($i = 0; $i < strlen($string); $i += 2) { 884 $charval = self::LittleEndian2Int(substr($string, $i, 2)); 885 $newcharstring .= self::iconv_fallback_int_utf8($charval); 886 } 887 return $newcharstring; 888 } 889 890 // UTF-16BE => ISO-8859-1 891 public static function iconv_fallback_utf16be_iso88591($string) { 892 if (substr($string, 0, 2) == "\xFE\xFF") { 893 // strip BOM 894 $string = substr($string, 2); 895 } 896 $newcharstring = ''; 897 for ($i = 0; $i < strlen($string); $i += 2) { 898 $charval = self::BigEndian2Int(substr($string, $i, 2)); 899 $newcharstring .= (($charval < 256) ? chr($charval) : '?'); 900 } 901 return $newcharstring; 902 } 903 904 // UTF-16LE => ISO-8859-1 905 public static function iconv_fallback_utf16le_iso88591($string) { 906 if (substr($string, 0, 2) == "\xFF\xFE") { 907 // strip BOM 908 $string = substr($string, 2); 909 } 910 $newcharstring = ''; 911 for ($i = 0; $i < strlen($string); $i += 2) { 912 $charval = self::LittleEndian2Int(substr($string, $i, 2)); 913 $newcharstring .= (($charval < 256) ? chr($charval) : '?'); 914 } 915 return $newcharstring; 916 } 917 918 // UTF-16 (BOM) => ISO-8859-1 919 public static function iconv_fallback_utf16_iso88591($string) { 920 $bom = substr($string, 0, 2); 921 if ($bom == "\xFE\xFF") { 922 return self::iconv_fallback_utf16be_iso88591(substr($string, 2)); 923 } elseif ($bom == "\xFF\xFE") { 924 return self::iconv_fallback_utf16le_iso88591(substr($string, 2)); 925 } 926 return $string; 927 } 928 929 // UTF-16 (BOM) => UTF-8 930 public static function iconv_fallback_utf16_utf8($string) { 931 $bom = substr($string, 0, 2); 932 if ($bom == "\xFE\xFF") { 933 return self::iconv_fallback_utf16be_utf8(substr($string, 2)); 934 } elseif ($bom == "\xFF\xFE") { 935 return self::iconv_fallback_utf16le_utf8(substr($string, 2)); 936 } 937 return $string; 938 } 939 940 public static function iconv_fallback($in_charset, $out_charset, $string) { 941 942 if ($in_charset == $out_charset) { 943 return $string; 944 } 945 946 // iconv() availble 947 if (function_exists('iconv')) { 948 if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) { 949 switch ($out_charset) { 950 case 'ISO-8859-1': 951 $converted_string = rtrim($converted_string, "\x00"); 952 break; 953 } 954 return $converted_string; 955 } 956 957 // iconv() may sometimes fail with "illegal character in input string" error message 958 // and return an empty string, but returning the unconverted string is more useful 959 return $string; 960 } 961 962 963 // iconv() not available 964 static $ConversionFunctionList = array(); 965 if (empty($ConversionFunctionList)) { 966 $ConversionFunctionList['ISO-8859-1']['UTF-8'] = 'iconv_fallback_iso88591_utf8'; 967 $ConversionFunctionList['ISO-8859-1']['UTF-16'] = 'iconv_fallback_iso88591_utf16'; 968 $ConversionFunctionList['ISO-8859-1']['UTF-16BE'] = 'iconv_fallback_iso88591_utf16be'; 969 $ConversionFunctionList['ISO-8859-1']['UTF-16LE'] = 'iconv_fallback_iso88591_utf16le'; 970 $ConversionFunctionList['UTF-8']['ISO-8859-1'] = 'iconv_fallback_utf8_iso88591'; 971 $ConversionFunctionList['UTF-8']['UTF-16'] = 'iconv_fallback_utf8_utf16'; 972 $ConversionFunctionList['UTF-8']['UTF-16BE'] = 'iconv_fallback_utf8_utf16be'; 973 $ConversionFunctionList['UTF-8']['UTF-16LE'] = 'iconv_fallback_utf8_utf16le'; 974 $ConversionFunctionList['UTF-16']['ISO-8859-1'] = 'iconv_fallback_utf16_iso88591'; 975 $ConversionFunctionList['UTF-16']['UTF-8'] = 'iconv_fallback_utf16_utf8'; 976 $ConversionFunctionList['UTF-16LE']['ISO-8859-1'] = 'iconv_fallback_utf16le_iso88591'; 977 $ConversionFunctionList['UTF-16LE']['UTF-8'] = 'iconv_fallback_utf16le_utf8'; 978 $ConversionFunctionList['UTF-16BE']['ISO-8859-1'] = 'iconv_fallback_utf16be_iso88591'; 979 $ConversionFunctionList['UTF-16BE']['UTF-8'] = 'iconv_fallback_utf16be_utf8'; 980 } 981 if (isset($ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)])) { 982 $ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)]; 983 return self::$ConversionFunction($string); 984 } 985 throw new Exception('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); 986 } 987 988 989 public static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') { 990 $string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string 991 $HTMLstring = ''; 992 993 switch ($charset) { 994 case '1251': 995 case '1252': 996 case '866': 997 case '932': 998 case '936': 999 case '950': 1000 case 'BIG5': 1001 case 'BIG5-HKSCS': 1002 case 'cp1251': 1003 case 'cp1252': 1004 case 'cp866': 1005 case 'EUC-JP': 1006 case 'EUCJP': 1007 case 'GB2312': 1008 case 'ibm866': 1009 case 'ISO-8859-1': 1010 case 'ISO-8859-15': 1011 case 'ISO8859-1': 1012 case 'ISO8859-15': 1013 case 'KOI8-R': 1014 case 'koi8-ru': 1015 case 'koi8r': 1016 case 'Shift_JIS': 1017 case 'SJIS': 1018 case 'win-1251': 1019 case 'Windows-1251': 1020 case 'Windows-1252': 1021 $HTMLstring = htmlentities($string, ENT_COMPAT, $charset); 1022 break; 1023 1024 case 'UTF-8': 1025 $strlen = strlen($string); 1026 for ($i = 0; $i < $strlen; $i++) { 1027 $char_ord_val = ord($string{$i}); 1028 $charval = 0; 1029 if ($char_ord_val < 0x80) { 1030 $charval = $char_ord_val; 1031 } elseif ((($char_ord_val & 0xF0) >> 4) == 0x0F && $i+3 < $strlen) { 1032 $charval = (($char_ord_val & 0x07) << 18); 1033 $charval += ((ord($string{++$i}) & 0x3F) << 12); 1034 $charval += ((ord($string{++$i}) & 0x3F) << 6); 1035 $charval += (ord($string{++$i}) & 0x3F); 1036 } elseif ((($char_ord_val & 0xE0) >> 5) == 0x07 && $i+2 < $strlen) { 1037 $charval = (($char_ord_val & 0x0F) << 12); 1038 $charval += ((ord($string{++$i}) & 0x3F) << 6); 1039 $charval += (ord($string{++$i}) & 0x3F); 1040 } elseif ((($char_ord_val & 0xC0) >> 6) == 0x03 && $i+1 < $strlen) { 1041 $charval = (($char_ord_val & 0x1F) << 6); 1042 $charval += (ord($string{++$i}) & 0x3F); 1043 } 1044 if (($charval >= 32) && ($charval <= 127)) { 1045 $HTMLstring .= htmlentities(chr($charval)); 1046 } else { 1047 $HTMLstring .= '&#'.$charval.';'; 1048 } 1049 } 1050 break; 1051 1052 case 'UTF-16LE': 1053 for ($i = 0; $i < strlen($string); $i += 2) { 1054 $charval = self::LittleEndian2Int(substr($string, $i, 2)); 1055 if (($charval >= 32) && ($charval <= 127)) { 1056 $HTMLstring .= chr($charval); 1057 } else { 1058 $HTMLstring .= '&#'.$charval.';'; 1059 } 1060 } 1061 break; 1062 1063 case 'UTF-16BE': 1064 for ($i = 0; $i < strlen($string); $i += 2) { 1065 $charval = self::BigEndian2Int(substr($string, $i, 2)); 1066 if (($charval >= 32) && ($charval <= 127)) { 1067 $HTMLstring .= chr($charval); 1068 } else { 1069 $HTMLstring .= '&#'.$charval.';'; 1070 } 1071 } 1072 break; 1073 1074 default: 1075 $HTMLstring = 'ERROR: Character set "'.$charset.'" not supported in MultiByteCharString2HTML()'; 1076 break; 1077 } 1078 return $HTMLstring; 1079 } 1080 1081 1082 1083 public static function RGADnameLookup($namecode) { 1084 static $RGADname = array(); 1085 if (empty($RGADname)) { 1086 $RGADname[0] = 'not set'; 1087 $RGADname[1] = 'Track Gain Adjustment'; 1088 $RGADname[2] = 'Album Gain Adjustment'; 1089 } 1090 1091 return (isset($RGADname[$namecode]) ? $RGADname[$namecode] : ''); 1092 } 1093 1094 1095 public static function RGADoriginatorLookup($originatorcode) { 1096 static $RGADoriginator = array(); 1097 if (empty($RGADoriginator)) { 1098 $RGADoriginator[0] = 'unspecified'; 1099 $RGADoriginator[1] = 'pre-set by artist/producer/mastering engineer'; 1100 $RGADoriginator[2] = 'set by user'; 1101 $RGADoriginator[3] = 'determined automatically'; 1102 } 1103 1104 return (isset($RGADoriginator[$originatorcode]) ? $RGADoriginator[$originatorcode] : ''); 1105 } 1106 1107 1108 public static function RGADadjustmentLookup($rawadjustment, $signbit) { 1109 $adjustment = $rawadjustment / 10; 1110 if ($signbit == 1) { 1111 $adjustment *= -1; 1112 } 1113 return (float) $adjustment; 1114 } 1115 1116 1117 public static function RGADgainString($namecode, $originatorcode, $replaygain) { 1118 if ($replaygain < 0) { 1119 $signbit = '1'; 1120 } else { 1121 $signbit = '0'; 1122 } 1123 $storedreplaygain = intval(round($replaygain * 10)); 1124 $gainstring = str_pad(decbin($namecode), 3, '0', STR_PAD_LEFT); 1125 $gainstring .= str_pad(decbin($originatorcode), 3, '0', STR_PAD_LEFT); 1126 $gainstring .= $signbit; 1127 $gainstring .= str_pad(decbin($storedreplaygain), 9, '0', STR_PAD_LEFT); 1128 1129 return $gainstring; 1130 } 1131 1132 public static function RGADamplitude2dB($amplitude) { 1133 return 20 * log10($amplitude); 1134 } 1135 1136 1137 public static function GetDataImageSize($imgData, &$imageinfo=array()) { 1138 static $tempdir = ''; 1139 if (empty($tempdir)) { 1140 // yes this is ugly, feel free to suggest a better way 1141 require_once(dirname(__FILE__).'/class-getid3.php'); 1142 $getid3_temp = new getID3(); 1143 $tempdir = $getid3_temp->tempdir; 1144 unset($getid3_temp); 1145 } 1146 $GetDataImageSize = false; 1147 if ($tempfilename = tempnam($tempdir, 'gI3')) { 1148 if (is_writable($tempfilename) && is_file($tempfilename) && ($tmp = fopen($tempfilename, 'wb'))) { 1149 fwrite($tmp, $imgData); 1150 fclose($tmp); 1151 $GetDataImageSize = @getimagesize($tempfilename, $imageinfo); 1152 } 1153 unlink($tempfilename); 1154 } 1155 return $GetDataImageSize; 1156 } 1157 1158 public static function ImageExtFromMime($mime_type) { 1159 // temporary way, works OK for now, but should be reworked in the future 1160 return str_replace(array('image/', 'x-', 'jpeg'), array('', '', 'jpg'), $mime_type); 1161 } 1162 1163 public static function ImageTypesLookup($imagetypeid) { 1164 static $ImageTypesLookup = array(); 1165 if (empty($ImageTypesLookup)) { 1166 $ImageTypesLookup[1] = 'gif'; 1167 $ImageTypesLookup[2] = 'jpeg'; 1168 $ImageTypesLookup[3] = 'png'; 1169 $ImageTypesLookup[4] = 'swf'; 1170 $ImageTypesLookup[5] = 'psd'; 1171 $ImageTypesLookup[6] = 'bmp'; 1172 $ImageTypesLookup[7] = 'tiff (little-endian)'; 1173 $ImageTypesLookup[8] = 'tiff (big-endian)'; 1174 $ImageTypesLookup[9] = 'jpc'; 1175 $ImageTypesLookup[10] = 'jp2'; 1176 $ImageTypesLookup[11] = 'jpx'; 1177 $ImageTypesLookup[12] = 'jb2'; 1178 $ImageTypesLookup[13] = 'swc'; 1179 $ImageTypesLookup[14] = 'iff'; 1180 } 1181 return (isset($ImageTypesLookup[$imagetypeid]) ? $ImageTypesLookup[$imagetypeid] : ''); 1182 } 1183 1184 public static function CopyTagsToComments(&$ThisFileInfo) { 1185 1186 // Copy all entries from ['tags'] into common ['comments'] 1187 if (!empty($ThisFileInfo['tags'])) { 1188 foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) { 1189 foreach ($tagarray as $tagname => $tagdata) { 1190 foreach ($tagdata as $key => $value) { 1191 if (!empty($value)) { 1192 if (empty($ThisFileInfo['comments'][$tagname])) { 1193 1194 // fall through and append value 1195 1196 } elseif ($tagtype == 'id3v1') { 1197 1198 $newvaluelength = strlen(trim($value)); 1199 foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { 1200 $oldvaluelength = strlen(trim($existingvalue)); 1201 if (($newvaluelength <= $oldvaluelength) && (substr($existingvalue, 0, $newvaluelength) == trim($value))) { 1202 // new value is identical but shorter-than (or equal-length to) one already in comments - skip 1203 break 2; 1204 } 1205 } 1206 1207 } elseif (!is_array($value)) { 1208 1209 $newvaluelength = strlen(trim($value)); 1210 foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { 1211 $oldvaluelength = strlen(trim($existingvalue)); 1212 if (($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) { 1213 $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value); 1214 break 2; 1215 } 1216 } 1217 1218 } 1219 if (is_array($value) || empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) { 1220 $value = (is_string($value) ? trim($value) : $value); 1221 $ThisFileInfo['comments'][$tagname][] = $value; 1222 } 1223 } 1224 } 1225 } 1226 } 1227 1228 // Copy to ['comments_html'] 1229 foreach ($ThisFileInfo['comments'] as $field => $values) { 1230 if ($field == 'picture') { 1231 // pictures can take up a lot of space, and we don't need multiple copies of them 1232 // let there be a single copy in [comments][picture], and not elsewhere 1233 continue; 1234 } 1235 foreach ($values as $index => $value) { 1236 if (is_array($value)) { 1237 $ThisFileInfo['comments_html'][$field][$index] = $value; 1238 } else { 1239 $ThisFileInfo['comments_html'][$field][$index] = str_replace('�', '', self::MultiByteCharString2HTML($value, $ThisFileInfo['encoding'])); 1240 } 1241 } 1242 } 1243 } 1244 return true; 1245 } 1246 1247 1248 public static function EmbeddedLookup($key, $begin, $end, $file, $name) { 1249 1250 // Cached 1251 static $cache; 1252 if (isset($cache[$file][$name])) { 1253 return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); 1254 } 1255 1256 // Init 1257 $keylength = strlen($key); 1258 $line_count = $end - $begin - 7; 1259 1260 // Open php file 1261 $fp = fopen($file, 'r'); 1262 1263 // Discard $begin lines 1264 for ($i = 0; $i < ($begin + 3); $i++) { 1265 fgets($fp, 1024); 1266 } 1267 1268 // Loop thru line 1269 while (0 < $line_count--) { 1270 1271 // Read line 1272 $line = ltrim(fgets($fp, 1024), "\t "); 1273 1274 // METHOD A: only cache the matching key - less memory but slower on next lookup of not-previously-looked-up key 1275 //$keycheck = substr($line, 0, $keylength); 1276 //if ($key == $keycheck) { 1277 // $cache[$file][$name][$keycheck] = substr($line, $keylength + 1); 1278 // break; 1279 //} 1280 1281 // METHOD B: cache all keys in this lookup - more memory but faster on next lookup of not-previously-looked-up key 1282 //$cache[$file][$name][substr($line, 0, $keylength)] = trim(substr($line, $keylength + 1)); 1283 $explodedLine = explode("\t", $line, 2); 1284 $ThisKey = (isset($explodedLine[0]) ? $explodedLine[0] : ''); 1285 $ThisValue = (isset($explodedLine[1]) ? $explodedLine[1] : ''); 1286 $cache[$file][$name][$ThisKey] = trim($ThisValue); 1287 } 1288 1289 // Close and return 1290 fclose($fp); 1291 return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); 1292 } 1293 1294 public static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { 1295 global $GETID3_ERRORARRAY; 1296 1297 if (file_exists($filename)) { 1298 if (include_once($filename)) { 1299 return true; 1300 } else { 1301 $diemessage = basename($sourcefile).' depends on '.$filename.', which has errors'; 1302 } 1303 } else { 1304 $diemessage = basename($sourcefile).' depends on '.$filename.', which is missing'; 1305 } 1306 if ($DieOnFailure) { 1307 throw new Exception($diemessage); 1308 } else { 1309 $GETID3_ERRORARRAY[] = $diemessage; 1310 } 1311 return false; 1312 } 1313 1314 public static function trimNullByte($string) { 1315 return trim($string, "\x00"); 1316 } 1317 1318 public static function getFileSizeSyscall($path) { 1319 $filesize = false; 1320 1321 if (GETID3_OS_ISWINDOWS) { 1322 if (class_exists('COM')) { // From PHP 5.3.15 and 5.4.5, COM and DOTNET is no longer built into the php core.you have to add COM support in php.ini: 1323 $filesystem = new COM('Scripting.FileSystemObject'); 1324 $file = $filesystem->GetFile($path); 1325 $filesize = $file->Size(); 1326 unset($filesystem, $file); 1327 } else { 1328 $commandline = 'for %I in ('.escapeshellarg($path).') do @echo %~zI'; 1329 } 1330 } else { 1331 $commandline = 'ls -l '.escapeshellarg($path).' | awk \'{print $5}\''; 1332 } 1333 if (isset($commandline)) { 1334 $output = trim(`$commandline`); 1335 if (ctype_digit($output)) { 1336 $filesize = (float) $output; 1337 } 1338 } 1339 return $filesize; 1340 } 1341 } 1342 No newline at end of file -
new file wp-includes/ID3/module.audio-video.asf.php
diff --git wp-includes/ID3/module.audio-video.asf.php wp-includes/ID3/module.audio-video.asf.php new file mode 100644 index 0000000..cfc60a7
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.audio-video.asf.php // 11 // module for analyzing ASF, WMA and WMV files // 12 // dependencies: module.audio-video.riff.php // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); 17 18 class getid3_asf extends getid3_handler 19 { 20 21 public function __construct(getID3 $getid3) { 22 parent::__construct($getid3); // extends getid3_handler::__construct() 23 24 // initialize all GUID constants 25 $GUIDarray = $this->KnownGUIDs(); 26 foreach ($GUIDarray as $GUIDname => $hexstringvalue) { 27 if (!defined($GUIDname)) { 28 define($GUIDname, $this->GUIDtoBytestring($hexstringvalue)); 29 } 30 } 31 } 32 33 public function Analyze() { 34 $info = &$this->getid3->info; 35 36 // Shortcuts 37 $thisfile_audio = &$info['audio']; 38 $thisfile_video = &$info['video']; 39 $info['asf'] = array(); 40 $thisfile_asf = &$info['asf']; 41 $thisfile_asf['comments'] = array(); 42 $thisfile_asf_comments = &$thisfile_asf['comments']; 43 $thisfile_asf['header_object'] = array(); 44 $thisfile_asf_headerobject = &$thisfile_asf['header_object']; 45 46 47 // ASF structure: 48 // * Header Object [required] 49 // * File Properties Object [required] (global file attributes) 50 // * Stream Properties Object [required] (defines media stream & characteristics) 51 // * Header Extension Object [required] (additional functionality) 52 // * Content Description Object (bibliographic information) 53 // * Script Command Object (commands for during playback) 54 // * Marker Object (named jumped points within the file) 55 // * Data Object [required] 56 // * Data Packets 57 // * Index Object 58 59 // Header Object: (mandatory, one only) 60 // Field Name Field Type Size (bits) 61 // Object ID GUID 128 // GUID for header object - GETID3_ASF_Header_Object 62 // Object Size QWORD 64 // size of header object, including 30 bytes of Header Object header 63 // Number of Header Objects DWORD 32 // number of objects in header object 64 // Reserved1 BYTE 8 // hardcoded: 0x01 65 // Reserved2 BYTE 8 // hardcoded: 0x02 66 67 $info['fileformat'] = 'asf'; 68 69 fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); 70 $HeaderObjectData = fread($this->getid3->fp, 30); 71 72 $thisfile_asf_headerobject['objectid'] = substr($HeaderObjectData, 0, 16); 73 $thisfile_asf_headerobject['objectid_guid'] = $this->BytestringToGUID($thisfile_asf_headerobject['objectid']); 74 if ($thisfile_asf_headerobject['objectid'] != GETID3_ASF_Header_Object) { 75 $info['warning'][] = 'ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}'; 76 unset($info['fileformat']); 77 unset($info['asf']); 78 return false; 79 break; 80 } 81 $thisfile_asf_headerobject['objectsize'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 16, 8)); 82 $thisfile_asf_headerobject['headerobjects'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 24, 4)); 83 $thisfile_asf_headerobject['reserved1'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 28, 1)); 84 $thisfile_asf_headerobject['reserved2'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 29, 1)); 85 86 $NextObjectOffset = ftell($this->getid3->fp); 87 $ASFHeaderData = fread($this->getid3->fp, $thisfile_asf_headerobject['objectsize'] - 30); 88 $offset = 0; 89 90 for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) { 91 $NextObjectGUID = substr($ASFHeaderData, $offset, 16); 92 $offset += 16; 93 $NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID); 94 $NextObjectSize = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); 95 $offset += 8; 96 switch ($NextObjectGUID) { 97 98 case GETID3_ASF_File_Properties_Object: 99 // File Properties Object: (mandatory, one only) 100 // Field Name Field Type Size (bits) 101 // Object ID GUID 128 // GUID for file properties object - GETID3_ASF_File_Properties_Object 102 // Object Size QWORD 64 // size of file properties object, including 104 bytes of File Properties Object header 103 // File ID GUID 128 // unique ID - identical to File ID in Data Object 104 // File Size QWORD 64 // entire file in bytes. Invalid if Broadcast Flag == 1 105 // Creation Date QWORD 64 // date & time of file creation. Maybe invalid if Broadcast Flag == 1 106 // Data Packets Count QWORD 64 // number of data packets in Data Object. Invalid if Broadcast Flag == 1 107 // Play Duration QWORD 64 // playtime, in 100-nanosecond units. Invalid if Broadcast Flag == 1 108 // Send Duration QWORD 64 // time needed to send file, in 100-nanosecond units. Players can ignore this value. Invalid if Broadcast Flag == 1 109 // Preroll QWORD 64 // time to buffer data before starting to play file, in 1-millisecond units. If <> 0, PlayDuration and PresentationTime have been offset by this amount 110 // Flags DWORD 32 // 111 // * Broadcast Flag bits 1 (0x01) // file is currently being written, some header values are invalid 112 // * Seekable Flag bits 1 (0x02) // is file seekable 113 // * Reserved bits 30 (0xFFFFFFFC) // reserved - set to zero 114 // Minimum Data Packet Size DWORD 32 // in bytes. should be same as Maximum Data Packet Size. Invalid if Broadcast Flag == 1 115 // Maximum Data Packet Size DWORD 32 // in bytes. should be same as Minimum Data Packet Size. Invalid if Broadcast Flag == 1 116 // Maximum Bitrate DWORD 32 // maximum instantaneous bitrate in bits per second for entire file, including all data streams and ASF overhead 117 118 // shortcut 119 $thisfile_asf['file_properties_object'] = array(); 120 $thisfile_asf_filepropertiesobject = &$thisfile_asf['file_properties_object']; 121 122 $thisfile_asf_filepropertiesobject['offset'] = $NextObjectOffset + $offset; 123 $thisfile_asf_filepropertiesobject['objectid'] = $NextObjectGUID; 124 $thisfile_asf_filepropertiesobject['objectid_guid'] = $NextObjectGUIDtext; 125 $thisfile_asf_filepropertiesobject['objectsize'] = $NextObjectSize; 126 $thisfile_asf_filepropertiesobject['fileid'] = substr($ASFHeaderData, $offset, 16); 127 $offset += 16; 128 $thisfile_asf_filepropertiesobject['fileid_guid'] = $this->BytestringToGUID($thisfile_asf_filepropertiesobject['fileid']); 129 $thisfile_asf_filepropertiesobject['filesize'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); 130 $offset += 8; 131 $thisfile_asf_filepropertiesobject['creation_date'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); 132 $thisfile_asf_filepropertiesobject['creation_date_unix'] = $this->FILETIMEtoUNIXtime($thisfile_asf_filepropertiesobject['creation_date']); 133 $offset += 8; 134 $thisfile_asf_filepropertiesobject['data_packets'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); 135 $offset += 8; 136 $thisfile_asf_filepropertiesobject['play_duration'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); 137 $offset += 8; 138 $thisfile_asf_filepropertiesobject['send_duration'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); 139 $offset += 8; 140 $thisfile_asf_filepropertiesobject['preroll'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); 141 $offset += 8; 142 $thisfile_asf_filepropertiesobject['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 143 $offset += 4; 144 $thisfile_asf_filepropertiesobject['flags']['broadcast'] = (bool) ($thisfile_asf_filepropertiesobject['flags_raw'] & 0x0001); 145 $thisfile_asf_filepropertiesobject['flags']['seekable'] = (bool) ($thisfile_asf_filepropertiesobject['flags_raw'] & 0x0002); 146 147 $thisfile_asf_filepropertiesobject['min_packet_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 148 $offset += 4; 149 $thisfile_asf_filepropertiesobject['max_packet_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 150 $offset += 4; 151 $thisfile_asf_filepropertiesobject['max_bitrate'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 152 $offset += 4; 153 154 if ($thisfile_asf_filepropertiesobject['flags']['broadcast']) { 155 156 // broadcast flag is set, some values invalid 157 unset($thisfile_asf_filepropertiesobject['filesize']); 158 unset($thisfile_asf_filepropertiesobject['data_packets']); 159 unset($thisfile_asf_filepropertiesobject['play_duration']); 160 unset($thisfile_asf_filepropertiesobject['send_duration']); 161 unset($thisfile_asf_filepropertiesobject['min_packet_size']); 162 unset($thisfile_asf_filepropertiesobject['max_packet_size']); 163 164 } else { 165 166 // broadcast flag NOT set, perform calculations 167 $info['playtime_seconds'] = ($thisfile_asf_filepropertiesobject['play_duration'] / 10000000) - ($thisfile_asf_filepropertiesobject['preroll'] / 1000); 168 169 //$info['bitrate'] = $thisfile_asf_filepropertiesobject['max_bitrate']; 170 $info['bitrate'] = ((isset($thisfile_asf_filepropertiesobject['filesize']) ? $thisfile_asf_filepropertiesobject['filesize'] : $info['filesize']) * 8) / $info['playtime_seconds']; 171 } 172 break; 173 174 case GETID3_ASF_Stream_Properties_Object: 175 // Stream Properties Object: (mandatory, one per media stream) 176 // Field Name Field Type Size (bits) 177 // Object ID GUID 128 // GUID for stream properties object - GETID3_ASF_Stream_Properties_Object 178 // Object Size QWORD 64 // size of stream properties object, including 78 bytes of Stream Properties Object header 179 // Stream Type GUID 128 // GETID3_ASF_Audio_Media, GETID3_ASF_Video_Media or GETID3_ASF_Command_Media 180 // Error Correction Type GUID 128 // GETID3_ASF_Audio_Spread for audio-only streams, GETID3_ASF_No_Error_Correction for other stream types 181 // Time Offset QWORD 64 // 100-nanosecond units. typically zero. added to all timestamps of samples in the stream 182 // Type-Specific Data Length DWORD 32 // number of bytes for Type-Specific Data field 183 // Error Correction Data Length DWORD 32 // number of bytes for Error Correction Data field 184 // Flags WORD 16 // 185 // * Stream Number bits 7 (0x007F) // number of this stream. 1 <= valid <= 127 186 // * Reserved bits 8 (0x7F80) // reserved - set to zero 187 // * Encrypted Content Flag bits 1 (0x8000) // stream contents encrypted if set 188 // Reserved DWORD 32 // reserved - set to zero 189 // Type-Specific Data BYTESTREAM variable // type-specific format data, depending on value of Stream Type 190 // Error Correction Data BYTESTREAM variable // error-correction-specific format data, depending on value of Error Correct Type 191 192 // There is one GETID3_ASF_Stream_Properties_Object for each stream (audio, video) but the 193 // stream number isn't known until halfway through decoding the structure, hence it 194 // it is decoded to a temporary variable and then stuck in the appropriate index later 195 196 $StreamPropertiesObjectData['offset'] = $NextObjectOffset + $offset; 197 $StreamPropertiesObjectData['objectid'] = $NextObjectGUID; 198 $StreamPropertiesObjectData['objectid_guid'] = $NextObjectGUIDtext; 199 $StreamPropertiesObjectData['objectsize'] = $NextObjectSize; 200 $StreamPropertiesObjectData['stream_type'] = substr($ASFHeaderData, $offset, 16); 201 $offset += 16; 202 $StreamPropertiesObjectData['stream_type_guid'] = $this->BytestringToGUID($StreamPropertiesObjectData['stream_type']); 203 $StreamPropertiesObjectData['error_correct_type'] = substr($ASFHeaderData, $offset, 16); 204 $offset += 16; 205 $StreamPropertiesObjectData['error_correct_guid'] = $this->BytestringToGUID($StreamPropertiesObjectData['error_correct_type']); 206 $StreamPropertiesObjectData['time_offset'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); 207 $offset += 8; 208 $StreamPropertiesObjectData['type_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 209 $offset += 4; 210 $StreamPropertiesObjectData['error_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 211 $offset += 4; 212 $StreamPropertiesObjectData['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 213 $offset += 2; 214 $StreamPropertiesObjectStreamNumber = $StreamPropertiesObjectData['flags_raw'] & 0x007F; 215 $StreamPropertiesObjectData['flags']['encrypted'] = (bool) ($StreamPropertiesObjectData['flags_raw'] & 0x8000); 216 217 $offset += 4; // reserved - DWORD 218 $StreamPropertiesObjectData['type_specific_data'] = substr($ASFHeaderData, $offset, $StreamPropertiesObjectData['type_data_length']); 219 $offset += $StreamPropertiesObjectData['type_data_length']; 220 $StreamPropertiesObjectData['error_correct_data'] = substr($ASFHeaderData, $offset, $StreamPropertiesObjectData['error_data_length']); 221 $offset += $StreamPropertiesObjectData['error_data_length']; 222 223 switch ($StreamPropertiesObjectData['stream_type']) { 224 225 case GETID3_ASF_Audio_Media: 226 $thisfile_audio['dataformat'] = (!empty($thisfile_audio['dataformat']) ? $thisfile_audio['dataformat'] : 'asf'); 227 $thisfile_audio['bitrate_mode'] = (!empty($thisfile_audio['bitrate_mode']) ? $thisfile_audio['bitrate_mode'] : 'cbr'); 228 229 $audiodata = getid3_riff::parseWAVEFORMATex(substr($StreamPropertiesObjectData['type_specific_data'], 0, 16)); 230 unset($audiodata['raw']); 231 $thisfile_audio = getid3_lib::array_merge_noclobber($audiodata, $thisfile_audio); 232 break; 233 234 case GETID3_ASF_Video_Media: 235 $thisfile_video['dataformat'] = (!empty($thisfile_video['dataformat']) ? $thisfile_video['dataformat'] : 'asf'); 236 $thisfile_video['bitrate_mode'] = (!empty($thisfile_video['bitrate_mode']) ? $thisfile_video['bitrate_mode'] : 'cbr'); 237 break; 238 239 case GETID3_ASF_Command_Media: 240 default: 241 // do nothing 242 break; 243 244 } 245 246 $thisfile_asf['stream_properties_object'][$StreamPropertiesObjectStreamNumber] = $StreamPropertiesObjectData; 247 unset($StreamPropertiesObjectData); // clear for next stream, if any 248 break; 249 250 case GETID3_ASF_Header_Extension_Object: 251 // Header Extension Object: (mandatory, one only) 252 // Field Name Field Type Size (bits) 253 // Object ID GUID 128 // GUID for Header Extension object - GETID3_ASF_Header_Extension_Object 254 // Object Size QWORD 64 // size of Header Extension object, including 46 bytes of Header Extension Object header 255 // Reserved Field 1 GUID 128 // hardcoded: GETID3_ASF_Reserved_1 256 // Reserved Field 2 WORD 16 // hardcoded: 0x00000006 257 // Header Extension Data Size DWORD 32 // in bytes. valid: 0, or > 24. equals object size minus 46 258 // Header Extension Data BYTESTREAM variable // array of zero or more extended header objects 259 260 // shortcut 261 $thisfile_asf['header_extension_object'] = array(); 262 $thisfile_asf_headerextensionobject = &$thisfile_asf['header_extension_object']; 263 264 $thisfile_asf_headerextensionobject['offset'] = $NextObjectOffset + $offset; 265 $thisfile_asf_headerextensionobject['objectid'] = $NextObjectGUID; 266 $thisfile_asf_headerextensionobject['objectid_guid'] = $NextObjectGUIDtext; 267 $thisfile_asf_headerextensionobject['objectsize'] = $NextObjectSize; 268 $thisfile_asf_headerextensionobject['reserved_1'] = substr($ASFHeaderData, $offset, 16); 269 $offset += 16; 270 $thisfile_asf_headerextensionobject['reserved_1_guid'] = $this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']); 271 if ($thisfile_asf_headerextensionobject['reserved_1'] != GETID3_ASF_Reserved_1) { 272 $info['warning'][] = 'header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')'; 273 //return false; 274 break; 275 } 276 $thisfile_asf_headerextensionobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 277 $offset += 2; 278 if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) { 279 $info['warning'][] = 'header_extension_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']).') does not match expected value of "6"'; 280 //return false; 281 break; 282 } 283 $thisfile_asf_headerextensionobject['extension_data_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 284 $offset += 4; 285 $thisfile_asf_headerextensionobject['extension_data'] = substr($ASFHeaderData, $offset, $thisfile_asf_headerextensionobject['extension_data_size']); 286 $unhandled_sections = 0; 287 $thisfile_asf_headerextensionobject['extension_data_parsed'] = $this->ASF_HeaderExtensionObjectDataParse($thisfile_asf_headerextensionobject['extension_data'], $unhandled_sections); 288 if ($unhandled_sections === 0) { 289 unset($thisfile_asf_headerextensionobject['extension_data']); 290 } 291 $offset += $thisfile_asf_headerextensionobject['extension_data_size']; 292 break; 293 294 case GETID3_ASF_Codec_List_Object: 295 // Codec List Object: (optional, one only) 296 // Field Name Field Type Size (bits) 297 // Object ID GUID 128 // GUID for Codec List object - GETID3_ASF_Codec_List_Object 298 // Object Size QWORD 64 // size of Codec List object, including 44 bytes of Codec List Object header 299 // Reserved GUID 128 // hardcoded: 86D15241-311D-11D0-A3A4-00A0C90348F6 300 // Codec Entries Count DWORD 32 // number of entries in Codec Entries array 301 // Codec Entries array of: variable // 302 // * Type WORD 16 // 0x0001 = Video Codec, 0x0002 = Audio Codec, 0xFFFF = Unknown Codec 303 // * Codec Name Length WORD 16 // number of Unicode characters stored in the Codec Name field 304 // * Codec Name WCHAR variable // array of Unicode characters - name of codec used to create the content 305 // * Codec Description Length WORD 16 // number of Unicode characters stored in the Codec Description field 306 // * Codec Description WCHAR variable // array of Unicode characters - description of format used to create the content 307 // * Codec Information Length WORD 16 // number of Unicode characters stored in the Codec Information field 308 // * Codec Information BYTESTREAM variable // opaque array of information bytes about the codec used to create the content 309 310 // shortcut 311 $thisfile_asf['codec_list_object'] = array(); 312 $thisfile_asf_codeclistobject = &$thisfile_asf['codec_list_object']; 313 314 $thisfile_asf_codeclistobject['offset'] = $NextObjectOffset + $offset; 315 $thisfile_asf_codeclistobject['objectid'] = $NextObjectGUID; 316 $thisfile_asf_codeclistobject['objectid_guid'] = $NextObjectGUIDtext; 317 $thisfile_asf_codeclistobject['objectsize'] = $NextObjectSize; 318 $thisfile_asf_codeclistobject['reserved'] = substr($ASFHeaderData, $offset, 16); 319 $offset += 16; 320 $thisfile_asf_codeclistobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']); 321 if ($thisfile_asf_codeclistobject['reserved'] != $this->GUIDtoBytestring('86D15241-311D-11D0-A3A4-00A0C90348F6')) { 322 $info['warning'][] = 'codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}'; 323 //return false; 324 break; 325 } 326 $thisfile_asf_codeclistobject['codec_entries_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 327 $offset += 4; 328 for ($CodecEntryCounter = 0; $CodecEntryCounter < $thisfile_asf_codeclistobject['codec_entries_count']; $CodecEntryCounter++) { 329 // shortcut 330 $thisfile_asf_codeclistobject['codec_entries'][$CodecEntryCounter] = array(); 331 $thisfile_asf_codeclistobject_codecentries_current = &$thisfile_asf_codeclistobject['codec_entries'][$CodecEntryCounter]; 332 333 $thisfile_asf_codeclistobject_codecentries_current['type_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 334 $offset += 2; 335 $thisfile_asf_codeclistobject_codecentries_current['type'] = $this->ASFCodecListObjectTypeLookup($thisfile_asf_codeclistobject_codecentries_current['type_raw']); 336 337 $CodecNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character 338 $offset += 2; 339 $thisfile_asf_codeclistobject_codecentries_current['name'] = substr($ASFHeaderData, $offset, $CodecNameLength); 340 $offset += $CodecNameLength; 341 342 $CodecDescriptionLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character 343 $offset += 2; 344 $thisfile_asf_codeclistobject_codecentries_current['description'] = substr($ASFHeaderData, $offset, $CodecDescriptionLength); 345 $offset += $CodecDescriptionLength; 346 347 $CodecInformationLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 348 $offset += 2; 349 $thisfile_asf_codeclistobject_codecentries_current['information'] = substr($ASFHeaderData, $offset, $CodecInformationLength); 350 $offset += $CodecInformationLength; 351 352 if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { // audio codec 353 354 if (strpos($thisfile_asf_codeclistobject_codecentries_current['description'], ',') === false) { 355 $info['warning'][] = '[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-seperated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"'; 356 } else { 357 358 list($AudioCodecBitrate, $AudioCodecFrequency, $AudioCodecChannels) = explode(',', $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description'])); 359 $thisfile_audio['codec'] = $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['name']); 360 361 if (!isset($thisfile_audio['bitrate']) && strstr($AudioCodecBitrate, 'kbps')) { 362 $thisfile_audio['bitrate'] = (int) (trim(str_replace('kbps', '', $AudioCodecBitrate)) * 1000); 363 } 364 //if (!isset($thisfile_video['bitrate']) && isset($thisfile_audio['bitrate']) && isset($thisfile_asf['file_properties_object']['max_bitrate']) && ($thisfile_asf_codeclistobject['codec_entries_count'] > 1)) { 365 if (empty($thisfile_video['bitrate']) && !empty($thisfile_audio['bitrate']) && !empty($info['bitrate'])) { 366 //$thisfile_video['bitrate'] = $thisfile_asf['file_properties_object']['max_bitrate'] - $thisfile_audio['bitrate']; 367 $thisfile_video['bitrate'] = $info['bitrate'] - $thisfile_audio['bitrate']; 368 } 369 370 $AudioCodecFrequency = (int) trim(str_replace('kHz', '', $AudioCodecFrequency)); 371 switch ($AudioCodecFrequency) { 372 case 8: 373 case 8000: 374 $thisfile_audio['sample_rate'] = 8000; 375 break; 376 377 case 11: 378 case 11025: 379 $thisfile_audio['sample_rate'] = 11025; 380 break; 381 382 case 12: 383 case 12000: 384 $thisfile_audio['sample_rate'] = 12000; 385 break; 386 387 case 16: 388 case 16000: 389 $thisfile_audio['sample_rate'] = 16000; 390 break; 391 392 case 22: 393 case 22050: 394 $thisfile_audio['sample_rate'] = 22050; 395 break; 396 397 case 24: 398 case 24000: 399 $thisfile_audio['sample_rate'] = 24000; 400 break; 401 402 case 32: 403 case 32000: 404 $thisfile_audio['sample_rate'] = 32000; 405 break; 406 407 case 44: 408 case 441000: 409 $thisfile_audio['sample_rate'] = 44100; 410 break; 411 412 case 48: 413 case 48000: 414 $thisfile_audio['sample_rate'] = 48000; 415 break; 416 417 default: 418 $info['warning'][] = 'unknown frequency: "'.$AudioCodecFrequency.'" ('.$this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']).')'; 419 break; 420 } 421 422 if (!isset($thisfile_audio['channels'])) { 423 if (strstr($AudioCodecChannels, 'stereo')) { 424 $thisfile_audio['channels'] = 2; 425 } elseif (strstr($AudioCodecChannels, 'mono')) { 426 $thisfile_audio['channels'] = 1; 427 } 428 } 429 430 } 431 } 432 } 433 break; 434 435 case GETID3_ASF_Script_Command_Object: 436 // Script Command Object: (optional, one only) 437 // Field Name Field Type Size (bits) 438 // Object ID GUID 128 // GUID for Script Command object - GETID3_ASF_Script_Command_Object 439 // Object Size QWORD 64 // size of Script Command object, including 44 bytes of Script Command Object header 440 // Reserved GUID 128 // hardcoded: 4B1ACBE3-100B-11D0-A39B-00A0C90348F6 441 // Commands Count WORD 16 // number of Commands structures in the Script Commands Objects 442 // Command Types Count WORD 16 // number of Command Types structures in the Script Commands Objects 443 // Command Types array of: variable // 444 // * Command Type Name Length WORD 16 // number of Unicode characters for Command Type Name 445 // * Command Type Name WCHAR variable // array of Unicode characters - name of a type of command 446 // Commands array of: variable // 447 // * Presentation Time DWORD 32 // presentation time of that command, in milliseconds 448 // * Type Index WORD 16 // type of this command, as a zero-based index into the array of Command Types of this object 449 // * Command Name Length WORD 16 // number of Unicode characters for Command Name 450 // * Command Name WCHAR variable // array of Unicode characters - name of this command 451 452 // shortcut 453 $thisfile_asf['script_command_object'] = array(); 454 $thisfile_asf_scriptcommandobject = &$thisfile_asf['script_command_object']; 455 456 $thisfile_asf_scriptcommandobject['offset'] = $NextObjectOffset + $offset; 457 $thisfile_asf_scriptcommandobject['objectid'] = $NextObjectGUID; 458 $thisfile_asf_scriptcommandobject['objectid_guid'] = $NextObjectGUIDtext; 459 $thisfile_asf_scriptcommandobject['objectsize'] = $NextObjectSize; 460 $thisfile_asf_scriptcommandobject['reserved'] = substr($ASFHeaderData, $offset, 16); 461 $offset += 16; 462 $thisfile_asf_scriptcommandobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']); 463 if ($thisfile_asf_scriptcommandobject['reserved'] != $this->GUIDtoBytestring('4B1ACBE3-100B-11D0-A39B-00A0C90348F6')) { 464 $info['warning'][] = 'script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}'; 465 //return false; 466 break; 467 } 468 $thisfile_asf_scriptcommandobject['commands_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 469 $offset += 2; 470 $thisfile_asf_scriptcommandobject['command_types_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 471 $offset += 2; 472 for ($CommandTypesCounter = 0; $CommandTypesCounter < $thisfile_asf_scriptcommandobject['command_types_count']; $CommandTypesCounter++) { 473 $CommandTypeNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character 474 $offset += 2; 475 $thisfile_asf_scriptcommandobject['command_types'][$CommandTypesCounter]['name'] = substr($ASFHeaderData, $offset, $CommandTypeNameLength); 476 $offset += $CommandTypeNameLength; 477 } 478 for ($CommandsCounter = 0; $CommandsCounter < $thisfile_asf_scriptcommandobject['commands_count']; $CommandsCounter++) { 479 $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['presentation_time'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 480 $offset += 4; 481 $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['type_index'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 482 $offset += 2; 483 484 $CommandTypeNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character 485 $offset += 2; 486 $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['name'] = substr($ASFHeaderData, $offset, $CommandTypeNameLength); 487 $offset += $CommandTypeNameLength; 488 } 489 break; 490 491 case GETID3_ASF_Marker_Object: 492 // Marker Object: (optional, one only) 493 // Field Name Field Type Size (bits) 494 // Object ID GUID 128 // GUID for Marker object - GETID3_ASF_Marker_Object 495 // Object Size QWORD 64 // size of Marker object, including 48 bytes of Marker Object header 496 // Reserved GUID 128 // hardcoded: 4CFEDB20-75F6-11CF-9C0F-00A0C90349CB 497 // Markers Count DWORD 32 // number of Marker structures in Marker Object 498 // Reserved WORD 16 // hardcoded: 0x0000 499 // Name Length WORD 16 // number of bytes in the Name field 500 // Name WCHAR variable // name of the Marker Object 501 // Markers array of: variable // 502 // * Offset QWORD 64 // byte offset into Data Object 503 // * Presentation Time QWORD 64 // in 100-nanosecond units 504 // * Entry Length WORD 16 // length in bytes of (Send Time + Flags + Marker Description Length + Marker Description + Padding) 505 // * Send Time DWORD 32 // in milliseconds 506 // * Flags DWORD 32 // hardcoded: 0x00000000 507 // * Marker Description Length DWORD 32 // number of bytes in Marker Description field 508 // * Marker Description WCHAR variable // array of Unicode characters - description of marker entry 509 // * Padding BYTESTREAM variable // optional padding bytes 510 511 // shortcut 512 $thisfile_asf['marker_object'] = array(); 513 $thisfile_asf_markerobject = &$thisfile_asf['marker_object']; 514 515 $thisfile_asf_markerobject['offset'] = $NextObjectOffset + $offset; 516 $thisfile_asf_markerobject['objectid'] = $NextObjectGUID; 517 $thisfile_asf_markerobject['objectid_guid'] = $NextObjectGUIDtext; 518 $thisfile_asf_markerobject['objectsize'] = $NextObjectSize; 519 $thisfile_asf_markerobject['reserved'] = substr($ASFHeaderData, $offset, 16); 520 $offset += 16; 521 $thisfile_asf_markerobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_markerobject['reserved']); 522 if ($thisfile_asf_markerobject['reserved'] != $this->GUIDtoBytestring('4CFEDB20-75F6-11CF-9C0F-00A0C90349CB')) { 523 $info['warning'][] = 'marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved_1']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}'; 524 break; 525 } 526 $thisfile_asf_markerobject['markers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 527 $offset += 4; 528 $thisfile_asf_markerobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 529 $offset += 2; 530 if ($thisfile_asf_markerobject['reserved_2'] != 0) { 531 $info['warning'][] = 'marker_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_markerobject['reserved_2']).') does not match expected value of "0"'; 532 break; 533 } 534 $thisfile_asf_markerobject['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 535 $offset += 2; 536 $thisfile_asf_markerobject['name'] = substr($ASFHeaderData, $offset, $thisfile_asf_markerobject['name_length']); 537 $offset += $thisfile_asf_markerobject['name_length']; 538 for ($MarkersCounter = 0; $MarkersCounter < $thisfile_asf_markerobject['markers_count']; $MarkersCounter++) { 539 $thisfile_asf_markerobject['markers'][$MarkersCounter]['offset'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); 540 $offset += 8; 541 $thisfile_asf_markerobject['markers'][$MarkersCounter]['presentation_time'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); 542 $offset += 8; 543 $thisfile_asf_markerobject['markers'][$MarkersCounter]['entry_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 544 $offset += 2; 545 $thisfile_asf_markerobject['markers'][$MarkersCounter]['send_time'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 546 $offset += 4; 547 $thisfile_asf_markerobject['markers'][$MarkersCounter]['flags'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 548 $offset += 4; 549 $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 550 $offset += 4; 551 $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description'] = substr($ASFHeaderData, $offset, $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']); 552 $offset += $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']; 553 $PaddingLength = $thisfile_asf_markerobject['markers'][$MarkersCounter]['entry_length'] - 4 - 4 - 4 - $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']; 554 if ($PaddingLength > 0) { 555 $thisfile_asf_markerobject['markers'][$MarkersCounter]['padding'] = substr($ASFHeaderData, $offset, $PaddingLength); 556 $offset += $PaddingLength; 557 } 558 } 559 break; 560 561 case GETID3_ASF_Bitrate_Mutual_Exclusion_Object: 562 // Bitrate Mutual Exclusion Object: (optional) 563 // Field Name Field Type Size (bits) 564 // Object ID GUID 128 // GUID for Bitrate Mutual Exclusion object - GETID3_ASF_Bitrate_Mutual_Exclusion_Object 565 // Object Size QWORD 64 // size of Bitrate Mutual Exclusion object, including 42 bytes of Bitrate Mutual Exclusion Object header 566 // Exlusion Type GUID 128 // nature of mutual exclusion relationship. one of: (GETID3_ASF_Mutex_Bitrate, GETID3_ASF_Mutex_Unknown) 567 // Stream Numbers Count WORD 16 // number of video streams 568 // Stream Numbers WORD variable // array of mutually exclusive video stream numbers. 1 <= valid <= 127 569 570 // shortcut 571 $thisfile_asf['bitrate_mutual_exclusion_object'] = array(); 572 $thisfile_asf_bitratemutualexclusionobject = &$thisfile_asf['bitrate_mutual_exclusion_object']; 573 574 $thisfile_asf_bitratemutualexclusionobject['offset'] = $NextObjectOffset + $offset; 575 $thisfile_asf_bitratemutualexclusionobject['objectid'] = $NextObjectGUID; 576 $thisfile_asf_bitratemutualexclusionobject['objectid_guid'] = $NextObjectGUIDtext; 577 $thisfile_asf_bitratemutualexclusionobject['objectsize'] = $NextObjectSize; 578 $thisfile_asf_bitratemutualexclusionobject['reserved'] = substr($ASFHeaderData, $offset, 16); 579 $thisfile_asf_bitratemutualexclusionobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']); 580 $offset += 16; 581 if (($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Bitrate) && ($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Unknown)) { 582 $info['warning'][] = 'bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}'; 583 //return false; 584 break; 585 } 586 $thisfile_asf_bitratemutualexclusionobject['stream_numbers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 587 $offset += 2; 588 for ($StreamNumberCounter = 0; $StreamNumberCounter < $thisfile_asf_bitratemutualexclusionobject['stream_numbers_count']; $StreamNumberCounter++) { 589 $thisfile_asf_bitratemutualexclusionobject['stream_numbers'][$StreamNumberCounter] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 590 $offset += 2; 591 } 592 break; 593 594 case GETID3_ASF_Error_Correction_Object: 595 // Error Correction Object: (optional, one only) 596 // Field Name Field Type Size (bits) 597 // Object ID GUID 128 // GUID for Error Correction object - GETID3_ASF_Error_Correction_Object 598 // Object Size QWORD 64 // size of Error Correction object, including 44 bytes of Error Correction Object header 599 // Error Correction Type GUID 128 // type of error correction. one of: (GETID3_ASF_No_Error_Correction, GETID3_ASF_Audio_Spread) 600 // Error Correction Data Length DWORD 32 // number of bytes in Error Correction Data field 601 // Error Correction Data BYTESTREAM variable // structure depends on value of Error Correction Type field 602 603 // shortcut 604 $thisfile_asf['error_correction_object'] = array(); 605 $thisfile_asf_errorcorrectionobject = &$thisfile_asf['error_correction_object']; 606 607 $thisfile_asf_errorcorrectionobject['offset'] = $NextObjectOffset + $offset; 608 $thisfile_asf_errorcorrectionobject['objectid'] = $NextObjectGUID; 609 $thisfile_asf_errorcorrectionobject['objectid_guid'] = $NextObjectGUIDtext; 610 $thisfile_asf_errorcorrectionobject['objectsize'] = $NextObjectSize; 611 $thisfile_asf_errorcorrectionobject['error_correction_type'] = substr($ASFHeaderData, $offset, 16); 612 $offset += 16; 613 $thisfile_asf_errorcorrectionobject['error_correction_guid'] = $this->BytestringToGUID($thisfile_asf_errorcorrectionobject['error_correction_type']); 614 $thisfile_asf_errorcorrectionobject['error_correction_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 615 $offset += 4; 616 switch ($thisfile_asf_errorcorrectionobject['error_correction_type']) { 617 case GETID3_ASF_No_Error_Correction: 618 // should be no data, but just in case there is, skip to the end of the field 619 $offset += $thisfile_asf_errorcorrectionobject['error_correction_data_length']; 620 break; 621 622 case GETID3_ASF_Audio_Spread: 623 // Field Name Field Type Size (bits) 624 // Span BYTE 8 // number of packets over which audio will be spread. 625 // Virtual Packet Length WORD 16 // size of largest audio payload found in audio stream 626 // Virtual Chunk Length WORD 16 // size of largest audio payload found in audio stream 627 // Silence Data Length WORD 16 // number of bytes in Silence Data field 628 // Silence Data BYTESTREAM variable // hardcoded: 0x00 * (Silence Data Length) bytes 629 630 $thisfile_asf_errorcorrectionobject['span'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 1)); 631 $offset += 1; 632 $thisfile_asf_errorcorrectionobject['virtual_packet_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 633 $offset += 2; 634 $thisfile_asf_errorcorrectionobject['virtual_chunk_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 635 $offset += 2; 636 $thisfile_asf_errorcorrectionobject['silence_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 637 $offset += 2; 638 $thisfile_asf_errorcorrectionobject['silence_data'] = substr($ASFHeaderData, $offset, $thisfile_asf_errorcorrectionobject['silence_data_length']); 639 $offset += $thisfile_asf_errorcorrectionobject['silence_data_length']; 640 break; 641 642 default: 643 $info['warning'][] = 'error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['reserved']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}'; 644 //return false; 645 break; 646 } 647 648 break; 649 650 case GETID3_ASF_Content_Description_Object: 651 // Content Description Object: (optional, one only) 652 // Field Name Field Type Size (bits) 653 // Object ID GUID 128 // GUID for Content Description object - GETID3_ASF_Content_Description_Object 654 // Object Size QWORD 64 // size of Content Description object, including 34 bytes of Content Description Object header 655 // Title Length WORD 16 // number of bytes in Title field 656 // Author Length WORD 16 // number of bytes in Author field 657 // Copyright Length WORD 16 // number of bytes in Copyright field 658 // Description Length WORD 16 // number of bytes in Description field 659 // Rating Length WORD 16 // number of bytes in Rating field 660 // Title WCHAR 16 // array of Unicode characters - Title 661 // Author WCHAR 16 // array of Unicode characters - Author 662 // Copyright WCHAR 16 // array of Unicode characters - Copyright 663 // Description WCHAR 16 // array of Unicode characters - Description 664 // Rating WCHAR 16 // array of Unicode characters - Rating 665 666 // shortcut 667 $thisfile_asf['content_description_object'] = array(); 668 $thisfile_asf_contentdescriptionobject = &$thisfile_asf['content_description_object']; 669 670 $thisfile_asf_contentdescriptionobject['offset'] = $NextObjectOffset + $offset; 671 $thisfile_asf_contentdescriptionobject['objectid'] = $NextObjectGUID; 672 $thisfile_asf_contentdescriptionobject['objectid_guid'] = $NextObjectGUIDtext; 673 $thisfile_asf_contentdescriptionobject['objectsize'] = $NextObjectSize; 674 $thisfile_asf_contentdescriptionobject['title_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 675 $offset += 2; 676 $thisfile_asf_contentdescriptionobject['author_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 677 $offset += 2; 678 $thisfile_asf_contentdescriptionobject['copyright_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 679 $offset += 2; 680 $thisfile_asf_contentdescriptionobject['description_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 681 $offset += 2; 682 $thisfile_asf_contentdescriptionobject['rating_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 683 $offset += 2; 684 $thisfile_asf_contentdescriptionobject['title'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['title_length']); 685 $offset += $thisfile_asf_contentdescriptionobject['title_length']; 686 $thisfile_asf_contentdescriptionobject['author'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['author_length']); 687 $offset += $thisfile_asf_contentdescriptionobject['author_length']; 688 $thisfile_asf_contentdescriptionobject['copyright'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['copyright_length']); 689 $offset += $thisfile_asf_contentdescriptionobject['copyright_length']; 690 $thisfile_asf_contentdescriptionobject['description'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['description_length']); 691 $offset += $thisfile_asf_contentdescriptionobject['description_length']; 692 $thisfile_asf_contentdescriptionobject['rating'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['rating_length']); 693 $offset += $thisfile_asf_contentdescriptionobject['rating_length']; 694 695 $ASFcommentKeysToCopy = array('title'=>'title', 'author'=>'artist', 'copyright'=>'copyright', 'description'=>'comment', 'rating'=>'rating'); 696 foreach ($ASFcommentKeysToCopy as $keytocopyfrom => $keytocopyto) { 697 if (!empty($thisfile_asf_contentdescriptionobject[$keytocopyfrom])) { 698 $thisfile_asf_comments[$keytocopyto][] = $this->TrimTerm($thisfile_asf_contentdescriptionobject[$keytocopyfrom]); 699 } 700 } 701 break; 702 703 case GETID3_ASF_Extended_Content_Description_Object: 704 // Extended Content Description Object: (optional, one only) 705 // Field Name Field Type Size (bits) 706 // Object ID GUID 128 // GUID for Extended Content Description object - GETID3_ASF_Extended_Content_Description_Object 707 // Object Size QWORD 64 // size of ExtendedContent Description object, including 26 bytes of Extended Content Description Object header 708 // Content Descriptors Count WORD 16 // number of entries in Content Descriptors list 709 // Content Descriptors array of: variable // 710 // * Descriptor Name Length WORD 16 // size in bytes of Descriptor Name field 711 // * Descriptor Name WCHAR variable // array of Unicode characters - Descriptor Name 712 // * Descriptor Value Data Type WORD 16 // Lookup array: 713 // 0x0000 = Unicode String (variable length) 714 // 0x0001 = BYTE array (variable length) 715 // 0x0002 = BOOL (DWORD, 32 bits) 716 // 0x0003 = DWORD (DWORD, 32 bits) 717 // 0x0004 = QWORD (QWORD, 64 bits) 718 // 0x0005 = WORD (WORD, 16 bits) 719 // * Descriptor Value Length WORD 16 // number of bytes stored in Descriptor Value field 720 // * Descriptor Value variable variable // value for Content Descriptor 721 722 // shortcut 723 $thisfile_asf['extended_content_description_object'] = array(); 724 $thisfile_asf_extendedcontentdescriptionobject = &$thisfile_asf['extended_content_description_object']; 725 726 $thisfile_asf_extendedcontentdescriptionobject['offset'] = $NextObjectOffset + $offset; 727 $thisfile_asf_extendedcontentdescriptionobject['objectid'] = $NextObjectGUID; 728 $thisfile_asf_extendedcontentdescriptionobject['objectid_guid'] = $NextObjectGUIDtext; 729 $thisfile_asf_extendedcontentdescriptionobject['objectsize'] = $NextObjectSize; 730 $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 731 $offset += 2; 732 for ($ExtendedContentDescriptorsCounter = 0; $ExtendedContentDescriptorsCounter < $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count']; $ExtendedContentDescriptorsCounter++) { 733 // shortcut 734 $thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter] = array(); 735 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current = &$thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter]; 736 737 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['base_offset'] = $offset + 30; 738 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 739 $offset += 2; 740 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name'] = substr($ASFHeaderData, $offset, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length']); 741 $offset += $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length']; 742 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 743 $offset += 2; 744 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 745 $offset += 2; 746 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = substr($ASFHeaderData, $offset, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length']); 747 $offset += $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length']; 748 switch ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']) { 749 case 0x0000: // Unicode string 750 break; 751 752 case 0x0001: // BYTE array 753 // do nothing 754 break; 755 756 case 0x0002: // BOOL 757 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = (bool) getid3_lib::LittleEndian2Int($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); 758 break; 759 760 case 0x0003: // DWORD 761 case 0x0004: // QWORD 762 case 0x0005: // WORD 763 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = getid3_lib::LittleEndian2Int($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); 764 break; 765 766 default: 767 $info['warning'][] = 'extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')'; 768 //return false; 769 break; 770 } 771 switch ($this->TrimConvert(strtolower($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']))) { 772 773 case 'wm/albumartist': 774 case 'artist': 775 // Note: not 'artist', that comes from 'author' tag 776 $thisfile_asf_comments['albumartist'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); 777 break; 778 779 case 'wm/albumtitle': 780 case 'album': 781 $thisfile_asf_comments['album'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); 782 break; 783 784 case 'wm/genre': 785 case 'genre': 786 $thisfile_asf_comments['genre'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); 787 break; 788 789 case 'wm/partofset': 790 $thisfile_asf_comments['partofset'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); 791 break; 792 793 case 'wm/tracknumber': 794 case 'tracknumber': 795 // be careful casting to int: casting unicode strings to int gives unexpected results (stops parsing at first non-numeric character) 796 $thisfile_asf_comments['track'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); 797 foreach ($thisfile_asf_comments['track'] as $key => $value) { 798 if (preg_match('/^[0-9\x00]+$/', $value)) { 799 $thisfile_asf_comments['track'][$key] = intval(str_replace("\x00", '', $value)); 800 } 801 } 802 break; 803 804 case 'wm/track': 805 if (empty($thisfile_asf_comments['track'])) { 806 $thisfile_asf_comments['track'] = array(1 + $this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); 807 } 808 break; 809 810 case 'wm/year': 811 case 'year': 812 case 'date': 813 $thisfile_asf_comments['year'] = array( $this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); 814 break; 815 816 case 'wm/lyrics': 817 case 'lyrics': 818 $thisfile_asf_comments['lyrics'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); 819 break; 820 821 case 'isvbr': 822 if ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']) { 823 $thisfile_audio['bitrate_mode'] = 'vbr'; 824 $thisfile_video['bitrate_mode'] = 'vbr'; 825 } 826 break; 827 828 case 'id3': 829 // id3v2 module might not be loaded 830 if (class_exists('getid3_id3v2')) { 831 $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); 832 $tempfilehandle = fopen($tempfile, 'wb'); 833 $tempThisfileInfo = array('encoding'=>$info['encoding']); 834 fwrite($tempfilehandle, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); 835 fclose($tempfilehandle); 836 837 $getid3_temp = new getID3(); 838 $getid3_temp->openfile($tempfile); 839 $getid3_id3v2 = new getid3_id3v2($getid3_temp); 840 $getid3_id3v2->Analyze(); 841 $info['id3v2'] = $getid3_temp->info['id3v2']; 842 unset($getid3_temp, $getid3_id3v2); 843 844 unlink($tempfile); 845 } 846 break; 847 848 case 'wm/encodingtime': 849 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['encoding_time_unix'] = $this->FILETIMEtoUNIXtime($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); 850 $thisfile_asf_comments['encoding_time_unix'] = array($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['encoding_time_unix']); 851 break; 852 853 case 'wm/picture': 854 $WMpicture = $this->ASF_WMpicture($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); 855 foreach ($WMpicture as $key => $value) { 856 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current[$key] = $value; 857 } 858 unset($WMpicture); 859 /* 860 $wm_picture_offset = 0; 861 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 1)); 862 $wm_picture_offset += 1; 863 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type'] = $this->WMpictureTypeLookup($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id']); 864 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_size'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 4)); 865 $wm_picture_offset += 4; 866 867 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = ''; 868 do { 869 $next_byte_pair = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 2); 870 $wm_picture_offset += 2; 871 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] .= $next_byte_pair; 872 } while ($next_byte_pair !== "\x00\x00"); 873 874 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_description'] = ''; 875 do { 876 $next_byte_pair = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 2); 877 $wm_picture_offset += 2; 878 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_description'] .= $next_byte_pair; 879 } while ($next_byte_pair !== "\x00\x00"); 880 881 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['dataoffset'] = $wm_picture_offset; 882 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'] = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset); 883 unset($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); 884 885 $imageinfo = array(); 886 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = ''; 887 $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], $imageinfo); 888 unset($imageinfo); 889 if (!empty($imagechunkcheck)) { 890 $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); 891 } 892 if (!isset($thisfile_asf_comments['picture'])) { 893 $thisfile_asf_comments['picture'] = array(); 894 } 895 $thisfile_asf_comments['picture'][] = array('data'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], 'image_mime'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime']); 896 */ 897 break; 898 899 default: 900 switch ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']) { 901 case 0: // Unicode string 902 if (substr($this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']), 0, 3) == 'WM/') { 903 $thisfile_asf_comments[str_replace('wm/', '', strtolower($this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name'])))] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); 904 } 905 break; 906 907 case 1: 908 break; 909 } 910 break; 911 } 912 913 } 914 break; 915 916 case GETID3_ASF_Stream_Bitrate_Properties_Object: 917 // Stream Bitrate Properties Object: (optional, one only) 918 // Field Name Field Type Size (bits) 919 // Object ID GUID 128 // GUID for Stream Bitrate Properties object - GETID3_ASF_Stream_Bitrate_Properties_Object 920 // Object Size QWORD 64 // size of Extended Content Description object, including 26 bytes of Stream Bitrate Properties Object header 921 // Bitrate Records Count WORD 16 // number of records in Bitrate Records 922 // Bitrate Records array of: variable // 923 // * Flags WORD 16 // 924 // * * Stream Number bits 7 (0x007F) // number of this stream 925 // * * Reserved bits 9 (0xFF80) // hardcoded: 0 926 // * Average Bitrate DWORD 32 // in bits per second 927 928 // shortcut 929 $thisfile_asf['stream_bitrate_properties_object'] = array(); 930 $thisfile_asf_streambitratepropertiesobject = &$thisfile_asf['stream_bitrate_properties_object']; 931 932 $thisfile_asf_streambitratepropertiesobject['offset'] = $NextObjectOffset + $offset; 933 $thisfile_asf_streambitratepropertiesobject['objectid'] = $NextObjectGUID; 934 $thisfile_asf_streambitratepropertiesobject['objectid_guid'] = $NextObjectGUIDtext; 935 $thisfile_asf_streambitratepropertiesobject['objectsize'] = $NextObjectSize; 936 $thisfile_asf_streambitratepropertiesobject['bitrate_records_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 937 $offset += 2; 938 for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < $thisfile_asf_streambitratepropertiesobject['bitrate_records_count']; $BitrateRecordsCounter++) { 939 $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); 940 $offset += 2; 941 $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags']['stream_number'] = $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags_raw'] & 0x007F; 942 $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['bitrate'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); 943 $offset += 4; 944 } 945 break; 946 947 case GETID3_ASF_Padding_Object: 948 // Padding Object: (optional) 949 // Field Name Field Type Size (bits) 950 // Object ID GUID 128 // GUID for Padding object - GETID3_ASF_Padding_Object 951 // Object Size QWORD 64 // size of Padding object, including 24 bytes of ASF Padding Object header 952 // Padding Data BYTESTREAM variable // ignore 953 954 // shortcut 955 $thisfile_asf['padding_object'] = array(); 956 $thisfile_asf_paddingobject = &$thisfile_asf['padding_object']; 957 958 $thisfile_asf_paddingobject['offset'] = $NextObjectOffset + $offset; 959 $thisfile_asf_paddingobject['objectid'] = $NextObjectGUID; 960 $thisfile_asf_paddingobject['objectid_guid'] = $NextObjectGUIDtext; 961 $thisfile_asf_paddingobject['objectsize'] = $NextObjectSize; 962 $thisfile_asf_paddingobject['padding_length'] = $thisfile_asf_paddingobject['objectsize'] - 16 - 8; 963 $thisfile_asf_paddingobject['padding'] = substr($ASFHeaderData, $offset, $thisfile_asf_paddingobject['padding_length']); 964 $offset += ($NextObjectSize - 16 - 8); 965 break; 966 967 case GETID3_ASF_Extended_Content_Encryption_Object: 968 case GETID3_ASF_Content_Encryption_Object: 969 // WMA DRM - just ignore 970 $offset += ($NextObjectSize - 16 - 8); 971 break; 972 973 default: 974 // Implementations shall ignore any standard or non-standard object that they do not know how to handle. 975 if ($this->GUIDname($NextObjectGUIDtext)) { 976 $info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); 977 } else { 978 $info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); 979 } 980 $offset += ($NextObjectSize - 16 - 8); 981 break; 982 } 983 } 984 if (isset($thisfile_asf_streambitrateproperties['bitrate_records_count'])) { 985 $ASFbitrateAudio = 0; 986 $ASFbitrateVideo = 0; 987 for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < $thisfile_asf_streambitrateproperties['bitrate_records_count']; $BitrateRecordsCounter++) { 988 if (isset($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter])) { 989 switch ($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter]['type_raw']) { 990 case 1: 991 $ASFbitrateVideo += $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate']; 992 break; 993 994 case 2: 995 $ASFbitrateAudio += $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate']; 996 break; 997 998 default: 999 // do nothing 1000 break; 1001 } 1002 } 1003 } 1004 if ($ASFbitrateAudio > 0) { 1005 $thisfile_audio['bitrate'] = $ASFbitrateAudio; 1006 } 1007 if ($ASFbitrateVideo > 0) { 1008 $thisfile_video['bitrate'] = $ASFbitrateVideo; 1009 } 1010 } 1011 if (isset($thisfile_asf['stream_properties_object']) && is_array($thisfile_asf['stream_properties_object'])) { 1012 1013 $thisfile_audio['bitrate'] = 0; 1014 $thisfile_video['bitrate'] = 0; 1015 1016 foreach ($thisfile_asf['stream_properties_object'] as $streamnumber => $streamdata) { 1017 1018 switch ($streamdata['stream_type']) { 1019 case GETID3_ASF_Audio_Media: 1020 // Field Name Field Type Size (bits) 1021 // Codec ID / Format Tag WORD 16 // unique ID of audio codec - defined as wFormatTag field of WAVEFORMATEX structure 1022 // Number of Channels WORD 16 // number of channels of audio - defined as nChannels field of WAVEFORMATEX structure 1023 // Samples Per Second DWORD 32 // in Hertz - defined as nSamplesPerSec field of WAVEFORMATEX structure 1024 // Average number of Bytes/sec DWORD 32 // bytes/sec of audio stream - defined as nAvgBytesPerSec field of WAVEFORMATEX structure 1025 // Block Alignment WORD 16 // block size in bytes of audio codec - defined as nBlockAlign field of WAVEFORMATEX structure 1026 // Bits per sample WORD 16 // bits per sample of mono data. set to zero for variable bitrate codecs. defined as wBitsPerSample field of WAVEFORMATEX structure 1027 // Codec Specific Data Size WORD 16 // size in bytes of Codec Specific Data buffer - defined as cbSize field of WAVEFORMATEX structure 1028 // Codec Specific Data BYTESTREAM variable // array of codec-specific data bytes 1029 1030 // shortcut 1031 $thisfile_asf['audio_media'][$streamnumber] = array(); 1032 $thisfile_asf_audiomedia_currentstream = &$thisfile_asf['audio_media'][$streamnumber]; 1033 1034 $audiomediaoffset = 0; 1035 1036 $thisfile_asf_audiomedia_currentstream = getid3_riff::parseWAVEFORMATex(substr($streamdata['type_specific_data'], $audiomediaoffset, 16)); 1037 $audiomediaoffset += 16; 1038 1039 $thisfile_audio['lossless'] = false; 1040 switch ($thisfile_asf_audiomedia_currentstream['raw']['wFormatTag']) { 1041 case 0x0001: // PCM 1042 case 0x0163: // WMA9 Lossless 1043 $thisfile_audio['lossless'] = true; 1044 break; 1045 } 1046 1047 if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { 1048 foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) { 1049 if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) { 1050 $thisfile_asf_audiomedia_currentstream['bitrate'] = $dataarray['bitrate']; 1051 $thisfile_audio['bitrate'] += $dataarray['bitrate']; 1052 break; 1053 } 1054 } 1055 } else { 1056 if (!empty($thisfile_asf_audiomedia_currentstream['bytes_sec'])) { 1057 $thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bytes_sec'] * 8; 1058 } elseif (!empty($thisfile_asf_audiomedia_currentstream['bitrate'])) { 1059 $thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bitrate']; 1060 } 1061 } 1062 $thisfile_audio['streams'][$streamnumber] = $thisfile_asf_audiomedia_currentstream; 1063 $thisfile_audio['streams'][$streamnumber]['wformattag'] = $thisfile_asf_audiomedia_currentstream['raw']['wFormatTag']; 1064 $thisfile_audio['streams'][$streamnumber]['lossless'] = $thisfile_audio['lossless']; 1065 $thisfile_audio['streams'][$streamnumber]['bitrate'] = $thisfile_audio['bitrate']; 1066 $thisfile_audio['streams'][$streamnumber]['dataformat'] = 'wma'; 1067 unset($thisfile_audio['streams'][$streamnumber]['raw']); 1068 1069 $thisfile_asf_audiomedia_currentstream['codec_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $audiomediaoffset, 2)); 1070 $audiomediaoffset += 2; 1071 $thisfile_asf_audiomedia_currentstream['codec_data'] = substr($streamdata['type_specific_data'], $audiomediaoffset, $thisfile_asf_audiomedia_currentstream['codec_data_size']); 1072 $audiomediaoffset += $thisfile_asf_audiomedia_currentstream['codec_data_size']; 1073 1074 break; 1075 1076 case GETID3_ASF_Video_Media: 1077 // Field Name Field Type Size (bits) 1078 // Encoded Image Width DWORD 32 // width of image in pixels 1079 // Encoded Image Height DWORD 32 // height of image in pixels 1080 // Reserved Flags BYTE 8 // hardcoded: 0x02 1081 // Format Data Size WORD 16 // size of Format Data field in bytes 1082 // Format Data array of: variable // 1083 // * Format Data Size DWORD 32 // number of bytes in Format Data field, in bytes - defined as biSize field of BITMAPINFOHEADER structure 1084 // * Image Width LONG 32 // width of encoded image in pixels - defined as biWidth field of BITMAPINFOHEADER structure 1085 // * Image Height LONG 32 // height of encoded image in pixels - defined as biHeight field of BITMAPINFOHEADER structure 1086 // * Reserved WORD 16 // hardcoded: 0x0001 - defined as biPlanes field of BITMAPINFOHEADER structure 1087 // * Bits Per Pixel Count WORD 16 // bits per pixel - defined as biBitCount field of BITMAPINFOHEADER structure 1088 // * Compression ID FOURCC 32 // fourcc of video codec - defined as biCompression field of BITMAPINFOHEADER structure 1089 // * Image Size DWORD 32 // image size in bytes - defined as biSizeImage field of BITMAPINFOHEADER structure 1090 // * Horizontal Pixels / Meter DWORD 32 // horizontal resolution of target device in pixels per meter - defined as biXPelsPerMeter field of BITMAPINFOHEADER structure 1091 // * Vertical Pixels / Meter DWORD 32 // vertical resolution of target device in pixels per meter - defined as biYPelsPerMeter field of BITMAPINFOHEADER structure 1092 // * Colors Used Count DWORD 32 // number of color indexes in the color table that are actually used - defined as biClrUsed field of BITMAPINFOHEADER structure 1093 // * Important Colors Count DWORD 32 // number of color index required for displaying bitmap. if zero, all colors are required. defined as biClrImportant field of BITMAPINFOHEADER structure 1094 // * Codec Specific Data BYTESTREAM variable // array of codec-specific data bytes 1095 1096 // shortcut 1097 $thisfile_asf['video_media'][$streamnumber] = array(); 1098 $thisfile_asf_videomedia_currentstream = &$thisfile_asf['video_media'][$streamnumber]; 1099 1100 $videomediaoffset = 0; 1101 $thisfile_asf_videomedia_currentstream['image_width'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); 1102 $videomediaoffset += 4; 1103 $thisfile_asf_videomedia_currentstream['image_height'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); 1104 $videomediaoffset += 4; 1105 $thisfile_asf_videomedia_currentstream['flags'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 1)); 1106 $videomediaoffset += 1; 1107 $thisfile_asf_videomedia_currentstream['format_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2)); 1108 $videomediaoffset += 2; 1109 $thisfile_asf_videomedia_currentstream['format_data']['format_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); 1110 $videomediaoffset += 4; 1111 $thisfile_asf_videomedia_currentstream['format_data']['image_width'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); 1112 $videomediaoffset += 4; 1113 $thisfile_asf_videomedia_currentstream['format_data']['image_height'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); 1114 $videomediaoffset += 4; 1115 $thisfile_asf_videomedia_currentstream['format_data']['reserved'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2)); 1116 $videomediaoffset += 2; 1117 $thisfile_asf_videomedia_currentstream['format_data']['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2)); 1118 $videomediaoffset += 2; 1119 $thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc'] = substr($streamdata['type_specific_data'], $videomediaoffset, 4); 1120 $videomediaoffset += 4; 1121 $thisfile_asf_videomedia_currentstream['format_data']['image_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); 1122 $videomediaoffset += 4; 1123 $thisfile_asf_videomedia_currentstream['format_data']['horizontal_pels'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); 1124 $videomediaoffset += 4; 1125 $thisfile_asf_videomedia_currentstream['format_data']['vertical_pels'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); 1126 $videomediaoffset += 4; 1127 $thisfile_asf_videomedia_currentstream['format_data']['colors_used'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); 1128 $videomediaoffset += 4; 1129 $thisfile_asf_videomedia_currentstream['format_data']['colors_important'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); 1130 $videomediaoffset += 4; 1131 $thisfile_asf_videomedia_currentstream['format_data']['codec_data'] = substr($streamdata['type_specific_data'], $videomediaoffset); 1132 1133 if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { 1134 foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) { 1135 if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) { 1136 $thisfile_asf_videomedia_currentstream['bitrate'] = $dataarray['bitrate']; 1137 $thisfile_video['streams'][$streamnumber]['bitrate'] = $dataarray['bitrate']; 1138 $thisfile_video['bitrate'] += $dataarray['bitrate']; 1139 break; 1140 } 1141 } 1142 } 1143 1144 $thisfile_asf_videomedia_currentstream['format_data']['codec'] = getid3_riff::fourccLookup($thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']); 1145 1146 $thisfile_video['streams'][$streamnumber]['fourcc'] = $thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']; 1147 $thisfile_video['streams'][$streamnumber]['codec'] = $thisfile_asf_videomedia_currentstream['format_data']['codec']; 1148 $thisfile_video['streams'][$streamnumber]['resolution_x'] = $thisfile_asf_videomedia_currentstream['image_width']; 1149 $thisfile_video['streams'][$streamnumber]['resolution_y'] = $thisfile_asf_videomedia_currentstream['image_height']; 1150 $thisfile_video['streams'][$streamnumber]['bits_per_sample'] = $thisfile_asf_videomedia_currentstream['format_data']['bits_per_pixel']; 1151 break; 1152 1153 default: 1154 break; 1155 } 1156 } 1157 } 1158 1159 while (ftell($this->getid3->fp) < $info['avdataend']) { 1160 $NextObjectDataHeader = fread($this->getid3->fp, 24); 1161 $offset = 0; 1162 $NextObjectGUID = substr($NextObjectDataHeader, 0, 16); 1163 $offset += 16; 1164 $NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID); 1165 $NextObjectSize = getid3_lib::LittleEndian2Int(substr($NextObjectDataHeader, $offset, 8)); 1166 $offset += 8; 1167 1168 switch ($NextObjectGUID) { 1169 case GETID3_ASF_Data_Object: 1170 // Data Object: (mandatory, one only) 1171 // Field Name Field Type Size (bits) 1172 // Object ID GUID 128 // GUID for Data object - GETID3_ASF_Data_Object 1173 // Object Size QWORD 64 // size of Data object, including 50 bytes of Data Object header. may be 0 if FilePropertiesObject.BroadcastFlag == 1 1174 // File ID GUID 128 // unique identifier. identical to File ID field in Header Object 1175 // Total Data Packets QWORD 64 // number of Data Packet entries in Data Object. invalid if FilePropertiesObject.BroadcastFlag == 1 1176 // Reserved WORD 16 // hardcoded: 0x0101 1177 1178 // shortcut 1179 $thisfile_asf['data_object'] = array(); 1180 $thisfile_asf_dataobject = &$thisfile_asf['data_object']; 1181 1182 $DataObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 50 - 24); 1183 $offset = 24; 1184 1185 $thisfile_asf_dataobject['objectid'] = $NextObjectGUID; 1186 $thisfile_asf_dataobject['objectid_guid'] = $NextObjectGUIDtext; 1187 $thisfile_asf_dataobject['objectsize'] = $NextObjectSize; 1188 1189 $thisfile_asf_dataobject['fileid'] = substr($DataObjectData, $offset, 16); 1190 $offset += 16; 1191 $thisfile_asf_dataobject['fileid_guid'] = $this->BytestringToGUID($thisfile_asf_dataobject['fileid']); 1192 $thisfile_asf_dataobject['total_data_packets'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 8)); 1193 $offset += 8; 1194 $thisfile_asf_dataobject['reserved'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 2)); 1195 $offset += 2; 1196 if ($thisfile_asf_dataobject['reserved'] != 0x0101) { 1197 $info['warning'][] = 'data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"'; 1198 //return false; 1199 break; 1200 } 1201 1202 // Data Packets array of: variable // 1203 // * Error Correction Flags BYTE 8 // 1204 // * * Error Correction Data Length bits 4 // if Error Correction Length Type == 00, size of Error Correction Data in bytes, else hardcoded: 0000 1205 // * * Opaque Data Present bits 1 // 1206 // * * Error Correction Length Type bits 2 // number of bits for size of the error correction data. hardcoded: 00 1207 // * * Error Correction Present bits 1 // If set, use Opaque Data Packet structure, else use Payload structure 1208 // * Error Correction Data 1209 1210 $info['avdataoffset'] = ftell($this->getid3->fp); 1211 fseek($this->getid3->fp, ($thisfile_asf_dataobject['objectsize'] - 50), SEEK_CUR); // skip actual audio/video data 1212 $info['avdataend'] = ftell($this->getid3->fp); 1213 break; 1214 1215 case GETID3_ASF_Simple_Index_Object: 1216 // Simple Index Object: (optional, recommended, one per video stream) 1217 // Field Name Field Type Size (bits) 1218 // Object ID GUID 128 // GUID for Simple Index object - GETID3_ASF_Data_Object 1219 // Object Size QWORD 64 // size of Simple Index object, including 56 bytes of Simple Index Object header 1220 // File ID GUID 128 // unique identifier. may be zero or identical to File ID field in Data Object and Header Object 1221 // Index Entry Time Interval QWORD 64 // interval between index entries in 100-nanosecond units 1222 // Maximum Packet Count DWORD 32 // maximum packet count for all index entries 1223 // Index Entries Count DWORD 32 // number of Index Entries structures 1224 // Index Entries array of: variable // 1225 // * Packet Number DWORD 32 // number of the Data Packet associated with this index entry 1226 // * Packet Count WORD 16 // number of Data Packets to sent at this index entry 1227 1228 // shortcut 1229 $thisfile_asf['simple_index_object'] = array(); 1230 $thisfile_asf_simpleindexobject = &$thisfile_asf['simple_index_object']; 1231 1232 $SimpleIndexObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 56 - 24); 1233 $offset = 24; 1234 1235 $thisfile_asf_simpleindexobject['objectid'] = $NextObjectGUID; 1236 $thisfile_asf_simpleindexobject['objectid_guid'] = $NextObjectGUIDtext; 1237 $thisfile_asf_simpleindexobject['objectsize'] = $NextObjectSize; 1238 1239 $thisfile_asf_simpleindexobject['fileid'] = substr($SimpleIndexObjectData, $offset, 16); 1240 $offset += 16; 1241 $thisfile_asf_simpleindexobject['fileid_guid'] = $this->BytestringToGUID($thisfile_asf_simpleindexobject['fileid']); 1242 $thisfile_asf_simpleindexobject['index_entry_time_interval'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 8)); 1243 $offset += 8; 1244 $thisfile_asf_simpleindexobject['maximum_packet_count'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4)); 1245 $offset += 4; 1246 $thisfile_asf_simpleindexobject['index_entries_count'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4)); 1247 $offset += 4; 1248 1249 $IndexEntriesData = $SimpleIndexObjectData.fread($this->getid3->fp, 6 * $thisfile_asf_simpleindexobject['index_entries_count']); 1250 for ($IndexEntriesCounter = 0; $IndexEntriesCounter < $thisfile_asf_simpleindexobject['index_entries_count']; $IndexEntriesCounter++) { 1251 $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_number'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4)); 1252 $offset += 4; 1253 $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_count'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4)); 1254 $offset += 2; 1255 } 1256 1257 break; 1258 1259 case GETID3_ASF_Index_Object: 1260 // 6.2 ASF top-level Index Object (optional but recommended when appropriate, 0 or 1) 1261 // Field Name Field Type Size (bits) 1262 // Object ID GUID 128 // GUID for the Index Object - GETID3_ASF_Index_Object 1263 // Object Size QWORD 64 // Specifies the size, in bytes, of the Index Object, including at least 34 bytes of Index Object header 1264 // Index Entry Time Interval DWORD 32 // Specifies the time interval between each index entry in ms. 1265 // Index Specifiers Count WORD 16 // Specifies the number of Index Specifiers structures in this Index Object. 1266 // Index Blocks Count DWORD 32 // Specifies the number of Index Blocks structures in this Index Object. 1267 1268 // Index Entry Time Interval DWORD 32 // Specifies the time interval between index entries in milliseconds. This value cannot be 0. 1269 // Index Specifiers Count WORD 16 // Specifies the number of entries in the Index Specifiers list. Valid values are 1 and greater. 1270 // Index Specifiers array of: varies // 1271 // * Stream Number WORD 16 // Specifies the stream number that the Index Specifiers refer to. Valid values are between 1 and 127. 1272 // * Index Type WORD 16 // Specifies Index Type values as follows: 1273 // 1 = Nearest Past Data Packet - indexes point to the data packet whose presentation time is closest to the index entry time. 1274 // 2 = Nearest Past Media Object - indexes point to the closest data packet containing an entire object or first fragment of an object. 1275 // 3 = Nearest Past Cleanpoint. - indexes point to the closest data packet containing an entire object (or first fragment of an object) that has the Cleanpoint Flag set. 1276 // Nearest Past Cleanpoint is the most common type of index. 1277 // Index Entry Count DWORD 32 // Specifies the number of Index Entries in the block. 1278 // * Block Positions QWORD varies // Specifies a list of byte offsets of the beginnings of the blocks relative to the beginning of the first Data Packet (i.e., the beginning of the Data Object + 50 bytes). The number of entries in this list is specified by the value of the Index Specifiers Count field. The order of those byte offsets is tied to the order in which Index Specifiers are listed. 1279 // * Index Entries array of: varies // 1280 // * * Offsets DWORD varies // An offset value of 0xffffffff indicates an invalid offset value 1281 1282 // shortcut 1283 $thisfile_asf['asf_index_object'] = array(); 1284 $thisfile_asf_asfindexobject = &$thisfile_asf['asf_index_object']; 1285 1286 $ASFIndexObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 34 - 24); 1287 $offset = 24; 1288 1289 $thisfile_asf_asfindexobject['objectid'] = $NextObjectGUID; 1290 $thisfile_asf_asfindexobject['objectid_guid'] = $NextObjectGUIDtext; 1291 $thisfile_asf_asfindexobject['objectsize'] = $NextObjectSize; 1292 1293 $thisfile_asf_asfindexobject['entry_time_interval'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); 1294 $offset += 4; 1295 $thisfile_asf_asfindexobject['index_specifiers_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); 1296 $offset += 2; 1297 $thisfile_asf_asfindexobject['index_blocks_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); 1298 $offset += 4; 1299 1300 $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count']); 1301 for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { 1302 $IndexSpecifierStreamNumber = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); 1303 $offset += 2; 1304 $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['stream_number'] = $IndexSpecifierStreamNumber; 1305 $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); 1306 $offset += 2; 1307 $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type_text'] = $this->ASFIndexObjectIndexTypeLookup($thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type']); 1308 } 1309 1310 $ASFIndexObjectData .= fread($this->getid3->fp, 4); 1311 $thisfile_asf_asfindexobject['index_entry_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); 1312 $offset += 4; 1313 1314 $ASFIndexObjectData .= fread($this->getid3->fp, 8 * $thisfile_asf_asfindexobject['index_specifiers_count']); 1315 for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { 1316 $thisfile_asf_asfindexobject['block_positions'][$IndexSpecifiersCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 8)); 1317 $offset += 8; 1318 } 1319 1320 $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']); 1321 for ($IndexEntryCounter = 0; $IndexEntryCounter < $thisfile_asf_asfindexobject['index_entry_count']; $IndexEntryCounter++) { 1322 for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { 1323 $thisfile_asf_asfindexobject['offsets'][$IndexSpecifiersCounter][$IndexEntryCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); 1324 $offset += 4; 1325 } 1326 } 1327 break; 1328 1329 1330 default: 1331 // Implementations shall ignore any standard or non-standard object that they do not know how to handle. 1332 if ($this->GUIDname($NextObjectGUIDtext)) { 1333 $info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8); 1334 } else { 1335 $info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.(ftell($this->getid3->fp) - 16 - 8); 1336 } 1337 fseek($this->getid3->fp, ($NextObjectSize - 16 - 8), SEEK_CUR); 1338 break; 1339 } 1340 } 1341 1342 if (isset($thisfile_asf_codeclistobject['codec_entries']) && is_array($thisfile_asf_codeclistobject['codec_entries'])) { 1343 foreach ($thisfile_asf_codeclistobject['codec_entries'] as $streamnumber => $streamdata) { 1344 switch ($streamdata['information']) { 1345 case 'WMV1': 1346 case 'WMV2': 1347 case 'WMV3': 1348 case 'MSS1': 1349 case 'MSS2': 1350 case 'WMVA': 1351 case 'WVC1': 1352 case 'WMVP': 1353 case 'WVP2': 1354 $thisfile_video['dataformat'] = 'wmv'; 1355 $info['mime_type'] = 'video/x-ms-wmv'; 1356 break; 1357 1358 case 'MP42': 1359 case 'MP43': 1360 case 'MP4S': 1361 case 'mp4s': 1362 $thisfile_video['dataformat'] = 'asf'; 1363 $info['mime_type'] = 'video/x-ms-asf'; 1364 break; 1365 1366 default: 1367 switch ($streamdata['type_raw']) { 1368 case 1: 1369 if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) { 1370 $thisfile_video['dataformat'] = 'wmv'; 1371 if ($info['mime_type'] == 'video/x-ms-asf') { 1372 $info['mime_type'] = 'video/x-ms-wmv'; 1373 } 1374 } 1375 break; 1376 1377 case 2: 1378 if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) { 1379 $thisfile_audio['dataformat'] = 'wma'; 1380 if ($info['mime_type'] == 'video/x-ms-asf') { 1381 $info['mime_type'] = 'audio/x-ms-wma'; 1382 } 1383 } 1384 break; 1385 1386 } 1387 break; 1388 } 1389 } 1390 } 1391 1392 switch (isset($thisfile_audio['codec']) ? $thisfile_audio['codec'] : '') { 1393 case 'MPEG Layer-3': 1394 $thisfile_audio['dataformat'] = 'mp3'; 1395 break; 1396 1397 default: 1398 break; 1399 } 1400 1401 if (isset($thisfile_asf_codeclistobject['codec_entries'])) { 1402 foreach ($thisfile_asf_codeclistobject['codec_entries'] as $streamnumber => $streamdata) { 1403 switch ($streamdata['type_raw']) { 1404 1405 case 1: // video 1406 $thisfile_video['encoder'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][$streamnumber]['name']); 1407 break; 1408 1409 case 2: // audio 1410 $thisfile_audio['encoder'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][$streamnumber]['name']); 1411 1412 // AH 2003-10-01 1413 $thisfile_audio['encoder_options'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][0]['description']); 1414 1415 $thisfile_audio['codec'] = $thisfile_audio['encoder']; 1416 break; 1417 1418 default: 1419 $info['warning'][] = 'Unknown streamtype: [codec_list_object][codec_entries]['.$streamnumber.'][type_raw] == '.$streamdata['type_raw']; 1420 break; 1421 1422 } 1423 } 1424 } 1425 1426 if (isset($info['audio'])) { 1427 $thisfile_audio['lossless'] = (isset($thisfile_audio['lossless']) ? $thisfile_audio['lossless'] : false); 1428 $thisfile_audio['dataformat'] = (!empty($thisfile_audio['dataformat']) ? $thisfile_audio['dataformat'] : 'asf'); 1429 } 1430 if (!empty($thisfile_video['dataformat'])) { 1431 $thisfile_video['lossless'] = (isset($thisfile_audio['lossless']) ? $thisfile_audio['lossless'] : false); 1432 $thisfile_video['pixel_aspect_ratio'] = (isset($thisfile_audio['pixel_aspect_ratio']) ? $thisfile_audio['pixel_aspect_ratio'] : (float) 1); 1433 $thisfile_video['dataformat'] = (!empty($thisfile_video['dataformat']) ? $thisfile_video['dataformat'] : 'asf'); 1434 } 1435 if (!empty($thisfile_video['streams'])) { 1436 $thisfile_video['streams']['resolution_x'] = 0; 1437 $thisfile_video['streams']['resolution_y'] = 0; 1438 foreach ($thisfile_video['streams'] as $key => $valuearray) { 1439 if (($valuearray['resolution_x'] > $thisfile_video['streams']['resolution_x']) || ($valuearray['resolution_y'] > $thisfile_video['streams']['resolution_y'])) { 1440 $thisfile_video['resolution_x'] = $valuearray['resolution_x']; 1441 $thisfile_video['resolution_y'] = $valuearray['resolution_y']; 1442 } 1443 } 1444 } 1445 $info['bitrate'] = (isset($thisfile_audio['bitrate']) ? $thisfile_audio['bitrate'] : 0) + (isset($thisfile_video['bitrate']) ? $thisfile_video['bitrate'] : 0); 1446 1447 if ((!isset($info['playtime_seconds']) || ($info['playtime_seconds'] <= 0)) && ($info['bitrate'] > 0)) { 1448 $info['playtime_seconds'] = ($info['filesize'] - $info['avdataoffset']) / ($info['bitrate'] / 8); 1449 } 1450 1451 return true; 1452 } 1453 1454 public static function ASFCodecListObjectTypeLookup($CodecListType) { 1455 static $ASFCodecListObjectTypeLookup = array(); 1456 if (empty($ASFCodecListObjectTypeLookup)) { 1457 $ASFCodecListObjectTypeLookup[0x0001] = 'Video Codec'; 1458 $ASFCodecListObjectTypeLookup[0x0002] = 'Audio Codec'; 1459 $ASFCodecListObjectTypeLookup[0xFFFF] = 'Unknown Codec'; 1460 } 1461 1462 return (isset($ASFCodecListObjectTypeLookup[$CodecListType]) ? $ASFCodecListObjectTypeLookup[$CodecListType] : 'Invalid Codec Type'); 1463 } 1464 1465 public static function KnownGUIDs() { 1466 static $GUIDarray = array( 1467 'GETID3_ASF_Extended_Stream_Properties_Object' => '14E6A5CB-C672-4332-8399-A96952065B5A', 1468 'GETID3_ASF_Padding_Object' => '1806D474-CADF-4509-A4BA-9AABCB96AAE8', 1469 'GETID3_ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio' => '1B1EE554-F9EA-4BC8-821A-376B74E4C4B8', 1470 'GETID3_ASF_Script_Command_Object' => '1EFB1A30-0B62-11D0-A39B-00A0C90348F6', 1471 'GETID3_ASF_No_Error_Correction' => '20FB5700-5B55-11CF-A8FD-00805F5C442B', 1472 'GETID3_ASF_Content_Branding_Object' => '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E', 1473 'GETID3_ASF_Content_Encryption_Object' => '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E', 1474 'GETID3_ASF_Digital_Signature_Object' => '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E', 1475 'GETID3_ASF_Extended_Content_Encryption_Object' => '298AE614-2622-4C17-B935-DAE07EE9289C', 1476 'GETID3_ASF_Simple_Index_Object' => '33000890-E5B1-11CF-89F4-00A0C90349CB', 1477 'GETID3_ASF_Degradable_JPEG_Media' => '35907DE0-E415-11CF-A917-00805F5C442B', 1478 'GETID3_ASF_Payload_Extension_System_Timecode' => '399595EC-8667-4E2D-8FDB-98814CE76C1E', 1479 'GETID3_ASF_Binary_Media' => '3AFB65E2-47EF-40F2-AC2C-70A90D71D343', 1480 'GETID3_ASF_Timecode_Index_Object' => '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C', 1481 'GETID3_ASF_Metadata_Library_Object' => '44231C94-9498-49D1-A141-1D134E457054', 1482 'GETID3_ASF_Reserved_3' => '4B1ACBE3-100B-11D0-A39B-00A0C90348F6', 1483 'GETID3_ASF_Reserved_4' => '4CFEDB20-75F6-11CF-9C0F-00A0C90349CB', 1484 'GETID3_ASF_Command_Media' => '59DACFC0-59E6-11D0-A3AC-00A0C90348F6', 1485 'GETID3_ASF_Header_Extension_Object' => '5FBF03B5-A92E-11CF-8EE3-00C00C205365', 1486 'GETID3_ASF_Media_Object_Index_Parameters_Obj' => '6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7', 1487 'GETID3_ASF_Header_Object' => '75B22630-668E-11CF-A6D9-00AA0062CE6C', 1488 'GETID3_ASF_Content_Description_Object' => '75B22633-668E-11CF-A6D9-00AA0062CE6C', 1489 'GETID3_ASF_Error_Correction_Object' => '75B22635-668E-11CF-A6D9-00AA0062CE6C', 1490 'GETID3_ASF_Data_Object' => '75B22636-668E-11CF-A6D9-00AA0062CE6C', 1491 'GETID3_ASF_Web_Stream_Media_Subtype' => '776257D4-C627-41CB-8F81-7AC7FF1C40CC', 1492 'GETID3_ASF_Stream_Bitrate_Properties_Object' => '7BF875CE-468D-11D1-8D82-006097C9A2B2', 1493 'GETID3_ASF_Language_List_Object' => '7C4346A9-EFE0-4BFC-B229-393EDE415C85', 1494 'GETID3_ASF_Codec_List_Object' => '86D15240-311D-11D0-A3A4-00A0C90348F6', 1495 'GETID3_ASF_Reserved_2' => '86D15241-311D-11D0-A3A4-00A0C90348F6', 1496 'GETID3_ASF_File_Properties_Object' => '8CABDCA1-A947-11CF-8EE4-00C00C205365', 1497 'GETID3_ASF_File_Transfer_Media' => '91BD222C-F21C-497A-8B6D-5AA86BFC0185', 1498 'GETID3_ASF_Old_RTP_Extension_Data' => '96800C63-4C94-11D1-837B-0080C7A37F95', 1499 'GETID3_ASF_Advanced_Mutual_Exclusion_Object' => 'A08649CF-4775-4670-8A16-6E35357566CD', 1500 'GETID3_ASF_Bandwidth_Sharing_Object' => 'A69609E6-517B-11D2-B6AF-00C04FD908E9', 1501 'GETID3_ASF_Reserved_1' => 'ABD3D211-A9BA-11cf-8EE6-00C00C205365', 1502 'GETID3_ASF_Bandwidth_Sharing_Exclusive' => 'AF6060AA-5197-11D2-B6AF-00C04FD908E9', 1503 'GETID3_ASF_Bandwidth_Sharing_Partial' => 'AF6060AB-5197-11D2-B6AF-00C04FD908E9', 1504 'GETID3_ASF_JFIF_Media' => 'B61BE100-5B4E-11CF-A8FD-00805F5C442B', 1505 'GETID3_ASF_Stream_Properties_Object' => 'B7DC0791-A9B7-11CF-8EE6-00C00C205365', 1506 'GETID3_ASF_Video_Media' => 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B', 1507 'GETID3_ASF_Audio_Spread' => 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220', 1508 'GETID3_ASF_Metadata_Object' => 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA', 1509 'GETID3_ASF_Payload_Ext_Syst_Sample_Duration' => 'C6BD9450-867F-4907-83A3-C77921B733AD', 1510 'GETID3_ASF_Group_Mutual_Exclusion_Object' => 'D1465A40-5A79-4338-B71B-E36B8FD6C249', 1511 'GETID3_ASF_Extended_Content_Description_Object' => 'D2D0A440-E307-11D2-97F0-00A0C95EA850', 1512 'GETID3_ASF_Stream_Prioritization_Object' => 'D4FED15B-88D3-454F-81F0-ED5C45999E24', 1513 'GETID3_ASF_Payload_Ext_System_Content_Type' => 'D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC', 1514 'GETID3_ASF_Old_File_Properties_Object' => 'D6E229D0-35DA-11D1-9034-00A0C90349BE', 1515 'GETID3_ASF_Old_ASF_Header_Object' => 'D6E229D1-35DA-11D1-9034-00A0C90349BE', 1516 'GETID3_ASF_Old_ASF_Data_Object' => 'D6E229D2-35DA-11D1-9034-00A0C90349BE', 1517 'GETID3_ASF_Index_Object' => 'D6E229D3-35DA-11D1-9034-00A0C90349BE', 1518 'GETID3_ASF_Old_Stream_Properties_Object' => 'D6E229D4-35DA-11D1-9034-00A0C90349BE', 1519 'GETID3_ASF_Old_Content_Description_Object' => 'D6E229D5-35DA-11D1-9034-00A0C90349BE', 1520 'GETID3_ASF_Old_Script_Command_Object' => 'D6E229D6-35DA-11D1-9034-00A0C90349BE', 1521 'GETID3_ASF_Old_Marker_Object' => 'D6E229D7-35DA-11D1-9034-00A0C90349BE', 1522 'GETID3_ASF_Old_Component_Download_Object' => 'D6E229D8-35DA-11D1-9034-00A0C90349BE', 1523 'GETID3_ASF_Old_Stream_Group_Object' => 'D6E229D9-35DA-11D1-9034-00A0C90349BE', 1524 'GETID3_ASF_Old_Scalable_Object' => 'D6E229DA-35DA-11D1-9034-00A0C90349BE', 1525 'GETID3_ASF_Old_Prioritization_Object' => 'D6E229DB-35DA-11D1-9034-00A0C90349BE', 1526 'GETID3_ASF_Bitrate_Mutual_Exclusion_Object' => 'D6E229DC-35DA-11D1-9034-00A0C90349BE', 1527 'GETID3_ASF_Old_Inter_Media_Dependency_Object' => 'D6E229DD-35DA-11D1-9034-00A0C90349BE', 1528 'GETID3_ASF_Old_Rating_Object' => 'D6E229DE-35DA-11D1-9034-00A0C90349BE', 1529 'GETID3_ASF_Index_Parameters_Object' => 'D6E229DF-35DA-11D1-9034-00A0C90349BE', 1530 'GETID3_ASF_Old_Color_Table_Object' => 'D6E229E0-35DA-11D1-9034-00A0C90349BE', 1531 'GETID3_ASF_Old_Language_List_Object' => 'D6E229E1-35DA-11D1-9034-00A0C90349BE', 1532 'GETID3_ASF_Old_Audio_Media' => 'D6E229E2-35DA-11D1-9034-00A0C90349BE', 1533 'GETID3_ASF_Old_Video_Media' => 'D6E229E3-35DA-11D1-9034-00A0C90349BE', 1534 'GETID3_ASF_Old_Image_Media' => 'D6E229E4-35DA-11D1-9034-00A0C90349BE', 1535 'GETID3_ASF_Old_Timecode_Media' => 'D6E229E5-35DA-11D1-9034-00A0C90349BE', 1536 'GETID3_ASF_Old_Text_Media' => 'D6E229E6-35DA-11D1-9034-00A0C90349BE', 1537 'GETID3_ASF_Old_MIDI_Media' => 'D6E229E7-35DA-11D1-9034-00A0C90349BE', 1538 'GETID3_ASF_Old_Command_Media' => 'D6E229E8-35DA-11D1-9034-00A0C90349BE', 1539 'GETID3_ASF_Old_No_Error_Concealment' => 'D6E229EA-35DA-11D1-9034-00A0C90349BE', 1540 'GETID3_ASF_Old_Scrambled_Audio' => 'D6E229EB-35DA-11D1-9034-00A0C90349BE', 1541 'GETID3_ASF_Old_No_Color_Table' => 'D6E229EC-35DA-11D1-9034-00A0C90349BE', 1542 'GETID3_ASF_Old_SMPTE_Time' => 'D6E229ED-35DA-11D1-9034-00A0C90349BE', 1543 'GETID3_ASF_Old_ASCII_Text' => 'D6E229EE-35DA-11D1-9034-00A0C90349BE', 1544 'GETID3_ASF_Old_Unicode_Text' => 'D6E229EF-35DA-11D1-9034-00A0C90349BE', 1545 'GETID3_ASF_Old_HTML_Text' => 'D6E229F0-35DA-11D1-9034-00A0C90349BE', 1546 'GETID3_ASF_Old_URL_Command' => 'D6E229F1-35DA-11D1-9034-00A0C90349BE', 1547 'GETID3_ASF_Old_Filename_Command' => 'D6E229F2-35DA-11D1-9034-00A0C90349BE', 1548 'GETID3_ASF_Old_ACM_Codec' => 'D6E229F3-35DA-11D1-9034-00A0C90349BE', 1549 'GETID3_ASF_Old_VCM_Codec' => 'D6E229F4-35DA-11D1-9034-00A0C90349BE', 1550 'GETID3_ASF_Old_QuickTime_Codec' => 'D6E229F5-35DA-11D1-9034-00A0C90349BE', 1551 'GETID3_ASF_Old_DirectShow_Transform_Filter' => 'D6E229F6-35DA-11D1-9034-00A0C90349BE', 1552 'GETID3_ASF_Old_DirectShow_Rendering_Filter' => 'D6E229F7-35DA-11D1-9034-00A0C90349BE', 1553 'GETID3_ASF_Old_No_Enhancement' => 'D6E229F8-35DA-11D1-9034-00A0C90349BE', 1554 'GETID3_ASF_Old_Unknown_Enhancement_Type' => 'D6E229F9-35DA-11D1-9034-00A0C90349BE', 1555 'GETID3_ASF_Old_Temporal_Enhancement' => 'D6E229FA-35DA-11D1-9034-00A0C90349BE', 1556 'GETID3_ASF_Old_Spatial_Enhancement' => 'D6E229FB-35DA-11D1-9034-00A0C90349BE', 1557 'GETID3_ASF_Old_Quality_Enhancement' => 'D6E229FC-35DA-11D1-9034-00A0C90349BE', 1558 'GETID3_ASF_Old_Number_of_Channels_Enhancement' => 'D6E229FD-35DA-11D1-9034-00A0C90349BE', 1559 'GETID3_ASF_Old_Frequency_Response_Enhancement' => 'D6E229FE-35DA-11D1-9034-00A0C90349BE', 1560 'GETID3_ASF_Old_Media_Object' => 'D6E229FF-35DA-11D1-9034-00A0C90349BE', 1561 'GETID3_ASF_Mutex_Language' => 'D6E22A00-35DA-11D1-9034-00A0C90349BE', 1562 'GETID3_ASF_Mutex_Bitrate' => 'D6E22A01-35DA-11D1-9034-00A0C90349BE', 1563 'GETID3_ASF_Mutex_Unknown' => 'D6E22A02-35DA-11D1-9034-00A0C90349BE', 1564 'GETID3_ASF_Old_ASF_Placeholder_Object' => 'D6E22A0E-35DA-11D1-9034-00A0C90349BE', 1565 'GETID3_ASF_Old_Data_Unit_Extension_Object' => 'D6E22A0F-35DA-11D1-9034-00A0C90349BE', 1566 'GETID3_ASF_Web_Stream_Format' => 'DA1E6B13-8359-4050-B398-388E965BF00C', 1567 'GETID3_ASF_Payload_Ext_System_File_Name' => 'E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B', 1568 'GETID3_ASF_Marker_Object' => 'F487CD01-A951-11CF-8EE6-00C00C205365', 1569 'GETID3_ASF_Timecode_Index_Parameters_Object' => 'F55E496D-9797-4B5D-8C8B-604DFE9BFB24', 1570 'GETID3_ASF_Audio_Media' => 'F8699E40-5B4D-11CF-A8FD-00805F5C442B', 1571 'GETID3_ASF_Media_Object_Index_Object' => 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C', 1572 'GETID3_ASF_Alt_Extended_Content_Encryption_Obj' => 'FF889EF1-ADEE-40DA-9E71-98704BB928CE', 1573 'GETID3_ASF_Index_Placeholder_Object' => 'D9AADE20-7C17-4F9C-BC28-8555DD98E2A2', // http://cpan.uwinnipeg.ca/htdocs/Audio-WMA/Audio/WMA.pm.html 1574 'GETID3_ASF_Compatibility_Object' => '26F18B5D-4584-47EC-9F5F-0E651F0452C9', // http://cpan.uwinnipeg.ca/htdocs/Audio-WMA/Audio/WMA.pm.html 1575 ); 1576 return $GUIDarray; 1577 } 1578 1579 public static function GUIDname($GUIDstring) { 1580 static $GUIDarray = array(); 1581 if (empty($GUIDarray)) { 1582 $GUIDarray = self::KnownGUIDs(); 1583 } 1584 return array_search($GUIDstring, $GUIDarray); 1585 } 1586 1587 public static function ASFIndexObjectIndexTypeLookup($id) { 1588 static $ASFIndexObjectIndexTypeLookup = array(); 1589 if (empty($ASFIndexObjectIndexTypeLookup)) { 1590 $ASFIndexObjectIndexTypeLookup[1] = 'Nearest Past Data Packet'; 1591 $ASFIndexObjectIndexTypeLookup[2] = 'Nearest Past Media Object'; 1592 $ASFIndexObjectIndexTypeLookup[3] = 'Nearest Past Cleanpoint'; 1593 } 1594 return (isset($ASFIndexObjectIndexTypeLookup[$id]) ? $ASFIndexObjectIndexTypeLookup[$id] : 'invalid'); 1595 } 1596 1597 public static function GUIDtoBytestring($GUIDstring) { 1598 // Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way: 1599 // first 4 bytes are in little-endian order 1600 // next 2 bytes are appended in little-endian order 1601 // next 2 bytes are appended in little-endian order 1602 // next 2 bytes are appended in big-endian order 1603 // next 6 bytes are appended in big-endian order 1604 1605 // AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp is stored as this 16-byte string: 1606 // $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp 1607 1608 $hexbytecharstring = chr(hexdec(substr($GUIDstring, 6, 2))); 1609 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 4, 2))); 1610 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 2, 2))); 1611 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 0, 2))); 1612 1613 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 11, 2))); 1614 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 9, 2))); 1615 1616 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 16, 2))); 1617 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 14, 2))); 1618 1619 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 19, 2))); 1620 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 21, 2))); 1621 1622 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 24, 2))); 1623 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 26, 2))); 1624 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 28, 2))); 1625 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 30, 2))); 1626 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 32, 2))); 1627 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 34, 2))); 1628 1629 return $hexbytecharstring; 1630 } 1631 1632 public static function BytestringToGUID($Bytestring) { 1633 $GUIDstring = str_pad(dechex(ord($Bytestring{3})), 2, '0', STR_PAD_LEFT); 1634 $GUIDstring .= str_pad(dechex(ord($Bytestring{2})), 2, '0', STR_PAD_LEFT); 1635 $GUIDstring .= str_pad(dechex(ord($Bytestring{1})), 2, '0', STR_PAD_LEFT); 1636 $GUIDstring .= str_pad(dechex(ord($Bytestring{0})), 2, '0', STR_PAD_LEFT); 1637 $GUIDstring .= '-'; 1638 $GUIDstring .= str_pad(dechex(ord($Bytestring{5})), 2, '0', STR_PAD_LEFT); 1639 $GUIDstring .= str_pad(dechex(ord($Bytestring{4})), 2, '0', STR_PAD_LEFT); 1640 $GUIDstring .= '-'; 1641 $GUIDstring .= str_pad(dechex(ord($Bytestring{7})), 2, '0', STR_PAD_LEFT); 1642 $GUIDstring .= str_pad(dechex(ord($Bytestring{6})), 2, '0', STR_PAD_LEFT); 1643 $GUIDstring .= '-'; 1644 $GUIDstring .= str_pad(dechex(ord($Bytestring{8})), 2, '0', STR_PAD_LEFT); 1645 $GUIDstring .= str_pad(dechex(ord($Bytestring{9})), 2, '0', STR_PAD_LEFT); 1646 $GUIDstring .= '-'; 1647 $GUIDstring .= str_pad(dechex(ord($Bytestring{10})), 2, '0', STR_PAD_LEFT); 1648 $GUIDstring .= str_pad(dechex(ord($Bytestring{11})), 2, '0', STR_PAD_LEFT); 1649 $GUIDstring .= str_pad(dechex(ord($Bytestring{12})), 2, '0', STR_PAD_LEFT); 1650 $GUIDstring .= str_pad(dechex(ord($Bytestring{13})), 2, '0', STR_PAD_LEFT); 1651 $GUIDstring .= str_pad(dechex(ord($Bytestring{14})), 2, '0', STR_PAD_LEFT); 1652 $GUIDstring .= str_pad(dechex(ord($Bytestring{15})), 2, '0', STR_PAD_LEFT); 1653 1654 return strtoupper($GUIDstring); 1655 } 1656 1657 public static function FILETIMEtoUNIXtime($FILETIME, $round=true) { 1658 // FILETIME is a 64-bit unsigned integer representing 1659 // the number of 100-nanosecond intervals since January 1, 1601 1660 // UNIX timestamp is number of seconds since January 1, 1970 1661 // 116444736000000000 = 10000000 * 60 * 60 * 24 * 365 * 369 + 89 leap days 1662 if ($round) { 1663 return intval(round(($FILETIME - 116444736000000000) / 10000000)); 1664 } 1665 return ($FILETIME - 116444736000000000) / 10000000; 1666 } 1667 1668 public static function WMpictureTypeLookup($WMpictureType) { 1669 static $WMpictureTypeLookup = array(); 1670 if (empty($WMpictureTypeLookup)) { 1671 $WMpictureTypeLookup[0x03] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Front Cover'); 1672 $WMpictureTypeLookup[0x04] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Back Cover'); 1673 $WMpictureTypeLookup[0x00] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'User Defined'); 1674 $WMpictureTypeLookup[0x05] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Leaflet Page'); 1675 $WMpictureTypeLookup[0x06] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Media Label'); 1676 $WMpictureTypeLookup[0x07] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Lead Artist'); 1677 $WMpictureTypeLookup[0x08] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Artist'); 1678 $WMpictureTypeLookup[0x09] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Conductor'); 1679 $WMpictureTypeLookup[0x0A] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Band'); 1680 $WMpictureTypeLookup[0x0B] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Composer'); 1681 $WMpictureTypeLookup[0x0C] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Lyricist'); 1682 $WMpictureTypeLookup[0x0D] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Recording Location'); 1683 $WMpictureTypeLookup[0x0E] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'During Recording'); 1684 $WMpictureTypeLookup[0x0F] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'During Performance'); 1685 $WMpictureTypeLookup[0x10] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Video Screen Capture'); 1686 $WMpictureTypeLookup[0x12] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Illustration'); 1687 $WMpictureTypeLookup[0x13] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Band Logotype'); 1688 $WMpictureTypeLookup[0x14] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Publisher Logotype'); 1689 } 1690 return (isset($WMpictureTypeLookup[$WMpictureType]) ? $WMpictureTypeLookup[$WMpictureType] : ''); 1691 } 1692 1693 public function ASF_HeaderExtensionObjectDataParse(&$asf_header_extension_object_data, &$unhandled_sections) { 1694 // http://msdn.microsoft.com/en-us/library/bb643323.aspx 1695 1696 $offset = 0; 1697 $objectOffset = 0; 1698 $HeaderExtensionObjectParsed = array(); 1699 while ($objectOffset < strlen($asf_header_extension_object_data)) { 1700 $offset = $objectOffset; 1701 $thisObject = array(); 1702 1703 $thisObject['guid'] = substr($asf_header_extension_object_data, $offset, 16); 1704 $offset += 16; 1705 $thisObject['guid_text'] = $this->BytestringToGUID($thisObject['guid']); 1706 $thisObject['guid_name'] = $this->GUIDname($thisObject['guid_text']); 1707 1708 $thisObject['size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 8)); 1709 $offset += 8; 1710 if ($thisObject['size'] <= 0) { 1711 break; 1712 } 1713 1714 switch ($thisObject['guid']) { 1715 case GETID3_ASF_Extended_Stream_Properties_Object: 1716 $thisObject['start_time'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 8)); 1717 $offset += 8; 1718 $thisObject['start_time_unix'] = $this->FILETIMEtoUNIXtime($thisObject['start_time']); 1719 1720 $thisObject['end_time'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 8)); 1721 $offset += 8; 1722 $thisObject['end_time_unix'] = $this->FILETIMEtoUNIXtime($thisObject['end_time']); 1723 1724 $thisObject['data_bitrate'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1725 $offset += 4; 1726 1727 $thisObject['buffer_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1728 $offset += 4; 1729 1730 $thisObject['initial_buffer_fullness'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1731 $offset += 4; 1732 1733 $thisObject['alternate_data_bitrate'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1734 $offset += 4; 1735 1736 $thisObject['alternate_buffer_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1737 $offset += 4; 1738 1739 $thisObject['alternate_initial_buffer_fullness'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1740 $offset += 4; 1741 1742 $thisObject['maximum_object_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1743 $offset += 4; 1744 1745 $thisObject['flags_raw'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1746 $offset += 4; 1747 $thisObject['flags']['reliable'] = (bool) $thisObject['flags_raw'] & 0x00000001; 1748 $thisObject['flags']['seekable'] = (bool) $thisObject['flags_raw'] & 0x00000002; 1749 $thisObject['flags']['no_cleanpoints'] = (bool) $thisObject['flags_raw'] & 0x00000004; 1750 $thisObject['flags']['resend_live_cleanpoints'] = (bool) $thisObject['flags_raw'] & 0x00000008; 1751 1752 $thisObject['stream_number'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1753 $offset += 2; 1754 1755 $thisObject['stream_language_id_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1756 $offset += 2; 1757 1758 $thisObject['average_time_per_frame'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1759 $offset += 4; 1760 1761 $thisObject['stream_name_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1762 $offset += 2; 1763 1764 $thisObject['payload_extension_system_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1765 $offset += 2; 1766 1767 for ($i = 0; $i < $thisObject['stream_name_count']; $i++) { 1768 $streamName = array(); 1769 1770 $streamName['language_id_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1771 $offset += 2; 1772 1773 $streamName['stream_name_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1774 $offset += 2; 1775 1776 $streamName['stream_name'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, $streamName['stream_name_length'])); 1777 $offset += $streamName['stream_name_length']; 1778 1779 $thisObject['stream_names'][$i] = $streamName; 1780 } 1781 1782 for ($i = 0; $i < $thisObject['payload_extension_system_count']; $i++) { 1783 $payloadExtensionSystem = array(); 1784 1785 $payloadExtensionSystem['extension_system_id'] = substr($asf_header_extension_object_data, $offset, 16); 1786 $offset += 16; 1787 $payloadExtensionSystem['extension_system_id_text'] = $this->BytestringToGUID($payloadExtensionSystem['extension_system_id']); 1788 1789 $payloadExtensionSystem['extension_system_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1790 $offset += 2; 1791 if ($payloadExtensionSystem['extension_system_size'] <= 0) { 1792 break 2; 1793 } 1794 1795 $payloadExtensionSystem['extension_system_info_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1796 $offset += 4; 1797 1798 $payloadExtensionSystem['extension_system_info_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, $payloadExtensionSystem['extension_system_info_length'])); 1799 $offset += $payloadExtensionSystem['extension_system_info_length']; 1800 1801 $thisObject['payload_extension_systems'][$i] = $payloadExtensionSystem; 1802 } 1803 1804 break; 1805 1806 case GETID3_ASF_Padding_Object: 1807 // padding, skip it 1808 break; 1809 1810 case GETID3_ASF_Metadata_Object: 1811 $thisObject['description_record_counts'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1812 $offset += 2; 1813 1814 for ($i = 0; $i < $thisObject['description_record_counts']; $i++) { 1815 $descriptionRecord = array(); 1816 1817 $descriptionRecord['reserved_1'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); // must be zero 1818 $offset += 2; 1819 1820 $descriptionRecord['stream_number'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1821 $offset += 2; 1822 1823 $descriptionRecord['name_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1824 $offset += 2; 1825 1826 $descriptionRecord['data_type'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1827 $offset += 2; 1828 $descriptionRecord['data_type_text'] = $this->ASFmetadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']); 1829 1830 $descriptionRecord['data_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1831 $offset += 4; 1832 1833 $descriptionRecord['name'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['name_length']); 1834 $offset += $descriptionRecord['name_length']; 1835 1836 $descriptionRecord['data'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['data_length']); 1837 $offset += $descriptionRecord['data_length']; 1838 switch ($descriptionRecord['data_type']) { 1839 case 0x0000: // Unicode string 1840 break; 1841 1842 case 0x0001: // BYTE array 1843 // do nothing 1844 break; 1845 1846 case 0x0002: // BOOL 1847 $descriptionRecord['data'] = (bool) getid3_lib::LittleEndian2Int($descriptionRecord['data']); 1848 break; 1849 1850 case 0x0003: // DWORD 1851 case 0x0004: // QWORD 1852 case 0x0005: // WORD 1853 $descriptionRecord['data'] = getid3_lib::LittleEndian2Int($descriptionRecord['data']); 1854 break; 1855 1856 case 0x0006: // GUID 1857 $descriptionRecord['data_text'] = $this->BytestringToGUID($descriptionRecord['data']); 1858 break; 1859 } 1860 1861 $thisObject['description_record'][$i] = $descriptionRecord; 1862 } 1863 break; 1864 1865 case GETID3_ASF_Language_List_Object: 1866 $thisObject['language_id_record_counts'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1867 $offset += 2; 1868 1869 for ($i = 0; $i < $thisObject['language_id_record_counts']; $i++) { 1870 $languageIDrecord = array(); 1871 1872 $languageIDrecord['language_id_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 1)); 1873 $offset += 1; 1874 1875 $languageIDrecord['language_id'] = substr($asf_header_extension_object_data, $offset, $languageIDrecord['language_id_length']); 1876 $offset += $languageIDrecord['language_id_length']; 1877 1878 $thisObject['language_id_record'][$i] = $languageIDrecord; 1879 } 1880 break; 1881 1882 case GETID3_ASF_Metadata_Library_Object: 1883 $thisObject['description_records_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1884 $offset += 2; 1885 1886 for ($i = 0; $i < $thisObject['description_records_count']; $i++) { 1887 $descriptionRecord = array(); 1888 1889 $descriptionRecord['language_list_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1890 $offset += 2; 1891 1892 $descriptionRecord['stream_number'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1893 $offset += 2; 1894 1895 $descriptionRecord['name_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1896 $offset += 2; 1897 1898 $descriptionRecord['data_type'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); 1899 $offset += 2; 1900 $descriptionRecord['data_type_text'] = $this->ASFmetadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']); 1901 1902 $descriptionRecord['data_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); 1903 $offset += 4; 1904 1905 $descriptionRecord['name'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['name_length']); 1906 $offset += $descriptionRecord['name_length']; 1907 1908 $descriptionRecord['data'] = substr($asf_header_extension_object_data, $offset, $descriptionRecord['data_length']); 1909 $offset += $descriptionRecord['data_length']; 1910 1911 if (preg_match('#^WM/Picture$#', str_replace("\x00", '', trim($descriptionRecord['name'])))) { 1912 $WMpicture = $this->ASF_WMpicture($descriptionRecord['data']); 1913 foreach ($WMpicture as $key => $value) { 1914 $descriptionRecord['data'] = $WMpicture; 1915 } 1916 unset($WMpicture); 1917 } 1918 1919 $thisObject['description_record'][$i] = $descriptionRecord; 1920 } 1921 break; 1922 1923 default: 1924 $unhandled_sections++; 1925 if ($this->GUIDname($thisObject['guid_text'])) { 1926 $this->getid3->info['warning'][] = 'unhandled Header Extension Object GUID "'.$this->GUIDname($thisObject['guid_text']).'" {'.$thisObject['guid_text'].'} at offset '.($offset - 16 - 8); 1927 } else { 1928 $this->getid3->info['warning'][] = 'unknown Header Extension Object GUID {'.$thisObject['guid_text'].'} in at offset '.($offset - 16 - 8); 1929 } 1930 break; 1931 } 1932 $HeaderExtensionObjectParsed[] = $thisObject; 1933 1934 $objectOffset += $thisObject['size']; 1935 } 1936 return $HeaderExtensionObjectParsed; 1937 } 1938 1939 1940 public static function ASFmetadataLibraryObjectDataTypeLookup($id) { 1941 static $ASFmetadataLibraryObjectDataTypeLookup = array( 1942 0x0000 => 'Unicode string', // The data consists of a sequence of Unicode characters 1943 0x0001 => 'BYTE array', // The type of the data is implementation-specific 1944 0x0002 => 'BOOL', // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer. Only 0x0000 or 0x0001 are permitted values 1945 0x0003 => 'DWORD', // The data is 4 bytes long and should be interpreted as a 32-bit unsigned integer 1946 0x0004 => 'QWORD', // The data is 8 bytes long and should be interpreted as a 64-bit unsigned integer 1947 0x0005 => 'WORD', // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer 1948 0x0006 => 'GUID', // The data is 16 bytes long and should be interpreted as a 128-bit GUID 1949 ); 1950 return (isset($ASFmetadataLibraryObjectDataTypeLookup[$id]) ? $ASFmetadataLibraryObjectDataTypeLookup[$id] : 'invalid'); 1951 } 1952 1953 public function ASF_WMpicture(&$data) { 1954 //typedef struct _WMPicture{ 1955 // LPWSTR pwszMIMEType; 1956 // BYTE bPictureType; 1957 // LPWSTR pwszDescription; 1958 // DWORD dwDataLen; 1959 // BYTE* pbData; 1960 //} WM_PICTURE; 1961 1962 $WMpicture = array(); 1963 1964 $offset = 0; 1965 $WMpicture['image_type_id'] = getid3_lib::LittleEndian2Int(substr($data, $offset, 1)); 1966 $offset += 1; 1967 $WMpicture['image_type'] = $this->WMpictureTypeLookup($WMpicture['image_type_id']); 1968 $WMpicture['image_size'] = getid3_lib::LittleEndian2Int(substr($data, $offset, 4)); 1969 $offset += 4; 1970 1971 $WMpicture['image_mime'] = ''; 1972 do { 1973 $next_byte_pair = substr($data, $offset, 2); 1974 $offset += 2; 1975 $WMpicture['image_mime'] .= $next_byte_pair; 1976 } while ($next_byte_pair !== "\x00\x00"); 1977 1978 $WMpicture['image_description'] = ''; 1979 do { 1980 $next_byte_pair = substr($data, $offset, 2); 1981 $offset += 2; 1982 $WMpicture['image_description'] .= $next_byte_pair; 1983 } while ($next_byte_pair !== "\x00\x00"); 1984 1985 $WMpicture['dataoffset'] = $offset; 1986 $WMpicture['data'] = substr($data, $offset); 1987 1988 $imageinfo = array(); 1989 $WMpicture['image_mime'] = ''; 1990 $imagechunkcheck = getid3_lib::GetDataImageSize($WMpicture['data'], $imageinfo); 1991 unset($imageinfo); 1992 if (!empty($imagechunkcheck)) { 1993 $WMpicture['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); 1994 } 1995 if (!isset($this->getid3->info['asf']['comments']['picture'])) { 1996 $this->getid3->info['asf']['comments']['picture'] = array(); 1997 } 1998 $this->getid3->info['asf']['comments']['picture'][] = array('data'=>$WMpicture['data'], 'image_mime'=>$WMpicture['image_mime']); 1999 2000 return $WMpicture; 2001 } 2002 2003 2004 // Remove terminator 00 00 and convert UTF-16LE to Latin-1 2005 public static function TrimConvert($string) { 2006 return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', self::TrimTerm($string)), ' '); 2007 } 2008 2009 2010 // Remove terminator 00 00 2011 public static function TrimTerm($string) { 2012 // remove terminator, only if present (it should be, but...) 2013 if (substr($string, -2) === "\x00\x00") { 2014 $string = substr($string, 0, -2); 2015 } 2016 return $string; 2017 } 2018 2019 } -
new file wp-includes/ID3/module.audio-video.flv.php
diff --git wp-includes/ID3/module.audio-video.flv.php wp-includes/ID3/module.audio-video.flv.php new file mode 100644 index 0000000..f9c4cf3
- + 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 // // 7 // FLV module by Seth Kaufman <sethØwhirl-i-gig*com> // 8 // // 9 // * version 0.1 (26 June 2005) // 10 // // 11 // // 12 // * version 0.1.1 (15 July 2005) // 13 // minor modifications by James Heinrich <info@getid3.org> // 14 // // 15 // * version 0.2 (22 February 2006) // 16 // Support for On2 VP6 codec and meta information // 17 // by Steve Webster <steve.websterØfeaturecreep*com> // 18 // // 19 // * version 0.3 (15 June 2006) // 20 // Modified to not read entire file into memory // 21 // by James Heinrich <info@getid3.org> // 22 // // 23 // * version 0.4 (07 December 2007) // 24 // Bugfixes for incorrectly parsed FLV dimensions // 25 // and incorrect parsing of onMetaTag // 26 // by Evgeny Moysevich <moysevichØgmail*com> // 27 // // 28 // * version 0.5 (21 May 2009) // 29 // Fixed parsing of audio tags and added additional codec // 30 // details. The duration is now read from onMetaTag (if // 31 // exists), rather than parsing whole file // 32 // by Nigel Barnes <ngbarnesØhotmail*com> // 33 // // 34 // * version 0.6 (24 May 2009) // 35 // Better parsing of files with h264 video // 36 // by Evgeny Moysevich <moysevichØgmail*com> // 37 // // 38 // * version 0.6.1 (30 May 2011) // 39 // prevent infinite loops in expGolombUe() // 40 // // 41 ///////////////////////////////////////////////////////////////// 42 // // 43 // module.audio-video.flv.php // 44 // module for analyzing Shockwave Flash Video files // 45 // dependencies: NONE // 46 // /// 47 ///////////////////////////////////////////////////////////////// 48 49 define('GETID3_FLV_TAG_AUDIO', 8); 50 define('GETID3_FLV_TAG_VIDEO', 9); 51 define('GETID3_FLV_TAG_META', 18); 52 53 define('GETID3_FLV_VIDEO_H263', 2); 54 define('GETID3_FLV_VIDEO_SCREEN', 3); 55 define('GETID3_FLV_VIDEO_VP6FLV', 4); 56 define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5); 57 define('GETID3_FLV_VIDEO_SCREENV2', 6); 58 define('GETID3_FLV_VIDEO_H264', 7); 59 60 define('H264_AVC_SEQUENCE_HEADER', 0); 61 define('H264_PROFILE_BASELINE', 66); 62 define('H264_PROFILE_MAIN', 77); 63 define('H264_PROFILE_EXTENDED', 88); 64 define('H264_PROFILE_HIGH', 100); 65 define('H264_PROFILE_HIGH10', 110); 66 define('H264_PROFILE_HIGH422', 122); 67 define('H264_PROFILE_HIGH444', 144); 68 define('H264_PROFILE_HIGH444_PREDICTIVE', 244); 69 70 class getid3_flv extends getid3_handler 71 { 72 public $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration 73 74 public function Analyze() { 75 $info = &$this->getid3->info; 76 77 fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); 78 79 $FLVdataLength = $info['avdataend'] - $info['avdataoffset']; 80 $FLVheader = fread($this->getid3->fp, 5); 81 82 $info['fileformat'] = 'flv'; 83 $info['flv']['header']['signature'] = substr($FLVheader, 0, 3); 84 $info['flv']['header']['version'] = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1)); 85 $TypeFlags = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1)); 86 87 $magic = 'FLV'; 88 if ($info['flv']['header']['signature'] != $magic) { 89 $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"'; 90 unset($info['flv']); 91 unset($info['fileformat']); 92 return false; 93 } 94 95 $info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04); 96 $info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01); 97 98 $FrameSizeDataLength = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 4)); 99 $FLVheaderFrameLength = 9; 100 if ($FrameSizeDataLength > $FLVheaderFrameLength) { 101 fseek($this->getid3->fp, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR); 102 } 103 $Duration = 0; 104 $found_video = false; 105 $found_audio = false; 106 $found_meta = false; 107 $found_valid_meta_playtime = false; 108 $tagParseCount = 0; 109 $info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0); 110 $flv_framecount = &$info['flv']['framecount']; 111 while (((ftell($this->getid3->fp) + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) { 112 $ThisTagHeader = fread($this->getid3->fp, 16); 113 114 $PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 0, 4)); 115 $TagType = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 4, 1)); 116 $DataLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 5, 3)); 117 $Timestamp = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 8, 3)); 118 $LastHeaderByte = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1)); 119 $NextOffset = ftell($this->getid3->fp) - 1 + $DataLength; 120 if ($Timestamp > $Duration) { 121 $Duration = $Timestamp; 122 } 123 124 $flv_framecount['total']++; 125 switch ($TagType) { 126 case GETID3_FLV_TAG_AUDIO: 127 $flv_framecount['audio']++; 128 if (!$found_audio) { 129 $found_audio = true; 130 $info['flv']['audio']['audioFormat'] = ($LastHeaderByte >> 4) & 0x0F; 131 $info['flv']['audio']['audioRate'] = ($LastHeaderByte >> 2) & 0x03; 132 $info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01; 133 $info['flv']['audio']['audioType'] = $LastHeaderByte & 0x01; 134 } 135 break; 136 137 case GETID3_FLV_TAG_VIDEO: 138 $flv_framecount['video']++; 139 if (!$found_video) { 140 $found_video = true; 141 $info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07; 142 143 $FLVvideoHeader = fread($this->getid3->fp, 11); 144 145 if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) { 146 // this code block contributed by: moysevichØgmail*com 147 148 $AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1)); 149 if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) { 150 // read AVCDecoderConfigurationRecord 151 $configurationVersion = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 1)); 152 $AVCProfileIndication = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 1)); 153 $profile_compatibility = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 1)); 154 $lengthSizeMinusOne = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 1)); 155 $numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 8, 1)); 156 157 if (($numOfSequenceParameterSets & 0x1F) != 0) { 158 // there is at least one SequenceParameterSet 159 // read size of the first SequenceParameterSet 160 //$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2)); 161 $spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2)); 162 // read the first SequenceParameterSet 163 $sps = fread($this->getid3->fp, $spsSize); 164 if (strlen($sps) == $spsSize) { // make sure that whole SequenceParameterSet was red 165 $spsReader = new AVCSequenceParameterSetReader($sps); 166 $spsReader->readData(); 167 $info['video']['resolution_x'] = $spsReader->getWidth(); 168 $info['video']['resolution_y'] = $spsReader->getHeight(); 169 } 170 } 171 } 172 // end: moysevichØgmail*com 173 174 } elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) { 175 176 $PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7; 177 $PictureSizeType = $PictureSizeType & 0x0007; 178 $info['flv']['header']['videoSizeType'] = $PictureSizeType; 179 switch ($PictureSizeType) { 180 case 0: 181 //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)); 182 //$PictureSizeEnc <<= 1; 183 //$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8; 184 //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2)); 185 //$PictureSizeEnc <<= 1; 186 //$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8; 187 188 $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2)); 189 $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)); 190 $PictureSizeEnc['x'] >>= 7; 191 $PictureSizeEnc['y'] >>= 7; 192 $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF; 193 $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF; 194 break; 195 196 case 1: 197 $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3)); 198 $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3)); 199 $PictureSizeEnc['x'] >>= 7; 200 $PictureSizeEnc['y'] >>= 7; 201 $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF; 202 $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF; 203 break; 204 205 case 2: 206 $info['video']['resolution_x'] = 352; 207 $info['video']['resolution_y'] = 288; 208 break; 209 210 case 3: 211 $info['video']['resolution_x'] = 176; 212 $info['video']['resolution_y'] = 144; 213 break; 214 215 case 4: 216 $info['video']['resolution_x'] = 128; 217 $info['video']['resolution_y'] = 96; 218 break; 219 220 case 5: 221 $info['video']['resolution_x'] = 320; 222 $info['video']['resolution_y'] = 240; 223 break; 224 225 case 6: 226 $info['video']['resolution_x'] = 160; 227 $info['video']['resolution_y'] = 120; 228 break; 229 230 default: 231 $info['video']['resolution_x'] = 0; 232 $info['video']['resolution_y'] = 0; 233 break; 234 235 } 236 } 237 $info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y']; 238 } 239 break; 240 241 // Meta tag 242 case GETID3_FLV_TAG_META: 243 if (!$found_meta) { 244 $found_meta = true; 245 fseek($this->getid3->fp, -1, SEEK_CUR); 246 $datachunk = fread($this->getid3->fp, $DataLength); 247 $AMFstream = new AMFStream($datachunk); 248 $reader = new AMFReader($AMFstream); 249 $eventName = $reader->readData(); 250 $info['flv']['meta'][$eventName] = $reader->readData(); 251 unset($reader); 252 253 $copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate'); 254 foreach ($copykeys as $sourcekey => $destkey) { 255 if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) { 256 switch ($sourcekey) { 257 case 'width': 258 case 'height': 259 $info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey])); 260 break; 261 case 'audiodatarate': 262 $info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000)); 263 break; 264 case 'videodatarate': 265 case 'frame_rate': 266 default: 267 $info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey]; 268 break; 269 } 270 } 271 } 272 if (!empty($info['flv']['meta']['onMetaData']['duration'])) { 273 $found_valid_meta_playtime = true; 274 } 275 } 276 break; 277 278 default: 279 // noop 280 break; 281 } 282 fseek($this->getid3->fp, $NextOffset, SEEK_SET); 283 } 284 285 $info['playtime_seconds'] = $Duration / 1000; 286 if ($info['playtime_seconds'] > 0) { 287 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; 288 } 289 290 if ($info['flv']['header']['hasAudio']) { 291 $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['audio']['audioFormat']); 292 $info['audio']['sample_rate'] = $this->FLVaudioRate($info['flv']['audio']['audioRate']); 293 $info['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($info['flv']['audio']['audioSampleSize']); 294 295 $info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo 296 $info['audio']['lossless'] = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed 297 $info['audio']['dataformat'] = 'flv'; 298 } 299 if (!empty($info['flv']['header']['hasVideo'])) { 300 $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['video']['videoCodec']); 301 $info['video']['dataformat'] = 'flv'; 302 $info['video']['lossless'] = false; 303 } 304 305 // Set information from meta 306 if (!empty($info['flv']['meta']['onMetaData']['duration'])) { 307 $info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration']; 308 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; 309 } 310 if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) { 311 $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['meta']['onMetaData']['audiocodecid']); 312 } 313 if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) { 314 $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['meta']['onMetaData']['videocodecid']); 315 } 316 return true; 317 } 318 319 320 public function FLVaudioFormat($id) { 321 $FLVaudioFormat = array( 322 0 => 'Linear PCM, platform endian', 323 1 => 'ADPCM', 324 2 => 'mp3', 325 3 => 'Linear PCM, little endian', 326 4 => 'Nellymoser 16kHz mono', 327 5 => 'Nellymoser 8kHz mono', 328 6 => 'Nellymoser', 329 7 => 'G.711A-law logarithmic PCM', 330 8 => 'G.711 mu-law logarithmic PCM', 331 9 => 'reserved', 332 10 => 'AAC', 333 11 => false, // unknown? 334 12 => false, // unknown? 335 13 => false, // unknown? 336 14 => 'mp3 8kHz', 337 15 => 'Device-specific sound', 338 ); 339 return (isset($FLVaudioFormat[$id]) ? $FLVaudioFormat[$id] : false); 340 } 341 342 public function FLVaudioRate($id) { 343 $FLVaudioRate = array( 344 0 => 5500, 345 1 => 11025, 346 2 => 22050, 347 3 => 44100, 348 ); 349 return (isset($FLVaudioRate[$id]) ? $FLVaudioRate[$id] : false); 350 } 351 352 public function FLVaudioBitDepth($id) { 353 $FLVaudioBitDepth = array( 354 0 => 8, 355 1 => 16, 356 ); 357 return (isset($FLVaudioBitDepth[$id]) ? $FLVaudioBitDepth[$id] : false); 358 } 359 360 public function FLVvideoCodec($id) { 361 $FLVvideoCodec = array( 362 GETID3_FLV_VIDEO_H263 => 'Sorenson H.263', 363 GETID3_FLV_VIDEO_SCREEN => 'Screen video', 364 GETID3_FLV_VIDEO_VP6FLV => 'On2 VP6', 365 GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel', 366 GETID3_FLV_VIDEO_SCREENV2 => 'Screen video v2', 367 GETID3_FLV_VIDEO_H264 => 'Sorenson H.264', 368 ); 369 return (isset($FLVvideoCodec[$id]) ? $FLVvideoCodec[$id] : false); 370 } 371 } 372 373 class AMFStream { 374 public $bytes; 375 public $pos; 376 377 public function AMFStream(&$bytes) { 378 $this->bytes =& $bytes; 379 $this->pos = 0; 380 } 381 382 public function readByte() { 383 return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1)); 384 } 385 386 public function readInt() { 387 return ($this->readByte() << 8) + $this->readByte(); 388 } 389 390 public function readLong() { 391 return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte(); 392 } 393 394 public function readDouble() { 395 return getid3_lib::BigEndian2Float($this->read(8)); 396 } 397 398 public function readUTF() { 399 $length = $this->readInt(); 400 return $this->read($length); 401 } 402 403 public function readLongUTF() { 404 $length = $this->readLong(); 405 return $this->read($length); 406 } 407 408 public function read($length) { 409 $val = substr($this->bytes, $this->pos, $length); 410 $this->pos += $length; 411 return $val; 412 } 413 414 public function peekByte() { 415 $pos = $this->pos; 416 $val = $this->readByte(); 417 $this->pos = $pos; 418 return $val; 419 } 420 421 public function peekInt() { 422 $pos = $this->pos; 423 $val = $this->readInt(); 424 $this->pos = $pos; 425 return $val; 426 } 427 428 public function peekLong() { 429 $pos = $this->pos; 430 $val = $this->readLong(); 431 $this->pos = $pos; 432 return $val; 433 } 434 435 public function peekDouble() { 436 $pos = $this->pos; 437 $val = $this->readDouble(); 438 $this->pos = $pos; 439 return $val; 440 } 441 442 public function peekUTF() { 443 $pos = $this->pos; 444 $val = $this->readUTF(); 445 $this->pos = $pos; 446 return $val; 447 } 448 449 public function peekLongUTF() { 450 $pos = $this->pos; 451 $val = $this->readLongUTF(); 452 $this->pos = $pos; 453 return $val; 454 } 455 } 456 457 class AMFReader { 458 public $stream; 459 460 public function AMFReader(&$stream) { 461 $this->stream =& $stream; 462 } 463 464 public function readData() { 465 $value = null; 466 467 $type = $this->stream->readByte(); 468 switch ($type) { 469 470 // Double 471 case 0: 472 $value = $this->readDouble(); 473 break; 474 475 // Boolean 476 case 1: 477 $value = $this->readBoolean(); 478 break; 479 480 // String 481 case 2: 482 $value = $this->readString(); 483 break; 484 485 // Object 486 case 3: 487 $value = $this->readObject(); 488 break; 489 490 // null 491 case 6: 492 return null; 493 break; 494 495 // Mixed array 496 case 8: 497 $value = $this->readMixedArray(); 498 break; 499 500 // Array 501 case 10: 502 $value = $this->readArray(); 503 break; 504 505 // Date 506 case 11: 507 $value = $this->readDate(); 508 break; 509 510 // Long string 511 case 13: 512 $value = $this->readLongString(); 513 break; 514 515 // XML (handled as string) 516 case 15: 517 $value = $this->readXML(); 518 break; 519 520 // Typed object (handled as object) 521 case 16: 522 $value = $this->readTypedObject(); 523 break; 524 525 // Long string 526 default: 527 $value = '(unknown or unsupported data type)'; 528 break; 529 } 530 531 return $value; 532 } 533 534 public function readDouble() { 535 return $this->stream->readDouble(); 536 } 537 538 public function readBoolean() { 539 return $this->stream->readByte() == 1; 540 } 541 542 public function readString() { 543 return $this->stream->readUTF(); 544 } 545 546 public function readObject() { 547 // Get highest numerical index - ignored 548 // $highestIndex = $this->stream->readLong(); 549 550 $data = array(); 551 552 while ($key = $this->stream->readUTF()) { 553 $data[$key] = $this->readData(); 554 } 555 // Mixed array record ends with empty string (0x00 0x00) and 0x09 556 if (($key == '') && ($this->stream->peekByte() == 0x09)) { 557 // Consume byte 558 $this->stream->readByte(); 559 } 560 return $data; 561 } 562 563 public function readMixedArray() { 564 // Get highest numerical index - ignored 565 $highestIndex = $this->stream->readLong(); 566 567 $data = array(); 568 569 while ($key = $this->stream->readUTF()) { 570 if (is_numeric($key)) { 571 $key = (float) $key; 572 } 573 $data[$key] = $this->readData(); 574 } 575 // Mixed array record ends with empty string (0x00 0x00) and 0x09 576 if (($key == '') && ($this->stream->peekByte() == 0x09)) { 577 // Consume byte 578 $this->stream->readByte(); 579 } 580 581 return $data; 582 } 583 584 public function readArray() { 585 $length = $this->stream->readLong(); 586 $data = array(); 587 588 for ($i = 0; $i < $length; $i++) { 589 $data[] = $this->readData(); 590 } 591 return $data; 592 } 593 594 public function readDate() { 595 $timestamp = $this->stream->readDouble(); 596 $timezone = $this->stream->readInt(); 597 return $timestamp; 598 } 599 600 public function readLongString() { 601 return $this->stream->readLongUTF(); 602 } 603 604 public function readXML() { 605 return $this->stream->readLongUTF(); 606 } 607 608 public function readTypedObject() { 609 $className = $this->stream->readUTF(); 610 return $this->readObject(); 611 } 612 } 613 614 class AVCSequenceParameterSetReader { 615 public $sps; 616 public $start = 0; 617 public $currentBytes = 0; 618 public $currentBits = 0; 619 public $width; 620 public $height; 621 622 public function AVCSequenceParameterSetReader($sps) { 623 $this->sps = $sps; 624 } 625 626 public function readData() { 627 $this->skipBits(8); 628 $this->skipBits(8); 629 $profile = $this->getBits(8); // read profile 630 $this->skipBits(16); 631 $this->expGolombUe(); // read sps id 632 if (in_array($profile, array(H264_PROFILE_HIGH, H264_PROFILE_HIGH10, H264_PROFILE_HIGH422, H264_PROFILE_HIGH444, H264_PROFILE_HIGH444_PREDICTIVE))) { 633 if ($this->expGolombUe() == 3) { 634 $this->skipBits(1); 635 } 636 $this->expGolombUe(); 637 $this->expGolombUe(); 638 $this->skipBits(1); 639 if ($this->getBit()) { 640 for ($i = 0; $i < 8; $i++) { 641 if ($this->getBit()) { 642 $size = $i < 6 ? 16 : 64; 643 $lastScale = 8; 644 $nextScale = 8; 645 for ($j = 0; $j < $size; $j++) { 646 if ($nextScale != 0) { 647 $deltaScale = $this->expGolombUe(); 648 $nextScale = ($lastScale + $deltaScale + 256) % 256; 649 } 650 if ($nextScale != 0) { 651 $lastScale = $nextScale; 652 } 653 } 654 } 655 } 656 } 657 } 658 $this->expGolombUe(); 659 $pocType = $this->expGolombUe(); 660 if ($pocType == 0) { 661 $this->expGolombUe(); 662 } elseif ($pocType == 1) { 663 $this->skipBits(1); 664 $this->expGolombSe(); 665 $this->expGolombSe(); 666 $pocCycleLength = $this->expGolombUe(); 667 for ($i = 0; $i < $pocCycleLength; $i++) { 668 $this->expGolombSe(); 669 } 670 } 671 $this->expGolombUe(); 672 $this->skipBits(1); 673 $this->width = ($this->expGolombUe() + 1) * 16; 674 $heightMap = $this->expGolombUe() + 1; 675 $this->height = (2 - $this->getBit()) * $heightMap * 16; 676 } 677 678 public function skipBits($bits) { 679 $newBits = $this->currentBits + $bits; 680 $this->currentBytes += (int)floor($newBits / 8); 681 $this->currentBits = $newBits % 8; 682 } 683 684 public function getBit() { 685 $result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01; 686 $this->skipBits(1); 687 return $result; 688 } 689 690 public function getBits($bits) { 691 $result = 0; 692 for ($i = 0; $i < $bits; $i++) { 693 $result = ($result << 1) + $this->getBit(); 694 } 695 return $result; 696 } 697 698 public function expGolombUe() { 699 $significantBits = 0; 700 $bit = $this->getBit(); 701 while ($bit == 0) { 702 $significantBits++; 703 $bit = $this->getBit(); 704 705 if ($significantBits > 31) { 706 // something is broken, this is an emergency escape to prevent infinite loops 707 return 0; 708 } 709 } 710 return (1 << $significantBits) + $this->getBits($significantBits) - 1; 711 } 712 713 public function expGolombSe() { 714 $result = $this->expGolombUe(); 715 if (($result & 0x01) == 0) { 716 return -($result >> 1); 717 } else { 718 return ($result + 1) >> 1; 719 } 720 } 721 722 public function getWidth() { 723 return $this->width; 724 } 725 726 public function getHeight() { 727 return $this->height; 728 } 729 } -
new file wp-includes/ID3/module.audio-video.matroska.php
diff --git wp-includes/ID3/module.audio-video.matroska.php wp-includes/ID3/module.audio-video.matroska.php new file mode 100644 index 0000000..3c1921f
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.audio-video.matriska.php // 11 // module for analyzing Matroska containers // 12 // dependencies: NONE // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 17 define('EBML_ID_CHAPTERS', 0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation. 18 define('EBML_ID_SEEKHEAD', 0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements. 19 define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found <http://www.matroska.org/technical/specs/tagging/index.html>. 20 define('EBML_ID_INFO', 0x0549A966); // [15][49][A9][66] -- Contains miscellaneous general information and statistics on the file. 21 define('EBML_ID_TRACKS', 0x0654AE6B); // [16][54][AE][6B] -- A top-level block of information with many tracks described. 22 define('EBML_ID_SEGMENT', 0x08538067); // [18][53][80][67] -- This element contains all other top-level (level 1) elements. Typically a Matroska file is composed of 1 segment. 23 define('EBML_ID_ATTACHMENTS', 0x0941A469); // [19][41][A4][69] -- Contain attached files. 24 define('EBML_ID_EBML', 0x0A45DFA3); // [1A][45][DF][A3] -- Set the EBML characteristics of the data to follow. Each EBML document has to start with this. 25 define('EBML_ID_CUES', 0x0C53BB6B); // [1C][53][BB][6B] -- A top-level element to speed seeking access. All entries are local to the segment. 26 define('EBML_ID_CLUSTER', 0x0F43B675); // [1F][43][B6][75] -- The lower level element containing the (monolithic) Block structure. 27 define('EBML_ID_LANGUAGE', 0x02B59C); // [22][B5][9C] -- Specifies the language of the track in the Matroska languages form. 28 define('EBML_ID_TRACKTIMECODESCALE', 0x03314F); // [23][31][4F] -- The scale to apply on this track to work at normal speed in relation with other tracks (mostly used to adjust video speed when the audio length differs). 29 define('EBML_ID_DEFAULTDURATION', 0x03E383); // [23][E3][83] -- Number of nanoseconds (i.e. not scaled) per frame. 30 define('EBML_ID_CODECNAME', 0x058688); // [25][86][88] -- A human-readable string specifying the codec. 31 define('EBML_ID_CODECDOWNLOADURL', 0x06B240); // [26][B2][40] -- A URL to download about the codec used. 32 define('EBML_ID_TIMECODESCALE', 0x0AD7B1); // [2A][D7][B1] -- Timecode scale in nanoseconds (1.000.000 means all timecodes in the segment are expressed in milliseconds). 33 define('EBML_ID_COLOURSPACE', 0x0EB524); // [2E][B5][24] -- Same value as in AVI (32 bits). 34 define('EBML_ID_GAMMAVALUE', 0x0FB523); // [2F][B5][23] -- Gamma Value. 35 define('EBML_ID_CODECSETTINGS', 0x1A9697); // [3A][96][97] -- A string describing the encoding setting used. 36 define('EBML_ID_CODECINFOURL', 0x1B4040); // [3B][40][40] -- A URL to find information about the codec used. 37 define('EBML_ID_PREVFILENAME', 0x1C83AB); // [3C][83][AB] -- An escaped filename corresponding to the previous segment. 38 define('EBML_ID_PREVUID', 0x1CB923); // [3C][B9][23] -- A unique ID to identify the previous chained segment (128 bits). 39 define('EBML_ID_NEXTFILENAME', 0x1E83BB); // [3E][83][BB] -- An escaped filename corresponding to the next segment. 40 define('EBML_ID_NEXTUID', 0x1EB923); // [3E][B9][23] -- A unique ID to identify the next chained segment (128 bits). 41 define('EBML_ID_CONTENTCOMPALGO', 0x0254); // [42][54] -- The compression algorithm used. Algorithms that have been specified so far are: 42 define('EBML_ID_CONTENTCOMPSETTINGS', 0x0255); // [42][55] -- Settings that might be needed by the decompressor. For Header Stripping (ContentCompAlgo=3), the bytes that were removed from the beggining of each frames of the track. 43 define('EBML_ID_DOCTYPE', 0x0282); // [42][82] -- A string that describes the type of document that follows this EBML header ('matroska' in our case). 44 define('EBML_ID_DOCTYPEREADVERSION', 0x0285); // [42][85] -- The minimum DocType version an interpreter has to support to read this file. 45 define('EBML_ID_EBMLVERSION', 0x0286); // [42][86] -- The version of EBML parser used to create the file. 46 define('EBML_ID_DOCTYPEVERSION', 0x0287); // [42][87] -- The version of DocType interpreter used to create the file. 47 define('EBML_ID_EBMLMAXIDLENGTH', 0x02F2); // [42][F2] -- The maximum length of the IDs you'll find in this file (4 or less in Matroska). 48 define('EBML_ID_EBMLMAXSIZELENGTH', 0x02F3); // [42][F3] -- The maximum length of the sizes you'll find in this file (8 or less in Matroska). This does not override the element size indicated at the beginning of an element. Elements that have an indicated size which is larger than what is allowed by EBMLMaxSizeLength shall be considered invalid. 49 define('EBML_ID_EBMLREADVERSION', 0x02F7); // [42][F7] -- The minimum EBML version a parser has to support to read this file. 50 define('EBML_ID_CHAPLANGUAGE', 0x037C); // [43][7C] -- The languages corresponding to the string, in the bibliographic ISO-639-2 form. 51 define('EBML_ID_CHAPCOUNTRY', 0x037E); // [43][7E] -- The countries corresponding to the string, same 2 octets as in Internet domains. 52 define('EBML_ID_SEGMENTFAMILY', 0x0444); // [44][44] -- A randomly generated unique ID that all segments related to each other must use (128 bits). 53 define('EBML_ID_DATEUTC', 0x0461); // [44][61] -- Date of the origin of timecode (value 0), i.e. production date. 54 define('EBML_ID_TAGLANGUAGE', 0x047A); // [44][7A] -- Specifies the language of the tag specified, in the Matroska languages form. 55 define('EBML_ID_TAGDEFAULT', 0x0484); // [44][84] -- Indication to know if this is the default/original language to use for the given tag. 56 define('EBML_ID_TAGBINARY', 0x0485); // [44][85] -- The values of the Tag if it is binary. Note that this cannot be used in the same SimpleTag as TagString. 57 define('EBML_ID_TAGSTRING', 0x0487); // [44][87] -- The value of the Tag. 58 define('EBML_ID_DURATION', 0x0489); // [44][89] -- Duration of the segment (based on TimecodeScale). 59 define('EBML_ID_CHAPPROCESSPRIVATE', 0x050D); // [45][0D] -- Some optional data attached to the ChapProcessCodecID information. For ChapProcessCodecID = 1, it is the "DVD level" equivalent. 60 define('EBML_ID_CHAPTERFLAGENABLED', 0x0598); // [45][98] -- Specify wether the chapter is enabled. It can be enabled/disabled by a Control Track. When disabled, the movie should skip all the content between the TimeStart and TimeEnd of this chapter. 61 define('EBML_ID_TAGNAME', 0x05A3); // [45][A3] -- The name of the Tag that is going to be stored. 62 define('EBML_ID_EDITIONENTRY', 0x05B9); // [45][B9] -- Contains all information about a segment edition. 63 define('EBML_ID_EDITIONUID', 0x05BC); // [45][BC] -- A unique ID to identify the edition. It's useful for tagging an edition. 64 define('EBML_ID_EDITIONFLAGHIDDEN', 0x05BD); // [45][BD] -- If an edition is hidden (1), it should not be available to the user interface (but still to Control Tracks). 65 define('EBML_ID_EDITIONFLAGDEFAULT', 0x05DB); // [45][DB] -- If a flag is set (1) the edition should be used as the default one. 66 define('EBML_ID_EDITIONFLAGORDERED', 0x05DD); // [45][DD] -- Specify if the chapters can be defined multiple times and the order to play them is enforced. 67 define('EBML_ID_FILEDATA', 0x065C); // [46][5C] -- The data of the file. 68 define('EBML_ID_FILEMIMETYPE', 0x0660); // [46][60] -- MIME type of the file. 69 define('EBML_ID_FILENAME', 0x066E); // [46][6E] -- Filename of the attached file. 70 define('EBML_ID_FILEREFERRAL', 0x0675); // [46][75] -- A binary value that a track/codec can refer to when the attachment is needed. 71 define('EBML_ID_FILEDESCRIPTION', 0x067E); // [46][7E] -- A human-friendly name for the attached file. 72 define('EBML_ID_FILEUID', 0x06AE); // [46][AE] -- Unique ID representing the file, as random as possible. 73 define('EBML_ID_CONTENTENCALGO', 0x07E1); // [47][E1] -- The encryption algorithm used. The value '0' means that the contents have not been encrypted but only signed. Predefined values: 74 define('EBML_ID_CONTENTENCKEYID', 0x07E2); // [47][E2] -- For public key algorithms this is the ID of the public key the the data was encrypted with. 75 define('EBML_ID_CONTENTSIGNATURE', 0x07E3); // [47][E3] -- A cryptographic signature of the contents. 76 define('EBML_ID_CONTENTSIGKEYID', 0x07E4); // [47][E4] -- This is the ID of the private key the data was signed with. 77 define('EBML_ID_CONTENTSIGALGO', 0x07E5); // [47][E5] -- The algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values: 78 define('EBML_ID_CONTENTSIGHASHALGO', 0x07E6); // [47][E6] -- The hash algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values: 79 define('EBML_ID_MUXINGAPP', 0x0D80); // [4D][80] -- Muxing application or library ("libmatroska-0.4.3"). 80 define('EBML_ID_SEEK', 0x0DBB); // [4D][BB] -- Contains a single seek entry to an EBML element. 81 define('EBML_ID_CONTENTENCODINGORDER', 0x1031); // [50][31] -- Tells when this modification was used during encoding/muxing starting with 0 and counting upwards. The decoder/demuxer has to start with the highest order number it finds and work its way down. This value has to be unique over all ContentEncodingOrder elements in the segment. 82 define('EBML_ID_CONTENTENCODINGSCOPE', 0x1032); // [50][32] -- A bit field that describes which elements have been modified in this way. Values (big endian) can be OR'ed. Possible values: 83 define('EBML_ID_CONTENTENCODINGTYPE', 0x1033); // [50][33] -- A value describing what kind of transformation has been done. Possible values: 84 define('EBML_ID_CONTENTCOMPRESSION', 0x1034); // [50][34] -- Settings describing the compression used. Must be present if the value of ContentEncodingType is 0 and absent otherwise. Each block must be decompressable even if no previous block is available in order not to prevent seeking. 85 define('EBML_ID_CONTENTENCRYPTION', 0x1035); // [50][35] -- Settings describing the encryption used. Must be present if the value of ContentEncodingType is 1 and absent otherwise. 86 define('EBML_ID_CUEREFNUMBER', 0x135F); // [53][5F] -- Number of the referenced Block of Track X in the specified Cluster. 87 define('EBML_ID_NAME', 0x136E); // [53][6E] -- A human-readable track name. 88 define('EBML_ID_CUEBLOCKNUMBER', 0x1378); // [53][78] -- Number of the Block in the specified Cluster. 89 define('EBML_ID_TRACKOFFSET', 0x137F); // [53][7F] -- A value to add to the Block's Timecode. This can be used to adjust the playback offset of a track. 90 define('EBML_ID_SEEKID', 0x13AB); // [53][AB] -- The binary ID corresponding to the element name. 91 define('EBML_ID_SEEKPOSITION', 0x13AC); // [53][AC] -- The position of the element in the segment in octets (0 = first level 1 element). 92 define('EBML_ID_STEREOMODE', 0x13B8); // [53][B8] -- Stereo-3D video mode. 93 define('EBML_ID_OLDSTEREOMODE', 0x13B9); // [53][B9] -- Bogus StereoMode value used in old versions of libmatroska. DO NOT USE. (0: mono, 1: right eye, 2: left eye, 3: both eyes). 94 define('EBML_ID_PIXELCROPBOTTOM', 0x14AA); // [54][AA] -- The number of video pixels to remove at the bottom of the image (for HDTV content). 95 define('EBML_ID_DISPLAYWIDTH', 0x14B0); // [54][B0] -- Width of the video frames to display. 96 define('EBML_ID_DISPLAYUNIT', 0x14B2); // [54][B2] -- Type of the unit for DisplayWidth/Height (0: pixels, 1: centimeters, 2: inches). 97 define('EBML_ID_ASPECTRATIOTYPE', 0x14B3); // [54][B3] -- Specify the possible modifications to the aspect ratio (0: free resizing, 1: keep aspect ratio, 2: fixed). 98 define('EBML_ID_DISPLAYHEIGHT', 0x14BA); // [54][BA] -- Height of the video frames to display. 99 define('EBML_ID_PIXELCROPTOP', 0x14BB); // [54][BB] -- The number of video pixels to remove at the top of the image. 100 define('EBML_ID_PIXELCROPLEFT', 0x14CC); // [54][CC] -- The number of video pixels to remove on the left of the image. 101 define('EBML_ID_PIXELCROPRIGHT', 0x14DD); // [54][DD] -- The number of video pixels to remove on the right of the image. 102 define('EBML_ID_FLAGFORCED', 0x15AA); // [55][AA] -- Set if that track MUST be used during playback. There can be many forced track for a kind (audio, video or subs), the player should select the one which language matches the user preference or the default + forced track. Overlay MAY happen between a forced and non-forced track of the same kind. 103 define('EBML_ID_MAXBLOCKADDITIONID', 0x15EE); // [55][EE] -- The maximum value of BlockAddID. A value 0 means there is no BlockAdditions for this track. 104 define('EBML_ID_WRITINGAPP', 0x1741); // [57][41] -- Writing application ("mkvmerge-0.3.3"). 105 define('EBML_ID_CLUSTERSILENTTRACKS', 0x1854); // [58][54] -- The list of tracks that are not used in that part of the stream. It is useful when using overlay tracks on seeking. Then you should decide what track to use. 106 define('EBML_ID_CLUSTERSILENTTRACKNUMBER', 0x18D7); // [58][D7] -- One of the track number that are not used from now on in the stream. It could change later if not specified as silent in a further Cluster. 107 define('EBML_ID_ATTACHEDFILE', 0x21A7); // [61][A7] -- An attached file. 108 define('EBML_ID_CONTENTENCODING', 0x2240); // [62][40] -- Settings for one content encoding like compression or encryption. 109 define('EBML_ID_BITDEPTH', 0x2264); // [62][64] -- Bits per sample, mostly used for PCM. 110 define('EBML_ID_CODECPRIVATE', 0x23A2); // [63][A2] -- Private data only known to the codec. 111 define('EBML_ID_TARGETS', 0x23C0); // [63][C0] -- Contain all UIDs where the specified meta data apply. It is void to describe everything in the segment. 112 define('EBML_ID_CHAPTERPHYSICALEQUIV', 0x23C3); // [63][C3] -- Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see complete list of values. 113 define('EBML_ID_TAGCHAPTERUID', 0x23C4); // [63][C4] -- A unique ID to identify the Chapter(s) the tags belong to. If the value is 0 at this level, the tags apply to all chapters in the Segment. 114 define('EBML_ID_TAGTRACKUID', 0x23C5); // [63][C5] -- A unique ID to identify the Track(s) the tags belong to. If the value is 0 at this level, the tags apply to all tracks in the Segment. 115 define('EBML_ID_TAGATTACHMENTUID', 0x23C6); // [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment. 116 define('EBML_ID_TAGEDITIONUID', 0x23C9); // [63][C9] -- A unique ID to identify the EditionEntry(s) the tags belong to. If the value is 0 at this level, the tags apply to all editions in the Segment. 117 define('EBML_ID_TARGETTYPE', 0x23CA); // [63][CA] -- An informational string that can be used to display the logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see TargetType). 118 define('EBML_ID_TRACKTRANSLATE', 0x2624); // [66][24] -- The track identification for the given Chapter Codec. 119 define('EBML_ID_TRACKTRANSLATETRACKID', 0x26A5); // [66][A5] -- The binary value used to represent this track in the chapter codec data. The format depends on the ChapProcessCodecID used. 120 define('EBML_ID_TRACKTRANSLATECODEC', 0x26BF); // [66][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu). 121 define('EBML_ID_TRACKTRANSLATEEDITIONUID', 0x26FC); // [66][FC] -- Specify an edition UID on which this translation applies. When not specified, it means for all editions found in the segment. 122 define('EBML_ID_SIMPLETAG', 0x27C8); // [67][C8] -- Contains general information about the target. 123 define('EBML_ID_TARGETTYPEVALUE', 0x28CA); // [68][CA] -- A number to indicate the logical level of the target (see TargetType). 124 define('EBML_ID_CHAPPROCESSCOMMAND', 0x2911); // [69][11] -- Contains all the commands associated to the Atom. 125 define('EBML_ID_CHAPPROCESSTIME', 0x2922); // [69][22] -- Defines when the process command should be handled (0: during the whole chapter, 1: before starting playback, 2: after playback of the chapter). 126 define('EBML_ID_CHAPTERTRANSLATE', 0x2924); // [69][24] -- A tuple of corresponding ID used by chapter codecs to represent this segment. 127 define('EBML_ID_CHAPPROCESSDATA', 0x2933); // [69][33] -- Contains the command information. The data should be interpreted depending on the ChapProcessCodecID value. For ChapProcessCodecID = 1, the data correspond to the binary DVD cell pre/post commands. 128 define('EBML_ID_CHAPPROCESS', 0x2944); // [69][44] -- Contains all the commands associated to the Atom. 129 define('EBML_ID_CHAPPROCESSCODECID', 0x2955); // [69][55] -- Contains the type of the codec used for the processing. A value of 0 means native Matroska processing (to be defined), a value of 1 means the DVD command set is used. More codec IDs can be added later. 130 define('EBML_ID_CHAPTERTRANSLATEID', 0x29A5); // [69][A5] -- The binary value used to represent this segment in the chapter codec data. The format depends on the ChapProcessCodecID used. 131 define('EBML_ID_CHAPTERTRANSLATECODEC', 0x29BF); // [69][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu). 132 define('EBML_ID_CHAPTERTRANSLATEEDITIONUID', 0x29FC); // [69][FC] -- Specify an edition UID on which this correspondance applies. When not specified, it means for all editions found in the segment. 133 define('EBML_ID_CONTENTENCODINGS', 0x2D80); // [6D][80] -- Settings for several content encoding mechanisms like compression or encryption. 134 define('EBML_ID_MINCACHE', 0x2DE7); // [6D][E7] -- The minimum number of frames a player should be able to cache during playback. If set to 0, the reference pseudo-cache system is not used. 135 define('EBML_ID_MAXCACHE', 0x2DF8); // [6D][F8] -- The maximum cache size required to store referenced frames in and the current frame. 0 means no cache is needed. 136 define('EBML_ID_CHAPTERSEGMENTUID', 0x2E67); // [6E][67] -- A segment to play in place of this chapter. Edition ChapterSegmentEditionUID should be used for this segment, otherwise no edition is used. 137 define('EBML_ID_CHAPTERSEGMENTEDITIONUID', 0x2EBC); // [6E][BC] -- The edition to play from the segment linked in ChapterSegmentUID. 138 define('EBML_ID_TRACKOVERLAY', 0x2FAB); // [6F][AB] -- Specify that this track is an overlay track for the Track specified (in the u-integer). That means when this track has a gap (see SilentTracks) the overlay track should be used instead. The order of multiple TrackOverlay matters, the first one is the one that should be used. If not found it should be the second, etc. 139 define('EBML_ID_TAG', 0x3373); // [73][73] -- Element containing elements specific to Tracks/Chapters. 140 define('EBML_ID_SEGMENTFILENAME', 0x3384); // [73][84] -- A filename corresponding to this segment. 141 define('EBML_ID_SEGMENTUID', 0x33A4); // [73][A4] -- A randomly generated unique ID to identify the current segment between many others (128 bits). 142 define('EBML_ID_CHAPTERUID', 0x33C4); // [73][C4] -- A unique ID to identify the Chapter. 143 define('EBML_ID_TRACKUID', 0x33C5); // [73][C5] -- A unique ID to identify the Track. This should be kept the same when making a direct stream copy of the Track to another file. 144 define('EBML_ID_ATTACHMENTLINK', 0x3446); // [74][46] -- The UID of an attachment that is used by this codec. 145 define('EBML_ID_CLUSTERBLOCKADDITIONS', 0x35A1); // [75][A1] -- Contain additional blocks to complete the main one. An EBML parser that has no knowledge of the Block structure could still see and use/skip these data. 146 define('EBML_ID_CHANNELPOSITIONS', 0x347B); // [7D][7B] -- Table of horizontal angles for each successive channel, see appendix. 147 define('EBML_ID_OUTPUTSAMPLINGFREQUENCY', 0x38B5); // [78][B5] -- Real output sampling frequency in Hz (used for SBR techniques). 148 define('EBML_ID_TITLE', 0x3BA9); // [7B][A9] -- General name of the segment. 149 define('EBML_ID_CHAPTERDISPLAY', 0x00); // [80] -- Contains all possible strings to use for the chapter display. 150 define('EBML_ID_TRACKTYPE', 0x03); // [83] -- A set of track types coded on 8 bits (1: video, 2: audio, 3: complex, 0x10: logo, 0x11: subtitle, 0x12: buttons, 0x20: control). 151 define('EBML_ID_CHAPSTRING', 0x05); // [85] -- Contains the string to use as the chapter atom. 152 define('EBML_ID_CODECID', 0x06); // [86] -- An ID corresponding to the codec, see the codec page for more info. 153 define('EBML_ID_FLAGDEFAULT', 0x08); // [88] -- Set if that track (audio, video or subs) SHOULD be used if no language found matches the user preference. 154 define('EBML_ID_CHAPTERTRACKNUMBER', 0x09); // [89] -- UID of the Track to apply this chapter too. In the absense of a control track, choosing this chapter will select the listed Tracks and deselect unlisted tracks. Absense of this element indicates that the Chapter should be applied to any currently used Tracks. 155 define('EBML_ID_CLUSTERSLICES', 0x0E); // [8E] -- Contains slices description. 156 define('EBML_ID_CHAPTERTRACK', 0x0F); // [8F] -- List of tracks on which the chapter applies. If this element is not present, all tracks apply 157 define('EBML_ID_CHAPTERTIMESTART', 0x11); // [91] -- Timecode of the start of Chapter (not scaled). 158 define('EBML_ID_CHAPTERTIMEEND', 0x12); // [92] -- Timecode of the end of Chapter (timecode excluded, not scaled). 159 define('EBML_ID_CUEREFTIME', 0x16); // [96] -- Timecode of the referenced Block. 160 define('EBML_ID_CUEREFCLUSTER', 0x17); // [97] -- Position of the Cluster containing the referenced Block. 161 define('EBML_ID_CHAPTERFLAGHIDDEN', 0x18); // [98] -- If a chapter is hidden (1), it should not be available to the user interface (but still to Control Tracks). 162 define('EBML_ID_FLAGINTERLACED', 0x1A); // [9A] -- Set if the video is interlaced. 163 define('EBML_ID_CLUSTERBLOCKDURATION', 0x1B); // [9B] -- The duration of the Block (based on TimecodeScale). This element is mandatory when DefaultDuration is set for the track. When not written and with no DefaultDuration, the value is assumed to be the difference between the timecode of this Block and the timecode of the next Block in "display" order (not coding order). This element can be useful at the end of a Track (as there is not other Block available), or when there is a break in a track like for subtitle tracks. 164 define('EBML_ID_FLAGLACING', 0x1C); // [9C] -- Set if the track may contain blocks using lacing. 165 define('EBML_ID_CHANNELS', 0x1F); // [9F] -- Numbers of channels in the track. 166 define('EBML_ID_CLUSTERBLOCKGROUP', 0x20); // [A0] -- Basic container of information containing a single Block or BlockVirtual, and information specific to that Block/VirtualBlock. 167 define('EBML_ID_CLUSTERBLOCK', 0x21); // [A1] -- Block containing the actual data to be rendered and a timecode relative to the Cluster Timecode. 168 define('EBML_ID_CLUSTERBLOCKVIRTUAL', 0x22); // [A2] -- A Block with no data. It must be stored in the stream at the place the real Block should be in display order. 169 define('EBML_ID_CLUSTERSIMPLEBLOCK', 0x23); // [A3] -- Similar to Block but without all the extra information, mostly used to reduced overhead when no extra feature is needed. 170 define('EBML_ID_CLUSTERCODECSTATE', 0x24); // [A4] -- The new codec state to use. Data interpretation is private to the codec. This information should always be referenced by a seek entry. 171 define('EBML_ID_CLUSTERBLOCKADDITIONAL', 0x25); // [A5] -- Interpreted by the codec as it wishes (using the BlockAddID). 172 define('EBML_ID_CLUSTERBLOCKMORE', 0x26); // [A6] -- Contain the BlockAdditional and some parameters. 173 define('EBML_ID_CLUSTERPOSITION', 0x27); // [A7] -- Position of the Cluster in the segment (0 in live broadcast streams). It might help to resynchronise offset on damaged streams. 174 define('EBML_ID_CODECDECODEALL', 0x2A); // [AA] -- The codec can decode potentially damaged data. 175 define('EBML_ID_CLUSTERPREVSIZE', 0x2B); // [AB] -- Size of the previous Cluster, in octets. Can be useful for backward playing. 176 define('EBML_ID_TRACKENTRY', 0x2E); // [AE] -- Describes a track with all elements. 177 define('EBML_ID_CLUSTERENCRYPTEDBLOCK', 0x2F); // [AF] -- Similar to SimpleBlock but the data inside the Block are Transformed (encrypt and/or signed). 178 define('EBML_ID_PIXELWIDTH', 0x30); // [B0] -- Width of the encoded video frames in pixels. 179 define('EBML_ID_CUETIME', 0x33); // [B3] -- Absolute timecode according to the segment time base. 180 define('EBML_ID_SAMPLINGFREQUENCY', 0x35); // [B5] -- Sampling frequency in Hz. 181 define('EBML_ID_CHAPTERATOM', 0x36); // [B6] -- Contains the atom information to use as the chapter atom (apply to all tracks). 182 define('EBML_ID_CUETRACKPOSITIONS', 0x37); // [B7] -- Contain positions for different tracks corresponding to the timecode. 183 define('EBML_ID_FLAGENABLED', 0x39); // [B9] -- Set if the track is used. 184 define('EBML_ID_PIXELHEIGHT', 0x3A); // [BA] -- Height of the encoded video frames in pixels. 185 define('EBML_ID_CUEPOINT', 0x3B); // [BB] -- Contains all information relative to a seek point in the segment. 186 define('EBML_ID_CRC32', 0x3F); // [BF] -- The CRC is computed on all the data of the Master element it's in, regardless of its position. It's recommended to put the CRC value at the beggining of the Master element for easier reading. All level 1 elements should include a CRC-32. 187 define('EBML_ID_CLUSTERBLOCKADDITIONID', 0x4B); // [CB] -- The ID of the BlockAdditional element (0 is the main Block). 188 define('EBML_ID_CLUSTERLACENUMBER', 0x4C); // [CC] -- The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc). While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback. 189 define('EBML_ID_CLUSTERFRAMENUMBER', 0x4D); // [CD] -- The number of the frame to generate from this lace with this delay (allow you to generate many frames from the same Block/Frame). 190 define('EBML_ID_CLUSTERDELAY', 0x4E); // [CE] -- The (scaled) delay to apply to the element. 191 define('EBML_ID_CLUSTERDURATION', 0x4F); // [CF] -- The (scaled) duration to apply to the element. 192 define('EBML_ID_TRACKNUMBER', 0x57); // [D7] -- The track number as used in the Block Header (using more than 127 tracks is not encouraged, though the design allows an unlimited number). 193 define('EBML_ID_CUEREFERENCE', 0x5B); // [DB] -- The Clusters containing the required referenced Blocks. 194 define('EBML_ID_VIDEO', 0x60); // [E0] -- Video settings. 195 define('EBML_ID_AUDIO', 0x61); // [E1] -- Audio settings. 196 define('EBML_ID_CLUSTERTIMESLICE', 0x68); // [E8] -- Contains extra time information about the data contained in the Block. While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback. 197 define('EBML_ID_CUECODECSTATE', 0x6A); // [EA] -- The position of the Codec State corresponding to this Cue element. 0 means that the data is taken from the initial Track Entry. 198 define('EBML_ID_CUEREFCODECSTATE', 0x6B); // [EB] -- The position of the Codec State corresponding to this referenced element. 0 means that the data is taken from the initial Track Entry. 199 define('EBML_ID_VOID', 0x6C); // [EC] -- Used to void damaged data, to avoid unexpected behaviors when using damaged data. The content is discarded. Also used to reserve space in a sub-element for later use. 200 define('EBML_ID_CLUSTERTIMECODE', 0x67); // [E7] -- Absolute timecode of the cluster (based on TimecodeScale). 201 define('EBML_ID_CLUSTERBLOCKADDID', 0x6E); // [EE] -- An ID to identify the BlockAdditional level. 202 define('EBML_ID_CUECLUSTERPOSITION', 0x71); // [F1] -- The position of the Cluster containing the required Block. 203 define('EBML_ID_CUETRACK', 0x77); // [F7] -- The track for which a position is given. 204 define('EBML_ID_CLUSTERREFERENCEPRIORITY', 0x7A); // [FA] -- This frame is referenced and has the specified cache priority. In cache only a frame of the same or higher priority can replace this frame. A value of 0 means the frame is not referenced. 205 define('EBML_ID_CLUSTERREFERENCEBLOCK', 0x7B); // [FB] -- Timecode of another frame used as a reference (ie: B or P frame). The timecode is relative to the block it's attached to. 206 define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- Relative position of the data that should be in position of the virtual block. 207 208 209 /** 210 * @tutorial http://www.matroska.org/technical/specs/index.html 211 * 212 * @todo Rewrite EBML parser to reduce it's size and honor default element values 213 * @todo After rewrite implement stream size calculation, that will provide additional useful info and enable AAC/FLAC audio bitrate detection 214 */ 215 class getid3_matroska extends getid3_handler 216 { 217 // public options 218 public static $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful [default: TRUE] 219 public static $parse_whole_file = false; // true to parse the whole file, not only header [default: FALSE] 220 221 // private parser settings/placeholders 222 private $EBMLbuffer = ''; 223 private $EBMLbuffer_offset = 0; 224 private $EBMLbuffer_length = 0; 225 private $current_offset = 0; 226 private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID); 227 228 public function Analyze() 229 { 230 $info = &$this->getid3->info; 231 232 // parse container 233 try { 234 $this->parseEBML($info); 235 } catch (Exception $e) { 236 $info['error'][] = 'EBML parser: '.$e->getMessage(); 237 } 238 239 // calculate playtime 240 if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { 241 foreach ($info['matroska']['info'] as $key => $infoarray) { 242 if (isset($infoarray['Duration'])) { 243 // TimecodeScale is how many nanoseconds each Duration unit is 244 $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); 245 break; 246 } 247 } 248 } 249 250 // extract tags 251 if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { 252 foreach ($info['matroska']['tags'] as $key => $infoarray) { 253 $this->ExtractCommentsSimpleTag($infoarray); 254 } 255 } 256 257 // process tracks 258 if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { 259 foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { 260 261 $track_info = array(); 262 $track_info['dataformat'] = self::CodecIDtoCommonName($trackarray['CodecID']); 263 $track_info['default'] = (isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true); 264 if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; } 265 266 switch ($trackarray['TrackType']) { 267 268 case 1: // Video 269 $track_info['resolution_x'] = $trackarray['PixelWidth']; 270 $track_info['resolution_y'] = $trackarray['PixelHeight']; 271 $track_info['display_unit'] = self::displayUnit(isset($trackarray['DisplayUnit']) ? $trackarray['DisplayUnit'] : 0); 272 $track_info['display_x'] = (isset($trackarray['DisplayWidth']) ? $trackarray['DisplayWidth'] : $trackarray['PixelWidth']); 273 $track_info['display_y'] = (isset($trackarray['DisplayHeight']) ? $trackarray['DisplayHeight'] : $trackarray['PixelHeight']); 274 275 if (isset($trackarray['PixelCropBottom'])) { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; } 276 if (isset($trackarray['PixelCropTop'])) { $track_info['crop_top'] = $trackarray['PixelCropTop']; } 277 if (isset($trackarray['PixelCropLeft'])) { $track_info['crop_left'] = $trackarray['PixelCropLeft']; } 278 if (isset($trackarray['PixelCropRight'])) { $track_info['crop_right'] = $trackarray['PixelCropRight']; } 279 if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } 280 if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } 281 282 switch ($trackarray['CodecID']) { 283 case 'V_MS/VFW/FOURCC': 284 if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { 285 $this->warning('Unable to parse codec private data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'); 286 break; 287 } 288 $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']); 289 $track_info['codec'] = getid3_riff::fourccLookup($parsed['fourcc']); 290 $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; 291 break; 292 293 /*case 'V_MPEG4/ISO/AVC': 294 $h264['profile'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 1, 1)); 295 $h264['level'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 3, 1)); 296 $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 4, 1)); 297 $h264['NALUlength'] = ($rn & 3) + 1; 298 $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 5, 1)); 299 $nsps = ($rn & 31); 300 $offset = 6; 301 for ($i = 0; $i < $nsps; $i ++) { 302 $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); 303 $h264['SPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); 304 $offset += 2 + $length; 305 } 306 $npps = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 1)); 307 $offset += 1; 308 for ($i = 0; $i < $npps; $i ++) { 309 $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); 310 $h264['PPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); 311 $offset += 2 + $length; 312 } 313 $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $h264; 314 break;*/ 315 } 316 317 $info['video']['streams'][] = $track_info; 318 break; 319 320 case 2: // Audio 321 $track_info['sample_rate'] = (isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0); 322 $track_info['channels'] = (isset($trackarray['Channels']) ? $trackarray['Channels'] : 1); 323 $track_info['language'] = (isset($trackarray['Language']) ? $trackarray['Language'] : 'eng'); 324 if (isset($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } 325 if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } 326 327 switch ($trackarray['CodecID']) { 328 case 'A_PCM/INT/LIT': 329 case 'A_PCM/INT/BIG': 330 $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; 331 break; 332 333 case 'A_AC3': 334 case 'A_DTS': 335 case 'A_MPEG/L3': 336 case 'A_MPEG/L2': 337 case 'A_FLAC': 338 if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']).'.php', __FILE__, false)) { 339 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.'.$track_info['dataformat'].'.php"'); 340 break; 341 } 342 343 if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { 344 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set'); 345 break; 346 } 347 348 // create temp instance 349 $getid3_temp = new getID3(); 350 if ($track_info['dataformat'] != 'flac') { 351 $getid3_temp->openfile($this->getid3->filename); 352 } 353 $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; 354 if ($track_info['dataformat'][0] == 'm' || $track_info['dataformat'] == 'flac') { 355 $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; 356 } 357 358 // analyze 359 $class = 'getid3_'.($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']); 360 $header_data_key = $track_info['dataformat'][0] == 'm' ? 'mpeg' : $track_info['dataformat']; 361 $getid3_audio = new $class($getid3_temp, __CLASS__); 362 if ($track_info['dataformat'] == 'flac') { 363 $getid3_audio->AnalyzeString($trackarray['CodecPrivate']); 364 } 365 else { 366 $getid3_audio->Analyze(); 367 } 368 if (!empty($getid3_temp->info[$header_data_key])) { 369 $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; 370 if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { 371 foreach ($getid3_temp->info['audio'] as $key => $value) { 372 $track_info[$key] = $value; 373 } 374 } 375 } 376 else { 377 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because '.$class.'::Analyze() failed at offset '.$getid3_temp->info['avdataoffset']); 378 } 379 380 // copy errors and warnings 381 if (!empty($getid3_temp->info['error'])) { 382 foreach ($getid3_temp->info['error'] as $newerror) { 383 $this->warning($class.'() says: ['.$newerror.']'); 384 } 385 } 386 if (!empty($getid3_temp->info['warning'])) { 387 foreach ($getid3_temp->info['warning'] as $newerror) { 388 if ($track_info['dataformat'] == 'mp3' && preg_match('/^Probable truncated file: expecting \d+ bytes of audio data, only found \d+ \(short by \d+ bytes\)$/', $newerror)) { 389 // LAME/Xing header is probably set, but audio data is chunked into Matroska file and near-impossible to verify if audio stream is complete, so ignore useless warning 390 continue; 391 } 392 $this->warning($class.'() says: ['.$newerror.']'); 393 } 394 } 395 unset($getid3_temp, $getid3_audio); 396 break; 397 398 case 'A_AAC': 399 case 'A_AAC/MPEG2/LC': 400 case 'A_AAC/MPEG2/LC/SBR': 401 case 'A_AAC/MPEG4/LC': 402 case 'A_AAC/MPEG4/LC/SBR': 403 $this->warning($trackarray['CodecID'].' audio data contains no header, audio/video bitrates can\'t be calculated'); 404 break; 405 406 case 'A_VORBIS': 407 if (!isset($trackarray['CodecPrivate'])) { 408 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data not set'); 409 break; 410 } 411 $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1); 412 if ($vorbis_offset === false) { 413 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data does not contain "vorbis" keyword'); 414 break; 415 } 416 $vorbis_offset -= 1; 417 418 if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, false)) { 419 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.ogg.php"'); 420 break; 421 } 422 423 // create temp instance 424 $getid3_temp = new getID3(); 425 426 // analyze 427 $getid3_ogg = new getid3_ogg($getid3_temp); 428 $oggpageinfo['page_seqno'] = 0; 429 $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); 430 if (!empty($getid3_temp->info['ogg'])) { 431 $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg']; 432 if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { 433 foreach ($getid3_temp->info['audio'] as $key => $value) { 434 $track_info[$key] = $value; 435 } 436 } 437 } 438 439 // copy errors and warnings 440 if (!empty($getid3_temp->info['error'])) { 441 foreach ($getid3_temp->info['error'] as $newerror) { 442 $this->warning('getid3_ogg() says: ['.$newerror.']'); 443 } 444 } 445 if (!empty($getid3_temp->info['warning'])) { 446 foreach ($getid3_temp->info['warning'] as $newerror) { 447 $this->warning('getid3_ogg() says: ['.$newerror.']'); 448 } 449 } 450 451 if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) { 452 $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal']; 453 } 454 unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset); 455 break; 456 457 case 'A_MS/ACM': 458 if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { 459 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'); 460 break; 461 } 462 463 $parsed = getid3_riff::parseWAVEFORMATex($trackarray['CodecPrivate']); 464 foreach ($parsed as $key => $value) { 465 if ($key != 'raw') { 466 $track_info[$key] = $value; 467 } 468 } 469 $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; 470 break; 471 472 default: 473 $this->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"'); 474 } 475 476 $info['audio']['streams'][] = $track_info; 477 break; 478 } 479 } 480 481 if (!empty($info['video']['streams'])) { 482 $info['video'] = self::getDefaultStreamInfo($info['video']['streams']); 483 } 484 if (!empty($info['audio']['streams'])) { 485 $info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']); 486 } 487 } 488 489 // process attachments 490 if (isset($info['matroska']['attachments']) && $this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE) { 491 foreach ($info['matroska']['attachments'] as $i => $entry) { 492 if (strpos($entry['FileMimeType'], 'image/') === 0 && !empty($entry['FileData'])) { 493 $info['matroska']['comments']['picture'][] = array('data' => $entry['FileData'], 'image_mime' => $entry['FileMimeType'], 'filename' => $entry['FileName']); 494 } 495 } 496 } 497 498 // determine mime type 499 if (!empty($info['video']['streams'])) { 500 $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska'); 501 } elseif (!empty($info['audio']['streams'])) { 502 $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska'); 503 } elseif (isset($info['mime_type'])) { 504 unset($info['mime_type']); 505 } 506 507 return true; 508 } 509 510 private function parseEBML(&$info) { 511 // http://www.matroska.org/technical/specs/index.html#EBMLBasics 512 $this->current_offset = $info['avdataoffset']; 513 514 while ($this->getEBMLelement($top_element, $info['avdataend'])) { 515 switch ($top_element['id']) { 516 517 case EBML_ID_EBML: 518 $info['fileformat'] = 'matroska'; 519 $info['matroska']['header']['offset'] = $top_element['offset']; 520 $info['matroska']['header']['length'] = $top_element['length']; 521 522 while ($this->getEBMLelement($element_data, $top_element['end'], true)) { 523 switch ($element_data['id']) { 524 525 case EBML_ID_EBMLVERSION: 526 case EBML_ID_EBMLREADVERSION: 527 case EBML_ID_EBMLMAXIDLENGTH: 528 case EBML_ID_EBMLMAXSIZELENGTH: 529 case EBML_ID_DOCTYPEVERSION: 530 case EBML_ID_DOCTYPEREADVERSION: 531 $element_data['data'] = getid3_lib::BigEndian2Int($element_data['data']); 532 break; 533 534 case EBML_ID_DOCTYPE: 535 $element_data['data'] = getid3_lib::trimNullByte($element_data['data']); 536 $info['matroska']['doctype'] = $element_data['data']; 537 break; 538 539 case EBML_ID_CRC32: // not useful, ignore 540 $this->current_offset = $element_data['end']; 541 unset($element_data); 542 break; 543 544 default: 545 $this->unhandledElement('header', __LINE__, $element_data); 546 } 547 if (!empty($element_data)) { 548 unset($element_data['offset'], $element_data['end']); 549 $info['matroska']['header']['elements'][] = $element_data; 550 } 551 } 552 break; 553 554 case EBML_ID_SEGMENT: 555 $info['matroska']['segment'][0]['offset'] = $top_element['offset']; 556 $info['matroska']['segment'][0]['length'] = $top_element['length']; 557 558 while ($this->getEBMLelement($element_data, $top_element['end'])) { 559 if ($element_data['id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required 560 $info['matroska']['segments'][] = $element_data; 561 } 562 switch ($element_data['id']) { 563 564 case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements. 565 566 while ($this->getEBMLelement($seek_entry, $element_data['end'])) { 567 switch ($seek_entry['id']) { 568 569 case EBML_ID_SEEK: // Contains a single seek entry to an EBML element 570 while ($this->getEBMLelement($sub_seek_entry, $seek_entry['end'], true)) { 571 572 switch ($sub_seek_entry['id']) { 573 574 case EBML_ID_SEEKID: 575 $seek_entry['target_id'] = self::EBML2Int($sub_seek_entry['data']); 576 $seek_entry['target_name'] = self::EBMLidName($seek_entry['target_id']); 577 break; 578 579 case EBML_ID_SEEKPOSITION: 580 $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($sub_seek_entry['data']); 581 break; 582 583 default: 584 $this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry); } 585 } 586 587 if ($seek_entry['target_id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required 588 $info['matroska']['seek'][] = $seek_entry; 589 } 590 break; 591 592 default: 593 $this->unhandledElement('seekhead', __LINE__, $seek_entry); 594 } 595 } 596 break; 597 598 case EBML_ID_TRACKS: // A top-level block of information with many tracks described. 599 $info['matroska']['tracks'] = $element_data; 600 601 while ($this->getEBMLelement($track_entry, $element_data['end'])) { 602 switch ($track_entry['id']) { 603 604 case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. 605 606 while ($this->getEBMLelement($subelement, $track_entry['end'], array(EBML_ID_VIDEO, EBML_ID_AUDIO, EBML_ID_CONTENTENCODINGS, EBML_ID_CODECPRIVATE))) { 607 switch ($subelement['id']) { 608 609 case EBML_ID_TRACKNUMBER: 610 case EBML_ID_TRACKUID: 611 case EBML_ID_TRACKTYPE: 612 case EBML_ID_MINCACHE: 613 case EBML_ID_MAXCACHE: 614 case EBML_ID_MAXBLOCKADDITIONID: 615 case EBML_ID_DEFAULTDURATION: // nanoseconds per frame 616 $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); 617 break; 618 619 case EBML_ID_TRACKTIMECODESCALE: 620 $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); 621 break; 622 623 case EBML_ID_CODECID: 624 case EBML_ID_LANGUAGE: 625 case EBML_ID_NAME: 626 case EBML_ID_CODECNAME: 627 $track_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); 628 break; 629 630 case EBML_ID_CODECPRIVATE: 631 $track_entry[$subelement['id_name']] = $this->readEBMLelementData($subelement['length'], true); 632 break; 633 634 case EBML_ID_FLAGENABLED: 635 case EBML_ID_FLAGDEFAULT: 636 case EBML_ID_FLAGFORCED: 637 case EBML_ID_FLAGLACING: 638 case EBML_ID_CODECDECODEALL: 639 $track_entry[$subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($subelement['data']); 640 break; 641 642 case EBML_ID_VIDEO: 643 644 while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { 645 switch ($sub_subelement['id']) { 646 647 case EBML_ID_PIXELWIDTH: 648 case EBML_ID_PIXELHEIGHT: 649 case EBML_ID_PIXELCROPBOTTOM: 650 case EBML_ID_PIXELCROPTOP: 651 case EBML_ID_PIXELCROPLEFT: 652 case EBML_ID_PIXELCROPRIGHT: 653 case EBML_ID_DISPLAYWIDTH: 654 case EBML_ID_DISPLAYHEIGHT: 655 case EBML_ID_DISPLAYUNIT: 656 case EBML_ID_ASPECTRATIOTYPE: 657 case EBML_ID_STEREOMODE: 658 case EBML_ID_OLDSTEREOMODE: 659 $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 660 break; 661 662 case EBML_ID_FLAGINTERLACED: 663 $track_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']); 664 break; 665 666 case EBML_ID_GAMMAVALUE: 667 $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); 668 break; 669 670 case EBML_ID_COLOURSPACE: 671 $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); 672 break; 673 674 default: 675 $this->unhandledElement('track.video', __LINE__, $sub_subelement); 676 } 677 } 678 break; 679 680 case EBML_ID_AUDIO: 681 682 while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { 683 switch ($sub_subelement['id']) { 684 685 case EBML_ID_CHANNELS: 686 case EBML_ID_BITDEPTH: 687 $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 688 break; 689 690 case EBML_ID_SAMPLINGFREQUENCY: 691 case EBML_ID_OUTPUTSAMPLINGFREQUENCY: 692 $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); 693 break; 694 695 case EBML_ID_CHANNELPOSITIONS: 696 $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); 697 break; 698 699 default: 700 $this->unhandledElement('track.audio', __LINE__, $sub_subelement); 701 } 702 } 703 break; 704 705 case EBML_ID_CONTENTENCODINGS: 706 707 while ($this->getEBMLelement($sub_subelement, $subelement['end'])) { 708 switch ($sub_subelement['id']) { 709 710 case EBML_ID_CONTENTENCODING: 711 712 while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CONTENTCOMPRESSION, EBML_ID_CONTENTENCRYPTION))) { 713 switch ($sub_sub_subelement['id']) { 714 715 case EBML_ID_CONTENTENCODINGORDER: 716 case EBML_ID_CONTENTENCODINGSCOPE: 717 case EBML_ID_CONTENTENCODINGTYPE: 718 $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 719 break; 720 721 case EBML_ID_CONTENTCOMPRESSION: 722 723 while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { 724 switch ($sub_sub_sub_subelement['id']) { 725 726 case EBML_ID_CONTENTCOMPALGO: 727 $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); 728 break; 729 730 case EBML_ID_CONTENTCOMPSETTINGS: 731 $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; 732 break; 733 734 default: 735 $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); 736 } 737 } 738 break; 739 740 case EBML_ID_CONTENTENCRYPTION: 741 742 while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { 743 switch ($sub_sub_sub_subelement['id']) { 744 745 case EBML_ID_CONTENTENCALGO: 746 case EBML_ID_CONTENTSIGALGO: 747 case EBML_ID_CONTENTSIGHASHALGO: 748 $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); 749 break; 750 751 case EBML_ID_CONTENTENCKEYID: 752 case EBML_ID_CONTENTSIGNATURE: 753 case EBML_ID_CONTENTSIGKEYID: 754 $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; 755 break; 756 757 default: 758 $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); 759 } 760 } 761 break; 762 763 default: 764 $this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement); 765 } 766 } 767 break; 768 769 default: 770 $this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement); 771 } 772 } 773 break; 774 775 default: 776 $this->unhandledElement('track', __LINE__, $subelement); 777 } 778 } 779 780 $info['matroska']['tracks']['tracks'][] = $track_entry; 781 break; 782 783 default: 784 $this->unhandledElement('tracks', __LINE__, $track_entry); 785 } 786 } 787 break; 788 789 case EBML_ID_INFO: // Contains miscellaneous general information and statistics on the file. 790 $info_entry = array(); 791 792 while ($this->getEBMLelement($subelement, $element_data['end'], true)) { 793 switch ($subelement['id']) { 794 795 case EBML_ID_TIMECODESCALE: 796 $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); 797 break; 798 799 case EBML_ID_DURATION: 800 $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); 801 break; 802 803 case EBML_ID_DATEUTC: 804 $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); 805 $info_entry[$subelement['id_name'].'_unix'] = self::EBMLdate2unix($info_entry[$subelement['id_name']]); 806 break; 807 808 case EBML_ID_SEGMENTUID: 809 case EBML_ID_PREVUID: 810 case EBML_ID_NEXTUID: 811 $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); 812 break; 813 814 case EBML_ID_SEGMENTFAMILY: 815 $info_entry[$subelement['id_name']][] = getid3_lib::trimNullByte($subelement['data']); 816 break; 817 818 case EBML_ID_SEGMENTFILENAME: 819 case EBML_ID_PREVFILENAME: 820 case EBML_ID_NEXTFILENAME: 821 case EBML_ID_TITLE: 822 case EBML_ID_MUXINGAPP: 823 case EBML_ID_WRITINGAPP: 824 $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); 825 $info['matroska']['comments'][strtolower($subelement['id_name'])][] = $info_entry[$subelement['id_name']]; 826 break; 827 828 case EBML_ID_CHAPTERTRANSLATE: 829 $chaptertranslate_entry = array(); 830 831 while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { 832 switch ($sub_subelement['id']) { 833 834 case EBML_ID_CHAPTERTRANSLATEEDITIONUID: 835 $chaptertranslate_entry[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data']); 836 break; 837 838 case EBML_ID_CHAPTERTRANSLATECODEC: 839 $chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 840 break; 841 842 case EBML_ID_CHAPTERTRANSLATEID: 843 $chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); 844 break; 845 846 default: 847 $this->unhandledElement('info.chaptertranslate', __LINE__, $sub_subelement); 848 } 849 } 850 $info_entry[$subelement['id_name']] = $chaptertranslate_entry; 851 break; 852 853 default: 854 $this->unhandledElement('info', __LINE__, $subelement); 855 } 856 } 857 $info['matroska']['info'][] = $info_entry; 858 break; 859 860 case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams. 861 if (self::$hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway 862 $this->current_offset = $element_data['end']; 863 break; 864 } 865 $cues_entry = array(); 866 867 while ($this->getEBMLelement($subelement, $element_data['end'])) { 868 switch ($subelement['id']) { 869 870 case EBML_ID_CUEPOINT: 871 $cuepoint_entry = array(); 872 873 while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CUETRACKPOSITIONS))) { 874 switch ($sub_subelement['id']) { 875 876 case EBML_ID_CUETRACKPOSITIONS: 877 $cuetrackpositions_entry = array(); 878 879 while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { 880 switch ($sub_sub_subelement['id']) { 881 882 case EBML_ID_CUETRACK: 883 case EBML_ID_CUECLUSTERPOSITION: 884 case EBML_ID_CUEBLOCKNUMBER: 885 case EBML_ID_CUECODECSTATE: 886 $cuetrackpositions_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 887 break; 888 889 default: 890 $this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement); 891 } 892 } 893 $cuepoint_entry[$sub_subelement['id_name']][] = $cuetrackpositions_entry; 894 break; 895 896 case EBML_ID_CUETIME: 897 $cuepoint_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 898 break; 899 900 default: 901 $this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement); 902 } 903 } 904 $cues_entry[] = $cuepoint_entry; 905 break; 906 907 default: 908 $this->unhandledElement('cues', __LINE__, $subelement); 909 } 910 } 911 $info['matroska']['cues'] = $cues_entry; 912 break; 913 914 case EBML_ID_TAGS: // Element containing elements specific to Tracks/Chapters. 915 $tags_entry = array(); 916 917 while ($this->getEBMLelement($subelement, $element_data['end'], false)) { 918 switch ($subelement['id']) { 919 920 case EBML_ID_TAG: 921 $tag_entry = array(); 922 923 while ($this->getEBMLelement($sub_subelement, $subelement['end'], false)) { 924 switch ($sub_subelement['id']) { 925 926 case EBML_ID_TARGETS: 927 $targets_entry = array(); 928 929 while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { 930 switch ($sub_sub_subelement['id']) { 931 932 case EBML_ID_TARGETTYPEVALUE: 933 $targets_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 934 $targets_entry[strtolower($sub_sub_subelement['id_name']).'_long'] = self::TargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]); 935 break; 936 937 case EBML_ID_TARGETTYPE: 938 $targets_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; 939 break; 940 941 case EBML_ID_TAGTRACKUID: 942 case EBML_ID_TAGEDITIONUID: 943 case EBML_ID_TAGCHAPTERUID: 944 case EBML_ID_TAGATTACHMENTUID: 945 $targets_entry[$sub_sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 946 break; 947 948 default: 949 $this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement); 950 } 951 } 952 $tag_entry[$sub_subelement['id_name']] = $targets_entry; 953 break; 954 955 case EBML_ID_SIMPLETAG: 956 $tag_entry[$sub_subelement['id_name']][] = $this->HandleEMBLSimpleTag($sub_subelement['end']); 957 break; 958 959 default: 960 $this->unhandledElement('tags.tag', __LINE__, $sub_subelement); 961 } 962 } 963 $tags_entry[] = $tag_entry; 964 break; 965 966 default: 967 $this->unhandledElement('tags', __LINE__, $subelement); 968 } 969 } 970 $info['matroska']['tags'] = $tags_entry; 971 break; 972 973 case EBML_ID_ATTACHMENTS: // Contain attached files. 974 975 while ($this->getEBMLelement($subelement, $element_data['end'])) { 976 switch ($subelement['id']) { 977 978 case EBML_ID_ATTACHEDFILE: 979 $attachedfile_entry = array(); 980 981 while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_FILEDATA))) { 982 switch ($sub_subelement['id']) { 983 984 case EBML_ID_FILEDESCRIPTION: 985 case EBML_ID_FILENAME: 986 case EBML_ID_FILEMIMETYPE: 987 $attachedfile_entry[$sub_subelement['id_name']] = $sub_subelement['data']; 988 break; 989 990 case EBML_ID_FILEDATA: 991 $attachedfile_entry['data_offset'] = $this->current_offset; 992 $attachedfile_entry['data_length'] = $sub_subelement['length']; 993 994 $attachedfile_entry[$sub_subelement['id_name']] = $this->saveAttachment( 995 $attachedfile_entry['FileName'], 996 $attachedfile_entry['data_offset'], 997 $attachedfile_entry['data_length']); 998 999 $this->current_offset = $sub_subelement['end']; 1000 break; 1001 1002 case EBML_ID_FILEUID: 1003 $attachedfile_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 1004 break; 1005 1006 default: 1007 $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement); 1008 } 1009 } 1010 $info['matroska']['attachments'][] = $attachedfile_entry; 1011 break; 1012 1013 default: 1014 $this->unhandledElement('attachments', __LINE__, $subelement); 1015 } 1016 } 1017 break; 1018 1019 case EBML_ID_CHAPTERS: 1020 1021 while ($this->getEBMLelement($subelement, $element_data['end'])) { 1022 switch ($subelement['id']) { 1023 1024 case EBML_ID_EDITIONENTRY: 1025 $editionentry_entry = array(); 1026 1027 while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CHAPTERATOM))) { 1028 switch ($sub_subelement['id']) { 1029 1030 case EBML_ID_EDITIONUID: 1031 $editionentry_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 1032 break; 1033 1034 case EBML_ID_EDITIONFLAGHIDDEN: 1035 case EBML_ID_EDITIONFLAGDEFAULT: 1036 case EBML_ID_EDITIONFLAGORDERED: 1037 $editionentry_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']); 1038 break; 1039 1040 case EBML_ID_CHAPTERATOM: 1041 $chapteratom_entry = array(); 1042 1043 while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CHAPTERTRACK, EBML_ID_CHAPTERDISPLAY))) { 1044 switch ($sub_sub_subelement['id']) { 1045 1046 case EBML_ID_CHAPTERSEGMENTUID: 1047 case EBML_ID_CHAPTERSEGMENTEDITIONUID: 1048 $chapteratom_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; 1049 break; 1050 1051 case EBML_ID_CHAPTERFLAGENABLED: 1052 case EBML_ID_CHAPTERFLAGHIDDEN: 1053 $chapteratom_entry[$sub_sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 1054 break; 1055 1056 case EBML_ID_CHAPTERUID: 1057 case EBML_ID_CHAPTERTIMESTART: 1058 case EBML_ID_CHAPTERTIMEEND: 1059 $chapteratom_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 1060 break; 1061 1062 case EBML_ID_CHAPTERTRACK: 1063 $chaptertrack_entry = array(); 1064 1065 while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { 1066 switch ($sub_sub_sub_subelement['id']) { 1067 1068 case EBML_ID_CHAPTERTRACKNUMBER: 1069 $chaptertrack_entry[$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); 1070 break; 1071 1072 default: 1073 $this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement); 1074 } 1075 } 1076 $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry; 1077 break; 1078 1079 case EBML_ID_CHAPTERDISPLAY: 1080 $chapterdisplay_entry = array(); 1081 1082 while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { 1083 switch ($sub_sub_sub_subelement['id']) { 1084 1085 case EBML_ID_CHAPSTRING: 1086 case EBML_ID_CHAPLANGUAGE: 1087 case EBML_ID_CHAPCOUNTRY: 1088 $chapterdisplay_entry[$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; 1089 break; 1090 1091 default: 1092 $this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement); 1093 } 1094 } 1095 $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry; 1096 break; 1097 1098 default: 1099 $this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement); 1100 } 1101 } 1102 $editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry; 1103 break; 1104 1105 default: 1106 $this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement); 1107 } 1108 } 1109 $info['matroska']['chapters'][] = $editionentry_entry; 1110 break; 1111 1112 default: 1113 $this->unhandledElement('chapters', __LINE__, $subelement); 1114 } 1115 } 1116 break; 1117 1118 case EBML_ID_CLUSTER: // The lower level element containing the (monolithic) Block structure. 1119 $cluster_entry = array(); 1120 1121 while ($this->getEBMLelement($subelement, $element_data['end'], array(EBML_ID_CLUSTERSILENTTRACKS, EBML_ID_CLUSTERBLOCKGROUP, EBML_ID_CLUSTERSIMPLEBLOCK))) { 1122 switch ($subelement['id']) { 1123 1124 case EBML_ID_CLUSTERTIMECODE: 1125 case EBML_ID_CLUSTERPOSITION: 1126 case EBML_ID_CLUSTERPREVSIZE: 1127 $cluster_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); 1128 break; 1129 1130 case EBML_ID_CLUSTERSILENTTRACKS: 1131 $cluster_silent_tracks = array(); 1132 1133 while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { 1134 switch ($sub_subelement['id']) { 1135 1136 case EBML_ID_CLUSTERSILENTTRACKNUMBER: 1137 $cluster_silent_tracks[] = getid3_lib::BigEndian2Int($sub_subelement['data']); 1138 break; 1139 1140 default: 1141 $this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement); 1142 } 1143 } 1144 $cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks; 1145 break; 1146 1147 case EBML_ID_CLUSTERBLOCKGROUP: 1148 $cluster_block_group = array('offset' => $this->current_offset); 1149 1150 while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CLUSTERBLOCK))) { 1151 switch ($sub_subelement['id']) { 1152 1153 case EBML_ID_CLUSTERBLOCK: 1154 $cluster_block_group[$sub_subelement['id_name']] = $this->HandleEMBLClusterBlock($sub_subelement, EBML_ID_CLUSTERBLOCK, $info); 1155 break; 1156 1157 case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int 1158 case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int 1159 $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 1160 break; 1161 1162 case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int 1163 $cluster_block_group[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data'], false, true); 1164 break; 1165 1166 case EBML_ID_CLUSTERCODECSTATE: 1167 $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); 1168 break; 1169 1170 default: 1171 $this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement); 1172 } 1173 } 1174 $cluster_entry[$subelement['id_name']][] = $cluster_block_group; 1175 break; 1176 1177 case EBML_ID_CLUSTERSIMPLEBLOCK: 1178 $cluster_entry[$subelement['id_name']][] = $this->HandleEMBLClusterBlock($subelement, EBML_ID_CLUSTERSIMPLEBLOCK, $info); 1179 break; 1180 1181 default: 1182 $this->unhandledElement('cluster', __LINE__, $subelement); 1183 } 1184 $this->current_offset = $subelement['end']; 1185 } 1186 if (!self::$hide_clusters) { 1187 $info['matroska']['cluster'][] = $cluster_entry; 1188 } 1189 1190 // check to see if all the data we need exists already, if so, break out of the loop 1191 if (!self::$parse_whole_file) { 1192 if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { 1193 if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { 1194 if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) { 1195 return; 1196 } 1197 } 1198 } 1199 } 1200 break; 1201 1202 default: 1203 $this->unhandledElement('segment', __LINE__, $element_data); 1204 } 1205 } 1206 break; 1207 1208 default: 1209 $this->unhandledElement('root', __LINE__, $top_element); 1210 } 1211 } 1212 } 1213 1214 private function EnsureBufferHasEnoughData($min_data=1024) { 1215 if (($this->current_offset - $this->EBMLbuffer_offset) >= ($this->EBMLbuffer_length - $min_data)) { 1216 $read_bytes = max($min_data, $this->getid3->fread_buffer_size()); 1217 1218 try { 1219 $this->fseek($this->current_offset); 1220 $this->EBMLbuffer_offset = $this->current_offset; 1221 $this->EBMLbuffer = $this->fread($read_bytes); 1222 $this->EBMLbuffer_length = strlen($this->EBMLbuffer); 1223 } catch (getid3_exception $e) { 1224 $this->warning('EBML parser: '.$e->getMessage()); 1225 return false; 1226 } 1227 1228 if ($this->EBMLbuffer_length == 0 && $this->feof()) { 1229 return $this->error('EBML parser: ran out of file at offset '.$this->current_offset); 1230 } 1231 } 1232 return true; 1233 } 1234 1235 private function readEBMLint() { 1236 $actual_offset = $this->current_offset - $this->EBMLbuffer_offset; 1237 1238 // get length of integer 1239 $first_byte_int = ord($this->EBMLbuffer[$actual_offset]); 1240 if (0x80 & $first_byte_int) { 1241 $length = 1; 1242 } elseif (0x40 & $first_byte_int) { 1243 $length = 2; 1244 } elseif (0x20 & $first_byte_int) { 1245 $length = 3; 1246 } elseif (0x10 & $first_byte_int) { 1247 $length = 4; 1248 } elseif (0x08 & $first_byte_int) { 1249 $length = 5; 1250 } elseif (0x04 & $first_byte_int) { 1251 $length = 6; 1252 } elseif (0x02 & $first_byte_int) { 1253 $length = 7; 1254 } elseif (0x01 & $first_byte_int) { 1255 $length = 8; 1256 } else { 1257 throw new Exception('invalid EBML integer (leading 0x00) at '.$this->current_offset); 1258 } 1259 1260 // read 1261 $int_value = self::EBML2Int(substr($this->EBMLbuffer, $actual_offset, $length)); 1262 $this->current_offset += $length; 1263 1264 return $int_value; 1265 } 1266 1267 private function readEBMLelementData($length, $check_buffer=false) { 1268 if ($check_buffer && !$this->EnsureBufferHasEnoughData($length)) { 1269 return false; 1270 } 1271 $data = substr($this->EBMLbuffer, $this->current_offset - $this->EBMLbuffer_offset, $length); 1272 $this->current_offset += $length; 1273 return $data; 1274 } 1275 1276 private function getEBMLelement(&$element, $parent_end, $get_data=false) { 1277 if ($this->current_offset >= $parent_end) { 1278 return false; 1279 } 1280 1281 if (!$this->EnsureBufferHasEnoughData()) { 1282 $this->current_offset = PHP_INT_MAX; // do not exit parser right now, allow to finish current loop to gather maximum information 1283 return false; 1284 } 1285 1286 $element = array(); 1287 1288 // set offset 1289 $element['offset'] = $this->current_offset; 1290 1291 // get ID 1292 $element['id'] = $this->readEBMLint(); 1293 1294 // get name 1295 $element['id_name'] = self::EBMLidName($element['id']); 1296 1297 // get length 1298 $element['length'] = $this->readEBMLint(); 1299 1300 // get end offset 1301 $element['end'] = $this->current_offset + $element['length']; 1302 1303 // get raw data 1304 $dont_parse = (in_array($element['id'], $this->unuseful_elements) || $element['id_name'] == dechex($element['id'])); 1305 if (($get_data === true || (is_array($get_data) && !in_array($element['id'], $get_data))) && !$dont_parse) { 1306 $element['data'] = $this->readEBMLelementData($element['length'], $element); 1307 } 1308 1309 return true; 1310 } 1311 1312 private function unhandledElement($type, $line, $element) { 1313 // warn only about unknown and missed elements, not about unuseful 1314 if (!in_array($element['id'], $this->unuseful_elements)) { 1315 $this->warning('Unhandled '.$type.' element ['.basename(__FILE__).':'.$line.'] ('.$element['id'].'::'.$element['id_name'].' ['.$element['length'].' bytes]) at '.$element['offset']); 1316 } 1317 1318 // increase offset for unparsed elements 1319 if (!isset($element['data'])) { 1320 $this->current_offset = $element['end']; 1321 } 1322 } 1323 1324 private function ExtractCommentsSimpleTag($SimpleTagArray) { 1325 if (!empty($SimpleTagArray['SimpleTag'])) { 1326 foreach ($SimpleTagArray['SimpleTag'] as $SimpleTagKey => $SimpleTagData) { 1327 if (!empty($SimpleTagData['TagName']) && !empty($SimpleTagData['TagString'])) { 1328 $this->getid3->info['matroska']['comments'][strtolower($SimpleTagData['TagName'])][] = $SimpleTagData['TagString']; 1329 } 1330 if (!empty($SimpleTagData['SimpleTag'])) { 1331 $this->ExtractCommentsSimpleTag($SimpleTagData); 1332 } 1333 } 1334 } 1335 1336 return true; 1337 } 1338 1339 private function HandleEMBLSimpleTag($parent_end) { 1340 $simpletag_entry = array(); 1341 1342 while ($this->getEBMLelement($element, $parent_end, array(EBML_ID_SIMPLETAG))) { 1343 switch ($element['id']) { 1344 1345 case EBML_ID_TAGNAME: 1346 case EBML_ID_TAGLANGUAGE: 1347 case EBML_ID_TAGSTRING: 1348 case EBML_ID_TAGBINARY: 1349 $simpletag_entry[$element['id_name']] = $element['data']; 1350 break; 1351 1352 case EBML_ID_SIMPLETAG: 1353 $simpletag_entry[$element['id_name']][] = $this->HandleEMBLSimpleTag($element['end']); 1354 break; 1355 1356 case EBML_ID_TAGDEFAULT: 1357 $simpletag_entry[$element['id_name']] = (bool)getid3_lib::BigEndian2Int($element['data']); 1358 break; 1359 1360 default: 1361 $this->unhandledElement('tag.simpletag', __LINE__, $element); 1362 } 1363 } 1364 1365 return $simpletag_entry; 1366 } 1367 1368 private function HandleEMBLClusterBlock($element, $block_type, &$info) { 1369 // http://www.matroska.org/technical/specs/index.html#block_structure 1370 // http://www.matroska.org/technical/specs/index.html#simpleblock_structure 1371 1372 $block_data = array(); 1373 $block_data['tracknumber'] = $this->readEBMLint(); 1374 $block_data['timecode'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(2), false, true); 1375 $block_data['flags_raw'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); 1376 1377 if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { 1378 $block_data['flags']['keyframe'] = (($block_data['flags_raw'] & 0x80) >> 7); 1379 //$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0x70) >> 4); 1380 } 1381 else { 1382 //$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0xF0) >> 4); 1383 } 1384 $block_data['flags']['invisible'] = (bool)(($block_data['flags_raw'] & 0x08) >> 3); 1385 $block_data['flags']['lacing'] = (($block_data['flags_raw'] & 0x06) >> 1); // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing 1386 if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { 1387 $block_data['flags']['discardable'] = (($block_data['flags_raw'] & 0x01)); 1388 } 1389 else { 1390 //$block_data['flags']['reserved2'] = (($block_data['flags_raw'] & 0x01) >> 0); 1391 } 1392 $block_data['flags']['lacing_type'] = self::BlockLacingType($block_data['flags']['lacing']); 1393 1394 // Lace (when lacing bit is set) 1395 if ($block_data['flags']['lacing'] > 0) { 1396 $block_data['lace_frames'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)) + 1; // Number of frames in the lace-1 (uint8) 1397 if ($block_data['flags']['lacing'] != 0x02) { 1398 for ($i = 1; $i < $block_data['lace_frames']; $i ++) { // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). 1399 if ($block_data['flags']['lacing'] == 0x03) { // EBML lacing 1400 $block_data['lace_frames_size'][$i] = $this->readEBMLint(); // TODO: read size correctly, calc size for the last frame. For now offsets are deteminded OK with readEBMLint() and that's the most important thing. 1401 } 1402 else { // Xiph lacing 1403 $block_data['lace_frames_size'][$i] = 0; 1404 do { 1405 $size = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); 1406 $block_data['lace_frames_size'][$i] += $size; 1407 } 1408 while ($size == 255); 1409 } 1410 } 1411 if ($block_data['flags']['lacing'] == 0x01) { // calc size of the last frame only for Xiph lacing, till EBML sizes are now anyway determined incorrectly 1412 $block_data['lace_frames_size'][] = $element['end'] - $this->current_offset - array_sum($block_data['lace_frames_size']); 1413 } 1414 } 1415 } 1416 1417 if (!isset($info['matroska']['track_data_offsets'][$block_data['tracknumber']])) { 1418 $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['offset'] = $this->current_offset; 1419 $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length'] = $element['end'] - $this->current_offset; 1420 //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] = 0; 1421 } 1422 //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] += $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length']; 1423 //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['duration'] = $block_data['timecode'] * ((isset($info['matroska']['info'][0]['TimecodeScale']) ? $info['matroska']['info'][0]['TimecodeScale'] : 1000000) / 1000000000); 1424 1425 // set offset manually 1426 $this->current_offset = $element['end']; 1427 1428 return $block_data; 1429 } 1430 1431 private static function EBML2Int($EBMLstring) { 1432 // http://matroska.org/specs/ 1433 1434 // Element ID coded with an UTF-8 like system: 1435 // 1xxx xxxx - Class A IDs (2^7 -2 possible values) (base 0x8X) 1436 // 01xx xxxx xxxx xxxx - Class B IDs (2^14-2 possible values) (base 0x4X 0xXX) 1437 // 001x xxxx xxxx xxxx xxxx xxxx - Class C IDs (2^21-2 possible values) (base 0x2X 0xXX 0xXX) 1438 // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - Class D IDs (2^28-2 possible values) (base 0x1X 0xXX 0xXX 0xXX) 1439 // Values with all x at 0 and 1 are reserved (hence the -2). 1440 1441 // Data size, in octets, is also coded with an UTF-8 like system : 1442 // 1xxx xxxx - value 0 to 2^7-2 1443 // 01xx xxxx xxxx xxxx - value 0 to 2^14-2 1444 // 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2 1445 // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2 1446 // 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2 1447 // 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2 1448 // 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 1449 // 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 1450 1451 $first_byte_int = ord($EBMLstring[0]); 1452 if (0x80 & $first_byte_int) { 1453 $EBMLstring[0] = chr($first_byte_int & 0x7F); 1454 } elseif (0x40 & $first_byte_int) { 1455 $EBMLstring[0] = chr($first_byte_int & 0x3F); 1456 } elseif (0x20 & $first_byte_int) { 1457 $EBMLstring[0] = chr($first_byte_int & 0x1F); 1458 } elseif (0x10 & $first_byte_int) { 1459 $EBMLstring[0] = chr($first_byte_int & 0x0F); 1460 } elseif (0x08 & $first_byte_int) { 1461 $EBMLstring[0] = chr($first_byte_int & 0x07); 1462 } elseif (0x04 & $first_byte_int) { 1463 $EBMLstring[0] = chr($first_byte_int & 0x03); 1464 } elseif (0x02 & $first_byte_int) { 1465 $EBMLstring[0] = chr($first_byte_int & 0x01); 1466 } elseif (0x01 & $first_byte_int) { 1467 $EBMLstring[0] = chr($first_byte_int & 0x00); 1468 } 1469 1470 return getid3_lib::BigEndian2Int($EBMLstring); 1471 } 1472 1473 private static function EBMLdate2unix($EBMLdatestamp) { 1474 // Date - signed 8 octets integer in nanoseconds with 0 indicating the precise beginning of the millennium (at 2001-01-01T00:00:00,000000000 UTC) 1475 // 978307200 == mktime(0, 0, 0, 1, 1, 2001) == January 1, 2001 12:00:00am UTC 1476 return round(($EBMLdatestamp / 1000000000) + 978307200); 1477 } 1478 1479 public static function TargetTypeValue($target_type) { 1480 // http://www.matroska.org/technical/specs/tagging/index.html 1481 static $TargetTypeValue = array(); 1482 if (empty($TargetTypeValue)) { 1483 $TargetTypeValue[10] = 'A: ~ V:shot'; // the lowest hierarchy found in music or movies 1484 $TargetTypeValue[20] = 'A:subtrack/part/movement ~ V:scene'; // corresponds to parts of a track for audio (like a movement) 1485 $TargetTypeValue[30] = 'A:track/song ~ V:chapter'; // the common parts of an album or a movie 1486 $TargetTypeValue[40] = 'A:part/session ~ V:part/session'; // when an album or episode has different logical parts 1487 $TargetTypeValue[50] = 'A:album/opera/concert ~ V:movie/episode/concert'; // the most common grouping level of music and video (equals to an episode for TV series) 1488 $TargetTypeValue[60] = 'A:edition/issue/volume/opus ~ V:season/sequel/volume'; // a list of lower levels grouped together 1489 $TargetTypeValue[70] = 'A:collection ~ V:collection'; // the high hierarchy consisting of many different lower items 1490 } 1491 return (isset($TargetTypeValue[$target_type]) ? $TargetTypeValue[$target_type] : $target_type); 1492 } 1493 1494 public static function BlockLacingType($lacingtype) { 1495 // http://matroska.org/technical/specs/index.html#block_structure 1496 static $BlockLacingType = array(); 1497 if (empty($BlockLacingType)) { 1498 $BlockLacingType[0x00] = 'no lacing'; 1499 $BlockLacingType[0x01] = 'Xiph lacing'; 1500 $BlockLacingType[0x02] = 'fixed-size lacing'; 1501 $BlockLacingType[0x03] = 'EBML lacing'; 1502 } 1503 return (isset($BlockLacingType[$lacingtype]) ? $BlockLacingType[$lacingtype] : $lacingtype); 1504 } 1505 1506 public static function CodecIDtoCommonName($codecid) { 1507 // http://www.matroska.org/technical/specs/codecid/index.html 1508 static $CodecIDlist = array(); 1509 if (empty($CodecIDlist)) { 1510 $CodecIDlist['A_AAC'] = 'aac'; 1511 $CodecIDlist['A_AAC/MPEG2/LC'] = 'aac'; 1512 $CodecIDlist['A_AC3'] = 'ac3'; 1513 $CodecIDlist['A_DTS'] = 'dts'; 1514 $CodecIDlist['A_FLAC'] = 'flac'; 1515 $CodecIDlist['A_MPEG/L1'] = 'mp1'; 1516 $CodecIDlist['A_MPEG/L2'] = 'mp2'; 1517 $CodecIDlist['A_MPEG/L3'] = 'mp3'; 1518 $CodecIDlist['A_PCM/INT/LIT'] = 'pcm'; // PCM Integer Little Endian 1519 $CodecIDlist['A_PCM/INT/BIG'] = 'pcm'; // PCM Integer Big Endian 1520 $CodecIDlist['A_QUICKTIME/QDMC'] = 'quicktime'; // Quicktime: QDesign Music 1521 $CodecIDlist['A_QUICKTIME/QDM2'] = 'quicktime'; // Quicktime: QDesign Music v2 1522 $CodecIDlist['A_VORBIS'] = 'vorbis'; 1523 $CodecIDlist['V_MPEG1'] = 'mpeg'; 1524 $CodecIDlist['V_THEORA'] = 'theora'; 1525 $CodecIDlist['V_REAL/RV40'] = 'real'; 1526 $CodecIDlist['V_REAL/RV10'] = 'real'; 1527 $CodecIDlist['V_REAL/RV20'] = 'real'; 1528 $CodecIDlist['V_REAL/RV30'] = 'real'; 1529 $CodecIDlist['V_QUICKTIME'] = 'quicktime'; // Quicktime 1530 $CodecIDlist['V_MPEG4/ISO/AP'] = 'mpeg4'; 1531 $CodecIDlist['V_MPEG4/ISO/ASP'] = 'mpeg4'; 1532 $CodecIDlist['V_MPEG4/ISO/AVC'] = 'h264'; 1533 $CodecIDlist['V_MPEG4/ISO/SP'] = 'mpeg4'; 1534 $CodecIDlist['V_VP8'] = 'vp8'; 1535 $CodecIDlist['V_MS/VFW/FOURCC'] = 'riff'; 1536 $CodecIDlist['A_MS/ACM'] = 'riff'; 1537 } 1538 return (isset($CodecIDlist[$codecid]) ? $CodecIDlist[$codecid] : $codecid); 1539 } 1540 1541 private static function EBMLidName($value) { 1542 static $EBMLidList = array(); 1543 if (empty($EBMLidList)) { 1544 $EBMLidList[EBML_ID_ASPECTRATIOTYPE] = 'AspectRatioType'; 1545 $EBMLidList[EBML_ID_ATTACHEDFILE] = 'AttachedFile'; 1546 $EBMLidList[EBML_ID_ATTACHMENTLINK] = 'AttachmentLink'; 1547 $EBMLidList[EBML_ID_ATTACHMENTS] = 'Attachments'; 1548 $EBMLidList[EBML_ID_AUDIO] = 'Audio'; 1549 $EBMLidList[EBML_ID_BITDEPTH] = 'BitDepth'; 1550 $EBMLidList[EBML_ID_CHANNELPOSITIONS] = 'ChannelPositions'; 1551 $EBMLidList[EBML_ID_CHANNELS] = 'Channels'; 1552 $EBMLidList[EBML_ID_CHAPCOUNTRY] = 'ChapCountry'; 1553 $EBMLidList[EBML_ID_CHAPLANGUAGE] = 'ChapLanguage'; 1554 $EBMLidList[EBML_ID_CHAPPROCESS] = 'ChapProcess'; 1555 $EBMLidList[EBML_ID_CHAPPROCESSCODECID] = 'ChapProcessCodecID'; 1556 $EBMLidList[EBML_ID_CHAPPROCESSCOMMAND] = 'ChapProcessCommand'; 1557 $EBMLidList[EBML_ID_CHAPPROCESSDATA] = 'ChapProcessData'; 1558 $EBMLidList[EBML_ID_CHAPPROCESSPRIVATE] = 'ChapProcessPrivate'; 1559 $EBMLidList[EBML_ID_CHAPPROCESSTIME] = 'ChapProcessTime'; 1560 $EBMLidList[EBML_ID_CHAPSTRING] = 'ChapString'; 1561 $EBMLidList[EBML_ID_CHAPTERATOM] = 'ChapterAtom'; 1562 $EBMLidList[EBML_ID_CHAPTERDISPLAY] = 'ChapterDisplay'; 1563 $EBMLidList[EBML_ID_CHAPTERFLAGENABLED] = 'ChapterFlagEnabled'; 1564 $EBMLidList[EBML_ID_CHAPTERFLAGHIDDEN] = 'ChapterFlagHidden'; 1565 $EBMLidList[EBML_ID_CHAPTERPHYSICALEQUIV] = 'ChapterPhysicalEquiv'; 1566 $EBMLidList[EBML_ID_CHAPTERS] = 'Chapters'; 1567 $EBMLidList[EBML_ID_CHAPTERSEGMENTEDITIONUID] = 'ChapterSegmentEditionUID'; 1568 $EBMLidList[EBML_ID_CHAPTERSEGMENTUID] = 'ChapterSegmentUID'; 1569 $EBMLidList[EBML_ID_CHAPTERTIMEEND] = 'ChapterTimeEnd'; 1570 $EBMLidList[EBML_ID_CHAPTERTIMESTART] = 'ChapterTimeStart'; 1571 $EBMLidList[EBML_ID_CHAPTERTRACK] = 'ChapterTrack'; 1572 $EBMLidList[EBML_ID_CHAPTERTRACKNUMBER] = 'ChapterTrackNumber'; 1573 $EBMLidList[EBML_ID_CHAPTERTRANSLATE] = 'ChapterTranslate'; 1574 $EBMLidList[EBML_ID_CHAPTERTRANSLATECODEC] = 'ChapterTranslateCodec'; 1575 $EBMLidList[EBML_ID_CHAPTERTRANSLATEEDITIONUID] = 'ChapterTranslateEditionUID'; 1576 $EBMLidList[EBML_ID_CHAPTERTRANSLATEID] = 'ChapterTranslateID'; 1577 $EBMLidList[EBML_ID_CHAPTERUID] = 'ChapterUID'; 1578 $EBMLidList[EBML_ID_CLUSTER] = 'Cluster'; 1579 $EBMLidList[EBML_ID_CLUSTERBLOCK] = 'ClusterBlock'; 1580 $EBMLidList[EBML_ID_CLUSTERBLOCKADDID] = 'ClusterBlockAddID'; 1581 $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONAL] = 'ClusterBlockAdditional'; 1582 $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONID] = 'ClusterBlockAdditionID'; 1583 $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONS] = 'ClusterBlockAdditions'; 1584 $EBMLidList[EBML_ID_CLUSTERBLOCKDURATION] = 'ClusterBlockDuration'; 1585 $EBMLidList[EBML_ID_CLUSTERBLOCKGROUP] = 'ClusterBlockGroup'; 1586 $EBMLidList[EBML_ID_CLUSTERBLOCKMORE] = 'ClusterBlockMore'; 1587 $EBMLidList[EBML_ID_CLUSTERBLOCKVIRTUAL] = 'ClusterBlockVirtual'; 1588 $EBMLidList[EBML_ID_CLUSTERCODECSTATE] = 'ClusterCodecState'; 1589 $EBMLidList[EBML_ID_CLUSTERDELAY] = 'ClusterDelay'; 1590 $EBMLidList[EBML_ID_CLUSTERDURATION] = 'ClusterDuration'; 1591 $EBMLidList[EBML_ID_CLUSTERENCRYPTEDBLOCK] = 'ClusterEncryptedBlock'; 1592 $EBMLidList[EBML_ID_CLUSTERFRAMENUMBER] = 'ClusterFrameNumber'; 1593 $EBMLidList[EBML_ID_CLUSTERLACENUMBER] = 'ClusterLaceNumber'; 1594 $EBMLidList[EBML_ID_CLUSTERPOSITION] = 'ClusterPosition'; 1595 $EBMLidList[EBML_ID_CLUSTERPREVSIZE] = 'ClusterPrevSize'; 1596 $EBMLidList[EBML_ID_CLUSTERREFERENCEBLOCK] = 'ClusterReferenceBlock'; 1597 $EBMLidList[EBML_ID_CLUSTERREFERENCEPRIORITY] = 'ClusterReferencePriority'; 1598 $EBMLidList[EBML_ID_CLUSTERREFERENCEVIRTUAL] = 'ClusterReferenceVirtual'; 1599 $EBMLidList[EBML_ID_CLUSTERSILENTTRACKNUMBER] = 'ClusterSilentTrackNumber'; 1600 $EBMLidList[EBML_ID_CLUSTERSILENTTRACKS] = 'ClusterSilentTracks'; 1601 $EBMLidList[EBML_ID_CLUSTERSIMPLEBLOCK] = 'ClusterSimpleBlock'; 1602 $EBMLidList[EBML_ID_CLUSTERTIMECODE] = 'ClusterTimecode'; 1603 $EBMLidList[EBML_ID_CLUSTERTIMESLICE] = 'ClusterTimeSlice'; 1604 $EBMLidList[EBML_ID_CODECDECODEALL] = 'CodecDecodeAll'; 1605 $EBMLidList[EBML_ID_CODECDOWNLOADURL] = 'CodecDownloadURL'; 1606 $EBMLidList[EBML_ID_CODECID] = 'CodecID'; 1607 $EBMLidList[EBML_ID_CODECINFOURL] = 'CodecInfoURL'; 1608 $EBMLidList[EBML_ID_CODECNAME] = 'CodecName'; 1609 $EBMLidList[EBML_ID_CODECPRIVATE] = 'CodecPrivate'; 1610 $EBMLidList[EBML_ID_CODECSETTINGS] = 'CodecSettings'; 1611 $EBMLidList[EBML_ID_COLOURSPACE] = 'ColourSpace'; 1612 $EBMLidList[EBML_ID_CONTENTCOMPALGO] = 'ContentCompAlgo'; 1613 $EBMLidList[EBML_ID_CONTENTCOMPRESSION] = 'ContentCompression'; 1614 $EBMLidList[EBML_ID_CONTENTCOMPSETTINGS] = 'ContentCompSettings'; 1615 $EBMLidList[EBML_ID_CONTENTENCALGO] = 'ContentEncAlgo'; 1616 $EBMLidList[EBML_ID_CONTENTENCKEYID] = 'ContentEncKeyID'; 1617 $EBMLidList[EBML_ID_CONTENTENCODING] = 'ContentEncoding'; 1618 $EBMLidList[EBML_ID_CONTENTENCODINGORDER] = 'ContentEncodingOrder'; 1619 $EBMLidList[EBML_ID_CONTENTENCODINGS] = 'ContentEncodings'; 1620 $EBMLidList[EBML_ID_CONTENTENCODINGSCOPE] = 'ContentEncodingScope'; 1621 $EBMLidList[EBML_ID_CONTENTENCODINGTYPE] = 'ContentEncodingType'; 1622 $EBMLidList[EBML_ID_CONTENTENCRYPTION] = 'ContentEncryption'; 1623 $EBMLidList[EBML_ID_CONTENTSIGALGO] = 'ContentSigAlgo'; 1624 $EBMLidList[EBML_ID_CONTENTSIGHASHALGO] = 'ContentSigHashAlgo'; 1625 $EBMLidList[EBML_ID_CONTENTSIGKEYID] = 'ContentSigKeyID'; 1626 $EBMLidList[EBML_ID_CONTENTSIGNATURE] = 'ContentSignature'; 1627 $EBMLidList[EBML_ID_CRC32] = 'CRC32'; 1628 $EBMLidList[EBML_ID_CUEBLOCKNUMBER] = 'CueBlockNumber'; 1629 $EBMLidList[EBML_ID_CUECLUSTERPOSITION] = 'CueClusterPosition'; 1630 $EBMLidList[EBML_ID_CUECODECSTATE] = 'CueCodecState'; 1631 $EBMLidList[EBML_ID_CUEPOINT] = 'CuePoint'; 1632 $EBMLidList[EBML_ID_CUEREFCLUSTER] = 'CueRefCluster'; 1633 $EBMLidList[EBML_ID_CUEREFCODECSTATE] = 'CueRefCodecState'; 1634 $EBMLidList[EBML_ID_CUEREFERENCE] = 'CueReference'; 1635 $EBMLidList[EBML_ID_CUEREFNUMBER] = 'CueRefNumber'; 1636 $EBMLidList[EBML_ID_CUEREFTIME] = 'CueRefTime'; 1637 $EBMLidList[EBML_ID_CUES] = 'Cues'; 1638 $EBMLidList[EBML_ID_CUETIME] = 'CueTime'; 1639 $EBMLidList[EBML_ID_CUETRACK] = 'CueTrack'; 1640 $EBMLidList[EBML_ID_CUETRACKPOSITIONS] = 'CueTrackPositions'; 1641 $EBMLidList[EBML_ID_DATEUTC] = 'DateUTC'; 1642 $EBMLidList[EBML_ID_DEFAULTDURATION] = 'DefaultDuration'; 1643 $EBMLidList[EBML_ID_DISPLAYHEIGHT] = 'DisplayHeight'; 1644 $EBMLidList[EBML_ID_DISPLAYUNIT] = 'DisplayUnit'; 1645 $EBMLidList[EBML_ID_DISPLAYWIDTH] = 'DisplayWidth'; 1646 $EBMLidList[EBML_ID_DOCTYPE] = 'DocType'; 1647 $EBMLidList[EBML_ID_DOCTYPEREADVERSION] = 'DocTypeReadVersion'; 1648 $EBMLidList[EBML_ID_DOCTYPEVERSION] = 'DocTypeVersion'; 1649 $EBMLidList[EBML_ID_DURATION] = 'Duration'; 1650 $EBMLidList[EBML_ID_EBML] = 'EBML'; 1651 $EBMLidList[EBML_ID_EBMLMAXIDLENGTH] = 'EBMLMaxIDLength'; 1652 $EBMLidList[EBML_ID_EBMLMAXSIZELENGTH] = 'EBMLMaxSizeLength'; 1653 $EBMLidList[EBML_ID_EBMLREADVERSION] = 'EBMLReadVersion'; 1654 $EBMLidList[EBML_ID_EBMLVERSION] = 'EBMLVersion'; 1655 $EBMLidList[EBML_ID_EDITIONENTRY] = 'EditionEntry'; 1656 $EBMLidList[EBML_ID_EDITIONFLAGDEFAULT] = 'EditionFlagDefault'; 1657 $EBMLidList[EBML_ID_EDITIONFLAGHIDDEN] = 'EditionFlagHidden'; 1658 $EBMLidList[EBML_ID_EDITIONFLAGORDERED] = 'EditionFlagOrdered'; 1659 $EBMLidList[EBML_ID_EDITIONUID] = 'EditionUID'; 1660 $EBMLidList[EBML_ID_FILEDATA] = 'FileData'; 1661 $EBMLidList[EBML_ID_FILEDESCRIPTION] = 'FileDescription'; 1662 $EBMLidList[EBML_ID_FILEMIMETYPE] = 'FileMimeType'; 1663 $EBMLidList[EBML_ID_FILENAME] = 'FileName'; 1664 $EBMLidList[EBML_ID_FILEREFERRAL] = 'FileReferral'; 1665 $EBMLidList[EBML_ID_FILEUID] = 'FileUID'; 1666 $EBMLidList[EBML_ID_FLAGDEFAULT] = 'FlagDefault'; 1667 $EBMLidList[EBML_ID_FLAGENABLED] = 'FlagEnabled'; 1668 $EBMLidList[EBML_ID_FLAGFORCED] = 'FlagForced'; 1669 $EBMLidList[EBML_ID_FLAGINTERLACED] = 'FlagInterlaced'; 1670 $EBMLidList[EBML_ID_FLAGLACING] = 'FlagLacing'; 1671 $EBMLidList[EBML_ID_GAMMAVALUE] = 'GammaValue'; 1672 $EBMLidList[EBML_ID_INFO] = 'Info'; 1673 $EBMLidList[EBML_ID_LANGUAGE] = 'Language'; 1674 $EBMLidList[EBML_ID_MAXBLOCKADDITIONID] = 'MaxBlockAdditionID'; 1675 $EBMLidList[EBML_ID_MAXCACHE] = 'MaxCache'; 1676 $EBMLidList[EBML_ID_MINCACHE] = 'MinCache'; 1677 $EBMLidList[EBML_ID_MUXINGAPP] = 'MuxingApp'; 1678 $EBMLidList[EBML_ID_NAME] = 'Name'; 1679 $EBMLidList[EBML_ID_NEXTFILENAME] = 'NextFilename'; 1680 $EBMLidList[EBML_ID_NEXTUID] = 'NextUID'; 1681 $EBMLidList[EBML_ID_OUTPUTSAMPLINGFREQUENCY] = 'OutputSamplingFrequency'; 1682 $EBMLidList[EBML_ID_PIXELCROPBOTTOM] = 'PixelCropBottom'; 1683 $EBMLidList[EBML_ID_PIXELCROPLEFT] = 'PixelCropLeft'; 1684 $EBMLidList[EBML_ID_PIXELCROPRIGHT] = 'PixelCropRight'; 1685 $EBMLidList[EBML_ID_PIXELCROPTOP] = 'PixelCropTop'; 1686 $EBMLidList[EBML_ID_PIXELHEIGHT] = 'PixelHeight'; 1687 $EBMLidList[EBML_ID_PIXELWIDTH] = 'PixelWidth'; 1688 $EBMLidList[EBML_ID_PREVFILENAME] = 'PrevFilename'; 1689 $EBMLidList[EBML_ID_PREVUID] = 'PrevUID'; 1690 $EBMLidList[EBML_ID_SAMPLINGFREQUENCY] = 'SamplingFrequency'; 1691 $EBMLidList[EBML_ID_SEEK] = 'Seek'; 1692 $EBMLidList[EBML_ID_SEEKHEAD] = 'SeekHead'; 1693 $EBMLidList[EBML_ID_SEEKID] = 'SeekID'; 1694 $EBMLidList[EBML_ID_SEEKPOSITION] = 'SeekPosition'; 1695 $EBMLidList[EBML_ID_SEGMENT] = 'Segment'; 1696 $EBMLidList[EBML_ID_SEGMENTFAMILY] = 'SegmentFamily'; 1697 $EBMLidList[EBML_ID_SEGMENTFILENAME] = 'SegmentFilename'; 1698 $EBMLidList[EBML_ID_SEGMENTUID] = 'SegmentUID'; 1699 $EBMLidList[EBML_ID_SIMPLETAG] = 'SimpleTag'; 1700 $EBMLidList[EBML_ID_CLUSTERSLICES] = 'ClusterSlices'; 1701 $EBMLidList[EBML_ID_STEREOMODE] = 'StereoMode'; 1702 $EBMLidList[EBML_ID_OLDSTEREOMODE] = 'OldStereoMode'; 1703 $EBMLidList[EBML_ID_TAG] = 'Tag'; 1704 $EBMLidList[EBML_ID_TAGATTACHMENTUID] = 'TagAttachmentUID'; 1705 $EBMLidList[EBML_ID_TAGBINARY] = 'TagBinary'; 1706 $EBMLidList[EBML_ID_TAGCHAPTERUID] = 'TagChapterUID'; 1707 $EBMLidList[EBML_ID_TAGDEFAULT] = 'TagDefault'; 1708 $EBMLidList[EBML_ID_TAGEDITIONUID] = 'TagEditionUID'; 1709 $EBMLidList[EBML_ID_TAGLANGUAGE] = 'TagLanguage'; 1710 $EBMLidList[EBML_ID_TAGNAME] = 'TagName'; 1711 $EBMLidList[EBML_ID_TAGTRACKUID] = 'TagTrackUID'; 1712 $EBMLidList[EBML_ID_TAGS] = 'Tags'; 1713 $EBMLidList[EBML_ID_TAGSTRING] = 'TagString'; 1714 $EBMLidList[EBML_ID_TARGETS] = 'Targets'; 1715 $EBMLidList[EBML_ID_TARGETTYPE] = 'TargetType'; 1716 $EBMLidList[EBML_ID_TARGETTYPEVALUE] = 'TargetTypeValue'; 1717 $EBMLidList[EBML_ID_TIMECODESCALE] = 'TimecodeScale'; 1718 $EBMLidList[EBML_ID_TITLE] = 'Title'; 1719 $EBMLidList[EBML_ID_TRACKENTRY] = 'TrackEntry'; 1720 $EBMLidList[EBML_ID_TRACKNUMBER] = 'TrackNumber'; 1721 $EBMLidList[EBML_ID_TRACKOFFSET] = 'TrackOffset'; 1722 $EBMLidList[EBML_ID_TRACKOVERLAY] = 'TrackOverlay'; 1723 $EBMLidList[EBML_ID_TRACKS] = 'Tracks'; 1724 $EBMLidList[EBML_ID_TRACKTIMECODESCALE] = 'TrackTimecodeScale'; 1725 $EBMLidList[EBML_ID_TRACKTRANSLATE] = 'TrackTranslate'; 1726 $EBMLidList[EBML_ID_TRACKTRANSLATECODEC] = 'TrackTranslateCodec'; 1727 $EBMLidList[EBML_ID_TRACKTRANSLATEEDITIONUID] = 'TrackTranslateEditionUID'; 1728 $EBMLidList[EBML_ID_TRACKTRANSLATETRACKID] = 'TrackTranslateTrackID'; 1729 $EBMLidList[EBML_ID_TRACKTYPE] = 'TrackType'; 1730 $EBMLidList[EBML_ID_TRACKUID] = 'TrackUID'; 1731 $EBMLidList[EBML_ID_VIDEO] = 'Video'; 1732 $EBMLidList[EBML_ID_VOID] = 'Void'; 1733 $EBMLidList[EBML_ID_WRITINGAPP] = 'WritingApp'; 1734 } 1735 1736 return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value)); 1737 } 1738 1739 public static function displayUnit($value) { 1740 // http://www.matroska.org/technical/specs/index.html#DisplayUnit 1741 static $units = array( 1742 0 => 'pixels', 1743 1 => 'centimeters', 1744 2 => 'inches', 1745 3 => 'Display Aspect Ratio'); 1746 1747 return (isset($units[$value]) ? $units[$value] : 'unknown'); 1748 } 1749 1750 private static function getDefaultStreamInfo($streams) 1751 { 1752 foreach (array_reverse($streams) as $stream) { 1753 if ($stream['default']) { 1754 break; 1755 } 1756 } 1757 1758 $unset = array('default', 'name'); 1759 foreach ($unset as $u) { 1760 if (isset($stream[$u])) { 1761 unset($stream[$u]); 1762 } 1763 } 1764 1765 $info = $stream; 1766 $info['streams'] = $streams; 1767 1768 return $info; 1769 } 1770 1771 } -
new file wp-includes/ID3/module.audio-video.quicktime.php
diff --git wp-includes/ID3/module.audio-video.quicktime.php wp-includes/ID3/module.audio-video.quicktime.php new file mode 100644 index 0000000..db63dab
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.audio-video.quicktime.php // 11 // module for analyzing Quicktime and MP3-in-MP4 files // 12 // dependencies: module.audio.mp3.php // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); 17 18 class getid3_quicktime extends getid3_handler 19 { 20 21 public $ReturnAtomData = true; 22 public $ParseAllPossibleAtoms = false; 23 24 public function Analyze() { 25 $info = &$this->getid3->info; 26 27 $info['fileformat'] = 'quicktime'; 28 $info['quicktime']['hinting'] = false; 29 $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present 30 31 fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); 32 33 $offset = 0; 34 $atomcounter = 0; 35 36 while ($offset < $info['avdataend']) { 37 if (!getid3_lib::intValueSupported($offset)) { 38 $info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; 39 break; 40 } 41 fseek($this->getid3->fp, $offset, SEEK_SET); 42 $AtomHeader = fread($this->getid3->fp, 8); 43 44 $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4)); 45 $atomname = substr($AtomHeader, 4, 4); 46 47 // 64-bit MOV patch by jlegateØktnc*com 48 if ($atomsize == 1) { 49 $atomsize = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 8)); 50 } 51 52 $info['quicktime'][$atomname]['name'] = $atomname; 53 $info['quicktime'][$atomname]['size'] = $atomsize; 54 $info['quicktime'][$atomname]['offset'] = $offset; 55 56 if (($offset + $atomsize) > $info['avdataend']) { 57 $info['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)'; 58 return false; 59 } 60 61 if ($atomsize == 0) { 62 // Furthermore, for historical reasons the list of atoms is optionally 63 // terminated by a 32-bit integer set to 0. If you are writing a program 64 // to read user data atoms, you should allow for the terminating 0. 65 break; 66 } 67 switch ($atomname) { 68 case 'mdat': // Media DATa atom 69 // 'mdat' contains the actual data for the audio/video 70 if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { 71 72 $info['avdataoffset'] = $info['quicktime'][$atomname]['offset'] + 8; 73 $OldAVDataEnd = $info['avdataend']; 74 $info['avdataend'] = $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; 75 76 $getid3_temp = new getID3(); 77 $getid3_temp->openfile($this->getid3->filename); 78 $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; 79 $getid3_temp->info['avdataend'] = $info['avdataend']; 80 $getid3_mp3 = new getid3_mp3($getid3_temp); 81 if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode(fread($this->getid3->fp, 4)))) { 82 $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); 83 if (!empty($getid3_temp->info['warning'])) { 84 foreach ($getid3_temp->info['warning'] as $value) { 85 $info['warning'][] = $value; 86 } 87 } 88 if (!empty($getid3_temp->info['mpeg'])) { 89 $info['mpeg'] = $getid3_temp->info['mpeg']; 90 if (isset($info['mpeg']['audio'])) { 91 $info['audio']['dataformat'] = 'mp3'; 92 $info['audio']['codec'] = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3'))); 93 $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; 94 $info['audio']['channels'] = $info['mpeg']['audio']['channels']; 95 $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; 96 $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); 97 $info['bitrate'] = $info['audio']['bitrate']; 98 } 99 } 100 } 101 unset($getid3_mp3, $getid3_temp); 102 $info['avdataend'] = $OldAVDataEnd; 103 unset($OldAVDataEnd); 104 105 } 106 break; 107 108 case 'free': // FREE space atom 109 case 'skip': // SKIP atom 110 case 'wide': // 64-bit expansion placeholder atom 111 // 'free', 'skip' and 'wide' are just padding, contains no useful data at all 112 break; 113 114 default: 115 $atomHierarchy = array(); 116 $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($this->getid3->fp, $atomsize), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); 117 break; 118 } 119 120 $offset += $atomsize; 121 $atomcounter++; 122 } 123 124 if (!empty($info['avdataend_tmp'])) { 125 // this value is assigned to a temp value and then erased because 126 // otherwise any atoms beyond the 'mdat' atom would not get parsed 127 $info['avdataend'] = $info['avdataend_tmp']; 128 unset($info['avdataend_tmp']); 129 } 130 131 if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { 132 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; 133 } 134 if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) { 135 $info['audio']['bitrate'] = $info['bitrate']; 136 } 137 if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) { 138 foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) { 139 $samples_per_second = $samples_count / $info['playtime_seconds']; 140 if ($samples_per_second > 240) { 141 // has to be audio samples 142 } else { 143 $info['video']['frame_rate'] = $samples_per_second; 144 break; 145 } 146 } 147 } 148 if (($info['audio']['dataformat'] == 'mp4') && empty($info['video']['resolution_x'])) { 149 $info['fileformat'] = 'mp4'; 150 $info['mime_type'] = 'audio/mp4'; 151 unset($info['video']['dataformat']); 152 } 153 154 if (!$this->ReturnAtomData) { 155 unset($info['quicktime']['moov']); 156 } 157 158 if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) { 159 $info['audio']['dataformat'] = 'quicktime'; 160 } 161 if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) { 162 $info['video']['dataformat'] = 'quicktime'; 163 } 164 165 return true; 166 } 167 168 public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { 169 // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm 170 171 $info = &$this->getid3->info; 172 173 $atom_parent = array_pop($atomHierarchy); 174 array_push($atomHierarchy, $atomname); 175 $atom_structure['hierarchy'] = implode(' ', $atomHierarchy); 176 $atom_structure['name'] = $atomname; 177 $atom_structure['size'] = $atomsize; 178 $atom_structure['offset'] = $baseoffset; 179 //echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8)).'<br>'; 180 //echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8), false).'<br><br>'; 181 switch ($atomname) { 182 case 'moov': // MOVie container atom 183 case 'trak': // TRAcK container atom 184 case 'clip': // CLIPping container atom 185 case 'matt': // track MATTe container atom 186 case 'edts': // EDiTS container atom 187 case 'tref': // Track REFerence container atom 188 case 'mdia': // MeDIA container atom 189 case 'minf': // Media INFormation container atom 190 case 'dinf': // Data INFormation container atom 191 case 'udta': // User DaTA container atom 192 case 'cmov': // Compressed MOVie container atom 193 case 'rmra': // Reference Movie Record Atom 194 case 'rmda': // Reference Movie Descriptor Atom 195 case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) 196 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 197 break; 198 199 case 'ilst': // Item LiST container atom 200 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 201 202 // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted 203 $allnumericnames = true; 204 foreach ($atom_structure['subatoms'] as $subatomarray) { 205 if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { 206 $allnumericnames = false; 207 break; 208 } 209 } 210 if ($allnumericnames) { 211 $newData = array(); 212 foreach ($atom_structure['subatoms'] as $subatomarray) { 213 foreach ($subatomarray['subatoms'] as $newData_subatomarray) { 214 unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); 215 $newData[$subatomarray['name']] = $newData_subatomarray; 216 break; 217 } 218 } 219 $atom_structure['data'] = $newData; 220 unset($atom_structure['subatoms']); 221 } 222 break; 223 224 case "\x00\x00\x00\x01": 225 case "\x00\x00\x00\x02": 226 case "\x00\x00\x00\x03": 227 case "\x00\x00\x00\x04": 228 case "\x00\x00\x00\x05": 229 $atomname = getid3_lib::BigEndian2Int($atomname); 230 $atom_structure['name'] = $atomname; 231 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 232 break; 233 234 case 'stbl': // Sample TaBLe container atom 235 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 236 $isVideo = false; 237 $framerate = 0; 238 $framecount = 0; 239 foreach ($atom_structure['subatoms'] as $key => $value_array) { 240 if (isset($value_array['sample_description_table'])) { 241 foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { 242 if (isset($value_array2['data_format'])) { 243 switch ($value_array2['data_format']) { 244 case 'avc1': 245 case 'mp4v': 246 // video data 247 $isVideo = true; 248 break; 249 case 'mp4a': 250 // audio data 251 break; 252 } 253 } 254 } 255 } elseif (isset($value_array['time_to_sample_table'])) { 256 foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) { 257 if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) { 258 $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); 259 $framecount = $value_array2['sample_count']; 260 } 261 } 262 } 263 } 264 if ($isVideo && $framerate) { 265 $info['quicktime']['video']['frame_rate'] = $framerate; 266 $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; 267 } 268 if ($isVideo && $framecount) { 269 $info['quicktime']['video']['frame_count'] = $framecount; 270 } 271 break; 272 273 274 case 'aART': // Album ARTist 275 case 'catg': // CaTeGory 276 case 'covr': // COVeR artwork 277 case 'cpil': // ComPILation 278 case 'cprt': // CoPyRighT 279 case 'desc': // DESCription 280 case 'disk': // DISK number 281 case 'egid': // Episode Global ID 282 case 'gnre': // GeNRE 283 case 'keyw': // KEYWord 284 case 'ldes': 285 case 'pcst': // PodCaST 286 case 'pgap': // GAPless Playback 287 case 'purd': // PURchase Date 288 case 'purl': // Podcast URL 289 case 'rati': 290 case 'rndu': 291 case 'rpdu': 292 case 'rtng': // RaTiNG 293 case 'stik': 294 case 'tmpo': // TeMPO (BPM) 295 case 'trkn': // TRacK Number 296 case 'tves': // TV EpiSode 297 case 'tvnn': // TV Network Name 298 case 'tvsh': // TV SHow Name 299 case 'tvsn': // TV SeasoN 300 case 'akID': // iTunes store account type 301 case 'apID': 302 case 'atID': 303 case 'cmID': 304 case 'cnID': 305 case 'geID': 306 case 'plID': 307 case 'sfID': // iTunes store country 308 case '©alb': // ALBum 309 case '©art': // ARTist 310 case '©ART': 311 case '©aut': 312 case '©cmt': // CoMmenT 313 case '©com': // COMposer 314 case '©cpy': 315 case '©day': // content created year 316 case '©dir': 317 case '©ed1': 318 case '©ed2': 319 case '©ed3': 320 case '©ed4': 321 case '©ed5': 322 case '©ed6': 323 case '©ed7': 324 case '©ed8': 325 case '©ed9': 326 case '©enc': 327 case '©fmt': 328 case '©gen': // GENre 329 case '©grp': // GRouPing 330 case '©hst': 331 case '©inf': 332 case '©lyr': // LYRics 333 case '©mak': 334 case '©mod': 335 case '©nam': // full NAMe 336 case '©ope': 337 case '©PRD': 338 case '©prd': 339 case '©prf': 340 case '©req': 341 case '©src': 342 case '©swr': 343 case '©too': // encoder 344 case '©trk': // TRacK 345 case '©url': 346 case '©wrn': 347 case '©wrt': // WRiTer 348 case '----': // itunes specific 349 if ($atom_parent == 'udta') { 350 // User data atom handler 351 $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 352 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); 353 $atom_structure['data'] = substr($atom_data, 4); 354 355 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 356 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 357 $info['comments']['language'][] = $atom_structure['language']; 358 } 359 } else { 360 // Apple item list box atom handler 361 $atomoffset = 0; 362 if (substr($atom_data, 2, 2) == "\x10\xB5") { 363 // not sure what it means, but observed on iPhone4 data. 364 // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data 365 while ($atomoffset < strlen($atom_data)) { 366 $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); 367 $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); 368 $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); 369 switch ($boxsmalltype) { 370 case "\x10\xB5": 371 $atom_structure['data'] = $boxsmalldata; 372 break; 373 default: 374 $info['warning'][] = 'Unknown QuickTime smallbox type: "'.getid3_lib::PrintHexBytes($boxsmalltype).'" at offset '.$baseoffset; 375 $atom_structure['data'] = $atom_data; 376 break; 377 } 378 $atomoffset += (4 + $boxsmallsize); 379 } 380 } else { 381 while ($atomoffset < strlen($atom_data)) { 382 $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); 383 $boxtype = substr($atom_data, $atomoffset + 4, 4); 384 $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); 385 if ($boxsize <= 1) { 386 $info['warning'][] = 'Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.$atomname.'" at offset: '.($atom_structure['offset'] + $atomoffset); 387 $atom_structure['data'] = null; 388 $atomoffset = strlen($atom_data); 389 break; 390 } 391 $atomoffset += $boxsize; 392 393 switch ($boxtype) { 394 case 'mean': 395 case 'name': 396 $atom_structure[$boxtype] = substr($boxdata, 4); 397 break; 398 399 case 'data': 400 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); 401 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); 402 switch ($atom_structure['flags_raw']) { 403 case 0: // data flag 404 case 21: // tmpo/cpil flag 405 switch ($atomname) { 406 case 'cpil': 407 case 'pcst': 408 case 'pgap': 409 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 410 break; 411 412 case 'tmpo': 413 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); 414 break; 415 416 case 'disk': 417 case 'trkn': 418 $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); 419 $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); 420 $atom_structure['data'] = empty($num) ? '' : $num; 421 $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; 422 break; 423 424 case 'gnre': 425 $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 426 $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); 427 break; 428 429 case 'rtng': 430 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 431 $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); 432 break; 433 434 case 'stik': 435 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 436 $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); 437 break; 438 439 case 'sfID': 440 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 441 $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); 442 break; 443 444 case 'egid': 445 case 'purl': 446 $atom_structure['data'] = substr($boxdata, 8); 447 break; 448 449 default: 450 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 451 } 452 break; 453 454 case 1: // text flag 455 case 13: // image flag 456 default: 457 $atom_structure['data'] = substr($boxdata, 8); 458 break; 459 460 } 461 break; 462 463 default: 464 $info['warning'][] = 'Unknown QuickTime box type: "'.getid3_lib::PrintHexBytes($boxtype).'" at offset '.$baseoffset; 465 $atom_structure['data'] = $atom_data; 466 467 } 468 } 469 } 470 } 471 $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); 472 break; 473 474 475 case 'play': // auto-PLAY atom 476 $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 477 478 $info['quicktime']['autoplay'] = $atom_structure['autoplay']; 479 break; 480 481 482 case 'WLOC': // Window LOCation atom 483 $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 484 $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); 485 break; 486 487 488 case 'LOOP': // LOOPing atom 489 case 'SelO': // play SELection Only atom 490 case 'AllF': // play ALL Frames atom 491 $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); 492 break; 493 494 495 case 'name': // 496 case 'MCPS': // Media Cleaner PRo 497 case '@PRM': // adobe PReMiere version 498 case '@PRQ': // adobe PRemiere Quicktime version 499 $atom_structure['data'] = $atom_data; 500 break; 501 502 503 case 'cmvd': // Compressed MooV Data atom 504 // Code by ubergeekØubergeek*tv based on information from 505 // http://developer.apple.com/quicktime/icefloe/dispatch012.html 506 $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 507 508 $CompressedFileData = substr($atom_data, 4); 509 if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { 510 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); 511 } else { 512 $info['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atom_structure['offset']; 513 } 514 break; 515 516 517 case 'dcom': // Data COMpression atom 518 $atom_structure['compression_id'] = $atom_data; 519 $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); 520 break; 521 522 523 case 'rdrf': // Reference movie Data ReFerence atom 524 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 525 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 526 $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001); 527 528 $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); 529 $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 530 switch ($atom_structure['reference_type_name']) { 531 case 'url ': 532 $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); 533 break; 534 535 case 'alis': 536 $atom_structure['file_alias'] = substr($atom_data, 12); 537 break; 538 539 case 'rsrc': 540 $atom_structure['resource_alias'] = substr($atom_data, 12); 541 break; 542 543 default: 544 $atom_structure['data'] = substr($atom_data, 12); 545 break; 546 } 547 break; 548 549 550 case 'rmqu': // Reference Movie QUality atom 551 $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); 552 break; 553 554 555 case 'rmcs': // Reference Movie Cpu Speed atom 556 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 557 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 558 $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 559 break; 560 561 562 case 'rmvc': // Reference Movie Version Check atom 563 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 564 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 565 $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4); 566 $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 567 $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 568 $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); 569 break; 570 571 572 case 'rmcd': // Reference Movie Component check atom 573 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 574 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 575 $atom_structure['component_type'] = substr($atom_data, 4, 4); 576 $atom_structure['component_subtype'] = substr($atom_data, 8, 4); 577 $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); 578 $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 579 $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 580 $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); 581 break; 582 583 584 case 'rmdr': // Reference Movie Data Rate atom 585 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 586 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 587 $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 588 589 $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; 590 break; 591 592 593 case 'rmla': // Reference Movie Language Atom 594 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 595 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 596 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 597 598 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 599 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 600 $info['comments']['language'][] = $atom_structure['language']; 601 } 602 break; 603 604 605 case 'rmla': // Reference Movie Language Atom 606 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 607 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 608 $atom_structure['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 609 break; 610 611 612 case 'ptv ': // Print To Video - defines a movie's full screen mode 613 // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm 614 $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 615 $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 616 $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 617 $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); 618 $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); 619 620 $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; 621 $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; 622 623 $ptv_lookup[0] = 'normal'; 624 $ptv_lookup[1] = 'double'; 625 $ptv_lookup[2] = 'half'; 626 $ptv_lookup[3] = 'full'; 627 $ptv_lookup[4] = 'current'; 628 if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { 629 $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; 630 } else { 631 $info['warning'][] = 'unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'; 632 } 633 break; 634 635 636 case 'stsd': // Sample Table Sample Description atom 637 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 638 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 639 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 640 $stsdEntriesDataOffset = 8; 641 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 642 $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); 643 $stsdEntriesDataOffset += 4; 644 $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); 645 $stsdEntriesDataOffset += 4; 646 $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); 647 $stsdEntriesDataOffset += 6; 648 $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); 649 $stsdEntriesDataOffset += 2; 650 $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2)); 651 $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); 652 653 $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); 654 $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); 655 $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); 656 657 switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { 658 659 case "\x00\x00\x00\x00": 660 // audio atom 661 $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); 662 $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); 663 $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); 664 $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); 665 $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); 666 667 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 668 case 'avc1': 669 case 'mp4v': 670 $info['fileformat'] = 'mp4'; 671 $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; 672 //$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] does not fully support MPEG-4 audio/video streams'; // 2011-02-18: why am I warning about this again? What's not supported? 673 break; 674 675 case 'qtvr': 676 $info['video']['dataformat'] = 'quicktimevr'; 677 break; 678 679 case 'mp4a': 680 default: 681 $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); 682 $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate']; 683 $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels']; 684 $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; 685 $info['audio']['codec'] = $info['quicktime']['audio']['codec']; 686 $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; 687 $info['audio']['channels'] = $info['quicktime']['audio']['channels']; 688 $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; 689 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 690 case 'raw ': // PCM 691 case 'alac': // Apple Lossless Audio Codec 692 $info['audio']['lossless'] = true; 693 break; 694 default: 695 $info['audio']['lossless'] = false; 696 break; 697 } 698 break; 699 } 700 break; 701 702 default: 703 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 704 case 'mp4s': 705 $info['fileformat'] = 'mp4'; 706 break; 707 708 default: 709 // video atom 710 $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); 711 $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); 712 $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); 713 $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); 714 $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); 715 $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); 716 $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); 717 $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); 718 $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); 719 $atom_structure['sample_description_table'][$i]['video_encoder_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 35, $atom_structure['sample_description_table'][$i]['video_encoder_name_len']); 720 $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); 721 $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); 722 723 $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); 724 $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); 725 726 if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { 727 $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; 728 $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); 729 $info['quicktime']['video']['codec'] = (($atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']); 730 $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; 731 $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; 732 733 $info['video']['codec'] = $info['quicktime']['video']['codec']; 734 $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; 735 } 736 $info['video']['lossless'] = false; 737 $info['video']['pixel_aspect_ratio'] = (float) 1; 738 break; 739 } 740 break; 741 } 742 switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { 743 case 'mp4a': 744 $info['audio']['dataformat'] = 'mp4'; 745 $info['quicktime']['audio']['codec'] = 'mp4'; 746 break; 747 748 case '3ivx': 749 case '3iv1': 750 case '3iv2': 751 $info['video']['dataformat'] = '3ivx'; 752 break; 753 754 case 'xvid': 755 $info['video']['dataformat'] = 'xvid'; 756 break; 757 758 case 'mp4v': 759 $info['video']['dataformat'] = 'mpeg4'; 760 break; 761 762 case 'divx': 763 case 'div1': 764 case 'div2': 765 case 'div3': 766 case 'div4': 767 case 'div5': 768 case 'div6': 769 $info['video']['dataformat'] = 'divx'; 770 break; 771 772 default: 773 // do nothing 774 break; 775 } 776 unset($atom_structure['sample_description_table'][$i]['data']); 777 } 778 break; 779 780 781 case 'stts': // Sample Table Time-to-Sample atom 782 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 783 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 784 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 785 $sttsEntriesDataOffset = 8; 786 //$FrameRateCalculatorArray = array(); 787 $frames_count = 0; 788 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 789 $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); 790 $sttsEntriesDataOffset += 4; 791 $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); 792 $sttsEntriesDataOffset += 4; 793 794 $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; 795 796 // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM 797 //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { 798 // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; 799 // if ($stts_new_framerate <= 60) { 800 // // some atoms have durations of "1" giving a very large framerate, which probably is not right 801 // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); 802 // } 803 //} 804 // 805 //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; 806 } 807 $info['quicktime']['stts_framecount'][] = $frames_count; 808 //$sttsFramesTotal = 0; 809 //$sttsSecondsTotal = 0; 810 //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { 811 // if (($frames_per_second > 60) || ($frames_per_second < 1)) { 812 // // not video FPS information, probably audio information 813 // $sttsFramesTotal = 0; 814 // $sttsSecondsTotal = 0; 815 // break; 816 // } 817 // $sttsFramesTotal += $frame_count; 818 // $sttsSecondsTotal += $frame_count / $frames_per_second; 819 //} 820 //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { 821 // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { 822 // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; 823 // } 824 //} 825 break; 826 827 828 case 'stss': // Sample Table Sync Sample (key frames) atom 829 if ($ParseAllPossibleAtoms) { 830 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 831 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 832 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 833 $stssEntriesDataOffset = 8; 834 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 835 $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); 836 $stssEntriesDataOffset += 4; 837 } 838 } 839 break; 840 841 842 case 'stsc': // Sample Table Sample-to-Chunk atom 843 if ($ParseAllPossibleAtoms) { 844 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 845 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 846 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 847 $stscEntriesDataOffset = 8; 848 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 849 $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 850 $stscEntriesDataOffset += 4; 851 $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 852 $stscEntriesDataOffset += 4; 853 $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 854 $stscEntriesDataOffset += 4; 855 } 856 } 857 break; 858 859 860 case 'stsz': // Sample Table SiZe atom 861 if ($ParseAllPossibleAtoms) { 862 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 863 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 864 $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 865 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 866 $stszEntriesDataOffset = 12; 867 if ($atom_structure['sample_size'] == 0) { 868 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 869 $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4)); 870 $stszEntriesDataOffset += 4; 871 } 872 } 873 } 874 break; 875 876 877 case 'stco': // Sample Table Chunk Offset atom 878 if ($ParseAllPossibleAtoms) { 879 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 880 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 881 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 882 $stcoEntriesDataOffset = 8; 883 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 884 $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); 885 $stcoEntriesDataOffset += 4; 886 } 887 } 888 break; 889 890 891 case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) 892 if ($ParseAllPossibleAtoms) { 893 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 894 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 895 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 896 $stcoEntriesDataOffset = 8; 897 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 898 $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); 899 $stcoEntriesDataOffset += 8; 900 } 901 } 902 break; 903 904 905 case 'dref': // Data REFerence atom 906 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 907 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 908 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 909 $drefDataOffset = 8; 910 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 911 $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); 912 $drefDataOffset += 4; 913 $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); 914 $drefDataOffset += 4; 915 $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); 916 $drefDataOffset += 1; 917 $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 918 $drefDataOffset += 3; 919 $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); 920 $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); 921 922 $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001); 923 } 924 break; 925 926 927 case 'gmin': // base Media INformation atom 928 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 929 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 930 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 931 $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 932 $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); 933 $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); 934 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); 935 $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); 936 break; 937 938 939 case 'smhd': // Sound Media information HeaDer atom 940 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 941 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 942 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 943 $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 944 break; 945 946 947 case 'vmhd': // Video Media information HeaDer atom 948 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 949 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 950 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 951 $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 952 $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); 953 $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); 954 955 $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001); 956 break; 957 958 959 case 'hdlr': // HanDLeR reference atom 960 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 961 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 962 $atom_structure['component_type'] = substr($atom_data, 4, 4); 963 $atom_structure['component_subtype'] = substr($atom_data, 8, 4); 964 $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); 965 $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 966 $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 967 $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24)); 968 969 if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) { 970 $info['video']['dataformat'] = 'quicktimevr'; 971 } 972 break; 973 974 975 case 'mdhd': // MeDia HeaDer atom 976 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 977 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 978 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 979 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 980 $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 981 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 982 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); 983 $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); 984 985 if ($atom_structure['time_scale'] == 0) { 986 $info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero'; 987 return false; 988 } 989 $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); 990 991 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 992 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 993 $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; 994 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 995 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 996 $info['comments']['language'][] = $atom_structure['language']; 997 } 998 break; 999 1000 1001 case 'pnot': // Preview atom 1002 $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" 1003 $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 1004 $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' 1005 $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 1006 1007 $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); 1008 break; 1009 1010 1011 case 'crgn': // Clipping ReGioN atom 1012 $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, 1013 $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields 1014 $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. 1015 break; 1016 1017 1018 case 'load': // track LOAD settings atom 1019 $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1020 $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1021 $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1022 $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1023 1024 $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020); 1025 $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100); 1026 break; 1027 1028 1029 case 'tmcd': // TiMe CoDe atom 1030 case 'chap': // CHAPter list atom 1031 case 'sync': // SYNChronization atom 1032 case 'scpt': // tranSCriPT atom 1033 case 'ssrc': // non-primary SouRCe atom 1034 for ($i = 0; $i < (strlen($atom_data) % 4); $i++) { 1035 $atom_structure['track_id'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $i * 4, 4)); 1036 } 1037 break; 1038 1039 1040 case 'elst': // Edit LiST atom 1041 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1042 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1043 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1044 for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) { 1045 $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4)); 1046 $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4)); 1047 $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4)); 1048 } 1049 break; 1050 1051 1052 case 'kmat': // compressed MATte atom 1053 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1054 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1055 $atom_structure['matte_data_raw'] = substr($atom_data, 4); 1056 break; 1057 1058 1059 case 'ctab': // Color TABle atom 1060 $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 1061 $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 1062 $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; 1063 for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { 1064 $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2)); 1065 $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2)); 1066 $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2)); 1067 $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2)); 1068 } 1069 break; 1070 1071 1072 case 'mvhd': // MoVie HeaDer atom 1073 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1074 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1075 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1076 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1077 $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1078 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1079 $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); 1080 $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); 1081 $atom_structure['reserved'] = substr($atom_data, 26, 10); 1082 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); 1083 $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); 1084 $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); 1085 $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); 1086 $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); 1087 $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); 1088 $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); 1089 $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); 1090 $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); 1091 $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); 1092 $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); 1093 $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); 1094 $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); 1095 $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); 1096 $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); 1097 $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); 1098 1099 if ($atom_structure['time_scale'] == 0) { 1100 $info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero'; 1101 return false; 1102 } 1103 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1104 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1105 $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); 1106 $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; 1107 $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; 1108 break; 1109 1110 1111 case 'tkhd': // TracK HeaDer atom 1112 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1113 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1114 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1115 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1116 $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1117 $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1118 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 1119 $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); 1120 $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); 1121 $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); 1122 $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); 1123 $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); 1124 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); 1125 $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); 1126 $atom_structure['matrix_u'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); 1127 $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); 1128 $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); 1129 $atom_structure['matrix_v'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); 1130 $atom_structure['matrix_x'] = getid3_lib::FixedPoint2_30(substr($atom_data, 64, 4)); 1131 $atom_structure['matrix_y'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); 1132 $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); 1133 $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); 1134 $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); 1135 1136 $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001); 1137 $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002); 1138 $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004); 1139 $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008); 1140 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1141 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1142 1143 if ($atom_structure['flags']['enabled'] == 1) { 1144 if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { 1145 $info['video']['resolution_x'] = $atom_structure['width']; 1146 $info['video']['resolution_y'] = $atom_structure['height']; 1147 } 1148 $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); 1149 $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); 1150 $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; 1151 $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; 1152 } else { 1153 // see: http://www.getid3.org/phpBB3/viewtopic.php?t=1295 1154 //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } 1155 //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } 1156 //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } 1157 } 1158 break; 1159 1160 1161 case 'iods': // Initial Object DeScriptor atom 1162 // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h 1163 // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html 1164 $offset = 0; 1165 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1166 $offset += 1; 1167 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); 1168 $offset += 3; 1169 $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1170 $offset += 1; 1171 $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); 1172 //$offset already adjusted by quicktime_read_mp4_descr_length() 1173 $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); 1174 $offset += 2; 1175 $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1176 $offset += 1; 1177 $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1178 $offset += 1; 1179 $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1180 $offset += 1; 1181 $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1182 $offset += 1; 1183 $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1184 $offset += 1; 1185 1186 $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields 1187 for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { 1188 $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1189 $offset += 1; 1190 $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); 1191 //$offset already adjusted by quicktime_read_mp4_descr_length() 1192 $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); 1193 $offset += 4; 1194 } 1195 1196 $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); 1197 $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); 1198 break; 1199 1200 case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) 1201 $atom_structure['signature'] = substr($atom_data, 0, 4); 1202 $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1203 $atom_structure['fourcc'] = substr($atom_data, 8, 4); 1204 break; 1205 1206 case 'mdat': // Media DATa atom 1207 case 'free': // FREE space atom 1208 case 'skip': // SKIP atom 1209 case 'wide': // 64-bit expansion placeholder atom 1210 // 'mdat' data is too big to deal with, contains no useful metadata 1211 // 'free', 'skip' and 'wide' are just padding, contains no useful data at all 1212 1213 // When writing QuickTime files, it is sometimes necessary to update an atom's size. 1214 // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom 1215 // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime 1216 // puts an 8-byte placeholder atom before any atoms it may have to update the size of. 1217 // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the 1218 // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. 1219 // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). 1220 break; 1221 1222 1223 case 'nsav': // NoSAVe atom 1224 // http://developer.apple.com/technotes/tn/tn2038.html 1225 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1226 break; 1227 1228 case 'ctyp': // Controller TYPe atom (seen on QTVR) 1229 // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt 1230 // some controller names are: 1231 // 0x00 + 'std' for linear movie 1232 // 'none' for no controls 1233 $atom_structure['ctyp'] = substr($atom_data, 0, 4); 1234 $info['quicktime']['controller'] = $atom_structure['ctyp']; 1235 switch ($atom_structure['ctyp']) { 1236 case 'qtvr': 1237 $info['video']['dataformat'] = 'quicktimevr'; 1238 break; 1239 } 1240 break; 1241 1242 case 'pano': // PANOrama track (seen on QTVR) 1243 $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1244 break; 1245 1246 case 'hint': // HINT track 1247 case 'hinf': // 1248 case 'hinv': // 1249 case 'hnti': // 1250 $info['quicktime']['hinting'] = true; 1251 break; 1252 1253 case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) 1254 for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) { 1255 $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); 1256 } 1257 break; 1258 1259 1260 // Observed-but-not-handled atom types are just listed here to prevent warnings being generated 1261 case 'FXTC': // Something to do with Adobe After Effects (?) 1262 case 'PrmA': 1263 case 'code': 1264 case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html 1265 case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html 1266 // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838] 1267 // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html 1268 // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html 1269 case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1270 case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1271 case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1272 case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1273 //$atom_structure['data'] = $atom_data; 1274 break; 1275 1276 case '©xyz': // GPS latitude+longitude+altitude 1277 $atom_structure['data'] = $atom_data; 1278 if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { 1279 @list($all, $latitude, $longitude, $altitude) = $matches; 1280 $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); 1281 $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); 1282 if (!empty($altitude)) { 1283 $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); 1284 } 1285 } else { 1286 $info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'; 1287 } 1288 break; 1289 1290 case 'NCDT': 1291 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1292 // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 1293 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); 1294 break; 1295 case 'NCTH': // Nikon Camera THumbnail image 1296 case 'NCVW': // Nikon Camera preVieW image 1297 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1298 if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) { 1299 $atom_structure['data'] = $atom_data; 1300 $atom_structure['image_mime'] = 'image/jpeg'; 1301 $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image')); 1302 $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']); 1303 } 1304 break; 1305 case 'NCHD': // MakerNoteVersion 1306 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1307 $atom_structure['data'] = $atom_data; 1308 break; 1309 case 'NCTG': // NikonTags 1310 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG 1311 $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); 1312 break; 1313 case 'NCDB': // NikonTags 1314 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1315 $atom_structure['data'] = $atom_data; 1316 break; 1317 1318 case "\x00\x00\x00\x00": 1319 case 'meta': // METAdata atom 1320 // some kind of metacontainer, may contain a big data dump such as: 1321 // mdta keys mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst 0 1322 data DEApple 0 (data DE2011-05-11T17:54:04+0200 2 *data DE+52.4936+013.3897+040.247/ 1 1323 data DE4.3.1 data DEiPhone 4 1324 // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt 1325 1326 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1327 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1328 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1329 //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1330 break; 1331 1332 case 'data': // metaDATA atom 1333 // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data 1334 $atom_structure['language'] = substr($atom_data, 4 + 0, 2); 1335 $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); 1336 $atom_structure['data'] = substr($atom_data, 4 + 4); 1337 break; 1338 1339 default: 1340 $info['warning'][] = 'Unknown QuickTime atom type: "'.$atomname.'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset; 1341 $atom_structure['data'] = $atom_data; 1342 break; 1343 } 1344 array_pop($atomHierarchy); 1345 return $atom_structure; 1346 } 1347 1348 public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { 1349 //echo 'QuicktimeParseContainerAtom('.substr($atom_data, 4, 4).') @ '.$baseoffset.'<br><br>'; 1350 $atom_structure = false; 1351 $subatomoffset = 0; 1352 $subatomcounter = 0; 1353 if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) { 1354 return false; 1355 } 1356 while ($subatomoffset < strlen($atom_data)) { 1357 $subatomsize = getid3_lib::BigEndian2Int(substr($atom_data, $subatomoffset + 0, 4)); 1358 $subatomname = substr($atom_data, $subatomoffset + 4, 4); 1359 $subatomdata = substr($atom_data, $subatomoffset + 8, $subatomsize - 8); 1360 if ($subatomsize == 0) { 1361 // Furthermore, for historical reasons the list of atoms is optionally 1362 // terminated by a 32-bit integer set to 0. If you are writing a program 1363 // to read user data atoms, you should allow for the terminating 0. 1364 return $atom_structure; 1365 } 1366 1367 $atom_structure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); 1368 1369 $subatomoffset += $subatomsize; 1370 $subatomcounter++; 1371 } 1372 return $atom_structure; 1373 } 1374 1375 1376 public function quicktime_read_mp4_descr_length($data, &$offset) { 1377 // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html 1378 $num_bytes = 0; 1379 $length = 0; 1380 do { 1381 $b = ord(substr($data, $offset++, 1)); 1382 $length = ($length << 7) | ($b & 0x7F); 1383 } while (($b & 0x80) && ($num_bytes++ < 4)); 1384 return $length; 1385 } 1386 1387 1388 public function QuicktimeLanguageLookup($languageid) { 1389 static $QuicktimeLanguageLookup = array(); 1390 if (empty($QuicktimeLanguageLookup)) { 1391 $QuicktimeLanguageLookup[0] = 'English'; 1392 $QuicktimeLanguageLookup[1] = 'French'; 1393 $QuicktimeLanguageLookup[2] = 'German'; 1394 $QuicktimeLanguageLookup[3] = 'Italian'; 1395 $QuicktimeLanguageLookup[4] = 'Dutch'; 1396 $QuicktimeLanguageLookup[5] = 'Swedish'; 1397 $QuicktimeLanguageLookup[6] = 'Spanish'; 1398 $QuicktimeLanguageLookup[7] = 'Danish'; 1399 $QuicktimeLanguageLookup[8] = 'Portuguese'; 1400 $QuicktimeLanguageLookup[9] = 'Norwegian'; 1401 $QuicktimeLanguageLookup[10] = 'Hebrew'; 1402 $QuicktimeLanguageLookup[11] = 'Japanese'; 1403 $QuicktimeLanguageLookup[12] = 'Arabic'; 1404 $QuicktimeLanguageLookup[13] = 'Finnish'; 1405 $QuicktimeLanguageLookup[14] = 'Greek'; 1406 $QuicktimeLanguageLookup[15] = 'Icelandic'; 1407 $QuicktimeLanguageLookup[16] = 'Maltese'; 1408 $QuicktimeLanguageLookup[17] = 'Turkish'; 1409 $QuicktimeLanguageLookup[18] = 'Croatian'; 1410 $QuicktimeLanguageLookup[19] = 'Chinese (Traditional)'; 1411 $QuicktimeLanguageLookup[20] = 'Urdu'; 1412 $QuicktimeLanguageLookup[21] = 'Hindi'; 1413 $QuicktimeLanguageLookup[22] = 'Thai'; 1414 $QuicktimeLanguageLookup[23] = 'Korean'; 1415 $QuicktimeLanguageLookup[24] = 'Lithuanian'; 1416 $QuicktimeLanguageLookup[25] = 'Polish'; 1417 $QuicktimeLanguageLookup[26] = 'Hungarian'; 1418 $QuicktimeLanguageLookup[27] = 'Estonian'; 1419 $QuicktimeLanguageLookup[28] = 'Lettish'; 1420 $QuicktimeLanguageLookup[28] = 'Latvian'; 1421 $QuicktimeLanguageLookup[29] = 'Saamisk'; 1422 $QuicktimeLanguageLookup[29] = 'Lappish'; 1423 $QuicktimeLanguageLookup[30] = 'Faeroese'; 1424 $QuicktimeLanguageLookup[31] = 'Farsi'; 1425 $QuicktimeLanguageLookup[31] = 'Persian'; 1426 $QuicktimeLanguageLookup[32] = 'Russian'; 1427 $QuicktimeLanguageLookup[33] = 'Chinese (Simplified)'; 1428 $QuicktimeLanguageLookup[34] = 'Flemish'; 1429 $QuicktimeLanguageLookup[35] = 'Irish'; 1430 $QuicktimeLanguageLookup[36] = 'Albanian'; 1431 $QuicktimeLanguageLookup[37] = 'Romanian'; 1432 $QuicktimeLanguageLookup[38] = 'Czech'; 1433 $QuicktimeLanguageLookup[39] = 'Slovak'; 1434 $QuicktimeLanguageLookup[40] = 'Slovenian'; 1435 $QuicktimeLanguageLookup[41] = 'Yiddish'; 1436 $QuicktimeLanguageLookup[42] = 'Serbian'; 1437 $QuicktimeLanguageLookup[43] = 'Macedonian'; 1438 $QuicktimeLanguageLookup[44] = 'Bulgarian'; 1439 $QuicktimeLanguageLookup[45] = 'Ukrainian'; 1440 $QuicktimeLanguageLookup[46] = 'Byelorussian'; 1441 $QuicktimeLanguageLookup[47] = 'Uzbek'; 1442 $QuicktimeLanguageLookup[48] = 'Kazakh'; 1443 $QuicktimeLanguageLookup[49] = 'Azerbaijani'; 1444 $QuicktimeLanguageLookup[50] = 'AzerbaijanAr'; 1445 $QuicktimeLanguageLookup[51] = 'Armenian'; 1446 $QuicktimeLanguageLookup[52] = 'Georgian'; 1447 $QuicktimeLanguageLookup[53] = 'Moldavian'; 1448 $QuicktimeLanguageLookup[54] = 'Kirghiz'; 1449 $QuicktimeLanguageLookup[55] = 'Tajiki'; 1450 $QuicktimeLanguageLookup[56] = 'Turkmen'; 1451 $QuicktimeLanguageLookup[57] = 'Mongolian'; 1452 $QuicktimeLanguageLookup[58] = 'MongolianCyr'; 1453 $QuicktimeLanguageLookup[59] = 'Pashto'; 1454 $QuicktimeLanguageLookup[60] = 'Kurdish'; 1455 $QuicktimeLanguageLookup[61] = 'Kashmiri'; 1456 $QuicktimeLanguageLookup[62] = 'Sindhi'; 1457 $QuicktimeLanguageLookup[63] = 'Tibetan'; 1458 $QuicktimeLanguageLookup[64] = 'Nepali'; 1459 $QuicktimeLanguageLookup[65] = 'Sanskrit'; 1460 $QuicktimeLanguageLookup[66] = 'Marathi'; 1461 $QuicktimeLanguageLookup[67] = 'Bengali'; 1462 $QuicktimeLanguageLookup[68] = 'Assamese'; 1463 $QuicktimeLanguageLookup[69] = 'Gujarati'; 1464 $QuicktimeLanguageLookup[70] = 'Punjabi'; 1465 $QuicktimeLanguageLookup[71] = 'Oriya'; 1466 $QuicktimeLanguageLookup[72] = 'Malayalam'; 1467 $QuicktimeLanguageLookup[73] = 'Kannada'; 1468 $QuicktimeLanguageLookup[74] = 'Tamil'; 1469 $QuicktimeLanguageLookup[75] = 'Telugu'; 1470 $QuicktimeLanguageLookup[76] = 'Sinhalese'; 1471 $QuicktimeLanguageLookup[77] = 'Burmese'; 1472 $QuicktimeLanguageLookup[78] = 'Khmer'; 1473 $QuicktimeLanguageLookup[79] = 'Lao'; 1474 $QuicktimeLanguageLookup[80] = 'Vietnamese'; 1475 $QuicktimeLanguageLookup[81] = 'Indonesian'; 1476 $QuicktimeLanguageLookup[82] = 'Tagalog'; 1477 $QuicktimeLanguageLookup[83] = 'MalayRoman'; 1478 $QuicktimeLanguageLookup[84] = 'MalayArabic'; 1479 $QuicktimeLanguageLookup[85] = 'Amharic'; 1480 $QuicktimeLanguageLookup[86] = 'Tigrinya'; 1481 $QuicktimeLanguageLookup[87] = 'Galla'; 1482 $QuicktimeLanguageLookup[87] = 'Oromo'; 1483 $QuicktimeLanguageLookup[88] = 'Somali'; 1484 $QuicktimeLanguageLookup[89] = 'Swahili'; 1485 $QuicktimeLanguageLookup[90] = 'Ruanda'; 1486 $QuicktimeLanguageLookup[91] = 'Rundi'; 1487 $QuicktimeLanguageLookup[92] = 'Chewa'; 1488 $QuicktimeLanguageLookup[93] = 'Malagasy'; 1489 $QuicktimeLanguageLookup[94] = 'Esperanto'; 1490 $QuicktimeLanguageLookup[128] = 'Welsh'; 1491 $QuicktimeLanguageLookup[129] = 'Basque'; 1492 $QuicktimeLanguageLookup[130] = 'Catalan'; 1493 $QuicktimeLanguageLookup[131] = 'Latin'; 1494 $QuicktimeLanguageLookup[132] = 'Quechua'; 1495 $QuicktimeLanguageLookup[133] = 'Guarani'; 1496 $QuicktimeLanguageLookup[134] = 'Aymara'; 1497 $QuicktimeLanguageLookup[135] = 'Tatar'; 1498 $QuicktimeLanguageLookup[136] = 'Uighur'; 1499 $QuicktimeLanguageLookup[137] = 'Dzongkha'; 1500 $QuicktimeLanguageLookup[138] = 'JavaneseRom'; 1501 } 1502 return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid'); 1503 } 1504 1505 public function QuicktimeVideoCodecLookup($codecid) { 1506 static $QuicktimeVideoCodecLookup = array(); 1507 if (empty($QuicktimeVideoCodecLookup)) { 1508 $QuicktimeVideoCodecLookup['.SGI'] = 'SGI'; 1509 $QuicktimeVideoCodecLookup['3IV1'] = '3ivx MPEG-4 v1'; 1510 $QuicktimeVideoCodecLookup['3IV2'] = '3ivx MPEG-4 v2'; 1511 $QuicktimeVideoCodecLookup['3IVX'] = '3ivx MPEG-4'; 1512 $QuicktimeVideoCodecLookup['8BPS'] = 'Planar RGB'; 1513 $QuicktimeVideoCodecLookup['avc1'] = 'H.264/MPEG-4 AVC'; 1514 $QuicktimeVideoCodecLookup['avr '] = 'AVR-JPEG'; 1515 $QuicktimeVideoCodecLookup['b16g'] = '16Gray'; 1516 $QuicktimeVideoCodecLookup['b32a'] = '32AlphaGray'; 1517 $QuicktimeVideoCodecLookup['b48r'] = '48RGB'; 1518 $QuicktimeVideoCodecLookup['b64a'] = '64ARGB'; 1519 $QuicktimeVideoCodecLookup['base'] = 'Base'; 1520 $QuicktimeVideoCodecLookup['clou'] = 'Cloud'; 1521 $QuicktimeVideoCodecLookup['cmyk'] = 'CMYK'; 1522 $QuicktimeVideoCodecLookup['cvid'] = 'Cinepak'; 1523 $QuicktimeVideoCodecLookup['dmb1'] = 'OpenDML JPEG'; 1524 $QuicktimeVideoCodecLookup['dvc '] = 'DVC-NTSC'; 1525 $QuicktimeVideoCodecLookup['dvcp'] = 'DVC-PAL'; 1526 $QuicktimeVideoCodecLookup['dvpn'] = 'DVCPro-NTSC'; 1527 $QuicktimeVideoCodecLookup['dvpp'] = 'DVCPro-PAL'; 1528 $QuicktimeVideoCodecLookup['fire'] = 'Fire'; 1529 $QuicktimeVideoCodecLookup['flic'] = 'FLC'; 1530 $QuicktimeVideoCodecLookup['gif '] = 'GIF'; 1531 $QuicktimeVideoCodecLookup['h261'] = 'H261'; 1532 $QuicktimeVideoCodecLookup['h263'] = 'H263'; 1533 $QuicktimeVideoCodecLookup['IV41'] = 'Indeo4'; 1534 $QuicktimeVideoCodecLookup['jpeg'] = 'JPEG'; 1535 $QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD'; 1536 $QuicktimeVideoCodecLookup['mjpa'] = 'Motion JPEG-A'; 1537 $QuicktimeVideoCodecLookup['mjpb'] = 'Motion JPEG-B'; 1538 $QuicktimeVideoCodecLookup['msvc'] = 'Microsoft Video1'; 1539 $QuicktimeVideoCodecLookup['myuv'] = 'MPEG YUV420'; 1540 $QuicktimeVideoCodecLookup['path'] = 'Vector'; 1541 $QuicktimeVideoCodecLookup['png '] = 'PNG'; 1542 $QuicktimeVideoCodecLookup['PNTG'] = 'MacPaint'; 1543 $QuicktimeVideoCodecLookup['qdgx'] = 'QuickDrawGX'; 1544 $QuicktimeVideoCodecLookup['qdrw'] = 'QuickDraw'; 1545 $QuicktimeVideoCodecLookup['raw '] = 'RAW'; 1546 $QuicktimeVideoCodecLookup['ripl'] = 'WaterRipple'; 1547 $QuicktimeVideoCodecLookup['rpza'] = 'Video'; 1548 $QuicktimeVideoCodecLookup['smc '] = 'Graphics'; 1549 $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 1'; 1550 $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 3'; 1551 $QuicktimeVideoCodecLookup['syv9'] = 'Sorenson YUV9'; 1552 $QuicktimeVideoCodecLookup['tga '] = 'Targa'; 1553 $QuicktimeVideoCodecLookup['tiff'] = 'TIFF'; 1554 $QuicktimeVideoCodecLookup['WRAW'] = 'Windows RAW'; 1555 $QuicktimeVideoCodecLookup['WRLE'] = 'BMP'; 1556 $QuicktimeVideoCodecLookup['y420'] = 'YUV420'; 1557 $QuicktimeVideoCodecLookup['yuv2'] = 'ComponentVideo'; 1558 $QuicktimeVideoCodecLookup['yuvs'] = 'ComponentVideoUnsigned'; 1559 $QuicktimeVideoCodecLookup['yuvu'] = 'ComponentVideoSigned'; 1560 } 1561 return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : ''); 1562 } 1563 1564 public function QuicktimeAudioCodecLookup($codecid) { 1565 static $QuicktimeAudioCodecLookup = array(); 1566 if (empty($QuicktimeAudioCodecLookup)) { 1567 $QuicktimeAudioCodecLookup['.mp3'] = 'Fraunhofer MPEG Layer-III alias'; 1568 $QuicktimeAudioCodecLookup['aac '] = 'ISO/IEC 14496-3 AAC'; 1569 $QuicktimeAudioCodecLookup['agsm'] = 'Apple GSM 10:1'; 1570 $QuicktimeAudioCodecLookup['alac'] = 'Apple Lossless Audio Codec'; 1571 $QuicktimeAudioCodecLookup['alaw'] = 'A-law 2:1'; 1572 $QuicktimeAudioCodecLookup['conv'] = 'Sample Format'; 1573 $QuicktimeAudioCodecLookup['dvca'] = 'DV'; 1574 $QuicktimeAudioCodecLookup['dvi '] = 'DV 4:1'; 1575 $QuicktimeAudioCodecLookup['eqal'] = 'Frequency Equalizer'; 1576 $QuicktimeAudioCodecLookup['fl32'] = '32-bit Floating Point'; 1577 $QuicktimeAudioCodecLookup['fl64'] = '64-bit Floating Point'; 1578 $QuicktimeAudioCodecLookup['ima4'] = 'Interactive Multimedia Association 4:1'; 1579 $QuicktimeAudioCodecLookup['in24'] = '24-bit Integer'; 1580 $QuicktimeAudioCodecLookup['in32'] = '32-bit Integer'; 1581 $QuicktimeAudioCodecLookup['lpc '] = 'LPC 23:1'; 1582 $QuicktimeAudioCodecLookup['MAC3'] = 'Macintosh Audio Compression/Expansion (MACE) 3:1'; 1583 $QuicktimeAudioCodecLookup['MAC6'] = 'Macintosh Audio Compression/Expansion (MACE) 6:1'; 1584 $QuicktimeAudioCodecLookup['mixb'] = '8-bit Mixer'; 1585 $QuicktimeAudioCodecLookup['mixw'] = '16-bit Mixer'; 1586 $QuicktimeAudioCodecLookup['mp4a'] = 'ISO/IEC 14496-3 AAC'; 1587 $QuicktimeAudioCodecLookup['MS'."\x00\x02"] = 'Microsoft ADPCM'; 1588 $QuicktimeAudioCodecLookup['MS'."\x00\x11"] = 'DV IMA'; 1589 $QuicktimeAudioCodecLookup['MS'."\x00\x55"] = 'Fraunhofer MPEG Layer III'; 1590 $QuicktimeAudioCodecLookup['NONE'] = 'No Encoding'; 1591 $QuicktimeAudioCodecLookup['Qclp'] = 'Qualcomm PureVoice'; 1592 $QuicktimeAudioCodecLookup['QDM2'] = 'QDesign Music 2'; 1593 $QuicktimeAudioCodecLookup['QDMC'] = 'QDesign Music 1'; 1594 $QuicktimeAudioCodecLookup['ratb'] = '8-bit Rate'; 1595 $QuicktimeAudioCodecLookup['ratw'] = '16-bit Rate'; 1596 $QuicktimeAudioCodecLookup['raw '] = 'raw PCM'; 1597 $QuicktimeAudioCodecLookup['sour'] = 'Sound Source'; 1598 $QuicktimeAudioCodecLookup['sowt'] = 'signed/two\'s complement (Little Endian)'; 1599 $QuicktimeAudioCodecLookup['str1'] = 'Iomega MPEG layer II'; 1600 $QuicktimeAudioCodecLookup['str2'] = 'Iomega MPEG *layer II'; 1601 $QuicktimeAudioCodecLookup['str3'] = 'Iomega MPEG **layer II'; 1602 $QuicktimeAudioCodecLookup['str4'] = 'Iomega MPEG ***layer II'; 1603 $QuicktimeAudioCodecLookup['twos'] = 'signed/two\'s complement (Big Endian)'; 1604 $QuicktimeAudioCodecLookup['ulaw'] = 'mu-law 2:1'; 1605 } 1606 return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : ''); 1607 } 1608 1609 public function QuicktimeDCOMLookup($compressionid) { 1610 static $QuicktimeDCOMLookup = array(); 1611 if (empty($QuicktimeDCOMLookup)) { 1612 $QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate'; 1613 $QuicktimeDCOMLookup['adec'] = 'Apple Compression'; 1614 } 1615 return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : ''); 1616 } 1617 1618 public function QuicktimeColorNameLookup($colordepthid) { 1619 static $QuicktimeColorNameLookup = array(); 1620 if (empty($QuicktimeColorNameLookup)) { 1621 $QuicktimeColorNameLookup[1] = '2-color (monochrome)'; 1622 $QuicktimeColorNameLookup[2] = '4-color'; 1623 $QuicktimeColorNameLookup[4] = '16-color'; 1624 $QuicktimeColorNameLookup[8] = '256-color'; 1625 $QuicktimeColorNameLookup[16] = 'thousands (16-bit color)'; 1626 $QuicktimeColorNameLookup[24] = 'millions (24-bit color)'; 1627 $QuicktimeColorNameLookup[32] = 'millions+ (32-bit color)'; 1628 $QuicktimeColorNameLookup[33] = 'black & white'; 1629 $QuicktimeColorNameLookup[34] = '4-gray'; 1630 $QuicktimeColorNameLookup[36] = '16-gray'; 1631 $QuicktimeColorNameLookup[40] = '256-gray'; 1632 } 1633 return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid'); 1634 } 1635 1636 public function QuicktimeSTIKLookup($stik) { 1637 static $QuicktimeSTIKLookup = array(); 1638 if (empty($QuicktimeSTIKLookup)) { 1639 $QuicktimeSTIKLookup[0] = 'Movie'; 1640 $QuicktimeSTIKLookup[1] = 'Normal'; 1641 $QuicktimeSTIKLookup[2] = 'Audiobook'; 1642 $QuicktimeSTIKLookup[5] = 'Whacked Bookmark'; 1643 $QuicktimeSTIKLookup[6] = 'Music Video'; 1644 $QuicktimeSTIKLookup[9] = 'Short Film'; 1645 $QuicktimeSTIKLookup[10] = 'TV Show'; 1646 $QuicktimeSTIKLookup[11] = 'Booklet'; 1647 $QuicktimeSTIKLookup[14] = 'Ringtone'; 1648 $QuicktimeSTIKLookup[21] = 'Podcast'; 1649 } 1650 return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid'); 1651 } 1652 1653 public function QuicktimeIODSaudioProfileName($audio_profile_id) { 1654 static $QuicktimeIODSaudioProfileNameLookup = array(); 1655 if (empty($QuicktimeIODSaudioProfileNameLookup)) { 1656 $QuicktimeIODSaudioProfileNameLookup = array( 1657 0x00 => 'ISO Reserved (0x00)', 1658 0x01 => 'Main Audio Profile @ Level 1', 1659 0x02 => 'Main Audio Profile @ Level 2', 1660 0x03 => 'Main Audio Profile @ Level 3', 1661 0x04 => 'Main Audio Profile @ Level 4', 1662 0x05 => 'Scalable Audio Profile @ Level 1', 1663 0x06 => 'Scalable Audio Profile @ Level 2', 1664 0x07 => 'Scalable Audio Profile @ Level 3', 1665 0x08 => 'Scalable Audio Profile @ Level 4', 1666 0x09 => 'Speech Audio Profile @ Level 1', 1667 0x0A => 'Speech Audio Profile @ Level 2', 1668 0x0B => 'Synthetic Audio Profile @ Level 1', 1669 0x0C => 'Synthetic Audio Profile @ Level 2', 1670 0x0D => 'Synthetic Audio Profile @ Level 3', 1671 0x0E => 'High Quality Audio Profile @ Level 1', 1672 0x0F => 'High Quality Audio Profile @ Level 2', 1673 0x10 => 'High Quality Audio Profile @ Level 3', 1674 0x11 => 'High Quality Audio Profile @ Level 4', 1675 0x12 => 'High Quality Audio Profile @ Level 5', 1676 0x13 => 'High Quality Audio Profile @ Level 6', 1677 0x14 => 'High Quality Audio Profile @ Level 7', 1678 0x15 => 'High Quality Audio Profile @ Level 8', 1679 0x16 => 'Low Delay Audio Profile @ Level 1', 1680 0x17 => 'Low Delay Audio Profile @ Level 2', 1681 0x18 => 'Low Delay Audio Profile @ Level 3', 1682 0x19 => 'Low Delay Audio Profile @ Level 4', 1683 0x1A => 'Low Delay Audio Profile @ Level 5', 1684 0x1B => 'Low Delay Audio Profile @ Level 6', 1685 0x1C => 'Low Delay Audio Profile @ Level 7', 1686 0x1D => 'Low Delay Audio Profile @ Level 8', 1687 0x1E => 'Natural Audio Profile @ Level 1', 1688 0x1F => 'Natural Audio Profile @ Level 2', 1689 0x20 => 'Natural Audio Profile @ Level 3', 1690 0x21 => 'Natural Audio Profile @ Level 4', 1691 0x22 => 'Mobile Audio Internetworking Profile @ Level 1', 1692 0x23 => 'Mobile Audio Internetworking Profile @ Level 2', 1693 0x24 => 'Mobile Audio Internetworking Profile @ Level 3', 1694 0x25 => 'Mobile Audio Internetworking Profile @ Level 4', 1695 0x26 => 'Mobile Audio Internetworking Profile @ Level 5', 1696 0x27 => 'Mobile Audio Internetworking Profile @ Level 6', 1697 0x28 => 'AAC Profile @ Level 1', 1698 0x29 => 'AAC Profile @ Level 2', 1699 0x2A => 'AAC Profile @ Level 4', 1700 0x2B => 'AAC Profile @ Level 5', 1701 0x2C => 'High Efficiency AAC Profile @ Level 2', 1702 0x2D => 'High Efficiency AAC Profile @ Level 3', 1703 0x2E => 'High Efficiency AAC Profile @ Level 4', 1704 0x2F => 'High Efficiency AAC Profile @ Level 5', 1705 0xFE => 'Not part of MPEG-4 audio profiles', 1706 0xFF => 'No audio capability required', 1707 ); 1708 } 1709 return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private'); 1710 } 1711 1712 1713 public function QuicktimeIODSvideoProfileName($video_profile_id) { 1714 static $QuicktimeIODSvideoProfileNameLookup = array(); 1715 if (empty($QuicktimeIODSvideoProfileNameLookup)) { 1716 $QuicktimeIODSvideoProfileNameLookup = array( 1717 0x00 => 'Reserved (0x00) Profile', 1718 0x01 => 'Simple Profile @ Level 1', 1719 0x02 => 'Simple Profile @ Level 2', 1720 0x03 => 'Simple Profile @ Level 3', 1721 0x08 => 'Simple Profile @ Level 0', 1722 0x10 => 'Simple Scalable Profile @ Level 0', 1723 0x11 => 'Simple Scalable Profile @ Level 1', 1724 0x12 => 'Simple Scalable Profile @ Level 2', 1725 0x15 => 'AVC/H264 Profile', 1726 0x21 => 'Core Profile @ Level 1', 1727 0x22 => 'Core Profile @ Level 2', 1728 0x32 => 'Main Profile @ Level 2', 1729 0x33 => 'Main Profile @ Level 3', 1730 0x34 => 'Main Profile @ Level 4', 1731 0x42 => 'N-bit Profile @ Level 2', 1732 0x51 => 'Scalable Texture Profile @ Level 1', 1733 0x61 => 'Simple Face Animation Profile @ Level 1', 1734 0x62 => 'Simple Face Animation Profile @ Level 2', 1735 0x63 => 'Simple FBA Profile @ Level 1', 1736 0x64 => 'Simple FBA Profile @ Level 2', 1737 0x71 => 'Basic Animated Texture Profile @ Level 1', 1738 0x72 => 'Basic Animated Texture Profile @ Level 2', 1739 0x81 => 'Hybrid Profile @ Level 1', 1740 0x82 => 'Hybrid Profile @ Level 2', 1741 0x91 => 'Advanced Real Time Simple Profile @ Level 1', 1742 0x92 => 'Advanced Real Time Simple Profile @ Level 2', 1743 0x93 => 'Advanced Real Time Simple Profile @ Level 3', 1744 0x94 => 'Advanced Real Time Simple Profile @ Level 4', 1745 0xA1 => 'Core Scalable Profile @ Level1', 1746 0xA2 => 'Core Scalable Profile @ Level2', 1747 0xA3 => 'Core Scalable Profile @ Level3', 1748 0xB1 => 'Advanced Coding Efficiency Profile @ Level 1', 1749 0xB2 => 'Advanced Coding Efficiency Profile @ Level 2', 1750 0xB3 => 'Advanced Coding Efficiency Profile @ Level 3', 1751 0xB4 => 'Advanced Coding Efficiency Profile @ Level 4', 1752 0xC1 => 'Advanced Core Profile @ Level 1', 1753 0xC2 => 'Advanced Core Profile @ Level 2', 1754 0xD1 => 'Advanced Scalable Texture @ Level1', 1755 0xD2 => 'Advanced Scalable Texture @ Level2', 1756 0xE1 => 'Simple Studio Profile @ Level 1', 1757 0xE2 => 'Simple Studio Profile @ Level 2', 1758 0xE3 => 'Simple Studio Profile @ Level 3', 1759 0xE4 => 'Simple Studio Profile @ Level 4', 1760 0xE5 => 'Core Studio Profile @ Level 1', 1761 0xE6 => 'Core Studio Profile @ Level 2', 1762 0xE7 => 'Core Studio Profile @ Level 3', 1763 0xE8 => 'Core Studio Profile @ Level 4', 1764 0xF0 => 'Advanced Simple Profile @ Level 0', 1765 0xF1 => 'Advanced Simple Profile @ Level 1', 1766 0xF2 => 'Advanced Simple Profile @ Level 2', 1767 0xF3 => 'Advanced Simple Profile @ Level 3', 1768 0xF4 => 'Advanced Simple Profile @ Level 4', 1769 0xF5 => 'Advanced Simple Profile @ Level 5', 1770 0xF7 => 'Advanced Simple Profile @ Level 3b', 1771 0xF8 => 'Fine Granularity Scalable Profile @ Level 0', 1772 0xF9 => 'Fine Granularity Scalable Profile @ Level 1', 1773 0xFA => 'Fine Granularity Scalable Profile @ Level 2', 1774 0xFB => 'Fine Granularity Scalable Profile @ Level 3', 1775 0xFC => 'Fine Granularity Scalable Profile @ Level 4', 1776 0xFD => 'Fine Granularity Scalable Profile @ Level 5', 1777 0xFE => 'Not part of MPEG-4 Visual profiles', 1778 0xFF => 'No visual capability required', 1779 ); 1780 } 1781 return (isset($QuicktimeIODSvideoProfileNameLookup[$video_profile_id]) ? $QuicktimeIODSvideoProfileNameLookup[$video_profile_id] : 'ISO Reserved Profile'); 1782 } 1783 1784 1785 public function QuicktimeContentRatingLookup($rtng) { 1786 static $QuicktimeContentRatingLookup = array(); 1787 if (empty($QuicktimeContentRatingLookup)) { 1788 $QuicktimeContentRatingLookup[0] = 'None'; 1789 $QuicktimeContentRatingLookup[2] = 'Clean'; 1790 $QuicktimeContentRatingLookup[4] = 'Explicit'; 1791 } 1792 return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid'); 1793 } 1794 1795 public function QuicktimeStoreAccountTypeLookup($akid) { 1796 static $QuicktimeStoreAccountTypeLookup = array(); 1797 if (empty($QuicktimeStoreAccountTypeLookup)) { 1798 $QuicktimeStoreAccountTypeLookup[0] = 'iTunes'; 1799 $QuicktimeStoreAccountTypeLookup[1] = 'AOL'; 1800 } 1801 return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid'); 1802 } 1803 1804 public function QuicktimeStoreFrontCodeLookup($sfid) { 1805 static $QuicktimeStoreFrontCodeLookup = array(); 1806 if (empty($QuicktimeStoreFrontCodeLookup)) { 1807 $QuicktimeStoreFrontCodeLookup[143460] = 'Australia'; 1808 $QuicktimeStoreFrontCodeLookup[143445] = 'Austria'; 1809 $QuicktimeStoreFrontCodeLookup[143446] = 'Belgium'; 1810 $QuicktimeStoreFrontCodeLookup[143455] = 'Canada'; 1811 $QuicktimeStoreFrontCodeLookup[143458] = 'Denmark'; 1812 $QuicktimeStoreFrontCodeLookup[143447] = 'Finland'; 1813 $QuicktimeStoreFrontCodeLookup[143442] = 'France'; 1814 $QuicktimeStoreFrontCodeLookup[143443] = 'Germany'; 1815 $QuicktimeStoreFrontCodeLookup[143448] = 'Greece'; 1816 $QuicktimeStoreFrontCodeLookup[143449] = 'Ireland'; 1817 $QuicktimeStoreFrontCodeLookup[143450] = 'Italy'; 1818 $QuicktimeStoreFrontCodeLookup[143462] = 'Japan'; 1819 $QuicktimeStoreFrontCodeLookup[143451] = 'Luxembourg'; 1820 $QuicktimeStoreFrontCodeLookup[143452] = 'Netherlands'; 1821 $QuicktimeStoreFrontCodeLookup[143461] = 'New Zealand'; 1822 $QuicktimeStoreFrontCodeLookup[143457] = 'Norway'; 1823 $QuicktimeStoreFrontCodeLookup[143453] = 'Portugal'; 1824 $QuicktimeStoreFrontCodeLookup[143454] = 'Spain'; 1825 $QuicktimeStoreFrontCodeLookup[143456] = 'Sweden'; 1826 $QuicktimeStoreFrontCodeLookup[143459] = 'Switzerland'; 1827 $QuicktimeStoreFrontCodeLookup[143444] = 'United Kingdom'; 1828 $QuicktimeStoreFrontCodeLookup[143441] = 'United States'; 1829 } 1830 return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid'); 1831 } 1832 1833 public function QuicktimeParseNikonNCTG($atom_data) { 1834 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG 1835 // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 1836 // Data is stored as records of: 1837 // * 4 bytes record type 1838 // * 2 bytes size of data field type: 1839 // 0x0001 = flag (size field *= 1-byte) 1840 // 0x0002 = char (size field *= 1-byte) 1841 // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB 1842 // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD 1843 // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together 1844 // 0x0007 = bytes (size field *= 1-byte), values are stored as ?????? 1845 // 0x0008 = ????? (size field *= 2-byte), values are stored as ?????? 1846 // * 2 bytes data size field 1847 // * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15") 1848 // all integers are stored BigEndian 1849 1850 $NCTGtagName = array( 1851 0x00000001 => 'Make', 1852 0x00000002 => 'Model', 1853 0x00000003 => 'Software', 1854 0x00000011 => 'CreateDate', 1855 0x00000012 => 'DateTimeOriginal', 1856 0x00000013 => 'FrameCount', 1857 0x00000016 => 'FrameRate', 1858 0x00000022 => 'FrameWidth', 1859 0x00000023 => 'FrameHeight', 1860 0x00000032 => 'AudioChannels', 1861 0x00000033 => 'AudioBitsPerSample', 1862 0x00000034 => 'AudioSampleRate', 1863 0x02000001 => 'MakerNoteVersion', 1864 0x02000005 => 'WhiteBalance', 1865 0x0200000b => 'WhiteBalanceFineTune', 1866 0x0200001e => 'ColorSpace', 1867 0x02000023 => 'PictureControlData', 1868 0x02000024 => 'WorldTime', 1869 0x02000032 => 'UnknownInfo', 1870 0x02000083 => 'LensType', 1871 0x02000084 => 'Lens', 1872 ); 1873 1874 $offset = 0; 1875 $datalength = strlen($atom_data); 1876 $parsed = array(); 1877 while ($offset < $datalength) { 1878 //echo getid3_lib::PrintHexBytes(substr($atom_data, $offset, 4)).'<br>'; 1879 $record_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4; 1880 $data_size_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; 1881 $data_size = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; 1882 switch ($data_size_type) { 1883 case 0x0001: // 0x0001 = flag (size field *= 1-byte) 1884 $data = getid3_lib::BigEndian2Int(substr($atom_data, $offset, $data_size * 1)); 1885 $offset += ($data_size * 1); 1886 break; 1887 case 0x0002: // 0x0002 = char (size field *= 1-byte) 1888 $data = substr($atom_data, $offset, $data_size * 1); 1889 $offset += ($data_size * 1); 1890 $data = rtrim($data, "\x00"); 1891 break; 1892 case 0x0003: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB 1893 $data = ''; 1894 for ($i = $data_size - 1; $i >= 0; $i--) { 1895 $data .= substr($atom_data, $offset + ($i * 2), 2); 1896 } 1897 $data = getid3_lib::BigEndian2Int($data); 1898 $offset += ($data_size * 2); 1899 break; 1900 case 0x0004: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD 1901 $data = ''; 1902 for ($i = $data_size - 1; $i >= 0; $i--) { 1903 $data .= substr($atom_data, $offset + ($i * 4), 4); 1904 } 1905 $data = getid3_lib::BigEndian2Int($data); 1906 $offset += ($data_size * 4); 1907 break; 1908 case 0x0005: // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together 1909 $data = array(); 1910 for ($i = 0; $i < $data_size; $i++) { 1911 $numerator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 0, 4)); 1912 $denomninator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 4, 4)); 1913 if ($denomninator == 0) { 1914 $data[$i] = false; 1915 } else { 1916 $data[$i] = (double) $numerator / $denomninator; 1917 } 1918 } 1919 $offset += (8 * $data_size); 1920 if (count($data) == 1) { 1921 $data = $data[0]; 1922 } 1923 break; 1924 case 0x0007: // 0x0007 = bytes (size field *= 1-byte), values are stored as ?????? 1925 $data = substr($atom_data, $offset, $data_size * 1); 1926 $offset += ($data_size * 1); 1927 break; 1928 case 0x0008: // 0x0008 = ????? (size field *= 2-byte), values are stored as ?????? 1929 $data = substr($atom_data, $offset, $data_size * 2); 1930 $offset += ($data_size * 2); 1931 break; 1932 default: 1933 echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br>'; 1934 break 2; 1935 } 1936 1937 switch ($record_type) { 1938 case 0x00000011: // CreateDate 1939 case 0x00000012: // DateTimeOriginal 1940 $data = strtotime($data); 1941 break; 1942 case 0x0200001e: // ColorSpace 1943 switch ($data) { 1944 case 1: 1945 $data = 'sRGB'; 1946 break; 1947 case 2: 1948 $data = 'Adobe RGB'; 1949 break; 1950 } 1951 break; 1952 case 0x02000023: // PictureControlData 1953 $PictureControlAdjust = array(0=>'default', 1=>'quick', 2=>'full'); 1954 $FilterEffect = array(0x80=>'off', 0x81=>'yellow', 0x82=>'orange', 0x83=>'red', 0x84=>'green', 0xff=>'n/a'); 1955 $ToningEffect = array(0x80=>'b&w', 0x81=>'sepia', 0x82=>'cyanotype', 0x83=>'red', 0x84=>'yellow', 0x85=>'green', 0x86=>'blue-green', 0x87=>'blue', 0x88=>'purple-blue', 0x89=>'red-purple', 0xff=>'n/a'); 1956 $data = array( 1957 'PictureControlVersion' => substr($data, 0, 4), 1958 'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"), 1959 'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"), 1960 //'?' => substr($data, 44, 4), 1961 'PictureControlAdjust' => $PictureControlAdjust[ord(substr($data, 48, 1))], 1962 'PictureControlQuickAdjust' => ord(substr($data, 49, 1)), 1963 'Sharpness' => ord(substr($data, 50, 1)), 1964 'Contrast' => ord(substr($data, 51, 1)), 1965 'Brightness' => ord(substr($data, 52, 1)), 1966 'Saturation' => ord(substr($data, 53, 1)), 1967 'HueAdjustment' => ord(substr($data, 54, 1)), 1968 'FilterEffect' => $FilterEffect[ord(substr($data, 55, 1))], 1969 'ToningEffect' => $ToningEffect[ord(substr($data, 56, 1))], 1970 'ToningSaturation' => ord(substr($data, 57, 1)), 1971 ); 1972 break; 1973 case 0x02000024: // WorldTime 1974 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#WorldTime 1975 // timezone is stored as offset from GMT in minutes 1976 $timezone = getid3_lib::BigEndian2Int(substr($data, 0, 2)); 1977 if ($timezone & 0x8000) { 1978 $timezone = 0 - (0x10000 - $timezone); 1979 } 1980 $timezone /= 60; 1981 1982 $dst = (bool) getid3_lib::BigEndian2Int(substr($data, 2, 1)); 1983 switch (getid3_lib::BigEndian2Int(substr($data, 3, 1))) { 1984 case 2: 1985 $datedisplayformat = 'D/M/Y'; break; 1986 case 1: 1987 $datedisplayformat = 'M/D/Y'; break; 1988 case 0: 1989 default: 1990 $datedisplayformat = 'Y/M/D'; break; 1991 } 1992 1993 $data = array('timezone'=>floatval($timezone), 'dst'=>$dst, 'display'=>$datedisplayformat); 1994 break; 1995 case 0x02000083: // LensType 1996 $data = array( 1997 //'_' => $data, 1998 'mf' => (bool) ($data & 0x01), 1999 'd' => (bool) ($data & 0x02), 2000 'g' => (bool) ($data & 0x04), 2001 'vr' => (bool) ($data & 0x08), 2002 ); 2003 break; 2004 } 2005 $tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x'.str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT)); 2006 $parsed[$tag_name] = $data; 2007 } 2008 return $parsed; 2009 } 2010 2011 2012 public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') { 2013 static $handyatomtranslatorarray = array(); 2014 if (empty($handyatomtranslatorarray)) { 2015 $handyatomtranslatorarray['©cpy'] = 'copyright'; 2016 $handyatomtranslatorarray['©day'] = 'creation_date'; // iTunes 4.0 2017 $handyatomtranslatorarray['©dir'] = 'director'; 2018 $handyatomtranslatorarray['©ed1'] = 'edit1'; 2019 $handyatomtranslatorarray['©ed2'] = 'edit2'; 2020 $handyatomtranslatorarray['©ed3'] = 'edit3'; 2021 $handyatomtranslatorarray['©ed4'] = 'edit4'; 2022 $handyatomtranslatorarray['©ed5'] = 'edit5'; 2023 $handyatomtranslatorarray['©ed6'] = 'edit6'; 2024 $handyatomtranslatorarray['©ed7'] = 'edit7'; 2025 $handyatomtranslatorarray['©ed8'] = 'edit8'; 2026 $handyatomtranslatorarray['©ed9'] = 'edit9'; 2027 $handyatomtranslatorarray['©fmt'] = 'format'; 2028 $handyatomtranslatorarray['©inf'] = 'information'; 2029 $handyatomtranslatorarray['©prd'] = 'producer'; 2030 $handyatomtranslatorarray['©prf'] = 'performers'; 2031 $handyatomtranslatorarray['©req'] = 'system_requirements'; 2032 $handyatomtranslatorarray['©src'] = 'source_credit'; 2033 $handyatomtranslatorarray['©wrt'] = 'writer'; 2034 2035 // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt 2036 $handyatomtranslatorarray['©nam'] = 'title'; // iTunes 4.0 2037 $handyatomtranslatorarray['©cmt'] = 'comment'; // iTunes 4.0 2038 $handyatomtranslatorarray['©wrn'] = 'warning'; 2039 $handyatomtranslatorarray['©hst'] = 'host_computer'; 2040 $handyatomtranslatorarray['©mak'] = 'make'; 2041 $handyatomtranslatorarray['©mod'] = 'model'; 2042 $handyatomtranslatorarray['©PRD'] = 'product'; 2043 $handyatomtranslatorarray['©swr'] = 'software'; 2044 $handyatomtranslatorarray['©aut'] = 'author'; 2045 $handyatomtranslatorarray['©ART'] = 'artist'; 2046 $handyatomtranslatorarray['©trk'] = 'track'; 2047 $handyatomtranslatorarray['©alb'] = 'album'; // iTunes 4.0 2048 $handyatomtranslatorarray['©com'] = 'comment'; 2049 $handyatomtranslatorarray['©gen'] = 'genre'; // iTunes 4.0 2050 $handyatomtranslatorarray['©ope'] = 'composer'; 2051 $handyatomtranslatorarray['©url'] = 'url'; 2052 $handyatomtranslatorarray['©enc'] = 'encoder'; 2053 2054 // http://atomicparsley.sourceforge.net/mpeg-4files.html 2055 $handyatomtranslatorarray['©art'] = 'artist'; // iTunes 4.0 2056 $handyatomtranslatorarray['aART'] = 'album_artist'; 2057 $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 2058 $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 2059 $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 2060 $handyatomtranslatorarray['©too'] = 'encoder'; // iTunes 4.0 2061 $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 2062 $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? 2063 $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 2064 $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 2065 $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 2066 $handyatomtranslatorarray['©grp'] = 'grouping'; // iTunes 4.2 2067 $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 2068 $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 2069 $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 2070 $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 2071 $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 2072 $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 2073 $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 2074 $handyatomtranslatorarray['©lyr'] = 'lyrics'; // iTunes 5.0 2075 $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0 2076 $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0 2077 $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0 2078 $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0 2079 $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 2080 $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 2081 2082 // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt 2083 2084 2085 2086 // boxnames: 2087 /* 2088 $handyatomtranslatorarray['iTunSMPB'] = 'iTunSMPB'; 2089 $handyatomtranslatorarray['iTunNORM'] = 'iTunNORM'; 2090 $handyatomtranslatorarray['Encoding Params'] = 'Encoding Params'; 2091 $handyatomtranslatorarray['replaygain_track_gain'] = 'replaygain_track_gain'; 2092 $handyatomtranslatorarray['replaygain_track_peak'] = 'replaygain_track_peak'; 2093 $handyatomtranslatorarray['replaygain_track_minmax'] = 'replaygain_track_minmax'; 2094 $handyatomtranslatorarray['MusicIP PUID'] = 'MusicIP PUID'; 2095 $handyatomtranslatorarray['MusicBrainz Artist Id'] = 'MusicBrainz Artist Id'; 2096 $handyatomtranslatorarray['MusicBrainz Album Id'] = 'MusicBrainz Album Id'; 2097 $handyatomtranslatorarray['MusicBrainz Album Artist Id'] = 'MusicBrainz Album Artist Id'; 2098 $handyatomtranslatorarray['MusicBrainz Track Id'] = 'MusicBrainz Track Id'; 2099 $handyatomtranslatorarray['MusicBrainz Disc Id'] = 'MusicBrainz Disc Id'; 2100 2101 // http://age.hobba.nl/audio/tag_frame_reference.html 2102 $handyatomtranslatorarray['PLAY_COUNTER'] = 'play_counter'; // Foobar2000 - http://www.getid3.org/phpBB3/viewtopic.php?t=1355 2103 $handyatomtranslatorarray['MEDIATYPE'] = 'mediatype'; // Foobar2000 - http://www.getid3.org/phpBB3/viewtopic.php?t=1355 2104 */ 2105 } 2106 $info = &$this->getid3->info; 2107 $comment_key = ''; 2108 if ($boxname && ($boxname != $keyname)) { 2109 $comment_key = (isset($handyatomtranslatorarray[$boxname]) ? $handyatomtranslatorarray[$boxname] : $boxname); 2110 } elseif (isset($handyatomtranslatorarray[$keyname])) { 2111 $comment_key = $handyatomtranslatorarray[$keyname]; 2112 } 2113 if ($comment_key) { 2114 if ($comment_key == 'picture') { 2115 if (!is_array($data)) { 2116 $image_mime = ''; 2117 if (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $data)) { 2118 $image_mime = 'image/png'; 2119 } elseif (preg_match('#^\xFF\xD8\xFF#', $data)) { 2120 $image_mime = 'image/jpeg'; 2121 } elseif (preg_match('#^GIF#', $data)) { 2122 $image_mime = 'image/gif'; 2123 } elseif (preg_match('#^BM#', $data)) { 2124 $image_mime = 'image/bmp'; 2125 } 2126 $data = array('data'=>$data, 'image_mime'=>$image_mime); 2127 } 2128 } 2129 $info['quicktime']['comments'][$comment_key][] = $data; 2130 } 2131 return true; 2132 } 2133 2134 public function NoNullString($nullterminatedstring) { 2135 // remove the single null terminator on null terminated strings 2136 if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") { 2137 return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1); 2138 } 2139 return $nullterminatedstring; 2140 } 2141 2142 public function Pascal2String($pascalstring) { 2143 // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string 2144 return substr($pascalstring, 1); 2145 } -
new file wp-includes/ID3/module.audio-video.riff.php
+ +} diff --git wp-includes/ID3/module.audio-video.riff.php wp-includes/ID3/module.audio-video.riff.php new file mode 100644 index 0000000..8f43100
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.audio-video.riff.php // 11 // module for analyzing RIFF files // 12 // multiple formats supported by this module: // 13 // Wave, AVI, AIFF/AIFC, (MP3,AC3)/RIFF, Wavpack v3, 8SVX // 14 // dependencies: module.audio.mp3.php // 15 // module.audio.ac3.php // 16 // module.audio.dts.php // 17 // /// 18 ///////////////////////////////////////////////////////////////// 19 20 /** 21 * @todo Parse AC-3/DTS audio inside WAVE correctly 22 * @todo Rewrite RIFF parser totally 23 */ 24 25 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); 26 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true); 27 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, true); 28 29 class getid3_riff extends getid3_handler 30 { 31 32 public function Analyze() { 33 $info = &$this->getid3->info; 34 35 // initialize these values to an empty array, otherwise they default to NULL 36 // and you can't append array values to a NULL value 37 $info['riff'] = array('raw'=>array()); 38 39 // Shortcuts 40 $thisfile_riff = &$info['riff']; 41 $thisfile_riff_raw = &$thisfile_riff['raw']; 42 $thisfile_audio = &$info['audio']; 43 $thisfile_video = &$info['video']; 44 $thisfile_audio_dataformat = &$thisfile_audio['dataformat']; 45 $thisfile_riff_audio = &$thisfile_riff['audio']; 46 $thisfile_riff_video = &$thisfile_riff['video']; 47 48 $Original['avdataoffset'] = $info['avdataoffset']; 49 $Original['avdataend'] = $info['avdataend']; 50 51 $this->fseek($info['avdataoffset']); 52 $RIFFheader = $this->fread(12); 53 $offset = $this->ftell(); 54 $RIFFtype = substr($RIFFheader, 0, 4); 55 $RIFFsize = substr($RIFFheader, 4, 4); 56 $RIFFsubtype = substr($RIFFheader, 8, 4); 57 58 switch ($RIFFtype) { 59 60 case 'FORM': // AIFF, AIFC 61 $info['fileformat'] = 'aiff'; 62 $thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize); 63 $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4)); 64 break; 65 66 case 'RIFF': // AVI, WAV, etc 67 case 'SDSS': // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com) 68 case 'RMP3': // RMP3 is identical to RIFF, just renamed. Used by [unknown program] when creating RIFF-MP3s 69 $info['fileformat'] = 'riff'; 70 $thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize); 71 if ($RIFFsubtype == 'RMP3') { 72 // RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s 73 $RIFFsubtype = 'WAVE'; 74 } 75 $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4)); 76 if (($info['avdataend'] - $info['filesize']) == 1) { 77 // LiteWave appears to incorrectly *not* pad actual output file 78 // to nearest WORD boundary so may appear to be short by one 79 // byte, in which case - skip warning 80 $info['avdataend'] = $info['filesize']; 81 } 82 83 $nextRIFFoffset = $Original['avdataoffset'] + 8 + $thisfile_riff['header_size']; // 8 = "RIFF" + 32-bit offset 84 while ($nextRIFFoffset < min($info['filesize'], $info['avdataend'])) { 85 try { 86 $this->fseek($nextRIFFoffset); 87 } catch (getid3_exception $e) { 88 if ($e->getCode() == 10) { 89 //$this->warning('RIFF parser: '.$e->getMessage()); 90 $this->error('AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime may be wrong'); 91 $this->warning('[avdataend] value may be incorrect, multiple AVIX chunks may be present'); 92 break; 93 } else { 94 throw $e; 95 } 96 } 97 $nextRIFFheader = $this->fread(12); 98 if ($nextRIFFoffset == ($info['avdataend'] - 1)) { 99 if (substr($nextRIFFheader, 0, 1) == "\x00") { 100 // RIFF padded to WORD boundary, we're actually already at the end 101 break; 102 } 103 } 104 $nextRIFFheaderID = substr($nextRIFFheader, 0, 4); 105 $nextRIFFsize = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4)); 106 $nextRIFFtype = substr($nextRIFFheader, 8, 4); 107 $chunkdata = array(); 108 $chunkdata['offset'] = $nextRIFFoffset + 8; 109 $chunkdata['size'] = $nextRIFFsize; 110 $nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size']; 111 112 switch ($nextRIFFheaderID) { 113 114 case 'RIFF': 115 $chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $nextRIFFoffset); 116 117 if (!isset($thisfile_riff[$nextRIFFtype])) { 118 $thisfile_riff[$nextRIFFtype] = array(); 119 } 120 $thisfile_riff[$nextRIFFtype][] = $chunkdata; 121 break; 122 123 case 'JUNK': 124 // ignore 125 $thisfile_riff[$nextRIFFheaderID][] = $chunkdata; 126 break; 127 128 case 'IDVX': 129 $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunkdata['size'])); 130 break; 131 132 default: 133 if ($info['filesize'] == ($chunkdata['offset'] - 8 + 128)) { 134 $DIVXTAG = $nextRIFFheader.$this->fread(128 - 12); 135 if (substr($DIVXTAG, -7) == 'DIVXTAG') { 136 // DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file 137 $this->warning('Found wrongly-structured DIVXTAG at offset '.($this->ftell() - 128).', parsing anyway'); 138 $info['divxtag']['comments'] = self::ParseDIVXTAG($DIVXTAG); 139 break 2; 140 } 141 } 142 $this->warning('Expecting "RIFF|JUNK|IDVX" at '.$nextRIFFoffset.', found "'.$nextRIFFheaderID.'" ('.getid3_lib::PrintHexBytes($nextRIFFheaderID).') - skipping rest of file'); 143 break 2; 144 145 } 146 147 } 148 if ($RIFFsubtype == 'WAVE') { 149 $thisfile_riff_WAVE = &$thisfile_riff['WAVE']; 150 } 151 break; 152 153 default: 154 $this->error('Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead'); 155 unset($info['fileformat']); 156 return false; 157 } 158 159 $streamindex = 0; 160 switch ($RIFFsubtype) { 161 case 'WAVE': 162 if (empty($thisfile_audio['bitrate_mode'])) { 163 $thisfile_audio['bitrate_mode'] = 'cbr'; 164 } 165 if (empty($thisfile_audio_dataformat)) { 166 $thisfile_audio_dataformat = 'wav'; 167 } 168 169 if (isset($thisfile_riff_WAVE['data'][0]['offset'])) { 170 $info['avdataoffset'] = $thisfile_riff_WAVE['data'][0]['offset'] + 8; 171 $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff_WAVE['data'][0]['size']; 172 } 173 if (isset($thisfile_riff_WAVE['fmt '][0]['data'])) { 174 175 $thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']); 176 $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; 177 if (!isset($thisfile_riff_audio[$streamindex]['bitrate']) || ($thisfile_riff_audio[$streamindex]['bitrate'] == 0)) { 178 $info['error'][] = 'Corrupt RIFF file: bitrate_audio == zero'; 179 return false; 180 } 181 $thisfile_riff_raw['fmt '] = $thisfile_riff_audio[$streamindex]['raw']; 182 unset($thisfile_riff_audio[$streamindex]['raw']); 183 $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex]; 184 185 $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); 186 if (substr($thisfile_audio['codec'], 0, strlen('unknown: 0x')) == 'unknown: 0x') { 187 $info['warning'][] = 'Audio codec = '.$thisfile_audio['codec']; 188 } 189 $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; 190 191 if (empty($info['playtime_seconds'])) { // may already be set (e.g. DTS-WAV) 192 $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); 193 } 194 195 $thisfile_audio['lossless'] = false; 196 if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { 197 switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { 198 199 case 0x0001: // PCM 200 $thisfile_audio['lossless'] = true; 201 break; 202 203 case 0x2000: // AC-3 204 $thisfile_audio_dataformat = 'ac3'; 205 break; 206 207 default: 208 // do nothing 209 break; 210 211 } 212 } 213 $thisfile_audio['streams'][$streamindex]['wformattag'] = $thisfile_audio['wformattag']; 214 $thisfile_audio['streams'][$streamindex]['bitrate_mode'] = $thisfile_audio['bitrate_mode']; 215 $thisfile_audio['streams'][$streamindex]['lossless'] = $thisfile_audio['lossless']; 216 $thisfile_audio['streams'][$streamindex]['dataformat'] = $thisfile_audio_dataformat; 217 } 218 219 if (isset($thisfile_riff_WAVE['rgad'][0]['data'])) { 220 221 // shortcuts 222 $rgadData = &$thisfile_riff_WAVE['rgad'][0]['data']; 223 $thisfile_riff_raw['rgad'] = array('track'=>array(), 'album'=>array()); 224 $thisfile_riff_raw_rgad = &$thisfile_riff_raw['rgad']; 225 $thisfile_riff_raw_rgad_track = &$thisfile_riff_raw_rgad['track']; 226 $thisfile_riff_raw_rgad_album = &$thisfile_riff_raw_rgad['album']; 227 228 $thisfile_riff_raw_rgad['fPeakAmplitude'] = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4)); 229 $thisfile_riff_raw_rgad['nRadioRgAdjust'] = $this->EitherEndian2Int(substr($rgadData, 4, 2)); 230 $thisfile_riff_raw_rgad['nAudiophileRgAdjust'] = $this->EitherEndian2Int(substr($rgadData, 6, 2)); 231 232 $nRadioRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nRadioRgAdjust']), 16, '0', STR_PAD_LEFT); 233 $nAudiophileRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nAudiophileRgAdjust']), 16, '0', STR_PAD_LEFT); 234 $thisfile_riff_raw_rgad_track['name'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 0, 3)); 235 $thisfile_riff_raw_rgad_track['originator'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 3, 3)); 236 $thisfile_riff_raw_rgad_track['signbit'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 6, 1)); 237 $thisfile_riff_raw_rgad_track['adjustment'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 7, 9)); 238 $thisfile_riff_raw_rgad_album['name'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 0, 3)); 239 $thisfile_riff_raw_rgad_album['originator'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 3, 3)); 240 $thisfile_riff_raw_rgad_album['signbit'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 6, 1)); 241 $thisfile_riff_raw_rgad_album['adjustment'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 7, 9)); 242 243 $thisfile_riff['rgad']['peakamplitude'] = $thisfile_riff_raw_rgad['fPeakAmplitude']; 244 if (($thisfile_riff_raw_rgad_track['name'] != 0) && ($thisfile_riff_raw_rgad_track['originator'] != 0)) { 245 $thisfile_riff['rgad']['track']['name'] = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_track['name']); 246 $thisfile_riff['rgad']['track']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_track['originator']); 247 $thisfile_riff['rgad']['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_track['adjustment'], $thisfile_riff_raw_rgad_track['signbit']); 248 } 249 if (($thisfile_riff_raw_rgad_album['name'] != 0) && ($thisfile_riff_raw_rgad_album['originator'] != 0)) { 250 $thisfile_riff['rgad']['album']['name'] = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_album['name']); 251 $thisfile_riff['rgad']['album']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_album['originator']); 252 $thisfile_riff['rgad']['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_album['adjustment'], $thisfile_riff_raw_rgad_album['signbit']); 253 } 254 } 255 256 if (isset($thisfile_riff_WAVE['fact'][0]['data'])) { 257 $thisfile_riff_raw['fact']['NumberOfSamples'] = $this->EitherEndian2Int(substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4)); 258 259 // This should be a good way of calculating exact playtime, 260 // but some sample files have had incorrect number of samples, 261 // so cannot use this method 262 263 // if (!empty($thisfile_riff_raw['fmt ']['nSamplesPerSec'])) { 264 // $info['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec']; 265 // } 266 } 267 if (!empty($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'])) { 268 $thisfile_audio['bitrate'] = getid3_lib::CastAsInt($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'] * 8); 269 } 270 271 if (isset($thisfile_riff_WAVE['bext'][0]['data'])) { 272 // shortcut 273 $thisfile_riff_WAVE_bext_0 = &$thisfile_riff_WAVE['bext'][0]; 274 275 $thisfile_riff_WAVE_bext_0['title'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 0, 256)); 276 $thisfile_riff_WAVE_bext_0['author'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 256, 32)); 277 $thisfile_riff_WAVE_bext_0['reference'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 288, 32)); 278 $thisfile_riff_WAVE_bext_0['origin_date'] = substr($thisfile_riff_WAVE_bext_0['data'], 320, 10); 279 $thisfile_riff_WAVE_bext_0['origin_time'] = substr($thisfile_riff_WAVE_bext_0['data'], 330, 8); 280 $thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338, 8)); 281 $thisfile_riff_WAVE_bext_0['bwf_version'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 346, 1)); 282 $thisfile_riff_WAVE_bext_0['reserved'] = substr($thisfile_riff_WAVE_bext_0['data'], 347, 254); 283 $thisfile_riff_WAVE_bext_0['coding_history'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601))); 284 if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) { 285 if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) { 286 list($dummy, $bext_timestamp['year'], $bext_timestamp['month'], $bext_timestamp['day']) = $matches_bext_date; 287 list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time; 288 $thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']); 289 } else { 290 $info['warning'][] = 'RIFF.WAVE.BEXT.origin_time is invalid'; 291 } 292 } else { 293 $info['warning'][] = 'RIFF.WAVE.BEXT.origin_date is invalid'; 294 } 295 $thisfile_riff['comments']['author'][] = $thisfile_riff_WAVE_bext_0['author']; 296 $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_bext_0['title']; 297 } 298 299 if (isset($thisfile_riff_WAVE['MEXT'][0]['data'])) { 300 // shortcut 301 $thisfile_riff_WAVE_MEXT_0 = &$thisfile_riff_WAVE['MEXT'][0]; 302 303 $thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 0, 2)); 304 $thisfile_riff_WAVE_MEXT_0['flags']['homogenous'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0001); 305 if ($thisfile_riff_WAVE_MEXT_0['flags']['homogenous']) { 306 $thisfile_riff_WAVE_MEXT_0['flags']['padding'] = ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0002) ? false : true; 307 $thisfile_riff_WAVE_MEXT_0['flags']['22_or_44'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0004); 308 $thisfile_riff_WAVE_MEXT_0['flags']['free_format'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0008); 309 310 $thisfile_riff_WAVE_MEXT_0['nominal_frame_size'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 2, 2)); 311 } 312 $thisfile_riff_WAVE_MEXT_0['anciliary_data_length'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 6, 2)); 313 $thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 8, 2)); 314 $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_left'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0001); 315 $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_free'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0002); 316 $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_right'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0004); 317 } 318 319 if (isset($thisfile_riff_WAVE['cart'][0]['data'])) { 320 // shortcut 321 $thisfile_riff_WAVE_cart_0 = &$thisfile_riff_WAVE['cart'][0]; 322 323 $thisfile_riff_WAVE_cart_0['version'] = substr($thisfile_riff_WAVE_cart_0['data'], 0, 4); 324 $thisfile_riff_WAVE_cart_0['title'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 4, 64)); 325 $thisfile_riff_WAVE_cart_0['artist'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 68, 64)); 326 $thisfile_riff_WAVE_cart_0['cut_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 132, 64)); 327 $thisfile_riff_WAVE_cart_0['client_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 196, 64)); 328 $thisfile_riff_WAVE_cart_0['category'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 260, 64)); 329 $thisfile_riff_WAVE_cart_0['classification'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 324, 64)); 330 $thisfile_riff_WAVE_cart_0['out_cue'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 388, 64)); 331 $thisfile_riff_WAVE_cart_0['start_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 452, 10)); 332 $thisfile_riff_WAVE_cart_0['start_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 462, 8)); 333 $thisfile_riff_WAVE_cart_0['end_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 470, 10)); 334 $thisfile_riff_WAVE_cart_0['end_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 480, 8)); 335 $thisfile_riff_WAVE_cart_0['producer_app_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 488, 64)); 336 $thisfile_riff_WAVE_cart_0['producer_app_version'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 552, 64)); 337 $thisfile_riff_WAVE_cart_0['user_defined_text'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 616, 64)); 338 $thisfile_riff_WAVE_cart_0['zero_db_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 680, 4), true); 339 for ($i = 0; $i < 8; $i++) { 340 $thisfile_riff_WAVE_cart_0['post_time'][$i]['usage_fourcc'] = substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8), 4); 341 $thisfile_riff_WAVE_cart_0['post_time'][$i]['timer_value'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8) + 4, 4)); 342 } 343 $thisfile_riff_WAVE_cart_0['url'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 748, 1024)); 344 $thisfile_riff_WAVE_cart_0['tag_text'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_cart_0['data'], 1772))); 345 346 $thisfile_riff['comments']['artist'][] = $thisfile_riff_WAVE_cart_0['artist']; 347 $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_cart_0['title']; 348 } 349 350 if (isset($thisfile_riff_WAVE['SNDM'][0]['data'])) { 351 // SoundMiner metadata 352 353 // shortcuts 354 $thisfile_riff_WAVE_SNDM_0 = &$thisfile_riff_WAVE['SNDM'][0]; 355 $thisfile_riff_WAVE_SNDM_0_data = &$thisfile_riff_WAVE_SNDM_0['data']; 356 $SNDM_startoffset = 0; 357 $SNDM_endoffset = $thisfile_riff_WAVE_SNDM_0['size']; 358 359 while ($SNDM_startoffset < $SNDM_endoffset) { 360 $SNDM_thisTagOffset = 0; 361 $SNDM_thisTagSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4)); 362 $SNDM_thisTagOffset += 4; 363 $SNDM_thisTagKey = substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4); 364 $SNDM_thisTagOffset += 4; 365 $SNDM_thisTagDataSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2)); 366 $SNDM_thisTagOffset += 2; 367 $SNDM_thisTagDataFlags = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2)); 368 $SNDM_thisTagOffset += 2; 369 $SNDM_thisTagDataText = substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, $SNDM_thisTagDataSize); 370 $SNDM_thisTagOffset += $SNDM_thisTagDataSize; 371 372 if ($SNDM_thisTagSize != (4 + 4 + 2 + 2 + $SNDM_thisTagDataSize)) { 373 $info['warning'][] = 'RIFF.WAVE.SNDM.data contains tag not expected length (expected: '.$SNDM_thisTagSize.', found: '.(4 + 4 + 2 + 2 + $SNDM_thisTagDataSize).') at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; 374 break; 375 } elseif ($SNDM_thisTagSize <= 0) { 376 $info['warning'][] = 'RIFF.WAVE.SNDM.data contains zero-size tag at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; 377 break; 378 } 379 $SNDM_startoffset += $SNDM_thisTagSize; 380 381 $thisfile_riff_WAVE_SNDM_0['parsed_raw'][$SNDM_thisTagKey] = $SNDM_thisTagDataText; 382 if ($parsedkey = self::waveSNDMtagLookup($SNDM_thisTagKey)) { 383 $thisfile_riff_WAVE_SNDM_0['parsed'][$parsedkey] = $SNDM_thisTagDataText; 384 } else { 385 $info['warning'][] = 'RIFF.WAVE.SNDM contains unknown tag "'.$SNDM_thisTagKey.'" at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; 386 } 387 } 388 389 $tagmapping = array( 390 'tracktitle'=>'title', 391 'category' =>'genre', 392 'cdtitle' =>'album', 393 'tracktitle'=>'title', 394 ); 395 foreach ($tagmapping as $fromkey => $tokey) { 396 if (isset($thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey])) { 397 $thisfile_riff['comments'][$tokey][] = $thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey]; 398 } 399 } 400 } 401 402 if (isset($thisfile_riff_WAVE['iXML'][0]['data'])) { 403 // requires functions simplexml_load_string and get_object_vars 404 if ($parsedXML = getid3_lib::XML2array($thisfile_riff_WAVE['iXML'][0]['data'])) { 405 $thisfile_riff_WAVE['iXML'][0]['parsed'] = $parsedXML; 406 if (isset($parsedXML['SPEED']['MASTER_SPEED'])) { 407 @list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['MASTER_SPEED']); 408 $thisfile_riff_WAVE['iXML'][0]['master_speed'] = $numerator / ($denominator ? $denominator : 1000); 409 } 410 if (isset($parsedXML['SPEED']['TIMECODE_RATE'])) { 411 @list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['TIMECODE_RATE']); 412 $thisfile_riff_WAVE['iXML'][0]['timecode_rate'] = $numerator / ($denominator ? $denominator : 1000); 413 } 414 if (isset($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO']) && !empty($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) && !empty($thisfile_riff_WAVE['iXML'][0]['timecode_rate'])) { 415 $samples_since_midnight = floatval(ltrim($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HI'].$parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO'], '0')); 416 $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] = $samples_since_midnight / $parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']; 417 $h = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] / 3600); 418 $m = floor(($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600)) / 60); 419 $s = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60)); 420 $f = ($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60) - $s) * $thisfile_riff_WAVE['iXML'][0]['timecode_rate']; 421 $thisfile_riff_WAVE['iXML'][0]['timecode_string'] = sprintf('%02d:%02d:%02d:%05.2f', $h, $m, $s, $f); 422 $thisfile_riff_WAVE['iXML'][0]['timecode_string_round'] = sprintf('%02d:%02d:%02d:%02d', $h, $m, $s, round($f)); 423 } 424 unset($parsedXML); 425 } 426 } 427 428 429 430 if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) { 431 $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; 432 $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); 433 } 434 435 if (!empty($info['wavpack'])) { 436 $thisfile_audio_dataformat = 'wavpack'; 437 $thisfile_audio['bitrate_mode'] = 'vbr'; 438 $thisfile_audio['encoder'] = 'WavPack v'.$info['wavpack']['version']; 439 440 // Reset to the way it was - RIFF parsing will have messed this up 441 $info['avdataend'] = $Original['avdataend']; 442 $thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; 443 444 $this->fseek($info['avdataoffset'] - 44); 445 $RIFFdata = $this->fread(44); 446 $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8; 447 $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; 448 449 if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { 450 $info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); 451 $this->fseek($info['avdataend']); 452 $RIFFdata .= $this->fread($OrignalRIFFheaderSize - $OrignalRIFFdataSize); 453 } 454 455 // move the data chunk after all other chunks (if any) 456 // so that the RIFF parser doesn't see EOF when trying 457 // to skip over the data chunk 458 $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8); 459 $getid3_riff = new getid3_riff($this->getid3); 460 $getid3_riff->ParseRIFFdata($RIFFdata); 461 unset($getid3_riff); 462 } 463 464 if (isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { 465 switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { 466 case 0x0001: // PCM 467 if (!empty($info['ac3'])) { 468 // Dolby Digital WAV files masquerade as PCM-WAV, but they're not 469 $thisfile_audio['wformattag'] = 0x2000; 470 $thisfile_audio['codec'] = self::wFormatTagLookup($thisfile_audio['wformattag']); 471 $thisfile_audio['lossless'] = false; 472 $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; 473 $thisfile_audio['sample_rate'] = $info['ac3']['sample_rate']; 474 } 475 if (!empty($info['dts'])) { 476 // Dolby DTS files masquerade as PCM-WAV, but they're not 477 $thisfile_audio['wformattag'] = 0x2001; 478 $thisfile_audio['codec'] = self::wFormatTagLookup($thisfile_audio['wformattag']); 479 $thisfile_audio['lossless'] = false; 480 $thisfile_audio['bitrate'] = $info['dts']['bitrate']; 481 $thisfile_audio['sample_rate'] = $info['dts']['sample_rate']; 482 } 483 break; 484 case 0x08AE: // ClearJump LiteWave 485 $thisfile_audio['bitrate_mode'] = 'vbr'; 486 $thisfile_audio_dataformat = 'litewave'; 487 488 //typedef struct tagSLwFormat { 489 // WORD m_wCompFormat; // low byte defines compression method, high byte is compression flags 490 // DWORD m_dwScale; // scale factor for lossy compression 491 // DWORD m_dwBlockSize; // number of samples in encoded blocks 492 // WORD m_wQuality; // alias for the scale factor 493 // WORD m_wMarkDistance; // distance between marks in bytes 494 // WORD m_wReserved; 495 // 496 // //following paramters are ignored if CF_FILESRC is not set 497 // DWORD m_dwOrgSize; // original file size in bytes 498 // WORD m_bFactExists; // indicates if 'fact' chunk exists in the original file 499 // DWORD m_dwRiffChunkSize; // riff chunk size in the original file 500 // 501 // PCMWAVEFORMAT m_OrgWf; // original wave format 502 // }SLwFormat, *PSLwFormat; 503 504 // shortcut 505 $thisfile_riff['litewave']['raw'] = array(); 506 $riff_litewave = &$thisfile_riff['litewave']; 507 $riff_litewave_raw = &$riff_litewave['raw']; 508 509 $flags = array( 510 'compression_method' => 1, 511 'compression_flags' => 1, 512 'm_dwScale' => 4, 513 'm_dwBlockSize' => 4, 514 'm_wQuality' => 2, 515 'm_wMarkDistance' => 2, 516 'm_wReserved' => 2, 517 'm_dwOrgSize' => 4, 518 'm_bFactExists' => 2, 519 'm_dwRiffChunkSize' => 4, 520 ); 521 $litewave_offset = 18; 522 foreach ($flags as $flag => $length) { 523 $riff_litewave_raw[$flag] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], $litewave_offset, $length)); 524 $litewave_offset += $length; 525 } 526 527 //$riff_litewave['quality_factor'] = intval(round((2000 - $riff_litewave_raw['m_dwScale']) / 20)); 528 $riff_litewave['quality_factor'] = $riff_litewave_raw['m_wQuality']; 529 530 $riff_litewave['flags']['raw_source'] = ($riff_litewave_raw['compression_flags'] & 0x01) ? false : true; 531 $riff_litewave['flags']['vbr_blocksize'] = ($riff_litewave_raw['compression_flags'] & 0x02) ? false : true; 532 $riff_litewave['flags']['seekpoints'] = (bool) ($riff_litewave_raw['compression_flags'] & 0x04); 533 534 $thisfile_audio['lossless'] = (($riff_litewave_raw['m_wQuality'] == 100) ? true : false); 535 $thisfile_audio['encoder_options'] = '-q'.$riff_litewave['quality_factor']; 536 break; 537 538 default: 539 break; 540 } 541 } 542 if ($info['avdataend'] > $info['filesize']) { 543 switch (!empty($thisfile_audio_dataformat) ? $thisfile_audio_dataformat : '') { 544 case 'wavpack': // WavPack 545 case 'lpac': // LPAC 546 case 'ofr': // OptimFROG 547 case 'ofs': // OptimFROG DualStream 548 // lossless compressed audio formats that keep original RIFF headers - skip warning 549 break; 550 551 case 'litewave': 552 if (($info['avdataend'] - $info['filesize']) == 1) { 553 // LiteWave appears to incorrectly *not* pad actual output file 554 // to nearest WORD boundary so may appear to be short by one 555 // byte, in which case - skip warning 556 } else { 557 // Short by more than one byte, throw warning 558 $info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; 559 $info['avdataend'] = $info['filesize']; 560 } 561 break; 562 563 default: 564 if ((($info['avdataend'] - $info['filesize']) == 1) && (($thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2) == 0) && ((($info['filesize'] - $info['avdataoffset']) % 2) == 1)) { 565 // output file appears to be incorrectly *not* padded to nearest WORD boundary 566 // Output less severe warning 567 $info['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; 568 $info['avdataend'] = $info['filesize']; 569 } else { 570 // Short by more than one byte, throw warning 571 $info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; 572 $info['avdataend'] = $info['filesize']; 573 } 574 break; 575 } 576 } 577 if (!empty($info['mpeg']['audio']['LAME']['audio_bytes'])) { 578 if ((($info['avdataend'] - $info['avdataoffset']) - $info['mpeg']['audio']['LAME']['audio_bytes']) == 1) { 579 $info['avdataend']--; 580 $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; 581 } 582 } 583 if (isset($thisfile_audio_dataformat) && ($thisfile_audio_dataformat == 'ac3')) { 584 unset($thisfile_audio['bits_per_sample']); 585 if (!empty($info['ac3']['bitrate']) && ($info['ac3']['bitrate'] != $thisfile_audio['bitrate'])) { 586 $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; 587 } 588 } 589 break; 590 591 case 'AVI ': 592 $thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably 593 $thisfile_video['dataformat'] = 'avi'; 594 $info['mime_type'] = 'video/avi'; 595 596 if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) { 597 $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8; 598 if (isset($thisfile_riff['AVIX'])) { 599 $info['avdataend'] = $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['offset'] + $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['size']; 600 } else { 601 $info['avdataend'] = $thisfile_riff['AVI ']['movi']['offset'] + $thisfile_riff['AVI ']['movi']['size']; 602 } 603 if ($info['avdataend'] > $info['filesize']) { 604 $info['warning'][] = 'Probably truncated file - expecting '.($info['avdataend'] - $info['avdataoffset']).' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($info['avdataend'] - $info['filesize']).' bytes)'; 605 $info['avdataend'] = $info['filesize']; 606 } 607 } 608 609 if (isset($thisfile_riff['AVI ']['hdrl']['strl']['indx'])) { 610 //$bIndexType = array( 611 // 0x00 => 'AVI_INDEX_OF_INDEXES', 612 // 0x01 => 'AVI_INDEX_OF_CHUNKS', 613 // 0x80 => 'AVI_INDEX_IS_DATA', 614 //); 615 //$bIndexSubtype = array( 616 // 0x01 => array( 617 // 0x01 => 'AVI_INDEX_2FIELD', 618 // ), 619 //); 620 foreach ($thisfile_riff['AVI ']['hdrl']['strl']['indx'] as $streamnumber => $steamdataarray) { 621 $ahsisd = &$thisfile_riff['AVI ']['hdrl']['strl']['indx'][$streamnumber]['data']; 622 623 $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = $this->EitherEndian2Int(substr($ahsisd, 0, 2)); 624 $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType'] = $this->EitherEndian2Int(substr($ahsisd, 2, 1)); 625 $thisfile_riff_raw['indx'][$streamnumber]['bIndexType'] = $this->EitherEndian2Int(substr($ahsisd, 3, 1)); 626 $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse'] = $this->EitherEndian2Int(substr($ahsisd, 4, 4)); 627 $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId'] = substr($ahsisd, 8, 4); 628 $thisfile_riff_raw['indx'][$streamnumber]['dwReserved'] = $this->EitherEndian2Int(substr($ahsisd, 12, 4)); 629 630 //$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name'] = $bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']]; 631 //$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = $bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']]; 632 633 unset($ahsisd); 634 } 635 } 636 if (isset($thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data'])) { 637 $avihData = $thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data']; 638 639 // shortcut 640 $thisfile_riff_raw['avih'] = array(); 641 $thisfile_riff_raw_avih = &$thisfile_riff_raw['avih']; 642 643 $thisfile_riff_raw_avih['dwMicroSecPerFrame'] = $this->EitherEndian2Int(substr($avihData, 0, 4)); // frame display rate (or 0L) 644 if ($thisfile_riff_raw_avih['dwMicroSecPerFrame'] == 0) { 645 $info['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero'; 646 return false; 647 } 648 649 $flags = array( 650 'dwMaxBytesPerSec', // max. transfer rate 651 'dwPaddingGranularity', // pad to multiples of this size; normally 2K. 652 'dwFlags', // the ever-present flags 653 'dwTotalFrames', // # frames in file 654 'dwInitialFrames', // 655 'dwStreams', // 656 'dwSuggestedBufferSize', // 657 'dwWidth', // 658 'dwHeight', // 659 'dwScale', // 660 'dwRate', // 661 'dwStart', // 662 'dwLength', // 663 ); 664 $avih_offset = 4; 665 foreach ($flags as $flag) { 666 $thisfile_riff_raw_avih[$flag] = $this->EitherEndian2Int(substr($avihData, $avih_offset, 4)); 667 $avih_offset += 4; 668 } 669 670 $flags = array( 671 'hasindex' => 0x00000010, 672 'mustuseindex' => 0x00000020, 673 'interleaved' => 0x00000100, 674 'trustcktype' => 0x00000800, 675 'capturedfile' => 0x00010000, 676 'copyrighted' => 0x00020010, 677 ); 678 foreach ($flags as $flag => $value) { 679 $thisfile_riff_raw_avih['flags'][$flag] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & $value); 680 } 681 682 // shortcut 683 $thisfile_riff_video[$streamindex] = array(); 684 $thisfile_riff_video_current = &$thisfile_riff_video[$streamindex]; 685 686 if ($thisfile_riff_raw_avih['dwWidth'] > 0) { 687 $thisfile_riff_video_current['frame_width'] = $thisfile_riff_raw_avih['dwWidth']; 688 $thisfile_video['resolution_x'] = $thisfile_riff_video_current['frame_width']; 689 } 690 if ($thisfile_riff_raw_avih['dwHeight'] > 0) { 691 $thisfile_riff_video_current['frame_height'] = $thisfile_riff_raw_avih['dwHeight']; 692 $thisfile_video['resolution_y'] = $thisfile_riff_video_current['frame_height']; 693 } 694 if ($thisfile_riff_raw_avih['dwTotalFrames'] > 0) { 695 $thisfile_riff_video_current['total_frames'] = $thisfile_riff_raw_avih['dwTotalFrames']; 696 $thisfile_video['total_frames'] = $thisfile_riff_video_current['total_frames']; 697 } 698 699 $thisfile_riff_video_current['frame_rate'] = round(1000000 / $thisfile_riff_raw_avih['dwMicroSecPerFrame'], 3); 700 $thisfile_video['frame_rate'] = $thisfile_riff_video_current['frame_rate']; 701 } 702 if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) { 703 if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) { 704 for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) { 705 if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) { 706 $strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data']; 707 $strhfccType = substr($strhData, 0, 4); 708 709 if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data'])) { 710 $strfData = $thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data']; 711 712 // shortcut 713 $thisfile_riff_raw_strf_strhfccType_streamindex = &$thisfile_riff_raw['strf'][$strhfccType][$streamindex]; 714 715 switch ($strhfccType) { 716 case 'auds': 717 $thisfile_audio['bitrate_mode'] = 'cbr'; 718 $thisfile_audio_dataformat = 'wav'; 719 if (isset($thisfile_riff_audio) && is_array($thisfile_riff_audio)) { 720 $streamindex = count($thisfile_riff_audio); 721 } 722 723 $thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($strfData); 724 $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; 725 726 // shortcut 727 $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex]; 728 $thisfile_audio_streams_currentstream = &$thisfile_audio['streams'][$streamindex]; 729 730 if ($thisfile_audio_streams_currentstream['bits_per_sample'] == 0) { 731 unset($thisfile_audio_streams_currentstream['bits_per_sample']); 732 } 733 $thisfile_audio_streams_currentstream['wformattag'] = $thisfile_audio_streams_currentstream['raw']['wFormatTag']; 734 unset($thisfile_audio_streams_currentstream['raw']); 735 736 // shortcut 737 $thisfile_riff_raw['strf'][$strhfccType][$streamindex] = $thisfile_riff_audio[$streamindex]['raw']; 738 739 unset($thisfile_riff_audio[$streamindex]['raw']); 740 $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); 741 742 $thisfile_audio['lossless'] = false; 743 switch ($thisfile_riff_raw_strf_strhfccType_streamindex['wFormatTag']) { 744 case 0x0001: // PCM 745 $thisfile_audio_dataformat = 'wav'; 746 $thisfile_audio['lossless'] = true; 747 break; 748 749 case 0x0050: // MPEG Layer 2 or Layer 1 750 $thisfile_audio_dataformat = 'mp2'; // Assume Layer-2 751 break; 752 753 case 0x0055: // MPEG Layer 3 754 $thisfile_audio_dataformat = 'mp3'; 755 break; 756 757 case 0x00FF: // AAC 758 $thisfile_audio_dataformat = 'aac'; 759 break; 760 761 case 0x0161: // Windows Media v7 / v8 / v9 762 case 0x0162: // Windows Media Professional v9 763 case 0x0163: // Windows Media Lossess v9 764 $thisfile_audio_dataformat = 'wma'; 765 break; 766 767 case 0x2000: // AC-3 768 $thisfile_audio_dataformat = 'ac3'; 769 break; 770 771 case 0x2001: // DTS 772 $thisfile_audio_dataformat = 'dts'; 773 break; 774 775 default: 776 $thisfile_audio_dataformat = 'wav'; 777 break; 778 } 779 $thisfile_audio_streams_currentstream['dataformat'] = $thisfile_audio_dataformat; 780 $thisfile_audio_streams_currentstream['lossless'] = $thisfile_audio['lossless']; 781 $thisfile_audio_streams_currentstream['bitrate_mode'] = $thisfile_audio['bitrate_mode']; 782 break; 783 784 785 case 'iavs': 786 case 'vids': 787 // shortcut 788 $thisfile_riff_raw['strh'][$i] = array(); 789 $thisfile_riff_raw_strh_current = &$thisfile_riff_raw['strh'][$i]; 790 791 $thisfile_riff_raw_strh_current['fccType'] = substr($strhData, 0, 4); // same as $strhfccType; 792 $thisfile_riff_raw_strh_current['fccHandler'] = substr($strhData, 4, 4); 793 $thisfile_riff_raw_strh_current['dwFlags'] = $this->EitherEndian2Int(substr($strhData, 8, 4)); // Contains AVITF_* flags 794 $thisfile_riff_raw_strh_current['wPriority'] = $this->EitherEndian2Int(substr($strhData, 12, 2)); 795 $thisfile_riff_raw_strh_current['wLanguage'] = $this->EitherEndian2Int(substr($strhData, 14, 2)); 796 $thisfile_riff_raw_strh_current['dwInitialFrames'] = $this->EitherEndian2Int(substr($strhData, 16, 4)); 797 $thisfile_riff_raw_strh_current['dwScale'] = $this->EitherEndian2Int(substr($strhData, 20, 4)); 798 $thisfile_riff_raw_strh_current['dwRate'] = $this->EitherEndian2Int(substr($strhData, 24, 4)); 799 $thisfile_riff_raw_strh_current['dwStart'] = $this->EitherEndian2Int(substr($strhData, 28, 4)); 800 $thisfile_riff_raw_strh_current['dwLength'] = $this->EitherEndian2Int(substr($strhData, 32, 4)); 801 $thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($strhData, 36, 4)); 802 $thisfile_riff_raw_strh_current['dwQuality'] = $this->EitherEndian2Int(substr($strhData, 40, 4)); 803 $thisfile_riff_raw_strh_current['dwSampleSize'] = $this->EitherEndian2Int(substr($strhData, 44, 4)); 804 $thisfile_riff_raw_strh_current['rcFrame'] = $this->EitherEndian2Int(substr($strhData, 48, 4)); 805 806 $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_riff_raw_strh_current['fccHandler']); 807 $thisfile_video['fourcc'] = $thisfile_riff_raw_strh_current['fccHandler']; 808 if (!$thisfile_riff_video_current['codec'] && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && self::fourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { 809 $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); 810 $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; 811 } 812 $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; 813 $thisfile_video['pixel_aspect_ratio'] = (float) 1; 814 switch ($thisfile_riff_raw_strh_current['fccHandler']) { 815 case 'HFYU': // Huffman Lossless Codec 816 case 'IRAW': // Intel YUV Uncompressed 817 case 'YUY2': // Uncompressed YUV 4:2:2 818 $thisfile_video['lossless'] = true; 819 break; 820 821 default: 822 $thisfile_video['lossless'] = false; 823 break; 824 } 825 826 switch ($strhfccType) { 827 case 'vids': 828 $thisfile_riff_raw_strf_strhfccType_streamindex = self::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($info['fileformat'] == 'riff')); 829 $thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount']; 830 831 if ($thisfile_riff_video_current['codec'] == 'DV') { 832 $thisfile_riff_video_current['dv_type'] = 2; 833 } 834 break; 835 836 case 'iavs': 837 $thisfile_riff_video_current['dv_type'] = 1; 838 break; 839 } 840 break; 841 842 default: 843 $info['warning'][] = 'Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"'; 844 break; 845 846 } 847 } 848 } 849 850 if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { 851 852 $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; 853 if (self::fourccLookup($thisfile_video['fourcc'])) { 854 $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_video['fourcc']); 855 $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; 856 } 857 858 switch ($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) { 859 case 'HFYU': // Huffman Lossless Codec 860 case 'IRAW': // Intel YUV Uncompressed 861 case 'YUY2': // Uncompressed YUV 4:2:2 862 $thisfile_video['lossless'] = true; 863 //$thisfile_video['bits_per_sample'] = 24; 864 break; 865 866 default: 867 $thisfile_video['lossless'] = false; 868 //$thisfile_video['bits_per_sample'] = 24; 869 break; 870 } 871 872 } 873 } 874 } 875 } 876 break; 877 878 case 'CDDA': 879 $thisfile_audio['bitrate_mode'] = 'cbr'; 880 $thisfile_audio_dataformat = 'cda'; 881 $thisfile_audio['lossless'] = true; 882 unset($info['mime_type']); 883 884 $info['avdataoffset'] = 44; 885 886 if (isset($thisfile_riff['CDDA']['fmt '][0]['data'])) { 887 // shortcut 888 $thisfile_riff_CDDA_fmt_0 = &$thisfile_riff['CDDA']['fmt '][0]; 889 890 $thisfile_riff_CDDA_fmt_0['unknown1'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 0, 2)); 891 $thisfile_riff_CDDA_fmt_0['track_num'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 2, 2)); 892 $thisfile_riff_CDDA_fmt_0['disc_id'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 4, 4)); 893 $thisfile_riff_CDDA_fmt_0['start_offset_frame'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 8, 4)); 894 $thisfile_riff_CDDA_fmt_0['playtime_frames'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4)); 895 $thisfile_riff_CDDA_fmt_0['unknown6'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4)); 896 $thisfile_riff_CDDA_fmt_0['unknown7'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4)); 897 898 $thisfile_riff_CDDA_fmt_0['start_offset_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['start_offset_frame'] / 75; 899 $thisfile_riff_CDDA_fmt_0['playtime_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['playtime_frames'] / 75; 900 $info['comments']['track'] = $thisfile_riff_CDDA_fmt_0['track_num']; 901 $info['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; 902 903 // hardcoded data for CD-audio 904 $thisfile_audio['sample_rate'] = 44100; 905 $thisfile_audio['channels'] = 2; 906 $thisfile_audio['bits_per_sample'] = 16; 907 $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $thisfile_audio['channels'] * $thisfile_audio['bits_per_sample']; 908 $thisfile_audio['bitrate_mode'] = 'cbr'; 909 } 910 break; 911 912 913 case 'AIFF': 914 case 'AIFC': 915 $thisfile_audio['bitrate_mode'] = 'cbr'; 916 $thisfile_audio_dataformat = 'aiff'; 917 $thisfile_audio['lossless'] = true; 918 $info['mime_type'] = 'audio/x-aiff'; 919 920 if (isset($thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'])) { 921 $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8; 922 $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size']; 923 if ($info['avdataend'] > $info['filesize']) { 924 if (($info['avdataend'] == ($info['filesize'] + 1)) && (($info['filesize'] % 2) == 1)) { 925 // structures rounded to 2-byte boundary, but dumb encoders 926 // forget to pad end of file to make this actually work 927 } else { 928 $info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found'; 929 } 930 $info['avdataend'] = $info['filesize']; 931 } 932 } 933 934 if (isset($thisfile_riff[$RIFFsubtype]['COMM'][0]['data'])) { 935 936 // shortcut 937 $thisfile_riff_RIFFsubtype_COMM_0_data = &$thisfile_riff[$RIFFsubtype]['COMM'][0]['data']; 938 939 $thisfile_riff_audio['channels'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 0, 2), true); 940 $thisfile_riff_audio['total_samples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 2, 4), false); 941 $thisfile_riff_audio['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 6, 2), true); 942 $thisfile_riff_audio['sample_rate'] = (int) getid3_lib::BigEndian2Float(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 8, 10)); 943 944 if ($thisfile_riff[$RIFFsubtype]['COMM'][0]['size'] > 18) { 945 $thisfile_riff_audio['codec_fourcc'] = substr($thisfile_riff_RIFFsubtype_COMM_0_data, 18, 4); 946 $CodecNameSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 22, 1), false); 947 $thisfile_riff_audio['codec_name'] = substr($thisfile_riff_RIFFsubtype_COMM_0_data, 23, $CodecNameSize); 948 switch ($thisfile_riff_audio['codec_name']) { 949 case 'NONE': 950 $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; 951 $thisfile_audio['lossless'] = true; 952 break; 953 954 case '': 955 switch ($thisfile_riff_audio['codec_fourcc']) { 956 // http://developer.apple.com/qa/snd/snd07.html 957 case 'sowt': 958 $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Little-Endian PCM'; 959 $thisfile_audio['lossless'] = true; 960 break; 961 962 case 'twos': 963 $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Big-Endian PCM'; 964 $thisfile_audio['lossless'] = true; 965 break; 966 967 default: 968 break; 969 } 970 break; 971 972 default: 973 $thisfile_audio['codec'] = $thisfile_riff_audio['codec_name']; 974 $thisfile_audio['lossless'] = false; 975 break; 976 } 977 } 978 979 $thisfile_audio['channels'] = $thisfile_riff_audio['channels']; 980 if ($thisfile_riff_audio['bits_per_sample'] > 0) { 981 $thisfile_audio['bits_per_sample'] = $thisfile_riff_audio['bits_per_sample']; 982 } 983 $thisfile_audio['sample_rate'] = $thisfile_riff_audio['sample_rate']; 984 if ($thisfile_audio['sample_rate'] == 0) { 985 $info['error'][] = 'Corrupted AIFF file: sample_rate == zero'; 986 return false; 987 } 988 $info['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate']; 989 } 990 991 if (isset($thisfile_riff[$RIFFsubtype]['COMT'])) { 992 $offset = 0; 993 $CommentCount = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); 994 $offset += 2; 995 for ($i = 0; $i < $CommentCount; $i++) { 996 $info['comments_raw'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false); 997 $offset += 4; 998 $info['comments_raw'][$i]['marker_id'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true); 999 $offset += 2; 1000 $CommentLength = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); 1001 $offset += 2; 1002 $info['comments_raw'][$i]['comment'] = substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength); 1003 $offset += $CommentLength; 1004 1005 $info['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($info['comments_raw'][$i]['timestamp']); 1006 $thisfile_riff['comments']['comment'][] = $info['comments_raw'][$i]['comment']; 1007 } 1008 } 1009 1010 $CommentsChunkNames = array('NAME'=>'title', 'author'=>'artist', '(c) '=>'copyright', 'ANNO'=>'comment'); 1011 foreach ($CommentsChunkNames as $key => $value) { 1012 if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) { 1013 $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; 1014 } 1015 } 1016 /* 1017 if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) { 1018 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); 1019 $getid3_temp = new getID3(); 1020 $getid3_temp->openfile($this->getid3->filename); 1021 $getid3_id3v2 = new getid3_id3v2($getid3_temp); 1022 $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8; 1023 if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) { 1024 $info['id3v2'] = $getid3_temp->info['id3v2']; 1025 } 1026 unset($getid3_temp, $getid3_id3v2); 1027 } 1028 */ 1029 break; 1030 1031 case '8SVX': 1032 $thisfile_audio['bitrate_mode'] = 'cbr'; 1033 $thisfile_audio_dataformat = '8svx'; 1034 $thisfile_audio['bits_per_sample'] = 8; 1035 $thisfile_audio['channels'] = 1; // overridden below, if need be 1036 $info['mime_type'] = 'audio/x-aiff'; 1037 1038 if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) { 1039 $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8; 1040 $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size']; 1041 if ($info['avdataend'] > $info['filesize']) { 1042 $info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found'; 1043 } 1044 } 1045 1046 if (isset($thisfile_riff[$RIFFsubtype]['VHDR'][0]['offset'])) { 1047 // shortcut 1048 $thisfile_riff_RIFFsubtype_VHDR_0 = &$thisfile_riff[$RIFFsubtype]['VHDR'][0]; 1049 1050 $thisfile_riff_RIFFsubtype_VHDR_0['oneShotHiSamples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 0, 4)); 1051 $thisfile_riff_RIFFsubtype_VHDR_0['repeatHiSamples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 4, 4)); 1052 $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerHiCycle'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 8, 4)); 1053 $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 12, 2)); 1054 $thisfile_riff_RIFFsubtype_VHDR_0['ctOctave'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 14, 1)); 1055 $thisfile_riff_RIFFsubtype_VHDR_0['sCompression'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 15, 1)); 1056 $thisfile_riff_RIFFsubtype_VHDR_0['Volume'] = getid3_lib::FixedPoint16_16(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 16, 4)); 1057 1058 $thisfile_audio['sample_rate'] = $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec']; 1059 1060 switch ($thisfile_riff_RIFFsubtype_VHDR_0['sCompression']) { 1061 case 0: 1062 $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; 1063 $thisfile_audio['lossless'] = true; 1064 $ActualBitsPerSample = 8; 1065 break; 1066 1067 case 1: 1068 $thisfile_audio['codec'] = 'Fibonacci-delta encoding'; 1069 $thisfile_audio['lossless'] = false; 1070 $ActualBitsPerSample = 4; 1071 break; 1072 1073 default: 1074 $info['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"'; 1075 break; 1076 } 1077 } 1078 1079 if (isset($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'])) { 1080 $ChannelsIndex = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'], 0, 4)); 1081 switch ($ChannelsIndex) { 1082 case 6: // Stereo 1083 $thisfile_audio['channels'] = 2; 1084 break; 1085 1086 case 2: // Left channel only 1087 case 4: // Right channel only 1088 $thisfile_audio['channels'] = 1; 1089 break; 1090 1091 default: 1092 $info['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"'; 1093 break; 1094 } 1095 1096 } 1097 1098 $CommentsChunkNames = array('NAME'=>'title', 'author'=>'artist', '(c) '=>'copyright', 'ANNO'=>'comment'); 1099 foreach ($CommentsChunkNames as $key => $value) { 1100 if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) { 1101 $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; 1102 } 1103 } 1104 1105 $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $ActualBitsPerSample * $thisfile_audio['channels']; 1106 if (!empty($thisfile_audio['bitrate'])) { 1107 $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($thisfile_audio['bitrate'] / 8); 1108 } 1109 break; 1110 1111 1112 case 'CDXA': 1113 $info['mime_type'] = 'video/mpeg'; 1114 if (!empty($thisfile_riff['CDXA']['data'][0]['size'])) { 1115 if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, false)) { 1116 $getid3_temp = new getID3(); 1117 $getid3_temp->openfile($this->getid3->filename); 1118 $getid3_mpeg = new getid3_mpeg($getid3_temp); 1119 $getid3_mpeg->Analyze(); 1120 if (empty($getid3_temp->info['error'])) { 1121 $info['audio'] = $getid3_temp->info['audio']; 1122 $info['video'] = $getid3_temp->info['video']; 1123 $info['mpeg'] = $getid3_temp->info['mpeg']; 1124 $info['warning'] = $getid3_temp->info['warning']; 1125 } 1126 unset($getid3_temp, $getid3_mpeg); 1127 } 1128 } 1129 break; 1130 1131 1132 default: 1133 $info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead'; 1134 unset($info['fileformat']); 1135 break; 1136 } 1137 1138 switch ($RIFFsubtype) { 1139 case 'WAVE': 1140 case 'AIFF': 1141 case 'AIFC': 1142 $ID3v2_key_good = 'id3 '; 1143 $ID3v2_keys_bad = array('ID3 ', 'tag '); 1144 foreach ($ID3v2_keys_bad as $ID3v2_key_bad) { 1145 if (isset($thisfile_riff[$RIFFsubtype][$ID3v2_key_bad]) && !array_key_exists($ID3v2_key_good, $thisfile_riff[$RIFFsubtype])) { 1146 $thisfile_riff[$RIFFsubtype][$ID3v2_key_good] = $thisfile_riff[$RIFFsubtype][$ID3v2_key_bad]; 1147 $info['warning'][] = 'mapping "'.$ID3v2_key_bad.'" chunk to "'.$ID3v2_key_good.'"'; 1148 } 1149 } 1150 1151 if (isset($thisfile_riff[$RIFFsubtype]['id3 '])) { 1152 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); 1153 $getid3_temp = new getID3(); 1154 $getid3_temp->openfile($this->getid3->filename); 1155 $getid3_id3v2 = new getid3_id3v2($getid3_temp); 1156 $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['id3 '][0]['offset'] + 8; 1157 if ($thisfile_riff[$RIFFsubtype]['id3 '][0]['valid'] = $getid3_id3v2->Analyze()) { 1158 $info['id3v2'] = $getid3_temp->info['id3v2']; 1159 } 1160 unset($getid3_temp, $getid3_id3v2); 1161 } 1162 break; 1163 } 1164 1165 if (isset($thisfile_riff_WAVE['DISP']) && is_array($thisfile_riff_WAVE['DISP'])) { 1166 $thisfile_riff['comments']['title'][] = trim(substr($thisfile_riff_WAVE['DISP'][count($thisfile_riff_WAVE['DISP']) - 1]['data'], 4)); 1167 } 1168 if (isset($thisfile_riff_WAVE['INFO']) && is_array($thisfile_riff_WAVE['INFO'])) { 1169 self::parseComments($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']); 1170 } 1171 if (isset($thisfile_riff['AVI ']['INFO']) && is_array($thisfile_riff['AVI ']['INFO'])) { 1172 self::parseComments($thisfile_riff['AVI ']['INFO'], $thisfile_riff['comments']); 1173 } 1174 1175 if (empty($thisfile_audio['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version'])) { 1176 $thisfile_audio['encoder'] = $info['mpeg']['audio']['LAME']['short_version']; 1177 } 1178 1179 if (!isset($info['playtime_seconds'])) { 1180 $info['playtime_seconds'] = 0; 1181 } 1182 if (isset($thisfile_riff_raw['strh'][0]['dwLength']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { 1183 // needed for >2GB AVIs where 'avih' chunk only lists number of frames in that chunk, not entire movie 1184 $info['playtime_seconds'] = $thisfile_riff_raw['strh'][0]['dwLength'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); 1185 } elseif (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { 1186 $info['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); 1187 } 1188 1189 if ($info['playtime_seconds'] > 0) { 1190 if (isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { 1191 1192 if (!isset($info['bitrate'])) { 1193 $info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); 1194 } 1195 1196 } elseif (isset($thisfile_riff_audio) && !isset($thisfile_riff_video)) { 1197 1198 if (!isset($thisfile_audio['bitrate'])) { 1199 $thisfile_audio['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); 1200 } 1201 1202 } elseif (!isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { 1203 1204 if (!isset($thisfile_video['bitrate'])) { 1205 $thisfile_video['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); 1206 } 1207 1208 } 1209 } 1210 1211 1212 if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && ($thisfile_audio['bitrate'] > 0) && ($info['playtime_seconds'] > 0)) { 1213 1214 $info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); 1215 $thisfile_audio['bitrate'] = 0; 1216 $thisfile_video['bitrate'] = $info['bitrate']; 1217 foreach ($thisfile_riff_audio as $channelnumber => $audioinfoarray) { 1218 $thisfile_video['bitrate'] -= $audioinfoarray['bitrate']; 1219 $thisfile_audio['bitrate'] += $audioinfoarray['bitrate']; 1220 } 1221 if ($thisfile_video['bitrate'] <= 0) { 1222 unset($thisfile_video['bitrate']); 1223 } 1224 if ($thisfile_audio['bitrate'] <= 0) { 1225 unset($thisfile_audio['bitrate']); 1226 } 1227 } 1228 1229 if (isset($info['mpeg']['audio'])) { 1230 $thisfile_audio_dataformat = 'mp'.$info['mpeg']['audio']['layer']; 1231 $thisfile_audio['sample_rate'] = $info['mpeg']['audio']['sample_rate']; 1232 $thisfile_audio['channels'] = $info['mpeg']['audio']['channels']; 1233 $thisfile_audio['bitrate'] = $info['mpeg']['audio']['bitrate']; 1234 $thisfile_audio['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); 1235 if (!empty($info['mpeg']['audio']['codec'])) { 1236 $thisfile_audio['codec'] = $info['mpeg']['audio']['codec'].' '.$thisfile_audio['codec']; 1237 } 1238 if (!empty($thisfile_audio['streams'])) { 1239 foreach ($thisfile_audio['streams'] as $streamnumber => $streamdata) { 1240 if ($streamdata['dataformat'] == $thisfile_audio_dataformat) { 1241 $thisfile_audio['streams'][$streamnumber]['sample_rate'] = $thisfile_audio['sample_rate']; 1242 $thisfile_audio['streams'][$streamnumber]['channels'] = $thisfile_audio['channels']; 1243 $thisfile_audio['streams'][$streamnumber]['bitrate'] = $thisfile_audio['bitrate']; 1244 $thisfile_audio['streams'][$streamnumber]['bitrate_mode'] = $thisfile_audio['bitrate_mode']; 1245 $thisfile_audio['streams'][$streamnumber]['codec'] = $thisfile_audio['codec']; 1246 } 1247 } 1248 } 1249 $getid3_mp3 = new getid3_mp3($this->getid3); 1250 $thisfile_audio['encoder_options'] = $getid3_mp3->GuessEncoderOptions(); 1251 unset($getid3_mp3); 1252 } 1253 1254 1255 if (!empty($thisfile_riff_raw['fmt ']['wBitsPerSample']) && ($thisfile_riff_raw['fmt ']['wBitsPerSample'] > 0)) { 1256 switch ($thisfile_audio_dataformat) { 1257 case 'ac3': 1258 // ignore bits_per_sample 1259 break; 1260 1261 default: 1262 $thisfile_audio['bits_per_sample'] = $thisfile_riff_raw['fmt ']['wBitsPerSample']; 1263 break; 1264 } 1265 } 1266 1267 1268 if (empty($thisfile_riff_raw)) { 1269 unset($thisfile_riff['raw']); 1270 } 1271 if (empty($thisfile_riff_audio)) { 1272 unset($thisfile_riff['audio']); 1273 } 1274 if (empty($thisfile_riff_video)) { 1275 unset($thisfile_riff['video']); 1276 } 1277 1278 return true; 1279 } 1280 1281 public function ParseRIFF($startoffset, $maxoffset) { 1282 $info = &$this->getid3->info; 1283 1284 $RIFFchunk = false; 1285 $FoundAllChunksWeNeed = false; 1286 1287 try { 1288 $this->fseek($startoffset); 1289 $maxoffset = min($maxoffset, $info['avdataend']); 1290 while ($this->ftell() < $maxoffset) { 1291 $chunknamesize = $this->fread(8); 1292 //$chunkname = substr($chunknamesize, 0, 4); 1293 $chunkname = str_replace("\x00", '_', substr($chunknamesize, 0, 4)); // note: chunk names of 4 null bytes do appear to be legal (has been observed inside INFO and PRMI chunks, for example), but makes traversing array keys more difficult 1294 $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); 1295 //if (strlen(trim($chunkname, "\x00")) < 4) { 1296 if (strlen($chunkname) < 4) { 1297 $this->error('Expecting chunk name at offset '.($this->ftell() - 8).' but found nothing. Aborting RIFF parsing.'); 1298 break; 1299 } 1300 if (($chunksize == 0) && ($chunkname != 'JUNK')) { 1301 $this->warning('Chunk ('.$chunkname.') size at offset '.($this->ftell() - 4).' is zero. Aborting RIFF parsing.'); 1302 break; 1303 } 1304 if (($chunksize % 2) != 0) { 1305 // all structures are packed on word boundaries 1306 $chunksize++; 1307 } 1308 1309 switch ($chunkname) { 1310 case 'LIST': 1311 $listname = $this->fread(4); 1312 if (preg_match('#^(movi|rec )$#i', $listname)) { 1313 $RIFFchunk[$listname]['offset'] = $this->ftell() - 4; 1314 $RIFFchunk[$listname]['size'] = $chunksize; 1315 1316 if (!$FoundAllChunksWeNeed) { 1317 $WhereWeWere = $this->ftell(); 1318 $AudioChunkHeader = $this->fread(12); 1319 $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); 1320 $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); 1321 $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); 1322 1323 if ($AudioChunkStreamType == 'wb') { 1324 $FirstFourBytes = substr($AudioChunkHeader, 8, 4); 1325 if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', $FirstFourBytes)) { 1326 // MP3 1327 if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { 1328 $getid3_temp = new getID3(); 1329 $getid3_temp->openfile($this->getid3->filename); 1330 $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; 1331 $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; 1332 $getid3_mp3 = new getid3_mp3($getid3_temp); 1333 $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); 1334 if (isset($getid3_temp->info['mpeg']['audio'])) { 1335 $info['mpeg']['audio'] = $getid3_temp->info['mpeg']['audio']; 1336 $info['audio'] = $getid3_temp->info['audio']; 1337 $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; 1338 $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; 1339 $info['audio']['channels'] = $info['mpeg']['audio']['channels']; 1340 $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; 1341 $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); 1342 //$info['bitrate'] = $info['audio']['bitrate']; 1343 } 1344 unset($getid3_temp, $getid3_mp3); 1345 } 1346 1347 } elseif (strpos($FirstFourBytes, getid3_ac3::syncword) === 0) { 1348 1349 // AC3 1350 $getid3_temp = new getID3(); 1351 $getid3_temp->openfile($this->getid3->filename); 1352 $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; 1353 $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; 1354 $getid3_ac3 = new getid3_ac3($getid3_temp); 1355 $getid3_ac3->Analyze(); 1356 if (empty($getid3_temp->info['error'])) { 1357 $info['audio'] = $getid3_temp->info['audio']; 1358 $info['ac3'] = $getid3_temp->info['ac3']; 1359 if (!empty($getid3_temp->info['warning'])) { 1360 foreach ($getid3_temp->info['warning'] as $key => $value) { 1361 $info['warning'][] = $value; 1362 } 1363 } 1364 } 1365 unset($getid3_temp, $getid3_ac3); 1366 } 1367 } 1368 $FoundAllChunksWeNeed = true; 1369 $this->fseek($WhereWeWere); 1370 } 1371 $this->fseek($chunksize - 4, SEEK_CUR); 1372 1373 } else { 1374 1375 if (!isset($RIFFchunk[$listname])) { 1376 $RIFFchunk[$listname] = array(); 1377 } 1378 $LISTchunkParent = $listname; 1379 $LISTchunkMaxOffset = $this->ftell() - 4 + $chunksize; 1380 if ($parsedChunk = $this->ParseRIFF($this->ftell(), $LISTchunkMaxOffset)) { 1381 $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); 1382 } 1383 1384 } 1385 break; 1386 1387 default: 1388 if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) { 1389 $this->fseek($chunksize, SEEK_CUR); 1390 break; 1391 } 1392 $thisindex = 0; 1393 if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { 1394 $thisindex = count($RIFFchunk[$chunkname]); 1395 } 1396 $RIFFchunk[$chunkname][$thisindex]['offset'] = $this->ftell() - 8; 1397 $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; 1398 switch ($chunkname) { 1399 case 'data': 1400 $info['avdataoffset'] = $this->ftell(); 1401 $info['avdataend'] = $info['avdataoffset'] + $chunksize; 1402 1403 $testData = $this->fread(36); 1404 if ($testData === '') { 1405 break; 1406 } 1407 if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($testData, 0, 4))) { 1408 1409 // Probably is MP3 data 1410 if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) { 1411 $getid3_temp = new getID3(); 1412 $getid3_temp->openfile($this->getid3->filename); 1413 $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; 1414 $getid3_temp->info['avdataend'] = $info['avdataend']; 1415 $getid3_mp3 = new getid3_mp3($getid3_temp); 1416 $getid3_mp3->getOnlyMPEGaudioInfo($info['avdataoffset'], false); 1417 if (empty($getid3_temp->info['error'])) { 1418 $info['audio'] = $getid3_temp->info['audio']; 1419 $info['mpeg'] = $getid3_temp->info['mpeg']; 1420 } 1421 unset($getid3_temp, $getid3_mp3); 1422 } 1423 1424 } elseif (($isRegularAC3 = (substr($testData, 0, 2) == getid3_ac3::syncword)) || substr($testData, 8, 2) == strrev(getid3_ac3::syncword)) { 1425 1426 // This is probably AC-3 data 1427 $getid3_temp = new getID3(); 1428 if ($isRegularAC3) { 1429 $getid3_temp->openfile($this->getid3->filename); 1430 $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; 1431 $getid3_temp->info['avdataend'] = $info['avdataend']; 1432 } 1433 $getid3_ac3 = new getid3_ac3($getid3_temp); 1434 if ($isRegularAC3) { 1435 $getid3_ac3->Analyze(); 1436 } else { 1437 // Dolby Digital WAV 1438 // AC-3 content, but not encoded in same format as normal AC-3 file 1439 // For one thing, byte order is swapped 1440 $ac3_data = ''; 1441 for ($i = 0; $i < 28; $i += 2) { 1442 $ac3_data .= substr($testData, 8 + $i + 1, 1); 1443 $ac3_data .= substr($testData, 8 + $i + 0, 1); 1444 } 1445 $getid3_ac3->AnalyzeString($ac3_data); 1446 } 1447 1448 if (empty($getid3_temp->info['error'])) { 1449 $info['audio'] = $getid3_temp->info['audio']; 1450 $info['ac3'] = $getid3_temp->info['ac3']; 1451 if (!empty($getid3_temp->info['warning'])) { 1452 foreach ($getid3_temp->info['warning'] as $newerror) { 1453 $this->warning('getid3_ac3() says: ['.$newerror.']'); 1454 } 1455 } 1456 } 1457 unset($getid3_temp, $getid3_ac3); 1458 1459 } elseif (preg_match('/^('.implode('|', array_map('preg_quote', getid3_dts::$syncwords)).')/', $testData)) { 1460 1461 // This is probably DTS data 1462 $getid3_temp = new getID3(); 1463 $getid3_temp->openfile($this->getid3->filename); 1464 $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; 1465 $getid3_dts = new getid3_dts($getid3_temp); 1466 $getid3_dts->Analyze(); 1467 if (empty($getid3_temp->info['error'])) { 1468 $info['audio'] = $getid3_temp->info['audio']; 1469 $info['dts'] = $getid3_temp->info['dts']; 1470 $info['playtime_seconds'] = $getid3_temp->info['playtime_seconds']; // may not match RIFF calculations since DTS-WAV often used 14/16 bit-word packing 1471 if (!empty($getid3_temp->info['warning'])) { 1472 foreach ($getid3_temp->info['warning'] as $newerror) { 1473 $this->warning('getid3_dts() says: ['.$newerror.']'); 1474 } 1475 } 1476 } 1477 1478 unset($getid3_temp, $getid3_dts); 1479 1480 } elseif (substr($testData, 0, 4) == 'wvpk') { 1481 1482 // This is WavPack data 1483 $info['wavpack']['offset'] = $info['avdataoffset']; 1484 $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($testData, 4, 4)); 1485 $this->parseWavPackHeader(substr($testData, 8, 28)); 1486 1487 } else { 1488 // This is some other kind of data (quite possibly just PCM) 1489 // do nothing special, just skip it 1490 } 1491 $nextoffset = $info['avdataend']; 1492 $this->fseek($nextoffset); 1493 break; 1494 1495 case 'iXML': 1496 case 'bext': 1497 case 'cart': 1498 case 'fmt ': 1499 case 'strh': 1500 case 'strf': 1501 case 'indx': 1502 case 'MEXT': 1503 case 'DISP': 1504 // always read data in 1505 case 'JUNK': 1506 // should be: never read data in 1507 // but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc) 1508 if ($chunksize < 1048576) { 1509 if ($chunksize > 0) { 1510 $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); 1511 if ($chunkname == 'JUNK') { 1512 if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) { 1513 // only keep text characters [chr(32)-chr(127)] 1514 $info['riff']['comments']['junk'][] = trim($matches[1]); 1515 } 1516 // but if nothing there, ignore 1517 // remove the key in either case 1518 unset($RIFFchunk[$chunkname][$thisindex]['data']); 1519 } 1520 } 1521 } else { 1522 $this->warning('Chunk "'.$chunkname.'" at offset '.$this->ftell().' is unexpectedly larger than 1MB (claims to be '.number_format($chunksize).' bytes), skipping data'); 1523 $this->fseek($chunksize, SEEK_CUR); 1524 } 1525 break; 1526 1527 //case 'IDVX': 1528 // $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunksize)); 1529 // break; 1530 1531 default: 1532 if (!empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { 1533 $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; 1534 $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; 1535 unset($RIFFchunk[$chunkname][$thisindex]['offset']); 1536 unset($RIFFchunk[$chunkname][$thisindex]['size']); 1537 if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) { 1538 unset($RIFFchunk[$chunkname][$thisindex]); 1539 } 1540 if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { 1541 unset($RIFFchunk[$chunkname]); 1542 } 1543 $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = $this->fread($chunksize); 1544 } elseif ($chunksize < 2048) { 1545 // only read data in if smaller than 2kB 1546 $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); 1547 } else { 1548 $this->fseek($chunksize, SEEK_CUR); 1549 } 1550 break; 1551 } 1552 break; 1553 } 1554 } 1555 1556 } catch (getid3_exception $e) { 1557 if ($e->getCode() == 10) { 1558 $this->warning('RIFF parser: '.$e->getMessage()); 1559 } else { 1560 throw $e; 1561 } 1562 } 1563 1564 return $RIFFchunk; 1565 } 1566 1567 public function ParseRIFFdata(&$RIFFdata) { 1568 $info = &$this->getid3->info; 1569 if ($RIFFdata) { 1570 $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); 1571 $fp_temp = fopen($tempfile, 'wb'); 1572 $RIFFdataLength = strlen($RIFFdata); 1573 $NewLengthString = getid3_lib::LittleEndian2String($RIFFdataLength, 4); 1574 for ($i = 0; $i < 4; $i++) { 1575 $RIFFdata[($i + 4)] = $NewLengthString[$i]; 1576 } 1577 fwrite($fp_temp, $RIFFdata); 1578 fclose($fp_temp); 1579 1580 $getid3_temp = new getID3(); 1581 $getid3_temp->openfile($tempfile); 1582 $getid3_temp->info['filesize'] = $RIFFdataLength; 1583 $getid3_temp->info['filenamepath'] = $info['filenamepath']; 1584 $getid3_temp->info['tags'] = $info['tags']; 1585 $getid3_temp->info['warning'] = $info['warning']; 1586 $getid3_temp->info['error'] = $info['error']; 1587 $getid3_temp->info['comments'] = $info['comments']; 1588 $getid3_temp->info['audio'] = (isset($info['audio']) ? $info['audio'] : array()); 1589 $getid3_temp->info['video'] = (isset($info['video']) ? $info['video'] : array()); 1590 $getid3_riff = new getid3_riff($getid3_temp); 1591 $getid3_riff->Analyze(); 1592 1593 $info['riff'] = $getid3_temp->info['riff']; 1594 $info['warning'] = $getid3_temp->info['warning']; 1595 $info['error'] = $getid3_temp->info['error']; 1596 $info['tags'] = $getid3_temp->info['tags']; 1597 $info['comments'] = $getid3_temp->info['comments']; 1598 unset($getid3_riff, $getid3_temp); 1599 unlink($tempfile); 1600 } 1601 return false; 1602 } 1603 1604 public static function parseComments(&$RIFFinfoArray, &$CommentsTargetArray) { 1605 $RIFFinfoKeyLookup = array( 1606 'IARL'=>'archivallocation', 1607 'IART'=>'artist', 1608 'ICDS'=>'costumedesigner', 1609 'ICMS'=>'commissionedby', 1610 'ICMT'=>'comment', 1611 'ICNT'=>'country', 1612 'ICOP'=>'copyright', 1613 'ICRD'=>'creationdate', 1614 'IDIM'=>'dimensions', 1615 'IDIT'=>'digitizationdate', 1616 'IDPI'=>'resolution', 1617 'IDST'=>'distributor', 1618 'IEDT'=>'editor', 1619 'IENG'=>'engineers', 1620 'IFRM'=>'accountofparts', 1621 'IGNR'=>'genre', 1622 'IKEY'=>'keywords', 1623 'ILGT'=>'lightness', 1624 'ILNG'=>'language', 1625 'IMED'=>'orignalmedium', 1626 'IMUS'=>'composer', 1627 'INAM'=>'title', 1628 'IPDS'=>'productiondesigner', 1629 'IPLT'=>'palette', 1630 'IPRD'=>'product', 1631 'IPRO'=>'producer', 1632 'IPRT'=>'part', 1633 'IRTD'=>'rating', 1634 'ISBJ'=>'subject', 1635 'ISFT'=>'software', 1636 'ISGN'=>'secondarygenre', 1637 'ISHP'=>'sharpness', 1638 'ISRC'=>'sourcesupplier', 1639 'ISRF'=>'digitizationsource', 1640 'ISTD'=>'productionstudio', 1641 'ISTR'=>'starring', 1642 'ITCH'=>'encoded_by', 1643 'IWEB'=>'url', 1644 'IWRI'=>'writer', 1645 '____'=>'comment', 1646 ); 1647 foreach ($RIFFinfoKeyLookup as $key => $value) { 1648 if (isset($RIFFinfoArray[$key])) { 1649 foreach ($RIFFinfoArray[$key] as $commentid => $commentdata) { 1650 if (trim($commentdata['data']) != '') { 1651 if (isset($CommentsTargetArray[$value])) { 1652 $CommentsTargetArray[$value][] = trim($commentdata['data']); 1653 } else { 1654 $CommentsTargetArray[$value] = array(trim($commentdata['data'])); 1655 } 1656 } 1657 } 1658 } 1659 } 1660 return true; 1661 } 1662 1663 public static function parseWAVEFORMATex($WaveFormatExData) { 1664 // shortcut 1665 $WaveFormatEx['raw'] = array(); 1666 $WaveFormatEx_raw = &$WaveFormatEx['raw']; 1667 1668 $WaveFormatEx_raw['wFormatTag'] = substr($WaveFormatExData, 0, 2); 1669 $WaveFormatEx_raw['nChannels'] = substr($WaveFormatExData, 2, 2); 1670 $WaveFormatEx_raw['nSamplesPerSec'] = substr($WaveFormatExData, 4, 4); 1671 $WaveFormatEx_raw['nAvgBytesPerSec'] = substr($WaveFormatExData, 8, 4); 1672 $WaveFormatEx_raw['nBlockAlign'] = substr($WaveFormatExData, 12, 2); 1673 $WaveFormatEx_raw['wBitsPerSample'] = substr($WaveFormatExData, 14, 2); 1674 if (strlen($WaveFormatExData) > 16) { 1675 $WaveFormatEx_raw['cbSize'] = substr($WaveFormatExData, 16, 2); 1676 } 1677 $WaveFormatEx_raw = array_map('getid3_lib::LittleEndian2Int', $WaveFormatEx_raw); 1678 1679 $WaveFormatEx['codec'] = self::wFormatTagLookup($WaveFormatEx_raw['wFormatTag']); 1680 $WaveFormatEx['channels'] = $WaveFormatEx_raw['nChannels']; 1681 $WaveFormatEx['sample_rate'] = $WaveFormatEx_raw['nSamplesPerSec']; 1682 $WaveFormatEx['bitrate'] = $WaveFormatEx_raw['nAvgBytesPerSec'] * 8; 1683 $WaveFormatEx['bits_per_sample'] = $WaveFormatEx_raw['wBitsPerSample']; 1684 1685 return $WaveFormatEx; 1686 } 1687 1688 public function parseWavPackHeader($WavPackChunkData) { 1689 // typedef struct { 1690 // char ckID [4]; 1691 // long ckSize; 1692 // short version; 1693 // short bits; // added for version 2.00 1694 // short flags, shift; // added for version 3.00 1695 // long total_samples, crc, crc2; 1696 // char extension [4], extra_bc, extras [3]; 1697 // } WavpackHeader; 1698 1699 // shortcut 1700 $info = &$this->getid3->info; 1701 $info['wavpack'] = array(); 1702 $thisfile_wavpack = &$info['wavpack']; 1703 1704 $thisfile_wavpack['version'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 0, 2)); 1705 if ($thisfile_wavpack['version'] >= 2) { 1706 $thisfile_wavpack['bits'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 2, 2)); 1707 } 1708 if ($thisfile_wavpack['version'] >= 3) { 1709 $thisfile_wavpack['flags_raw'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 4, 2)); 1710 $thisfile_wavpack['shift'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 6, 2)); 1711 $thisfile_wavpack['total_samples'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 8, 4)); 1712 $thisfile_wavpack['crc1'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 12, 4)); 1713 $thisfile_wavpack['crc2'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 16, 4)); 1714 $thisfile_wavpack['extension'] = substr($WavPackChunkData, 20, 4); 1715 $thisfile_wavpack['extra_bc'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 24, 1)); 1716 for ($i = 0; $i <= 2; $i++) { 1717 $thisfile_wavpack['extras'][] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 25 + $i, 1)); 1718 } 1719 1720 // shortcut 1721 $thisfile_wavpack['flags'] = array(); 1722 $thisfile_wavpack_flags = &$thisfile_wavpack['flags']; 1723 1724 $thisfile_wavpack_flags['mono'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000001); 1725 $thisfile_wavpack_flags['fast_mode'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000002); 1726 $thisfile_wavpack_flags['raw_mode'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000004); 1727 $thisfile_wavpack_flags['calc_noise'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000008); 1728 $thisfile_wavpack_flags['high_quality'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000010); 1729 $thisfile_wavpack_flags['3_byte_samples'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000020); 1730 $thisfile_wavpack_flags['over_20_bits'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000040); 1731 $thisfile_wavpack_flags['use_wvc'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000080); 1732 $thisfile_wavpack_flags['noiseshaping'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000100); 1733 $thisfile_wavpack_flags['very_fast_mode'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000200); 1734 $thisfile_wavpack_flags['new_high_quality'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000400); 1735 $thisfile_wavpack_flags['cancel_extreme'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000800); 1736 $thisfile_wavpack_flags['cross_decorrelation'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x001000); 1737 $thisfile_wavpack_flags['new_decorrelation'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x002000); 1738 $thisfile_wavpack_flags['joint_stereo'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x004000); 1739 $thisfile_wavpack_flags['extra_decorrelation'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x008000); 1740 $thisfile_wavpack_flags['override_noiseshape'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x010000); 1741 $thisfile_wavpack_flags['override_jointstereo'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x020000); 1742 $thisfile_wavpack_flags['copy_source_filetime'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x040000); 1743 $thisfile_wavpack_flags['create_exe'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x080000); 1744 } 1745 1746 return true; 1747 } 1748 1749 public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) { 1750 1751 $parsed['biSize'] = substr($BITMAPINFOHEADER, 0, 4); // number of bytes required by the BITMAPINFOHEADER structure 1752 $parsed['biWidth'] = substr($BITMAPINFOHEADER, 4, 4); // width of the bitmap in pixels 1753 $parsed['biHeight'] = substr($BITMAPINFOHEADER, 8, 4); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner 1754 $parsed['biPlanes'] = substr($BITMAPINFOHEADER, 12, 2); // number of color planes on the target device. In most cases this value must be set to 1 1755 $parsed['biBitCount'] = substr($BITMAPINFOHEADER, 14, 2); // Specifies the number of bits per pixels 1756 $parsed['biSizeImage'] = substr($BITMAPINFOHEADER, 20, 4); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) 1757 $parsed['biXPelsPerMeter'] = substr($BITMAPINFOHEADER, 24, 4); // horizontal resolution, in pixels per metre, of the target device 1758 $parsed['biYPelsPerMeter'] = substr($BITMAPINFOHEADER, 28, 4); // vertical resolution, in pixels per metre, of the target device 1759 $parsed['biClrUsed'] = substr($BITMAPINFOHEADER, 32, 4); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression 1760 $parsed['biClrImportant'] = substr($BITMAPINFOHEADER, 36, 4); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important 1761 $parsed = array_map('getid3_lib::'.($littleEndian ? 'Little' : 'Big').'Endian2Int', $parsed); 1762 1763 $parsed['fourcc'] = substr($BITMAPINFOHEADER, 16, 4); // compression identifier 1764 1765 return $parsed; 1766 } 1767 1768 public static function ParseDIVXTAG($DIVXTAG, $raw=false) { 1769 // structure from "IDivX" source, Form1.frm, by "Greg Frazier of Daemonic Software Group", email: gfrazier@icestorm.net, web: http://dsg.cjb.net/ 1770 // source available at http://files.divx-digest.com/download/c663efe7ef8ad2e90bf4af4d3ea6188a/on0SWN2r/edit/IDivX.zip 1771 // 'Byte Layout: '1111111111111111 1772 // '32 for Movie - 1 '1111111111111111 1773 // '28 for Author - 6 '6666666666666666 1774 // '4 for year - 2 '6666666666662222 1775 // '3 for genre - 3 '7777777777777777 1776 // '48 for Comments - 7 '7777777777777777 1777 // '1 for Rating - 4 '7777777777777777 1778 // '5 for Future Additions - 0 '333400000DIVXTAG 1779 // '128 bytes total 1780 1781 static $DIVXTAGgenre = array( 1782 0 => 'Action', 1783 1 => 'Action/Adventure', 1784 2 => 'Adventure', 1785 3 => 'Adult', 1786 4 => 'Anime', 1787 5 => 'Cartoon', 1788 6 => 'Claymation', 1789 7 => 'Comedy', 1790 8 => 'Commercial', 1791 9 => 'Documentary', 1792 10 => 'Drama', 1793 11 => 'Home Video', 1794 12 => 'Horror', 1795 13 => 'Infomercial', 1796 14 => 'Interactive', 1797 15 => 'Mystery', 1798 16 => 'Music Video', 1799 17 => 'Other', 1800 18 => 'Religion', 1801 19 => 'Sci Fi', 1802 20 => 'Thriller', 1803 21 => 'Western', 1804 ), 1805 $DIVXTAGrating = array( 1806 0 => 'Unrated', 1807 1 => 'G', 1808 2 => 'PG', 1809 3 => 'PG-13', 1810 4 => 'R', 1811 5 => 'NC-17', 1812 ); 1813 1814 $parsed['title'] = trim(substr($DIVXTAG, 0, 32)); 1815 $parsed['artist'] = trim(substr($DIVXTAG, 32, 28)); 1816 $parsed['year'] = intval(trim(substr($DIVXTAG, 60, 4))); 1817 $parsed['comment'] = trim(substr($DIVXTAG, 64, 48)); 1818 $parsed['genre_id'] = intval(trim(substr($DIVXTAG, 112, 3))); 1819 $parsed['rating_id'] = ord(substr($DIVXTAG, 115, 1)); 1820 //$parsed['padding'] = substr($DIVXTAG, 116, 5); // 5-byte null 1821 //$parsed['magic'] = substr($DIVXTAG, 121, 7); // "DIVXTAG" 1822 1823 $parsed['genre'] = (isset($DIVXTAGgenre[$parsed['genre_id']]) ? $DIVXTAGgenre[$parsed['genre_id']] : $parsed['genre_id']); 1824 $parsed['rating'] = (isset($DIVXTAGrating[$parsed['rating_id']]) ? $DIVXTAGrating[$parsed['rating_id']] : $parsed['rating_id']); 1825 1826 if (!$raw) { 1827 unset($parsed['genre_id'], $parsed['rating_id']); 1828 foreach ($parsed as $key => $value) { 1829 if (!$value === '') { 1830 unset($parsed['key']); 1831 } 1832 } 1833 } 1834 1835 foreach ($parsed as $tag => $value) { 1836 $parsed[$tag] = array($value); 1837 } 1838 1839 return $parsed; 1840 } 1841 1842 public static function waveSNDMtagLookup($tagshortname) { 1843 $begin = __LINE__; 1844 1845 /** This is not a comment! 1846 1847 ©kwd keywords 1848 ©BPM bpm 1849 ©trt tracktitle 1850 ©des description 1851 ©gen category 1852 ©fin featuredinstrument 1853 ©LID longid 1854 ©bex bwdescription 1855 ©pub publisher 1856 ©cdt cdtitle 1857 ©alb library 1858 ©com composer 1859 1860 */ 1861 1862 return getid3_lib::EmbeddedLookup($tagshortname, $begin, __LINE__, __FILE__, 'riff-sndm'); 1863 } 1864 1865 public static function wFormatTagLookup($wFormatTag) { 1866 1867 $begin = __LINE__; 1868 1869 /** This is not a comment! 1870 1871 0x0000 Microsoft Unknown Wave Format 1872 0x0001 Pulse Code Modulation (PCM) 1873 0x0002 Microsoft ADPCM 1874 0x0003 IEEE Float 1875 0x0004 Compaq Computer VSELP 1876 0x0005 IBM CVSD 1877 0x0006 Microsoft A-Law 1878 0x0007 Microsoft mu-Law 1879 0x0008 Microsoft DTS 1880 0x0010 OKI ADPCM 1881 0x0011 Intel DVI/IMA ADPCM 1882 0x0012 Videologic MediaSpace ADPCM 1883 0x0013 Sierra Semiconductor ADPCM 1884 0x0014 Antex Electronics G.723 ADPCM 1885 0x0015 DSP Solutions DigiSTD 1886 0x0016 DSP Solutions DigiFIX 1887 0x0017 Dialogic OKI ADPCM 1888 0x0018 MediaVision ADPCM 1889 0x0019 Hewlett-Packard CU 1890 0x0020 Yamaha ADPCM 1891 0x0021 Speech Compression Sonarc 1892 0x0022 DSP Group TrueSpeech 1893 0x0023 Echo Speech EchoSC1 1894 0x0024 Audiofile AF36 1895 0x0025 Audio Processing Technology APTX 1896 0x0026 AudioFile AF10 1897 0x0027 Prosody 1612 1898 0x0028 LRC 1899 0x0030 Dolby AC2 1900 0x0031 Microsoft GSM 6.10 1901 0x0032 MSNAudio 1902 0x0033 Antex Electronics ADPCME 1903 0x0034 Control Resources VQLPC 1904 0x0035 DSP Solutions DigiREAL 1905 0x0036 DSP Solutions DigiADPCM 1906 0x0037 Control Resources CR10 1907 0x0038 Natural MicroSystems VBXADPCM 1908 0x0039 Crystal Semiconductor IMA ADPCM 1909 0x003A EchoSC3 1910 0x003B Rockwell ADPCM 1911 0x003C Rockwell Digit LK 1912 0x003D Xebec 1913 0x0040 Antex Electronics G.721 ADPCM 1914 0x0041 G.728 CELP 1915 0x0042 MSG723 1916 0x0050 MPEG Layer-2 or Layer-1 1917 0x0052 RT24 1918 0x0053 PAC 1919 0x0055 MPEG Layer-3 1920 0x0059 Lucent G.723 1921 0x0060 Cirrus 1922 0x0061 ESPCM 1923 0x0062 Voxware 1924 0x0063 Canopus Atrac 1925 0x0064 G.726 ADPCM 1926 0x0065 G.722 ADPCM 1927 0x0066 DSAT 1928 0x0067 DSAT Display 1929 0x0069 Voxware Byte Aligned 1930 0x0070 Voxware AC8 1931 0x0071 Voxware AC10 1932 0x0072 Voxware AC16 1933 0x0073 Voxware AC20 1934 0x0074 Voxware MetaVoice 1935 0x0075 Voxware MetaSound 1936 0x0076 Voxware RT29HW 1937 0x0077 Voxware VR12 1938 0x0078 Voxware VR18 1939 0x0079 Voxware TQ40 1940 0x0080 Softsound 1941 0x0081 Voxware TQ60 1942 0x0082 MSRT24 1943 0x0083 G.729A 1944 0x0084 MVI MV12 1945 0x0085 DF G.726 1946 0x0086 DF GSM610 1947 0x0088 ISIAudio 1948 0x0089 Onlive 1949 0x0091 SBC24 1950 0x0092 Dolby AC3 SPDIF 1951 0x0093 MediaSonic G.723 1952 0x0094 Aculab PLC Prosody 8kbps 1953 0x0097 ZyXEL ADPCM 1954 0x0098 Philips LPCBB 1955 0x0099 Packed 1956 0x00FF AAC 1957 0x0100 Rhetorex ADPCM 1958 0x0101 IBM mu-law 1959 0x0102 IBM A-law 1960 0x0103 IBM AVC Adaptive Differential Pulse Code Modulation (ADPCM) 1961 0x0111 Vivo G.723 1962 0x0112 Vivo Siren 1963 0x0123 Digital G.723 1964 0x0125 Sanyo LD ADPCM 1965 0x0130 Sipro Lab Telecom ACELP NET 1966 0x0131 Sipro Lab Telecom ACELP 4800 1967 0x0132 Sipro Lab Telecom ACELP 8V3 1968 0x0133 Sipro Lab Telecom G.729 1969 0x0134 Sipro Lab Telecom G.729A 1970 0x0135 Sipro Lab Telecom Kelvin 1971 0x0140 Windows Media Video V8 1972 0x0150 Qualcomm PureVoice 1973 0x0151 Qualcomm HalfRate 1974 0x0155 Ring Zero Systems TUB GSM 1975 0x0160 Microsoft Audio 1 1976 0x0161 Windows Media Audio V7 / V8 / V9 1977 0x0162 Windows Media Audio Professional V9 1978 0x0163 Windows Media Audio Lossless V9 1979 0x0200 Creative Labs ADPCM 1980 0x0202 Creative Labs Fastspeech8 1981 0x0203 Creative Labs Fastspeech10 1982 0x0210 UHER Informatic GmbH ADPCM 1983 0x0220 Quarterdeck 1984 0x0230 I-link Worldwide VC 1985 0x0240 Aureal RAW Sport 1986 0x0250 Interactive Products HSX 1987 0x0251 Interactive Products RPELP 1988 0x0260 Consistent Software CS2 1989 0x0270 Sony SCX 1990 0x0300 Fujitsu FM Towns Snd 1991 0x0400 BTV Digital 1992 0x0401 Intel Music Coder 1993 0x0450 QDesign Music 1994 0x0680 VME VMPCM 1995 0x0681 AT&T Labs TPC 1996 0x08AE ClearJump LiteWave 1997 0x1000 Olivetti GSM 1998 0x1001 Olivetti ADPCM 1999 0x1002 Olivetti CELP 2000 0x1003 Olivetti SBC 2001 0x1004 Olivetti OPR 2002 0x1100 Lernout & Hauspie Codec (0x1100) 2003 0x1101 Lernout & Hauspie CELP Codec (0x1101) 2004 0x1102 Lernout & Hauspie SBC Codec (0x1102) 2005 0x1103 Lernout & Hauspie SBC Codec (0x1103) 2006 0x1104 Lernout & Hauspie SBC Codec (0x1104) 2007 0x1400 Norris 2008 0x1401 AT&T ISIAudio 2009 0x1500 Soundspace Music Compression 2010 0x181C VoxWare RT24 Speech 2011 0x1FC4 NCT Soft ALF2CD (www.nctsoft.com) 2012 0x2000 Dolby AC3 2013 0x2001 Dolby DTS 2014 0x2002 WAVE_FORMAT_14_4 2015 0x2003 WAVE_FORMAT_28_8 2016 0x2004 WAVE_FORMAT_COOK 2017 0x2005 WAVE_FORMAT_DNET 2018 0x674F Ogg Vorbis 1 2019 0x6750 Ogg Vorbis 2 2020 0x6751 Ogg Vorbis 3 2021 0x676F Ogg Vorbis 1+ 2022 0x6770 Ogg Vorbis 2+ 2023 0x6771 Ogg Vorbis 3+ 2024 0x7A21 GSM-AMR (CBR, no SID) 2025 0x7A22 GSM-AMR (VBR, including SID) 2026 0xFFFE WAVE_FORMAT_EXTENSIBLE 2027 0xFFFF WAVE_FORMAT_DEVELOPMENT 2028 2029 */ 2030 2031 return getid3_lib::EmbeddedLookup('0x'.str_pad(strtoupper(dechex($wFormatTag)), 4, '0', STR_PAD_LEFT), $begin, __LINE__, __FILE__, 'riff-wFormatTag'); 2032 } 2033 2034 public static function fourccLookup($fourcc) { 2035 2036 $begin = __LINE__; 2037 2038 /** This is not a comment! 2039 2040 swot http://developer.apple.com/qa/snd/snd07.html 2041 ____ No Codec (____) 2042 _BIT BI_BITFIELDS (Raw RGB) 2043 _JPG JPEG compressed 2044 _PNG PNG compressed W3C/ISO/IEC (RFC-2083) 2045 _RAW Full Frames (Uncompressed) 2046 _RGB Raw RGB Bitmap 2047 _RL4 RLE 4bpp RGB 2048 _RL8 RLE 8bpp RGB 2049 3IV1 3ivx MPEG-4 v1 2050 3IV2 3ivx MPEG-4 v2 2051 3IVX 3ivx MPEG-4 2052 AASC Autodesk Animator 2053 ABYR Kensington ?ABYR? 2054 AEMI Array Microsystems VideoONE MPEG1-I Capture 2055 AFLC Autodesk Animator FLC 2056 AFLI Autodesk Animator FLI 2057 AMPG Array Microsystems VideoONE MPEG 2058 ANIM Intel RDX (ANIM) 2059 AP41 AngelPotion Definitive 2060 ASV1 Asus Video v1 2061 ASV2 Asus Video v2 2062 ASVX Asus Video 2.0 (audio) 2063 AUR2 AuraVision Aura 2 Codec - YUV 4:2:2 2064 AURA AuraVision Aura 1 Codec - YUV 4:1:1 2065 AVDJ Independent JPEG Group\'s codec (AVDJ) 2066 AVRN Independent JPEG Group\'s codec (AVRN) 2067 AYUV 4:4:4 YUV (AYUV) 2068 AZPR Quicktime Apple Video (AZPR) 2069 BGR Raw RGB32 2070 BLZ0 Blizzard DivX MPEG-4 2071 BTVC Conexant Composite Video 2072 BINK RAD Game Tools Bink Video 2073 BT20 Conexant Prosumer Video 2074 BTCV Conexant Composite Video Codec 2075 BW10 Data Translation Broadway MPEG Capture 2076 CC12 Intel YUV12 2077 CDVC Canopus DV 2078 CFCC Digital Processing Systems DPS Perception 2079 CGDI Microsoft Office 97 Camcorder Video 2080 CHAM Winnov Caviara Champagne 2081 CJPG Creative WebCam JPEG 2082 CLJR Cirrus Logic YUV 4:1:1 2083 CMYK Common Data Format in Printing (Colorgraph) 2084 CPLA Weitek 4:2:0 YUV Planar 2085 CRAM Microsoft Video 1 (CRAM) 2086 cvid Radius Cinepak 2087 CVID Radius Cinepak 2088 CWLT Microsoft Color WLT DIB 2089 CYUV Creative Labs YUV 2090 CYUY ATI YUV 2091 D261 H.261 2092 D263 H.263 2093 DIB Device Independent Bitmap 2094 DIV1 FFmpeg OpenDivX 2095 DIV2 Microsoft MPEG-4 v1/v2 2096 DIV3 DivX ;-) MPEG-4 v3.x Low-Motion 2097 DIV4 DivX ;-) MPEG-4 v3.x Fast-Motion 2098 DIV5 DivX MPEG-4 v5.x 2099 DIV6 DivX ;-) (MS MPEG-4 v3.x) 2100 DIVX DivX MPEG-4 v4 (OpenDivX / Project Mayo) 2101 divx DivX MPEG-4 2102 DMB1 Matrox Rainbow Runner hardware MJPEG 2103 DMB2 Paradigm MJPEG 2104 DSVD ?DSVD? 2105 DUCK Duck TrueMotion 1.0 2106 DPS0 DPS/Leitch Reality Motion JPEG 2107 DPSC DPS/Leitch PAR Motion JPEG 2108 DV25 Matrox DVCPRO codec 2109 DV50 Matrox DVCPRO50 codec 2110 DVC IEC 61834 and SMPTE 314M (DVC/DV Video) 2111 DVCP IEC 61834 and SMPTE 314M (DVC/DV Video) 2112 DVHD IEC Standard DV 1125 lines @ 30fps / 1250 lines @ 25fps 2113 DVMA Darim Vision DVMPEG (dummy for MPEG compressor) (www.darvision.com) 2114 DVSL IEC Standard DV compressed in SD (SDL) 2115 DVAN ?DVAN? 2116 DVE2 InSoft DVE-2 Videoconferencing 2117 dvsd IEC 61834 and SMPTE 314M DVC/DV Video 2118 DVSD IEC 61834 and SMPTE 314M DVC/DV Video 2119 DVX1 Lucent DVX1000SP Video Decoder 2120 DVX2 Lucent DVX2000S Video Decoder 2121 DVX3 Lucent DVX3000S Video Decoder 2122 DX50 DivX v5 2123 DXT1 Microsoft DirectX Compressed Texture (DXT1) 2124 DXT2 Microsoft DirectX Compressed Texture (DXT2) 2125 DXT3 Microsoft DirectX Compressed Texture (DXT3) 2126 DXT4 Microsoft DirectX Compressed Texture (DXT4) 2127 DXT5 Microsoft DirectX Compressed Texture (DXT5) 2128 DXTC Microsoft DirectX Compressed Texture (DXTC) 2129 DXTn Microsoft DirectX Compressed Texture (DXTn) 2130 EM2V Etymonix MPEG-2 I-frame (www.etymonix.com) 2131 EKQ0 Elsa ?EKQ0? 2132 ELK0 Elsa ?ELK0? 2133 ESCP Eidos Escape 2134 ETV1 eTreppid Video ETV1 2135 ETV2 eTreppid Video ETV2 2136 ETVC eTreppid Video ETVC 2137 FLIC Autodesk FLI/FLC Animation 2138 FLV1 Sorenson Spark 2139 FLV4 On2 TrueMotion VP6 2140 FRWT Darim Vision Forward Motion JPEG (www.darvision.com) 2141 FRWU Darim Vision Forward Uncompressed (www.darvision.com) 2142 FLJP D-Vision Field Encoded Motion JPEG 2143 FPS1 FRAPS v1 2144 FRWA SoftLab-Nsk Forward Motion JPEG w/ alpha channel 2145 FRWD SoftLab-Nsk Forward Motion JPEG 2146 FVF1 Iterated Systems Fractal Video Frame 2147 GLZW Motion LZW (gabest@freemail.hu) 2148 GPEG Motion JPEG (gabest@freemail.hu) 2149 GWLT Microsoft Greyscale WLT DIB 2150 H260 Intel ITU H.260 Videoconferencing 2151 H261 Intel ITU H.261 Videoconferencing 2152 H262 Intel ITU H.262 Videoconferencing 2153 H263 Intel ITU H.263 Videoconferencing 2154 H264 Intel ITU H.264 Videoconferencing 2155 H265 Intel ITU H.265 Videoconferencing 2156 H266 Intel ITU H.266 Videoconferencing 2157 H267 Intel ITU H.267 Videoconferencing 2158 H268 Intel ITU H.268 Videoconferencing 2159 H269 Intel ITU H.269 Videoconferencing 2160 HFYU Huffman Lossless Codec 2161 HMCR Rendition Motion Compensation Format (HMCR) 2162 HMRR Rendition Motion Compensation Format (HMRR) 2163 I263 FFmpeg I263 decoder 2164 IF09 Indeo YVU9 ("YVU9 with additional delta-frame info after the U plane") 2165 IUYV Interlaced version of UYVY (www.leadtools.com) 2166 IY41 Interlaced version of Y41P (www.leadtools.com) 2167 IYU1 12 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec IEEE standard 2168 IYU2 24 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec IEEE standard 2169 IYUV Planar YUV format (8-bpp Y plane, followed by 8-bpp 2×2 U and V planes) 2170 i263 Intel ITU H.263 Videoconferencing (i263) 2171 I420 Intel Indeo 4 2172 IAN Intel Indeo 4 (RDX) 2173 ICLB InSoft CellB Videoconferencing 2174 IGOR Power DVD 2175 IJPG Intergraph JPEG 2176 ILVC Intel Layered Video 2177 ILVR ITU-T H.263+ 2178 IPDV I-O Data Device Giga AVI DV Codec 2179 IR21 Intel Indeo 2.1 2180 IRAW Intel YUV Uncompressed 2181 IV30 Intel Indeo 3.0 2182 IV31 Intel Indeo 3.1 2183 IV32 Ligos Indeo 3.2 2184 IV33 Ligos Indeo 3.3 2185 IV34 Ligos Indeo 3.4 2186 IV35 Ligos Indeo 3.5 2187 IV36 Ligos Indeo 3.6 2188 IV37 Ligos Indeo 3.7 2189 IV38 Ligos Indeo 3.8 2190 IV39 Ligos Indeo 3.9 2191 IV40 Ligos Indeo Interactive 4.0 2192 IV41 Ligos Indeo Interactive 4.1 2193 IV42 Ligos Indeo Interactive 4.2 2194 IV43 Ligos Indeo Interactive 4.3 2195 IV44 Ligos Indeo Interactive 4.4 2196 IV45 Ligos Indeo Interactive 4.5 2197 IV46 Ligos Indeo Interactive 4.6 2198 IV47 Ligos Indeo Interactive 4.7 2199 IV48 Ligos Indeo Interactive 4.8 2200 IV49 Ligos Indeo Interactive 4.9 2201 IV50 Ligos Indeo Interactive 5.0 2202 JBYR Kensington ?JBYR? 2203 JPEG Still Image JPEG DIB 2204 JPGL Pegasus Lossless Motion JPEG 2205 KMVC Team17 Software Karl Morton\'s Video Codec 2206 LSVM Vianet Lighting Strike Vmail (Streaming) (www.vianet.com) 2207 LEAD LEAD Video Codec 2208 Ljpg LEAD MJPEG Codec 2209 MDVD Alex MicroDVD Video (hacked MS MPEG-4) (www.tiasoft.de) 2210 MJPA Morgan Motion JPEG (MJPA) (www.morgan-multimedia.com) 2211 MJPB Morgan Motion JPEG (MJPB) (www.morgan-multimedia.com) 2212 MMES Matrox MPEG-2 I-frame 2213 MP2v Microsoft S-Mpeg 4 version 1 (MP2v) 2214 MP42 Microsoft S-Mpeg 4 version 2 (MP42) 2215 MP43 Microsoft S-Mpeg 4 version 3 (MP43) 2216 MP4S Microsoft S-Mpeg 4 version 3 (MP4S) 2217 MP4V FFmpeg MPEG-4 2218 MPG1 FFmpeg MPEG 1/2 2219 MPG2 FFmpeg MPEG 1/2 2220 MPG3 FFmpeg DivX ;-) (MS MPEG-4 v3) 2221 MPG4 Microsoft MPEG-4 2222 MPGI Sigma Designs MPEG 2223 MPNG PNG images decoder 2224 MSS1 Microsoft Windows Screen Video 2225 MSZH LCL (Lossless Codec Library) (www.geocities.co.jp/Playtown-Denei/2837/LRC.htm) 2226 M261 Microsoft H.261 2227 M263 Microsoft H.263 2228 M4S2 Microsoft Fully Compliant MPEG-4 v2 simple profile (M4S2) 2229 m4s2 Microsoft Fully Compliant MPEG-4 v2 simple profile (m4s2) 2230 MC12 ATI Motion Compensation Format (MC12) 2231 MCAM ATI Motion Compensation Format (MCAM) 2232 MJ2C Morgan Multimedia Motion JPEG2000 2233 mJPG IBM Motion JPEG w/ Huffman Tables 2234 MJPG Microsoft Motion JPEG DIB 2235 MP42 Microsoft MPEG-4 (low-motion) 2236 MP43 Microsoft MPEG-4 (fast-motion) 2237 MP4S Microsoft MPEG-4 (MP4S) 2238 mp4s Microsoft MPEG-4 (mp4s) 2239 MPEG Chromatic Research MPEG-1 Video I-Frame 2240 MPG4 Microsoft MPEG-4 Video High Speed Compressor 2241 MPGI Sigma Designs MPEG 2242 MRCA FAST Multimedia Martin Regen Codec 2243 MRLE Microsoft Run Length Encoding 2244 MSVC Microsoft Video 1 2245 MTX1 Matrox ?MTX1? 2246 MTX2 Matrox ?MTX2? 2247 MTX3 Matrox ?MTX3? 2248 MTX4 Matrox ?MTX4? 2249 MTX5 Matrox ?MTX5? 2250 MTX6 Matrox ?MTX6? 2251 MTX7 Matrox ?MTX7? 2252 MTX8 Matrox ?MTX8? 2253 MTX9 Matrox ?MTX9? 2254 MV12 Motion Pixels Codec (old) 2255 MWV1 Aware Motion Wavelets 2256 nAVI SMR Codec (hack of Microsoft MPEG-4) (IRC #shadowrealm) 2257 NT00 NewTek LightWave HDTV YUV w/ Alpha (www.newtek.com) 2258 NUV1 NuppelVideo 2259 NTN1 Nogatech Video Compression 1 2260 NVS0 nVidia GeForce Texture (NVS0) 2261 NVS1 nVidia GeForce Texture (NVS1) 2262 NVS2 nVidia GeForce Texture (NVS2) 2263 NVS3 nVidia GeForce Texture (NVS3) 2264 NVS4 nVidia GeForce Texture (NVS4) 2265 NVS5 nVidia GeForce Texture (NVS5) 2266 NVT0 nVidia GeForce Texture (NVT0) 2267 NVT1 nVidia GeForce Texture (NVT1) 2268 NVT2 nVidia GeForce Texture (NVT2) 2269 NVT3 nVidia GeForce Texture (NVT3) 2270 NVT4 nVidia GeForce Texture (NVT4) 2271 NVT5 nVidia GeForce Texture (NVT5) 2272 PIXL MiroXL, Pinnacle PCTV 2273 PDVC I-O Data Device Digital Video Capture DV codec 2274 PGVV Radius Video Vision 2275 PHMO IBM Photomotion 2276 PIM1 MPEG Realtime (Pinnacle Cards) 2277 PIM2 Pegasus Imaging ?PIM2? 2278 PIMJ Pegasus Imaging Lossless JPEG 2279 PVEZ Horizons Technology PowerEZ 2280 PVMM PacketVideo Corporation MPEG-4 2281 PVW2 Pegasus Imaging Wavelet Compression 2282 Q1.0 Q-Team\'s QPEG 1.0 (www.q-team.de) 2283 Q1.1 Q-Team\'s QPEG 1.1 (www.q-team.de) 2284 QPEG Q-Team QPEG 1.0 2285 qpeq Q-Team QPEG 1.1 2286 RGB Raw BGR32 2287 RGBA Raw RGB w/ Alpha 2288 RMP4 REALmagic MPEG-4 (unauthorized XVID copy) (www.sigmadesigns.com) 2289 ROQV Id RoQ File Video Decoder 2290 RPZA Quicktime Apple Video (RPZA) 2291 RUD0 Rududu video codec (http://rududu.ifrance.com/rududu/) 2292 RV10 RealVideo 1.0 (aka RealVideo 5.0) 2293 RV13 RealVideo 1.0 (RV13) 2294 RV20 RealVideo G2 2295 RV30 RealVideo 8 2296 RV40 RealVideo 9 2297 RGBT Raw RGB w/ Transparency 2298 RLE Microsoft Run Length Encoder 2299 RLE4 Run Length Encoded (4bpp, 16-color) 2300 RLE8 Run Length Encoded (8bpp, 256-color) 2301 RT21 Intel Indeo RealTime Video 2.1 2302 rv20 RealVideo G2 2303 rv30 RealVideo 8 2304 RVX Intel RDX (RVX ) 2305 SMC Apple Graphics (SMC ) 2306 SP54 Logitech Sunplus Sp54 Codec for Mustek GSmart Mini 2 2307 SPIG Radius Spigot 2308 SVQ3 Sorenson Video 3 (Apple Quicktime 5) 2309 s422 Tekram VideoCap C210 YUV 4:2:2 2310 SDCC Sun Communication Digital Camera Codec 2311 SFMC CrystalNet Surface Fitting Method 2312 SMSC Radius SMSC 2313 SMSD Radius SMSD 2314 smsv WorldConnect Wavelet Video 2315 SPIG Radius Spigot 2316 SPLC Splash Studios ACM Audio Codec (www.splashstudios.net) 2317 SQZ2 Microsoft VXTreme Video Codec V2 2318 STVA ST Microelectronics CMOS Imager Data (Bayer) 2319 STVB ST Microelectronics CMOS Imager Data (Nudged Bayer) 2320 STVC ST Microelectronics CMOS Imager Data (Bunched) 2321 STVX ST Microelectronics CMOS Imager Data (Extended CODEC Data Format) 2322 STVY ST Microelectronics CMOS Imager Data (Extended CODEC Data Format with Correction Data) 2323 SV10 Sorenson Video R1 2324 SVQ1 Sorenson Video 2325 T420 Toshiba YUV 4:2:0 2326 TM2A Duck TrueMotion Archiver 2.0 (www.duck.com) 2327 TVJP Pinnacle/Truevision Targa 2000 board (TVJP) 2328 TVMJ Pinnacle/Truevision Targa 2000 board (TVMJ) 2329 TY0N Tecomac Low-Bit Rate Codec (www.tecomac.com) 2330 TY2C Trident Decompression Driver 2331 TLMS TeraLogic Motion Intraframe Codec (TLMS) 2332 TLST TeraLogic Motion Intraframe Codec (TLST) 2333 TM20 Duck TrueMotion 2.0 2334 TM2X Duck TrueMotion 2X 2335 TMIC TeraLogic Motion Intraframe Codec (TMIC) 2336 TMOT Horizons Technology TrueMotion S 2337 tmot Horizons TrueMotion Video Compression 2338 TR20 Duck TrueMotion RealTime 2.0 2339 TSCC TechSmith Screen Capture Codec 2340 TV10 Tecomac Low-Bit Rate Codec 2341 TY2N Trident ?TY2N? 2342 U263 UB Video H.263/H.263+/H.263++ Decoder 2343 UMP4 UB Video MPEG 4 (www.ubvideo.com) 2344 UYNV Nvidia UYVY packed 4:2:2 2345 UYVP Evans & Sutherland YCbCr 4:2:2 extended precision 2346 UCOD eMajix.com ClearVideo 2347 ULTI IBM Ultimotion 2348 UYVY UYVY packed 4:2:2 2349 V261 Lucent VX2000S 2350 VIFP VFAPI Reader Codec (www.yks.ne.jp/~hori/) 2351 VIV1 FFmpeg H263+ decoder 2352 VIV2 Vivo H.263 2353 VQC2 Vector-quantised codec 2 (research) http://eprints.ecs.soton.ac.uk/archive/00001310/01/VTC97-js.pdf) 2354 VTLP Alaris VideoGramPiX 2355 VYU9 ATI YUV (VYU9) 2356 VYUY ATI YUV (VYUY) 2357 V261 Lucent VX2000S 2358 V422 Vitec Multimedia 24-bit YUV 4:2:2 Format 2359 V655 Vitec Multimedia 16-bit YUV 4:2:2 Format 2360 VCR1 ATI Video Codec 1 2361 VCR2 ATI Video Codec 2 2362 VCR3 ATI VCR 3.0 2363 VCR4 ATI VCR 4.0 2364 VCR5 ATI VCR 5.0 2365 VCR6 ATI VCR 6.0 2366 VCR7 ATI VCR 7.0 2367 VCR8 ATI VCR 8.0 2368 VCR9 ATI VCR 9.0 2369 VDCT Vitec Multimedia Video Maker Pro DIB 2370 VDOM VDOnet VDOWave 2371 VDOW VDOnet VDOLive (H.263) 2372 VDTZ Darim Vison VideoTizer YUV 2373 VGPX Alaris VideoGramPiX 2374 VIDS Vitec Multimedia YUV 4:2:2 CCIR 601 for V422 2375 VIVO Vivo H.263 v2.00 2376 vivo Vivo H.263 2377 VIXL Miro/Pinnacle Video XL 2378 VLV1 VideoLogic/PURE Digital Videologic Capture 2379 VP30 On2 VP3.0 2380 VP31 On2 VP3.1 2381 VP6F On2 TrueMotion VP6 2382 VX1K Lucent VX1000S Video Codec 2383 VX2K Lucent VX2000S Video Codec 2384 VXSP Lucent VX1000SP Video Codec 2385 WBVC Winbond W9960 2386 WHAM Microsoft Video 1 (WHAM) 2387 WINX Winnov Software Compression 2388 WJPG AverMedia Winbond JPEG 2389 WMV1 Windows Media Video V7 2390 WMV2 Windows Media Video V8 2391 WMV3 Windows Media Video V9 2392 WNV1 Winnov Hardware Compression 2393 XYZP Extended PAL format XYZ palette (www.riff.org) 2394 x263 Xirlink H.263 2395 XLV0 NetXL Video Decoder 2396 XMPG Xing MPEG (I-Frame only) 2397 XVID XviD MPEG-4 (www.xvid.org) 2398 XXAN ?XXAN? 2399 YU92 Intel YUV (YU92) 2400 YUNV Nvidia Uncompressed YUV 4:2:2 2401 YUVP Extended PAL format YUV palette (www.riff.org) 2402 Y211 YUV 2:1:1 Packed 2403 Y411 YUV 4:1:1 Packed 2404 Y41B Weitek YUV 4:1:1 Planar 2405 Y41P Brooktree PC1 YUV 4:1:1 Packed 2406 Y41T Brooktree PC1 YUV 4:1:1 with transparency 2407 Y42B Weitek YUV 4:2:2 Planar 2408 Y42T Brooktree UYUV 4:2:2 with transparency 2409 Y422 ADS Technologies Copy of UYVY used in Pyro WebCam firewire camera 2410 Y800 Simple, single Y plane for monochrome images 2411 Y8 Grayscale video 2412 YC12 Intel YUV 12 codec 2413 YUV8 Winnov Caviar YUV8 2414 YUV9 Intel YUV9 2415 YUY2 Uncompressed YUV 4:2:2 2416 YUYV Canopus YUV 2417 YV12 YVU12 Planar 2418 YVU9 Intel YVU9 Planar (8-bpp Y plane, followed by 8-bpp 4x4 U and V planes) 2419 YVYU YVYU 4:2:2 Packed 2420 ZLIB Lossless Codec Library zlib compression (www.geocities.co.jp/Playtown-Denei/2837/LRC.htm) 2421 ZPEG Metheus Video Zipper 2422 2423 */ 2424 2425 return getid3_lib::EmbeddedLookup($fourcc, $begin, __LINE__, __FILE__, 'riff-fourcc'); 2426 } 2427 2428 private function EitherEndian2Int($byteword, $signed=false) { 2429 if ($this->getid3->info['fileformat'] == 'riff') { 2430 return getid3_lib::LittleEndian2Int($byteword, $signed); 2431 } 2432 return getid3_lib::BigEndian2Int($byteword, false, $signed); 2433 } 2434 2435 } -
new file wp-includes/ID3/module.audio.ac3.php
diff --git wp-includes/ID3/module.audio.ac3.php wp-includes/ID3/module.audio.ac3.php new file mode 100644 index 0000000..9834feb
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.audio.ac3.php // 11 // module for analyzing AC-3 (aka Dolby Digital) audio files // 12 // dependencies: NONE // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 17 class getid3_ac3 extends getid3_handler 18 { 19 private $AC3header = array(); 20 private $BSIoffset = 0; 21 22 const syncword = "\x0B\x77"; 23 24 public function Analyze() { 25 $info = &$this->getid3->info; 26 27 ///AH 28 $info['ac3']['raw']['bsi'] = array(); 29 $thisfile_ac3 = &$info['ac3']; 30 $thisfile_ac3_raw = &$thisfile_ac3['raw']; 31 $thisfile_ac3_raw_bsi = &$thisfile_ac3_raw['bsi']; 32 33 34 // http://www.atsc.org/standards/a_52a.pdf 35 36 $info['fileformat'] = 'ac3'; 37 38 // An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames 39 // Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256 40 // new audio samples per channel. A synchronization information (SI) header at the beginning 41 // of each frame contains information needed to acquire and maintain synchronization. A 42 // bit stream information (BSI) header follows SI, and contains parameters describing the coded 43 // audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the 44 // end of each frame is an error check field that includes a CRC word for error detection. An 45 // additional CRC word is located in the SI header, the use of which, by a decoder, is optional. 46 // 47 // syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC 48 49 // syncinfo() { 50 // syncword 16 51 // crc1 16 52 // fscod 2 53 // frmsizecod 6 54 // } /* end of syncinfo */ 55 56 $this->fseek($info['avdataoffset']); 57 $this->AC3header['syncinfo'] = $this->fread(5); 58 59 if (strpos($this->AC3header['syncinfo'], self::syncword) === 0) { 60 $thisfile_ac3_raw['synchinfo']['synchword'] = self::syncword; 61 $offset = 2; 62 } else { 63 if (!$this->isDependencyFor('matroska')) { 64 unset($info['fileformat'], $info['ac3']); 65 return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($this->AC3header['syncinfo'], 0, 2)).'"'); 66 } 67 $offset = 0; 68 $this->fseek(-2, SEEK_CUR); 69 } 70 71 $info['audio']['dataformat'] = 'ac3'; 72 $info['audio']['bitrate_mode'] = 'cbr'; 73 $info['audio']['lossless'] = false; 74 75 $thisfile_ac3_raw['synchinfo']['crc1'] = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], $offset, 2)); 76 $ac3_synchinfo_fscod_frmsizecod = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], ($offset + 2), 1)); 77 $thisfile_ac3_raw['synchinfo']['fscod'] = ($ac3_synchinfo_fscod_frmsizecod & 0xC0) >> 6; 78 $thisfile_ac3_raw['synchinfo']['frmsizecod'] = ($ac3_synchinfo_fscod_frmsizecod & 0x3F); 79 80 $thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']); 81 if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) { 82 $info['audio']['sample_rate'] = $thisfile_ac3['sample_rate']; 83 } 84 85 $thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']); 86 $thisfile_ac3['bitrate'] = self::bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']); 87 $info['audio']['bitrate'] = $thisfile_ac3['bitrate']; 88 89 $this->AC3header['bsi'] = getid3_lib::BigEndian2Bin($this->fread(15)); 90 $ac3_bsi_offset = 0; 91 92 $thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5); 93 if ($thisfile_ac3_raw_bsi['bsid'] > 8) { 94 // Decoders which can decode version 8 will thus be able to decode version numbers less than 8. 95 // If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used. 96 // Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8. 97 $this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8'); 98 unset($info['ac3']); 99 return false; 100 } 101 102 $thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3); 103 $thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3); 104 105 $thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); 106 $ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']); 107 foreach($ac3_coding_mode as $key => $value) { 108 $thisfile_ac3[$key] = $value; 109 } 110 switch ($thisfile_ac3_raw_bsi['acmod']) { 111 case 0: 112 case 1: 113 $info['audio']['channelmode'] = 'mono'; 114 break; 115 case 3: 116 case 4: 117 $info['audio']['channelmode'] = 'stereo'; 118 break; 119 default: 120 $info['audio']['channelmode'] = 'surround'; 121 break; 122 } 123 $info['audio']['channels'] = $thisfile_ac3['num_channels']; 124 125 if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) { 126 // If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream. 127 $thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2); 128 $thisfile_ac3['center_mix_level'] = self::centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']); 129 } 130 131 if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) { 132 // If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream. 133 $thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2); 134 $thisfile_ac3['surround_mix_level'] = self::surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']); 135 } 136 137 if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) { 138 // When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround. 139 $thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2); 140 $thisfile_ac3['dolby_surround_mode'] = self::dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']); 141 } 142 143 $thisfile_ac3_raw_bsi['lfeon'] = (bool) $this->readHeaderBSI(1); 144 $thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['lfeon']; 145 if ($thisfile_ac3_raw_bsi['lfeon']) { 146 //$info['audio']['channels']++; 147 $info['audio']['channels'] .= '.1'; 148 } 149 150 $thisfile_ac3['channels_enabled'] = self::channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']); 151 152 // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31. 153 // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent. 154 $thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5); 155 $thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB'; 156 157 $thisfile_ac3_raw_bsi['compre_flag'] = (bool) $this->readHeaderBSI(1); 158 if ($thisfile_ac3_raw_bsi['compre_flag']) { 159 $thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8); 160 $thisfile_ac3['heavy_compression'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr']); 161 } 162 163 $thisfile_ac3_raw_bsi['langcode_flag'] = (bool) $this->readHeaderBSI(1); 164 if ($thisfile_ac3_raw_bsi['langcode_flag']) { 165 $thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8); 166 } 167 168 $thisfile_ac3_raw_bsi['audprodie'] = (bool) $this->readHeaderBSI(1); 169 if ($thisfile_ac3_raw_bsi['audprodie']) { 170 $thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5); 171 $thisfile_ac3_raw_bsi['roomtyp'] = $this->readHeaderBSI(2); 172 173 $thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB'; 174 $thisfile_ac3['room_type'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']); 175 } 176 177 if ($thisfile_ac3_raw_bsi['acmod'] == 0x00) { 178 // If acmod is 0, then two completely independent program channels (dual mono) 179 // are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case, 180 // a number of additional items are present in BSI or audblk to fully describe Ch2. 181 182 // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31. 183 // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent. 184 $thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5); 185 $thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB'; 186 187 $thisfile_ac3_raw_bsi['compre_flag2'] = (bool) $this->readHeaderBSI(1); 188 if ($thisfile_ac3_raw_bsi['compre_flag2']) { 189 $thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8); 190 $thisfile_ac3['heavy_compression2'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr2']); 191 } 192 193 $thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) $this->readHeaderBSI(1); 194 if ($thisfile_ac3_raw_bsi['langcode_flag2']) { 195 $thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8); 196 } 197 198 $thisfile_ac3_raw_bsi['audprodie2'] = (bool) $this->readHeaderBSI(1); 199 if ($thisfile_ac3_raw_bsi['audprodie2']) { 200 $thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5); 201 $thisfile_ac3_raw_bsi['roomtyp2'] = $this->readHeaderBSI(2); 202 203 $thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB'; 204 $thisfile_ac3['room_type2'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']); 205 } 206 207 } 208 209 $thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1); 210 211 $thisfile_ac3_raw_bsi['original'] = (bool) $this->readHeaderBSI(1); 212 213 $thisfile_ac3_raw_bsi['timecode1_flag'] = (bool) $this->readHeaderBSI(1); 214 if ($thisfile_ac3_raw_bsi['timecode1_flag']) { 215 $thisfile_ac3_raw_bsi['timecode1'] = $this->readHeaderBSI(14); 216 } 217 218 $thisfile_ac3_raw_bsi['timecode2_flag'] = (bool) $this->readHeaderBSI(1); 219 if ($thisfile_ac3_raw_bsi['timecode2_flag']) { 220 $thisfile_ac3_raw_bsi['timecode2'] = $this->readHeaderBSI(14); 221 } 222 223 $thisfile_ac3_raw_bsi['addbsi_flag'] = (bool) $this->readHeaderBSI(1); 224 if ($thisfile_ac3_raw_bsi['addbsi_flag']) { 225 $thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6); 226 227 $this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin($this->fread($thisfile_ac3_raw_bsi['addbsi_length'])); 228 229 $thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8); 230 $this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8; 231 } 232 233 return true; 234 } 235 236 private function readHeaderBSI($length) { 237 $data = substr($this->AC3header['bsi'], $this->BSIoffset, $length); 238 $this->BSIoffset += $length; 239 240 return bindec($data); 241 } 242 243 public static function sampleRateCodeLookup($fscod) { 244 static $sampleRateCodeLookup = array( 245 0 => 48000, 246 1 => 44100, 247 2 => 32000, 248 3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute. 249 ); 250 return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false); 251 } 252 253 public static function serviceTypeLookup($bsmod, $acmod) { 254 static $serviceTypeLookup = array(); 255 if (empty($serviceTypeLookup)) { 256 for ($i = 0; $i <= 7; $i++) { 257 $serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)'; 258 $serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)'; 259 $serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)'; 260 $serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)'; 261 $serviceTypeLookup[4][$i] = 'associated service: dialogue (D)'; 262 $serviceTypeLookup[5][$i] = 'associated service: commentary (C)'; 263 $serviceTypeLookup[6][$i] = 'associated service: emergency (E)'; 264 } 265 266 $serviceTypeLookup[7][1] = 'associated service: voice over (VO)'; 267 for ($i = 2; $i <= 7; $i++) { 268 $serviceTypeLookup[7][$i] = 'main audio service: karaoke'; 269 } 270 } 271 return (isset($serviceTypeLookup[$bsmod][$acmod]) ? $serviceTypeLookup[$bsmod][$acmod] : false); 272 } 273 274 public static function audioCodingModeLookup($acmod) { 275 // array(channel configuration, # channels (not incl LFE), channel order) 276 static $audioCodingModeLookup = array ( 277 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'), 278 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'), 279 2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'), 280 3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'), 281 4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'), 282 5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'), 283 6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'), 284 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR'), 285 ); 286 return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false); 287 } 288 289 public static function centerMixLevelLookup($cmixlev) { 290 static $centerMixLevelLookup; 291 if (empty($centerMixLevelLookup)) { 292 $centerMixLevelLookup = array( 293 0 => pow(2, -3.0 / 6), // 0.707 (-3.0 dB) 294 1 => pow(2, -4.5 / 6), // 0.595 (-4.5 dB) 295 2 => pow(2, -6.0 / 6), // 0.500 (-6.0 dB) 296 3 => 'reserved' 297 ); 298 } 299 return (isset($centerMixLevelLookup[$cmixlev]) ? $centerMixLevelLookup[$cmixlev] : false); 300 } 301 302 public static function surroundMixLevelLookup($surmixlev) { 303 static $surroundMixLevelLookup; 304 if (empty($surroundMixLevelLookup)) { 305 $surroundMixLevelLookup = array( 306 0 => pow(2, -3.0 / 6), 307 1 => pow(2, -6.0 / 6), 308 2 => 0, 309 3 => 'reserved' 310 ); 311 } 312 return (isset($surroundMixLevelLookup[$surmixlev]) ? $surroundMixLevelLookup[$surmixlev] : false); 313 } 314 315 public static function dolbySurroundModeLookup($dsurmod) { 316 static $dolbySurroundModeLookup = array( 317 0 => 'not indicated', 318 1 => 'Not Dolby Surround encoded', 319 2 => 'Dolby Surround encoded', 320 3 => 'reserved' 321 ); 322 return (isset($dolbySurroundModeLookup[$dsurmod]) ? $dolbySurroundModeLookup[$dsurmod] : false); 323 } 324 325 public static function channelsEnabledLookup($acmod, $lfeon) { 326 $lookup = array( 327 'ch1'=>(bool) ($acmod == 0), 328 'ch2'=>(bool) ($acmod == 0), 329 'left'=>(bool) ($acmod > 1), 330 'right'=>(bool) ($acmod > 1), 331 'center'=>(bool) ($acmod & 0x01), 332 'surround_mono'=>false, 333 'surround_left'=>false, 334 'surround_right'=>false, 335 'lfe'=>$lfeon); 336 switch ($acmod) { 337 case 4: 338 case 5: 339 $lookup['surround_mono'] = true; 340 break; 341 case 6: 342 case 7: 343 $lookup['surround_left'] = true; 344 $lookup['surround_right'] = true; 345 break; 346 } 347 return $lookup; 348 } 349 350 public static function heavyCompression($compre) { 351 // The first four bits indicate gain changes in 6.02dB increments which can be 352 // implemented with an arithmetic shift operation. The following four bits 353 // indicate linear gain changes, and require a 5-bit multiply. 354 // We will represent the two 4-bit fields of compr as follows: 355 // X0 X1 X2 X3 . Y4 Y5 Y6 Y7 356 // The meaning of the X values is most simply described by considering X to represent a 4-bit 357 // signed integer with values from -8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The 358 // following table shows this in detail. 359 360 // Meaning of 4 msb of compr 361 // 7 +48.16 dB 362 // 6 +42.14 dB 363 // 5 +36.12 dB 364 // 4 +30.10 dB 365 // 3 +24.08 dB 366 // 2 +18.06 dB 367 // 1 +12.04 dB 368 // 0 +6.02 dB 369 // -1 0 dB 370 // -2 -6.02 dB 371 // -3 -12.04 dB 372 // -4 -18.06 dB 373 // -5 -24.08 dB 374 // -6 -30.10 dB 375 // -7 -36.12 dB 376 // -8 -42.14 dB 377 378 $fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT); 379 if ($fourbit{0} == '1') { 380 $log_gain = -8 + bindec(substr($fourbit, 1)); 381 } else { 382 $log_gain = bindec(substr($fourbit, 1)); 383 } 384 $log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2); 385 386 // The value of Y is a linear representation of a gain change of up to -6 dB. Y is considered to 387 // be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can 388 // represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain 389 // changes from -0.28 dB to -6.02 dB. 390 391 $lin_gain = (16 + ($compre & 0x0F)) / 32; 392 393 // The combination of X and Y values allows compr to indicate gain changes from 394 // 48.16 - 0.28 = +47.89 dB, to 395 // -42.14 - 6.02 = -48.16 dB. 396 397 return $log_gain - $lin_gain; 398 } 399 400 public static function roomTypeLookup($roomtyp) { 401 static $roomTypeLookup = array( 402 0 => 'not indicated', 403 1 => 'large room, X curve monitor', 404 2 => 'small room, flat monitor', 405 3 => 'reserved' 406 ); 407 return (isset($roomTypeLookup[$roomtyp]) ? $roomTypeLookup[$roomtyp] : false); 408 } 409 410 public static function frameSizeLookup($frmsizecod, $fscod) { 411 $padding = (bool) ($frmsizecod % 2); 412 $framesizeid = floor($frmsizecod / 2); 413 414 static $frameSizeLookup = array(); 415 if (empty($frameSizeLookup)) { 416 $frameSizeLookup = array ( 417 0 => array(128, 138, 192), 418 1 => array(40, 160, 174, 240), 419 2 => array(48, 192, 208, 288), 420 3 => array(56, 224, 242, 336), 421 4 => array(64, 256, 278, 384), 422 5 => array(80, 320, 348, 480), 423 6 => array(96, 384, 416, 576), 424 7 => array(112, 448, 486, 672), 425 8 => array(128, 512, 556, 768), 426 9 => array(160, 640, 696, 960), 427 10 => array(192, 768, 834, 1152), 428 11 => array(224, 896, 974, 1344), 429 12 => array(256, 1024, 1114, 1536), 430 13 => array(320, 1280, 1392, 1920), 431 14 => array(384, 1536, 1670, 2304), 432 15 => array(448, 1792, 1950, 2688), 433 16 => array(512, 2048, 2228, 3072), 434 17 => array(576, 2304, 2506, 3456), 435 18 => array(640, 2560, 2786, 3840) 436 ); 437 } 438 if (($fscod == 1) && $padding) { 439 // frame lengths are padded by 1 word (16 bits) at 44100 440 $frameSizeLookup[$frmsizecod] += 2; 441 } 442 return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] : false); 443 } 444 445 public static function bitrateLookup($frmsizecod) { 446 $framesizeid = floor($frmsizecod / 2); 447 448 static $bitrateLookup = array( 449 0 => 32000, 450 1 => 40000, 451 2 => 48000, 452 3 => 56000, 453 4 => 64000, 454 5 => 80000, 455 6 => 96000, 456 7 => 112000, 457 8 => 128000, 458 9 => 160000, 459 10 => 192000, 460 11 => 224000, 461 12 => 256000, 462 13 => 320000, 463 14 => 384000, 464 15 => 448000, 465 16 => 512000, 466 17 => 576000, 467 18 => 640000 468 ); 469 return (isset($bitrateLookup[$framesizeid]) ? $bitrateLookup[$framesizeid] : false); 470 } 471 472 473 } -
new file wp-includes/ID3/module.audio.dts.php
diff --git wp-includes/ID3/module.audio.dts.php wp-includes/ID3/module.audio.dts.php new file mode 100644 index 0000000..79982cc
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.audio.dts.php // 11 // module for analyzing DTS Audio files // 12 // dependencies: NONE // 13 // // 14 ///////////////////////////////////////////////////////////////// 15 16 17 /** 18 * @tutorial http://wiki.multimedia.cx/index.php?title=DTS 19 */ 20 class getid3_dts extends getid3_handler 21 { 22 /** 23 * Default DTS syncword used in native .cpt or .dts formats 24 */ 25 const syncword = "\x7F\xFE\x80\x01"; 26 27 private $readBinDataOffset = 0; 28 29 /** 30 * Possible syncwords indicating bitstream encoding 31 */ 32 public static $syncwords = array( 33 0 => "\x7F\xFE\x80\x01", // raw big-endian 34 1 => "\xFE\x7F\x01\x80", // raw little-endian 35 2 => "\x1F\xFF\xE8\x00", // 14-bit big-endian 36 3 => "\xFF\x1F\x00\xE8"); // 14-bit little-endian 37 38 public function Analyze() { 39 $info = &$this->getid3->info; 40 $info['fileformat'] = 'dts'; 41 42 $this->fseek($info['avdataoffset']); 43 $DTSheader = $this->fread(20); // we only need 2 words magic + 6 words frame header, but these words may be normal 16-bit words OR 14-bit words with 2 highest bits set to zero, so 8 words can be either 8*16/8 = 16 bytes OR 8*16*(16/14)/8 = 18.3 bytes 44 45 // check syncword 46 $sync = substr($DTSheader, 0, 4); 47 if (($encoding = array_search($sync, self::$syncwords)) !== false) { 48 49 $info['dts']['raw']['magic'] = $sync; 50 $this->readBinDataOffset = 32; 51 52 } elseif ($this->isDependencyFor('matroska')) { 53 54 // Matroska contains DTS without syncword encoded as raw big-endian format 55 $encoding = 0; 56 $this->readBinDataOffset = 0; 57 58 } else { 59 60 unset($info['fileformat']); 61 return $this->error('Expecting "'.implode('| ', array_map('getid3_lib::PrintHexBytes', self::$syncwords)).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($sync).'"'); 62 63 } 64 65 // decode header 66 $fhBS = ''; 67 for ($word_offset = 0; $word_offset <= strlen($DTSheader); $word_offset += 2) { 68 switch ($encoding) { 69 case 0: // raw big-endian 70 $fhBS .= getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) ); 71 break; 72 case 1: // raw little-endian 73 $fhBS .= getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))); 74 break; 75 case 2: // 14-bit big-endian 76 $fhBS .= substr(getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) ), 2, 14); 77 break; 78 case 3: // 14-bit little-endian 79 $fhBS .= substr(getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))), 2, 14); 80 break; 81 } 82 } 83 84 $info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, 1); 85 $info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, 5); 86 $info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, 1); 87 $info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, 7); 88 $info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, 14); 89 $info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, 6); 90 $info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, 4); 91 $info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, 5); 92 $info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, 1); 93 $info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, 1); 94 $info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, 1); 95 $info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, 1); 96 $info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, 1); 97 $info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, 3); 98 $info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, 1); 99 $info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, 1); 100 $info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, 2); 101 $info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, 1); 102 if ($info['dts']['flags']['crc_present']) { 103 $info['dts']['raw']['crc16'] = $this->readBinData($fhBS, 16); 104 } 105 $info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, 1); 106 $info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, 4); 107 $info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, 2); 108 $info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, 2); 109 $info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, 1); 110 $info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, 1); 111 $info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, 1); 112 $info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, 4); 113 114 115 $info['dts']['bitrate'] = self::bitrateLookup($info['dts']['raw']['bitrate']); 116 $info['dts']['bits_per_sample'] = self::bitPerSampleLookup($info['dts']['raw']['bits_per_sample']); 117 $info['dts']['sample_rate'] = self::sampleRateLookup($info['dts']['raw']['sample_frequency']); 118 $info['dts']['dialog_normalization'] = self::dialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']); 119 $info['dts']['flags']['lossless'] = (($info['dts']['raw']['bitrate'] == 31) ? true : false); 120 $info['dts']['bitrate_mode'] = (($info['dts']['raw']['bitrate'] == 30) ? 'vbr' : 'cbr'); 121 $info['dts']['channels'] = self::numChannelsLookup($info['dts']['raw']['channel_arrangement']); 122 $info['dts']['channel_arrangement'] = self::channelArrangementLookup($info['dts']['raw']['channel_arrangement']); 123 124 $info['audio']['dataformat'] = 'dts'; 125 $info['audio']['lossless'] = $info['dts']['flags']['lossless']; 126 $info['audio']['bitrate_mode'] = $info['dts']['bitrate_mode']; 127 $info['audio']['bits_per_sample'] = $info['dts']['bits_per_sample']; 128 $info['audio']['sample_rate'] = $info['dts']['sample_rate']; 129 $info['audio']['channels'] = $info['dts']['channels']; 130 $info['audio']['bitrate'] = $info['dts']['bitrate']; 131 if (isset($info['avdataend']) && !empty($info['dts']['bitrate']) && is_numeric($info['dts']['bitrate'])) { 132 $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8); 133 if (($encoding == 2) || ($encoding == 3)) { 134 // 14-bit data packed into 16-bit words, so the playtime is wrong because only (14/16) of the bytes in the data portion of the file are used at the specified bitrate 135 $info['playtime_seconds'] *= (14 / 16); 136 } 137 } 138 return true; 139 } 140 141 private function readBinData($bin, $length) { 142 $data = substr($bin, $this->readBinDataOffset, $length); 143 $this->readBinDataOffset += $length; 144 145 return bindec($data); 146 } 147 148 public static function bitrateLookup($index) { 149 static $lookup = array( 150 0 => 32000, 151 1 => 56000, 152 2 => 64000, 153 3 => 96000, 154 4 => 112000, 155 5 => 128000, 156 6 => 192000, 157 7 => 224000, 158 8 => 256000, 159 9 => 320000, 160 10 => 384000, 161 11 => 448000, 162 12 => 512000, 163 13 => 576000, 164 14 => 640000, 165 15 => 768000, 166 16 => 960000, 167 17 => 1024000, 168 18 => 1152000, 169 19 => 1280000, 170 20 => 1344000, 171 21 => 1408000, 172 22 => 1411200, 173 23 => 1472000, 174 24 => 1536000, 175 25 => 1920000, 176 26 => 2048000, 177 27 => 3072000, 178 28 => 3840000, 179 29 => 'open', 180 30 => 'variable', 181 31 => 'lossless', 182 ); 183 return (isset($lookup[$index]) ? $lookup[$index] : false); 184 } 185 186 public static function sampleRateLookup($index) { 187 static $lookup = array( 188 0 => 'invalid', 189 1 => 8000, 190 2 => 16000, 191 3 => 32000, 192 4 => 'invalid', 193 5 => 'invalid', 194 6 => 11025, 195 7 => 22050, 196 8 => 44100, 197 9 => 'invalid', 198 10 => 'invalid', 199 11 => 12000, 200 12 => 24000, 201 13 => 48000, 202 14 => 'invalid', 203 15 => 'invalid', 204 ); 205 return (isset($lookup[$index]) ? $lookup[$index] : false); 206 } 207 208 public static function bitPerSampleLookup($index) { 209 static $lookup = array( 210 0 => 16, 211 1 => 20, 212 2 => 24, 213 3 => 24, 214 ); 215 return (isset($lookup[$index]) ? $lookup[$index] : false); 216 } 217 218 public static function numChannelsLookup($index) { 219 switch ($index) { 220 case 0: 221 return 1; 222 break; 223 case 1: 224 case 2: 225 case 3: 226 case 4: 227 return 2; 228 break; 229 case 5: 230 case 6: 231 return 3; 232 break; 233 case 7: 234 case 8: 235 return 4; 236 break; 237 case 9: 238 return 5; 239 break; 240 case 10: 241 case 11: 242 case 12: 243 return 6; 244 break; 245 case 13: 246 return 7; 247 break; 248 case 14: 249 case 15: 250 return 8; 251 break; 252 } 253 return false; 254 } 255 256 public static function channelArrangementLookup($index) { 257 static $lookup = array( 258 0 => 'A', 259 1 => 'A + B (dual mono)', 260 2 => 'L + R (stereo)', 261 3 => '(L+R) + (L-R) (sum-difference)', 262 4 => 'LT + RT (left and right total)', 263 5 => 'C + L + R', 264 6 => 'L + R + S', 265 7 => 'C + L + R + S', 266 8 => 'L + R + SL + SR', 267 9 => 'C + L + R + SL + SR', 268 10 => 'CL + CR + L + R + SL + SR', 269 11 => 'C + L + R+ LR + RR + OV', 270 12 => 'CF + CR + LF + RF + LR + RR', 271 13 => 'CL + C + CR + L + R + SL + SR', 272 14 => 'CL + CR + L + R + SL1 + SL2 + SR1 + SR2', 273 15 => 'CL + C+ CR + L + R + SL + S + SR', 274 ); 275 return (isset($lookup[$index]) ? $lookup[$index] : 'user-defined'); 276 } 277 278 public static function dialogNormalization($index, $version) { 279 switch ($version) { 280 case 7: 281 return 0 - $index; 282 break; 283 case 6: 284 return 0 - 16 - $index; 285 break; 286 } 287 return false; 288 } 289 290 } -
new file wp-includes/ID3/module.audio.flac.php
diff --git wp-includes/ID3/module.audio.flac.php wp-includes/ID3/module.audio.flac.php new file mode 100644 index 0000000..6b9598c
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.audio.flac.php // 11 // module for analyzing FLAC and OggFLAC audio files // 12 // dependencies: module.audio.ogg.php // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 17 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); 18 19 /** 20 * @tutorial http://flac.sourceforge.net/format.html 21 */ 22 class getid3_flac extends getid3_handler 23 { 24 const syncword = 'fLaC'; 25 26 public function Analyze() { 27 $info = &$this->getid3->info; 28 29 $this->fseek($info['avdataoffset']); 30 $StreamMarker = $this->fread(4); 31 if ($StreamMarker != self::syncword) { 32 return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"'); 33 } 34 $info['fileformat'] = 'flac'; 35 $info['audio']['dataformat'] = 'flac'; 36 $info['audio']['bitrate_mode'] = 'vbr'; 37 $info['audio']['lossless'] = true; 38 39 // parse flac container 40 return $this->parseMETAdata(); 41 } 42 43 public function parseMETAdata() { 44 $info = &$this->getid3->info; 45 do { 46 $BlockOffset = $this->ftell(); 47 $BlockHeader = $this->fread(4); 48 $LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1)); 49 $LastBlockFlag = (bool) ($LBFBT & 0x80); 50 $BlockType = ($LBFBT & 0x7F); 51 $BlockLength = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3)); 52 $BlockTypeText = self::metaBlockTypeLookup($BlockType); 53 54 if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) { 55 $this->error('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file'); 56 break; 57 } 58 if ($BlockLength < 1) { 59 $this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid'); 60 break; 61 } 62 63 $info['flac'][$BlockTypeText]['raw'] = array(); 64 $BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw']; 65 66 $BlockTypeText_raw['offset'] = $BlockOffset; 67 $BlockTypeText_raw['last_meta_block'] = $LastBlockFlag; 68 $BlockTypeText_raw['block_type'] = $BlockType; 69 $BlockTypeText_raw['block_type_text'] = $BlockTypeText; 70 $BlockTypeText_raw['block_length'] = $BlockLength; 71 if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically 72 $BlockTypeText_raw['block_data'] = $this->fread($BlockLength); 73 } 74 75 switch ($BlockTypeText) { 76 case 'STREAMINFO': // 0x00 77 if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) { 78 return false; 79 } 80 break; 81 82 case 'PADDING': // 0x01 83 unset($info['flac']['PADDING']); // ignore 84 break; 85 86 case 'APPLICATION': // 0x02 87 if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) { 88 return false; 89 } 90 break; 91 92 case 'SEEKTABLE': // 0x03 93 if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) { 94 return false; 95 } 96 break; 97 98 case 'VORBIS_COMMENT': // 0x04 99 if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) { 100 return false; 101 } 102 break; 103 104 case 'CUESHEET': // 0x05 105 if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) { 106 return false; 107 } 108 break; 109 110 case 'PICTURE': // 0x06 111 if (!$this->parsePICTURE()) { 112 return false; 113 } 114 break; 115 116 default: 117 $this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset); 118 } 119 120 unset($info['flac'][$BlockTypeText]['raw']); 121 $info['avdataoffset'] = $this->ftell(); 122 } 123 while ($LastBlockFlag === false); 124 125 // handle tags 126 if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) { 127 $info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments']; 128 } 129 if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) { 130 $info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']); 131 } 132 133 // copy attachments to 'comments' array if nesesary 134 if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) { 135 foreach ($info['flac']['PICTURE'] as $entry) { 136 if (!empty($entry['data'])) { 137 $info['flac']['comments']['picture'][] = array('image_mime'=>$entry['image_mime'], 'data'=>$entry['data']); 138 } 139 } 140 } 141 142 if (isset($info['flac']['STREAMINFO'])) { 143 if (!$this->isDependencyFor('matroska')) { 144 $info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset']; 145 } 146 $info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8); 147 if ($info['flac']['uncompressed_audio_bytes'] == 0) { 148 return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero'); 149 } 150 if (!empty($info['flac']['compressed_audio_bytes'])) { 151 $info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes']; 152 } 153 } 154 155 // set md5_data_source - built into flac 0.5+ 156 if (isset($info['flac']['STREAMINFO']['audio_signature'])) { 157 158 if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { 159 $this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'); 160 } 161 else { 162 $info['md5_data_source'] = ''; 163 $md5 = $info['flac']['STREAMINFO']['audio_signature']; 164 for ($i = 0; $i < strlen($md5); $i++) { 165 $info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT); 166 } 167 if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) { 168 unset($info['md5_data_source']); 169 } 170 } 171 } 172 173 if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) { 174 $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; 175 if ($info['audio']['bits_per_sample'] == 8) { 176 // special case 177 // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value 178 // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed 179 $this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file'); 180 } 181 } 182 183 return true; 184 } 185 186 private function parseSTREAMINFO($BlockData) { 187 $info = &$this->getid3->info; 188 189 $info['flac']['STREAMINFO'] = array(); 190 $streaminfo = &$info['flac']['STREAMINFO']; 191 192 $streaminfo['min_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2)); 193 $streaminfo['max_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2)); 194 $streaminfo['min_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3)); 195 $streaminfo['max_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3)); 196 197 $SRCSBSS = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8)); 198 $streaminfo['sample_rate'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 0, 20)); 199 $streaminfo['channels'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1; 200 $streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1; 201 $streaminfo['samples_stream'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36)); 202 203 $streaminfo['audio_signature'] = substr($BlockData, 18, 16); 204 205 if (!empty($streaminfo['sample_rate'])) { 206 207 $info['audio']['bitrate_mode'] = 'vbr'; 208 $info['audio']['sample_rate'] = $streaminfo['sample_rate']; 209 $info['audio']['channels'] = $streaminfo['channels']; 210 $info['audio']['bits_per_sample'] = $streaminfo['bits_per_sample']; 211 $info['playtime_seconds'] = $streaminfo['samples_stream'] / $streaminfo['sample_rate']; 212 if ($info['playtime_seconds'] > 0) { 213 if (!$this->isDependencyFor('matroska')) { 214 $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; 215 } 216 else { 217 $this->warning('Cannot determine audio bitrate because total stream size is unknown'); 218 } 219 } 220 221 } else { 222 return $this->error('Corrupt METAdata block: STREAMINFO'); 223 } 224 225 return true; 226 } 227 228 private function parseAPPLICATION($BlockData) { 229 $info = &$this->getid3->info; 230 231 $ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4)); 232 $info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID); 233 $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4); 234 235 return true; 236 } 237 238 private function parseSEEKTABLE($BlockData) { 239 $info = &$this->getid3->info; 240 241 $offset = 0; 242 $BlockLength = strlen($BlockData); 243 $placeholderpattern = str_repeat("\xFF", 8); 244 while ($offset < $BlockLength) { 245 $SampleNumberString = substr($BlockData, $offset, 8); 246 $offset += 8; 247 if ($SampleNumberString == $placeholderpattern) { 248 249 // placeholder point 250 getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1); 251 $offset += 10; 252 253 } else { 254 255 $SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString); 256 $info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); 257 $offset += 8; 258 $info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2)); 259 $offset += 2; 260 261 } 262 } 263 264 return true; 265 } 266 267 private function parseVORBIS_COMMENT($BlockData) { 268 $info = &$this->getid3->info; 269 270 $getid3_ogg = new getid3_ogg($this->getid3); 271 if ($this->isDependencyFor('matroska')) { 272 $getid3_ogg->setStringMode($this->data_string); 273 } 274 $getid3_ogg->ParseVorbisComments(); 275 if (isset($info['ogg'])) { 276 unset($info['ogg']['comments_raw']); 277 $info['flac']['VORBIS_COMMENT'] = $info['ogg']; 278 unset($info['ogg']); 279 } 280 281 unset($getid3_ogg); 282 283 return true; 284 } 285 286 private function parseCUESHEET($BlockData) { 287 $info = &$this->getid3->info; 288 $offset = 0; 289 $info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($BlockData, $offset, 128), "\0"); 290 $offset += 128; 291 $info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); 292 $offset += 8; 293 $info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80); 294 $offset += 1; 295 296 $offset += 258; // reserved 297 298 $info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); 299 $offset += 1; 300 301 for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) { 302 $TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); 303 $offset += 8; 304 $TrackNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); 305 $offset += 1; 306 307 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset; 308 309 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($BlockData, $offset, 12); 310 $offset += 12; 311 312 $TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); 313 $offset += 1; 314 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80); 315 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40); 316 317 $offset += 13; // reserved 318 319 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); 320 $offset += 1; 321 322 for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) { 323 $IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); 324 $offset += 8; 325 $IndexNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); 326 $offset += 1; 327 328 $offset += 3; // reserved 329 330 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset; 331 } 332 } 333 334 return true; 335 } 336 337 /** 338 * Parse METADATA_BLOCK_PICTURE flac structure and extract attachment 339 * External usage: audio.ogg 340 */ 341 public function parsePICTURE() { 342 $info = &$this->getid3->info; 343 344 $picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4)); 345 $picture['type'] = self::pictureTypeLookup($picture['typeid']); 346 $picture['image_mime'] = $this->fread(getid3_lib::BigEndian2Int($this->fread(4))); 347 $descr_length = getid3_lib::BigEndian2Int($this->fread(4)); 348 if ($descr_length) { 349 $picture['description'] = $this->fread($descr_length); 350 } 351 $picture['width'] = getid3_lib::BigEndian2Int($this->fread(4)); 352 $picture['height'] = getid3_lib::BigEndian2Int($this->fread(4)); 353 $picture['color_depth'] = getid3_lib::BigEndian2Int($this->fread(4)); 354 $picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4)); 355 $data_length = getid3_lib::BigEndian2Int($this->fread(4)); 356 357 if ($picture['image_mime'] == '-->') { 358 $picture['data'] = $this->fread($data_length); 359 } else { 360 $picture['data'] = $this->saveAttachment( 361 str_replace('/', '_', $picture['type']).'_'.$this->ftell(), 362 $this->ftell(), 363 $data_length, 364 $picture['image_mime']); 365 } 366 367 $info['flac']['PICTURE'][] = $picture; 368 369 return true; 370 } 371 372 public static function metaBlockTypeLookup($blocktype) { 373 static $lookup = array( 374 0 => 'STREAMINFO', 375 1 => 'PADDING', 376 2 => 'APPLICATION', 377 3 => 'SEEKTABLE', 378 4 => 'VORBIS_COMMENT', 379 5 => 'CUESHEET', 380 6 => 'PICTURE', 381 ); 382 return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved'); 383 } 384 385 public static function applicationIDLookup($applicationid) { 386 // http://flac.sourceforge.net/id.html 387 static $lookup = array( 388 0x41544348 => 'FlacFile', // "ATCH" 389 0x42534F4C => 'beSolo', // "BSOL" 390 0x42554753 => 'Bugs Player', // "BUGS" 391 0x43756573 => 'GoldWave cue points (specification)', // "Cues" 392 0x46696361 => 'CUE Splitter', // "Fica" 393 0x46746F6C => 'flac-tools', // "Ftol" 394 0x4D4F5442 => 'MOTB MetaCzar', // "MOTB" 395 0x4D505345 => 'MP3 Stream Editor', // "MPSE" 396 0x4D754D4C => 'MusicML: Music Metadata Language', // "MuML" 397 0x52494646 => 'Sound Devices RIFF chunk storage', // "RIFF" 398 0x5346464C => 'Sound Font FLAC', // "SFFL" 399 0x534F4E59 => 'Sony Creative Software', // "SONY" 400 0x5351455A => 'flacsqueeze', // "SQEZ" 401 0x54745776 => 'TwistedWave', // "TtWv" 402 0x55495453 => 'UITS Embedding tools', // "UITS" 403 0x61696666 => 'FLAC AIFF chunk storage', // "aiff" 404 0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks', // "imag" 405 0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // "peem" 406 0x71667374 => 'QFLAC Studio', // "qfst" 407 0x72696666 => 'FLAC RIFF chunk storage', // "riff" 408 0x74756E65 => 'TagTuner', // "tune" 409 0x78626174 => 'XBAT', // "xbat" 410 0x786D6364 => 'xmcd', // "xmcd" 411 ); 412 return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved'); 413 } 414 415 public static function pictureTypeLookup($type_id) { 416 static $lookup = array ( 417 0 => 'Other', 418 1 => '32x32 pixels \'file icon\' (PNG only)', 419 2 => 'Other file icon', 420 3 => 'Cover (front)', 421 4 => 'Cover (back)', 422 5 => 'Leaflet page', 423 6 => 'Media (e.g. label side of CD)', 424 7 => 'Lead artist/lead performer/soloist', 425 8 => 'Artist/performer', 426 9 => 'Conductor', 427 10 => 'Band/Orchestra', 428 11 => 'Composer', 429 12 => 'Lyricist/text writer', 430 13 => 'Recording Location', 431 14 => 'During recording', 432 15 => 'During performance', 433 16 => 'Movie/video screen capture', 434 17 => 'A bright coloured fish', 435 18 => 'Illustration', 436 19 => 'Band/artist logotype', 437 20 => 'Publisher/Studio logotype', 438 ); 439 return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved'); 440 } 441 442 } -
new file wp-includes/ID3/module.audio.mp3.php
diff --git wp-includes/ID3/module.audio.mp3.php wp-includes/ID3/module.audio.mp3.php new file mode 100644 index 0000000..e6ffea9
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.audio.mp3.php // 11 // module for analyzing MP3 files // 12 // dependencies: NONE // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 17 // number of frames to scan to determine if MPEG-audio sequence is valid 18 // Lower this number to 5-20 for faster scanning 19 // Increase this number to 50+ for most accurate detection of valid VBR/CBR 20 // mpeg-audio streams 21 define('GETID3_MP3_VALID_CHECK_FRAMES', 35); 22 23 24 class getid3_mp3 extends getid3_handler 25 { 26 27 public $allow_bruteforce = false; // forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, unrecommended, but may provide data from otherwise-unusuable files 28 29 public function Analyze() { 30 $info = &$this->getid3->info; 31 32 $initialOffset = $info['avdataoffset']; 33 34 if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) { 35 if ($this->allow_bruteforce) { 36 $info['error'][] = 'Rescanning file in BruteForce mode'; 37 $this->getOnlyMPEGaudioInfoBruteForce($this->getid3->fp, $info); 38 } 39 } 40 41 42 if (isset($info['mpeg']['audio']['bitrate_mode'])) { 43 $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); 44 } 45 46 if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) { 47 48 $synchoffsetwarning = 'Unknown data before synch '; 49 if (isset($info['id3v2']['headerlength'])) { 50 $synchoffsetwarning .= '(ID3v2 header ends at '.$info['id3v2']['headerlength'].', then '.($info['avdataoffset'] - $info['id3v2']['headerlength']).' bytes garbage, '; 51 } elseif ($initialOffset > 0) { 52 $synchoffsetwarning .= '(should be at '.$initialOffset.', '; 53 } else { 54 $synchoffsetwarning .= '(should be at beginning of file, '; 55 } 56 $synchoffsetwarning .= 'synch detected at '.$info['avdataoffset'].')'; 57 if (isset($info['audio']['bitrate_mode']) && ($info['audio']['bitrate_mode'] == 'cbr')) { 58 59 if (!empty($info['id3v2']['headerlength']) && (($info['avdataoffset'] - $info['id3v2']['headerlength']) == $info['mpeg']['audio']['framelength'])) { 60 61 $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.'; 62 $info['audio']['codec'] = 'LAME'; 63 $CurrentDataLAMEversionString = 'LAME3.'; 64 65 } elseif (empty($info['id3v2']['headerlength']) && ($info['avdataoffset'] == $info['mpeg']['audio']['framelength'])) { 66 67 $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.'; 68 $info['audio']['codec'] = 'LAME'; 69 $CurrentDataLAMEversionString = 'LAME3.'; 70 71 } 72 73 } 74 $info['warning'][] = $synchoffsetwarning; 75 76 } 77 78 if (isset($info['mpeg']['audio']['LAME'])) { 79 $info['audio']['codec'] = 'LAME'; 80 if (!empty($info['mpeg']['audio']['LAME']['long_version'])) { 81 $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['long_version'], "\x00"); 82 } elseif (!empty($info['mpeg']['audio']['LAME']['short_version'])) { 83 $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['short_version'], "\x00"); 84 } 85 } 86 87 $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : (isset($info['audio']['encoder']) ? $info['audio']['encoder'] : '')); 88 if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) { 89 // a version number of LAME that does not end with a number like "LAME3.92" 90 // or with a closing parenthesis like "LAME3.88 (alpha)" 91 // or a version of LAME with the LAMEtag-not-filled-in-DLL-mode bug (3.90-3.92) 92 93 // not sure what the actual last frame length will be, but will be less than or equal to 1441 94 $PossiblyLongerLAMEversion_FrameLength = 1441; 95 96 // Not sure what version of LAME this is - look in padding of last frame for longer version string 97 $PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength; 98 fseek($this->getid3->fp, $PossibleLAMEversionStringOffset); 99 $PossiblyLongerLAMEversion_Data = fread($this->getid3->fp, $PossiblyLongerLAMEversion_FrameLength); 100 switch (substr($CurrentDataLAMEversionString, -1)) { 101 case 'a': 102 case 'b': 103 // "LAME3.94a" will have a longer version string of "LAME3.94 (alpha)" for example 104 // need to trim off "a" to match longer string 105 $CurrentDataLAMEversionString = substr($CurrentDataLAMEversionString, 0, -1); 106 break; 107 } 108 if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) { 109 if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) { 110 $PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)" 111 if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) { 112 $info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString; 113 } 114 } 115 } 116 } 117 if (!empty($info['audio']['encoder'])) { 118 $info['audio']['encoder'] = rtrim($info['audio']['encoder'], "\x00 "); 119 } 120 121 switch (isset($info['mpeg']['audio']['layer']) ? $info['mpeg']['audio']['layer'] : '') { 122 case 1: 123 case 2: 124 $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; 125 break; 126 } 127 if (isset($info['fileformat']) && ($info['fileformat'] == 'mp3')) { 128 switch ($info['audio']['dataformat']) { 129 case 'mp1': 130 case 'mp2': 131 case 'mp3': 132 $info['fileformat'] = $info['audio']['dataformat']; 133 break; 134 135 default: 136 $info['warning'][] = 'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"'; 137 break; 138 } 139 } 140 141 if (empty($info['fileformat'])) { 142 unset($info['fileformat']); 143 unset($info['audio']['bitrate_mode']); 144 unset($info['avdataoffset']); 145 unset($info['avdataend']); 146 return false; 147 } 148 149 $info['mime_type'] = 'audio/mpeg'; 150 $info['audio']['lossless'] = false; 151 152 // Calculate playtime 153 if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) { 154 $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['audio']['bitrate']; 155 } 156 157 $info['audio']['encoder_options'] = $this->GuessEncoderOptions(); 158 159 return true; 160 } 161 162 163 public function GuessEncoderOptions() { 164 // shortcuts 165 $info = &$this->getid3->info; 166 if (!empty($info['mpeg']['audio'])) { 167 $thisfile_mpeg_audio = &$info['mpeg']['audio']; 168 if (!empty($thisfile_mpeg_audio['LAME'])) { 169 $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME']; 170 } 171 } 172 173 $encoder_options = ''; 174 static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256); 175 176 if (isset($thisfile_mpeg_audio['VBR_method']) && ($thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) { 177 178 $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality']; 179 180 } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) { 181 182 $encoder_options = $thisfile_mpeg_audio_lame['preset_used']; 183 184 } elseif (!empty($thisfile_mpeg_audio_lame['vbr_quality'])) { 185 186 static $KnownEncoderValues = array(); 187 if (empty($KnownEncoderValues)) { 188 189 //$KnownEncoderValues[abrbitrate_minbitrate][vbr_quality][raw_vbr_method][raw_noise_shaping][raw_stereo_mode][ath_type][lowpass_frequency] = 'preset name'; 190 $KnownEncoderValues[0xFF][58][1][1][3][2][20500] = '--alt-preset insane'; // 3.90, 3.90.1, 3.92 191 $KnownEncoderValues[0xFF][58][1][1][3][2][20600] = '--alt-preset insane'; // 3.90.2, 3.90.3, 3.91 192 $KnownEncoderValues[0xFF][57][1][1][3][4][20500] = '--alt-preset insane'; // 3.94, 3.95 193 $KnownEncoderValues['**'][78][3][2][3][2][19500] = '--alt-preset extreme'; // 3.90, 3.90.1, 3.92 194 $KnownEncoderValues['**'][78][3][2][3][2][19600] = '--alt-preset extreme'; // 3.90.2, 3.91 195 $KnownEncoderValues['**'][78][3][1][3][2][19600] = '--alt-preset extreme'; // 3.90.3 196 $KnownEncoderValues['**'][78][4][2][3][2][19500] = '--alt-preset fast extreme'; // 3.90, 3.90.1, 3.92 197 $KnownEncoderValues['**'][78][4][2][3][2][19600] = '--alt-preset fast extreme'; // 3.90.2, 3.90.3, 3.91 198 $KnownEncoderValues['**'][78][3][2][3][4][19000] = '--alt-preset standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 199 $KnownEncoderValues['**'][78][3][1][3][4][19000] = '--alt-preset standard'; // 3.90.3 200 $KnownEncoderValues['**'][78][4][2][3][4][19000] = '--alt-preset fast standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 201 $KnownEncoderValues['**'][78][4][1][3][4][19000] = '--alt-preset fast standard'; // 3.90.3 202 $KnownEncoderValues['**'][88][4][1][3][3][19500] = '--r3mix'; // 3.90, 3.90.1, 3.92 203 $KnownEncoderValues['**'][88][4][1][3][3][19600] = '--r3mix'; // 3.90.2, 3.90.3, 3.91 204 $KnownEncoderValues['**'][67][4][1][3][4][18000] = '--r3mix'; // 3.94, 3.95 205 $KnownEncoderValues['**'][68][3][2][3][4][18000] = '--alt-preset medium'; // 3.90.3 206 $KnownEncoderValues['**'][68][4][2][3][4][18000] = '--alt-preset fast medium'; // 3.90.3 207 208 $KnownEncoderValues[0xFF][99][1][1][1][2][0] = '--preset studio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 209 $KnownEncoderValues[0xFF][58][2][1][3][2][20600] = '--preset studio'; // 3.90.3, 3.93.1 210 $KnownEncoderValues[0xFF][58][2][1][3][2][20500] = '--preset studio'; // 3.93 211 $KnownEncoderValues[0xFF][57][2][1][3][4][20500] = '--preset studio'; // 3.94, 3.95 212 $KnownEncoderValues[0xC0][88][1][1][1][2][0] = '--preset cd'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 213 $KnownEncoderValues[0xC0][58][2][2][3][2][19600] = '--preset cd'; // 3.90.3, 3.93.1 214 $KnownEncoderValues[0xC0][58][2][2][3][2][19500] = '--preset cd'; // 3.93 215 $KnownEncoderValues[0xC0][57][2][1][3][4][19500] = '--preset cd'; // 3.94, 3.95 216 $KnownEncoderValues[0xA0][78][1][1][3][2][18000] = '--preset hifi'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 217 $KnownEncoderValues[0xA0][58][2][2][3][2][18000] = '--preset hifi'; // 3.90.3, 3.93, 3.93.1 218 $KnownEncoderValues[0xA0][57][2][1][3][4][18000] = '--preset hifi'; // 3.94, 3.95 219 $KnownEncoderValues[0x80][67][1][1][3][2][18000] = '--preset tape'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 220 $KnownEncoderValues[0x80][67][1][1][3][2][15000] = '--preset radio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 221 $KnownEncoderValues[0x70][67][1][1][3][2][15000] = '--preset fm'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 222 $KnownEncoderValues[0x70][58][2][2][3][2][16000] = '--preset tape/radio/fm'; // 3.90.3, 3.93, 3.93.1 223 $KnownEncoderValues[0x70][57][2][1][3][4][16000] = '--preset tape/radio/fm'; // 3.94, 3.95 224 $KnownEncoderValues[0x38][58][2][2][0][2][10000] = '--preset voice'; // 3.90.3, 3.93, 3.93.1 225 $KnownEncoderValues[0x38][57][2][1][0][4][15000] = '--preset voice'; // 3.94, 3.95 226 $KnownEncoderValues[0x38][57][2][1][0][4][16000] = '--preset voice'; // 3.94a14 227 $KnownEncoderValues[0x28][65][1][1][0][2][7500] = '--preset mw-us'; // 3.90, 3.90.1, 3.92 228 $KnownEncoderValues[0x28][65][1][1][0][2][7600] = '--preset mw-us'; // 3.90.2, 3.91 229 $KnownEncoderValues[0x28][58][2][2][0][2][7000] = '--preset mw-us'; // 3.90.3, 3.93, 3.93.1 230 $KnownEncoderValues[0x28][57][2][1][0][4][10500] = '--preset mw-us'; // 3.94, 3.95 231 $KnownEncoderValues[0x28][57][2][1][0][4][11200] = '--preset mw-us'; // 3.94a14 232 $KnownEncoderValues[0x28][57][2][1][0][4][8800] = '--preset mw-us'; // 3.94a15 233 $KnownEncoderValues[0x18][58][2][2][0][2][4000] = '--preset phon+/lw/mw-eu/sw'; // 3.90.3, 3.93.1 234 $KnownEncoderValues[0x18][58][2][2][0][2][3900] = '--preset phon+/lw/mw-eu/sw'; // 3.93 235 $KnownEncoderValues[0x18][57][2][1][0][4][5900] = '--preset phon+/lw/mw-eu/sw'; // 3.94, 3.95 236 $KnownEncoderValues[0x18][57][2][1][0][4][6200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a14 237 $KnownEncoderValues[0x18][57][2][1][0][4][3200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a15 238 $KnownEncoderValues[0x10][58][2][2][0][2][3800] = '--preset phone'; // 3.90.3, 3.93.1 239 $KnownEncoderValues[0x10][58][2][2][0][2][3700] = '--preset phone'; // 3.93 240 $KnownEncoderValues[0x10][57][2][1][0][4][5600] = '--preset phone'; // 3.94, 3.95 241 } 242 243 if (isset($KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) { 244 245 $encoder_options = $KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']]; 246 247 } elseif (isset($KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) { 248 249 $encoder_options = $KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']]; 250 251 } elseif ($info['audio']['bitrate_mode'] == 'vbr') { 252 253 // http://gabriel.mp3-tech.org/mp3infotag.html 254 // int Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h 255 256 257 $LAME_V_value = 10 - ceil($thisfile_mpeg_audio_lame['vbr_quality'] / 10); 258 $LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10); 259 $encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value; 260 261 } elseif ($info['audio']['bitrate_mode'] == 'cbr') { 262 263 $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); 264 265 } else { 266 267 $encoder_options = strtoupper($info['audio']['bitrate_mode']); 268 269 } 270 271 } elseif (!empty($thisfile_mpeg_audio_lame['bitrate_abr'])) { 272 273 $encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr']; 274 275 } elseif (!empty($info['audio']['bitrate'])) { 276 277 if ($info['audio']['bitrate_mode'] == 'cbr') { 278 $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); 279 } else { 280 $encoder_options = strtoupper($info['audio']['bitrate_mode']); 281 } 282 283 } 284 if (!empty($thisfile_mpeg_audio_lame['bitrate_min'])) { 285 $encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min']; 286 } 287 288 if (!empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']) || !empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'])) { 289 $encoder_options .= ' --nogap'; 290 } 291 292 if (!empty($thisfile_mpeg_audio_lame['lowpass_frequency'])) { 293 $ExplodedOptions = explode(' ', $encoder_options, 4); 294 if ($ExplodedOptions[0] == '--r3mix') { 295 $ExplodedOptions[1] = 'r3mix'; 296 } 297 switch ($ExplodedOptions[0]) { 298 case '--preset': 299 case '--alt-preset': 300 case '--r3mix': 301 if ($ExplodedOptions[1] == 'fast') { 302 $ExplodedOptions[1] .= ' '.$ExplodedOptions[2]; 303 } 304 switch ($ExplodedOptions[1]) { 305 case 'portable': 306 case 'medium': 307 case 'standard': 308 case 'extreme': 309 case 'insane': 310 case 'fast portable': 311 case 'fast medium': 312 case 'fast standard': 313 case 'fast extreme': 314 case 'fast insane': 315 case 'r3mix': 316 static $ExpectedLowpass = array( 317 'insane|20500' => 20500, 318 'insane|20600' => 20600, // 3.90.2, 3.90.3, 3.91 319 'medium|18000' => 18000, 320 'fast medium|18000' => 18000, 321 'extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95 322 'extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1 323 'fast extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95 324 'fast extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1 325 'standard|19000' => 19000, 326 'fast standard|19000' => 19000, 327 'r3mix|19500' => 19500, // 3.90, 3.90.1, 3.92 328 'r3mix|19600' => 19600, // 3.90.2, 3.90.3, 3.91 329 'r3mix|18000' => 18000, // 3.94, 3.95 330 ); 331 if (!isset($ExpectedLowpass[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio_lame['lowpass_frequency']]) && ($thisfile_mpeg_audio_lame['lowpass_frequency'] < 22050) && (round($thisfile_mpeg_audio_lame['lowpass_frequency'] / 1000) < round($thisfile_mpeg_audio['sample_rate'] / 2000))) { 332 $encoder_options .= ' --lowpass '.$thisfile_mpeg_audio_lame['lowpass_frequency']; 333 } 334 break; 335 336 default: 337 break; 338 } 339 break; 340 } 341 } 342 343 if (isset($thisfile_mpeg_audio_lame['raw']['source_sample_freq'])) { 344 if (($thisfile_mpeg_audio['sample_rate'] == 44100) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 1)) { 345 $encoder_options .= ' --resample 44100'; 346 } elseif (($thisfile_mpeg_audio['sample_rate'] == 48000) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 2)) { 347 $encoder_options .= ' --resample 48000'; 348 } elseif ($thisfile_mpeg_audio['sample_rate'] < 44100) { 349 switch ($thisfile_mpeg_audio_lame['raw']['source_sample_freq']) { 350 case 0: // <= 32000 351 // may or may not be same as source frequency - ignore 352 break; 353 case 1: // 44100 354 case 2: // 48000 355 case 3: // 48000+ 356 $ExplodedOptions = explode(' ', $encoder_options, 4); 357 switch ($ExplodedOptions[0]) { 358 case '--preset': 359 case '--alt-preset': 360 switch ($ExplodedOptions[1]) { 361 case 'fast': 362 case 'portable': 363 case 'medium': 364 case 'standard': 365 case 'extreme': 366 case 'insane': 367 $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; 368 break; 369 370 default: 371 static $ExpectedResampledRate = array( 372 'phon+/lw/mw-eu/sw|16000' => 16000, 373 'mw-us|24000' => 24000, // 3.95 374 'mw-us|32000' => 32000, // 3.93 375 'mw-us|16000' => 16000, // 3.92 376 'phone|16000' => 16000, 377 'phone|11025' => 11025, // 3.94a15 378 'radio|32000' => 32000, // 3.94a15 379 'fm/radio|32000' => 32000, // 3.92 380 'fm|32000' => 32000, // 3.90 381 'voice|32000' => 32000); 382 if (!isset($ExpectedResampledRate[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio['sample_rate']])) { 383 $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; 384 } 385 break; 386 } 387 break; 388 389 case '--r3mix': 390 default: 391 $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; 392 break; 393 } 394 break; 395 } 396 } 397 } 398 if (empty($encoder_options) && !empty($info['audio']['bitrate']) && !empty($info['audio']['bitrate_mode'])) { 399 //$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); 400 $encoder_options = strtoupper($info['audio']['bitrate_mode']); 401 } 402 403 return $encoder_options; 404 } 405 406 407 public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { 408 static $MPEGaudioVersionLookup; 409 static $MPEGaudioLayerLookup; 410 static $MPEGaudioBitrateLookup; 411 static $MPEGaudioFrequencyLookup; 412 static $MPEGaudioChannelModeLookup; 413 static $MPEGaudioModeExtensionLookup; 414 static $MPEGaudioEmphasisLookup; 415 if (empty($MPEGaudioVersionLookup)) { 416 $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); 417 $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); 418 $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); 419 $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); 420 $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); 421 $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); 422 $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); 423 } 424 425 if (fseek($this->getid3->fp, $offset, SEEK_SET) != 0) { 426 $info['error'][] = 'decodeMPEGaudioHeader() failed to seek to next offset at '.$offset; 427 return false; 428 } 429 //$headerstring = fread($this->getid3->fp, 1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame 430 $headerstring = fread($this->getid3->fp, 226); // LAME header at offset 36 + 190 bytes of Xing/LAME data 431 432 // MP3 audio frame structure: 433 // $aa $aa $aa $aa [$bb $bb] $cc... 434 // where $aa..$aa is the four-byte mpeg-audio header (below) 435 // $bb $bb is the optional 2-byte CRC 436 // and $cc... is the audio data 437 438 $head4 = substr($headerstring, 0, 4); 439 440 static $MPEGaudioHeaderDecodeCache = array(); 441 if (isset($MPEGaudioHeaderDecodeCache[$head4])) { 442 $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4]; 443 } else { 444 $MPEGheaderRawArray = self::MPEGaudioHeaderDecode($head4); 445 $MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray; 446 } 447 448 static $MPEGaudioHeaderValidCache = array(); 449 if (!isset($MPEGaudioHeaderValidCache[$head4])) { // Not in cache 450 //$MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1) 451 $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false); 452 } 453 454 // shortcut 455 if (!isset($info['mpeg']['audio'])) { 456 $info['mpeg']['audio'] = array(); 457 } 458 $thisfile_mpeg_audio = &$info['mpeg']['audio']; 459 460 461 if ($MPEGaudioHeaderValidCache[$head4]) { 462 $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray; 463 } else { 464 $info['error'][] = 'Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset; 465 return false; 466 } 467 468 if (!$FastMPEGheaderScan) { 469 $thisfile_mpeg_audio['version'] = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']]; 470 $thisfile_mpeg_audio['layer'] = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']]; 471 472 $thisfile_mpeg_audio['channelmode'] = $MPEGaudioChannelModeLookup[$thisfile_mpeg_audio['raw']['channelmode']]; 473 $thisfile_mpeg_audio['channels'] = (($thisfile_mpeg_audio['channelmode'] == 'mono') ? 1 : 2); 474 $thisfile_mpeg_audio['sample_rate'] = $MPEGaudioFrequencyLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['raw']['sample_rate']]; 475 $thisfile_mpeg_audio['protection'] = !$thisfile_mpeg_audio['raw']['protection']; 476 $thisfile_mpeg_audio['private'] = (bool) $thisfile_mpeg_audio['raw']['private']; 477 $thisfile_mpeg_audio['modeextension'] = $MPEGaudioModeExtensionLookup[$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['modeextension']]; 478 $thisfile_mpeg_audio['copyright'] = (bool) $thisfile_mpeg_audio['raw']['copyright']; 479 $thisfile_mpeg_audio['original'] = (bool) $thisfile_mpeg_audio['raw']['original']; 480 $thisfile_mpeg_audio['emphasis'] = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']]; 481 482 $info['audio']['channels'] = $thisfile_mpeg_audio['channels']; 483 $info['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate']; 484 485 if ($thisfile_mpeg_audio['protection']) { 486 $thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2)); 487 } 488 } 489 490 if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) { 491 // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0 492 $info['warning'][] = 'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1'; 493 $thisfile_mpeg_audio['raw']['bitrate'] = 0; 494 } 495 $thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding']; 496 $thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']]; 497 498 if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $info['avdataoffset'])) { 499 // only skip multiple frame check if free-format bitstream found at beginning of file 500 // otherwise is quite possibly simply corrupted data 501 $recursivesearch = false; 502 } 503 504 // For Layer 2 there are some combinations of bitrate and mode which are not allowed. 505 if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) { 506 507 $info['audio']['dataformat'] = 'mp2'; 508 switch ($thisfile_mpeg_audio['channelmode']) { 509 510 case 'mono': 511 if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) { 512 // these are ok 513 } else { 514 $info['error'][] = $thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; 515 return false; 516 } 517 break; 518 519 case 'stereo': 520 case 'joint stereo': 521 case 'dual channel': 522 if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) { 523 // these are ok 524 } else { 525 $info['error'][] = intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; 526 return false; 527 } 528 break; 529 530 } 531 532 } 533 534 535 if ($info['audio']['sample_rate'] > 0) { 536 $thisfile_mpeg_audio['framelength'] = self::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']); 537 } 538 539 $nextframetestoffset = $offset + 1; 540 if ($thisfile_mpeg_audio['bitrate'] != 'free') { 541 542 $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; 543 544 if (isset($thisfile_mpeg_audio['framelength'])) { 545 $nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength']; 546 } else { 547 $info['error'][] = 'Frame at offset('.$offset.') is has an invalid frame length.'; 548 return false; 549 } 550 551 } 552 553 $ExpectedNumberOfAudioBytes = 0; 554 555 //////////////////////////////////////////////////////////////////////////////////// 556 // Variable-bitrate headers 557 558 if (substr($headerstring, 4 + 32, 4) == 'VBRI') { 559 // Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36) 560 // specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html 561 562 $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; 563 $thisfile_mpeg_audio['VBR_method'] = 'Fraunhofer'; 564 $info['audio']['codec'] = 'Fraunhofer'; 565 566 $SideInfoData = substr($headerstring, 4 + 2, 32); 567 568 $FraunhoferVBROffset = 36; 569 570 $thisfile_mpeg_audio['VBR_encoder_version'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 4, 2)); // VbriVersion 571 $thisfile_mpeg_audio['VBR_encoder_delay'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 6, 2)); // VbriDelay 572 $thisfile_mpeg_audio['VBR_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 8, 2)); // VbriQuality 573 $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4)); // VbriStreamBytes 574 $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4)); // VbriStreamFrames 575 $thisfile_mpeg_audio['VBR_seek_offsets'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2)); // VbriTableSize 576 $thisfile_mpeg_audio['VBR_seek_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 2)); // VbriTableScale 577 $thisfile_mpeg_audio['VBR_entry_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 22, 2)); // VbriEntryBytes 578 $thisfile_mpeg_audio['VBR_entry_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2)); // VbriEntryFrames 579 580 $ExpectedNumberOfAudioBytes = $thisfile_mpeg_audio['VBR_bytes']; 581 582 $previousbyteoffset = $offset; 583 for ($i = 0; $i < $thisfile_mpeg_audio['VBR_seek_offsets']; $i++) { 584 $Fraunhofer_OffsetN = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, $thisfile_mpeg_audio['VBR_entry_bytes'])); 585 $FraunhoferVBROffset += $thisfile_mpeg_audio['VBR_entry_bytes']; 586 $thisfile_mpeg_audio['VBR_offsets_relative'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']); 587 $thisfile_mpeg_audio['VBR_offsets_absolute'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']) + $previousbyteoffset; 588 $previousbyteoffset += $Fraunhofer_OffsetN; 589 } 590 591 592 } else { 593 594 // Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36) 595 // depending on MPEG layer and number of channels 596 597 $VBRidOffset = self::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']); 598 $SideInfoData = substr($headerstring, 4 + 2, $VBRidOffset - 4); 599 600 if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) { 601 // 'Xing' is traditional Xing VBR frame 602 // 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.) 603 // 'Info' *can* legally be used to specify a VBR file as well, however. 604 605 // http://www.multiweb.cz/twoinches/MP3inside.htm 606 //00..03 = "Xing" or "Info" 607 //04..07 = Flags: 608 // 0x01 Frames Flag set if value for number of frames in file is stored 609 // 0x02 Bytes Flag set if value for filesize in bytes is stored 610 // 0x04 TOC Flag set if values for TOC are stored 611 // 0x08 VBR Scale Flag set if values for VBR scale is stored 612 //08..11 Frames: Number of frames in file (including the first Xing/Info one) 613 //12..15 Bytes: File length in Bytes 614 //16..115 TOC (Table of Contents): 615 // Contains of 100 indexes (one Byte length) for easier lookup in file. Approximately solves problem with moving inside file. 616 // Each Byte has a value according this formula: 617 // (TOC[i] / 256) * fileLenInBytes 618 // So if song lasts eg. 240 sec. and you want to jump to 60. sec. (and file is 5 000 000 Bytes length) you can use: 619 // TOC[(60/240)*100] = TOC[25] 620 // and corresponding Byte in file is then approximately at: 621 // (TOC[25]/256) * 5000000 622 //116..119 VBR Scale 623 624 625 // should be safe to leave this at 'vbr' and let it be overriden to 'cbr' if a CBR preset/mode is used by LAME 626 // if (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Xing') { 627 $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; 628 $thisfile_mpeg_audio['VBR_method'] = 'Xing'; 629 // } else { 630 // $ScanAsCBR = true; 631 // $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; 632 // } 633 634 $thisfile_mpeg_audio['xing_flags_raw'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4)); 635 636 $thisfile_mpeg_audio['xing_flags']['frames'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000001); 637 $thisfile_mpeg_audio['xing_flags']['bytes'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000002); 638 $thisfile_mpeg_audio['xing_flags']['toc'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000004); 639 $thisfile_mpeg_audio['xing_flags']['vbr_scale'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000008); 640 641 if ($thisfile_mpeg_audio['xing_flags']['frames']) { 642 $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 8, 4)); 643 //$thisfile_mpeg_audio['VBR_frames']--; // don't count header Xing/Info frame 644 } 645 if ($thisfile_mpeg_audio['xing_flags']['bytes']) { 646 $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4)); 647 } 648 649 //if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { 650 if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { 651 652 $framelengthfloat = $thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']; 653 654 if ($thisfile_mpeg_audio['layer'] == '1') { 655 // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 656 //$info['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; 657 $info['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 12; 658 } else { 659 // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 660 //$info['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; 661 $info['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 144; 662 } 663 $thisfile_mpeg_audio['framelength'] = floor($framelengthfloat); 664 } 665 666 if ($thisfile_mpeg_audio['xing_flags']['toc']) { 667 $LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100); 668 for ($i = 0; $i < 100; $i++) { 669 $thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData{$i}); 670 } 671 } 672 if ($thisfile_mpeg_audio['xing_flags']['vbr_scale']) { 673 $thisfile_mpeg_audio['VBR_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4)); 674 } 675 676 677 // http://gabriel.mp3-tech.org/mp3infotag.html 678 if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') { 679 680 // shortcut 681 $thisfile_mpeg_audio['LAME'] = array(); 682 $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME']; 683 684 685 $thisfile_mpeg_audio_lame['long_version'] = substr($headerstring, $VBRidOffset + 120, 20); 686 $thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9); 687 688 if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') { 689 690 // extra 11 chars are not part of version string when LAMEtag present 691 unset($thisfile_mpeg_audio_lame['long_version']); 692 693 // It the LAME tag was only introduced in LAME v3.90 694 // http://www.hydrogenaudio.org/?act=ST&f=15&t=9933 695 696 // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html 697 // are assuming a 'Xing' identifier offset of 0x24, which is the case for 698 // MPEG-1 non-mono, but not for other combinations 699 $LAMEtagOffsetContant = $VBRidOffset - 0x24; 700 701 // shortcuts 702 $thisfile_mpeg_audio_lame['RGAD'] = array('track'=>array(), 'album'=>array()); 703 $thisfile_mpeg_audio_lame_RGAD = &$thisfile_mpeg_audio_lame['RGAD']; 704 $thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track']; 705 $thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album']; 706 $thisfile_mpeg_audio_lame['raw'] = array(); 707 $thisfile_mpeg_audio_lame_raw = &$thisfile_mpeg_audio_lame['raw']; 708 709 // byte $9B VBR Quality 710 // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications. 711 // Actually overwrites original Xing bytes 712 unset($thisfile_mpeg_audio['VBR_scale']); 713 $thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1)); 714 715 // bytes $9C-$A4 Encoder short VersionString 716 $thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9); 717 718 // byte $A5 Info Tag revision + VBR method 719 $LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1)); 720 721 $thisfile_mpeg_audio_lame['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4; 722 $thisfile_mpeg_audio_lame_raw['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F; 723 $thisfile_mpeg_audio_lame['vbr_method'] = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']); 724 $thisfile_mpeg_audio['bitrate_mode'] = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr' 725 726 // byte $A6 Lowpass filter value 727 $thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100; 728 729 // bytes $A7-$AE Replay Gain 730 // http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html 731 // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude" 732 if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') { 733 // LAME 3.94a16 and later - 9.23 fixed point 734 // ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375 735 $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608); 736 } else { 737 // LAME 3.94a15 and earlier - 32-bit floating point 738 // Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15 739 $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4)); 740 } 741 if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) { 742 unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']); 743 } else { 744 $thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']); 745 } 746 747 $thisfile_mpeg_audio_lame_raw['RGAD_track'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2)); 748 $thisfile_mpeg_audio_lame_raw['RGAD_album'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2)); 749 750 751 if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) { 752 753 $thisfile_mpeg_audio_lame_RGAD_track['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13; 754 $thisfile_mpeg_audio_lame_RGAD_track['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10; 755 $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9; 756 $thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF; 757 $thisfile_mpeg_audio_lame_RGAD_track['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']); 758 $thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']); 759 $thisfile_mpeg_audio_lame_RGAD_track['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']); 760 761 if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { 762 $info['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; 763 } 764 $info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator']; 765 $info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db']; 766 } else { 767 unset($thisfile_mpeg_audio_lame_RGAD['track']); 768 } 769 if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) { 770 771 $thisfile_mpeg_audio_lame_RGAD_album['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13; 772 $thisfile_mpeg_audio_lame_RGAD_album['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10; 773 $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9; 774 $thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF; 775 $thisfile_mpeg_audio_lame_RGAD_album['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']); 776 $thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']); 777 $thisfile_mpeg_audio_lame_RGAD_album['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']); 778 779 if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { 780 $info['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; 781 } 782 $info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator']; 783 $info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db']; 784 } else { 785 unset($thisfile_mpeg_audio_lame_RGAD['album']); 786 } 787 if (empty($thisfile_mpeg_audio_lame_RGAD)) { 788 unset($thisfile_mpeg_audio_lame['RGAD']); 789 } 790 791 792 // byte $AF Encoding flags + ATH Type 793 $EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1)); 794 $thisfile_mpeg_audio_lame['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10); 795 $thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20); 796 $thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40); 797 $thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80); 798 $thisfile_mpeg_audio_lame['ath_type'] = $EncodingFlagsATHtype & 0x0F; 799 800 // byte $B0 if ABR {specified bitrate} else {minimal bitrate} 801 $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1)); 802 if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR) 803 $thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']; 804 } elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR) 805 // ignore 806 } elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate 807 $thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']; 808 } 809 810 // bytes $B1-$B3 Encoder delays 811 $EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3)); 812 $thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12; 813 $thisfile_mpeg_audio_lame['end_padding'] = $EncoderDelays & 0x000FFF; 814 815 // byte $B4 Misc 816 $MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1)); 817 $thisfile_mpeg_audio_lame_raw['noise_shaping'] = ($MiscByte & 0x03); 818 $thisfile_mpeg_audio_lame_raw['stereo_mode'] = ($MiscByte & 0x1C) >> 2; 819 $thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5; 820 $thisfile_mpeg_audio_lame_raw['source_sample_freq'] = ($MiscByte & 0xC0) >> 6; 821 $thisfile_mpeg_audio_lame['noise_shaping'] = $thisfile_mpeg_audio_lame_raw['noise_shaping']; 822 $thisfile_mpeg_audio_lame['stereo_mode'] = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']); 823 $thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality']; 824 $thisfile_mpeg_audio_lame['source_sample_freq'] = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']); 825 826 // byte $B5 MP3 Gain 827 $thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true); 828 $thisfile_mpeg_audio_lame['mp3_gain_db'] = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain']; 829 $thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6)); 830 831 // bytes $B6-$B7 Preset and surround info 832 $PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2)); 833 // Reserved = ($PresetSurroundBytes & 0xC000); 834 $thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800); 835 $thisfile_mpeg_audio_lame['surround_info'] = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']); 836 $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF); 837 $thisfile_mpeg_audio_lame['preset_used'] = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame); 838 if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) { 839 $info['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org'; 840 } 841 if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) { 842 // this may change if 3.90.4 ever comes out 843 $thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3'; 844 } 845 846 // bytes $B8-$BB MusicLength 847 $thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4)); 848 $ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']); 849 850 // bytes $BC-$BD MusicCRC 851 $thisfile_mpeg_audio_lame['music_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2)); 852 853 // bytes $BE-$BF CRC-16 of Info Tag 854 $thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2)); 855 856 857 // LAME CBR 858 if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { 859 860 $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; 861 $thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']); 862 $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; 863 //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) { 864 // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min']; 865 //} 866 867 } 868 869 } 870 } 871 872 } else { 873 874 // not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header) 875 $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; 876 if ($recursivesearch) { 877 $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; 878 if ($this->RecursiveFrameScanning($offset, $nextframetestoffset, true)) { 879 $recursivesearch = false; 880 $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; 881 } 882 if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') { 883 $info['warning'][] = 'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.'; 884 } 885 } 886 887 } 888 889 } 890 891 if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) { 892 if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) { 893 if (isset($info['fileformat']) && ($info['fileformat'] == 'riff')) { 894 // ignore, audio data is broken into chunks so will always be data "missing" 895 } elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) { 896 $info['warning'][] = 'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'; 897 } else { 898 $info['warning'][] = 'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)'; 899 } 900 } else { 901 if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) { 902 // $prenullbytefileoffset = ftell($this->getid3->fp); 903 // fseek($this->getid3->fp, $info['avdataend'], SEEK_SET); 904 // $PossibleNullByte = fread($this->getid3->fp, 1); 905 // fseek($this->getid3->fp, $prenullbytefileoffset, SEEK_SET); 906 // if ($PossibleNullByte === "\x00") { 907 $info['avdataend']--; 908 // $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; 909 // } else { 910 // $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; 911 // } 912 } else { 913 $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; 914 } 915 } 916 } 917 918 if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) { 919 if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) { 920 $framebytelength = $this->FreeFormatFrameLength($offset, true); 921 if ($framebytelength > 0) { 922 $thisfile_mpeg_audio['framelength'] = $framebytelength; 923 if ($thisfile_mpeg_audio['layer'] == '1') { 924 // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 925 $info['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; 926 } else { 927 // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 928 $info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; 929 } 930 } else { 931 $info['error'][] = 'Error calculating frame length of free-format MP3 without Xing/LAME header'; 932 } 933 } 934 } 935 936 if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') { 937 switch ($thisfile_mpeg_audio['bitrate_mode']) { 938 case 'vbr': 939 case 'abr': 940 $bytes_per_frame = 1152; 941 if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) { 942 $bytes_per_frame = 384; 943 } elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) { 944 $bytes_per_frame = 576; 945 } 946 $thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0); 947 if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) { 948 $info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; 949 $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion 950 } 951 break; 952 } 953 } 954 955 // End variable-bitrate headers 956 //////////////////////////////////////////////////////////////////////////////////// 957 958 if ($recursivesearch) { 959 960 if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) { 961 return false; 962 } 963 964 } 965 966 967 //if (false) { 968 // // experimental side info parsing section - not returning anything useful yet 969 // 970 // $SideInfoBitstream = getid3_lib::BigEndian2Bin($SideInfoData); 971 // $SideInfoOffset = 0; 972 // 973 // if ($thisfile_mpeg_audio['version'] == '1') { 974 // if ($thisfile_mpeg_audio['channelmode'] == 'mono') { 975 // // MPEG-1 (mono) 976 // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9); 977 // $SideInfoOffset += 9; 978 // $SideInfoOffset += 5; 979 // } else { 980 // // MPEG-1 (stereo, joint-stereo, dual-channel) 981 // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9); 982 // $SideInfoOffset += 9; 983 // $SideInfoOffset += 3; 984 // } 985 // } else { // 2 or 2.5 986 // if ($thisfile_mpeg_audio['channelmode'] == 'mono') { 987 // // MPEG-2, MPEG-2.5 (mono) 988 // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8); 989 // $SideInfoOffset += 8; 990 // $SideInfoOffset += 1; 991 // } else { 992 // // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel) 993 // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8); 994 // $SideInfoOffset += 8; 995 // $SideInfoOffset += 2; 996 // } 997 // } 998 // 999 // if ($thisfile_mpeg_audio['version'] == '1') { 1000 // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) { 1001 // for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) { 1002 // $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1); 1003 // $SideInfoOffset += 2; 1004 // } 1005 // } 1006 // } 1007 // for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) { 1008 // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) { 1009 // $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12); 1010 // $SideInfoOffset += 12; 1011 // $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); 1012 // $SideInfoOffset += 9; 1013 // $thisfile_mpeg_audio['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8); 1014 // $SideInfoOffset += 8; 1015 // if ($thisfile_mpeg_audio['version'] == '1') { 1016 // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4); 1017 // $SideInfoOffset += 4; 1018 // } else { 1019 // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); 1020 // $SideInfoOffset += 9; 1021 // } 1022 // $thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); 1023 // $SideInfoOffset += 1; 1024 // 1025 // if ($thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] == '1') { 1026 // 1027 // $thisfile_mpeg_audio['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2); 1028 // $SideInfoOffset += 2; 1029 // $thisfile_mpeg_audio['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); 1030 // $SideInfoOffset += 1; 1031 // 1032 // for ($region = 0; $region < 2; $region++) { 1033 // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5); 1034 // $SideInfoOffset += 5; 1035 // } 1036 // $thisfile_mpeg_audio['table_select'][$granule][$channel][2] = 0; 1037 // 1038 // for ($window = 0; $window < 3; $window++) { 1039 // $thisfile_mpeg_audio['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3); 1040 // $SideInfoOffset += 3; 1041 // } 1042 // 1043 // } else { 1044 // 1045 // for ($region = 0; $region < 3; $region++) { 1046 // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5); 1047 // $SideInfoOffset += 5; 1048 // } 1049 // 1050 // $thisfile_mpeg_audio['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4); 1051 // $SideInfoOffset += 4; 1052 // $thisfile_mpeg_audio['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3); 1053 // $SideInfoOffset += 3; 1054 // $thisfile_mpeg_audio['block_type'][$granule][$channel] = 0; 1055 // } 1056 // 1057 // if ($thisfile_mpeg_audio['version'] == '1') { 1058 // $thisfile_mpeg_audio['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); 1059 // $SideInfoOffset += 1; 1060 // } 1061 // $thisfile_mpeg_audio['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); 1062 // $SideInfoOffset += 1; 1063 // $thisfile_mpeg_audio['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); 1064 // $SideInfoOffset += 1; 1065 // } 1066 // } 1067 //} 1068 1069 return true; 1070 } 1071 1072 public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) { 1073 $info = &$this->getid3->info; 1074 $firstframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); 1075 $this->decodeMPEGaudioHeader($offset, $firstframetestarray, false); 1076 1077 for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) { 1078 // check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch 1079 if (($nextframetestoffset + 4) >= $info['avdataend']) { 1080 // end of file 1081 return true; 1082 } 1083 1084 $nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); 1085 if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) { 1086 if ($ScanAsCBR) { 1087 // force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header 1088 if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) { 1089 return false; 1090 } 1091 } 1092 1093 1094 // next frame is OK, get ready to check the one after that 1095 if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) { 1096 $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength']; 1097 } else { 1098 $info['error'][] = 'Frame at offset ('.$offset.') is has an invalid frame length.'; 1099 return false; 1100 } 1101 1102 } elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) { 1103 1104 // it's not the end of the file, but there's not enough data left for another frame, so assume it's garbage/padding and return OK 1105 return true; 1106 1107 } else { 1108 1109 // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence 1110 $info['warning'][] = 'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.'; 1111 1112 return false; 1113 } 1114 } 1115 return true; 1116 } 1117 1118 public function FreeFormatFrameLength($offset, $deepscan=false) { 1119 $info = &$this->getid3->info; 1120 1121 fseek($this->getid3->fp, $offset, SEEK_SET); 1122 $MPEGaudioData = fread($this->getid3->fp, 32768); 1123 1124 $SyncPattern1 = substr($MPEGaudioData, 0, 4); 1125 // may be different pattern due to padding 1126 $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) | 0x02).$SyncPattern1{3}; 1127 if ($SyncPattern2 === $SyncPattern1) { 1128 $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) & 0xFD).$SyncPattern1{3}; 1129 } 1130 1131 $framelength = false; 1132 $framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4); 1133 $framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4); 1134 if ($framelength1 > 4) { 1135 $framelength = $framelength1; 1136 } 1137 if (($framelength2 > 4) && ($framelength2 < $framelength1)) { 1138 $framelength = $framelength2; 1139 } 1140 if (!$framelength) { 1141 1142 // LAME 3.88 has a different value for modeextension on the first frame vs the rest 1143 $framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4); 1144 $framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4); 1145 1146 if ($framelength1 > 4) { 1147 $framelength = $framelength1; 1148 } 1149 if (($framelength2 > 4) && ($framelength2 < $framelength1)) { 1150 $framelength = $framelength2; 1151 } 1152 if (!$framelength) { 1153 $info['error'][] = 'Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset; 1154 return false; 1155 } else { 1156 $info['warning'][] = 'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)'; 1157 $info['audio']['codec'] = 'LAME'; 1158 $info['audio']['encoder'] = 'LAME3.88'; 1159 $SyncPattern1 = substr($SyncPattern1, 0, 3); 1160 $SyncPattern2 = substr($SyncPattern2, 0, 3); 1161 } 1162 } 1163 1164 if ($deepscan) { 1165 1166 $ActualFrameLengthValues = array(); 1167 $nextoffset = $offset + $framelength; 1168 while ($nextoffset < ($info['avdataend'] - 6)) { 1169 fseek($this->getid3->fp, $nextoffset - 1, SEEK_SET); 1170 $NextSyncPattern = fread($this->getid3->fp, 6); 1171 if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) { 1172 // good - found where expected 1173 $ActualFrameLengthValues[] = $framelength; 1174 } elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) { 1175 // ok - found one byte earlier than expected (last frame wasn't padded, first frame was) 1176 $ActualFrameLengthValues[] = ($framelength - 1); 1177 $nextoffset--; 1178 } elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) { 1179 // ok - found one byte later than expected (last frame was padded, first frame wasn't) 1180 $ActualFrameLengthValues[] = ($framelength + 1); 1181 $nextoffset++; 1182 } else { 1183 $info['error'][] = 'Did not find expected free-format sync pattern at offset '.$nextoffset; 1184 return false; 1185 } 1186 $nextoffset += $framelength; 1187 } 1188 if (count($ActualFrameLengthValues) > 0) { 1189 $framelength = intval(round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues))); 1190 } 1191 } 1192 return $framelength; 1193 } 1194 1195 public function getOnlyMPEGaudioInfoBruteForce() { 1196 $MPEGaudioHeaderDecodeCache = array(); 1197 $MPEGaudioHeaderValidCache = array(); 1198 $MPEGaudioHeaderLengthCache = array(); 1199 $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); 1200 $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); 1201 $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); 1202 $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); 1203 $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); 1204 $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); 1205 $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); 1206 $LongMPEGversionLookup = array(); 1207 $LongMPEGlayerLookup = array(); 1208 $LongMPEGbitrateLookup = array(); 1209 $LongMPEGpaddingLookup = array(); 1210 $LongMPEGfrequencyLookup = array(); 1211 $Distribution['bitrate'] = array(); 1212 $Distribution['frequency'] = array(); 1213 $Distribution['layer'] = array(); 1214 $Distribution['version'] = array(); 1215 $Distribution['padding'] = array(); 1216 1217 $info = &$this->getid3->info; 1218 fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); 1219 1220 $max_frames_scan = 5000; 1221 $frames_scanned = 0; 1222 1223 $previousvalidframe = $info['avdataoffset']; 1224 while (ftell($this->getid3->fp) < $info['avdataend']) { 1225 set_time_limit(30); 1226 $head4 = fread($this->getid3->fp, 4); 1227 if (strlen($head4) < 4) { 1228 break; 1229 } 1230 if ($head4{0} != "\xFF") { 1231 for ($i = 1; $i < 4; $i++) { 1232 if ($head4{$i} == "\xFF") { 1233 fseek($this->getid3->fp, $i - 4, SEEK_CUR); 1234 continue 2; 1235 } 1236 } 1237 continue; 1238 } 1239 if (!isset($MPEGaudioHeaderDecodeCache[$head4])) { 1240 $MPEGaudioHeaderDecodeCache[$head4] = self::MPEGaudioHeaderDecode($head4); 1241 } 1242 if (!isset($MPEGaudioHeaderValidCache[$head4])) { 1243 $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false); 1244 } 1245 if ($MPEGaudioHeaderValidCache[$head4]) { 1246 1247 if (!isset($MPEGaudioHeaderLengthCache[$head4])) { 1248 $LongMPEGversionLookup[$head4] = $MPEGaudioVersionLookup[$MPEGaudioHeaderDecodeCache[$head4]['version']]; 1249 $LongMPEGlayerLookup[$head4] = $MPEGaudioLayerLookup[$MPEGaudioHeaderDecodeCache[$head4]['layer']]; 1250 $LongMPEGbitrateLookup[$head4] = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']]; 1251 $LongMPEGpaddingLookup[$head4] = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding']; 1252 $LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']]; 1253 $MPEGaudioHeaderLengthCache[$head4] = self::MPEGaudioFrameLength( 1254 $LongMPEGbitrateLookup[$head4], 1255 $LongMPEGversionLookup[$head4], 1256 $LongMPEGlayerLookup[$head4], 1257 $LongMPEGpaddingLookup[$head4], 1258 $LongMPEGfrequencyLookup[$head4]); 1259 } 1260 if ($MPEGaudioHeaderLengthCache[$head4] > 4) { 1261 $WhereWeWere = ftell($this->getid3->fp); 1262 fseek($this->getid3->fp, $MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); 1263 $next4 = fread($this->getid3->fp, 4); 1264 if ($next4{0} == "\xFF") { 1265 if (!isset($MPEGaudioHeaderDecodeCache[$next4])) { 1266 $MPEGaudioHeaderDecodeCache[$next4] = self::MPEGaudioHeaderDecode($next4); 1267 } 1268 if (!isset($MPEGaudioHeaderValidCache[$next4])) { 1269 $MPEGaudioHeaderValidCache[$next4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false); 1270 } 1271 if ($MPEGaudioHeaderValidCache[$next4]) { 1272 fseek($this->getid3->fp, -4, SEEK_CUR); 1273 1274 getid3_lib::safe_inc($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]); 1275 getid3_lib::safe_inc($Distribution['layer'][$LongMPEGlayerLookup[$head4]]); 1276 getid3_lib::safe_inc($Distribution['version'][$LongMPEGversionLookup[$head4]]); 1277 getid3_lib::safe_inc($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]); 1278 getid3_lib::safe_inc($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]); 1279 if ($max_frames_scan && (++$frames_scanned >= $max_frames_scan)) { 1280 $pct_data_scanned = (ftell($this->getid3->fp) - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']); 1281 $info['warning'][] = 'too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; 1282 foreach ($Distribution as $key1 => $value1) { 1283 foreach ($value1 as $key2 => $value2) { 1284 $Distribution[$key1][$key2] = round($value2 / $pct_data_scanned); 1285 } 1286 } 1287 break; 1288 } 1289 continue; 1290 } 1291 } 1292 unset($next4); 1293 fseek($this->getid3->fp, $WhereWeWere - 3, SEEK_SET); 1294 } 1295 1296 } 1297 } 1298 foreach ($Distribution as $key => $value) { 1299 ksort($Distribution[$key], SORT_NUMERIC); 1300 } 1301 ksort($Distribution['version'], SORT_STRING); 1302 $info['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate']; 1303 $info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency']; 1304 $info['mpeg']['audio']['layer_distribution'] = $Distribution['layer']; 1305 $info['mpeg']['audio']['version_distribution'] = $Distribution['version']; 1306 $info['mpeg']['audio']['padding_distribution'] = $Distribution['padding']; 1307 if (count($Distribution['version']) > 1) { 1308 $info['error'][] = 'Corrupt file - more than one MPEG version detected'; 1309 } 1310 if (count($Distribution['layer']) > 1) { 1311 $info['error'][] = 'Corrupt file - more than one MPEG layer detected'; 1312 } 1313 if (count($Distribution['frequency']) > 1) { 1314 $info['error'][] = 'Corrupt file - more than one MPEG sample rate detected'; 1315 } 1316 1317 1318 $bittotal = 0; 1319 foreach ($Distribution['bitrate'] as $bitratevalue => $bitratecount) { 1320 if ($bitratevalue != 'free') { 1321 $bittotal += ($bitratevalue * $bitratecount); 1322 } 1323 } 1324 $info['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']); 1325 if ($info['mpeg']['audio']['frame_count'] == 0) { 1326 $info['error'][] = 'no MPEG audio frames found'; 1327 return false; 1328 } 1329 $info['mpeg']['audio']['bitrate'] = ($bittotal / $info['mpeg']['audio']['frame_count']); 1330 $info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr'); 1331 $info['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true); 1332 1333 $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; 1334 $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; 1335 $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; 1336 $info['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true); 1337 $info['fileformat'] = $info['audio']['dataformat']; 1338 1339 return true; 1340 } 1341 1342 1343 public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) { 1344 // looks for synch, decodes MPEG audio header 1345 1346 $info = &$this->getid3->info; 1347 1348 static $MPEGaudioVersionLookup; 1349 static $MPEGaudioLayerLookup; 1350 static $MPEGaudioBitrateLookup; 1351 if (empty($MPEGaudioVersionLookup)) { 1352 $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); 1353 $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); 1354 $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); 1355 1356 } 1357 1358 fseek($this->getid3->fp, $avdataoffset, SEEK_SET); 1359 $sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset); 1360 if ($sync_seek_buffer_size <= 0) { 1361 $info['error'][] = 'Invalid $sync_seek_buffer_size at offset '.$avdataoffset; 1362 return false; 1363 } 1364 $header = fread($this->getid3->fp, $sync_seek_buffer_size); 1365 $sync_seek_buffer_size = strlen($header); 1366 $SynchSeekOffset = 0; 1367 while ($SynchSeekOffset < $sync_seek_buffer_size) { 1368 if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) { 1369 1370 if ($SynchSeekOffset > $sync_seek_buffer_size) { 1371 // if a synch's not found within the first 128k bytes, then give up 1372 $info['error'][] = 'Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB'; 1373 if (isset($info['audio']['bitrate'])) { 1374 unset($info['audio']['bitrate']); 1375 } 1376 if (isset($info['mpeg']['audio'])) { 1377 unset($info['mpeg']['audio']); 1378 } 1379 if (empty($info['mpeg'])) { 1380 unset($info['mpeg']); 1381 } 1382 return false; 1383 1384 } elseif (feof($this->getid3->fp)) { 1385 1386 $info['error'][] = 'Could not find valid MPEG audio synch before end of file'; 1387 if (isset($info['audio']['bitrate'])) { 1388 unset($info['audio']['bitrate']); 1389 } 1390 if (isset($info['mpeg']['audio'])) { 1391 unset($info['mpeg']['audio']); 1392 } 1393 if (isset($info['mpeg']) && (!is_array($info['mpeg']) || (count($info['mpeg']) == 0))) { 1394 unset($info['mpeg']); 1395 } 1396 return false; 1397 } 1398 } 1399 1400 if (($SynchSeekOffset + 1) >= strlen($header)) { 1401 $info['error'][] = 'Could not find valid MPEG synch before end of file'; 1402 return false; 1403 } 1404 1405 if (($header{$SynchSeekOffset} == "\xFF") && ($header{($SynchSeekOffset + 1)} > "\xE0")) { // synch detected 1406 if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) { 1407 $FirstFrameThisfileInfo = $info; 1408 $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset; 1409 if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) { 1410 // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's 1411 // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below 1412 unset($FirstFrameThisfileInfo); 1413 } 1414 } 1415 1416 $dummy = $info; // only overwrite real data if valid header found 1417 if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) { 1418 $info = $dummy; 1419 $info['avdataoffset'] = $avdataoffset + $SynchSeekOffset; 1420 switch (isset($info['fileformat']) ? $info['fileformat'] : '') { 1421 case '': 1422 case 'id3': 1423 case 'ape': 1424 case 'mp3': 1425 $info['fileformat'] = 'mp3'; 1426 $info['audio']['dataformat'] = 'mp3'; 1427 break; 1428 } 1429 if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) { 1430 if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) { 1431 // If there is garbage data between a valid VBR header frame and a sequence 1432 // of valid MPEG-audio frames the VBR data is no longer discarded. 1433 $info = $FirstFrameThisfileInfo; 1434 $info['avdataoffset'] = $FirstFrameAVDataOffset; 1435 $info['fileformat'] = 'mp3'; 1436 $info['audio']['dataformat'] = 'mp3'; 1437 $dummy = $info; 1438 unset($dummy['mpeg']['audio']); 1439 $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength']; 1440 $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset; 1441 if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) { 1442 $info = $dummy; 1443 $info['avdataoffset'] = $GarbageOffsetEnd; 1444 $info['warning'][] = 'apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd; 1445 } else { 1446 $info['warning'][] = 'using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')'; 1447 } 1448 } 1449 } 1450 if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) { 1451 // VBR file with no VBR header 1452 $BitrateHistogram = true; 1453 } 1454 1455 if ($BitrateHistogram) { 1456 1457 $info['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0); 1458 $info['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0); 1459 1460 if ($info['mpeg']['audio']['version'] == '1') { 1461 if ($info['mpeg']['audio']['layer'] == 3) { 1462 $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0); 1463 } elseif ($info['mpeg']['audio']['layer'] == 2) { 1464 $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0); 1465 } elseif ($info['mpeg']['audio']['layer'] == 1) { 1466 $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0); 1467 } 1468 } elseif ($info['mpeg']['audio']['layer'] == 1) { 1469 $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0); 1470 } else { 1471 $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0); 1472 } 1473 1474 $dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); 1475 $synchstartoffset = $info['avdataoffset']; 1476 fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); 1477 1478 // you can play with these numbers: 1479 $max_frames_scan = 50000; 1480 $max_scan_segments = 10; 1481 1482 // don't play with these numbers: 1483 $FastMode = false; 1484 $SynchErrorsFound = 0; 1485 $frames_scanned = 0; 1486 $this_scan_segment = 0; 1487 $frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments); 1488 $pct_data_scanned = 0; 1489 for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) { 1490 $frames_scanned_this_segment = 0; 1491 if (ftell($this->getid3->fp) >= $info['avdataend']) { 1492 break; 1493 } 1494 $scan_start_offset[$current_segment] = max(ftell($this->getid3->fp), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments))); 1495 if ($current_segment > 0) { 1496 fseek($this->getid3->fp, $scan_start_offset[$current_segment], SEEK_SET); 1497 $buffer_4k = fread($this->getid3->fp, 4096); 1498 for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) { 1499 if (($buffer_4k{$j} == "\xFF") && ($buffer_4k{($j + 1)} > "\xE0")) { // synch detected 1500 if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) { 1501 $calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength']; 1502 if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) { 1503 $scan_start_offset[$current_segment] += $j; 1504 break; 1505 } 1506 } 1507 } 1508 } 1509 } 1510 $synchstartoffset = $scan_start_offset[$current_segment]; 1511 while ($this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) { 1512 $FastMode = true; 1513 $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']]; 1514 1515 if (empty($dummy['mpeg']['audio']['framelength'])) { 1516 $SynchErrorsFound++; 1517 $synchstartoffset++; 1518 } else { 1519 getid3_lib::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]); 1520 getid3_lib::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]); 1521 getid3_lib::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]); 1522 $synchstartoffset += $dummy['mpeg']['audio']['framelength']; 1523 } 1524 $frames_scanned++; 1525 if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) { 1526 $this_pct_scanned = (ftell($this->getid3->fp) - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']); 1527 if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) { 1528 // file likely contains < $max_frames_scan, just scan as one segment 1529 $max_scan_segments = 1; 1530 $frames_scan_per_segment = $max_frames_scan; 1531 } else { 1532 $pct_data_scanned += $this_pct_scanned; 1533 break; 1534 } 1535 } 1536 } 1537 } 1538 if ($pct_data_scanned > 0) { 1539 $info['warning'][] = 'too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; 1540 foreach ($info['mpeg']['audio'] as $key1 => $value1) { 1541 if (!preg_match('#_distribution$#i', $key1)) { 1542 continue; 1543 } 1544 foreach ($value1 as $key2 => $value2) { 1545 $info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned); 1546 } 1547 } 1548 } 1549 1550 if ($SynchErrorsFound > 0) { 1551 $info['warning'][] = 'Found '.$SynchErrorsFound.' synch errors in histogram analysis'; 1552 //return false; 1553 } 1554 1555 $bittotal = 0; 1556 $framecounter = 0; 1557 foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) { 1558 $framecounter += $bitratecount; 1559 if ($bitratevalue != 'free') { 1560 $bittotal += ($bitratevalue * $bitratecount); 1561 } 1562 } 1563 if ($framecounter == 0) { 1564 $info['error'][] = 'Corrupt MP3 file: framecounter == zero'; 1565 return false; 1566 } 1567 $info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter); 1568 $info['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter); 1569 1570 $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; 1571 1572 1573 // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently 1574 $distinct_bitrates = 0; 1575 foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) { 1576 if ($bitrate_count > 0) { 1577 $distinct_bitrates++; 1578 } 1579 } 1580 if ($distinct_bitrates > 1) { 1581 $info['mpeg']['audio']['bitrate_mode'] = 'vbr'; 1582 } else { 1583 $info['mpeg']['audio']['bitrate_mode'] = 'cbr'; 1584 } 1585 $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; 1586 1587 } 1588 1589 break; // exit while() 1590 } 1591 } 1592 1593 $SynchSeekOffset++; 1594 if (($avdataoffset + $SynchSeekOffset) >= $info['avdataend']) { 1595 // end of file/data 1596 1597 if (empty($info['mpeg']['audio'])) { 1598 1599 $info['error'][] = 'could not find valid MPEG synch before end of file'; 1600 if (isset($info['audio']['bitrate'])) { 1601 unset($info['audio']['bitrate']); 1602 } 1603 if (isset($info['mpeg']['audio'])) { 1604 unset($info['mpeg']['audio']); 1605 } 1606 if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) { 1607 unset($info['mpeg']); 1608 } 1609 return false; 1610 1611 } 1612 break; 1613 } 1614 1615 } 1616 $info['audio']['channels'] = $info['mpeg']['audio']['channels']; 1617 $info['audio']['channelmode'] = $info['mpeg']['audio']['channelmode']; 1618 $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; 1619 return true; 1620 } 1621 1622 1623 public static function MPEGaudioVersionArray() { 1624 static $MPEGaudioVersion = array('2.5', false, '2', '1'); 1625 return $MPEGaudioVersion; 1626 } 1627 1628 public static function MPEGaudioLayerArray() { 1629 static $MPEGaudioLayer = array(false, 3, 2, 1); 1630 return $MPEGaudioLayer; 1631 } 1632 1633 public static function MPEGaudioBitrateArray() { 1634 static $MPEGaudioBitrate; 1635 if (empty($MPEGaudioBitrate)) { 1636 $MPEGaudioBitrate = array ( 1637 '1' => array (1 => array('free', 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000), 1638 2 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000), 1639 3 => array('free', 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000) 1640 ), 1641 1642 '2' => array (1 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000), 1643 2 => array('free', 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000), 1644 ) 1645 ); 1646 $MPEGaudioBitrate['2'][3] = $MPEGaudioBitrate['2'][2]; 1647 $MPEGaudioBitrate['2.5'] = $MPEGaudioBitrate['2']; 1648 } 1649 return $MPEGaudioBitrate; 1650 } 1651 1652 public static function MPEGaudioFrequencyArray() { 1653 static $MPEGaudioFrequency; 1654 if (empty($MPEGaudioFrequency)) { 1655 $MPEGaudioFrequency = array ( 1656 '1' => array(44100, 48000, 32000), 1657 '2' => array(22050, 24000, 16000), 1658 '2.5' => array(11025, 12000, 8000) 1659 ); 1660 } 1661 return $MPEGaudioFrequency; 1662 } 1663 1664 public static function MPEGaudioChannelModeArray() { 1665 static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono'); 1666 return $MPEGaudioChannelMode; 1667 } 1668 1669 public static function MPEGaudioModeExtensionArray() { 1670 static $MPEGaudioModeExtension; 1671 if (empty($MPEGaudioModeExtension)) { 1672 $MPEGaudioModeExtension = array ( 1673 1 => array('4-31', '8-31', '12-31', '16-31'), 1674 2 => array('4-31', '8-31', '12-31', '16-31'), 1675 3 => array('', 'IS', 'MS', 'IS+MS') 1676 ); 1677 } 1678 return $MPEGaudioModeExtension; 1679 } 1680 1681 public static function MPEGaudioEmphasisArray() { 1682 static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17'); 1683 return $MPEGaudioEmphasis; 1684 } 1685 1686 public static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { 1687 return self::MPEGaudioHeaderValid(self::MPEGaudioHeaderDecode($head4), false, $allowBitrate15); 1688 } 1689 1690 public static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { 1691 if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) { 1692 return false; 1693 } 1694 1695 static $MPEGaudioVersionLookup; 1696 static $MPEGaudioLayerLookup; 1697 static $MPEGaudioBitrateLookup; 1698 static $MPEGaudioFrequencyLookup; 1699 static $MPEGaudioChannelModeLookup; 1700 static $MPEGaudioModeExtensionLookup; 1701 static $MPEGaudioEmphasisLookup; 1702 if (empty($MPEGaudioVersionLookup)) { 1703 $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); 1704 $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); 1705 $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); 1706 $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); 1707 $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); 1708 $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); 1709 $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); 1710 } 1711 1712 if (isset($MPEGaudioVersionLookup[$rawarray['version']])) { 1713 $decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']]; 1714 } else { 1715 echo ($echoerrors ? "\n".'invalid Version ('.$rawarray['version'].')' : ''); 1716 return false; 1717 } 1718 if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) { 1719 $decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']]; 1720 } else { 1721 echo ($echoerrors ? "\n".'invalid Layer ('.$rawarray['layer'].')' : ''); 1722 return false; 1723 } 1724 if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) { 1725 echo ($echoerrors ? "\n".'invalid Bitrate ('.$rawarray['bitrate'].')' : ''); 1726 if ($rawarray['bitrate'] == 15) { 1727 // known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0 1728 // let it go through here otherwise file will not be identified 1729 if (!$allowBitrate15) { 1730 return false; 1731 } 1732 } else { 1733 return false; 1734 } 1735 } 1736 if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) { 1737 echo ($echoerrors ? "\n".'invalid Frequency ('.$rawarray['sample_rate'].')' : ''); 1738 return false; 1739 } 1740 if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) { 1741 echo ($echoerrors ? "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')' : ''); 1742 return false; 1743 } 1744 if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) { 1745 echo ($echoerrors ? "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')' : ''); 1746 return false; 1747 } 1748 if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) { 1749 echo ($echoerrors ? "\n".'invalid Emphasis ('.$rawarray['emphasis'].')' : ''); 1750 return false; 1751 } 1752 // These are just either set or not set, you can't mess that up :) 1753 // $rawarray['protection']; 1754 // $rawarray['padding']; 1755 // $rawarray['private']; 1756 // $rawarray['copyright']; 1757 // $rawarray['original']; 1758 1759 return true; 1760 } 1761 1762 public static function MPEGaudioHeaderDecode($Header4Bytes) { 1763 // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM 1764 // A - Frame sync (all bits set) 1765 // B - MPEG Audio version ID 1766 // C - Layer description 1767 // D - Protection bit 1768 // E - Bitrate index 1769 // F - Sampling rate frequency index 1770 // G - Padding bit 1771 // H - Private bit 1772 // I - Channel Mode 1773 // J - Mode extension (Only if Joint stereo) 1774 // K - Copyright 1775 // L - Original 1776 // M - Emphasis 1777 1778 if (strlen($Header4Bytes) != 4) { 1779 return false; 1780 } 1781 1782 $MPEGrawHeader['synch'] = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4; 1783 $MPEGrawHeader['version'] = (ord($Header4Bytes{1}) & 0x18) >> 3; // BB 1784 $MPEGrawHeader['layer'] = (ord($Header4Bytes{1}) & 0x06) >> 1; // CC 1785 $MPEGrawHeader['protection'] = (ord($Header4Bytes{1}) & 0x01); // D 1786 $MPEGrawHeader['bitrate'] = (ord($Header4Bytes{2}) & 0xF0) >> 4; // EEEE 1787 $MPEGrawHeader['sample_rate'] = (ord($Header4Bytes{2}) & 0x0C) >> 2; // FF 1788 $MPEGrawHeader['padding'] = (ord($Header4Bytes{2}) & 0x02) >> 1; // G 1789 $MPEGrawHeader['private'] = (ord($Header4Bytes{2}) & 0x01); // H 1790 $MPEGrawHeader['channelmode'] = (ord($Header4Bytes{3}) & 0xC0) >> 6; // II 1791 $MPEGrawHeader['modeextension'] = (ord($Header4Bytes{3}) & 0x30) >> 4; // JJ 1792 $MPEGrawHeader['copyright'] = (ord($Header4Bytes{3}) & 0x08) >> 3; // K 1793 $MPEGrawHeader['original'] = (ord($Header4Bytes{3}) & 0x04) >> 2; // L 1794 $MPEGrawHeader['emphasis'] = (ord($Header4Bytes{3}) & 0x03); // MM 1795 1796 return $MPEGrawHeader; 1797 } 1798 1799 public static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { 1800 static $AudioFrameLengthCache = array(); 1801 1802 if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) { 1803 $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false; 1804 if ($bitrate != 'free') { 1805 1806 if ($version == '1') { 1807 1808 if ($layer == '1') { 1809 1810 // For Layer I slot is 32 bits long 1811 $FrameLengthCoefficient = 48; 1812 $SlotLength = 4; 1813 1814 } else { // Layer 2 / 3 1815 1816 // for Layer 2 and Layer 3 slot is 8 bits long. 1817 $FrameLengthCoefficient = 144; 1818 $SlotLength = 1; 1819 1820 } 1821 1822 } else { // MPEG-2 / MPEG-2.5 1823 1824 if ($layer == '1') { 1825 1826 // For Layer I slot is 32 bits long 1827 $FrameLengthCoefficient = 24; 1828 $SlotLength = 4; 1829 1830 } elseif ($layer == '2') { 1831 1832 // for Layer 2 and Layer 3 slot is 8 bits long. 1833 $FrameLengthCoefficient = 144; 1834 $SlotLength = 1; 1835 1836 } else { // layer 3 1837 1838 // for Layer 2 and Layer 3 slot is 8 bits long. 1839 $FrameLengthCoefficient = 72; 1840 $SlotLength = 1; 1841 1842 } 1843 1844 } 1845 1846 // FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding 1847 if ($samplerate > 0) { 1848 $NewFramelength = ($FrameLengthCoefficient * $bitrate) / $samplerate; 1849 $NewFramelength = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer 2/3, 4 bytes for Layer I) 1850 if ($padding) { 1851 $NewFramelength += $SlotLength; 1852 } 1853 $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength; 1854 } 1855 } 1856 } 1857 return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate]; 1858 } 1859 1860 public static function ClosestStandardMP3Bitrate($bit_rate) { 1861 static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000); 1862 static $bit_rate_table = array (0=>'-'); 1863 $round_bit_rate = intval(round($bit_rate, -3)); 1864 if (!isset($bit_rate_table[$round_bit_rate])) { 1865 if ($round_bit_rate > max($standard_bit_rates)) { 1866 $bit_rate_table[$round_bit_rate] = round($bit_rate, 2 - strlen($bit_rate)); 1867 } else { 1868 $bit_rate_table[$round_bit_rate] = max($standard_bit_rates); 1869 foreach ($standard_bit_rates as $standard_bit_rate) { 1870 if ($round_bit_rate >= $standard_bit_rate + (($bit_rate_table[$round_bit_rate] - $standard_bit_rate) / 2)) { 1871 break; 1872 } 1873 $bit_rate_table[$round_bit_rate] = $standard_bit_rate; 1874 } 1875 } 1876 } 1877 return $bit_rate_table[$round_bit_rate]; 1878 } 1879 1880 public static function XingVBRidOffset($version, $channelmode) { 1881 static $XingVBRidOffsetCache = array(); 1882 if (empty($XingVBRidOffset)) { 1883 $XingVBRidOffset = array ( 1884 '1' => array ('mono' => 0x15, // 4 + 17 = 21 1885 'stereo' => 0x24, // 4 + 32 = 36 1886 'joint stereo' => 0x24, 1887 'dual channel' => 0x24 1888 ), 1889 1890 '2' => array ('mono' => 0x0D, // 4 + 9 = 13 1891 'stereo' => 0x15, // 4 + 17 = 21 1892 'joint stereo' => 0x15, 1893 'dual channel' => 0x15 1894 ), 1895 1896 '2.5' => array ('mono' => 0x15, 1897 'stereo' => 0x15, 1898 'joint stereo' => 0x15, 1899 'dual channel' => 0x15 1900 ) 1901 ); 1902 } 1903 return $XingVBRidOffset[$version][$channelmode]; 1904 } 1905 1906 public static function LAMEvbrMethodLookup($VBRmethodID) { 1907 static $LAMEvbrMethodLookup = array( 1908 0x00 => 'unknown', 1909 0x01 => 'cbr', 1910 0x02 => 'abr', 1911 0x03 => 'vbr-old / vbr-rh', 1912 0x04 => 'vbr-new / vbr-mtrh', 1913 0x05 => 'vbr-mt', 1914 0x06 => 'vbr (full vbr method 4)', 1915 0x08 => 'cbr (constant bitrate 2 pass)', 1916 0x09 => 'abr (2 pass)', 1917 0x0F => 'reserved' 1918 ); 1919 return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : ''); 1920 } 1921 1922 public static function LAMEmiscStereoModeLookup($StereoModeID) { 1923 static $LAMEmiscStereoModeLookup = array( 1924 0 => 'mono', 1925 1 => 'stereo', 1926 2 => 'dual mono', 1927 3 => 'joint stereo', 1928 4 => 'forced stereo', 1929 5 => 'auto', 1930 6 => 'intensity stereo', 1931 7 => 'other' 1932 ); 1933 return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : ''); 1934 } 1935 1936 public static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { 1937 static $LAMEmiscSourceSampleFrequencyLookup = array( 1938 0 => '<= 32 kHz', 1939 1 => '44.1 kHz', 1940 2 => '48 kHz', 1941 3 => '> 48kHz' 1942 ); 1943 return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : ''); 1944 } 1945 1946 public static function LAMEsurroundInfoLookup($SurroundInfoID) { 1947 static $LAMEsurroundInfoLookup = array( 1948 0 => 'no surround info', 1949 1 => 'DPL encoding', 1950 2 => 'DPL2 encoding', 1951 3 => 'Ambisonic encoding' 1952 ); 1953 return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved'); 1954 } 1955 1956 public static function LAMEpresetUsedLookup($LAMEtag) { 1957 1958 if ($LAMEtag['preset_used_id'] == 0) { 1959 // no preset used (LAME >=3.93) 1960 // no preset recorded (LAME <3.93) 1961 return ''; 1962 } 1963 $LAMEpresetUsedLookup = array(); 1964 1965 ///// THIS PART CANNOT BE STATIC . 1966 for ($i = 8; $i <= 320; $i++) { 1967 switch ($LAMEtag['vbr_method']) { 1968 case 'cbr': 1969 $LAMEpresetUsedLookup[$i] = '--alt-preset '.$LAMEtag['vbr_method'].' '.$i; 1970 break; 1971 case 'abr': 1972 default: // other VBR modes shouldn't be here(?) 1973 $LAMEpresetUsedLookup[$i] = '--alt-preset '.$i; 1974 break; 1975 } 1976 } 1977 1978 // named old-style presets (studio, phone, voice, etc) are handled in GuessEncoderOptions() 1979 1980 // named alt-presets 1981 $LAMEpresetUsedLookup[1000] = '--r3mix'; 1982 $LAMEpresetUsedLookup[1001] = '--alt-preset standard'; 1983 $LAMEpresetUsedLookup[1002] = '--alt-preset extreme'; 1984 $LAMEpresetUsedLookup[1003] = '--alt-preset insane'; 1985 $LAMEpresetUsedLookup[1004] = '--alt-preset fast standard'; 1986 $LAMEpresetUsedLookup[1005] = '--alt-preset fast extreme'; 1987 $LAMEpresetUsedLookup[1006] = '--alt-preset medium'; 1988 $LAMEpresetUsedLookup[1007] = '--alt-preset fast medium'; 1989 1990 // LAME 3.94 additions/changes 1991 $LAMEpresetUsedLookup[1010] = '--preset portable'; // 3.94a15 Oct 21 2003 1992 $LAMEpresetUsedLookup[1015] = '--preset radio'; // 3.94a15 Oct 21 2003 1993 1994 $LAMEpresetUsedLookup[320] = '--preset insane'; // 3.94a15 Nov 12 2003 1995 $LAMEpresetUsedLookup[410] = '-V9'; 1996 $LAMEpresetUsedLookup[420] = '-V8'; 1997 $LAMEpresetUsedLookup[440] = '-V6'; 1998 $LAMEpresetUsedLookup[430] = '--preset radio'; // 3.94a15 Nov 12 2003 1999 $LAMEpresetUsedLookup[450] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'portable'; // 3.94a15 Nov 12 2003 2000 $LAMEpresetUsedLookup[460] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'medium'; // 3.94a15 Nov 12 2003 2001 $LAMEpresetUsedLookup[470] = '--r3mix'; // 3.94b1 Dec 18 2003 2002 $LAMEpresetUsedLookup[480] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'standard'; // 3.94a15 Nov 12 2003 2003 $LAMEpresetUsedLookup[490] = '-V1'; 2004 $LAMEpresetUsedLookup[500] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'extreme'; // 3.94a15 Nov 12 2003 2005 2006 return (isset($LAMEpresetUsedLookup[$LAMEtag['preset_used_id']]) ? $LAMEpresetUsedLookup[$LAMEtag['preset_used_id']] : 'new/unknown preset: '.$LAMEtag['preset_used_id'].' - report to info@getid3.org'); 2007 } 2008 2009 } -
new file wp-includes/ID3/module.audio.ogg.php
diff --git wp-includes/ID3/module.audio.ogg.php wp-includes/ID3/module.audio.ogg.php new file mode 100644 index 0000000..a2a35aa
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.audio.ogg.php // 11 // module for analyzing Ogg Vorbis, OggFLAC and Speex files // 12 // dependencies: module.audio.flac.php // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true); 17 18 class getid3_ogg extends getid3_handler 19 { 20 // http://xiph.org/vorbis/doc/Vorbis_I_spec.html 21 public function Analyze() { 22 $info = &$this->getid3->info; 23 24 $info['fileformat'] = 'ogg'; 25 26 // Warn about illegal tags - only vorbiscomments are allowed 27 if (isset($info['id3v2'])) { 28 $info['warning'][] = 'Illegal ID3v2 tag present.'; 29 } 30 if (isset($info['id3v1'])) { 31 $info['warning'][] = 'Illegal ID3v1 tag present.'; 32 } 33 if (isset($info['ape'])) { 34 $info['warning'][] = 'Illegal APE tag present.'; 35 } 36 37 38 // Page 1 - Stream Header 39 40 $this->fseek($info['avdataoffset']); 41 42 $oggpageinfo = $this->ParseOggPageHeader(); 43 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 44 45 if ($this->ftell() >= $this->getid3->fread_buffer_size()) { 46 $info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)'; 47 unset($info['fileformat']); 48 unset($info['ogg']); 49 return false; 50 } 51 52 $filedata = $this->fread($oggpageinfo['page_length']); 53 $filedataoffset = 0; 54 55 if (substr($filedata, 0, 4) == 'fLaC') { 56 57 $info['audio']['dataformat'] = 'flac'; 58 $info['audio']['bitrate_mode'] = 'vbr'; 59 $info['audio']['lossless'] = true; 60 61 } elseif (substr($filedata, 1, 6) == 'vorbis') { 62 63 $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); 64 65 } elseif (substr($filedata, 0, 8) == 'Speex ') { 66 67 // http://www.speex.org/manual/node10.html 68 69 $info['audio']['dataformat'] = 'speex'; 70 $info['mime_type'] = 'audio/speex'; 71 $info['audio']['bitrate_mode'] = 'abr'; 72 $info['audio']['lossless'] = false; 73 74 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex ' 75 $filedataoffset += 8; 76 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20); 77 $filedataoffset += 20; 78 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 79 $filedataoffset += 4; 80 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 81 $filedataoffset += 4; 82 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 83 $filedataoffset += 4; 84 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 85 $filedataoffset += 4; 86 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 87 $filedataoffset += 4; 88 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 89 $filedataoffset += 4; 90 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 91 $filedataoffset += 4; 92 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 93 $filedataoffset += 4; 94 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 95 $filedataoffset += 4; 96 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 97 $filedataoffset += 4; 98 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 99 $filedataoffset += 4; 100 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 101 $filedataoffset += 4; 102 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 103 $filedataoffset += 4; 104 105 $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']); 106 $info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']; 107 $info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']; 108 $info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']; 109 $info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']); 110 111 $info['audio']['sample_rate'] = $info['speex']['sample_rate']; 112 $info['audio']['channels'] = $info['speex']['channels']; 113 if ($info['speex']['vbr']) { 114 $info['audio']['bitrate_mode'] = 'vbr'; 115 } 116 117 118 } elseif (substr($filedata, 0, 8) == "fishead\x00") { 119 120 // Ogg Skeleton version 3.0 Format Specification 121 // http://xiph.org/ogg/doc/skeleton.html 122 $filedataoffset += 8; 123 $info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); 124 $filedataoffset += 2; 125 $info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); 126 $filedataoffset += 2; 127 $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 128 $filedataoffset += 8; 129 $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 130 $filedataoffset += 8; 131 $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 132 $filedataoffset += 8; 133 $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 134 $filedataoffset += 8; 135 $info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20)); 136 $filedataoffset += 20; 137 138 $info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor']; 139 $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator']; 140 $info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']; 141 $info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc']; 142 143 144 $counter = 0; 145 do { 146 $oggpageinfo = $this->ParseOggPageHeader(); 147 $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo; 148 $filedata = $this->fread($oggpageinfo['page_length']); 149 $this->fseek($oggpageinfo['page_end_offset']); 150 151 if (substr($filedata, 0, 8) == "fisbone\x00") { 152 153 $filedataoffset = 8; 154 $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 155 $filedataoffset += 4; 156 $info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 157 $filedataoffset += 4; 158 $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 159 $filedataoffset += 4; 160 $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 161 $filedataoffset += 8; 162 $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 163 $filedataoffset += 8; 164 $info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 165 $filedataoffset += 8; 166 $info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 167 $filedataoffset += 4; 168 $info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 169 $filedataoffset += 1; 170 $info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3); 171 $filedataoffset += 3; 172 173 } elseif (substr($filedata, 1, 6) == 'theora') { 174 175 $info['video']['dataformat'] = 'theora'; 176 $info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']'; 177 //break; 178 179 } elseif (substr($filedata, 1, 6) == 'vorbis') { 180 181 $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); 182 183 } else { 184 $info['error'][] = 'unexpected'; 185 //break; 186 } 187 //} while ($oggpageinfo['page_seqno'] == 0); 188 } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00")); 189 190 $this->fseek($oggpageinfo['page_start_offset']); 191 192 $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']'; 193 //return false; 194 195 } else { 196 197 $info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"'; 198 unset($info['ogg']); 199 unset($info['mime_type']); 200 return false; 201 202 } 203 204 // Page 2 - Comment Header 205 $oggpageinfo = $this->ParseOggPageHeader(); 206 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 207 208 switch ($info['audio']['dataformat']) { 209 case 'vorbis': 210 $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 211 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1)); 212 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis' 213 214 $this->ParseVorbisComments(); 215 break; 216 217 case 'flac': 218 $flac = new getid3_flac($this->getid3); 219 if (!$flac->parseMETAdata()) { 220 $info['error'][] = 'Failed to parse FLAC headers'; 221 return false; 222 } 223 unset($flac); 224 break; 225 226 case 'speex': 227 $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); 228 $this->ParseVorbisComments(); 229 break; 230 } 231 232 233 // Last Page - Number of Samples 234 if (!getid3_lib::intValueSupported($info['avdataend'])) { 235 236 $info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)'; 237 238 } else { 239 240 $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0)); 241 $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size())); 242 if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) { 243 $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO'))); 244 $info['avdataend'] = $this->ftell(); 245 $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader(); 246 $info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position']; 247 if ($info['ogg']['samples'] == 0) { 248 $info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero'; 249 return false; 250 } 251 if (!empty($info['audio']['sample_rate'])) { 252 $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']); 253 } 254 } 255 256 } 257 258 if (!empty($info['ogg']['bitrate_average'])) { 259 $info['audio']['bitrate'] = $info['ogg']['bitrate_average']; 260 } elseif (!empty($info['ogg']['bitrate_nominal'])) { 261 $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal']; 262 } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) { 263 $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2; 264 } 265 if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) { 266 if ($info['audio']['bitrate'] == 0) { 267 $info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero'; 268 return false; 269 } 270 $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']); 271 } 272 273 if (isset($info['ogg']['vendor'])) { 274 $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']); 275 276 // Vorbis only 277 if ($info['audio']['dataformat'] == 'vorbis') { 278 279 // Vorbis 1.0 starts with Xiph.Org 280 if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) { 281 282 if ($info['audio']['bitrate_mode'] == 'abr') { 283 284 // Set -b 128 on abr files 285 $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000); 286 287 } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) { 288 // Set -q N on vbr files 289 $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']); 290 291 } 292 } 293 294 if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) { 295 $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps'; 296 } 297 } 298 } 299 300 return true; 301 } 302 303 public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { 304 $info = &$this->getid3->info; 305 $info['audio']['dataformat'] = 'vorbis'; 306 $info['audio']['lossless'] = false; 307 308 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 309 $filedataoffset += 1; 310 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis' 311 $filedataoffset += 6; 312 $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 313 $filedataoffset += 4; 314 $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 315 $filedataoffset += 1; 316 $info['audio']['channels'] = $info['ogg']['numberofchannels']; 317 $info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 318 $filedataoffset += 4; 319 if ($info['ogg']['samplerate'] == 0) { 320 $info['error'][] = 'Corrupt Ogg file: sample rate == zero'; 321 return false; 322 } 323 $info['audio']['sample_rate'] = $info['ogg']['samplerate']; 324 $info['ogg']['samples'] = 0; // filled in later 325 $info['ogg']['bitrate_average'] = 0; // filled in later 326 $info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 327 $filedataoffset += 4; 328 $info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 329 $filedataoffset += 4; 330 $info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 331 $filedataoffset += 4; 332 $info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F); 333 $info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4); 334 $info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet 335 336 $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr 337 if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) { 338 unset($info['ogg']['bitrate_max']); 339 $info['audio']['bitrate_mode'] = 'abr'; 340 } 341 if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) { 342 unset($info['ogg']['bitrate_nominal']); 343 } 344 if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) { 345 unset($info['ogg']['bitrate_min']); 346 $info['audio']['bitrate_mode'] = 'abr'; 347 } 348 return true; 349 } 350 351 public function ParseOggPageHeader() { 352 // http://xiph.org/ogg/vorbis/doc/framing.html 353 $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file 354 355 $filedata = $this->fread($this->getid3->fread_buffer_size()); 356 $filedataoffset = 0; 357 while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) { 358 if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) { 359 // should be found before here 360 return false; 361 } 362 if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) { 363 if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) { 364 // get some more data, unless eof, in which case fail 365 return false; 366 } 367 } 368 } 369 $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS' 370 371 $oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 372 $filedataoffset += 1; 373 $oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 374 $filedataoffset += 1; 375 $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet 376 $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos) 377 $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos) 378 379 $oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 380 $filedataoffset += 8; 381 $oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 382 $filedataoffset += 4; 383 $oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 384 $filedataoffset += 4; 385 $oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 386 $filedataoffset += 4; 387 $oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 388 $filedataoffset += 1; 389 $oggheader['page_length'] = 0; 390 for ($i = 0; $i < $oggheader['page_segments']; $i++) { 391 $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 392 $filedataoffset += 1; 393 $oggheader['page_length'] += $oggheader['segment_table'][$i]; 394 } 395 $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset; 396 $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length']; 397 $this->fseek($oggheader['header_end_offset']); 398 399 return $oggheader; 400 } 401 402 // http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005 403 public function ParseVorbisComments() { 404 $info = &$this->getid3->info; 405 406 $OriginalOffset = $this->ftell(); 407 $commentdataoffset = 0; 408 $VorbisCommentPage = 1; 409 410 switch ($info['audio']['dataformat']) { 411 case 'vorbis': 412 case 'speex': 413 $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block 414 $this->fseek($CommentStartOffset); 415 $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; 416 $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); 417 418 if ($info['audio']['dataformat'] == 'vorbis') { 419 $commentdataoffset += (strlen('vorbis') + 1); 420 } 421 break; 422 423 case 'flac': 424 $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4; 425 $this->fseek($CommentStartOffset); 426 $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']); 427 break; 428 429 default: 430 return false; 431 } 432 433 $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 434 $commentdataoffset += 4; 435 436 $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize); 437 $commentdataoffset += $VendorSize; 438 439 $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 440 $commentdataoffset += 4; 441 $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset; 442 443 $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT'); 444 $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw']; 445 for ($i = 0; $i < $CommentsCount; $i++) { 446 447 $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; 448 449 if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) { 450 if ($oggpageinfo = $this->ParseOggPageHeader()) { 451 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 452 453 $VorbisCommentPage++; 454 455 // First, save what we haven't read yet 456 $AsYetUnusedData = substr($commentdata, $commentdataoffset); 457 458 // Then take that data off the end 459 $commentdata = substr($commentdata, 0, $commentdataoffset); 460 461 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct 462 $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 463 $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 464 465 // Finally, stick the unused data back on the end 466 $commentdata .= $AsYetUnusedData; 467 468 //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 469 $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1)); 470 } 471 472 } 473 $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 474 475 // replace avdataoffset with position just after the last vorbiscomment 476 $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4; 477 478 $commentdataoffset += 4; 479 while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) { 480 if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) { 481 $info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments'; 482 break 2; 483 } 484 485 $VorbisCommentPage++; 486 487 $oggpageinfo = $this->ParseOggPageHeader(); 488 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 489 490 // First, save what we haven't read yet 491 $AsYetUnusedData = substr($commentdata, $commentdataoffset); 492 493 // Then take that data off the end 494 $commentdata = substr($commentdata, 0, $commentdataoffset); 495 496 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct 497 $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 498 $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 499 500 // Finally, stick the unused data back on the end 501 $commentdata .= $AsYetUnusedData; 502 503 //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 504 if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) { 505 $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell(); 506 break; 507 } 508 $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1); 509 if ($readlength <= 0) { 510 $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell(); 511 break; 512 } 513 $commentdata .= $this->fread($readlength); 514 515 //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; 516 } 517 $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset; 518 $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']); 519 $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size']; 520 521 if (!$commentstring) { 522 523 // no comment? 524 $info['warning'][] = 'Blank Ogg comment ['.$i.']'; 525 526 } elseif (strstr($commentstring, '=')) { 527 528 $commentexploded = explode('=', $commentstring, 2); 529 $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]); 530 $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : ''); 531 532 if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') { 533 534 // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE 535 // The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard. 536 // http://flac.sourceforge.net/format.html#metadata_block_picture 537 $flac = new getid3_flac($this->getid3); 538 $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value'])); 539 $flac->parsePICTURE(); 540 $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0]; 541 unset($flac); 542 543 } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') { 544 545 $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']); 546 $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure'); 547 /** @todo use 'coverartmime' where available */ 548 $imageinfo = getid3_lib::GetDataImageSize($data); 549 if ($imageinfo === false || !isset($imageinfo['mime'])) { 550 $this->warning('COVERART vorbiscomment tag contains invalid image'); 551 continue; 552 } 553 554 $ogg = new self($this->getid3); 555 $ogg->setStringMode($data); 556 $info['ogg']['comments']['picture'][] = array( 557 'image_mime' => $imageinfo['mime'], 558 'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']), 559 ); 560 unset($ogg); 561 562 } else { 563 564 $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value']; 565 566 } 567 568 } else { 569 570 $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring; 571 572 } 573 unset($ThisFileInfo_ogg_comments_raw[$i]); 574 } 575 unset($ThisFileInfo_ogg_comments_raw); 576 577 578 // Replay Gain Adjustment 579 // http://privatewww.essex.ac.uk/~djmrob/replaygain/ 580 if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) { 581 foreach ($info['ogg']['comments'] as $index => $commentvalue) { 582 switch ($index) { 583 case 'rg_audiophile': 584 case 'replaygain_album_gain': 585 $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0]; 586 unset($info['ogg']['comments'][$index]); 587 break; 588 589 case 'rg_radio': 590 case 'replaygain_track_gain': 591 $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0]; 592 unset($info['ogg']['comments'][$index]); 593 break; 594 595 case 'replaygain_album_peak': 596 $info['replay_gain']['album']['peak'] = (double) $commentvalue[0]; 597 unset($info['ogg']['comments'][$index]); 598 break; 599 600 case 'rg_peak': 601 case 'replaygain_track_peak': 602 $info['replay_gain']['track']['peak'] = (double) $commentvalue[0]; 603 unset($info['ogg']['comments'][$index]); 604 break; 605 606 case 'replaygain_reference_loudness': 607 $info['replay_gain']['reference_volume'] = (double) $commentvalue[0]; 608 unset($info['ogg']['comments'][$index]); 609 break; 610 611 default: 612 // do nothing 613 break; 614 } 615 } 616 } 617 618 $this->fseek($OriginalOffset); 619 620 return true; 621 } 622 623 public static function SpeexBandModeLookup($mode) { 624 static $SpeexBandModeLookup = array(); 625 if (empty($SpeexBandModeLookup)) { 626 $SpeexBandModeLookup[0] = 'narrow'; 627 $SpeexBandModeLookup[1] = 'wide'; 628 $SpeexBandModeLookup[2] = 'ultra-wide'; 629 } 630 return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null); 631 } 632 633 634 public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { 635 for ($i = 0; $i < $SegmentNumber; $i++) { 636 $segmentlength = 0; 637 foreach ($OggInfoArray['segment_table'] as $key => $value) { 638 $segmentlength += $value; 639 if ($value < 255) { 640 break; 641 } 642 } 643 } 644 return $segmentlength; 645 } 646 647 648 public static function get_quality_from_nominal_bitrate($nominal_bitrate) { 649 650 // decrease precision 651 $nominal_bitrate = $nominal_bitrate / 1000; 652 653 if ($nominal_bitrate < 128) { 654 // q-1 to q4 655 $qval = ($nominal_bitrate - 64) / 16; 656 } elseif ($nominal_bitrate < 256) { 657 // q4 to q8 658 $qval = $nominal_bitrate / 32; 659 } elseif ($nominal_bitrate < 320) { 660 // q8 to q9 661 $qval = ($nominal_bitrate + 256) / 64; 662 } else { 663 // q9 to q10 664 $qval = ($nominal_bitrate + 1300) / 180; 665 } 666 //return $qval; // 5.031324 667 //return intval($qval); // 5 668 return round($qval, 1); // 5 or 4.9 669 } 670 671 } -
new file wp-includes/ID3/module.tag.apetag.php
diff --git wp-includes/ID3/module.tag.apetag.php wp-includes/ID3/module.tag.apetag.php new file mode 100644 index 0000000..afeede7
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.tag.apetag.php // 11 // module for analyzing APE tags // 12 // dependencies: NONE // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 class getid3_apetag extends getid3_handler 17 { 18 public $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory 19 public $overrideendoffset = 0; 20 21 public function Analyze() { 22 $info = &$this->getid3->info; 23 24 if (!getid3_lib::intValueSupported($info['filesize'])) { 25 $info['warning'][] = 'Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; 26 return false; 27 } 28 29 $id3v1tagsize = 128; 30 $apetagheadersize = 32; 31 $lyrics3tagsize = 10; 32 33 if ($this->overrideendoffset == 0) { 34 35 fseek($this->getid3->fp, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END); 36 $APEfooterID3v1 = fread($this->getid3->fp, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize); 37 38 //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) { 39 if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') { 40 41 // APE tag found before ID3v1 42 $info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize; 43 44 //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) { 45 } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') { 46 47 // APE tag found, no ID3v1 48 $info['ape']['tag_offset_end'] = $info['filesize']; 49 50 } 51 52 } else { 53 54 fseek($this->getid3->fp, $this->overrideendoffset - $apetagheadersize, SEEK_SET); 55 if (fread($this->getid3->fp, 8) == 'APETAGEX') { 56 $info['ape']['tag_offset_end'] = $this->overrideendoffset; 57 } 58 59 } 60 if (!isset($info['ape']['tag_offset_end'])) { 61 62 // APE tag not found 63 unset($info['ape']); 64 return false; 65 66 } 67 68 // shortcut 69 $thisfile_ape = &$info['ape']; 70 71 fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET); 72 $APEfooterData = fread($this->getid3->fp, 32); 73 if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) { 74 $info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']; 75 return false; 76 } 77 78 if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) { 79 fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET); 80 $thisfile_ape['tag_offset_start'] = ftell($this->getid3->fp); 81 $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize); 82 } else { 83 $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize']; 84 fseek($this->getid3->fp, $thisfile_ape['tag_offset_start'], SEEK_SET); 85 $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize']); 86 } 87 $info['avdataend'] = $thisfile_ape['tag_offset_start']; 88 89 if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) { 90 $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data'; 91 unset($info['id3v1']); 92 foreach ($info['warning'] as $key => $value) { 93 if ($value == 'Some ID3v1 fields do not use NULL characters for padding') { 94 unset($info['warning'][$key]); 95 sort($info['warning']); 96 break; 97 } 98 } 99 } 100 101 $offset = 0; 102 if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) { 103 if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) { 104 $offset += $apetagheadersize; 105 } else { 106 $info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']; 107 return false; 108 } 109 } 110 111 // shortcut 112 $info['replay_gain'] = array(); 113 $thisfile_replaygain = &$info['replay_gain']; 114 115 for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) { 116 $value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4)); 117 $offset += 4; 118 $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4)); 119 $offset += 4; 120 if (strstr(substr($APEtagData, $offset), "\x00") === false) { 121 $info['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset); 122 return false; 123 } 124 $ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset; 125 $item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength)); 126 127 // shortcut 128 $thisfile_ape['items'][$item_key] = array(); 129 $thisfile_ape_items_current = &$thisfile_ape['items'][$item_key]; 130 131 $thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset; 132 133 $offset += ($ItemKeyLength + 1); // skip 0x00 terminator 134 $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size); 135 $offset += $value_size; 136 137 $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags); 138 switch ($thisfile_ape_items_current['flags']['item_contents_raw']) { 139 case 0: // UTF-8 140 case 3: // Locator (URL, filename, etc), UTF-8 encoded 141 $thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data'])); 142 break; 143 144 default: // binary data 145 break; 146 } 147 148 switch (strtolower($item_key)) { 149 case 'replaygain_track_gain': 150 $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! 151 $thisfile_replaygain['track']['originator'] = 'unspecified'; 152 break; 153 154 case 'replaygain_track_peak': 155 $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! 156 $thisfile_replaygain['track']['originator'] = 'unspecified'; 157 if ($thisfile_replaygain['track']['peak'] <= 0) { 158 $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; 159 } 160 break; 161 162 case 'replaygain_album_gain': 163 $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! 164 $thisfile_replaygain['album']['originator'] = 'unspecified'; 165 break; 166 167 case 'replaygain_album_peak': 168 $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! 169 $thisfile_replaygain['album']['originator'] = 'unspecified'; 170 if ($thisfile_replaygain['album']['peak'] <= 0) { 171 $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; 172 } 173 break; 174 175 case 'mp3gain_undo': 176 list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]); 177 $thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left); 178 $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right); 179 $thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false); 180 break; 181 182 case 'mp3gain_minmax': 183 list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]); 184 $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min); 185 $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max); 186 break; 187 188 case 'mp3gain_album_minmax': 189 list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]); 190 $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min); 191 $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max); 192 break; 193 194 case 'tracknumber': 195 if (is_array($thisfile_ape_items_current['data'])) { 196 foreach ($thisfile_ape_items_current['data'] as $comment) { 197 $thisfile_ape['comments']['track'][] = $comment; 198 } 199 } 200 break; 201 202 case 'cover art (artist)': 203 case 'cover art (back)': 204 case 'cover art (band logo)': 205 case 'cover art (band)': 206 case 'cover art (colored fish)': 207 case 'cover art (composer)': 208 case 'cover art (conductor)': 209 case 'cover art (front)': 210 case 'cover art (icon)': 211 case 'cover art (illustration)': 212 case 'cover art (lead)': 213 case 'cover art (leaflet)': 214 case 'cover art (lyricist)': 215 case 'cover art (media)': 216 case 'cover art (movie scene)': 217 case 'cover art (other icon)': 218 case 'cover art (other)': 219 case 'cover art (performance)': 220 case 'cover art (publisher logo)': 221 case 'cover art (recording)': 222 case 'cover art (studio)': 223 // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html 224 list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2); 225 $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00"); 226 $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']); 227 228 $thisfile_ape_items_current['image_mime'] = ''; 229 $imageinfo = array(); 230 $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo); 231 $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); 232 233 do { 234 if ($this->inline_attachments === false) { 235 // skip entirely 236 unset($thisfile_ape_items_current['data']); 237 break; 238 } 239 if ($this->inline_attachments === true) { 240 // great 241 } elseif (is_int($this->inline_attachments)) { 242 if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) { 243 // too big, skip 244 $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)'; 245 unset($thisfile_ape_items_current['data']); 246 break; 247 } 248 } elseif (is_string($this->inline_attachments)) { 249 $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); 250 if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { 251 // cannot write, skip 252 $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; 253 unset($thisfile_ape_items_current['data']); 254 break; 255 } 256 } 257 // if we get this far, must be OK 258 if (is_string($this->inline_attachments)) { 259 $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset']; 260 if (!file_exists($destination_filename) || is_writable($destination_filename)) { 261 file_put_contents($destination_filename, $thisfile_ape_items_current['data']); 262 } else { 263 $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)'; 264 } 265 $thisfile_ape_items_current['data_filename'] = $destination_filename; 266 unset($thisfile_ape_items_current['data']); 267 } else { 268 if (!isset($info['ape']['comments']['picture'])) { 269 $info['ape']['comments']['picture'] = array(); 270 } 271 $info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']); 272 } 273 } while (false); 274 break; 275 276 default: 277 if (is_array($thisfile_ape_items_current['data'])) { 278 foreach ($thisfile_ape_items_current['data'] as $comment) { 279 $thisfile_ape['comments'][strtolower($item_key)][] = $comment; 280 } 281 } 282 break; 283 } 284 285 } 286 if (empty($thisfile_replaygain)) { 287 unset($info['replay_gain']); 288 } 289 return true; 290 } 291 292 public function parseAPEheaderFooter($APEheaderFooterData) { 293 // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html 294 295 // shortcut 296 $headerfooterinfo['raw'] = array(); 297 $headerfooterinfo_raw = &$headerfooterinfo['raw']; 298 299 $headerfooterinfo_raw['footer_tag'] = substr($APEheaderFooterData, 0, 8); 300 if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') { 301 return false; 302 } 303 $headerfooterinfo_raw['version'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 8, 4)); 304 $headerfooterinfo_raw['tagsize'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4)); 305 $headerfooterinfo_raw['tag_items'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4)); 306 $headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4)); 307 $headerfooterinfo_raw['reserved'] = substr($APEheaderFooterData, 24, 8); 308 309 $headerfooterinfo['tag_version'] = $headerfooterinfo_raw['version'] / 1000; 310 if ($headerfooterinfo['tag_version'] >= 2) { 311 $headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']); 312 } 313 return $headerfooterinfo; 314 } 315 316 public function parseAPEtagFlags($rawflagint) { 317 // "Note: APE Tags 1.0 do not use any of the APE Tag flags. 318 // All are set to zero on creation and ignored on reading." 319 // http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html 320 $flags['header'] = (bool) ($rawflagint & 0x80000000); 321 $flags['footer'] = (bool) ($rawflagint & 0x40000000); 322 $flags['this_is_header'] = (bool) ($rawflagint & 0x20000000); 323 $flags['item_contents_raw'] = ($rawflagint & 0x00000006) >> 1; 324 $flags['read_only'] = (bool) ($rawflagint & 0x00000001); 325 326 $flags['item_contents'] = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']); 327 328 return $flags; 329 } 330 331 public function APEcontentTypeFlagLookup($contenttypeid) { 332 static $APEcontentTypeFlagLookup = array( 333 0 => 'utf-8', 334 1 => 'binary', 335 2 => 'external', 336 3 => 'reserved' 337 ); 338 return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid'); 339 } 340 341 public function APEtagItemIsUTF8Lookup($itemkey) { 342 static $APEtagItemIsUTF8Lookup = array( 343 'title', 344 'subtitle', 345 'artist', 346 'album', 347 'debut album', 348 'publisher', 349 'conductor', 350 'track', 351 'composer', 352 'comment', 353 'copyright', 354 'publicationright', 355 'file', 356 'year', 357 'record date', 358 'record location', 359 'genre', 360 'media', 361 'related', 362 'isrc', 363 'abstract', 364 'language', 365 'bibliography' 366 ); 367 return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup); 368 } 369 370 } -
new file wp-includes/ID3/module.tag.id3v1.php
diff --git wp-includes/ID3/module.tag.id3v1.php wp-includes/ID3/module.tag.id3v1.php new file mode 100644 index 0000000..fd9069e
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 // // 10 // module.tag.id3v1.php // 11 // module for analyzing ID3v1 tags // 12 // dependencies: NONE // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 17 class getid3_id3v1 extends getid3_handler 18 { 19 20 public function Analyze() { 21 $info = &$this->getid3->info; 22 23 if (!getid3_lib::intValueSupported($info['filesize'])) { 24 $info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; 25 return false; 26 } 27 28 fseek($this->getid3->fp, -256, SEEK_END); 29 $preid3v1 = fread($this->getid3->fp, 128); 30 $id3v1tag = fread($this->getid3->fp, 128); 31 32 if (substr($id3v1tag, 0, 3) == 'TAG') { 33 34 $info['avdataend'] = $info['filesize'] - 128; 35 36 $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30)); 37 $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30)); 38 $ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30)); 39 $ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4)); 40 $ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them 41 $ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1)); 42 43 // If second-last byte of comment field is null and last byte of comment field is non-null 44 // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number 45 if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) { 46 $ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1)); 47 $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28); 48 } 49 $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']); 50 51 $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']); 52 if (!empty($ParsedID3v1['genre'])) { 53 unset($ParsedID3v1['genreid']); 54 } 55 if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) { 56 unset($ParsedID3v1['genre']); 57 } 58 59 foreach ($ParsedID3v1 as $key => $value) { 60 $ParsedID3v1['comments'][$key][0] = $value; 61 } 62 63 // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces 64 $GoodFormatID3v1tag = $this->GenerateID3v1Tag( 65 $ParsedID3v1['title'], 66 $ParsedID3v1['artist'], 67 $ParsedID3v1['album'], 68 $ParsedID3v1['year'], 69 (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false), 70 $ParsedID3v1['comment'], 71 (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : '')); 72 $ParsedID3v1['padding_valid'] = true; 73 if ($id3v1tag !== $GoodFormatID3v1tag) { 74 $ParsedID3v1['padding_valid'] = false; 75 $info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding'; 76 } 77 78 $ParsedID3v1['tag_offset_end'] = $info['filesize']; 79 $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128; 80 81 $info['id3v1'] = $ParsedID3v1; 82 } 83 84 if (substr($preid3v1, 0, 3) == 'TAG') { 85 // The way iTunes handles tags is, well, brain-damaged. 86 // It completely ignores v1 if ID3v2 is present. 87 // This goes as far as adding a new v1 tag *even if there already is one* 88 89 // A suspected double-ID3v1 tag has been detected, but it could be that 90 // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag 91 if (substr($preid3v1, 96, 8) == 'APETAGEX') { 92 // an APE tag footer was found before the last ID3v1, assume false "TAG" synch 93 } elseif (substr($preid3v1, 119, 6) == 'LYRICS') { 94 // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch 95 } else { 96 // APE and Lyrics3 footers not found - assume double ID3v1 97 $info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes'; 98 $info['avdataend'] -= 128; 99 } 100 } 101 102 return true; 103 } 104 105 public static function cutfield($str) { 106 return trim(substr($str, 0, strcspn($str, "\x00"))); 107 } 108 109 public static function ArrayOfGenres($allowSCMPXextended=false) { 110 static $GenreLookup = array( 111 0 => 'Blues', 112 1 => 'Classic Rock', 113 2 => 'Country', 114 3 => 'Dance', 115 4 => 'Disco', 116 5 => 'Funk', 117 6 => 'Grunge', 118 7 => 'Hip-Hop', 119 8 => 'Jazz', 120 9 => 'Metal', 121 10 => 'New Age', 122 11 => 'Oldies', 123 12 => 'Other', 124 13 => 'Pop', 125 14 => 'R&B', 126 15 => 'Rap', 127 16 => 'Reggae', 128 17 => 'Rock', 129 18 => 'Techno', 130 19 => 'Industrial', 131 20 => 'Alternative', 132 21 => 'Ska', 133 22 => 'Death Metal', 134 23 => 'Pranks', 135 24 => 'Soundtrack', 136 25 => 'Euro-Techno', 137 26 => 'Ambient', 138 27 => 'Trip-Hop', 139 28 => 'Vocal', 140 29 => 'Jazz+Funk', 141 30 => 'Fusion', 142 31 => 'Trance', 143 32 => 'Classical', 144 33 => 'Instrumental', 145 34 => 'Acid', 146 35 => 'House', 147 36 => 'Game', 148 37 => 'Sound Clip', 149 38 => 'Gospel', 150 39 => 'Noise', 151 40 => 'Alt. Rock', 152 41 => 'Bass', 153 42 => 'Soul', 154 43 => 'Punk', 155 44 => 'Space', 156 45 => 'Meditative', 157 46 => 'Instrumental Pop', 158 47 => 'Instrumental Rock', 159 48 => 'Ethnic', 160 49 => 'Gothic', 161 50 => 'Darkwave', 162 51 => 'Techno-Industrial', 163 52 => 'Electronic', 164 53 => 'Pop-Folk', 165 54 => 'Eurodance', 166 55 => 'Dream', 167 56 => 'Southern Rock', 168 57 => 'Comedy', 169 58 => 'Cult', 170 59 => 'Gangsta Rap', 171 60 => 'Top 40', 172 61 => 'Christian Rap', 173 62 => 'Pop/Funk', 174 63 => 'Jungle', 175 64 => 'Native American', 176 65 => 'Cabaret', 177 66 => 'New Wave', 178 67 => 'Psychedelic', 179 68 => 'Rave', 180 69 => 'Showtunes', 181 70 => 'Trailer', 182 71 => 'Lo-Fi', 183 72 => 'Tribal', 184 73 => 'Acid Punk', 185 74 => 'Acid Jazz', 186 75 => 'Polka', 187 76 => 'Retro', 188 77 => 'Musical', 189 78 => 'Rock & Roll', 190 79 => 'Hard Rock', 191 80 => 'Folk', 192 81 => 'Folk/Rock', 193 82 => 'National Folk', 194 83 => 'Swing', 195 84 => 'Fast-Fusion', 196 85 => 'Bebob', 197 86 => 'Latin', 198 87 => 'Revival', 199 88 => 'Celtic', 200 89 => 'Bluegrass', 201 90 => 'Avantgarde', 202 91 => 'Gothic Rock', 203 92 => 'Progressive Rock', 204 93 => 'Psychedelic Rock', 205 94 => 'Symphonic Rock', 206 95 => 'Slow Rock', 207 96 => 'Big Band', 208 97 => 'Chorus', 209 98 => 'Easy Listening', 210 99 => 'Acoustic', 211 100 => 'Humour', 212 101 => 'Speech', 213 102 => 'Chanson', 214 103 => 'Opera', 215 104 => 'Chamber Music', 216 105 => 'Sonata', 217 106 => 'Symphony', 218 107 => 'Booty Bass', 219 108 => 'Primus', 220 109 => 'Porn Groove', 221 110 => 'Satire', 222 111 => 'Slow Jam', 223 112 => 'Club', 224 113 => 'Tango', 225 114 => 'Samba', 226 115 => 'Folklore', 227 116 => 'Ballad', 228 117 => 'Power Ballad', 229 118 => 'Rhythmic Soul', 230 119 => 'Freestyle', 231 120 => 'Duet', 232 121 => 'Punk Rock', 233 122 => 'Drum Solo', 234 123 => 'A Cappella', 235 124 => 'Euro-House', 236 125 => 'Dance Hall', 237 126 => 'Goa', 238 127 => 'Drum & Bass', 239 128 => 'Club-House', 240 129 => 'Hardcore', 241 130 => 'Terror', 242 131 => 'Indie', 243 132 => 'BritPop', 244 133 => 'Negerpunk', 245 134 => 'Polsk Punk', 246 135 => 'Beat', 247 136 => 'Christian Gangsta Rap', 248 137 => 'Heavy Metal', 249 138 => 'Black Metal', 250 139 => 'Crossover', 251 140 => 'Contemporary Christian', 252 141 => 'Christian Rock', 253 142 => 'Merengue', 254 143 => 'Salsa', 255 144 => 'Thrash Metal', 256 145 => 'Anime', 257 146 => 'JPop', 258 147 => 'Synthpop', 259 260 255 => 'Unknown', 261 262 'CR' => 'Cover', 263 'RX' => 'Remix' 264 ); 265 266 static $GenreLookupSCMPX = array(); 267 if ($allowSCMPXextended && empty($GenreLookupSCMPX)) { 268 $GenreLookupSCMPX = $GenreLookup; 269 // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended 270 // Extended ID3v1 genres invented by SCMPX 271 // Note that 255 "Japanese Anime" conflicts with standard "Unknown" 272 $GenreLookupSCMPX[240] = 'Sacred'; 273 $GenreLookupSCMPX[241] = 'Northern Europe'; 274 $GenreLookupSCMPX[242] = 'Irish & Scottish'; 275 $GenreLookupSCMPX[243] = 'Scotland'; 276 $GenreLookupSCMPX[244] = 'Ethnic Europe'; 277 $GenreLookupSCMPX[245] = 'Enka'; 278 $GenreLookupSCMPX[246] = 'Children\'s Song'; 279 $GenreLookupSCMPX[247] = 'Japanese Sky'; 280 $GenreLookupSCMPX[248] = 'Japanese Heavy Rock'; 281 $GenreLookupSCMPX[249] = 'Japanese Doom Rock'; 282 $GenreLookupSCMPX[250] = 'Japanese J-POP'; 283 $GenreLookupSCMPX[251] = 'Japanese Seiyu'; 284 $GenreLookupSCMPX[252] = 'Japanese Ambient Techno'; 285 $GenreLookupSCMPX[253] = 'Japanese Moemoe'; 286 $GenreLookupSCMPX[254] = 'Japanese Tokusatsu'; 287 //$GenreLookupSCMPX[255] = 'Japanese Anime'; 288 } 289 290 return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup); 291 } 292 293 public static function LookupGenreName($genreid, $allowSCMPXextended=true) { 294 switch ($genreid) { 295 case 'RX': 296 case 'CR': 297 break; 298 default: 299 if (!is_numeric($genreid)) { 300 return false; 301 } 302 $genreid = intval($genreid); // to handle 3 or '3' or '03' 303 break; 304 } 305 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); 306 return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); 307 } 308 309 public static function LookupGenreID($genre, $allowSCMPXextended=false) { 310 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); 311 $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre)); 312 foreach ($GenreLookup as $key => $value) { 313 if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { 314 return $key; 315 } 316 } 317 return false; 318 } 319 320 public static function StandardiseID3v1GenreName($OriginalGenre) { 321 if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) { 322 return self::LookupGenreName($GenreID); 323 } 324 return $OriginalGenre; 325 } 326 327 public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { 328 $ID3v1Tag = 'TAG'; 329 $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT); 330 $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT); 331 $ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT); 332 $ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT); 333 if (!empty($track) && ($track > 0) && ($track <= 255)) { 334 $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT); 335 $ID3v1Tag .= "\x00"; 336 if (gettype($track) == 'string') { 337 $track = (int) $track; 338 } 339 $ID3v1Tag .= chr($track); 340 } else { 341 $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT); 342 } 343 if (($genreid < 0) || ($genreid > 147)) { 344 $genreid = 255; // 'unknown' genre 345 } 346 switch (gettype($genreid)) { 347 case 'string': 348 case 'integer': 349 $ID3v1Tag .= chr(intval($genreid)); 350 break; 351 default: 352 $ID3v1Tag .= chr(255); // 'unknown' genre 353 break; 354 } 355 356 return $ID3v1Tag; 357 } 358 359 } -
new file wp-includes/ID3/module.tag.id3v2.php
diff --git wp-includes/ID3/module.tag.id3v2.php wp-includes/ID3/module.tag.id3v2.php new file mode 100644 index 0000000..23784e9
- + 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 ///////////////////////////////////////////////////////////////// 7 // See readme.txt for more details // 8 ///////////////////////////////////////////////////////////////// 9 /// // 10 // module.tag.id3v2.php // 11 // module for analyzing ID3v2 tags // 12 // dependencies: module.tag.id3v1.php // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); 17 18 class getid3_id3v2 extends getid3_handler 19 { 20 public $StartingOffset = 0; 21 22 public function Analyze() { 23 $info = &$this->getid3->info; 24 25 // Overall tag structure: 26 // +-----------------------------+ 27 // | Header (10 bytes) | 28 // +-----------------------------+ 29 // | Extended Header | 30 // | (variable length, OPTIONAL) | 31 // +-----------------------------+ 32 // | Frames (variable length) | 33 // +-----------------------------+ 34 // | Padding | 35 // | (variable length, OPTIONAL) | 36 // +-----------------------------+ 37 // | Footer (10 bytes, OPTIONAL) | 38 // +-----------------------------+ 39 40 // Header 41 // ID3v2/file identifier "ID3" 42 // ID3v2 version $04 00 43 // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x) 44 // ID3v2 size 4 * %0xxxxxxx 45 46 47 // shortcuts 48 $info['id3v2']['header'] = true; 49 $thisfile_id3v2 = &$info['id3v2']; 50 $thisfile_id3v2['flags'] = array(); 51 $thisfile_id3v2_flags = &$thisfile_id3v2['flags']; 52 53 54 fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET); 55 $header = fread($this->getid3->fp, 10); 56 if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) { 57 58 $thisfile_id3v2['majorversion'] = ord($header{3}); 59 $thisfile_id3v2['minorversion'] = ord($header{4}); 60 61 // shortcut 62 $id3v2_majorversion = &$thisfile_id3v2['majorversion']; 63 64 } else { 65 66 unset($info['id3v2']); 67 return false; 68 69 } 70 71 if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists) 72 73 $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']; 74 return false; 75 76 } 77 78 $id3_flags = ord($header{5}); 79 switch ($id3v2_majorversion) { 80 case 2: 81 // %ab000000 in v2.2 82 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation 83 $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression 84 break; 85 86 case 3: 87 // %abc00000 in v2.3 88 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation 89 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header 90 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator 91 break; 92 93 case 4: 94 // %abcd0000 in v2.4 95 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation 96 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header 97 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator 98 $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present 99 break; 100 } 101 102 $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length 103 104 $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset; 105 $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength']; 106 107 108 109 // create 'encoding' key - used by getid3::HandleAllTags() 110 // in ID3v2 every field can have it's own encoding type 111 // so force everything to UTF-8 so it can be handled consistantly 112 $thisfile_id3v2['encoding'] = 'UTF-8'; 113 114 115 // Frames 116 117 // All ID3v2 frames consists of one frame header followed by one or more 118 // fields containing the actual information. The header is always 10 119 // bytes and laid out as follows: 120 // 121 // Frame ID $xx xx xx xx (four characters) 122 // Size 4 * %0xxxxxxx 123 // Flags $xx xx 124 125 $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header 126 if (!empty($thisfile_id3v2['exthead']['length'])) { 127 $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4); 128 } 129 if (!empty($thisfile_id3v2_flags['isfooter'])) { 130 $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio 131 } 132 if ($sizeofframes > 0) { 133 134 $framedata = fread($this->getid3->fp, $sizeofframes); // read all frames from file into $framedata variable 135 136 // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x) 137 if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) { 138 $framedata = $this->DeUnsynchronise($framedata); 139 } 140 // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead 141 // of on tag level, making it easier to skip frames, increasing the streamability 142 // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that 143 // there exists an unsynchronised frame, while the new unsynchronisation flag in 144 // the frame header [S:4.1.2] indicates unsynchronisation. 145 146 147 //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present) 148 $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header 149 150 151 // Extended Header 152 if (!empty($thisfile_id3v2_flags['exthead'])) { 153 $extended_header_offset = 0; 154 155 if ($id3v2_majorversion == 3) { 156 157 // v2.3 definition: 158 //Extended header size $xx xx xx xx // 32-bit integer 159 //Extended Flags $xx xx 160 // %x0000000 %00000000 // v2.3 161 // x - CRC data present 162 //Size of padding $xx xx xx xx 163 164 $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0); 165 $extended_header_offset += 4; 166 167 $thisfile_id3v2['exthead']['flag_bytes'] = 2; 168 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes'])); 169 $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes']; 170 171 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000); 172 173 $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4)); 174 $extended_header_offset += 4; 175 176 if ($thisfile_id3v2['exthead']['flags']['crc']) { 177 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4)); 178 $extended_header_offset += 4; 179 } 180 $extended_header_offset += $thisfile_id3v2['exthead']['padding_size']; 181 182 } elseif ($id3v2_majorversion == 4) { 183 184 // v2.4 definition: 185 //Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer 186 //Number of flag bytes $01 187 //Extended Flags $xx 188 // %0bcd0000 // v2.4 189 // b - Tag is an update 190 // Flag data length $00 191 // c - CRC data present 192 // Flag data length $05 193 // Total frame CRC 5 * %0xxxxxxx 194 // d - Tag restrictions 195 // Flag data length $01 196 197 $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true); 198 $extended_header_offset += 4; 199 200 $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1 201 $extended_header_offset += 1; 202 203 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes'])); 204 $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes']; 205 206 $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40); 207 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20); 208 $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10); 209 210 if ($thisfile_id3v2['exthead']['flags']['update']) { 211 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0 212 $extended_header_offset += 1; 213 } 214 215 if ($thisfile_id3v2['exthead']['flags']['crc']) { 216 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5 217 $extended_header_offset += 1; 218 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false); 219 $extended_header_offset += $ext_header_chunk_length; 220 } 221 222 if ($thisfile_id3v2['exthead']['flags']['restrictions']) { 223 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1 224 $extended_header_offset += 1; 225 226 // %ppqrrstt 227 $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); 228 $extended_header_offset += 1; 229 $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions 230 $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions 231 $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions 232 $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions 233 $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions 234 235 $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']); 236 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']); 237 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']); 238 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']); 239 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']); 240 } 241 242 if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) { 243 $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')'; 244 } 245 } 246 247 $framedataoffset += $extended_header_offset; 248 $framedata = substr($framedata, $extended_header_offset); 249 } // end extended header 250 251 252 while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse 253 if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) { 254 // insufficient room left in ID3v2 header for actual data - must be padding 255 $thisfile_id3v2['padding']['start'] = $framedataoffset; 256 $thisfile_id3v2['padding']['length'] = strlen($framedata); 257 $thisfile_id3v2['padding']['valid'] = true; 258 for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) { 259 if ($framedata{$i} != "\x00") { 260 $thisfile_id3v2['padding']['valid'] = false; 261 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; 262 $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; 263 break; 264 } 265 } 266 break; // skip rest of ID3v2 header 267 } 268 if ($id3v2_majorversion == 2) { 269 // Frame ID $xx xx xx (three characters) 270 // Size $xx xx xx (24-bit integer) 271 // Flags $xx xx 272 273 $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header 274 $framedata = substr($framedata, 6); // and leave the rest in $framedata 275 $frame_name = substr($frame_header, 0, 3); 276 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0); 277 $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs 278 279 } elseif ($id3v2_majorversion > 2) { 280 281 // Frame ID $xx xx xx xx (four characters) 282 // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+) 283 // Flags $xx xx 284 285 $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header 286 $framedata = substr($framedata, 10); // and leave the rest in $framedata 287 288 $frame_name = substr($frame_header, 0, 4); 289 if ($id3v2_majorversion == 3) { 290 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer 291 } else { // ID3v2.4+ 292 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value) 293 } 294 295 if ($frame_size < (strlen($framedata) + 4)) { 296 $nextFrameID = substr($framedata, $frame_size, 4); 297 if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) { 298 // next frame is OK 299 } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) { 300 // MP3ext known broken frames - "ok" for the purposes of this test 301 } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) { 302 $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3'; 303 $id3v2_majorversion = 3; 304 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer 305 } 306 } 307 308 309 $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2)); 310 } 311 312 if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) { 313 // padding encountered 314 315 $thisfile_id3v2['padding']['start'] = $framedataoffset; 316 $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata); 317 $thisfile_id3v2['padding']['valid'] = true; 318 319 $len = strlen($framedata); 320 for ($i = 0; $i < $len; $i++) { 321 if ($framedata{$i} != "\x00") { 322 $thisfile_id3v2['padding']['valid'] = false; 323 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; 324 $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; 325 break; 326 } 327 } 328 break; // skip rest of ID3v2 header 329 } 330 331 if ($frame_name == 'COM ') { 332 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]'; 333 $frame_name = 'COMM'; 334 } 335 if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { 336 337 unset($parsedFrame); 338 $parsedFrame['frame_name'] = $frame_name; 339 $parsedFrame['frame_flags_raw'] = $frame_flags; 340 $parsedFrame['data'] = substr($framedata, 0, $frame_size); 341 $parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size); 342 $parsedFrame['dataoffset'] = $framedataoffset; 343 344 $this->ParseID3v2Frame($parsedFrame); 345 $thisfile_id3v2[$frame_name][] = $parsedFrame; 346 347 $framedata = substr($framedata, $frame_size); 348 349 } else { // invalid frame length or FrameID 350 351 if ($frame_size <= strlen($framedata)) { 352 353 if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) { 354 355 // next frame is valid, just skip the current frame 356 $framedata = substr($framedata, $frame_size); 357 $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.'; 358 359 } else { 360 361 // next frame is invalid too, abort processing 362 //unset($framedata); 363 $framedata = null; 364 $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.'; 365 366 } 367 368 } elseif ($frame_size == strlen($framedata)) { 369 370 // this is the last frame, just skip 371 $info['warning'][] = 'This was the last ID3v2 frame.'; 372 373 } else { 374 375 // next frame is invalid too, abort processing 376 //unset($framedata); 377 $framedata = null; 378 $info['warning'][] = 'Invalid ID3v2 frame size, aborting.'; 379 380 } 381 if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) { 382 383 switch ($frame_name) { 384 case "\x00\x00".'MP': 385 case "\x00".'MP3': 386 case ' MP3': 387 case 'MP3e': 388 case "\x00".'MP': 389 case ' MP': 390 case 'MP3': 391 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]'; 392 break; 393 394 default: 395 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).'; 396 break; 397 } 398 399 } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) { 400 401 $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).'; 402 403 } else { 404 405 $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).'; 406 407 } 408 409 } 410 $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion)); 411 412 } 413 414 } 415 416 417 // Footer 418 419 // The footer is a copy of the header, but with a different identifier. 420 // ID3v2 identifier "3DI" 421 // ID3v2 version $04 00 422 // ID3v2 flags %abcd0000 423 // ID3v2 size 4 * %0xxxxxxx 424 425 if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) { 426 $footer = fread($this->getid3->fp, 10); 427 if (substr($footer, 0, 3) == '3DI') { 428 $thisfile_id3v2['footer'] = true; 429 $thisfile_id3v2['majorversion_footer'] = ord($footer{3}); 430 $thisfile_id3v2['minorversion_footer'] = ord($footer{4}); 431 } 432 if ($thisfile_id3v2['majorversion_footer'] <= 4) { 433 $id3_flags = ord(substr($footer{5})); 434 $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80); 435 $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40); 436 $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20); 437 $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10); 438 439 $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1); 440 } 441 } // end footer 442 443 if (isset($thisfile_id3v2['comments']['genre'])) { 444 foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) { 445 unset($thisfile_id3v2['comments']['genre'][$key]); 446 $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value))); 447 } 448 } 449 450 if (isset($thisfile_id3v2['comments']['track'])) { 451 foreach ($thisfile_id3v2['comments']['track'] as $key => $value) { 452 if (strstr($value, '/')) { 453 list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]); 454 } 455 } 456 } 457 458 if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) { 459 $thisfile_id3v2['comments']['year'] = array($matches[1]); 460 } 461 462 463 if (!empty($thisfile_id3v2['TXXX'])) { 464 // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames 465 foreach ($thisfile_id3v2['TXXX'] as $txxx_array) { 466 switch ($txxx_array['description']) { 467 case 'replaygain_track_gain': 468 if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) { 469 $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); 470 } 471 break; 472 case 'replaygain_track_peak': 473 if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) { 474 $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']); 475 } 476 break; 477 case 'replaygain_album_gain': 478 if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) { 479 $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); 480 } 481 break; 482 } 483 } 484 } 485 486 487 // Set avdataoffset 488 $info['avdataoffset'] = $thisfile_id3v2['headerlength']; 489 if (isset($thisfile_id3v2['footer'])) { 490 $info['avdataoffset'] += 10; 491 } 492 493 return true; 494 } 495 496 497 public function ParseID3v2GenreString($genrestring) { 498 // Parse genres into arrays of genreName and genreID 499 // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)' 500 // ID3v2.4.x: '21' $00 'Eurodisco' $00 501 $clean_genres = array(); 502 if (strpos($genrestring, "\x00") === false) { 503 $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring); 504 } 505 $genre_elements = explode("\x00", $genrestring); 506 foreach ($genre_elements as $element) { 507 $element = trim($element); 508 if ($element) { 509 if (preg_match('#^[0-9]{1,3}#', $element)) { 510 $clean_genres[] = getid3_id3v1::LookupGenreName($element); 511 } else { 512 $clean_genres[] = str_replace('((', '(', $element); 513 } 514 } 515 } 516 return $clean_genres; 517 } 518 519 520 public function ParseID3v2Frame(&$parsedFrame) { 521 522 // shortcuts 523 $info = &$this->getid3->info; 524 $id3v2_majorversion = $info['id3v2']['majorversion']; 525 526 $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']); 527 if (empty($parsedFrame['framenamelong'])) { 528 unset($parsedFrame['framenamelong']); 529 } 530 $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']); 531 if (empty($parsedFrame['framenameshort'])) { 532 unset($parsedFrame['framenameshort']); 533 } 534 535 if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard 536 if ($id3v2_majorversion == 3) { 537 // Frame Header Flags 538 // %abc00000 %ijk00000 539 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation 540 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation 541 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only 542 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression 543 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption 544 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity 545 546 } elseif ($id3v2_majorversion == 4) { 547 // Frame Header Flags 548 // %0abc0000 %0h00kmnp 549 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation 550 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation 551 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only 552 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity 553 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression 554 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption 555 $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation 556 $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator 557 558 // Frame-level de-unsynchronisation - ID3v2.4 559 if ($parsedFrame['flags']['Unsynchronisation']) { 560 $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']); 561 } 562 563 if ($parsedFrame['flags']['DataLengthIndicator']) { 564 $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1); 565 $parsedFrame['data'] = substr($parsedFrame['data'], 4); 566 } 567 } 568 569 // Frame-level de-compression 570 if ($parsedFrame['flags']['compression']) { 571 $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4)); 572 if (!function_exists('gzuncompress')) { 573 $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'; 574 } else { 575 if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { 576 //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) { 577 $parsedFrame['data'] = $decompresseddata; 578 unset($decompresseddata); 579 } else { 580 $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'; 581 } 582 } 583 } 584 } 585 586 if (!empty($parsedFrame['flags']['DataLengthIndicator'])) { 587 if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) { 588 $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data'; 589 } 590 } 591 592 if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) { 593 594 $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion'; 595 switch ($parsedFrame['frame_name']) { 596 case 'WCOM': 597 $warning .= ' (this is known to happen with files tagged by RioPort)'; 598 break; 599 600 default: 601 break; 602 } 603 $info['warning'][] = $warning; 604 605 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier 606 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier 607 // There may be more than one 'UFID' frame in a tag, 608 // but only one with the same 'Owner identifier'. 609 // <Header for 'Unique file identifier', ID: 'UFID'> 610 // Owner identifier <text string> $00 611 // Identifier <up to 64 bytes binary data> 612 $exploded = explode("\x00", $parsedFrame['data'], 2); 613 $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : ''); 614 $parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : ''); 615 616 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame 617 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame 618 // There may be more than one 'TXXX' frame in each tag, 619 // but only one with the same description. 620 // <Header for 'User defined text information frame', ID: 'TXXX'> 621 // Text encoding $xx 622 // Description <text string according to encoding> $00 (00) 623 // Value <text string according to encoding> 624 625 $frame_offset = 0; 626 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 627 628 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 629 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 630 } 631 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); 632 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 633 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 634 } 635 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 636 if (ord($frame_description) === 0) { 637 $frame_description = ''; 638 } 639 $parsedFrame['encodingid'] = $frame_textencoding; 640 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 641 642 $parsedFrame['description'] = $frame_description; 643 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); 644 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { 645 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); 646 } 647 //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain 648 649 650 } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame 651 // There may only be one text information frame of its kind in an tag. 652 // <Header for 'Text information frame', ID: 'T000' - 'TZZZ', 653 // excluding 'TXXX' described in 4.2.6.> 654 // Text encoding $xx 655 // Information <text string(s) according to encoding> 656 657 $frame_offset = 0; 658 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 659 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 660 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 661 } 662 663 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); 664 665 $parsedFrame['encodingid'] = $frame_textencoding; 666 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 667 668 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { 669 // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with / 670 // This of course breaks when an aritst name contains slash character, e.g. "AC/DC" 671 // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense 672 // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user 673 switch ($parsedFrame['encoding']) { 674 case 'UTF-16': 675 case 'UTF-16BE': 676 case 'UTF-16LE': 677 $wordsize = 2; 678 break; 679 case 'ISO-8859-1': 680 case 'UTF-8': 681 default: 682 $wordsize = 1; 683 break; 684 } 685 $Txxx_elements = array(); 686 $Txxx_elements_start_offset = 0; 687 for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) { 688 if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) { 689 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset); 690 $Txxx_elements_start_offset = $i + $wordsize; 691 } 692 } 693 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset); 694 foreach ($Txxx_elements as $Txxx_element) { 695 $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element); 696 if (!empty($string)) { 697 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; 698 } 699 } 700 unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset); 701 } 702 703 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame 704 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame 705 // There may be more than one 'WXXX' frame in each tag, 706 // but only one with the same description 707 // <Header for 'User defined URL link frame', ID: 'WXXX'> 708 // Text encoding $xx 709 // Description <text string according to encoding> $00 (00) 710 // URL <text string> 711 712 $frame_offset = 0; 713 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 714 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 715 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 716 } 717 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); 718 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 719 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 720 } 721 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 722 723 if (ord($frame_description) === 0) { 724 $frame_description = ''; 725 } 726 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); 727 728 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); 729 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 730 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 731 } 732 if ($frame_terminatorpos) { 733 // there are null bytes after the data - this is not according to spec 734 // only use data up to first null byte 735 $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos); 736 } else { 737 // no null bytes following data, just use all data 738 $frame_urldata = (string) $parsedFrame['data']; 739 } 740 741 $parsedFrame['encodingid'] = $frame_textencoding; 742 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 743 744 $parsedFrame['url'] = $frame_urldata; 745 $parsedFrame['description'] = $frame_description; 746 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { 747 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']); 748 } 749 unset($parsedFrame['data']); 750 751 752 } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames 753 // There may only be one URL link frame of its kind in a tag, 754 // except when stated otherwise in the frame description 755 // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX' 756 // described in 4.3.2.> 757 // URL <text string> 758 759 $parsedFrame['url'] = trim($parsedFrame['data']); 760 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { 761 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url']; 762 } 763 unset($parsedFrame['data']); 764 765 766 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only) 767 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only) 768 // http://id3.org/id3v2.3.0#sec4.4 769 // There may only be one 'IPL' frame in each tag 770 // <Header for 'User defined URL link frame', ID: 'IPL'> 771 // Text encoding $xx 772 // People list strings <textstrings> 773 774 $frame_offset = 0; 775 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 776 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 777 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 778 } 779 $parsedFrame['encodingid'] = $frame_textencoding; 780 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); 781 $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset); 782 783 // http://www.getid3.org/phpBB3/viewtopic.php?t=1369 784 // "this tag typically contains null terminated strings, which are associated in pairs" 785 // "there are users that use the tag incorrectly" 786 $IPLS_parts = array(); 787 if (strpos($parsedFrame['data_raw'], "\x00") !== false) { 788 $IPLS_parts_unsorted = array(); 789 if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) { 790 // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding 791 $thisILPS = ''; 792 for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) { 793 $twobytes = substr($parsedFrame['data_raw'], $i, 2); 794 if ($twobytes === "\x00\x00") { 795 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); 796 $thisILPS = ''; 797 } else { 798 $thisILPS .= $twobytes; 799 } 800 } 801 if (strlen($thisILPS) > 2) { // 2-byte BOM 802 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); 803 } 804 } else { 805 // ISO-8859-1 or UTF-8 or other single-byte-null character set 806 $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']); 807 } 808 if (count($IPLS_parts_unsorted) == 1) { 809 // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson" 810 foreach ($IPLS_parts_unsorted as $key => $value) { 811 $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value); 812 $position = ''; 813 foreach ($IPLS_parts_sorted as $person) { 814 $IPLS_parts[] = array('position'=>$position, 'person'=>$person); 815 } 816 } 817 } elseif ((count($IPLS_parts_unsorted) % 2) == 0) { 818 $position = ''; 819 $person = ''; 820 foreach ($IPLS_parts_unsorted as $key => $value) { 821 if (($key % 2) == 0) { 822 $position = $value; 823 } else { 824 $person = $value; 825 $IPLS_parts[] = array('position'=>$position, 'person'=>$person); 826 $position = ''; 827 $person = ''; 828 } 829 } 830 } else { 831 foreach ($IPLS_parts_unsorted as $key => $value) { 832 $IPLS_parts[] = array($value); 833 } 834 } 835 836 } else { 837 $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']); 838 } 839 $parsedFrame['data'] = $IPLS_parts; 840 841 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { 842 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; 843 } 844 845 846 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier 847 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier 848 // There may only be one 'MCDI' frame in each tag 849 // <Header for 'Music CD identifier', ID: 'MCDI'> 850 // CD TOC <binary data> 851 852 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { 853 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; 854 } 855 856 857 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes 858 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes 859 // There may only be one 'ETCO' frame in each tag 860 // <Header for 'Event timing codes', ID: 'ETCO'> 861 // Time stamp format $xx 862 // Where time stamp format is: 863 // $01 (32-bit value) MPEG frames from beginning of file 864 // $02 (32-bit value) milliseconds from beginning of file 865 // Followed by a list of key events in the following format: 866 // Type of event $xx 867 // Time stamp $xx (xx ...) 868 // The 'Time stamp' is set to zero if directly at the beginning of the sound 869 // or after the previous event. All events MUST be sorted in chronological order. 870 871 $frame_offset = 0; 872 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 873 874 while ($frame_offset < strlen($parsedFrame['data'])) { 875 $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1); 876 $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']); 877 $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); 878 $frame_offset += 4; 879 } 880 unset($parsedFrame['data']); 881 882 883 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table 884 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table 885 // There may only be one 'MLLT' frame in each tag 886 // <Header for 'Location lookup table', ID: 'MLLT'> 887 // MPEG frames between reference $xx xx 888 // Bytes between reference $xx xx xx 889 // Milliseconds between reference $xx xx xx 890 // Bits for bytes deviation $xx 891 // Bits for milliseconds dev. $xx 892 // Then for every reference the following data is included; 893 // Deviation in bytes %xxx.... 894 // Deviation in milliseconds %xxx.... 895 896 $frame_offset = 0; 897 $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2)); 898 $parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3)); 899 $parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3)); 900 $parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1)); 901 $parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1)); 902 $parsedFrame['data'] = substr($parsedFrame['data'], 10); 903 while ($frame_offset < strlen($parsedFrame['data'])) { 904 $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); 905 } 906 $reference_counter = 0; 907 while (strlen($deviationbitstream) > 0) { 908 $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation'])); 909 $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation'])); 910 $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']); 911 $reference_counter++; 912 } 913 unset($parsedFrame['data']); 914 915 916 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes 917 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes 918 // There may only be one 'SYTC' frame in each tag 919 // <Header for 'Synchronised tempo codes', ID: 'SYTC'> 920 // Time stamp format $xx 921 // Tempo data <binary data> 922 // Where time stamp format is: 923 // $01 (32-bit value) MPEG frames from beginning of file 924 // $02 (32-bit value) milliseconds from beginning of file 925 926 $frame_offset = 0; 927 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 928 $timestamp_counter = 0; 929 while ($frame_offset < strlen($parsedFrame['data'])) { 930 $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 931 if ($parsedFrame[$timestamp_counter]['tempo'] == 255) { 932 $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1)); 933 } 934 $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); 935 $frame_offset += 4; 936 $timestamp_counter++; 937 } 938 unset($parsedFrame['data']); 939 940 941 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription 942 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription 943 // There may be more than one 'Unsynchronised lyrics/text transcription' frame 944 // in each tag, but only one with the same language and content descriptor. 945 // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'> 946 // Text encoding $xx 947 // Language $xx xx xx 948 // Content descriptor <text string according to encoding> $00 (00) 949 // Lyrics/text <full text string according to encoding> 950 951 $frame_offset = 0; 952 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 953 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 954 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 955 } 956 $frame_language = substr($parsedFrame['data'], $frame_offset, 3); 957 $frame_offset += 3; 958 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); 959 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 960 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 961 } 962 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 963 if (ord($frame_description) === 0) { 964 $frame_description = ''; 965 } 966 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); 967 968 $parsedFrame['encodingid'] = $frame_textencoding; 969 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 970 971 $parsedFrame['data'] = $parsedFrame['data']; 972 $parsedFrame['language'] = $frame_language; 973 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); 974 $parsedFrame['description'] = $frame_description; 975 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { 976 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); 977 } 978 unset($parsedFrame['data']); 979 980 981 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text 982 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text 983 // There may be more than one 'SYLT' frame in each tag, 984 // but only one with the same language and content descriptor. 985 // <Header for 'Synchronised lyrics/text', ID: 'SYLT'> 986 // Text encoding $xx 987 // Language $xx xx xx 988 // Time stamp format $xx 989 // $01 (32-bit value) MPEG frames from beginning of file 990 // $02 (32-bit value) milliseconds from beginning of file 991 // Content type $xx 992 // Content descriptor <text string according to encoding> $00 (00) 993 // Terminated text to be synced (typically a syllable) 994 // Sync identifier (terminator to above string) $00 (00) 995 // Time stamp $xx (xx ...) 996 997 $frame_offset = 0; 998 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 999 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 1000 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 1001 } 1002 $frame_language = substr($parsedFrame['data'], $frame_offset, 3); 1003 $frame_offset += 3; 1004 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1005 $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1006 $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']); 1007 $parsedFrame['encodingid'] = $frame_textencoding; 1008 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 1009 1010 $parsedFrame['language'] = $frame_language; 1011 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); 1012 1013 $timestampindex = 0; 1014 $frame_remainingdata = substr($parsedFrame['data'], $frame_offset); 1015 while (strlen($frame_remainingdata)) { 1016 $frame_offset = 0; 1017 $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding)); 1018 if ($frame_terminatorpos === false) { 1019 $frame_remainingdata = ''; 1020 } else { 1021 if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 1022 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 1023 } 1024 $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); 1025 1026 $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); 1027 if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) { 1028 // timestamp probably omitted for first data item 1029 } else { 1030 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4)); 1031 $frame_remainingdata = substr($frame_remainingdata, 4); 1032 } 1033 $timestampindex++; 1034 } 1035 } 1036 unset($parsedFrame['data']); 1037 1038 1039 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments 1040 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments 1041 // There may be more than one comment frame in each tag, 1042 // but only one with the same language and content descriptor. 1043 // <Header for 'Comment', ID: 'COMM'> 1044 // Text encoding $xx 1045 // Language $xx xx xx 1046 // Short content descrip. <text string according to encoding> $00 (00) 1047 // The actual text <full text string according to encoding> 1048 1049 if (strlen($parsedFrame['data']) < 5) { 1050 1051 $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']; 1052 1053 } else { 1054 1055 $frame_offset = 0; 1056 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1057 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 1058 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 1059 } 1060 $frame_language = substr($parsedFrame['data'], $frame_offset, 3); 1061 $frame_offset += 3; 1062 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); 1063 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 1064 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 1065 } 1066 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1067 if (ord($frame_description) === 0) { 1068 $frame_description = ''; 1069 } 1070 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); 1071 1072 $parsedFrame['encodingid'] = $frame_textencoding; 1073 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 1074 1075 $parsedFrame['language'] = $frame_language; 1076 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); 1077 $parsedFrame['description'] = $frame_description; 1078 $parsedFrame['data'] = $frame_text; 1079 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { 1080 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); 1081 } 1082 1083 } 1084 1085 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only) 1086 // There may be more than one 'RVA2' frame in each tag, 1087 // but only one with the same identification string 1088 // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'> 1089 // Identification <text string> $00 1090 // The 'identification' string is used to identify the situation and/or 1091 // device where this adjustment should apply. The following is then 1092 // repeated for every channel: 1093 // Type of channel $xx 1094 // Volume adjustment $xx xx 1095 // Bits representing peak $xx 1096 // Peak volume $xx (xx ...) 1097 1098 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00"); 1099 $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos); 1100 if (ord($frame_idstring) === 0) { 1101 $frame_idstring = ''; 1102 } 1103 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); 1104 $parsedFrame['description'] = $frame_idstring; 1105 $RVA2channelcounter = 0; 1106 while (strlen($frame_remainingdata) >= 5) { 1107 $frame_offset = 0; 1108 $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1)); 1109 $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid; 1110 $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid); 1111 $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed 1112 $frame_offset += 2; 1113 $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1)); 1114 if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) { 1115 $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value'; 1116 break; 1117 } 1118 $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8); 1119 $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume)); 1120 $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume); 1121 $RVA2channelcounter++; 1122 } 1123 unset($parsedFrame['data']); 1124 1125 1126 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only) 1127 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only) 1128 // There may only be one 'RVA' frame in each tag 1129 // <Header for 'Relative volume adjustment', ID: 'RVA'> 1130 // ID3v2.2 => Increment/decrement %000000ba 1131 // ID3v2.3 => Increment/decrement %00fedcba 1132 // Bits used for volume descr. $xx 1133 // Relative volume change, right $xx xx (xx ...) // a 1134 // Relative volume change, left $xx xx (xx ...) // b 1135 // Peak volume right $xx xx (xx ...) 1136 // Peak volume left $xx xx (xx ...) 1137 // ID3v2.3 only, optional (not present in ID3v2.2): 1138 // Relative volume change, right back $xx xx (xx ...) // c 1139 // Relative volume change, left back $xx xx (xx ...) // d 1140 // Peak volume right back $xx xx (xx ...) 1141 // Peak volume left back $xx xx (xx ...) 1142 // ID3v2.3 only, optional (not present in ID3v2.2): 1143 // Relative volume change, center $xx xx (xx ...) // e 1144 // Peak volume center $xx xx (xx ...) 1145 // ID3v2.3 only, optional (not present in ID3v2.2): 1146 // Relative volume change, bass $xx xx (xx ...) // f 1147 // Peak volume bass $xx xx (xx ...) 1148 1149 $frame_offset = 0; 1150 $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); 1151 $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1); 1152 $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1); 1153 $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1154 $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8); 1155 $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1156 if ($parsedFrame['incdec']['right'] === false) { 1157 $parsedFrame['volumechange']['right'] *= -1; 1158 } 1159 $frame_offset += $frame_bytesvolume; 1160 $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1161 if ($parsedFrame['incdec']['left'] === false) { 1162 $parsedFrame['volumechange']['left'] *= -1; 1163 } 1164 $frame_offset += $frame_bytesvolume; 1165 $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1166 $frame_offset += $frame_bytesvolume; 1167 $parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1168 $frame_offset += $frame_bytesvolume; 1169 if ($id3v2_majorversion == 3) { 1170 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); 1171 if (strlen($parsedFrame['data']) > 0) { 1172 $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1); 1173 $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1); 1174 $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1175 if ($parsedFrame['incdec']['rightrear'] === false) { 1176 $parsedFrame['volumechange']['rightrear'] *= -1; 1177 } 1178 $frame_offset += $frame_bytesvolume; 1179 $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1180 if ($parsedFrame['incdec']['leftrear'] === false) { 1181 $parsedFrame['volumechange']['leftrear'] *= -1; 1182 } 1183 $frame_offset += $frame_bytesvolume; 1184 $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1185 $frame_offset += $frame_bytesvolume; 1186 $parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1187 $frame_offset += $frame_bytesvolume; 1188 } 1189 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); 1190 if (strlen($parsedFrame['data']) > 0) { 1191 $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1); 1192 $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1193 if ($parsedFrame['incdec']['center'] === false) { 1194 $parsedFrame['volumechange']['center'] *= -1; 1195 } 1196 $frame_offset += $frame_bytesvolume; 1197 $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1198 $frame_offset += $frame_bytesvolume; 1199 } 1200 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); 1201 if (strlen($parsedFrame['data']) > 0) { 1202 $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1); 1203 $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1204 if ($parsedFrame['incdec']['bass'] === false) { 1205 $parsedFrame['volumechange']['bass'] *= -1; 1206 } 1207 $frame_offset += $frame_bytesvolume; 1208 $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); 1209 $frame_offset += $frame_bytesvolume; 1210 } 1211 } 1212 unset($parsedFrame['data']); 1213 1214 1215 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only) 1216 // There may be more than one 'EQU2' frame in each tag, 1217 // but only one with the same identification string 1218 // <Header of 'Equalisation (2)', ID: 'EQU2'> 1219 // Interpolation method $xx 1220 // $00 Band 1221 // $01 Linear 1222 // Identification <text string> $00 1223 // The following is then repeated for every adjustment point 1224 // Frequency $xx xx 1225 // Volume adjustment $xx xx 1226 1227 $frame_offset = 0; 1228 $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1229 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1230 $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1231 if (ord($frame_idstring) === 0) { 1232 $frame_idstring = ''; 1233 } 1234 $parsedFrame['description'] = $frame_idstring; 1235 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); 1236 while (strlen($frame_remainingdata)) { 1237 $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2; 1238 $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true); 1239 $frame_remainingdata = substr($frame_remainingdata, 4); 1240 } 1241 $parsedFrame['interpolationmethod'] = $frame_interpolationmethod; 1242 unset($parsedFrame['data']); 1243 1244 1245 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only) 1246 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only) 1247 // There may only be one 'EQUA' frame in each tag 1248 // <Header for 'Relative volume adjustment', ID: 'EQU'> 1249 // Adjustment bits $xx 1250 // This is followed by 2 bytes + ('adjustment bits' rounded up to the 1251 // nearest byte) for every equalisation band in the following format, 1252 // giving a frequency range of 0 - 32767Hz: 1253 // Increment/decrement %x (MSB of the Frequency) 1254 // Frequency (lower 15 bits) 1255 // Adjustment $xx (xx ...) 1256 1257 $frame_offset = 0; 1258 $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1); 1259 $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8); 1260 1261 $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset); 1262 while (strlen($frame_remainingdata) > 0) { 1263 $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2)); 1264 $frame_incdec = (bool) substr($frame_frequencystr, 0, 1); 1265 $frame_frequency = bindec(substr($frame_frequencystr, 1, 15)); 1266 $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec; 1267 $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes)); 1268 if ($parsedFrame[$frame_frequency]['incdec'] === false) { 1269 $parsedFrame[$frame_frequency]['adjustment'] *= -1; 1270 } 1271 $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes); 1272 } 1273 unset($parsedFrame['data']); 1274 1275 1276 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb 1277 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb 1278 // There may only be one 'RVRB' frame in each tag. 1279 // <Header for 'Reverb', ID: 'RVRB'> 1280 // Reverb left (ms) $xx xx 1281 // Reverb right (ms) $xx xx 1282 // Reverb bounces, left $xx 1283 // Reverb bounces, right $xx 1284 // Reverb feedback, left to left $xx 1285 // Reverb feedback, left to right $xx 1286 // Reverb feedback, right to right $xx 1287 // Reverb feedback, right to left $xx 1288 // Premix left to right $xx 1289 // Premix right to left $xx 1290 1291 $frame_offset = 0; 1292 $parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); 1293 $frame_offset += 2; 1294 $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); 1295 $frame_offset += 2; 1296 $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1297 $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1298 $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1299 $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1300 $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1301 $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1302 $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1303 $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1304 unset($parsedFrame['data']); 1305 1306 1307 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture 1308 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture 1309 // There may be several pictures attached to one file, 1310 // each in their individual 'APIC' frame, but only one 1311 // with the same content descriptor 1312 // <Header for 'Attached picture', ID: 'APIC'> 1313 // Text encoding $xx 1314 // ID3v2.3+ => MIME type <text string> $00 1315 // ID3v2.2 => Image format $xx xx xx 1316 // Picture type $xx 1317 // Description <text string according to encoding> $00 (00) 1318 // Picture data <binary data> 1319 1320 $frame_offset = 0; 1321 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1322 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 1323 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 1324 } 1325 1326 if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) { 1327 $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3); 1328 if (strtolower($frame_imagetype) == 'ima') { 1329 // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted 1330 // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net) 1331 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1332 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1333 if (ord($frame_mimetype) === 0) { 1334 $frame_mimetype = ''; 1335 } 1336 $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype))); 1337 if ($frame_imagetype == 'JPEG') { 1338 $frame_imagetype = 'JPG'; 1339 } 1340 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1341 } else { 1342 $frame_offset += 3; 1343 } 1344 } 1345 if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) { 1346 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1347 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1348 if (ord($frame_mimetype) === 0) { 1349 $frame_mimetype = ''; 1350 } 1351 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1352 } 1353 1354 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1355 1356 if ($frame_offset >= $parsedFrame['datalength']) { 1357 $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset); 1358 } else { 1359 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); 1360 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 1361 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 1362 } 1363 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1364 if (ord($frame_description) === 0) { 1365 $frame_description = ''; 1366 } 1367 $parsedFrame['encodingid'] = $frame_textencoding; 1368 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 1369 1370 if ($id3v2_majorversion == 2) { 1371 $parsedFrame['imagetype'] = $frame_imagetype; 1372 } else { 1373 $parsedFrame['mime'] = $frame_mimetype; 1374 } 1375 $parsedFrame['picturetypeid'] = $frame_picturetype; 1376 $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); 1377 $parsedFrame['description'] = $frame_description; 1378 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); 1379 $parsedFrame['datalength'] = strlen($parsedFrame['data']); 1380 1381 $parsedFrame['image_mime'] = ''; 1382 $imageinfo = array(); 1383 $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo); 1384 if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { 1385 $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); 1386 if ($imagechunkcheck[0]) { 1387 $parsedFrame['image_width'] = $imagechunkcheck[0]; 1388 } 1389 if ($imagechunkcheck[1]) { 1390 $parsedFrame['image_height'] = $imagechunkcheck[1]; 1391 } 1392 } 1393 1394 do { 1395 if ($this->getid3->option_save_attachments === false) { 1396 // skip entirely 1397 unset($parsedFrame['data']); 1398 break; 1399 } 1400 if ($this->getid3->option_save_attachments === true) { 1401 // great 1402 /* 1403 } elseif (is_int($this->getid3->option_save_attachments)) { 1404 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) { 1405 // too big, skip 1406 $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)'; 1407 unset($parsedFrame['data']); 1408 break; 1409 } 1410 */ 1411 } elseif (is_string($this->getid3->option_save_attachments)) { 1412 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); 1413 if (!is_dir($dir) || !is_writable($dir)) { 1414 // cannot write, skip 1415 $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)'; 1416 unset($parsedFrame['data']); 1417 break; 1418 } 1419 } 1420 // if we get this far, must be OK 1421 if (is_string($this->getid3->option_save_attachments)) { 1422 $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset; 1423 if (!file_exists($destination_filename) || is_writable($destination_filename)) { 1424 file_put_contents($destination_filename, $parsedFrame['data']); 1425 } else { 1426 $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)'; 1427 } 1428 $parsedFrame['data_filename'] = $destination_filename; 1429 unset($parsedFrame['data']); 1430 } else { 1431 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { 1432 if (!isset($info['id3v2']['comments']['picture'])) { 1433 $info['id3v2']['comments']['picture'] = array(); 1434 } 1435 $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']); 1436 } 1437 } 1438 } while (false); 1439 } 1440 1441 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object 1442 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object 1443 // There may be more than one 'GEOB' frame in each tag, 1444 // but only one with the same content descriptor 1445 // <Header for 'General encapsulated object', ID: 'GEOB'> 1446 // Text encoding $xx 1447 // MIME type <text string> $00 1448 // Filename <text string according to encoding> $00 (00) 1449 // Content description <text string according to encoding> $00 (00) 1450 // Encapsulated object <binary data> 1451 1452 $frame_offset = 0; 1453 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1454 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 1455 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 1456 } 1457 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1458 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1459 if (ord($frame_mimetype) === 0) { 1460 $frame_mimetype = ''; 1461 } 1462 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1463 1464 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); 1465 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 1466 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 1467 } 1468 $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1469 if (ord($frame_filename) === 0) { 1470 $frame_filename = ''; 1471 } 1472 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); 1473 1474 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); 1475 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 1476 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 1477 } 1478 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1479 if (ord($frame_description) === 0) { 1480 $frame_description = ''; 1481 } 1482 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); 1483 1484 $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset); 1485 $parsedFrame['encodingid'] = $frame_textencoding; 1486 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 1487 1488 $parsedFrame['mime'] = $frame_mimetype; 1489 $parsedFrame['filename'] = $frame_filename; 1490 $parsedFrame['description'] = $frame_description; 1491 unset($parsedFrame['data']); 1492 1493 1494 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter 1495 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter 1496 // There may only be one 'PCNT' frame in each tag. 1497 // When the counter reaches all one's, one byte is inserted in 1498 // front of the counter thus making the counter eight bits bigger 1499 // <Header for 'Play counter', ID: 'PCNT'> 1500 // Counter $xx xx xx xx (xx ...) 1501 1502 $parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']); 1503 1504 1505 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter 1506 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter 1507 // There may be more than one 'POPM' frame in each tag, 1508 // but only one with the same email address 1509 // <Header for 'Popularimeter', ID: 'POPM'> 1510 // Email to user <text string> $00 1511 // Rating $xx 1512 // Counter $xx xx xx xx (xx ...) 1513 1514 $frame_offset = 0; 1515 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1516 $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1517 if (ord($frame_emailaddress) === 0) { 1518 $frame_emailaddress = ''; 1519 } 1520 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1521 $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1522 $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); 1523 $parsedFrame['email'] = $frame_emailaddress; 1524 $parsedFrame['rating'] = $frame_rating; 1525 unset($parsedFrame['data']); 1526 1527 1528 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size 1529 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size 1530 // There may only be one 'RBUF' frame in each tag 1531 // <Header for 'Recommended buffer size', ID: 'RBUF'> 1532 // Buffer size $xx xx xx 1533 // Embedded info flag %0000000x 1534 // Offset to next tag $xx xx xx xx 1535 1536 $frame_offset = 0; 1537 $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3)); 1538 $frame_offset += 3; 1539 1540 $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); 1541 $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1); 1542 $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); 1543 unset($parsedFrame['data']); 1544 1545 1546 } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only) 1547 // There may be more than one 'CRM' frame in a tag, 1548 // but only one with the same 'owner identifier' 1549 // <Header for 'Encrypted meta frame', ID: 'CRM'> 1550 // Owner identifier <textstring> $00 (00) 1551 // Content/explanation <textstring> $00 (00) 1552 // Encrypted datablock <binary data> 1553 1554 $frame_offset = 0; 1555 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1556 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1557 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1558 1559 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1560 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1561 if (ord($frame_description) === 0) { 1562 $frame_description = ''; 1563 } 1564 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1565 1566 $parsedFrame['ownerid'] = $frame_ownerid; 1567 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); 1568 $parsedFrame['description'] = $frame_description; 1569 unset($parsedFrame['data']); 1570 1571 1572 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption 1573 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption 1574 // There may be more than one 'AENC' frames in a tag, 1575 // but only one with the same 'Owner identifier' 1576 // <Header for 'Audio encryption', ID: 'AENC'> 1577 // Owner identifier <text string> $00 1578 // Preview start $xx xx 1579 // Preview length $xx xx 1580 // Encryption info <binary data> 1581 1582 $frame_offset = 0; 1583 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1584 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1585 if (ord($frame_ownerid) === 0) { 1586 $frame_ownerid == ''; 1587 } 1588 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1589 $parsedFrame['ownerid'] = $frame_ownerid; 1590 $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); 1591 $frame_offset += 2; 1592 $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); 1593 $frame_offset += 2; 1594 $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset); 1595 unset($parsedFrame['data']); 1596 1597 1598 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information 1599 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information 1600 // There may be more than one 'LINK' frame in a tag, 1601 // but only one with the same contents 1602 // <Header for 'Linked information', ID: 'LINK'> 1603 // ID3v2.3+ => Frame identifier $xx xx xx xx 1604 // ID3v2.2 => Frame identifier $xx xx xx 1605 // URL <text string> $00 1606 // ID and additional data <text string(s)> 1607 1608 $frame_offset = 0; 1609 if ($id3v2_majorversion == 2) { 1610 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3); 1611 $frame_offset += 3; 1612 } else { 1613 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4); 1614 $frame_offset += 4; 1615 } 1616 1617 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1618 $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1619 if (ord($frame_url) === 0) { 1620 $frame_url = ''; 1621 } 1622 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1623 $parsedFrame['url'] = $frame_url; 1624 1625 $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset); 1626 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { 1627 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']); 1628 } 1629 unset($parsedFrame['data']); 1630 1631 1632 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only) 1633 // There may only be one 'POSS' frame in each tag 1634 // <Head for 'Position synchronisation', ID: 'POSS'> 1635 // Time stamp format $xx 1636 // Position $xx (xx ...) 1637 1638 $frame_offset = 0; 1639 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1640 $parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); 1641 unset($parsedFrame['data']); 1642 1643 1644 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only) 1645 // There may be more than one 'Terms of use' frame in a tag, 1646 // but only one with the same 'Language' 1647 // <Header for 'Terms of use frame', ID: 'USER'> 1648 // Text encoding $xx 1649 // Language $xx xx xx 1650 // The actual text <text string according to encoding> 1651 1652 $frame_offset = 0; 1653 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1654 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 1655 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 1656 } 1657 $frame_language = substr($parsedFrame['data'], $frame_offset, 3); 1658 $frame_offset += 3; 1659 $parsedFrame['language'] = $frame_language; 1660 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); 1661 $parsedFrame['encodingid'] = $frame_textencoding; 1662 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 1663 1664 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); 1665 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { 1666 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); 1667 } 1668 unset($parsedFrame['data']); 1669 1670 1671 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only) 1672 // There may only be one 'OWNE' frame in a tag 1673 // <Header for 'Ownership frame', ID: 'OWNE'> 1674 // Text encoding $xx 1675 // Price paid <text string> $00 1676 // Date of purch. <text string> 1677 // Seller <text string according to encoding> 1678 1679 $frame_offset = 0; 1680 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1681 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 1682 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 1683 } 1684 $parsedFrame['encodingid'] = $frame_textencoding; 1685 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 1686 1687 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1688 $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1689 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1690 1691 $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3); 1692 $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']); 1693 $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3); 1694 1695 $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8); 1696 if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) { 1697 $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4)); 1698 } 1699 $frame_offset += 8; 1700 1701 $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset); 1702 unset($parsedFrame['data']); 1703 1704 1705 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only) 1706 // There may be more than one 'commercial frame' in a tag, 1707 // but no two may be identical 1708 // <Header for 'Commercial frame', ID: 'COMR'> 1709 // Text encoding $xx 1710 // Price string <text string> $00 1711 // Valid until <text string> 1712 // Contact URL <text string> $00 1713 // Received as $xx 1714 // Name of seller <text string according to encoding> $00 (00) 1715 // Description <text string according to encoding> $00 (00) 1716 // Picture MIME type <string> $00 1717 // Seller logo <binary data> 1718 1719 $frame_offset = 0; 1720 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1721 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { 1722 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; 1723 } 1724 1725 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1726 $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1727 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1728 $frame_rawpricearray = explode('/', $frame_pricestring); 1729 foreach ($frame_rawpricearray as $key => $val) { 1730 $frame_currencyid = substr($val, 0, 3); 1731 $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid); 1732 $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3); 1733 } 1734 1735 $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8); 1736 $frame_offset += 8; 1737 1738 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1739 $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1740 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1741 1742 $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1743 1744 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); 1745 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 1746 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 1747 } 1748 $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1749 if (ord($frame_sellername) === 0) { 1750 $frame_sellername = ''; 1751 } 1752 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); 1753 1754 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); 1755 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { 1756 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 1757 } 1758 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1759 if (ord($frame_description) === 0) { 1760 $frame_description = ''; 1761 } 1762 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); 1763 1764 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1765 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1766 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1767 1768 $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset); 1769 1770 $parsedFrame['encodingid'] = $frame_textencoding; 1771 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); 1772 1773 $parsedFrame['pricevaliduntil'] = $frame_datestring; 1774 $parsedFrame['contacturl'] = $frame_contacturl; 1775 $parsedFrame['receivedasid'] = $frame_receivedasid; 1776 $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid); 1777 $parsedFrame['sellername'] = $frame_sellername; 1778 $parsedFrame['description'] = $frame_description; 1779 $parsedFrame['mime'] = $frame_mimetype; 1780 $parsedFrame['logo'] = $frame_sellerlogo; 1781 unset($parsedFrame['data']); 1782 1783 1784 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only) 1785 // There may be several 'ENCR' frames in a tag, 1786 // but only one containing the same symbol 1787 // and only one containing the same owner identifier 1788 // <Header for 'Encryption method registration', ID: 'ENCR'> 1789 // Owner identifier <text string> $00 1790 // Method symbol $xx 1791 // Encryption data <binary data> 1792 1793 $frame_offset = 0; 1794 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1795 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1796 if (ord($frame_ownerid) === 0) { 1797 $frame_ownerid = ''; 1798 } 1799 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1800 1801 $parsedFrame['ownerid'] = $frame_ownerid; 1802 $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1803 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); 1804 1805 1806 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only) 1807 1808 // There may be several 'GRID' frames in a tag, 1809 // but only one containing the same symbol 1810 // and only one containing the same owner identifier 1811 // <Header for 'Group ID registration', ID: 'GRID'> 1812 // Owner identifier <text string> $00 1813 // Group symbol $xx 1814 // Group dependent data <binary data> 1815 1816 $frame_offset = 0; 1817 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1818 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1819 if (ord($frame_ownerid) === 0) { 1820 $frame_ownerid = ''; 1821 } 1822 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1823 1824 $parsedFrame['ownerid'] = $frame_ownerid; 1825 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1826 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); 1827 1828 1829 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only) 1830 // The tag may contain more than one 'PRIV' frame 1831 // but only with different contents 1832 // <Header for 'Private frame', ID: 'PRIV'> 1833 // Owner identifier <text string> $00 1834 // The private data <binary data> 1835 1836 $frame_offset = 0; 1837 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); 1838 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); 1839 if (ord($frame_ownerid) === 0) { 1840 $frame_ownerid = ''; 1841 } 1842 $frame_offset = $frame_terminatorpos + strlen("\x00"); 1843 1844 $parsedFrame['ownerid'] = $frame_ownerid; 1845 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); 1846 1847 1848 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only) 1849 // There may be more than one 'signature frame' in a tag, 1850 // but no two may be identical 1851 // <Header for 'Signature frame', ID: 'SIGN'> 1852 // Group symbol $xx 1853 // Signature <binary data> 1854 1855 $frame_offset = 0; 1856 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1857 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); 1858 1859 1860 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only) 1861 // There may only be one 'seek frame' in a tag 1862 // <Header for 'Seek frame', ID: 'SEEK'> 1863 // Minimum offset to next tag $xx xx xx xx 1864 1865 $frame_offset = 0; 1866 $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); 1867 1868 1869 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only) 1870 // There may only be one 'audio seek point index' frame in a tag 1871 // <Header for 'Seek Point Index', ID: 'ASPI'> 1872 // Indexed data start (S) $xx xx xx xx 1873 // Indexed data length (L) $xx xx xx xx 1874 // Number of index points (N) $xx xx 1875 // Bits per index point (b) $xx 1876 // Then for every index point the following data is included: 1877 // Fraction at index (Fi) $xx (xx) 1878 1879 $frame_offset = 0; 1880 $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); 1881 $frame_offset += 4; 1882 $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); 1883 $frame_offset += 4; 1884 $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); 1885 $frame_offset += 2; 1886 $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); 1887 $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8); 1888 for ($i = 0; $i < $frame_indexpoints; $i++) { 1889 $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint)); 1890 $frame_offset += $frame_bytesperpoint; 1891 } 1892 unset($parsedFrame['data']); 1893 1894 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment 1895 // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html 1896 // There may only be one 'RGAD' frame in a tag 1897 // <Header for 'Replay Gain Adjustment', ID: 'RGAD'> 1898 // Peak Amplitude $xx $xx $xx $xx 1899 // Radio Replay Gain Adjustment %aaabbbcd %dddddddd 1900 // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd 1901 // a - name code 1902 // b - originator code 1903 // c - sign bit 1904 // d - replay gain adjustment 1905 1906 $frame_offset = 0; 1907 $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4)); 1908 $frame_offset += 4; 1909 $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); 1910 $frame_offset += 2; 1911 $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); 1912 $frame_offset += 2; 1913 $parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3)); 1914 $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3)); 1915 $parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1)); 1916 $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9)); 1917 $parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3)); 1918 $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3)); 1919 $parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1)); 1920 $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9)); 1921 $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']); 1922 $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']); 1923 $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']); 1924 $parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']); 1925 $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']); 1926 $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']); 1927 1928 $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude']; 1929 $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; 1930 $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; 1931 $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; 1932 $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; 1933 1934 unset($parsedFrame['data']); 1935 1936 } 1937 1938 return true; 1939 } 1940 1941 1942 public function DeUnsynchronise($data) { 1943 return str_replace("\xFF\x00", "\xFF", $data); 1944 } 1945 1946 public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) { 1947 static $LookupExtendedHeaderRestrictionsTagSizeLimits = array( 1948 0x00 => 'No more than 128 frames and 1 MB total tag size', 1949 0x01 => 'No more than 64 frames and 128 KB total tag size', 1950 0x02 => 'No more than 32 frames and 40 KB total tag size', 1951 0x03 => 'No more than 32 frames and 4 KB total tag size', 1952 ); 1953 return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : ''); 1954 } 1955 1956 public function LookupExtendedHeaderRestrictionsTextEncodings($index) { 1957 static $LookupExtendedHeaderRestrictionsTextEncodings = array( 1958 0x00 => 'No restrictions', 1959 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8', 1960 ); 1961 return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : ''); 1962 } 1963 1964 public function LookupExtendedHeaderRestrictionsTextFieldSize($index) { 1965 static $LookupExtendedHeaderRestrictionsTextFieldSize = array( 1966 0x00 => 'No restrictions', 1967 0x01 => 'No string is longer than 1024 characters', 1968 0x02 => 'No string is longer than 128 characters', 1969 0x03 => 'No string is longer than 30 characters', 1970 ); 1971 return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : ''); 1972 } 1973 1974 public function LookupExtendedHeaderRestrictionsImageEncoding($index) { 1975 static $LookupExtendedHeaderRestrictionsImageEncoding = array( 1976 0x00 => 'No restrictions', 1977 0x01 => 'Images are encoded only with PNG or JPEG', 1978 ); 1979 return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : ''); 1980 } 1981 1982 public function LookupExtendedHeaderRestrictionsImageSizeSize($index) { 1983 static $LookupExtendedHeaderRestrictionsImageSizeSize = array( 1984 0x00 => 'No restrictions', 1985 0x01 => 'All images are 256x256 pixels or smaller', 1986 0x02 => 'All images are 64x64 pixels or smaller', 1987 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise', 1988 ); 1989 return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : ''); 1990 } 1991 1992 public function LookupCurrencyUnits($currencyid) { 1993 1994 $begin = __LINE__; 1995 1996 /** This is not a comment! 1997 1998 1999 AED Dirhams 2000 AFA Afghanis 2001 ALL Leke 2002 AMD Drams 2003 ANG Guilders 2004 AOA Kwanza 2005 ARS Pesos 2006 ATS Schillings 2007 AUD Dollars 2008 AWG Guilders 2009 AZM Manats 2010 BAM Convertible Marka 2011 BBD Dollars 2012 BDT Taka 2013 BEF Francs 2014 BGL Leva 2015 BHD Dinars 2016 BIF Francs 2017 BMD Dollars 2018 BND Dollars 2019 BOB Bolivianos 2020 BRL Brazil Real 2021 BSD Dollars 2022 BTN Ngultrum 2023 BWP Pulas 2024 BYR Rubles 2025 BZD Dollars 2026 CAD Dollars 2027 CDF Congolese Francs 2028 CHF Francs 2029 CLP Pesos 2030 CNY Yuan Renminbi 2031 COP Pesos 2032 CRC Colones 2033 CUP Pesos 2034 CVE Escudos 2035 CYP Pounds 2036 CZK Koruny 2037 DEM Deutsche Marks 2038 DJF Francs 2039 DKK Kroner 2040 DOP Pesos 2041 DZD Algeria Dinars 2042 EEK Krooni 2043 EGP Pounds 2044 ERN Nakfa 2045 ESP Pesetas 2046 ETB Birr 2047 EUR Euro 2048 FIM Markkaa 2049 FJD Dollars 2050 FKP Pounds 2051 FRF Francs 2052 GBP Pounds 2053 GEL Lari 2054 GGP Pounds 2055 GHC Cedis 2056 GIP Pounds 2057 GMD Dalasi 2058 GNF Francs 2059 GRD Drachmae 2060 GTQ Quetzales 2061 GYD Dollars 2062 HKD Dollars 2063 HNL Lempiras 2064 HRK Kuna 2065 HTG Gourdes 2066 HUF Forints 2067 IDR Rupiahs 2068 IEP Pounds 2069 ILS New Shekels 2070 IMP Pounds 2071 INR Rupees 2072 IQD Dinars 2073 IRR Rials 2074 ISK Kronur 2075 ITL Lire 2076 JEP Pounds 2077 JMD Dollars 2078 JOD Dinars 2079 JPY Yen 2080 KES Shillings 2081 KGS Soms 2082 KHR Riels 2083 KMF Francs 2084 KPW Won 2085 KWD Dinars 2086 KYD Dollars 2087 KZT Tenge 2088 LAK Kips 2089 LBP Pounds 2090 LKR Rupees 2091 LRD Dollars 2092 LSL Maloti 2093 LTL Litai 2094 LUF Francs 2095 LVL Lati 2096 LYD Dinars 2097 MAD Dirhams 2098 MDL Lei 2099 MGF Malagasy Francs 2100 MKD Denars 2101 MMK Kyats 2102 MNT Tugriks 2103 MOP Patacas 2104 MRO Ouguiyas 2105 MTL Liri 2106 MUR Rupees 2107 MVR Rufiyaa 2108 MWK Kwachas 2109 MXN Pesos 2110 MYR Ringgits 2111 MZM Meticais 2112 NAD Dollars 2113 NGN Nairas 2114 NIO Gold Cordobas 2115 NLG Guilders 2116 NOK Krone 2117 NPR Nepal Rupees 2118 NZD Dollars 2119 OMR Rials 2120 PAB Balboa 2121 PEN Nuevos Soles 2122 PGK Kina 2123 PHP Pesos 2124 PKR Rupees 2125 PLN Zlotych 2126 PTE Escudos 2127 PYG Guarani 2128 QAR Rials 2129 ROL Lei 2130 RUR Rubles 2131 RWF Rwanda Francs 2132 SAR Riyals 2133 SBD Dollars 2134 SCR Rupees 2135 SDD Dinars 2136 SEK Kronor 2137 SGD Dollars 2138 SHP Pounds 2139 SIT Tolars 2140 SKK Koruny 2141 SLL Leones 2142 SOS Shillings 2143 SPL Luigini 2144 SRG Guilders 2145 STD Dobras 2146 SVC Colones 2147 SYP Pounds 2148 SZL Emalangeni 2149 THB Baht 2150 TJR Rubles 2151 TMM Manats 2152 TND Dinars 2153 TOP Pa'anga 2154 TRL Liras 2155 TTD Dollars 2156 TVD Tuvalu Dollars 2157 TWD New Dollars 2158 TZS Shillings 2159 UAH Hryvnia 2160 UGX Shillings 2161 USD Dollars 2162 UYU Pesos 2163 UZS Sums 2164 VAL Lire 2165 VEB Bolivares 2166 VND Dong 2167 VUV Vatu 2168 WST Tala 2169 XAF Francs 2170 XAG Ounces 2171 XAU Ounces 2172 XCD Dollars 2173 XDR Special Drawing Rights 2174 XPD Ounces 2175 XPF Francs 2176 XPT Ounces 2177 YER Rials 2178 YUM New Dinars 2179 ZAR Rand 2180 ZMK Kwacha 2181 ZWD Zimbabwe Dollars 2182 2183 */ 2184 2185 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units'); 2186 } 2187 2188 2189 public function LookupCurrencyCountry($currencyid) { 2190 2191 $begin = __LINE__; 2192 2193 /** This is not a comment! 2194 2195 AED United Arab Emirates 2196 AFA Afghanistan 2197 ALL Albania 2198 AMD Armenia 2199 ANG Netherlands Antilles 2200 AOA Angola 2201 ARS Argentina 2202 ATS Austria 2203 AUD Australia 2204 AWG Aruba 2205 AZM Azerbaijan 2206 BAM Bosnia and Herzegovina 2207 BBD Barbados 2208 BDT Bangladesh 2209 BEF Belgium 2210 BGL Bulgaria 2211 BHD Bahrain 2212 BIF Burundi 2213 BMD Bermuda 2214 BND Brunei Darussalam 2215 BOB Bolivia 2216 BRL Brazil 2217 BSD Bahamas 2218 BTN Bhutan 2219 BWP Botswana 2220 BYR Belarus 2221 BZD Belize 2222 CAD Canada 2223 CDF Congo/Kinshasa 2224 CHF Switzerland 2225 CLP Chile 2226 CNY China 2227 COP Colombia 2228 CRC Costa Rica 2229 CUP Cuba 2230 CVE Cape Verde 2231 CYP Cyprus 2232 CZK Czech Republic 2233 DEM Germany 2234 DJF Djibouti 2235 DKK Denmark 2236 DOP Dominican Republic 2237 DZD Algeria 2238 EEK Estonia 2239 EGP Egypt 2240 ERN Eritrea 2241 ESP Spain 2242 ETB Ethiopia 2243 EUR Euro Member Countries 2244 FIM Finland 2245 FJD Fiji 2246 FKP Falkland Islands (Malvinas) 2247 FRF France 2248 GBP United Kingdom 2249 GEL Georgia 2250 GGP Guernsey 2251 GHC Ghana 2252 GIP Gibraltar 2253 GMD Gambia 2254 GNF Guinea 2255 GRD Greece 2256 GTQ Guatemala 2257 GYD Guyana 2258 HKD Hong Kong 2259 HNL Honduras 2260 HRK Croatia 2261 HTG Haiti 2262 HUF Hungary 2263 IDR Indonesia 2264 IEP Ireland (Eire) 2265 ILS Israel 2266 IMP Isle of Man 2267 INR India 2268 IQD Iraq 2269 IRR Iran 2270 ISK Iceland 2271 ITL Italy 2272 JEP Jersey 2273 JMD Jamaica 2274 JOD Jordan 2275 JPY Japan 2276 KES Kenya 2277 KGS Kyrgyzstan 2278 KHR Cambodia 2279 KMF Comoros 2280 KPW Korea 2281 KWD Kuwait 2282 KYD Cayman Islands 2283 KZT Kazakstan 2284 LAK Laos 2285 LBP Lebanon 2286 LKR Sri Lanka 2287 LRD Liberia 2288 LSL Lesotho 2289 LTL Lithuania 2290 LUF Luxembourg 2291 LVL Latvia 2292 LYD Libya 2293 MAD Morocco 2294 MDL Moldova 2295 MGF Madagascar 2296 MKD Macedonia 2297 MMK Myanmar (Burma) 2298 MNT Mongolia 2299 MOP Macau 2300 MRO Mauritania 2301 MTL Malta 2302 MUR Mauritius 2303 MVR Maldives (Maldive Islands) 2304 MWK Malawi 2305 MXN Mexico 2306 MYR Malaysia 2307 MZM Mozambique 2308 NAD Namibia 2309 NGN Nigeria 2310 NIO Nicaragua 2311 NLG Netherlands (Holland) 2312 NOK Norway 2313 NPR Nepal 2314 NZD New Zealand 2315 OMR Oman 2316 PAB Panama 2317 PEN Peru 2318 PGK Papua New Guinea 2319 PHP Philippines 2320 PKR Pakistan 2321 PLN Poland 2322 PTE Portugal 2323 PYG Paraguay 2324 QAR Qatar 2325 ROL Romania 2326 RUR Russia 2327 RWF Rwanda 2328 SAR Saudi Arabia 2329 SBD Solomon Islands 2330 SCR Seychelles 2331 SDD Sudan 2332 SEK Sweden 2333 SGD Singapore 2334 SHP Saint Helena 2335 SIT Slovenia 2336 SKK Slovakia 2337 SLL Sierra Leone 2338 SOS Somalia 2339 SPL Seborga 2340 SRG Suriname 2341 STD São Tome and Principe 2342 SVC El Salvador 2343 SYP Syria 2344 SZL Swaziland 2345 THB Thailand 2346 TJR Tajikistan 2347 TMM Turkmenistan 2348 TND Tunisia 2349 TOP Tonga 2350 TRL Turkey 2351 TTD Trinidad and Tobago 2352 TVD Tuvalu 2353 TWD Taiwan 2354 TZS Tanzania 2355 UAH Ukraine 2356 UGX Uganda 2357 USD United States of America 2358 UYU Uruguay 2359 UZS Uzbekistan 2360 VAL Vatican City 2361 VEB Venezuela 2362 VND Viet Nam 2363 VUV Vanuatu 2364 WST Samoa 2365 XAF Communauté Financière Africaine 2366 XAG Silver 2367 XAU Gold 2368 XCD East Caribbean 2369 XDR International Monetary Fund 2370 XPD Palladium 2371 XPF Comptoirs Français du Pacifique 2372 XPT Platinum 2373 YER Yemen 2374 YUM Yugoslavia 2375 ZAR South Africa 2376 ZMK Zambia 2377 ZWD Zimbabwe 2378 2379 */ 2380 2381 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country'); 2382 } 2383 2384 2385 2386 public static function LanguageLookup($languagecode, $casesensitive=false) { 2387 2388 if (!$casesensitive) { 2389 $languagecode = strtolower($languagecode); 2390 } 2391 2392 // http://www.id3.org/id3v2.4.0-structure.txt 2393 // [4. ID3v2 frame overview] 2394 // The three byte language field, present in several frames, is used to 2395 // describe the language of the frame's content, according to ISO-639-2 2396 // [ISO-639-2]. The language should be represented in lower case. If the 2397 // language is not known the string "XXX" should be used. 2398 2399 2400 // ISO 639-2 - http://www.id3.org/iso639-2.html 2401 2402 $begin = __LINE__; 2403 2404 /** This is not a comment! 2405 2406 XXX unknown 2407 xxx unknown 2408 aar Afar 2409 abk Abkhazian 2410 ace Achinese 2411 ach Acoli 2412 ada Adangme 2413 afa Afro-Asiatic (Other) 2414 afh Afrihili 2415 afr Afrikaans 2416 aka Akan 2417 akk Akkadian 2418 alb Albanian 2419 ale Aleut 2420 alg Algonquian Languages 2421 amh Amharic 2422 ang English, Old (ca. 450-1100) 2423 apa Apache Languages 2424 ara Arabic 2425 arc Aramaic 2426 arm Armenian 2427 arn Araucanian 2428 arp Arapaho 2429 art Artificial (Other) 2430 arw Arawak 2431 asm Assamese 2432 ath Athapascan Languages 2433 ava Avaric 2434 ave Avestan 2435 awa Awadhi 2436 aym Aymara 2437 aze Azerbaijani 2438 bad Banda 2439 bai Bamileke Languages 2440 bak Bashkir 2441 bal Baluchi 2442 bam Bambara 2443 ban Balinese 2444 baq Basque 2445 bas Basa 2446 bat Baltic (Other) 2447 bej Beja 2448 bel Byelorussian 2449 bem Bemba 2450 ben Bengali 2451 ber Berber (Other) 2452 bho Bhojpuri 2453 bih Bihari 2454 bik Bikol 2455 bin Bini 2456 bis Bislama 2457 bla Siksika 2458 bnt Bantu (Other) 2459 bod Tibetan 2460 bra Braj 2461 bre Breton 2462 bua Buriat 2463 bug Buginese 2464 bul Bulgarian 2465 bur Burmese 2466 cad Caddo 2467 cai Central American Indian (Other) 2468 car Carib 2469 cat Catalan 2470 cau Caucasian (Other) 2471 ceb Cebuano 2472 cel Celtic (Other) 2473 ces Czech 2474 cha Chamorro 2475 chb Chibcha 2476 che Chechen 2477 chg Chagatai 2478 chi Chinese 2479 chm Mari 2480 chn Chinook jargon 2481 cho Choctaw 2482 chr Cherokee 2483 chu Church Slavic 2484 chv Chuvash 2485 chy Cheyenne 2486 cop Coptic 2487 cor Cornish 2488 cos Corsican 2489 cpe Creoles and Pidgins, English-based (Other) 2490 cpf Creoles and Pidgins, French-based (Other) 2491 cpp Creoles and Pidgins, Portuguese-based (Other) 2492 cre Cree 2493 crp Creoles and Pidgins (Other) 2494 cus Cushitic (Other) 2495 cym Welsh 2496 cze Czech 2497 dak Dakota 2498 dan Danish 2499 del Delaware 2500 deu German 2501 din Dinka 2502 div Divehi 2503 doi Dogri 2504 dra Dravidian (Other) 2505 dua Duala 2506 dum Dutch, Middle (ca. 1050-1350) 2507 dut Dutch 2508 dyu Dyula 2509 dzo Dzongkha 2510 efi Efik 2511 egy Egyptian (Ancient) 2512 eka Ekajuk 2513 ell Greek, Modern (1453-) 2514 elx Elamite 2515 eng English 2516 enm English, Middle (ca. 1100-1500) 2517 epo Esperanto 2518 esk Eskimo (Other) 2519 esl Spanish 2520 est Estonian 2521 eus Basque 2522 ewe Ewe 2523 ewo Ewondo 2524 fan Fang 2525 fao Faroese 2526 fas Persian 2527 fat Fanti 2528 fij Fijian 2529 fin Finnish 2530 fiu Finno-Ugrian (Other) 2531 fon Fon 2532 fra French 2533 fre French 2534 frm French, Middle (ca. 1400-1600) 2535 fro French, Old (842- ca. 1400) 2536 fry Frisian 2537 ful Fulah 2538 gaa Ga 2539 gae Gaelic (Scots) 2540 gai Irish 2541 gay Gayo 2542 gdh Gaelic (Scots) 2543 gem Germanic (Other) 2544 geo Georgian 2545 ger German 2546 gez Geez 2547 gil Gilbertese 2548 glg Gallegan 2549 gmh German, Middle High (ca. 1050-1500) 2550 goh German, Old High (ca. 750-1050) 2551 gon Gondi 2552 got Gothic 2553 grb Grebo 2554 grc Greek, Ancient (to 1453) 2555 gre Greek, Modern (1453-) 2556 grn Guarani 2557 guj Gujarati 2558 hai Haida 2559 hau Hausa 2560 haw Hawaiian 2561 heb Hebrew 2562 her Herero 2563 hil Hiligaynon 2564 him Himachali 2565 hin Hindi 2566 hmo Hiri Motu 2567 hun Hungarian 2568 hup Hupa 2569 hye Armenian 2570 iba Iban 2571 ibo Igbo 2572 ice Icelandic 2573 ijo Ijo 2574 iku Inuktitut 2575 ilo Iloko 2576 ina Interlingua (International Auxiliary language Association) 2577 inc Indic (Other) 2578 ind Indonesian 2579 ine Indo-European (Other) 2580 ine Interlingue 2581 ipk Inupiak 2582 ira Iranian (Other) 2583 iri Irish 2584 iro Iroquoian uages 2585 isl Icelandic 2586 ita Italian 2587 jav Javanese 2588 jaw Javanese 2589 jpn Japanese 2590 jpr Judeo-Persian 2591 jrb Judeo-Arabic 2592 kaa Kara-Kalpak 2593 kab Kabyle 2594 kac Kachin 2595 kal Greenlandic 2596 kam Kamba 2597 kan Kannada 2598 kar Karen 2599 kas Kashmiri 2600 kat Georgian 2601 kau Kanuri 2602 kaw Kawi 2603 kaz Kazakh 2604 kha Khasi 2605 khi Khoisan (Other) 2606 khm Khmer 2607 kho Khotanese 2608 kik Kikuyu 2609 kin Kinyarwanda 2610 kir Kirghiz 2611 kok Konkani 2612 kom Komi 2613 kon Kongo 2614 kor Korean 2615 kpe Kpelle 2616 kro Kru 2617 kru Kurukh 2618 kua Kuanyama 2619 kum Kumyk 2620 kur Kurdish 2621 kus Kusaie 2622 kut Kutenai 2623 lad Ladino 2624 lah Lahnda 2625 lam Lamba 2626 lao Lao 2627 lat Latin 2628 lav Latvian 2629 lez Lezghian 2630 lin Lingala 2631 lit Lithuanian 2632 lol Mongo 2633 loz Lozi 2634 ltz Letzeburgesch 2635 lub Luba-Katanga 2636 lug Ganda 2637 lui Luiseno 2638 lun Lunda 2639 luo Luo (Kenya and Tanzania) 2640 mac Macedonian 2641 mad Madurese 2642 mag Magahi 2643 mah Marshall 2644 mai Maithili 2645 mak Macedonian 2646 mak Makasar 2647 mal Malayalam 2648 man Mandingo 2649 mao Maori 2650 map Austronesian (Other) 2651 mar Marathi 2652 mas Masai 2653 max Manx 2654 may Malay 2655 men Mende 2656 mga Irish, Middle (900 - 1200) 2657 mic Micmac 2658 min Minangkabau 2659 mis Miscellaneous (Other) 2660 mkh Mon-Kmer (Other) 2661 mlg Malagasy 2662 mlt Maltese 2663 mni Manipuri 2664 mno Manobo Languages 2665 moh Mohawk 2666 mol Moldavian 2667 mon Mongolian 2668 mos Mossi 2669 mri Maori 2670 msa Malay 2671 mul Multiple Languages 2672 mun Munda Languages 2673 mus Creek 2674 mwr Marwari 2675 mya Burmese 2676 myn Mayan Languages 2677 nah Aztec 2678 nai North American Indian (Other) 2679 nau Nauru 2680 nav Navajo 2681 nbl Ndebele, South 2682 nde Ndebele, North 2683 ndo Ndongo 2684 nep Nepali 2685 new Newari 2686 nic Niger-Kordofanian (Other) 2687 niu Niuean 2688 nla Dutch 2689 nno Norwegian (Nynorsk) 2690 non Norse, Old 2691 nor Norwegian 2692 nso Sotho, Northern 2693 nub Nubian Languages 2694 nya Nyanja 2695 nym Nyamwezi 2696 nyn Nyankole 2697 nyo Nyoro 2698 nzi Nzima 2699 oci Langue d'Oc (post 1500) 2700 oji Ojibwa 2701 ori Oriya 2702 orm Oromo 2703 osa Osage 2704 oss Ossetic 2705 ota Turkish, Ottoman (1500 - 1928) 2706 oto Otomian Languages 2707 paa Papuan-Australian (Other) 2708 pag Pangasinan 2709 pal Pahlavi 2710 pam Pampanga 2711 pan Panjabi 2712 pap Papiamento 2713 pau Palauan 2714 peo Persian, Old (ca 600 - 400 B.C.) 2715 per Persian 2716 phn Phoenician 2717 pli Pali 2718 pol Polish 2719 pon Ponape 2720 por Portuguese 2721 pra Prakrit uages 2722 pro Provencal, Old (to 1500) 2723 pus Pushto 2724 que Quechua 2725 raj Rajasthani 2726 rar Rarotongan 2727 roa Romance (Other) 2728 roh Rhaeto-Romance 2729 rom Romany 2730 ron Romanian 2731 rum Romanian 2732 run Rundi 2733 rus Russian 2734 sad Sandawe 2735 sag Sango 2736 sah Yakut 2737 sai South American Indian (Other) 2738 sal Salishan Languages 2739 sam Samaritan Aramaic 2740 san Sanskrit 2741 sco Scots 2742 scr Serbo-Croatian 2743 sel Selkup 2744 sem Semitic (Other) 2745 sga Irish, Old (to 900) 2746 shn Shan 2747 sid Sidamo 2748 sin Singhalese 2749 sio Siouan Languages 2750 sit Sino-Tibetan (Other) 2751 sla Slavic (Other) 2752 slk Slovak 2753 slo Slovak 2754 slv Slovenian 2755 smi Sami Languages 2756 smo Samoan 2757 sna Shona 2758 snd Sindhi 2759 sog Sogdian 2760 som Somali 2761 son Songhai 2762 sot Sotho, Southern 2763 spa Spanish 2764 sqi Albanian 2765 srd Sardinian 2766 srr Serer 2767 ssa Nilo-Saharan (Other) 2768 ssw Siswant 2769 ssw Swazi 2770 suk Sukuma 2771 sun Sudanese 2772 sus Susu 2773 sux Sumerian 2774 sve Swedish 2775 swa Swahili 2776 swe Swedish 2777 syr Syriac 2778 tah Tahitian 2779 tam Tamil 2780 tat Tatar 2781 tel Telugu 2782 tem Timne 2783 ter Tereno 2784 tgk Tajik 2785 tgl Tagalog 2786 tha Thai 2787 tib Tibetan 2788 tig Tigre 2789 tir Tigrinya 2790 tiv Tivi 2791 tli Tlingit 2792 tmh Tamashek 2793 tog Tonga (Nyasa) 2794 ton Tonga (Tonga Islands) 2795 tru Truk 2796 tsi Tsimshian 2797 tsn Tswana 2798 tso Tsonga 2799 tuk Turkmen 2800 tum Tumbuka 2801 tur Turkish 2802 tut Altaic (Other) 2803 twi Twi 2804 tyv Tuvinian 2805 uga Ugaritic 2806 uig Uighur 2807 ukr Ukrainian 2808 umb Umbundu 2809 und Undetermined 2810 urd Urdu 2811 uzb Uzbek 2812 vai Vai 2813 ven Venda 2814 vie Vietnamese 2815 vol Volapük 2816 vot Votic 2817 wak Wakashan Languages 2818 wal Walamo 2819 war Waray 2820 was Washo 2821 wel Welsh 2822 wen Sorbian Languages 2823 wol Wolof 2824 xho Xhosa 2825 yao Yao 2826 yap Yap 2827 yid Yiddish 2828 yor Yoruba 2829 zap Zapotec 2830 zen Zenaga 2831 zha Zhuang 2832 zho Chinese 2833 zul Zulu 2834 zun Zuni 2835 2836 */ 2837 2838 return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode'); 2839 } 2840 2841 2842 public static function ETCOEventLookup($index) { 2843 if (($index >= 0x17) && ($index <= 0xDF)) { 2844 return 'reserved for future use'; 2845 } 2846 if (($index >= 0xE0) && ($index <= 0xEF)) { 2847 return 'not predefined synch 0-F'; 2848 } 2849 if (($index >= 0xF0) && ($index <= 0xFC)) { 2850 return 'reserved for future use'; 2851 } 2852 2853 static $EventLookup = array( 2854 0x00 => 'padding (has no meaning)', 2855 0x01 => 'end of initial silence', 2856 0x02 => 'intro start', 2857 0x03 => 'main part start', 2858 0x04 => 'outro start', 2859 0x05 => 'outro end', 2860 0x06 => 'verse start', 2861 0x07 => 'refrain start', 2862 0x08 => 'interlude start', 2863 0x09 => 'theme start', 2864 0x0A => 'variation start', 2865 0x0B => 'key change', 2866 0x0C => 'time change', 2867 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)', 2868 0x0E => 'sustained noise', 2869 0x0F => 'sustained noise end', 2870 0x10 => 'intro end', 2871 0x11 => 'main part end', 2872 0x12 => 'verse end', 2873 0x13 => 'refrain end', 2874 0x14 => 'theme end', 2875 0x15 => 'profanity', 2876 0x16 => 'profanity end', 2877 0xFD => 'audio end (start of silence)', 2878 0xFE => 'audio file ends', 2879 0xFF => 'one more byte of events follows' 2880 ); 2881 2882 return (isset($EventLookup[$index]) ? $EventLookup[$index] : ''); 2883 } 2884 2885 public static function SYTLContentTypeLookup($index) { 2886 static $SYTLContentTypeLookup = array( 2887 0x00 => 'other', 2888 0x01 => 'lyrics', 2889 0x02 => 'text transcription', 2890 0x03 => 'movement/part name', // (e.g. 'Adagio') 2891 0x04 => 'events', // (e.g. 'Don Quijote enters the stage') 2892 0x05 => 'chord', // (e.g. 'Bb F Fsus') 2893 0x06 => 'trivia/\'pop up\' information', 2894 0x07 => 'URLs to webpages', 2895 0x08 => 'URLs to images' 2896 ); 2897 2898 return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : ''); 2899 } 2900 2901 public static function APICPictureTypeLookup($index, $returnarray=false) { 2902 static $APICPictureTypeLookup = array( 2903 0x00 => 'Other', 2904 0x01 => '32x32 pixels \'file icon\' (PNG only)', 2905 0x02 => 'Other file icon', 2906 0x03 => 'Cover (front)', 2907 0x04 => 'Cover (back)', 2908 0x05 => 'Leaflet page', 2909 0x06 => 'Media (e.g. label side of CD)', 2910 0x07 => 'Lead artist/lead performer/soloist', 2911 0x08 => 'Artist/performer', 2912 0x09 => 'Conductor', 2913 0x0A => 'Band/Orchestra',