From 1ed691f574a7785747ae99a229a51bf65cab8469 Mon Sep 17 00:00:00 2001 From: Aaron Jorbin Date: Sat, 14 Sep 2019 19:07:57 +0000 Subject: [PATCH] Update getID3 library to fix issues with PHP7.4 Updates to trunk version that includes fixes for PHP7.4 Changelog: https://github.com/JamesHeinrich/getID3/compare/v1.9.14...00f3fbfd77e583099ca70a3cf0bc092e113d2b20 See: #47751,#47783. Fixes: #48040. Built from https://develop.svn.wordpress.org/trunk@46112 git-svn-id: http://core.svn.wordpress.org/trunk@45924 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/ID3/getid3.lib.php | 815 +++-- wp-includes/ID3/getid3.php | 521 ++- wp-includes/ID3/license.txt | 6 +- wp-includes/ID3/module.audio-video.asf.php | 145 +- wp-includes/ID3/module.audio-video.flv.php | 209 +- .../ID3/module.audio-video.matroska.php | 137 +- .../ID3/module.audio-video.quicktime.php | 3147 +++++++++-------- wp-includes/ID3/module.audio-video.riff.php | 202 +- wp-includes/ID3/module.audio.ac3.php | 131 +- wp-includes/ID3/module.audio.dts.php | 83 +- wp-includes/ID3/module.audio.flac.php | 107 +- wp-includes/ID3/module.audio.mp3.php | 219 +- wp-includes/ID3/module.audio.ogg.php | 120 +- wp-includes/ID3/module.tag.apetag.php | 62 +- wp-includes/ID3/module.tag.id3v1.php | 62 +- wp-includes/ID3/module.tag.id3v2.php | 402 ++- wp-includes/ID3/module.tag.lyrics3.php | 43 +- wp-includes/ID3/readme.txt | 85 +- wp-includes/version.php | 2 +- 19 files changed, 4285 insertions(+), 2213 deletions(-) diff --git a/wp-includes/ID3/getid3.lib.php b/wp-includes/ID3/getid3.lib.php index 1931bb3724..1d51e8c4f3 100644 --- a/wp-includes/ID3/getid3.lib.php +++ b/wp-includes/ID3/getid3.lib.php @@ -1,27 +1,34 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // // // // getid3.lib.php - part of getID3() // -// See readme.txt for more details // +// see readme.txt for more details // // /// ///////////////////////////////////////////////////////////////// class getid3_lib { - + /** + * @param string $string + * @param bool $hex + * @param bool $spaces + * @param string $htmlencoding + * + * @return string + */ public static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') { $returnstring = ''; for ($i = 0; $i < strlen($string); $i++) { if ($hex) { - $returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT); + $returnstring .= str_pad(dechex(ord($string[$i])), 2, '0', STR_PAD_LEFT); } else { - $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : '¤'); + $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string[$i]) ? $string[$i] : '¤'); } if ($spaces) { $returnstring .= ' '; @@ -36,9 +43,14 @@ class getid3_lib return $returnstring; } + /** + * Truncates a floating-point number at the decimal point. + * + * @param float $floatnumber + * + * @return float|int returns int (if possible, otherwise float) + */ public static function trunc($floatnumber) { - // truncates a floating-point number at the decimal point - // returns int (if possible, otherwise float) if ($floatnumber >= 1) { $truncatednumber = floor($floatnumber); } elseif ($floatnumber <= -1) { @@ -52,7 +64,12 @@ class getid3_lib return $truncatednumber; } - + /** + * @param int|null $variable + * @param int $increment + * + * @return bool + */ public static function safe_inc(&$variable, $increment=1) { if (isset($variable)) { $variable += $increment; @@ -62,6 +79,11 @@ class getid3_lib return true; } + /** + * @param int|float $floatnum + * + * @return int|float + */ public static function CastAsInt($floatnum) { // convert to float if not already $floatnum = (float) $floatnum; @@ -77,6 +99,11 @@ class getid3_lib return $floatnum; } + /** + * @param int $num + * + * @return bool + */ public static function intValueSupported($num) { // check if integers are 64-bit static $hasINT64 = null; @@ -93,28 +120,43 @@ class getid3_lib return false; } + /** + * @param string $fraction + * + * @return float + */ public static function DecimalizeFraction($fraction) { list($numerator, $denominator) = explode('/', $fraction); return $numerator / ($denominator ? $denominator : 1); } - + /** + * @param string $binarynumerator + * + * @return float + */ public static function DecimalBinary2Float($binarynumerator) { $numerator = self::Bin2Dec($binarynumerator); $denominator = self::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator))); return ($numerator / $denominator); } - + /** + * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html + * + * @param string $binarypointnumber + * @param int $maxbits + * + * @return array + */ public static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { - // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html if (strpos($binarypointnumber, '.') === false) { $binarypointnumber = '0.'.$binarypointnumber; - } elseif ($binarypointnumber{0} == '.') { + } elseif ($binarypointnumber[0] == '.') { $binarypointnumber = '0'.$binarypointnumber; } $exponent = 0; - while (($binarypointnumber{0} != '1') || (substr($binarypointnumber, 1, 1) != '.')) { + while (($binarypointnumber[0] != '1') || (substr($binarypointnumber, 1, 1) != '.')) { if (substr($binarypointnumber, 1, 1) == '.') { $exponent--; $binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3); @@ -122,16 +164,21 @@ class getid3_lib $pointpos = strpos($binarypointnumber, '.'); $exponent += ($pointpos - 1); $binarypointnumber = str_replace('.', '', $binarypointnumber); - $binarypointnumber = $binarypointnumber{0}.'.'.substr($binarypointnumber, 1); + $binarypointnumber = $binarypointnumber[0].'.'.substr($binarypointnumber, 1); } } $binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT); return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent); } - + /** + * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html + * + * @param float $floatvalue + * + * @return string + */ public static function Float2BinaryDecimal($floatvalue) { - // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html $maxbits = 128; // to how many bits of precision should the calculations be taken? $intpart = self::trunc($floatvalue); $floatpart = abs($floatvalue - $intpart); @@ -145,9 +192,17 @@ class getid3_lib return $binarypointnumber; } - + /** + * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html + * + * @param float $floatvalue + * @param int $bits + * + * @return string|false + */ public static function Float2String($floatvalue, $bits) { - // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html + $exponentbits = 0; + $fractionbits = 0; switch ($bits) { case 32: $exponentbits = 8; @@ -176,22 +231,34 @@ class getid3_lib return self::BigEndian2String(self::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false); } - + /** + * @param string $byteword + * + * @return float|false + */ public static function LittleEndian2Float($byteword) { return self::BigEndian2Float(strrev($byteword)); } - + /** + * ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic + * + * @link http://www.psc.edu/general/software/packages/ieee/ieee.html + * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html + * + * @param string $byteword + * + * @return float|false + */ public static function BigEndian2Float($byteword) { - // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic - // http://www.psc.edu/general/software/packages/ieee/ieee.html - // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html - $bitword = self::BigEndian2Bin($byteword); if (!$bitword) { return 0; } - $signbit = $bitword{0}; + $signbit = $bitword[0]; + $floatvalue = 0; + $exponentbits = 0; + $fractionbits = 0; switch (strlen($byteword) * 8) { case 32: @@ -208,7 +275,7 @@ class getid3_lib // 80-bit Apple SANE format // http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ $exponentstring = substr($bitword, 1, 15); - $isnormalized = intval($bitword{16}); + $isnormalized = intval($bitword[16]); $fractionstring = substr($bitword, 17, 63); $exponent = pow(2, self::Bin2Dec($exponentstring) - 16383); $fraction = $isnormalized + self::DecimalBinary2Float($fractionstring); @@ -259,7 +326,14 @@ class getid3_lib return (float) $floatvalue; } - + /** + * @param string $byteword + * @param bool $synchsafe + * @param bool $signed + * + * @return int|float|false + * @throws Exception + */ public static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { $intvalue = 0; $bytewordlen = strlen($byteword); @@ -269,9 +343,9 @@ class getid3_lib for ($i = 0; $i < $bytewordlen; $i++) { if ($synchsafe) { // disregard MSB, effectively 7-bit bytes //$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); // faster, but runs into problems past 2^31 on 32-bit systems - $intvalue += (ord($byteword{$i}) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7); + $intvalue += (ord($byteword[$i]) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7); } else { - $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i)); + $intvalue += ord($byteword[$i]) * pow(256, ($bytewordlen - 1 - $i)); } } if ($signed && !$synchsafe) { @@ -288,25 +362,48 @@ class getid3_lib return self::CastAsInt($intvalue); } - + /** + * @param string $byteword + * @param bool $signed + * + * @return int|float|false + */ public static function LittleEndian2Int($byteword, $signed=false) { return self::BigEndian2Int(strrev($byteword), false, $signed); } + /** + * @param string $byteword + * + * @return string + */ public static function LittleEndian2Bin($byteword) { return self::BigEndian2Bin(strrev($byteword)); } + /** + * @param string $byteword + * + * @return string + */ public static function BigEndian2Bin($byteword) { $binvalue = ''; $bytewordlen = strlen($byteword); for ($i = 0; $i < $bytewordlen; $i++) { - $binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT); + $binvalue .= str_pad(decbin(ord($byteword[$i])), 8, '0', STR_PAD_LEFT); } return $binvalue; } - + /** + * @param int $number + * @param int $minbytes + * @param bool $synchsafe + * @param bool $signed + * + * @return string + * @throws Exception + */ public static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { if ($number < 0) { throw new Exception('ERROR: self::BigEndian2String() does not support negative numbers'); @@ -327,7 +424,11 @@ class getid3_lib return str_pad($intstring, $minbytes, "\x00", STR_PAD_LEFT); } - + /** + * @param int $number + * + * @return string + */ public static function Dec2Bin($number) { while ($number >= 256) { $bytes[] = (($number / 256) - (floor($number / 256))) * 256; @@ -341,11 +442,16 @@ class getid3_lib return $binstring; } - + /** + * @param string $binstring + * @param bool $signed + * + * @return int|float + */ public static function Bin2Dec($binstring, $signed=false) { $signmult = 1; if ($signed) { - if ($binstring{0} == '1') { + if ($binstring[0] == '1') { $signmult = -1; } $binstring = substr($binstring, 1); @@ -357,7 +463,11 @@ class getid3_lib return self::CastAsInt($decvalue * $signmult); } - + /** + * @param string $binstring + * + * @return string + */ public static function Bin2String($binstring) { // return 'hi' for input of '0110100001101001' $string = ''; @@ -368,7 +478,13 @@ class getid3_lib return $string; } - + /** + * @param int $number + * @param int $minbytes + * @param bool $synchsafe + * + * @return string + */ public static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { $intstring = ''; while ($number > 0) { @@ -383,7 +499,12 @@ class getid3_lib return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT); } - + /** + * @param array $array1 + * @param array $array2 + * + * @return array|false + */ public static function array_merge_clobber($array1, $array2) { // written by kcØhireability*com // taken from http://www.php.net/manual/en/function.array-merge-recursive.php @@ -401,7 +522,12 @@ class getid3_lib return $newarray; } - + /** + * @param array $array1 + * @param array $array2 + * + * @return array|false + */ public static function array_merge_noclobber($array1, $array2) { if (!is_array($array1) || !is_array($array2)) { return false; @@ -417,6 +543,12 @@ class getid3_lib return $newarray; } + /** + * @param array $array1 + * @param array $array2 + * + * @return array|false|null + */ public static function flipped_array_merge_noclobber($array1, $array2) { if (!is_array($array1) || !is_array($array2)) { return false; @@ -431,7 +563,11 @@ class getid3_lib return array_flip($newarray); } - + /** + * @param array $theArray + * + * @return bool + */ public static function ksort_recursive(&$theArray) { ksort($theArray); foreach ($theArray as $key => $value) { @@ -442,6 +578,12 @@ class getid3_lib return true; } + /** + * @param string $filename + * @param int $numextensions + * + * @return string + */ public static function fileextension($filename, $numextensions=1) { if (strstr($filename, '.')) { $reversedfilename = strrev($filename); @@ -457,7 +599,11 @@ class getid3_lib return ''; } - + /** + * @param int $seconds + * + * @return string + */ public static function PlaytimeString($seconds) { $sign = (($seconds < 0) ? '-' : ''); $seconds = round(abs($seconds)); @@ -467,30 +613,53 @@ class getid3_lib return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT); } - + /** + * @param int $macdate + * + * @return int|float + */ public static function DateMac2Unix($macdate) { // Macintosh timestamp: seconds since 00:00h January 1, 1904 // UNIX timestamp: seconds since 00:00h January 1, 1970 return self::CastAsInt($macdate - 2082844800); } - + /** + * @param string $rawdata + * + * @return float + */ public static function FixedPoint8_8($rawdata) { return self::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (self::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); } - + /** + * @param string $rawdata + * + * @return float + */ public static function FixedPoint16_16($rawdata) { return self::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (self::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); } - + /** + * @param string $rawdata + * + * @return float + */ public static function FixedPoint2_30($rawdata) { $binarystring = self::BigEndian2Bin($rawdata); return self::Bin2Dec(substr($binarystring, 0, 2)) + (float) (self::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30)); } + /** + * @param string $ArrayPath + * @param string $Separator + * @param mixed $Value + * + * @return array + */ public static function CreateDeepArray($ArrayPath, $Separator, $Value) { // assigns $Value to a nested array path: // $foo = self::CreateDeepArray('/path/to/my', '/', 'file.txt') @@ -507,6 +676,12 @@ class getid3_lib return $ReturnedArray; } + /** + * @param array $arraydata + * @param bool $returnkey + * + * @return int|false + */ public static function array_max($arraydata, $returnkey=false) { $maxvalue = false; $maxkey = false; @@ -521,6 +696,12 @@ class getid3_lib return ($returnkey ? $maxkey : $maxvalue); } + /** + * @param array $arraydata + * @param bool $returnkey + * + * @return int|false + */ public static function array_min($arraydata, $returnkey=false) { $minvalue = false; $minkey = false; @@ -535,6 +716,11 @@ class getid3_lib return ($returnkey ? $minkey : $minvalue); } + /** + * @param string $XMLstring + * + * @return array|false + */ public static function XML2array($XMLstring) { if (function_exists('simplexml_load_string') && function_exists('libxml_disable_entity_loader')) { // http://websec.io/2012/08/27/Preventing-XEE-in-PHP.html @@ -548,106 +734,68 @@ class getid3_lib return false; } + /** + * @param SimpleXMLElement|array $XMLobject + * + * @return array + */ public static function SimpleXMLelement2array($XMLobject) { if (!is_object($XMLobject) && !is_array($XMLobject)) { return $XMLobject; } - $XMLarray = (is_object($XMLobject) ? get_object_vars($XMLobject) : $XMLobject); + $XMLarray = $XMLobject instanceof SimpleXMLElement ? get_object_vars($XMLobject) : $XMLobject; foreach ($XMLarray as $key => $value) { $XMLarray[$key] = self::SimpleXMLelement2array($value); } return $XMLarray; } - - // Allan Hansen - // self::md5_data() - returns md5sum for a file from startuing position to absolute end position + /** + * Returns checksum for a file from starting position to absolute end position. + * + * @param string $file + * @param int $offset + * @param int $end + * @param string $algorithm + * + * @return string|false + * @throws getid3_exception + */ public static function hash_data($file, $offset, $end, $algorithm) { - static $tempdir = ''; if (!self::intValueSupported($end)) { return false; } - switch ($algorithm) { - case 'md5': - $hash_function = 'md5_file'; - $unix_call = 'md5sum'; - $windows_call = 'md5sum.exe'; - $hash_length = 32; - break; - - case 'sha1': - $hash_function = 'sha1_file'; - $unix_call = 'sha1sum'; - $windows_call = 'sha1sum.exe'; - $hash_length = 40; - break; - - default: - throw new Exception('Invalid algorithm ('.$algorithm.') in self::hash_data()'); - break; + if (!in_array($algorithm, array('md5', 'sha1'))) { + throw new getid3_exception('Invalid algorithm ('.$algorithm.') in self::hash_data()'); } + $size = $end - $offset; - while (true) { - if (GETID3_OS_ISWINDOWS) { - // It seems that sha1sum.exe for Windows only works on physical files, does not accept piped data - // Fall back to create-temp-file method: - if ($algorithm == 'sha1') { - break; - } - - $RequiredFiles = array('cygwin1.dll', 'head.exe', 'tail.exe', $windows_call); - foreach ($RequiredFiles as $required_file) { - if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { - // helper apps not available - fall back to old method - break 2; - } - } - $commandline = GETID3_HELPERAPPSDIR.'head.exe -c '.$end.' '.escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $file)).' | '; - $commandline .= GETID3_HELPERAPPSDIR.'tail.exe -c '.$size.' | '; - $commandline .= GETID3_HELPERAPPSDIR.$windows_call; - - } else { - - $commandline = 'head -c'.$end.' '.escapeshellarg($file).' | '; - $commandline .= 'tail -c'.$size.' | '; - $commandline .= $unix_call; - - } - if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { - //throw new Exception('PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm'); - break; - } - return substr(`$commandline`, 0, $hash_length); + $fp = fopen($file, 'rb'); + fseek($fp, $offset); + $ctx = hash_init($algorithm); + while ($size > 0) { + $buffer = fread($fp, min($size, getID3::FREAD_BUFFER_SIZE)); + hash_update($ctx, $buffer); + $size -= getID3::FREAD_BUFFER_SIZE; } + $hash = hash_final($ctx); + fclose($fp); - if (empty($tempdir)) { - // yes this is ugly, feel free to suggest a better way - require_once(dirname(__FILE__).'/getid3.php'); - $getid3_temp = new getID3(); - $tempdir = $getid3_temp->tempdir; - unset($getid3_temp); - } - // try to create a temporary file in the system temp directory - invalid dirname should force to system temp dir - if (($data_filename = tempnam($tempdir, 'gI3')) === false) { - // can't find anywhere to create a temp file, just fail - return false; - } - - // Init - $result = false; - - // copy parts of file - try { - self::CopyFileParts($file, $data_filename, $offset, $end - $offset); - $result = $hash_function($data_filename); - } catch (Exception $e) { - throw new Exception('self::CopyFileParts() failed in getid_lib::hash_data(): '.$e->getMessage()); - } - unlink($data_filename); - return $result; + return $hash; } + /** + * @param string $filename_source + * @param string $filename_dest + * @param int $offset + * @param int $length + * + * @return bool + * @throws Exception + * + * @deprecated Unused, may be removed in future versions of getID3 + */ public static function CopyFileParts($filename_source, $filename_dest, $offset, $length) { if (!self::intValueSupported($offset + $length)) { throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit'); @@ -660,21 +808,25 @@ class getid3_lib $byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite); $byteslefttowrite -= $byteswritten; } + fclose($fp_dest); return true; } else { + fclose($fp_src); throw new Exception('failed to seek to offset '.$offset.' in '.$filename_source); } - fclose($fp_dest); } else { throw new Exception('failed to create file for writing '.$filename_dest); } - fclose($fp_src); } else { throw new Exception('failed to open file for reading '.$filename_source); } - return false; } + /** + * @param int $charval + * + * @return string + */ public static function iconv_fallback_int_utf8($charval) { if ($charval < 128) { // 0bbbbbbb @@ -698,7 +850,14 @@ class getid3_lib return $newcharstring; } - // ISO-8859-1 => UTF-8 + /** + * ISO-8859-1 => UTF-8 + * + * @param string $string + * @param bool $bom + * + * @return string + */ public static function iconv_fallback_iso88591_utf8($string, $bom=false) { if (function_exists('utf8_encode')) { return utf8_encode($string); @@ -709,42 +868,68 @@ class getid3_lib $newcharstring .= "\xEF\xBB\xBF"; } for ($i = 0; $i < strlen($string); $i++) { - $charval = ord($string{$i}); + $charval = ord($string[$i]); $newcharstring .= self::iconv_fallback_int_utf8($charval); } return $newcharstring; } - // ISO-8859-1 => UTF-16BE + /** + * ISO-8859-1 => UTF-16BE + * + * @param string $string + * @param bool $bom + * + * @return string + */ public static function iconv_fallback_iso88591_utf16be($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFE\xFF"; } for ($i = 0; $i < strlen($string); $i++) { - $newcharstring .= "\x00".$string{$i}; + $newcharstring .= "\x00".$string[$i]; } return $newcharstring; } - // ISO-8859-1 => UTF-16LE + /** + * ISO-8859-1 => UTF-16LE + * + * @param string $string + * @param bool $bom + * + * @return string + */ public static function iconv_fallback_iso88591_utf16le($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFF\xFE"; } for ($i = 0; $i < strlen($string); $i++) { - $newcharstring .= $string{$i}."\x00"; + $newcharstring .= $string[$i]."\x00"; } return $newcharstring; } - // ISO-8859-1 => UTF-16LE (BOM) + /** + * ISO-8859-1 => UTF-16LE (BOM) + * + * @param string $string + * + * @return string + */ public static function iconv_fallback_iso88591_utf16($string) { return self::iconv_fallback_iso88591_utf16le($string, true); } - // UTF-8 => ISO-8859-1 + /** + * UTF-8 => ISO-8859-1 + * + * @param string $string + * + * @return string + */ public static function iconv_fallback_utf8_iso88591($string) { if (function_exists('utf8_decode')) { return utf8_decode($string); @@ -754,27 +939,27 @@ class getid3_lib $offset = 0; $stringlength = strlen($string); while ($offset < $stringlength) { - if ((ord($string{$offset}) | 0x07) == 0xF7) { + if ((ord($string[$offset]) | 0x07) == 0xF7) { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb - $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & - ((ord($string{($offset + 1)}) & 0x3F) << 12) & - ((ord($string{($offset + 2)}) & 0x3F) << 6) & - (ord($string{($offset + 3)}) & 0x3F); + $charval = ((ord($string[($offset + 0)]) & 0x07) << 18) & + ((ord($string[($offset + 1)]) & 0x3F) << 12) & + ((ord($string[($offset + 2)]) & 0x3F) << 6) & + (ord($string[($offset + 3)]) & 0x3F); $offset += 4; - } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { + } elseif ((ord($string[$offset]) | 0x0F) == 0xEF) { // 1110bbbb 10bbbbbb 10bbbbbb - $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & - ((ord($string{($offset + 1)}) & 0x3F) << 6) & - (ord($string{($offset + 2)}) & 0x3F); + $charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) & + ((ord($string[($offset + 1)]) & 0x3F) << 6) & + (ord($string[($offset + 2)]) & 0x3F); $offset += 3; - } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { + } elseif ((ord($string[$offset]) | 0x1F) == 0xDF) { // 110bbbbb 10bbbbbb - $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & - (ord($string{($offset + 1)}) & 0x3F); + $charval = ((ord($string[($offset + 0)]) & 0x1F) << 6) & + (ord($string[($offset + 1)]) & 0x3F); $offset += 2; - } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { + } elseif ((ord($string[$offset]) | 0x7F) == 0x7F) { // 0bbbbbbb - $charval = ord($string{$offset}); + $charval = ord($string[$offset]); $offset += 1; } else { // error? throw some kind of warning here? @@ -788,7 +973,14 @@ class getid3_lib return $newcharstring; } - // UTF-8 => UTF-16BE + /** + * UTF-8 => UTF-16BE + * + * @param string $string + * @param bool $bom + * + * @return string + */ public static function iconv_fallback_utf8_utf16be($string, $bom=false) { $newcharstring = ''; if ($bom) { @@ -797,27 +989,27 @@ class getid3_lib $offset = 0; $stringlength = strlen($string); while ($offset < $stringlength) { - if ((ord($string{$offset}) | 0x07) == 0xF7) { + if ((ord($string[$offset]) | 0x07) == 0xF7) { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb - $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & - ((ord($string{($offset + 1)}) & 0x3F) << 12) & - ((ord($string{($offset + 2)}) & 0x3F) << 6) & - (ord($string{($offset + 3)}) & 0x3F); + $charval = ((ord($string[($offset + 0)]) & 0x07) << 18) & + ((ord($string[($offset + 1)]) & 0x3F) << 12) & + ((ord($string[($offset + 2)]) & 0x3F) << 6) & + (ord($string[($offset + 3)]) & 0x3F); $offset += 4; - } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { + } elseif ((ord($string[$offset]) | 0x0F) == 0xEF) { // 1110bbbb 10bbbbbb 10bbbbbb - $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & - ((ord($string{($offset + 1)}) & 0x3F) << 6) & - (ord($string{($offset + 2)}) & 0x3F); + $charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) & + ((ord($string[($offset + 1)]) & 0x3F) << 6) & + (ord($string[($offset + 2)]) & 0x3F); $offset += 3; - } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { + } elseif ((ord($string[$offset]) | 0x1F) == 0xDF) { // 110bbbbb 10bbbbbb - $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & - (ord($string{($offset + 1)}) & 0x3F); + $charval = ((ord($string[($offset + 0)]) & 0x1F) << 6) & + (ord($string[($offset + 1)]) & 0x3F); $offset += 2; - } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { + } elseif ((ord($string[$offset]) | 0x7F) == 0x7F) { // 0bbbbbbb - $charval = ord($string{$offset}); + $charval = ord($string[$offset]); $offset += 1; } else { // error? throw some kind of warning here? @@ -831,7 +1023,14 @@ class getid3_lib return $newcharstring; } - // UTF-8 => UTF-16LE + /** + * UTF-8 => UTF-16LE + * + * @param string $string + * @param bool $bom + * + * @return string + */ public static function iconv_fallback_utf8_utf16le($string, $bom=false) { $newcharstring = ''; if ($bom) { @@ -840,27 +1039,27 @@ class getid3_lib $offset = 0; $stringlength = strlen($string); while ($offset < $stringlength) { - if ((ord($string{$offset}) | 0x07) == 0xF7) { + if ((ord($string[$offset]) | 0x07) == 0xF7) { // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb - $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & - ((ord($string{($offset + 1)}) & 0x3F) << 12) & - ((ord($string{($offset + 2)}) & 0x3F) << 6) & - (ord($string{($offset + 3)}) & 0x3F); + $charval = ((ord($string[($offset + 0)]) & 0x07) << 18) & + ((ord($string[($offset + 1)]) & 0x3F) << 12) & + ((ord($string[($offset + 2)]) & 0x3F) << 6) & + (ord($string[($offset + 3)]) & 0x3F); $offset += 4; - } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { + } elseif ((ord($string[$offset]) | 0x0F) == 0xEF) { // 1110bbbb 10bbbbbb 10bbbbbb - $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & - ((ord($string{($offset + 1)}) & 0x3F) << 6) & - (ord($string{($offset + 2)}) & 0x3F); + $charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) & + ((ord($string[($offset + 1)]) & 0x3F) << 6) & + (ord($string[($offset + 2)]) & 0x3F); $offset += 3; - } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { + } elseif ((ord($string[$offset]) | 0x1F) == 0xDF) { // 110bbbbb 10bbbbbb - $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & - (ord($string{($offset + 1)}) & 0x3F); + $charval = ((ord($string[($offset + 0)]) & 0x1F) << 6) & + (ord($string[($offset + 1)]) & 0x3F); $offset += 2; - } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { + } elseif ((ord($string[$offset]) | 0x7F) == 0x7F) { // 0bbbbbbb - $charval = ord($string{$offset}); + $charval = ord($string[$offset]); $offset += 1; } else { // error? maybe throw some warning here? @@ -874,12 +1073,24 @@ class getid3_lib return $newcharstring; } - // UTF-8 => UTF-16LE (BOM) + /** + * UTF-8 => UTF-16LE (BOM) + * + * @param string $string + * + * @return string + */ public static function iconv_fallback_utf8_utf16($string) { return self::iconv_fallback_utf8_utf16le($string, true); } - // UTF-16BE => UTF-8 + /** + * UTF-16BE => UTF-8 + * + * @param string $string + * + * @return string + */ public static function iconv_fallback_utf16be_utf8($string) { if (substr($string, 0, 2) == "\xFE\xFF") { // strip BOM @@ -893,7 +1104,13 @@ class getid3_lib return $newcharstring; } - // UTF-16LE => UTF-8 + /** + * UTF-16LE => UTF-8 + * + * @param string $string + * + * @return string + */ public static function iconv_fallback_utf16le_utf8($string) { if (substr($string, 0, 2) == "\xFF\xFE") { // strip BOM @@ -907,7 +1124,13 @@ class getid3_lib return $newcharstring; } - // UTF-16BE => ISO-8859-1 + /** + * UTF-16BE => ISO-8859-1 + * + * @param string $string + * + * @return string + */ public static function iconv_fallback_utf16be_iso88591($string) { if (substr($string, 0, 2) == "\xFE\xFF") { // strip BOM @@ -921,7 +1144,13 @@ class getid3_lib return $newcharstring; } - // UTF-16LE => ISO-8859-1 + /** + * UTF-16LE => ISO-8859-1 + * + * @param string $string + * + * @return string + */ public static function iconv_fallback_utf16le_iso88591($string) { if (substr($string, 0, 2) == "\xFF\xFE") { // strip BOM @@ -935,7 +1164,13 @@ class getid3_lib return $newcharstring; } - // UTF-16 (BOM) => ISO-8859-1 + /** + * UTF-16 (BOM) => ISO-8859-1 + * + * @param string $string + * + * @return string + */ public static function iconv_fallback_utf16_iso88591($string) { $bom = substr($string, 0, 2); if ($bom == "\xFE\xFF") { @@ -946,7 +1181,13 @@ class getid3_lib return $string; } - // UTF-16 (BOM) => UTF-8 + /** + * UTF-16 (BOM) => UTF-8 + * + * @param string $string + * + * @return string + */ public static function iconv_fallback_utf16_utf8($string) { $bom = substr($string, 0, 2); if ($bom == "\xFE\xFF") { @@ -957,14 +1198,32 @@ class getid3_lib return $string; } + /** + * @param string $in_charset + * @param string $out_charset + * @param string $string + * + * @return string + * @throws Exception + */ public static function iconv_fallback($in_charset, $out_charset, $string) { if ($in_charset == $out_charset) { return $string; } - // mb_convert_encoding() availble + // mb_convert_encoding() available if (function_exists('mb_convert_encoding')) { + if ((strtoupper($in_charset) == 'UTF-16') && (substr($string, 0, 2) != "\xFE\xFF") && (substr($string, 0, 2) != "\xFF\xFE")) { + // if BOM missing, mb_convert_encoding will mishandle the conversion, assume UTF-16BE and prepend appropriate BOM + $string = "\xFF\xFE".$string; + } + if ((strtoupper($in_charset) == 'UTF-16') && (strtoupper($out_charset) == 'UTF-8')) { + if (($string == "\xFF\xFE") || ($string == "\xFE\xFF")) { + // if string consists of only BOM, mb_convert_encoding will return the BOM unmodified + return ''; + } + } if ($converted_string = @mb_convert_encoding($string, $out_charset, $in_charset)) { switch ($out_charset) { case 'ISO-8859-1': @@ -974,9 +1233,9 @@ class getid3_lib return $converted_string; } return $string; - } - // iconv() availble - else if (function_exists('iconv')) { + + // iconv() available + } elseif (function_exists('iconv')) { if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) { switch ($out_charset) { case 'ISO-8859-1': @@ -1017,6 +1276,12 @@ class getid3_lib throw new Exception('PHP does not has mb_convert_encoding() or iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); } + /** + * @param mixed $data + * @param string $charset + * + * @return mixed + */ public static function recursiveMultiByteCharString2HTML($data, $charset='ISO-8859-1') { if (is_string($data)) { return self::MultiByteCharString2HTML($data, $charset); @@ -1031,6 +1296,12 @@ class getid3_lib return $data; } + /** + * @param string|int|float $string + * @param string $charset + * + * @return string + */ public static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') { $string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string $HTMLstring = ''; @@ -1069,22 +1340,22 @@ class getid3_lib case 'utf-8': $strlen = strlen($string); for ($i = 0; $i < $strlen; $i++) { - $char_ord_val = ord($string{$i}); + $char_ord_val = ord($string[$i]); $charval = 0; if ($char_ord_val < 0x80) { $charval = $char_ord_val; } elseif ((($char_ord_val & 0xF0) >> 4) == 0x0F && $i+3 < $strlen) { $charval = (($char_ord_val & 0x07) << 18); - $charval += ((ord($string{++$i}) & 0x3F) << 12); - $charval += ((ord($string{++$i}) & 0x3F) << 6); - $charval += (ord($string{++$i}) & 0x3F); + $charval += ((ord($string[++$i]) & 0x3F) << 12); + $charval += ((ord($string[++$i]) & 0x3F) << 6); + $charval += (ord($string[++$i]) & 0x3F); } elseif ((($char_ord_val & 0xE0) >> 5) == 0x07 && $i+2 < $strlen) { $charval = (($char_ord_val & 0x0F) << 12); - $charval += ((ord($string{++$i}) & 0x3F) << 6); - $charval += (ord($string{++$i}) & 0x3F); + $charval += ((ord($string[++$i]) & 0x3F) << 6); + $charval += (ord($string[++$i]) & 0x3F); } elseif ((($char_ord_val & 0xC0) >> 6) == 0x03 && $i+1 < $strlen) { $charval = (($char_ord_val & 0x1F) << 6); - $charval += (ord($string{++$i}) & 0x3F); + $charval += (ord($string[++$i]) & 0x3F); } if (($charval >= 32) && ($charval <= 127)) { $HTMLstring .= htmlentities(chr($charval)); @@ -1123,8 +1394,11 @@ class getid3_lib return $HTMLstring; } - - + /** + * @param int $namecode + * + * @return string + */ public static function RGADnameLookup($namecode) { static $RGADname = array(); if (empty($RGADname)) { @@ -1136,7 +1410,11 @@ class getid3_lib return (isset($RGADname[$namecode]) ? $RGADname[$namecode] : ''); } - + /** + * @param int $originatorcode + * + * @return string + */ public static function RGADoriginatorLookup($originatorcode) { static $RGADoriginator = array(); if (empty($RGADoriginator)) { @@ -1149,16 +1427,27 @@ class getid3_lib return (isset($RGADoriginator[$originatorcode]) ? $RGADoriginator[$originatorcode] : ''); } - + /** + * @param int $rawadjustment + * @param int $signbit + * + * @return float + */ public static function RGADadjustmentLookup($rawadjustment, $signbit) { - $adjustment = $rawadjustment / 10; + $adjustment = (float) $rawadjustment / 10; if ($signbit == 1) { $adjustment *= -1; } - return (float) $adjustment; + return $adjustment; } - + /** + * @param int $namecode + * @param int $originatorcode + * @param int $replaygain + * + * @return string + */ public static function RGADgainString($namecode, $originatorcode, $replaygain) { if ($replaygain < 0) { $signbit = '1'; @@ -1174,11 +1463,21 @@ class getid3_lib return $gainstring; } + /** + * @param float $amplitude + * + * @return float + */ public static function RGADamplitude2dB($amplitude) { return 20 * log10($amplitude); } - + /** + * @param string $imgData + * @param array $imageinfo + * + * @return array|false + */ public static function GetDataImageSize($imgData, &$imageinfo=array()) { static $tempdir = ''; if (empty($tempdir)) { @@ -1213,32 +1512,21 @@ class getid3_lib return $GetDataImageSize; } + /** + * @param string $mime_type + * + * @return string + */ public static function ImageExtFromMime($mime_type) { // temporary way, works OK for now, but should be reworked in the future return str_replace(array('image/', 'x-', 'jpeg'), array('', '', 'jpg'), $mime_type); } - public static function ImageTypesLookup($imagetypeid) { - static $ImageTypesLookup = array(); - if (empty($ImageTypesLookup)) { - $ImageTypesLookup[1] = 'gif'; - $ImageTypesLookup[2] = 'jpeg'; - $ImageTypesLookup[3] = 'png'; - $ImageTypesLookup[4] = 'swf'; - $ImageTypesLookup[5] = 'psd'; - $ImageTypesLookup[6] = 'bmp'; - $ImageTypesLookup[7] = 'tiff (little-endian)'; - $ImageTypesLookup[8] = 'tiff (big-endian)'; - $ImageTypesLookup[9] = 'jpc'; - $ImageTypesLookup[10] = 'jp2'; - $ImageTypesLookup[11] = 'jpx'; - $ImageTypesLookup[12] = 'jb2'; - $ImageTypesLookup[13] = 'swc'; - $ImageTypesLookup[14] = 'iff'; - } - return (isset($ImageTypesLookup[$imagetypeid]) ? $ImageTypesLookup[$imagetypeid] : ''); - } - + /** + * @param array $ThisFileInfo + * + * @return bool + */ public static function CopyTagsToComments(&$ThisFileInfo) { // Copy all entries from ['tags'] into common ['comments'] @@ -1326,7 +1614,15 @@ class getid3_lib return true; } - + /** + * @param string $key + * @param int $begin + * @param int $end + * @param string $file + * @param string $name + * + * @return string + */ public static function EmbeddedLookup($key, $begin, $end, $file, $name) { // Cached @@ -1373,6 +1669,14 @@ class getid3_lib return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); } + /** + * @param string $filename + * @param string $sourcefile + * @param bool $DieOnFailure + * + * @return bool + * @throws Exception + */ public static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { global $GETID3_ERRORARRAY; @@ -1393,10 +1697,20 @@ class getid3_lib return false; } + /** + * @param string $string + * + * @return string + */ public static function trimNullByte($string) { return trim($string, "\x00"); } + /** + * @param string $path + * + * @return float|bool + */ public static function getFileSizeSyscall($path) { $filesize = false; @@ -1421,13 +1735,44 @@ class getid3_lib return $filesize; } + /** + * @param string $filename + * + * @return string|false + */ + public static function truepath($filename) { + // 2017-11-08: this could use some improvement, patches welcome + if (preg_match('#^(\\\\\\\\|//)[a-z0-9]#i', $filename, $matches)) { + // PHP's built-in realpath function does not work on UNC Windows shares + $goodpath = array(); + foreach (explode('/', str_replace('\\', '/', $filename)) as $part) { + if ($part == '.') { + continue; + } + if ($part == '..') { + if (count($goodpath)) { + array_pop($goodpath); + } else { + // cannot step above this level, already at top level + return false; + } + } else { + $goodpath[] = $part; + } + } + return implode(DIRECTORY_SEPARATOR, $goodpath); + } + return realpath($filename); + } /** - * Workaround for Bug #37268 (https://bugs.php.net/bug.php?id=37268) - * @param string $path A path. - * @param string $suffix If the name component ends in suffix this will also be cut off. - * @return string - */ + * Workaround for Bug #37268 (https://bugs.php.net/bug.php?id=37268) + * + * @param string $path A path. + * @param string $suffix If the name component ends in suffix this will also be cut off. + * + * @return string + */ public static function mb_basename($path, $suffix = null) { $splited = preg_split('#/#', rtrim($path, '/ ')); return substr(basename('X'.$splited[count($splited) - 1], $suffix), 1); diff --git a/wp-includes/ID3/getid3.php b/wp-includes/ID3/getid3.php index 7732c19587..9eb3c82d55 100644 --- a/wp-includes/ID3/getid3.php +++ b/wp-includes/ID3/getid3.php @@ -1,10 +1,9 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // // // // Please see readme.txt for more information // // /// @@ -26,6 +25,14 @@ if (!defined('ENT_SUBSTITUTE')) { // PHP5.3 adds ENT_IGNORE, PHP5.4 adds ENT_SUB define('ENT_SUBSTITUTE', (defined('ENT_IGNORE') ? ENT_IGNORE : 8)); } +/* +https://www.getid3.org/phpBB3/viewtopic.php?t=2114 +If you are running into a the problem where filenames with special characters are being handled +incorrectly by external helper programs (e.g. metaflac), notably with the special characters removed, +and you are passing in the filename in UTF8 (typically via a HTML form), try uncommenting this line: +*/ +//setlocale(LC_CTYPE, 'en_US.UTF-8'); + // attempt to define temp dir as something flexible but reliable $temp_dir = ini_get('upload_tmp_dir'); if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { @@ -74,53 +81,190 @@ unset($open_basedir, $temp_dir); class getID3 { - // public: Settings - public $encoding = 'UTF-8'; // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE - 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' + /* + * Settings + */ - // public: Optional tag checks - disable for speed. - public $option_tag_id3v1 = true; // Read and process ID3v1 tags - public $option_tag_id3v2 = true; // Read and process ID3v2 tags - public $option_tag_lyrics3 = true; // Read and process Lyrics3 tags - public $option_tag_apetag = true; // Read and process APE tags - public $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding - public $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities + /** + * CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE + * + * @var string + */ + public $encoding = 'UTF-8'; - // public: Optional tag/comment calucations - public $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc + /** + * Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252' + * + * @var string + */ + public $encoding_id3v1 = 'ISO-8859-1'; - // public: Optional handling of embedded attachments (e.g. images) - public $option_save_attachments = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility + /* + * Optional tag checks - disable for speed. + */ - // public: Optional calculations - public $option_md5_data = false; // Get MD5 sum of data part - slow - public $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG - public $option_sha1_data = false; // Get SHA1 sum of data part - slow - 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) + /** + * Read and process ID3v1 tags + * + * @var bool + */ + public $option_tag_id3v1 = true; - // public: Read buffer size in bytes + /** + * Read and process ID3v2 tags + * + * @var bool + */ + public $option_tag_id3v2 = true; + + /** + * Read and process Lyrics3 tags + * + * @var bool + */ + public $option_tag_lyrics3 = true; + + /** + * Read and process APE tags + * + * @var bool + */ + public $option_tag_apetag = true; + + /** + * Copy tags to root key 'tags' and encode to $this->encoding + * + * @var bool + */ + public $option_tags_process = true; + + /** + * Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities + * + * @var bool + */ + public $option_tags_html = true; + + /* + * Optional tag/comment calculations + */ + + /** + * Calculate additional info such as bitrate, channelmode etc + * + * @var bool + */ + public $option_extra_info = true; + + /* + * Optional handling of embedded attachments (e.g. images) + */ + + /** + * Defaults to true (ATTACHMENTS_INLINE) for backward compatibility + * + * @var bool|string + */ + public $option_save_attachments = true; + + /* + * Optional calculations + */ + + /** + * Get MD5 sum of data part - slow + * + * @var bool + */ + public $option_md5_data = false; + + /** + * Use MD5 of source file if availble - only FLAC and OptimFROG + * + * @var bool + */ + public $option_md5_data_source = false; + + /** + * Get SHA1 sum of data part - slow + * + * @var bool + */ + public $option_sha1_data = false; + + /** + * Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on + * PHP_INT_MAX) + * + * @var bool|null + */ + public $option_max_2gb_check; + + /** + * Read buffer size in bytes + * + * @var int + */ public $option_fread_buffer_size = 32768; // Public variables - public $filename; // Filename of file being analysed. - public $fp; // Filepointer to file being analysed. - public $info; // Result array. + + /** + * Filename of file being analysed. + * + * @var string + */ + public $filename; + + /** + * Filepointer to file being analysed. + * + * @var resource + */ + public $fp; + + /** + * Result array. + * + * @var array + */ + public $info; + + /** + * @var string + */ public $tempdir = GETID3_TEMP_DIR; + + /** + * @var int + */ public $memory_limit = 0; - // Protected variables + /** + * @var string + */ protected $startup_error = ''; + + /** + * @var string + */ protected $startup_warning = ''; - const VERSION = '1.9.14-201706111222'; + const VERSION = '1.9.17-201907240906'; const FREAD_BUFFER_SIZE = 32768; const ATTACHMENTS_NONE = false; const ATTACHMENTS_INLINE = true; - // public: constructor public function __construct() { + // Check for PHP version + $required_php_version = '5.3.0'; + if (version_compare(PHP_VERSION, $required_php_version, '<')) { + $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION."\n"; + return; + } + // Check memory $this->memory_limit = ini_get('memory_limit'); if (preg_match('#([0-9]+) ?M#i', $this->memory_limit, $matches)) { @@ -176,7 +320,7 @@ class getID3 // Needed for Windows only: // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC - // as well as other helper functions such as head, tail, md5sum, etc + // as well as other helper functions such as head, etc // This path cannot contain spaces, but the below code will attempt to get the // 8.3-equivalent path automatically // IMPORTANT: This path must include the trailing slash @@ -219,20 +363,27 @@ class getID3 echo $this->startup_error; throw new getid3_exception($this->startup_error); } - - return true; } + /** + * @return string + */ public function version() { return self::VERSION; } + /** + * @return int + */ public function fread_buffer_size() { return $this->option_fread_buffer_size; } - - // public: setOption + /** + * @param array $optArray + * + * @return bool + */ public function setOption($optArray) { if (!is_array($optArray) || empty($optArray)) { return false; @@ -246,8 +397,15 @@ class getID3 return true; } - - public function openfile($filename, $filesize=null) { + /** + * @param string $filename + * @param int $filesize + * + * @return bool + * + * @throws getid3_exception + */ + public function openfile($filename, $filesize=null, $fp=null) { try { if (!empty($this->startup_error)) { throw new getid3_exception($this->startup_error); @@ -270,11 +428,13 @@ class getID3 } $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); - $filename = preg_replace('#(?fp = fopen($filename, 'rb'))) { // see http://www.getid3.org/phpBB3/viewtopic.php?t=1720 - if ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { + //if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see https://www.getid3.org/phpBB3/viewtopic.php?t=1720 + if (($fp != null) && ((get_resource_type($fp) == 'file') || (get_resource_type($fp) == 'stream'))) { + $this->fp = $fp; + } elseif ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // great } else { $errormessagelist = array(); @@ -331,10 +491,10 @@ class getID3 } elseif (getid3_lib::intValueSupported($real_filesize)) { unset($this->info['filesize']); fclose($this->fp); - 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'); + throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB, please report to info@getid3.org'); } $this->info['filesize'] = $real_filesize; - $this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.'); + $this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB) and is not properly supported by PHP.'); } } @@ -346,10 +506,18 @@ class getID3 return false; } - // public: analyze file - public function analyze($filename, $filesize=null, $original_filename='') { + /** + * analyze file + * + * @param string $filename + * @param int $filesize + * @param string $original_filename + * + * @return array + */ + public function analyze($filename, $filesize=null, $original_filename='', $fp=null) { try { - if (!$this->openfile($filename, $filesize)) { + if (!$this->openfile($filename, $filesize, $fp)) { return $this->info; } @@ -383,8 +551,8 @@ class getID3 $header = fread($this->fp, 10); if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { $this->info['id3v2']['header'] = true; - $this->info['id3v2']['majorversion'] = ord($header{3}); - $this->info['id3v2']['minorversion'] = ord($header{4}); + $this->info['id3v2']['majorversion'] = ord($header[3]); + $this->info['id3v2']['minorversion'] = ord($header[4]); $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 } } @@ -497,7 +665,13 @@ class getID3 } - // private: error handling + /** + * Error handling. + * + * @param string $message + * + * @return array + */ public function error($message) { $this->CleanUp(); if (!isset($this->info['error'])) { @@ -508,14 +682,22 @@ class getID3 } - // private: warning handling + /** + * Warning handling. + * + * @param string $message + * + * @return bool + */ public function warning($message) { $this->info['warning'][] = $message; return true; } - // private: CleanUp + /** + * @return bool + */ private function CleanUp() { // remove possible empty keys @@ -562,8 +744,11 @@ class getID3 return true; } - - // return array containing information about all supported formats + /** + * Return array containing information about all supported formats. + * + * @return array + */ public function GetFileFormatArray() { static $format_info = array(); if (empty($format_info)) { @@ -584,7 +769,7 @@ class getID3 'pattern' => '^ADIF', 'group' => 'audio', 'module' => 'aac', - 'mime_type' => 'application/octet-stream', + 'mime_type' => 'audio/aac', 'fail_ape' => 'WARNING', ), @@ -602,7 +787,7 @@ class getID3 'pattern' => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]', 'group' => 'audio', 'module' => 'aac', - 'mime_type' => 'application/octet-stream', + 'mime_type' => 'audio/aac', 'fail_ape' => 'WARNING', ), @@ -649,7 +834,7 @@ class getID3 // DSS - audio - Digital Speech Standard 'dss' => array( - 'pattern' => '^[\\x02-\\x06]ds[s2]', + 'pattern' => '^[\\x02-\\x08]ds[s2]', 'group' => 'audio', 'module' => 'dss', 'mime_type' => 'application/octet-stream', @@ -668,7 +853,7 @@ class getID3 'pattern' => '^fLaC', 'group' => 'audio', 'module' => 'flac', - 'mime_type' => 'audio/x-flac', + 'mime_type' => 'audio/flac', ), // LA - audio - Lossless Audio (LA) @@ -700,7 +885,7 @@ class getID3 'pattern' => '^MAC ', 'group' => 'audio', 'module' => 'monkey', - 'mime_type' => 'application/octet-stream', + 'mime_type' => 'audio/x-monkeys-audio', ), // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available @@ -889,7 +1074,7 @@ class getID3 'pattern' => '^(RIFF|SDSS|FORM)', 'group' => 'audio-video', 'module' => 'riff', - 'mime_type' => 'audio/x-wav', + 'mime_type' => 'audio/wav', 'fail_ape' => 'WARNING', ), @@ -1053,7 +1238,7 @@ class getID3 'pattern' => '^\\x1F\\x8B\\x08', 'group' => 'archive', 'module' => 'gzip', - 'mime_type' => 'application/x-gzip', + 'mime_type' => 'application/gzip', 'fail_id3' => 'ERROR', 'fail_ape' => 'ERROR', ), @@ -1068,6 +1253,16 @@ class getID3 'fail_ape' => 'ERROR', ), + // XZ - data - XZ compressed data + 'xz' => array( + 'pattern' => '^\\xFD7zXZ\\x00', + 'group' => 'archive', + 'module' => 'xz', + 'mime_type' => 'application/x-xz', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + // Misc other formats @@ -1115,8 +1310,12 @@ class getID3 return $format_info; } - - + /** + * @param string $filedata + * @param string $filename + * + * @return mixed|false + */ public function GetFileFormat(&$filedata, $filename='') { // this function will determine the format of a file based on usually // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG, @@ -1135,7 +1334,7 @@ class getID3 if (preg_match('#\\.mp[123a]$#i', $filename)) { - // Too many mp3 encoders on the market put gabage in front of mpeg files + // Too many mp3 encoders on the market put garbage in front of mpeg files // use assume format on these if format detection failed $GetFileFormatArray = $this->GetFileFormatArray(); $info = $GetFileFormatArray['mp3']; @@ -1154,8 +1353,12 @@ class getID3 return false; } - - // converts array to $encoding charset from $this->encoding + /** + * Converts array to $encoding charset from $this->encoding. + * + * @param array $array + * @param string $encoding + */ public function CharConvert(&$array, $encoding) { // identical encoding - end here @@ -1178,7 +1381,9 @@ class getID3 } } - + /** + * @return bool + */ public function HandleAllTags() { // key name => array (tag name, character encoding) @@ -1233,6 +1438,7 @@ class getID3 } } if ($tag_key == 'picture') { + // pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere unset($this->info[$comment_name]['comments'][$tag_key]); } } @@ -1246,6 +1452,11 @@ class getID3 if ($this->option_tags_html) { foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { + if ($tag_key == 'picture') { + // Do not to try to convert binary picture data to HTML + // https://github.com/JamesHeinrich/getID3/issues/178 + continue; + } $this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']); } } @@ -1254,8 +1465,7 @@ class getID3 } - // pictures can take up a lot of space, and we don't need multiple copies of them - // let there be a single copy in [comments][picture], and not elsewhere + // pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere if (!empty($this->info['tags'])) { $unset_keys = array('tags', 'tags_html'); foreach ($this->info['tags'] as $tagtype => $tagarray) { @@ -1301,6 +1511,11 @@ class getID3 return true; } + /** + * @param string $algorithm + * + * @return array|bool + */ public function getHashdata($algorithm) { switch ($algorithm) { case 'md5': @@ -1365,7 +1580,6 @@ class getID3 } else { - $commandline = 'vorbiscomment -w -c "'.$empty.'" "'.$file.'" "'.$temp.'" 2>&1'; $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1'; $VorbisCommentError = `$commandline`; @@ -1424,7 +1638,6 @@ class getID3 return true; } - public function ChannelsBitratePlaytimeCalculations() { // set channelmode on audio @@ -1489,7 +1702,9 @@ class getID3 } } - + /** + * @return bool + */ public function CalculateCompressionRatioVideo() { if (empty($this->info['video'])) { return false; @@ -1537,7 +1752,9 @@ class getID3 return true; } - + /** + * @return bool + */ public function CalculateCompressionRatioAudio() { if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) { return false; @@ -1554,11 +1771,13 @@ class getID3 return true; } - + /** + * @return bool + */ public function CalculateReplayGain() { if (isset($this->info['replay_gain'])) { if (!isset($this->info['replay_gain']['reference_volume'])) { - $this->info['replay_gain']['reference_volume'] = (double) 89.0; + $this->info['replay_gain']['reference_volume'] = 89.0; } if (isset($this->info['replay_gain']['track']['adjustment'])) { $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; @@ -1577,6 +1796,9 @@ class getID3 return true; } + /** + * @return bool + */ public function ProcessAudioStreams() { if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { if (!isset($this->info['audio']['streams'])) { @@ -1590,10 +1812,20 @@ class getID3 return true; } + /** + * @return string|bool + */ public function getid3_tempnam() { return tempnam($this->tempdir, 'gI3'); } + /** + * @param string $name + * + * @return bool + * + * @throws getid3_exception + */ public function include_module($name) { //if (!file_exists($this->include_path.'module.'.$name.'.php')) { if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { @@ -1603,35 +1835,70 @@ class getID3 return true; } - public static function is_writable ($filename) { - $ret = is_writable($filename); - - if (!$ret) { - $perms = fileperms($filename); - $ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002); - } - - return $ret; - } + /** + * @param string $filename + * + * @return bool + */ + public static function is_writable ($filename) { + $ret = is_writable($filename); + if (!$ret) { + $perms = fileperms($filename); + $ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002); + } + return $ret; + } } -abstract class getid3_handler { +abstract class getid3_handler +{ /** * @var getID3 */ protected $getid3; // pointer - protected $data_string_flag = false; // analyzing filepointer or string - protected $data_string = ''; // string to analyze - protected $data_string_position = 0; // seek position in string - protected $data_string_length = 0; // string length + /** + * Analyzing filepointer or string. + * + * @var bool + */ + protected $data_string_flag = false; - private $dependency_to = null; + /** + * String to analyze. + * + * @var string + */ + protected $data_string = ''; + /** + * Seek position in string. + * + * @var int + */ + protected $data_string_position = 0; + /** + * String length. + * + * @var int + */ + protected $data_string_length = 0; + + /** + * @var string + */ + private $dependency_to; + + /** + * getid3_handler constructor. + * + * @param getID3 $getid3 + * @param string $call_module + */ public function __construct(getID3 $getid3, $call_module=null) { $this->getid3 = $getid3; @@ -1640,12 +1907,18 @@ abstract class getid3_handler { } } - - // Analyze from file pointer + /** + * Analyze from file pointer. + * + * @return bool + */ abstract public function Analyze(); - - // Analyze from string instead + /** + * Analyze from string instead. + * + * @param string $string + */ public function AnalyzeString($string) { // Enter string mode $this->setStringMode($string); @@ -1671,12 +1944,18 @@ abstract class getid3_handler { $this->data_string_flag = false; } + /** + * @param string $string + */ public function setStringMode($string) { $this->data_string_flag = true; $this->data_string = $string; $this->data_string_length = strlen($string); } + /** + * @return int|bool + */ protected function ftell() { if ($this->data_string_flag) { return $this->data_string_position; @@ -1684,6 +1963,13 @@ abstract class getid3_handler { return ftell($this->getid3->fp); } + /** + * @param int $bytes + * + * @return string|false + * + * @throws getid3_exception + */ protected function fread($bytes) { if ($this->data_string_flag) { $this->data_string_position += $bytes; @@ -1696,7 +1982,7 @@ abstract class getid3_handler { //return fread($this->getid3->fp, $bytes); /* - * http://www.getid3.org/phpBB3/viewtopic.php?t=1930 + * https://www.getid3.org/phpBB3/viewtopic.php?t=1930 * "I found out that the root cause for the problem was how getID3 uses the PHP system function fread(). * It seems to assume that fread() would always return as many bytes as were requested. * However, according the PHP manual (http://php.net/manual/en/function.fread.php), this is the case only with regular local files, but not e.g. with Linux pipes. @@ -1704,6 +1990,10 @@ abstract class getid3_handler { */ $contents = ''; do { + //if (($this->getid3->memory_limit > 0) && ($bytes > $this->getid3->memory_limit)) { + if (($this->getid3->memory_limit > 0) && (($bytes / $this->getid3->memory_limit) > 0.99)) { // enable a more-fuzzy match to prevent close misses generating errors like "PHP Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 33554464 bytes)" + throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') that is more than available PHP memory ('.$this->getid3->memory_limit.')', 10); + } $part = fread($this->getid3->fp, $bytes); $partLength = strlen($part); $bytes -= $partLength; @@ -1712,6 +2002,14 @@ abstract class getid3_handler { return $contents; } + /** + * @param int $bytes + * @param int $whence + * + * @return int + * + * @throws getid3_exception + */ protected function fseek($bytes, $whence=SEEK_SET) { if ($this->data_string_flag) { switch ($whence) { @@ -1742,6 +2040,9 @@ abstract class getid3_handler { return fseek($this->getid3->fp, $bytes, $whence); } + /** + * @return bool + */ protected function feof() { if ($this->data_string_flag) { return $this->data_string_position >= $this->data_string_length; @@ -1749,24 +2050,53 @@ abstract class getid3_handler { return feof($this->getid3->fp); } + /** + * @param string $module + * + * @return bool + */ final protected function isDependencyFor($module) { return $this->dependency_to == $module; } + /** + * @param string $text + * + * @return bool + */ protected function error($text) { $this->getid3->info['error'][] = $text; return false; } + /** + * @param string $text + * + * @return bool + */ protected function warning($text) { return $this->getid3->warning($text); } + /** + * @param string $text + */ protected function notice($text) { // does nothing for now } + /** + * @param string $name + * @param int $offset + * @param int $length + * @param string $image_mime + * + * @return string|null + * + * @throws Exception + * @throws getid3_exception + */ public function saveAttachment($name, $offset, $length, $image_mime=null) { try { @@ -1820,6 +2150,9 @@ abstract class getid3_handler { // close and remove dest file if created if (isset($fp_dest) && is_resource($fp_dest)) { fclose($fp_dest); + } + + if (isset($dest) && file_exists($dest)) { unlink($dest); } diff --git a/wp-includes/ID3/license.txt b/wp-includes/ID3/license.txt index 205c7e61e3..c67873ae69 100644 --- a/wp-includes/ID3/license.txt +++ b/wp-includes/ID3/license.txt @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////// /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // -// or http://www.getid3.org // +// or https://www.getid3.org // // also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// @@ -18,9 +18,9 @@ GNU GPL: https://gnu.org/licenses/gpl.html (v3) GNU LGPL: https://gnu.org/licenses/lgpl.html (v3) -Mozilla MPL: http://www.mozilla.org/MPL/2.0/ (v2) +Mozilla MPL: https://www.mozilla.org/MPL/2.0/ (v2) -getID3 Commercial License: http://getid3.org/#gCL (payment required) +getID3 Commercial License: https://www.getid3.org/#gCL (payment required) ***************************************************************** ***************************************************************** diff --git a/wp-includes/ID3/module.audio-video.asf.php b/wp-includes/ID3/module.audio-video.asf.php index 23d3a0e680..0a2132dd1d 100644 --- a/wp-includes/ID3/module.audio-video.asf.php +++ b/wp-includes/ID3/module.audio-video.asf.php @@ -1,11 +1,10 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.audio-video.asf.php // @@ -16,8 +15,11 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); -class getid3_asf extends getid3_handler { - +class getid3_asf extends getid3_handler +{ + /** + * @param getID3 $getid3 + */ public function __construct(getID3 $getid3) { parent::__construct($getid3); // extends getid3_handler::__construct() @@ -30,6 +32,9 @@ class getid3_asf extends getid3_handler { } } + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -83,6 +88,8 @@ class getid3_asf extends getid3_handler { $NextObjectOffset = $this->ftell(); $ASFHeaderData = $this->fread($thisfile_asf_headerobject['objectsize'] - 30); $offset = 0; + $thisfile_asf_streambitratepropertiesobject = array(); + $thisfile_asf_codeclistobject = array(); for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) { $NextObjectGUID = substr($ASFHeaderData, $offset, 16); @@ -790,17 +797,17 @@ class getid3_asf extends getid3_handler { case 'wm/tracknumber': case 'tracknumber': // be careful casting to int: casting unicode strings to int gives unexpected results (stops parsing at first non-numeric character) - $thisfile_asf_comments['track'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); - foreach ($thisfile_asf_comments['track'] as $key => $value) { + $thisfile_asf_comments['track_number'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + foreach ($thisfile_asf_comments['track_number'] as $key => $value) { if (preg_match('/^[0-9\x00]+$/', $value)) { - $thisfile_asf_comments['track'][$key] = intval(str_replace("\x00", '', $value)); + $thisfile_asf_comments['track_number'][$key] = intval(str_replace("\x00", '', $value)); } } break; case 'wm/track': - if (empty($thisfile_asf_comments['track'])) { - $thisfile_asf_comments['track'] = array(1 + $this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + if (empty($thisfile_asf_comments['track_number'])) { + $thisfile_asf_comments['track_number'] = array(1 + $this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); } break; @@ -970,18 +977,18 @@ class getid3_asf extends getid3_handler { break; } } - if (isset($thisfile_asf_streambitrateproperties['bitrate_records_count'])) { + if (isset($thisfile_asf_streambitratepropertiesobject['bitrate_records_count'])) { $ASFbitrateAudio = 0; $ASFbitrateVideo = 0; - for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < $thisfile_asf_streambitrateproperties['bitrate_records_count']; $BitrateRecordsCounter++) { + for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < $thisfile_asf_streambitratepropertiesobject['bitrate_records_count']; $BitrateRecordsCounter++) { if (isset($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter])) { switch ($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter]['type_raw']) { case 1: - $ASFbitrateVideo += $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate']; + $ASFbitrateVideo += $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['bitrate']; break; case 2: - $ASFbitrateAudio += $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate']; + $ASFbitrateAudio += $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['bitrate']; break; default: @@ -1440,6 +1447,11 @@ class getid3_asf extends getid3_handler { return true; } + /** + * @param int $CodecListType + * + * @return string + */ public static function codecListObjectTypeLookup($CodecListType) { static $lookup = array( 0x0001 => 'Video Codec', @@ -1450,6 +1462,9 @@ class getid3_asf extends getid3_handler { return (isset($lookup[$CodecListType]) ? $lookup[$CodecListType] : 'Invalid Codec Type'); } + /** + * @return array + */ public static function KnownGUIDs() { static $GUIDarray = array( 'GETID3_ASF_Extended_Stream_Properties_Object' => '14E6A5CB-C672-4332-8399-A96952065B5A', @@ -1564,6 +1579,11 @@ class getid3_asf extends getid3_handler { return $GUIDarray; } + /** + * @param string $GUIDstring + * + * @return string|false + */ public static function GUIDname($GUIDstring) { static $GUIDarray = array(); if (empty($GUIDarray)) { @@ -1572,6 +1592,11 @@ class getid3_asf extends getid3_handler { return array_search($GUIDstring, $GUIDarray); } + /** + * @param int $id + * + * @return string + */ public static function ASFIndexObjectIndexTypeLookup($id) { static $ASFIndexObjectIndexTypeLookup = array(); if (empty($ASFIndexObjectIndexTypeLookup)) { @@ -1582,6 +1607,11 @@ class getid3_asf extends getid3_handler { return (isset($ASFIndexObjectIndexTypeLookup[$id]) ? $ASFIndexObjectIndexTypeLookup[$id] : 'invalid'); } + /** + * @param string $GUIDstring + * + * @return string + */ public static function GUIDtoBytestring($GUIDstring) { // Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way: // first 4 bytes are in little-endian order @@ -1617,31 +1647,42 @@ class getid3_asf extends getid3_handler { return $hexbytecharstring; } + /** + * @param string $Bytestring + * + * @return string + */ public static function BytestringToGUID($Bytestring) { - $GUIDstring = str_pad(dechex(ord($Bytestring{3})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{2})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{1})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{0})), 2, '0', STR_PAD_LEFT); + $GUIDstring = str_pad(dechex(ord($Bytestring[3])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[2])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[1])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[0])), 2, '0', STR_PAD_LEFT); $GUIDstring .= '-'; - $GUIDstring .= str_pad(dechex(ord($Bytestring{5})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{4})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[5])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[4])), 2, '0', STR_PAD_LEFT); $GUIDstring .= '-'; - $GUIDstring .= str_pad(dechex(ord($Bytestring{7})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{6})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[7])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[6])), 2, '0', STR_PAD_LEFT); $GUIDstring .= '-'; - $GUIDstring .= str_pad(dechex(ord($Bytestring{8})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{9})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[8])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[9])), 2, '0', STR_PAD_LEFT); $GUIDstring .= '-'; - $GUIDstring .= str_pad(dechex(ord($Bytestring{10})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{11})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{12})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{13})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{14})), 2, '0', STR_PAD_LEFT); - $GUIDstring .= str_pad(dechex(ord($Bytestring{15})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[10])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[11])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[12])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[13])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[14])), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring[15])), 2, '0', STR_PAD_LEFT); return strtoupper($GUIDstring); } + /** + * @param int $FILETIME + * @param bool $round + * + * @return float|int + */ public static function FILETIMEtoUNIXtime($FILETIME, $round=true) { // FILETIME is a 64-bit unsigned integer representing // the number of 100-nanosecond intervals since January 1, 1601 @@ -1653,6 +1694,11 @@ class getid3_asf extends getid3_handler { return ($FILETIME - 116444736000000000) / 10000000; } + /** + * @param int $WMpictureType + * + * @return string + */ public static function WMpictureTypeLookup($WMpictureType) { static $lookup = null; if ($lookup === null) { @@ -1684,6 +1730,12 @@ class getid3_asf extends getid3_handler { return (isset($lookup[$WMpictureType]) ? $lookup[$WMpictureType] : ''); } + /** + * @param string $asf_header_extension_object_data + * @param int $unhandled_sections + * + * @return array + */ public function HeaderExtensionObjectDataParse(&$asf_header_extension_object_data, &$unhandled_sections) { // http://msdn.microsoft.com/en-us/library/bb643323.aspx @@ -1930,7 +1982,11 @@ class getid3_asf extends getid3_handler { return $HeaderExtensionObjectParsed; } - + /** + * @param int $id + * + * @return string + */ public static function metadataLibraryObjectDataTypeLookup($id) { static $lookup = array( 0x0000 => 'Unicode string', // The data consists of a sequence of Unicode characters @@ -1944,6 +2000,11 @@ class getid3_asf extends getid3_handler { return (isset($lookup[$id]) ? $lookup[$id] : 'invalid'); } + /** + * @param string $data + * + * @return array + */ public function ASF_WMpicture(&$data) { //typedef struct _WMPicture{ // LPWSTR pwszMIMEType; @@ -1994,14 +2055,24 @@ class getid3_asf extends getid3_handler { return $WMpicture; } - - // Remove terminator 00 00 and convert UTF-16LE to Latin-1 + /** + * Remove terminator 00 00 and convert UTF-16LE to Latin-1. + * + * @param string $string + * + * @return string + */ public static function TrimConvert($string) { return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', self::TrimTerm($string)), ' '); } - - // Remove terminator 00 00 + /** + * Remove terminator 00 00. + * + * @param string $string + * + * @return string + */ public static function TrimTerm($string) { // remove terminator, only if present (it should be, but...) if (substr($string, -2) === "\x00\x00") { diff --git a/wp-includes/ID3/module.audio-video.flv.php b/wp-includes/ID3/module.audio-video.flv.php index 661c77ca12..eb292c6136 100644 --- a/wp-includes/ID3/module.audio-video.flv.php +++ b/wp-includes/ID3/module.audio-video.flv.php @@ -1,15 +1,22 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.flv.php // +// module for analyzing Shockwave Flash Video files // +// dependencies: NONE // +// // +///////////////////////////////////////////////////////////////// // // // FLV module by Seth Kaufman // // // // * version 0.1 (26 June 2005) // // // -// // // * version 0.1.1 (15 July 2005) // // minor modifications by James Heinrich // // // @@ -43,12 +50,6 @@ // handle GETID3_FLV_VIDEO_VP6FLV_ALPHA // // improved AVCSequenceParameterSetReader::readData() // // by Xander Schouwerwou // -// // -///////////////////////////////////////////////////////////////// -// // -// module.audio-video.flv.php // -// module for analyzing Shockwave Flash Video files // -// dependencies: NONE // // /// ///////////////////////////////////////////////////////////////// @@ -73,12 +74,21 @@ define('H264_PROFILE_HIGH422', 122); define('H264_PROFILE_HIGH444', 144); define('H264_PROFILE_HIGH444_PREDICTIVE', 244); -class getid3_flv extends getid3_handler { - +class getid3_flv extends getid3_handler +{ const magic = 'FLV'; - 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 + /** + * Break out of the loop if too many frames have been scanned; only scan this + * many if meta frame does not contain useful duration. + * + * @var int + */ + public $max_frames = 100000; + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -332,7 +342,11 @@ class getid3_flv extends getid3_handler { return true; } - + /** + * @param int $id + * + * @return string|false + */ public static function audioFormatLookup($id) { static $lookup = array( 0 => 'Linear PCM, platform endian', @@ -355,6 +369,11 @@ class getid3_flv extends getid3_handler { return (isset($lookup[$id]) ? $lookup[$id] : false); } + /** + * @param int $id + * + * @return int|false + */ public static function audioRateLookup($id) { static $lookup = array( 0 => 5500, @@ -365,6 +384,11 @@ class getid3_flv extends getid3_handler { return (isset($lookup[$id]) ? $lookup[$id] : false); } + /** + * @param int $id + * + * @return int|false + */ public static function audioBitDepthLookup($id) { static $lookup = array( 0 => 8, @@ -373,6 +397,11 @@ class getid3_flv extends getid3_handler { return (isset($lookup[$id]) ? $lookup[$id] : false); } + /** + * @param int $id + * + * @return string|false + */ public static function videoCodecLookup($id) { static $lookup = array( GETID3_FLV_VIDEO_H263 => 'Sorenson H.263', @@ -386,47 +415,84 @@ class getid3_flv extends getid3_handler { } } -class AMFStream { +class AMFStream +{ + /** + * @var string + */ public $bytes; + + /** + * @var int + */ public $pos; + /** + * @param string $bytes + */ public function __construct(&$bytes) { $this->bytes =& $bytes; $this->pos = 0; } - public function readByte() { - return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1)); + /** + * @return int + */ + public function readByte() { // 8-bit + return ord(substr($this->bytes, $this->pos++, 1)); } - public function readInt() { + /** + * @return int + */ + public function readInt() { // 16-bit return ($this->readByte() << 8) + $this->readByte(); } - public function readLong() { + /** + * @return int + */ + public function readLong() { // 32-bit return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte(); } + /** + * @return float|false + */ public function readDouble() { return getid3_lib::BigEndian2Float($this->read(8)); } + /** + * @return string + */ public function readUTF() { $length = $this->readInt(); return $this->read($length); } + /** + * @return string + */ public function readLongUTF() { $length = $this->readLong(); return $this->read($length); } + /** + * @param int $length + * + * @return string + */ public function read($length) { $val = substr($this->bytes, $this->pos, $length); $this->pos += $length; return $val; } + /** + * @return int + */ public function peekByte() { $pos = $this->pos; $val = $this->readByte(); @@ -434,6 +500,9 @@ class AMFStream { return $val; } + /** + * @return int + */ public function peekInt() { $pos = $this->pos; $val = $this->readInt(); @@ -441,6 +510,9 @@ class AMFStream { return $val; } + /** + * @return int + */ public function peekLong() { $pos = $this->pos; $val = $this->readLong(); @@ -448,6 +520,9 @@ class AMFStream { return $val; } + /** + * @return float|false + */ public function peekDouble() { $pos = $this->pos; $val = $this->readDouble(); @@ -455,6 +530,9 @@ class AMFStream { return $val; } + /** + * @return string + */ public function peekUTF() { $pos = $this->pos; $val = $this->readUTF(); @@ -462,6 +540,9 @@ class AMFStream { return $val; } + /** + * @return string + */ public function peekLongUTF() { $pos = $this->pos; $val = $this->readLongUTF(); @@ -470,13 +551,23 @@ class AMFStream { } } -class AMFReader { +class AMFReader +{ + /** + * @var AMFStream + */ public $stream; - public function __construct(&$stream) { - $this->stream =& $stream; + /** + * @param AMFStream $stream + */ + public function __construct(AMFStream $stream) { + $this->stream = $stream; } + /** + * @return mixed + */ public function readData() { $value = null; @@ -547,23 +638,36 @@ class AMFReader { return $value; } + /** + * @return float|false + */ public function readDouble() { return $this->stream->readDouble(); } + /** + * @return bool + */ public function readBoolean() { return $this->stream->readByte() == 1; } + /** + * @return string + */ public function readString() { return $this->stream->readUTF(); } + /** + * @return array + */ public function readObject() { // Get highest numerical index - ignored // $highestIndex = $this->stream->readLong(); $data = array(); + $key = null; while ($key = $this->stream->readUTF()) { $data[$key] = $this->readData(); @@ -576,15 +680,19 @@ class AMFReader { return $data; } + /** + * @return array + */ public function readMixedArray() { // Get highest numerical index - ignored $highestIndex = $this->stream->readLong(); $data = array(); + $key = null; while ($key = $this->stream->readUTF()) { if (is_numeric($key)) { - $key = (float) $key; + $key = (int) $key; } $data[$key] = $this->readData(); } @@ -597,6 +705,9 @@ class AMFReader { return $data; } + /** + * @return array + */ public function readArray() { $length = $this->stream->readLong(); $data = array(); @@ -607,34 +718,61 @@ class AMFReader { return $data; } + /** + * @return float|false + */ public function readDate() { $timestamp = $this->stream->readDouble(); $timezone = $this->stream->readInt(); return $timestamp; } + /** + * @return string + */ public function readLongString() { return $this->stream->readLongUTF(); } + /** + * @return string + */ public function readXML() { return $this->stream->readLongUTF(); } + /** + * @return array + */ public function readTypedObject() { $className = $this->stream->readUTF(); return $this->readObject(); } } -class AVCSequenceParameterSetReader { +class AVCSequenceParameterSetReader +{ + /** + * @var string + */ public $sps; public $start = 0; public $currentBytes = 0; public $currentBits = 0; + + /** + * @var int + */ public $width; + + /** + * @var int + */ public $height; + /** + * @param string $sps + */ public function __construct($sps) { $this->sps = $sps; } @@ -691,18 +829,29 @@ class AVCSequenceParameterSetReader { } } + /** + * @param int $bits + */ public function skipBits($bits) { $newBits = $this->currentBits + $bits; $this->currentBytes += (int)floor($newBits / 8); $this->currentBits = $newBits % 8; } + /** + * @return int + */ public function getBit() { $result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01; $this->skipBits(1); return $result; } + /** + * @param int $bits + * + * @return int + */ public function getBits($bits) { $result = 0; for ($i = 0; $i < $bits; $i++) { @@ -711,6 +860,9 @@ class AVCSequenceParameterSetReader { return $result; } + /** + * @return int + */ public function expGolombUe() { $significantBits = 0; $bit = $this->getBit(); @@ -726,6 +878,9 @@ class AVCSequenceParameterSetReader { return (1 << $significantBits) + $this->getBits($significantBits) - 1; } + /** + * @return int + */ public function expGolombSe() { $result = $this->expGolombUe(); if (($result & 0x01) == 0) { @@ -735,10 +890,16 @@ class AVCSequenceParameterSetReader { } } + /** + * @return int + */ public function getWidth() { return $this->width; } + /** + * @return int + */ public function getHeight() { return $this->height; } diff --git a/wp-includes/ID3/module.audio-video.matroska.php b/wp-includes/ID3/module.audio-video.matroska.php index 825a22e192..b2b187b6f1 100644 --- a/wp-includes/ID3/module.audio-video.matroska.php +++ b/wp-includes/ID3/module.audio-video.matroska.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.audio-video.matriska.php // @@ -72,7 +72,7 @@ define('EBML_ID_FILEREFERRAL', 0x0675); // [46][75] -- define('EBML_ID_FILEDESCRIPTION', 0x067E); // [46][7E] -- A human-friendly name for the attached file. define('EBML_ID_FILEUID', 0x06AE); // [46][AE] -- Unique ID representing the file, as random as possible. 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: -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. +define('EBML_ID_CONTENTENCKEYID', 0x07E2); // [47][E2] -- For public key algorithms this is the ID of the public key the data was encrypted with. define('EBML_ID_CONTENTSIGNATURE', 0x07E3); // [47][E3] -- A cryptographic signature of the contents. define('EBML_ID_CONTENTSIGKEYID', 0x07E4); // [47][E4] -- This is the ID of the private key the data was signed with. 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: @@ -215,17 +215,33 @@ define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- */ class getid3_matroska extends getid3_handler { - // public options - 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] - public static $parse_whole_file = false; // true to parse the whole file, not only header [default: FALSE] + /** + * If true, do not return information about CLUSTER chunks, since there's a lot of them + * and they're not usually useful [default: TRUE]. + * + * @var bool + */ + public static $hide_clusters = true; - // private parser settings/placeholders + /** + * True to parse the whole file, not only header [default: FALSE]. + * + * @var bool + */ + public static $parse_whole_file = false; + + /* + * Private parser settings/placeholders. + */ private $EBMLbuffer = ''; private $EBMLbuffer_offset = 0; private $EBMLbuffer_length = 0; private $current_offset = 0; private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID); + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -366,8 +382,8 @@ class getid3_matroska extends getid3_handler if (!empty($getid3_temp->info[$header_data_key])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { - foreach ($getid3_temp->info['audio'] as $key => $value) { - $track_info[$key] = $value; + foreach ($getid3_temp->info['audio'] as $sub_key => $value) { + $track_info[$sub_key] = $value; } } } @@ -421,8 +437,8 @@ class getid3_matroska extends getid3_handler if (!empty($getid3_temp->info['ogg'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg']; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { - foreach ($getid3_temp->info['audio'] as $key => $value) { - $track_info[$key] = $value; + foreach ($getid3_temp->info['audio'] as $sub_key => $value) { + $track_info[$sub_key] = $value; } } } @@ -449,9 +465,9 @@ class getid3_matroska extends getid3_handler getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); $parsed = getid3_riff::parseWAVEFORMATex($trackarray['CodecPrivate']); - foreach ($parsed as $key => $value) { - if ($key != 'raw') { - $track_info[$key] = $value; + foreach ($parsed as $sub_key => $value) { + if ($sub_key != 'raw') { + $track_info[$sub_key] = $value; } } $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; @@ -496,6 +512,9 @@ class getid3_matroska extends getid3_handler return true; } + /** + * @param array $info + */ private function parseEBML(&$info) { // http://www.matroska.org/technical/specs/index.html#EBMLBasics $this->current_offset = $info['avdataoffset']; @@ -1228,6 +1247,11 @@ class getid3_matroska extends getid3_handler } } + /** + * @param int $min_data + * + * @return bool + */ private function EnsureBufferHasEnoughData($min_data=1024) { if (($this->current_offset - $this->EBMLbuffer_offset) >= ($this->EBMLbuffer_length - $min_data)) { $read_bytes = max($min_data, $this->getid3->fread_buffer_size()); @@ -1249,6 +1273,9 @@ class getid3_matroska extends getid3_handler return true; } + /** + * @return int|float|false + */ private function readEBMLint() { $actual_offset = $this->current_offset - $this->EBMLbuffer_offset; @@ -1281,6 +1308,12 @@ class getid3_matroska extends getid3_handler return $int_value; } + /** + * @param int $length + * @param bool $check_buffer + * + * @return string|false + */ private function readEBMLelementData($length, $check_buffer=false) { if ($check_buffer && !$this->EnsureBufferHasEnoughData($length)) { return false; @@ -1290,6 +1323,13 @@ class getid3_matroska extends getid3_handler return $data; } + /** + * @param array $element + * @param int $parent_end + * @param array|bool $get_data + * + * @return bool + */ private function getEBMLelement(&$element, $parent_end, $get_data=false) { if ($this->current_offset >= $parent_end) { return false; @@ -1326,6 +1366,11 @@ class getid3_matroska extends getid3_handler return true; } + /** + * @param string $type + * @param int $line + * @param array $element + */ private function unhandledElement($type, $line, $element) { // warn only about unknown and missed elements, not about unuseful if (!in_array($element['id'], $this->unuseful_elements)) { @@ -1338,6 +1383,11 @@ class getid3_matroska extends getid3_handler } } + /** + * @param array $SimpleTagArray + * + * @return bool + */ private function ExtractCommentsSimpleTag($SimpleTagArray) { if (!empty($SimpleTagArray['SimpleTag'])) { foreach ($SimpleTagArray['SimpleTag'] as $SimpleTagKey => $SimpleTagData) { @@ -1353,6 +1403,11 @@ class getid3_matroska extends getid3_handler return true; } + /** + * @param int $parent_end + * + * @return array + */ private function HandleEMBLSimpleTag($parent_end) { $simpletag_entry = array(); @@ -1383,6 +1438,13 @@ class getid3_matroska extends getid3_handler return $simpletag_entry; } + /** + * @param array $element + * @param int $block_type + * @param array $info + * + * @return array + */ private function HandleEMBLClusterBlock($element, $block_type, &$info) { // http://www.matroska.org/technical/specs/index.html#block_structure // http://www.matroska.org/technical/specs/index.html#simpleblock_structure @@ -1446,6 +1508,11 @@ class getid3_matroska extends getid3_handler return $block_data; } + /** + * @param string $EBMLstring + * + * @return int|float|false + */ private static function EBML2Int($EBMLstring) { // http://matroska.org/specs/ @@ -1488,12 +1555,22 @@ class getid3_matroska extends getid3_handler return getid3_lib::BigEndian2Int($EBMLstring); } + /** + * @param int $EBMLdatestamp + * + * @return float + */ private static function EBMLdate2unix($EBMLdatestamp) { // Date - signed 8 octets integer in nanoseconds with 0 indicating the precise beginning of the millennium (at 2001-01-01T00:00:00,000000000 UTC) // 978307200 == mktime(0, 0, 0, 1, 1, 2001) == January 1, 2001 12:00:00am UTC return round(($EBMLdatestamp / 1000000000) + 978307200); } + /** + * @param int $target_type + * + * @return string|int + */ public static function TargetTypeValue($target_type) { // http://www.matroska.org/technical/specs/tagging/index.html static $TargetTypeValue = array(); @@ -1509,6 +1586,11 @@ class getid3_matroska extends getid3_handler return (isset($TargetTypeValue[$target_type]) ? $TargetTypeValue[$target_type] : $target_type); } + /** + * @param int $lacingtype + * + * @return string|int + */ public static function BlockLacingType($lacingtype) { // http://matroska.org/technical/specs/index.html#block_structure static $BlockLacingType = array(); @@ -1521,6 +1603,11 @@ class getid3_matroska extends getid3_handler return (isset($BlockLacingType[$lacingtype]) ? $BlockLacingType[$lacingtype] : $lacingtype); } + /** + * @param string $codecid + * + * @return string + */ public static function CodecIDtoCommonName($codecid) { // http://www.matroska.org/technical/specs/codecid/index.html static $CodecIDlist = array(); @@ -1557,6 +1644,11 @@ class getid3_matroska extends getid3_handler return (isset($CodecIDlist[$codecid]) ? $CodecIDlist[$codecid] : $codecid); } + /** + * @param int $value + * + * @return string + */ private static function EBMLidName($value) { static $EBMLidList = array(); if (empty($EBMLidList)) { @@ -1755,6 +1847,11 @@ class getid3_matroska extends getid3_handler return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value)); } + /** + * @param int $value + * + * @return string + */ public static function displayUnit($value) { // http://www.matroska.org/technical/specs/index.html#DisplayUnit static $units = array( @@ -1766,8 +1863,14 @@ class getid3_matroska extends getid3_handler return (isset($units[$value]) ? $units[$value] : 'unknown'); } + /** + * @param array $streams + * + * @return array + */ private static function getDefaultStreamInfo($streams) { + $stream = array(); foreach (array_reverse($streams) as $stream) { if ($stream['default']) { break; diff --git a/wp-includes/ID3/module.audio-video.quicktime.php b/wp-includes/ID3/module.audio-video.quicktime.php index 6f29ca4e87..cb1173bdbd 100644 --- a/wp-includes/ID3/module.audio-video.quicktime.php +++ b/wp-includes/ID3/module.audio-video.quicktime.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.audio-video.quicktime.php // @@ -24,6 +24,9 @@ class getid3_quicktime extends getid3_handler public $ReturnAtomData = true; public $ParseAllPossibleAtoms = false; + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -35,7 +38,7 @@ class getid3_quicktime extends getid3_handler $offset = 0; $atomcounter = 0; - $atom_data_read_buffer_size = max($this->getid3->option_fread_buffer_size * 1024, ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 4) : 1024)); // set read buffer to 25% of PHP memory limit (if one is specified), otherwise use option_fread_buffer_size [default: 32MB] + $atom_data_read_buffer_size = $info['php_memory_limit'] ? round($info['php_memory_limit'] / 4) : $this->getid3->option_fread_buffer_size * 1024; // set read buffer to 25% of PHP memory limit (if one is specified), otherwise use option_fread_buffer_size [default: 32MB] while ($offset < $info['avdataend']) { if (!getid3_lib::intValueSupported($offset)) { $this->error('Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'); @@ -162,6 +165,9 @@ class getid3_quicktime extends getid3_handler if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) { $info['audio']['bitrate'] = $info['bitrate']; } + if (!empty($info['bitrate']) && !empty($info['audio']['bitrate']) && empty($info['video']['bitrate']) && !empty($info['video']['frame_rate']) && !empty($info['video']['resolution_x']) && ($info['bitrate'] > $info['audio']['bitrate'])) { + $info['video']['bitrate'] = $info['bitrate'] - $info['audio']['bitrate']; + } if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) { foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) { $samples_per_second = $samples_count / $info['playtime_seconds']; @@ -193,318 +199,353 @@ class getid3_quicktime extends getid3_handler if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) { $info['video']['dataformat'] = 'quicktime'; } + if (isset($info['video']) && ($info['mime_type'] == 'audio/mp4') && empty($info['video']['resolution_x']) && empty($info['video']['resolution_y'])) { + unset($info['video']); + } return true; } + /** + * @param string $atomname + * @param int $atomsize + * @param string $atom_data + * @param int $baseoffset + * @param array $atomHierarchy + * @param bool $ParseAllPossibleAtoms + * + * @return array|false + */ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm // https://code.google.com/p/mp4v2/wiki/iTunesMetadata $info = &$this->getid3->info; - $atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see http://www.getid3.org/phpBB3/viewtopic.php?t=1717 + $atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see https://www.getid3.org/phpBB3/viewtopic.php?t=1717 array_push($atomHierarchy, $atomname); $atom_structure['hierarchy'] = implode(' ', $atomHierarchy); $atom_structure['name'] = $atomname; $atom_structure['size'] = $atomsize; $atom_structure['offset'] = $baseoffset; - switch ($atomname) { - case 'moov': // MOVie container atom - case 'trak': // TRAcK container atom - case 'clip': // CLIPping container atom - case 'matt': // track MATTe container atom - case 'edts': // EDiTS container atom - case 'tref': // Track REFerence container atom - case 'mdia': // MeDIA container atom - case 'minf': // Media INFormation container atom - case 'dinf': // Data INFormation container atom - case 'udta': // User DaTA container atom - case 'cmov': // Compressed MOVie container atom - case 'rmra': // Reference Movie Record Atom - case 'rmda': // Reference Movie Descriptor Atom - case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) - $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); - break; + if (substr($atomname, 0, 3) == "\x00\x00\x00") { + // https://github.com/JamesHeinrich/getID3/issues/139 + $atomname = getid3_lib::BigEndian2Int($atomname); + $atom_structure['name'] = $atomname; + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + } else { + switch ($atomname) { + case 'moov': // MOVie container atom + case 'trak': // TRAcK container atom + case 'clip': // CLIPping container atom + case 'matt': // track MATTe container atom + case 'edts': // EDiTS container atom + case 'tref': // Track REFerence container atom + case 'mdia': // MeDIA container atom + case 'minf': // Media INFormation container atom + case 'dinf': // Data INFormation container atom + case 'udta': // User DaTA container atom + case 'cmov': // Compressed MOVie container atom + case 'rmra': // Reference Movie Record Atom + case 'rmda': // Reference Movie Descriptor Atom + case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; - case 'ilst': // Item LiST container atom - if ($atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms)) { - // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted - $allnumericnames = true; - foreach ($atom_structure['subatoms'] as $subatomarray) { - if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { - $allnumericnames = false; - break; - } - } - if ($allnumericnames) { - $newData = array(); + case 'ilst': // Item LiST container atom + if ($atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms)) { + // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted + $allnumericnames = true; foreach ($atom_structure['subatoms'] as $subatomarray) { - foreach ($subatomarray['subatoms'] as $newData_subatomarray) { - unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); - $newData[$subatomarray['name']] = $newData_subatomarray; + if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { + $allnumericnames = false; break; } } - $atom_structure['data'] = $newData; - unset($atom_structure['subatoms']); + if ($allnumericnames) { + $newData = array(); + foreach ($atom_structure['subatoms'] as $subatomarray) { + foreach ($subatomarray['subatoms'] as $newData_subatomarray) { + unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); + $newData[$subatomarray['name']] = $newData_subatomarray; + break; + } + } + $atom_structure['data'] = $newData; + unset($atom_structure['subatoms']); + } } - } - break; + break; - case "\x00\x00\x00\x01": - case "\x00\x00\x00\x02": - case "\x00\x00\x00\x03": - case "\x00\x00\x00\x04": - case "\x00\x00\x00\x05": - $atomname = getid3_lib::BigEndian2Int($atomname); - $atom_structure['name'] = $atomname; - $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); - break; - - case 'stbl': // Sample TaBLe container atom - $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); - $isVideo = false; - $framerate = 0; - $framecount = 0; - foreach ($atom_structure['subatoms'] as $key => $value_array) { - if (isset($value_array['sample_description_table'])) { - foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { - if (isset($value_array2['data_format'])) { - switch ($value_array2['data_format']) { - case 'avc1': - case 'mp4v': - // video data - $isVideo = true; - break; - case 'mp4a': - // audio data - break; + case 'stbl': // Sample TaBLe container atom + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + $isVideo = false; + $framerate = 0; + $framecount = 0; + foreach ($atom_structure['subatoms'] as $key => $value_array) { + if (isset($value_array['sample_description_table'])) { + foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { + if (isset($value_array2['data_format'])) { + switch ($value_array2['data_format']) { + case 'avc1': + case 'mp4v': + // video data + $isVideo = true; + break; + case 'mp4a': + // audio data + break; + } + } + } + } elseif (isset($value_array['time_to_sample_table'])) { + foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) { + if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) { + $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); + $framecount = $value_array2['sample_count']; } } } - } elseif (isset($value_array['time_to_sample_table'])) { - foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) { - if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) { - $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); - $framecount = $value_array2['sample_count']; - } - } } - } - if ($isVideo && $framerate) { - $info['quicktime']['video']['frame_rate'] = $framerate; - $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; - } - if ($isVideo && $framecount) { - $info['quicktime']['video']['frame_count'] = $framecount; - } - break; - - - case "\xA9".'alb': // ALBum - case "\xA9".'ART': // - case "\xA9".'art': // ARTist - case "\xA9".'aut': // - case "\xA9".'cmt': // CoMmenT - case "\xA9".'com': // COMposer - case "\xA9".'cpy': // - case "\xA9".'day': // content created year - case "\xA9".'dir': // - case "\xA9".'ed1': // - case "\xA9".'ed2': // - case "\xA9".'ed3': // - case "\xA9".'ed4': // - case "\xA9".'ed5': // - case "\xA9".'ed6': // - case "\xA9".'ed7': // - case "\xA9".'ed8': // - case "\xA9".'ed9': // - case "\xA9".'enc': // - case "\xA9".'fmt': // - case "\xA9".'gen': // GENre - case "\xA9".'grp': // GRouPing - case "\xA9".'hst': // - case "\xA9".'inf': // - case "\xA9".'lyr': // LYRics - case "\xA9".'mak': // - case "\xA9".'mod': // - case "\xA9".'nam': // full NAMe - case "\xA9".'ope': // - case "\xA9".'PRD': // - case "\xA9".'prf': // - case "\xA9".'req': // - case "\xA9".'src': // - case "\xA9".'swr': // - case "\xA9".'too': // encoder - case "\xA9".'trk': // TRacK - case "\xA9".'url': // - case "\xA9".'wrn': // - case "\xA9".'wrt': // WRiTer - case '----': // itunes specific - case 'aART': // Album ARTist - case 'akID': // iTunes store account type - case 'apID': // Purchase Account - case 'atID': // - case 'catg': // CaTeGory - case 'cmID': // - case 'cnID': // - case 'covr': // COVeR artwork - case 'cpil': // ComPILation - case 'cprt': // CoPyRighT - case 'desc': // DESCription - case 'disk': // DISK number - case 'egid': // Episode Global ID - case 'geID': // - case 'gnre': // GeNRE - case 'hdvd': // HD ViDeo - case 'keyw': // KEYWord - case 'ldes': // Long DEScription - case 'pcst': // PodCaST - case 'pgap': // GAPless Playback - case 'plID': // - case 'purd': // PURchase Date - case 'purl': // Podcast URL - case 'rati': // - case 'rndu': // - case 'rpdu': // - case 'rtng': // RaTiNG - case 'sfID': // iTunes store country - case 'soaa': // SOrt Album Artist - case 'soal': // SOrt ALbum - case 'soar': // SOrt ARtist - case 'soco': // SOrt COmposer - case 'sonm': // SOrt NaMe - case 'sosn': // SOrt Show Name - case 'stik': // - case 'tmpo': // TeMPO (BPM) - case 'trkn': // TRacK Number - case 'tven': // tvEpisodeID - case 'tves': // TV EpiSode - case 'tvnn': // TV Network Name - case 'tvsh': // TV SHow Name - case 'tvsn': // TV SeasoN - if ($atom_parent == 'udta') { - // User data atom handler - $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); - $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); - $atom_structure['data'] = substr($atom_data, 4); - - $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); - if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { - $info['comments']['language'][] = $atom_structure['language']; + if ($isVideo && $framerate) { + $info['quicktime']['video']['frame_rate'] = $framerate; + $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; } - } else { - // Apple item list box atom handler - $atomoffset = 0; - if (substr($atom_data, 2, 2) == "\x10\xB5") { - // not sure what it means, but observed on iPhone4 data. - // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data - while ($atomoffset < strlen($atom_data)) { - $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); - $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); - $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); - if ($boxsmallsize <= 1) { - $this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); - $atom_structure['data'] = null; - $atomoffset = strlen($atom_data); - break; - } - switch ($boxsmalltype) { - case "\x10\xB5": - $atom_structure['data'] = $boxsmalldata; - break; - default: - $this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset); - $atom_structure['data'] = $atom_data; - break; - } - $atomoffset += (4 + $boxsmallsize); + if ($isVideo && $framecount) { + $info['quicktime']['video']['frame_count'] = $framecount; + } + break; + + + case "\xA9".'alb': // ALBum + case "\xA9".'ART': // + case "\xA9".'art': // ARTist + case "\xA9".'aut': // + case "\xA9".'cmt': // CoMmenT + case "\xA9".'com': // COMposer + case "\xA9".'cpy': // + case "\xA9".'day': // content created year + case "\xA9".'dir': // + case "\xA9".'ed1': // + case "\xA9".'ed2': // + case "\xA9".'ed3': // + case "\xA9".'ed4': // + case "\xA9".'ed5': // + case "\xA9".'ed6': // + case "\xA9".'ed7': // + case "\xA9".'ed8': // + case "\xA9".'ed9': // + case "\xA9".'enc': // + case "\xA9".'fmt': // + case "\xA9".'gen': // GENre + case "\xA9".'grp': // GRouPing + case "\xA9".'hst': // + case "\xA9".'inf': // + case "\xA9".'lyr': // LYRics + case "\xA9".'mak': // + case "\xA9".'mod': // + case "\xA9".'nam': // full NAMe + case "\xA9".'ope': // + case "\xA9".'PRD': // + case "\xA9".'prf': // + case "\xA9".'req': // + case "\xA9".'src': // + case "\xA9".'swr': // + case "\xA9".'too': // encoder + case "\xA9".'trk': // TRacK + case "\xA9".'url': // + case "\xA9".'wrn': // + case "\xA9".'wrt': // WRiTer + case '----': // itunes specific + case 'aART': // Album ARTist + case 'akID': // iTunes store account type + case 'apID': // Purchase Account + case 'atID': // + case 'catg': // CaTeGory + case 'cmID': // + case 'cnID': // + case 'covr': // COVeR artwork + case 'cpil': // ComPILation + case 'cprt': // CoPyRighT + case 'desc': // DESCription + case 'disk': // DISK number + case 'egid': // Episode Global ID + case 'geID': // + case 'gnre': // GeNRE + case 'hdvd': // HD ViDeo + case 'keyw': // KEYWord + case 'ldes': // Long DEScription + case 'pcst': // PodCaST + case 'pgap': // GAPless Playback + case 'plID': // + case 'purd': // PURchase Date + case 'purl': // Podcast URL + case 'rati': // + case 'rndu': // + case 'rpdu': // + case 'rtng': // RaTiNG + case 'sfID': // iTunes store country + case 'soaa': // SOrt Album Artist + case 'soal': // SOrt ALbum + case 'soar': // SOrt ARtist + case 'soco': // SOrt COmposer + case 'sonm': // SOrt NaMe + case 'sosn': // SOrt Show Name + case 'stik': // + case 'tmpo': // TeMPO (BPM) + case 'trkn': // TRacK Number + case 'tven': // tvEpisodeID + case 'tves': // TV EpiSode + case 'tvnn': // TV Network Name + case 'tvsh': // TV SHow Name + case 'tvsn': // TV SeasoN + if ($atom_parent == 'udta') { + // User data atom handler + $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); + $atom_structure['data'] = substr($atom_data, 4); + + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; } } else { - while ($atomoffset < strlen($atom_data)) { - $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); - $boxtype = substr($atom_data, $atomoffset + 4, 4); - $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); - if ($boxsize <= 1) { - $this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); - $atom_structure['data'] = null; - $atomoffset = strlen($atom_data); - break; - } - $atomoffset += $boxsize; - - switch ($boxtype) { - case 'mean': - case 'name': - $atom_structure[$boxtype] = substr($boxdata, 4); + // Apple item list box atom handler + $atomoffset = 0; + if (substr($atom_data, 2, 2) == "\x10\xB5") { + // not sure what it means, but observed on iPhone4 data. + // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data + while ($atomoffset < strlen($atom_data)) { + $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); + $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); + $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); + if ($boxsmallsize <= 1) { + $this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); + $atom_structure['data'] = null; + $atomoffset = strlen($atom_data); break; + } + switch ($boxsmalltype) { + case "\x10\xB5": + $atom_structure['data'] = $boxsmalldata; + break; + default: + $this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset); + $atom_structure['data'] = $atom_data; + break; + } + $atomoffset += (4 + $boxsmallsize); + } + } else { + while ($atomoffset < strlen($atom_data)) { + $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); + $boxtype = substr($atom_data, $atomoffset + 4, 4); + $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); + if ($boxsize <= 1) { + $this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); + $atom_structure['data'] = null; + $atomoffset = strlen($atom_data); + break; + } + $atomoffset += $boxsize; - case 'data': - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); - switch ($atom_structure['flags_raw']) { - case 0: // data flag - case 21: // tmpo/cpil flag - switch ($atomname) { - case 'cpil': - case 'hdvd': - case 'pcst': - case 'pgap': - // 8-bit integer (boolean) - $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); - break; + switch ($boxtype) { + case 'mean': + case 'name': + $atom_structure[$boxtype] = substr($boxdata, 4); + break; - case 'tmpo': - // 16-bit integer - $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); - break; + case 'data': + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); + switch ($atom_structure['flags_raw']) { + case 0: // data flag + case 21: // tmpo/cpil flag + switch ($atomname) { + case 'cpil': + case 'hdvd': + case 'pcst': + case 'pgap': + // 8-bit integer (boolean) + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + break; - case 'disk': - case 'trkn': - // binary - $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); - $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); - $atom_structure['data'] = empty($num) ? '' : $num; - $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; - break; + case 'tmpo': + // 16-bit integer + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); + break; - case 'gnre': - // enum - $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); - $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); - break; + case 'disk': + case 'trkn': + // binary + $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); + $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); + $atom_structure['data'] = empty($num) ? '' : $num; + $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; + break; - case 'rtng': - // 8-bit integer - $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); - $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); - break; + case 'gnre': + // enum + $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); + break; - case 'stik': - // 8-bit integer (enum) - $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); - $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); - break; + case 'rtng': + // 8-bit integer + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); + break; - case 'sfID': - // 32-bit integer - $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); - $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); - break; + case 'stik': + // 8-bit integer (enum) + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); + break; - case 'egid': - case 'purl': - $atom_structure['data'] = substr($boxdata, 8); - break; + case 'sfID': + // 32-bit integer + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); + break; - case 'plID': - // 64-bit integer - $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8)); - break; + case 'egid': + case 'purl': + $atom_structure['data'] = substr($boxdata, 8); + break; - case 'covr': - $atom_structure['data'] = substr($boxdata, 8); + case 'plID': + // 64-bit integer + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8)); + break; + + case 'covr': + $atom_structure['data'] = substr($boxdata, 8); + // not a foolproof check, but better than nothing + if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { + $atom_structure['image_mime'] = 'image/jpeg'; + } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) { + $atom_structure['image_mime'] = 'image/png'; + } elseif (preg_match('#^GIF#', $atom_structure['data'])) { + $atom_structure['image_mime'] = 'image/gif'; + } + break; + + case 'atID': + case 'cnID': + case 'geID': + case 'tves': + case 'tvsn': + default: + // 32-bit integer + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + } + break; + + case 1: // text flag + case 13: // image flag + default: + $atom_structure['data'] = substr($boxdata, 8); + if ($atomname == 'covr') { // not a foolproof check, but better than nothing if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { $atom_structure['image_mime'] = 'image/jpeg'; @@ -513,1214 +554,1312 @@ class getid3_quicktime extends getid3_handler } elseif (preg_match('#^GIF#', $atom_structure['data'])) { $atom_structure['image_mime'] = 'image/gif'; } - break; - - case 'atID': - case 'cnID': - case 'geID': - case 'tves': - case 'tvsn': - default: - // 32-bit integer - $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); - } - break; - - case 1: // text flag - case 13: // image flag - default: - $atom_structure['data'] = substr($boxdata, 8); - if ($atomname == 'covr') { - // not a foolproof check, but better than nothing - if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { - $atom_structure['image_mime'] = 'image/jpeg'; - } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) { - $atom_structure['image_mime'] = 'image/png'; - } elseif (preg_match('#^GIF#', $atom_structure['data'])) { - $atom_structure['image_mime'] = 'image/gif'; } - } - break; + break; - } - break; + } + break; - default: - $this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset); - $atom_structure['data'] = $atom_data; + default: + $this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset); + $atom_structure['data'] = $atom_data; + } } } } - } - $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); - break; + $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); + break; - case 'play': // auto-PLAY atom - $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + case 'play': // auto-PLAY atom + $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $info['quicktime']['autoplay'] = $atom_structure['autoplay']; - break; + $info['quicktime']['autoplay'] = $atom_structure['autoplay']; + break; - case 'WLOC': // Window LOCation atom - $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); - $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); - break; + case 'WLOC': // Window LOCation atom + $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); + break; - case 'LOOP': // LOOPing atom - case 'SelO': // play SELection Only atom - case 'AllF': // play ALL Frames atom - $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); - break; + case 'LOOP': // LOOPing atom + case 'SelO': // play SELection Only atom + case 'AllF': // play ALL Frames atom + $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); + break; - case 'name': // - case 'MCPS': // Media Cleaner PRo - case '@PRM': // adobe PReMiere version - case '@PRQ': // adobe PRemiere Quicktime version - $atom_structure['data'] = $atom_data; - break; + case 'name': // + case 'MCPS': // Media Cleaner PRo + case '@PRM': // adobe PReMiere version + case '@PRQ': // adobe PRemiere Quicktime version + $atom_structure['data'] = $atom_data; + break; - case 'cmvd': // Compressed MooV Data atom - // Code by ubergeekØubergeek*tv based on information from - // http://developer.apple.com/quicktime/icefloe/dispatch012.html - $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + case 'cmvd': // Compressed MooV Data atom + // Code by ubergeekØubergeek*tv based on information from + // http://developer.apple.com/quicktime/icefloe/dispatch012.html + $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); - $CompressedFileData = substr($atom_data, 4); - if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { - $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); - } else { - $this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']); - } - break; - - - case 'dcom': // Data COMpression atom - $atom_structure['compression_id'] = $atom_data; - $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); - break; - - - case 'rdrf': // Reference movie Data ReFerence atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); - $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001); - - $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); - $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); - switch ($atom_structure['reference_type_name']) { - case 'url ': - $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); - break; - - case 'alis': - $atom_structure['file_alias'] = substr($atom_data, 12); - break; - - case 'rsrc': - $atom_structure['resource_alias'] = substr($atom_data, 12); - break; - - default: - $atom_structure['data'] = substr($atom_data, 12); - break; - } - break; - - - case 'rmqu': // Reference Movie QUality atom - $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); - break; - - - case 'rmcs': // Reference Movie Cpu Speed atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); - break; - - - case 'rmvc': // Reference Movie Version Check atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4); - $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); - $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); - $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); - break; - - - case 'rmcd': // Reference Movie Component check atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['component_type'] = substr($atom_data, 4, 4); - $atom_structure['component_subtype'] = substr($atom_data, 8, 4); - $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); - $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); - $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); - $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); - break; - - - case 'rmdr': // Reference Movie Data Rate atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - - $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; - break; - - - case 'rmla': // Reference Movie Language Atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); - - $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); - if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { - $info['comments']['language'][] = $atom_structure['language']; - } - break; - - - case 'rmla': // Reference Movie Language Atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); - break; - - - case 'ptv ': // Print To Video - defines a movie's full screen mode - // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm - $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); - $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 - $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 - $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); - $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); - - $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; - $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; - - $ptv_lookup[0] = 'normal'; - $ptv_lookup[1] = 'double'; - $ptv_lookup[2] = 'half'; - $ptv_lookup[3] = 'full'; - $ptv_lookup[4] = 'current'; - if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { - $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; - } else { - $this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'); - } - break; - - - case 'stsd': // Sample Table Sample Description atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - - // see: https://github.com/JamesHeinrich/getID3/issues/111 - // Some corrupt files have been known to have high bits set in the number_entries field - // This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000 - // Workaround: mask off the upper byte and throw a warning if it's nonzero - if ($atom_structure['number_entries'] > 0x000FFFFF) { - if ($atom_structure['number_entries'] > 0x00FFFFFF) { - $this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Ignoring upper byte and interpreting this as 0x'.getid3_lib::PrintHexBytes(substr($atom_data, 5, 3), true, false).' = '.($atom_structure['number_entries'] & 0x00FFFFFF)); - $atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF); + $CompressedFileData = substr($atom_data, 4); + if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); } else { - $this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Please report this to info@getid3.org referencing bug report #111'); + $this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']); } - } + break; - $stsdEntriesDataOffset = 8; - for ($i = 0; $i < $atom_structure['number_entries']; $i++) { - $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); - $stsdEntriesDataOffset += 4; - $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); - $stsdEntriesDataOffset += 4; - $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); - $stsdEntriesDataOffset += 6; - $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); - $stsdEntriesDataOffset += 2; - $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2)); - $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); - $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); - $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); - $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); + case 'dcom': // Data COMpression atom + $atom_structure['compression_id'] = $atom_data; + $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); + break; - switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { - case "\x00\x00\x00\x00": - // audio tracks - $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); - $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); - $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); - $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); - $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); + case 'rdrf': // Reference movie Data ReFerence atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001); - // video tracks - // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html - $atom_structure['sample_description_table'][$i]['temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); - $atom_structure['sample_description_table'][$i]['spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); - $atom_structure['sample_description_table'][$i]['width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); - $atom_structure['sample_description_table'][$i]['height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); - $atom_structure['sample_description_table'][$i]['resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); - $atom_structure['sample_description_table'][$i]['resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); - $atom_structure['sample_description_table'][$i]['data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 4)); - $atom_structure['sample_description_table'][$i]['frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 36, 2)); - $atom_structure['sample_description_table'][$i]['compressor_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 38, 4); - $atom_structure['sample_description_table'][$i]['pixel_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 42, 2)); - $atom_structure['sample_description_table'][$i]['color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 44, 2)); + $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); + $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + switch ($atom_structure['reference_type_name']) { + case 'url ': + $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); + break; - switch ($atom_structure['sample_description_table'][$i]['data_format']) { - case '2vuY': - case 'avc1': - case 'cvid': - case 'dvc ': - case 'dvcp': - case 'gif ': - case 'h263': - case 'jpeg': - case 'kpcd': - case 'mjpa': - case 'mjpb': - case 'mp4v': - case 'png ': - case 'raw ': - case 'rle ': - case 'rpza': - case 'smc ': - case 'SVQ1': - case 'SVQ3': - case 'tiff': - case 'v210': - case 'v216': - case 'v308': - case 'v408': - case 'v410': - case 'yuv2': - $info['fileformat'] = 'mp4'; - $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; -// http://www.getid3.org/phpBB3/viewtopic.php?t=1550 -//if ((!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['width'])) && (empty($info['video']['resolution_x']) || empty($info['video']['resolution_y']) || (number_format($info['video']['resolution_x'], 6) != number_format(round($info['video']['resolution_x']), 6)) || (number_format($info['video']['resolution_y'], 6) != number_format(round($info['video']['resolution_y']), 6)))) { // ugly check for floating point numbers -if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) { - // assume that values stored here are more important than values stored in [tkhd] atom - $info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width']; - $info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height']; - $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; - $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; -} - break; + case 'alis': + $atom_structure['file_alias'] = substr($atom_data, 12); + break; - case 'qtvr': - $info['video']['dataformat'] = 'quicktimevr'; - break; - - case 'mp4a': - default: - $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); - $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate']; - $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels']; - $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; - $info['audio']['codec'] = $info['quicktime']['audio']['codec']; - $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; - $info['audio']['channels'] = $info['quicktime']['audio']['channels']; - $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; - switch ($atom_structure['sample_description_table'][$i]['data_format']) { - case 'raw ': // PCM - case 'alac': // Apple Lossless Audio Codec - $info['audio']['lossless'] = true; - break; - default: - $info['audio']['lossless'] = false; - break; - } - break; - } + case 'rsrc': + $atom_structure['resource_alias'] = substr($atom_data, 12); break; default: - switch ($atom_structure['sample_description_table'][$i]['data_format']) { - case 'mp4s': - $info['fileformat'] = 'mp4'; - break; - - default: - // video atom - $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); - $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); - $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); - $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); - $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); - $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); - $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); - $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); - $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); - $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']); - $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); - $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); - - $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); - $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); - - if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { - $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; - $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); - $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']); - $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; - $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; - - $info['video']['codec'] = $info['quicktime']['video']['codec']; - $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; - } - $info['video']['lossless'] = false; - $info['video']['pixel_aspect_ratio'] = (float) 1; - break; - } + $atom_structure['data'] = substr($atom_data, 12); break; } - switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { - case 'mp4a': - $info['audio']['dataformat'] = 'mp4'; - $info['quicktime']['audio']['codec'] = 'mp4'; - break; + break; - case '3ivx': - case '3iv1': - case '3iv2': - $info['video']['dataformat'] = '3ivx'; - break; - case 'xvid': - $info['video']['dataformat'] = 'xvid'; - break; + case 'rmqu': // Reference Movie QUality atom + $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); + break; - case 'mp4v': - $info['video']['dataformat'] = 'mpeg4'; - break; - case 'divx': - case 'div1': - case 'div2': - case 'div3': - case 'div4': - case 'div5': - case 'div6': - $info['video']['dataformat'] = 'divx'; - break; + case 'rmcs': // Reference Movie Cpu Speed atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + break; - default: - // do nothing - break; + + case 'rmvc': // Reference Movie Version Check atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4); + $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); + break; + + + case 'rmcd': // Reference Movie Component check atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['component_type'] = substr($atom_data, 4, 4); + $atom_structure['component_subtype'] = substr($atom_data, 8, 4); + $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); + $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); + break; + + + case 'rmdr': // Reference Movie Data Rate atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + + $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; + break; + + + case 'rmla': // Reference Movie Language Atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; } - unset($atom_structure['sample_description_table'][$i]['data']); - } - break; + break; - case 'stts': // Sample Table Time-to-Sample atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $sttsEntriesDataOffset = 8; - //$FrameRateCalculatorArray = array(); - $frames_count = 0; + case 'ptv ': // Print To Video - defines a movie's full screen mode + // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm + $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 + $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 + $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); + $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); - $max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']); - if ($max_stts_entries_to_scan < $atom_structure['number_entries']) { - $this->warning('QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).'); - } - for ($i = 0; $i < $max_stts_entries_to_scan; $i++) { - $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); - $sttsEntriesDataOffset += 4; - $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); - $sttsEntriesDataOffset += 4; + $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; + $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; - $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; + $ptv_lookup[0] = 'normal'; + $ptv_lookup[1] = 'double'; + $ptv_lookup[2] = 'half'; + $ptv_lookup[3] = 'full'; + $ptv_lookup[4] = 'current'; + if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { + $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; + } else { + $this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'); + } + break; - // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM - //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { - // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; - // if ($stts_new_framerate <= 60) { - // // some atoms have durations of "1" giving a very large framerate, which probably is not right - // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); + + case 'stsd': // Sample Table Sample Description atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + + // see: https://github.com/JamesHeinrich/getID3/issues/111 + // Some corrupt files have been known to have high bits set in the number_entries field + // This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000 + // Workaround: mask off the upper byte and throw a warning if it's nonzero + if ($atom_structure['number_entries'] > 0x000FFFFF) { + if ($atom_structure['number_entries'] > 0x00FFFFFF) { + $this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Ignoring upper byte and interpreting this as 0x'.getid3_lib::PrintHexBytes(substr($atom_data, 5, 3), true, false).' = '.($atom_structure['number_entries'] & 0x00FFFFFF)); + $atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF); + } else { + $this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Please report this to info@getid3.org referencing bug report #111'); + } + } + + $stsdEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); + $stsdEntriesDataOffset += 4; + $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); + $stsdEntriesDataOffset += 4; + $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); + $stsdEntriesDataOffset += 6; + $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); + $stsdEntriesDataOffset += 2; + $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2)); + $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); + + $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); + $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); + $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); + + switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { + + case "\x00\x00\x00\x00": + // audio tracks + $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); + $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); + $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); + $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); + $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); + + // video tracks + // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html + $atom_structure['sample_description_table'][$i]['temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); + $atom_structure['sample_description_table'][$i]['spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); + $atom_structure['sample_description_table'][$i]['width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); + $atom_structure['sample_description_table'][$i]['height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); + $atom_structure['sample_description_table'][$i]['resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); + $atom_structure['sample_description_table'][$i]['resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); + $atom_structure['sample_description_table'][$i]['data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 4)); + $atom_structure['sample_description_table'][$i]['frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 36, 2)); + $atom_structure['sample_description_table'][$i]['compressor_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 38, 4); + $atom_structure['sample_description_table'][$i]['pixel_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 42, 2)); + $atom_structure['sample_description_table'][$i]['color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 44, 2)); + + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case '2vuY': + case 'avc1': + case 'cvid': + case 'dvc ': + case 'dvcp': + case 'gif ': + case 'h263': + case 'jpeg': + case 'kpcd': + case 'mjpa': + case 'mjpb': + case 'mp4v': + case 'png ': + case 'raw ': + case 'rle ': + case 'rpza': + case 'smc ': + case 'SVQ1': + case 'SVQ3': + case 'tiff': + case 'v210': + case 'v216': + case 'v308': + case 'v408': + case 'v410': + case 'yuv2': + $info['fileformat'] = 'mp4'; + $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; + if ($this->QuicktimeVideoCodecLookup($info['video']['fourcc'])) { + $info['video']['fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($info['video']['fourcc']); + } + + // https://www.getid3.org/phpBB3/viewtopic.php?t=1550 + //if ((!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['width'])) && (empty($info['video']['resolution_x']) || empty($info['video']['resolution_y']) || (number_format($info['video']['resolution_x'], 6) != number_format(round($info['video']['resolution_x']), 6)) || (number_format($info['video']['resolution_y'], 6) != number_format(round($info['video']['resolution_y']), 6)))) { // ugly check for floating point numbers + if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) { + // assume that values stored here are more important than values stored in [tkhd] atom + $info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width']; + $info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height']; + $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; + $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; + } + break; + + case 'qtvr': + $info['video']['dataformat'] = 'quicktimevr'; + break; + + case 'mp4a': + default: + $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); + $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate']; + $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels']; + $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; + $info['audio']['codec'] = $info['quicktime']['audio']['codec']; + $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; + $info['audio']['channels'] = $info['quicktime']['audio']['channels']; + $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case 'raw ': // PCM + case 'alac': // Apple Lossless Audio Codec + case 'sowt': // signed/two's complement (Little Endian) + case 'twos': // signed/two's complement (Big Endian) + case 'in24': // 24-bit Integer + case 'in32': // 32-bit Integer + case 'fl32': // 32-bit Floating Point + case 'fl64': // 64-bit Floating Point + $info['audio']['lossless'] = $info['quicktime']['audio']['lossless'] = true; + $info['audio']['bitrate'] = $info['quicktime']['audio']['bitrate'] = $info['audio']['channels'] * $info['audio']['bits_per_sample'] * $info['audio']['sample_rate']; + break; + default: + $info['audio']['lossless'] = false; + break; + } + break; + } + break; + + default: + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case 'mp4s': + $info['fileformat'] = 'mp4'; + break; + + default: + // video atom + $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); + $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); + $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); + $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); + $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); + $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); + $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); + $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); + $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); + $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']); + $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); + $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); + + $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); + $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); + + if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { + $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; + $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); + $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']); + $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; + $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; + + $info['video']['codec'] = $info['quicktime']['video']['codec']; + $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; + } + $info['video']['lossless'] = false; + $info['video']['pixel_aspect_ratio'] = (float) 1; + break; + } + break; + } + switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { + case 'mp4a': + $info['audio']['dataformat'] = 'mp4'; + $info['quicktime']['audio']['codec'] = 'mp4'; + break; + + case '3ivx': + case '3iv1': + case '3iv2': + $info['video']['dataformat'] = '3ivx'; + break; + + case 'xvid': + $info['video']['dataformat'] = 'xvid'; + break; + + case 'mp4v': + $info['video']['dataformat'] = 'mpeg4'; + break; + + case 'divx': + case 'div1': + case 'div2': + case 'div3': + case 'div4': + case 'div5': + case 'div6': + $info['video']['dataformat'] = 'divx'; + break; + + default: + // do nothing + break; + } + unset($atom_structure['sample_description_table'][$i]['data']); + } + break; + + + case 'stts': // Sample Table Time-to-Sample atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $sttsEntriesDataOffset = 8; + //$FrameRateCalculatorArray = array(); + $frames_count = 0; + + $max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']); + if ($max_stts_entries_to_scan < $atom_structure['number_entries']) { + $this->warning('QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($this->getid3->memory_limit / 1048576).'MB).'); + } + for ($i = 0; $i < $max_stts_entries_to_scan; $i++) { + $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); + $sttsEntriesDataOffset += 4; + $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); + $sttsEntriesDataOffset += 4; + + $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; + + // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM + //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { + // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; + // if ($stts_new_framerate <= 60) { + // // some atoms have durations of "1" giving a very large framerate, which probably is not right + // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); + // } + //} + // + //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; + } + $info['quicktime']['stts_framecount'][] = $frames_count; + //$sttsFramesTotal = 0; + //$sttsSecondsTotal = 0; + //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { + // if (($frames_per_second > 60) || ($frames_per_second < 1)) { + // // not video FPS information, probably audio information + // $sttsFramesTotal = 0; + // $sttsSecondsTotal = 0; + // break; + // } + // $sttsFramesTotal += $frame_count; + // $sttsSecondsTotal += $frame_count / $frames_per_second; + //} + //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { + // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { + // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; // } //} - // - //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; - } - $info['quicktime']['stts_framecount'][] = $frames_count; - //$sttsFramesTotal = 0; - //$sttsSecondsTotal = 0; - //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { - // if (($frames_per_second > 60) || ($frames_per_second < 1)) { - // // not video FPS information, probably audio information - // $sttsFramesTotal = 0; - // $sttsSecondsTotal = 0; - // break; - // } - // $sttsFramesTotal += $frame_count; - // $sttsSecondsTotal += $frame_count / $frames_per_second; - //} - //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { - // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { - // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; - // } - //} - break; + break; - case 'stss': // Sample Table Sync Sample (key frames) atom - if ($ParseAllPossibleAtoms) { - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $stssEntriesDataOffset = 8; - for ($i = 0; $i < $atom_structure['number_entries']; $i++) { - $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); - $stssEntriesDataOffset += 4; - } - } - break; - - - case 'stsc': // Sample Table Sample-to-Chunk atom - if ($ParseAllPossibleAtoms) { - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $stscEntriesDataOffset = 8; - for ($i = 0; $i < $atom_structure['number_entries']; $i++) { - $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); - $stscEntriesDataOffset += 4; - $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); - $stscEntriesDataOffset += 4; - $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); - $stscEntriesDataOffset += 4; - } - } - break; - - - case 'stsz': // Sample Table SiZe atom - if ($ParseAllPossibleAtoms) { - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); - $stszEntriesDataOffset = 12; - if ($atom_structure['sample_size'] == 0) { + case 'stss': // Sample Table Sync Sample (key frames) atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stssEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { - $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4)); - $stszEntriesDataOffset += 4; + $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); + $stssEntriesDataOffset += 4; } } - } - break; + break; - case 'stco': // Sample Table Chunk Offset atom - if ($ParseAllPossibleAtoms) { - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $stcoEntriesDataOffset = 8; - for ($i = 0; $i < $atom_structure['number_entries']; $i++) { - $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); - $stcoEntriesDataOffset += 4; - } - } - break; - - - case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) - if ($ParseAllPossibleAtoms) { - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $stcoEntriesDataOffset = 8; - for ($i = 0; $i < $atom_structure['number_entries']; $i++) { - $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); - $stcoEntriesDataOffset += 8; - } - } - break; - - - case 'dref': // Data REFerence atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $drefDataOffset = 8; - for ($i = 0; $i < $atom_structure['number_entries']; $i++) { - $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); - $drefDataOffset += 4; - $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); - $drefDataOffset += 4; - $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); - $drefDataOffset += 1; - $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 - $drefDataOffset += 3; - $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); - $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); - - $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001); - } - break; - - - case 'gmin': // base Media INformation atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); - $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); - $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); - $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); - $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); - $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); - break; - - - case 'smhd': // Sound Media information HeaDer atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); - $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); - break; - - - case 'vmhd': // Video Media information HeaDer atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); - $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); - $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); - $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); - $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); - - $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001); - break; - - - case 'hdlr': // HanDLeR reference atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['component_type'] = substr($atom_data, 4, 4); - $atom_structure['component_subtype'] = substr($atom_data, 8, 4); - $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); - $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); - $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); - $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24)); - - if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) { - $info['video']['dataformat'] = 'quicktimevr'; - } - break; - - - case 'mdhd': // MeDia HeaDer atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); - $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); - $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); - $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); - $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); - - if ($atom_structure['time_scale'] == 0) { - $this->error('Corrupt Quicktime file: mdhd.time_scale == zero'); - return false; - } - $info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); - - $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); - $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); - $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; - $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); - if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { - $info['comments']['language'][] = $atom_structure['language']; - } - break; - - - case 'pnot': // Preview atom - $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" - $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 - $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' - $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 - - $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); - break; - - - case 'crgn': // Clipping ReGioN atom - $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, - $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields - $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. - break; - - - case 'load': // track LOAD settings atom - $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); - $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); - $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); - - $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020); - $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100); - break; - - - case 'tmcd': // TiMe CoDe atom - case 'chap': // CHAPter list atom - case 'sync': // SYNChronization atom - case 'scpt': // tranSCriPT atom - case 'ssrc': // non-primary SouRCe atom - for ($i = 0; $i < strlen($atom_data); $i += 4) { - @$atom_structure['track_id'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); - } - break; - - - case 'elst': // Edit LiST atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) { - $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4)); - $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4)); - $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4)); - } - break; - - - case 'kmat': // compressed MATte atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 - $atom_structure['matte_data_raw'] = substr($atom_data, 4); - break; - - - case 'ctab': // Color TABle atom - $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 - $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 - $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; - for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { - $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2)); - $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2)); - $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2)); - $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2)); - } - break; - - - case 'mvhd': // MoVie HeaDer atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); - $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); - $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); - $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); - $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); - $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); - $atom_structure['reserved'] = substr($atom_data, 26, 10); - $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); - $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); - $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); - $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); - $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); - $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); - $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); - $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); - $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); - $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); - $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); - $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); - $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); - $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); - $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); - $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); - - if ($atom_structure['time_scale'] == 0) { - $this->error('Corrupt Quicktime file: mvhd.time_scale == zero'); - return false; - } - $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); - $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); - $info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); - $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; - $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; - break; - - - case 'tkhd': // TracK HeaDer atom - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); - $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); - $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); - $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); - $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); - $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); - $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); - $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); - $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); - $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); -// http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html -// http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737 - $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); - $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); - $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4)); - $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); - $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); - $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4)); - $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); - $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4)); - $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); - $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); - $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); - $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001); - $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002); - $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004); - $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008); - $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); - $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); - - if ($atom_structure['flags']['enabled'] == 1) { - if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { - $info['video']['resolution_x'] = $atom_structure['width']; - $info['video']['resolution_y'] = $atom_structure['height']; - } - $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); - $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); - $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; - $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; - } else { - // see: http://www.getid3.org/phpBB3/viewtopic.php?t=1295 - //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } - //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } - //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } - } - break; - - - case 'iods': // Initial Object DeScriptor atom - // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h - // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html - $offset = 0; - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); - $offset += 1; - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); - $offset += 3; - $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); - $offset += 1; - $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); - //$offset already adjusted by quicktime_read_mp4_descr_length() - $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); - $offset += 2; - $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); - $offset += 1; - $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); - $offset += 1; - $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); - $offset += 1; - $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); - $offset += 1; - $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); - $offset += 1; - - $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields - for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { - $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); - $offset += 1; - $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); - //$offset already adjusted by quicktime_read_mp4_descr_length() - $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); - $offset += 4; - } - - $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); - $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); - break; - - case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) - $atom_structure['signature'] = substr($atom_data, 0, 4); - $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $atom_structure['fourcc'] = substr($atom_data, 8, 4); - break; - - case 'mdat': // Media DATa atom - // 'mdat' contains the actual data for the audio/video, possibly also subtitles - -/* due to lack of known documentation, this is a kludge implementation. If you know of documentation on how mdat is properly structed, please send it to info@getid3.org */ - - // first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?) - $mdat_offset = 0; - while (true) { - if (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x08".'wide') { - $mdat_offset += 8; - } elseif (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x00".'mdat') { - $mdat_offset += 8; - } else { - break; - } - } - - // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field - while (($mdat_offset < (strlen($atom_data) - 8)) - && ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2))) - && ($chapter_string_length < 1000) - && ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2)) - && preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) { - list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches; - $mdat_offset += (2 + $chapter_string_length); - @$info['quicktime']['comments']['chapters'][] = $chapter_string; - - // "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled) - if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8 - $mdat_offset += 12; + case 'stsc': // Sample Table Sample-to-Chunk atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stscEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; } - } + } + break; - if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { - - $info['avdataoffset'] = $atom_structure['offset'] + 8; // $info['quicktime'][$atomname]['offset'] + 8; - $OldAVDataEnd = $info['avdataend']; - $info['avdataend'] = $atom_structure['offset'] + $atom_structure['size']; // $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; - - $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; - $getid3_temp->info['avdataend'] = $info['avdataend']; - $getid3_mp3 = new getid3_mp3($getid3_temp); - if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) { - $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); - if (!empty($getid3_temp->info['warning'])) { - foreach ($getid3_temp->info['warning'] as $value) { - $this->warning($value); - } - } - if (!empty($getid3_temp->info['mpeg'])) { - $info['mpeg'] = $getid3_temp->info['mpeg']; - if (isset($info['mpeg']['audio'])) { - $info['audio']['dataformat'] = 'mp3'; - $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'))); - $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; - $info['audio']['channels'] = $info['mpeg']['audio']['channels']; - $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; - $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); - $info['bitrate'] = $info['audio']['bitrate']; + case 'stsz': // Sample Table SiZe atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $stszEntriesDataOffset = 12; + if ($atom_structure['sample_size'] == 0) { + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4)); + $stszEntriesDataOffset += 4; } } } - unset($getid3_mp3, $getid3_temp); - $info['avdataend'] = $OldAVDataEnd; - unset($OldAVDataEnd); - - } - - unset($mdat_offset, $chapter_string_length, $chapter_matches); - break; - - case 'free': // FREE space atom - case 'skip': // SKIP atom - case 'wide': // 64-bit expansion placeholder atom - // 'free', 'skip' and 'wide' are just padding, contains no useful data at all - - // When writing QuickTime files, it is sometimes necessary to update an atom's size. - // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom - // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime - // puts an 8-byte placeholder atom before any atoms it may have to update the size of. - // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the - // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. - // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). - break; + break; - case 'nsav': // NoSAVe atom - // http://developer.apple.com/technotes/tn/tn2038.html - $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); - break; + case 'stco': // Sample Table Chunk Offset atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stcoEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); + $stcoEntriesDataOffset += 4; + } + } + break; - case 'ctyp': // Controller TYPe atom (seen on QTVR) - // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt - // some controller names are: - // 0x00 + 'std' for linear movie - // 'none' for no controls - $atom_structure['ctyp'] = substr($atom_data, 0, 4); - $info['quicktime']['controller'] = $atom_structure['ctyp']; - switch ($atom_structure['ctyp']) { - case 'qtvr': + + case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stcoEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); + $stcoEntriesDataOffset += 8; + } + } + break; + + + case 'dref': // Data REFerence atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $drefDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); + $drefDataOffset += 4; + $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); + $drefDataOffset += 4; + $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); + $drefDataOffset += 1; + $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 + $drefDataOffset += 3; + $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); + $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); + + $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001); + } + break; + + + case 'gmin': // base Media INformation atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); + $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); + $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); + $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); + break; + + + case 'smhd': // Sound Media information HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + break; + + + case 'vmhd': // Video Media information HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); + $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); + + $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001); + break; + + + case 'hdlr': // HanDLeR reference atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['component_type'] = substr($atom_data, 4, 4); + $atom_structure['component_subtype'] = substr($atom_data, 8, 4); + $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); + $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24)); + + if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) { $info['video']['dataformat'] = 'quicktimevr'; - break; - } - break; - - case 'pano': // PANOrama track (seen on QTVR) - $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); - break; - - case 'hint': // HINT track - case 'hinf': // - case 'hinv': // - case 'hnti': // - $info['quicktime']['hinting'] = true; - break; - - case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) - for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) { - $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); - } - break; - - - // Observed-but-not-handled atom types are just listed here to prevent warnings being generated - case 'FXTC': // Something to do with Adobe After Effects (?) - case 'PrmA': - case 'code': - case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html - case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html - // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838] - // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html - // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html - case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html - case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html - case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html - case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html - //$atom_structure['data'] = $atom_data; - break; - - case "\xA9".'xyz': // GPS latitude+longitude+altitude - $atom_structure['data'] = $atom_data; - if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { - @list($all, $latitude, $longitude, $altitude) = $matches; - $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); - $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); - if (!empty($altitude)) { - $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); } - } else { - $this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'); - } - break; + break; - case 'NCDT': - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html - // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 - $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); - break; - case 'NCTH': // Nikon Camera THumbnail image - case 'NCVW': // Nikon Camera preVieW image - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html - if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) { - $atom_structure['data'] = $atom_data; - $atom_structure['image_mime'] = 'image/jpeg'; - $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image')); - $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']); - } - break; - case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG - $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); - break; - case 'NCHD': // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html - case 'NCDB': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html - case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html - $atom_structure['data'] = $atom_data; - break; - case "\x00\x00\x00\x00": - // some kind of metacontainer, may contain a big data dump such as: - // mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4 - // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt + case 'mdhd': // MeDia HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); + $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); - $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); - //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); - break; + if ($atom_structure['time_scale'] == 0) { + $this->error('Corrupt Quicktime file: mdhd.time_scale == zero'); + return false; + } + $info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); - case 'meta': // METAdata atom - // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; + } + break; - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); - $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); - break; - case 'data': // metaDATA atom - static $metaDATAkey = 1; // real ugly, but so is the QuickTime structure that stores keys and values in different multinested locations that are hard to relate to each other - // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data - $atom_structure['language'] = substr($atom_data, 4 + 0, 2); - $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); - $atom_structure['data'] = substr($atom_data, 4 + 4); - $atom_structure['key_name'] = @$info['quicktime']['temp_meta_key_names'][$metaDATAkey++]; + case 'pnot': // Preview atom + $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" + $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 + $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' + $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 - if ($atom_structure['key_name'] && $atom_structure['data']) { - @$info['quicktime']['comments'][str_replace('com.apple.quicktime.', '', $atom_structure['key_name'])][] = $atom_structure['data']; - } - break; + $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); + break; - case 'keys': // KEYS that may be present in the metadata atom. - // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21 - // The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom. - // This list is indexed starting with 1; 0 is a reserved index value. The metadata item keys atom is a full atom with an atom type of "keys". - $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); - $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); - $atom_structure['entry_count'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); - $keys_atom_offset = 8; - for ($i = 1; $i <= $atom_structure['entry_count']; $i++) { - $atom_structure['keys'][$i]['key_size'] = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4)); - $atom_structure['keys'][$i]['key_namespace'] = substr($atom_data, $keys_atom_offset + 4, 4); - $atom_structure['keys'][$i]['key_value'] = substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8); - $keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace - $info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value']; - } - break; + case 'crgn': // Clipping ReGioN atom + $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, + $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields + $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. + break; - case 'gps ': - // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 - // The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data. - // The first row is version/metadata/notsure, I skip that. - // The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file. - $GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size - if (strlen($atom_data) > 0) { - if ((strlen($atom_data) % $GPS_rowsize) == 0) { - $atom_structure['gps_toc'] = array(); - foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) { - $atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize)); + case 'load': // track LOAD settings atom + $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + + $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020); + $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100); + break; + + + case 'tmcd': // TiMe CoDe atom + case 'chap': // CHAPter list atom + case 'sync': // SYNChronization atom + case 'scpt': // tranSCriPT atom + case 'ssrc': // non-primary SouRCe atom + for ($i = 0; $i < strlen($atom_data); $i += 4) { + @$atom_structure['track_id'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); + } + break; + + + case 'elst': // Edit LiST atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) { + $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4)); + $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4)); + $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4)); + } + break; + + + case 'kmat': // compressed MATte atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['matte_data_raw'] = substr($atom_data, 4); + break; + + + case 'ctab': // Color TABle atom + $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 + $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 + $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; + for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { + $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2)); + $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2)); + $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2)); + $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2)); + } + break; + + + case 'mvhd': // MoVie HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); + $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); + $atom_structure['reserved'] = substr($atom_data, 26, 10); + $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); + $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); + $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); + $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); + $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); + $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); + $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); + $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); + $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); + $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); + $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); + $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); + $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); + $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); + $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); + $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); + + if ($atom_structure['time_scale'] == 0) { + $this->error('Corrupt Quicktime file: mvhd.time_scale == zero'); + return false; + } + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + $info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); + $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; + $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; + break; + + + case 'tkhd': // TracK HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); + $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); + $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); + $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); + $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); + // http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html + // http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737 + $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); + $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); + $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4)); + $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); + $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); + $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4)); + $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); + $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4)); + $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); + $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); + $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); + $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001); + $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002); + $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004); + $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008); + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + + // https://www.getid3.org/phpBB3/viewtopic.php?t=1908 + // attempt to compute rotation from matrix values + // 2017-Dec-28: uncertain if 90/270 are correctly oriented; values returned by FixedPoint16_16 should perhaps be -1 instead of 65535(?) + $matrixRotation = 0; + switch ($atom_structure['matrix_a'].':'.$atom_structure['matrix_b'].':'.$atom_structure['matrix_c'].':'.$atom_structure['matrix_d']) { + case '1:0:0:1': $matrixRotation = 0; break; + case '0:1:65535:0': $matrixRotation = 90; break; + case '65535:0:0:65535': $matrixRotation = 180; break; + case '0:65535:1:0': $matrixRotation = 270; break; + default: break; + } + + // https://www.getid3.org/phpBB3/viewtopic.php?t=2468 + // The rotation matrix can appear in the Quicktime file multiple times, at least once for each track, + // and it's possible that only the video track (or, in theory, one of the video tracks) is flagged as + // rotated while the other tracks (e.g. audio) is tagged as rotation=0 (behavior noted on iPhone 8 Plus) + // The correct solution would be to check if the TrackID associated with the rotation matrix is indeed + // a video track (or the main video track) and only set the rotation then, but since information about + // what track is what is not trivially there to be examined, the lazy solution is to set the rotation + // if it is found to be nonzero, on the assumption that tracks that don't need it will have rotation set + // to zero (and be effectively ignored) and the video track will have rotation set correctly, which will + // either be zero and automatically correct, or nonzero and be set correctly. + if (!isset($info['video']['rotate']) || (($info['video']['rotate'] == 0) && ($matrixRotation > 0))) { + $info['quicktime']['video']['rotate'] = $info['video']['rotate'] = $matrixRotation; + } + + if ($atom_structure['flags']['enabled'] == 1) { + if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { + $info['video']['resolution_x'] = $atom_structure['width']; + $info['video']['resolution_y'] = $atom_structure['height']; } - - $atom_structure['gps_entries'] = array(); - $previous_offset = $this->ftell(); - foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) { - if ($key == 0) { - // "The first row is version/metadata/notsure, I skip that." - continue; - } - $this->fseek($gps_pointer['offset']); - $GPS_free_data = $this->fread($gps_pointer['size']); - - /* - // 2017-05-10: I see some of the data, notably the Hour-Minute-Second, but cannot reconcile the rest of the data. However, the NMEA "GPRMC" line is there and relatively easy to parse, so I'm using that instead - - // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 - // The structure of the GPS data atom (the 'free' atoms mentioned above) is following: - // hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from(' 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess - $GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms; - - $GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void - - foreach (array('latitude','longitude') as $latlon) { - preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches); - list($dummy, $deg, $min) = $matches; - $GPS_this_GPRMC[$latlon] = $deg + ($min / 60); - } - $GPS_this_GPRMC['latitude'] *= (($GPS_this_GPRMC['raw']['latitude_direction'] == 'S') ? -1 : 1); - $GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1); - - $GPS_this_GPRMC['heading'] = $GPS_this_GPRMC['raw']['angle']; - $GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots']; - $GPS_this_GPRMC['speed_kmh'] = $GPS_this_GPRMC['raw']['knots'] * 1.852; - if ($GPS_this_GPRMC['raw']['variation']) { - $GPS_this_GPRMC['variation'] = $GPS_this_GPRMC['raw']['variation']; - $GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1); - } - - $atom_structure['gps_entries'][$key] = $GPS_this_GPRMC; - - @$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array( - 'latitude' => $GPS_this_GPRMC['latitude'], - 'longitude' => $GPS_this_GPRMC['longitude'], - 'speed_kmh' => $GPS_this_GPRMC['speed_kmh'], - 'heading' => $GPS_this_GPRMC['heading'], - ); - - } else { - $this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']); - } - } - $this->fseek($previous_offset); - + $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); + $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); + $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; + $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; } else { - $this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset); + // see: https://www.getid3.org/phpBB3/viewtopic.php?t=1295 + //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } + //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } + //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } } - } else { - $this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset); - } - break; + break; - case 'loci':// 3GP location (El Loco) - $info['quicktime']['comments']['gps_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); - $info['quicktime']['comments']['gps_lang'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); - $loffset = 0; - $info['quicktime']['comments']['gps_location'] = $this->LociString(substr($atom_data, 6), $loffset); - $loci_data=substr($atom_data, 6 + $loffset); - $info['quicktime']['comments']['gps_role'] = getid3_lib::BigEndian2Int(substr($loci_data, 0, 1)); - $info['quicktime']['comments']['gps_longitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4)); - $info['quicktime']['comments']['gps_latitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4)); - $info['quicktime']['comments']['gps_altitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4)); - $info['quicktime']['comments']['gps_body'] = $this->LociString(substr($loci_data, 13), $loffset); - $info['quicktime']['comments']['gps_notes'] = $this->LociString(substr($loci_data, 13 + $loffset), $loffset); - break; - default: - $this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset); - $atom_structure['data'] = $atom_data; - break; + case 'iods': // Initial Object DeScriptor atom + // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h + // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html + $offset = 0; + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); + $offset += 3; + $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); + //$offset already adjusted by quicktime_read_mp4_descr_length() + $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); + $offset += 2; + $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + + $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields + for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { + $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); + //$offset already adjusted by quicktime_read_mp4_descr_length() + $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); + $offset += 4; + } + + $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); + $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); + break; + + case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) + $atom_structure['signature'] = substr($atom_data, 0, 4); + $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['fourcc'] = substr($atom_data, 8, 4); + break; + + case 'mdat': // Media DATa atom + // 'mdat' contains the actual data for the audio/video, possibly also subtitles + + /* due to lack of known documentation, this is a kludge implementation. If you know of documentation on how mdat is properly structed, please send it to info@getid3.org */ + + // first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?) + $mdat_offset = 0; + while (true) { + if (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x08".'wide') { + $mdat_offset += 8; + } elseif (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x00".'mdat') { + $mdat_offset += 8; + } else { + break; + } + } + if (substr($atom_data, $mdat_offset, 4) == 'GPRO') { + $GOPRO_chunk_length = getid3_lib::LittleEndian2Int(substr($atom_data, $mdat_offset + 4, 4)); + $GOPRO_offset = 8; + $atom_structure['GPRO']['raw'] = substr($atom_data, $mdat_offset + 8, $GOPRO_chunk_length - 8); + $atom_structure['GPRO']['firmware'] = substr($atom_structure['GPRO']['raw'], 0, 15); + $atom_structure['GPRO']['unknown1'] = substr($atom_structure['GPRO']['raw'], 15, 16); + $atom_structure['GPRO']['unknown2'] = substr($atom_structure['GPRO']['raw'], 31, 32); + $atom_structure['GPRO']['unknown3'] = substr($atom_structure['GPRO']['raw'], 63, 16); + $atom_structure['GPRO']['camera'] = substr($atom_structure['GPRO']['raw'], 79, 32); + $info['quicktime']['camera']['model'] = rtrim($atom_structure['GPRO']['camera'], "\x00"); + } + + // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field + while (($mdat_offset < (strlen($atom_data) - 8)) + && ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2))) + && ($chapter_string_length < 1000) + && ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2)) + && preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) { + list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches; + $mdat_offset += (2 + $chapter_string_length); + @$info['quicktime']['comments']['chapters'][] = $chapter_string; + + // "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled) + if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8 + $mdat_offset += 12; + } + } + + if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { + + $info['avdataoffset'] = $atom_structure['offset'] + 8; // $info['quicktime'][$atomname]['offset'] + 8; + $OldAVDataEnd = $info['avdataend']; + $info['avdataend'] = $atom_structure['offset'] + $atom_structure['size']; // $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + $getid3_mp3 = new getid3_mp3($getid3_temp); + if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) { + $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $value) { + $this->warning($value); + } + } + if (!empty($getid3_temp->info['mpeg'])) { + $info['mpeg'] = $getid3_temp->info['mpeg']; + if (isset($info['mpeg']['audio'])) { + $info['audio']['dataformat'] = 'mp3'; + $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'))); + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + $info['bitrate'] = $info['audio']['bitrate']; + } + } + } + unset($getid3_mp3, $getid3_temp); + $info['avdataend'] = $OldAVDataEnd; + unset($OldAVDataEnd); + + } + + unset($mdat_offset, $chapter_string_length, $chapter_matches); + break; + + case 'free': // FREE space atom + case 'skip': // SKIP atom + case 'wide': // 64-bit expansion placeholder atom + // 'free', 'skip' and 'wide' are just padding, contains no useful data at all + + // When writing QuickTime files, it is sometimes necessary to update an atom's size. + // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom + // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime + // puts an 8-byte placeholder atom before any atoms it may have to update the size of. + // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the + // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. + // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). + break; + + + case 'nsav': // NoSAVe atom + // http://developer.apple.com/technotes/tn/tn2038.html + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + break; + + case 'ctyp': // Controller TYPe atom (seen on QTVR) + // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt + // some controller names are: + // 0x00 + 'std' for linear movie + // 'none' for no controls + $atom_structure['ctyp'] = substr($atom_data, 0, 4); + $info['quicktime']['controller'] = $atom_structure['ctyp']; + switch ($atom_structure['ctyp']) { + case 'qtvr': + $info['video']['dataformat'] = 'quicktimevr'; + break; + } + break; + + case 'pano': // PANOrama track (seen on QTVR) + $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + break; + + case 'hint': // HINT track + case 'hinf': // + case 'hinv': // + case 'hnti': // + $info['quicktime']['hinting'] = true; + break; + + case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) + for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) { + $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); + } + break; + + + // Observed-but-not-handled atom types are just listed here to prevent warnings being generated + case 'FXTC': // Something to do with Adobe After Effects (?) + case 'PrmA': + case 'code': + case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html + case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html + // tapt seems to be used to compute the video size [https://www.getid3.org/phpBB3/viewtopic.php?t=838] + // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html + // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html + case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + //$atom_structure['data'] = $atom_data; + break; + + case "\xA9".'xyz': // GPS latitude+longitude+altitude + $atom_structure['data'] = $atom_data; + if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { + @list($all, $latitude, $longitude, $altitude) = $matches; + $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); + $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); + if (!empty($altitude)) { + $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); + } + } else { + $this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'); + } + break; + + case 'NCDT': + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); + break; + case 'NCTH': // Nikon Camera THumbnail image + case 'NCVW': // Nikon Camera preVieW image + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) { + $atom_structure['data'] = $atom_data; + $atom_structure['image_mime'] = 'image/jpeg'; + $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image')); + $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']); + } + break; + case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG + $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); + break; + case 'NCHD': // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + case 'NCDB': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html + $atom_structure['data'] = $atom_data; + break; + + case "\x00\x00\x00\x00": + // some kind of metacontainer, may contain a big data dump such as: + // mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4 + // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt + + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'meta': // METAdata atom + // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html + + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'data': // metaDATA atom + static $metaDATAkey = 1; // real ugly, but so is the QuickTime structure that stores keys and values in different multinested locations that are hard to relate to each other + // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data + $atom_structure['language'] = substr($atom_data, 4 + 0, 2); + $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); + $atom_structure['data'] = substr($atom_data, 4 + 4); + $atom_structure['key_name'] = @$info['quicktime']['temp_meta_key_names'][$metaDATAkey++]; + + if ($atom_structure['key_name'] && $atom_structure['data']) { + @$info['quicktime']['comments'][str_replace('com.apple.quicktime.', '', $atom_structure['key_name'])][] = $atom_structure['data']; + } + break; + + case 'keys': // KEYS that may be present in the metadata atom. + // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21 + // The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom. + // This list is indexed starting with 1; 0 is a reserved index value. The metadata item keys atom is a full atom with an atom type of "keys". + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['entry_count'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $keys_atom_offset = 8; + for ($i = 1; $i <= $atom_structure['entry_count']; $i++) { + $atom_structure['keys'][$i]['key_size'] = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4)); + $atom_structure['keys'][$i]['key_namespace'] = substr($atom_data, $keys_atom_offset + 4, 4); + $atom_structure['keys'][$i]['key_value'] = substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8); + $keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace + + $info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value']; + } + break; + + case 'gps ': + // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 + // The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data. + // The first row is version/metadata/notsure, I skip that. + // The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file. + + $GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size + if (strlen($atom_data) > 0) { + if ((strlen($atom_data) % $GPS_rowsize) == 0) { + $atom_structure['gps_toc'] = array(); + foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) { + $atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize)); + } + + $atom_structure['gps_entries'] = array(); + $previous_offset = $this->ftell(); + foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) { + if ($key == 0) { + // "The first row is version/metadata/notsure, I skip that." + continue; + } + $this->fseek($gps_pointer['offset']); + $GPS_free_data = $this->fread($gps_pointer['size']); + + /* + // 2017-05-10: I see some of the data, notably the Hour-Minute-Second, but cannot reconcile the rest of the data. However, the NMEA "GPRMC" line is there and relatively easy to parse, so I'm using that instead + + // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 + // The structure of the GPS data atom (the 'free' atoms mentioned above) is following: + // hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from(' 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess + $GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms; + + $GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void + + foreach (array('latitude','longitude') as $latlon) { + preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches); + list($dummy, $deg, $min) = $matches; + $GPS_this_GPRMC[$latlon] = $deg + ($min / 60); + } + $GPS_this_GPRMC['latitude'] *= (($GPS_this_GPRMC['raw']['latitude_direction'] == 'S') ? -1 : 1); + $GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1); + + $GPS_this_GPRMC['heading'] = $GPS_this_GPRMC['raw']['angle']; + $GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots']; + $GPS_this_GPRMC['speed_kmh'] = $GPS_this_GPRMC['raw']['knots'] * 1.852; + if ($GPS_this_GPRMC['raw']['variation']) { + $GPS_this_GPRMC['variation'] = $GPS_this_GPRMC['raw']['variation']; + $GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1); + } + + $atom_structure['gps_entries'][$key] = $GPS_this_GPRMC; + + @$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array( + 'latitude' => (float) $GPS_this_GPRMC['latitude'], + 'longitude' => (float) $GPS_this_GPRMC['longitude'], + 'speed_kmh' => (float) $GPS_this_GPRMC['speed_kmh'], + 'heading' => (float) $GPS_this_GPRMC['heading'], + ); + + } else { + $this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']); + } + } + $this->fseek($previous_offset); + + } else { + $this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset); + } + } else { + $this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset); + } + break; + + case 'loci':// 3GP location (El Loco) + $loffset = 0; + $info['quicktime']['comments']['gps_flags'] = array( getid3_lib::BigEndian2Int(substr($atom_data, 0, 4))); + $info['quicktime']['comments']['gps_lang'] = array( getid3_lib::BigEndian2Int(substr($atom_data, 4, 2))); + $info['quicktime']['comments']['gps_location'] = array( $this->LociString(substr($atom_data, 6), $loffset)); + $loci_data = substr($atom_data, 6 + $loffset); + $info['quicktime']['comments']['gps_role'] = array( getid3_lib::BigEndian2Int(substr($loci_data, 0, 1))); + $info['quicktime']['comments']['gps_longitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4))); + $info['quicktime']['comments']['gps_latitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4))); + $info['quicktime']['comments']['gps_altitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4))); + $info['quicktime']['comments']['gps_body'] = array( $this->LociString(substr($loci_data, 13 ), $loffset)); + $info['quicktime']['comments']['gps_notes'] = array( $this->LociString(substr($loci_data, 13 + $loffset), $loffset)); + break; + + case 'chpl': // CHaPter List + // https://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf + $chpl_version = getid3_lib::BigEndian2Int(substr($atom_data, 4, 1)); // Expected to be 0 + $chpl_flags = getid3_lib::BigEndian2Int(substr($atom_data, 5, 3)); // Reserved, set to 0 + $chpl_count = getid3_lib::BigEndian2Int(substr($atom_data, 8, 1)); + $chpl_offset = 9; + for ($i = 0; $i < $chpl_count; $i++) { + if (($chpl_offset + 9) >= strlen($atom_data)) { + $this->warning('QuickTime chapter '.$i.' extends beyond end of "chpl" atom'); + break; + } + $info['quicktime']['chapters'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 8)) / 10000000; // timestamps are stored as 100-nanosecond units + $chpl_offset += 8; + $chpl_title_size = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 1)); + $chpl_offset += 1; + $info['quicktime']['chapters'][$i]['title'] = substr($atom_data, $chpl_offset, $chpl_title_size); + $chpl_offset += $chpl_title_size; + } + break; + + case 'FIRM': // FIRMware version(?), seen on GoPro Hero4 + $info['quicktime']['camera']['firmware'] = $atom_data; + break; + + case 'CAME': // FIRMware version(?), seen on GoPro Hero4 + $info['quicktime']['camera']['serial_hash'] = unpack('H*', $atom_data); + break; + + case 'dscp': + case 'rcif': + // https://www.getid3.org/phpBB3/viewtopic.php?t=1908 + if (substr($atom_data, 0, 7) == "\x00\x00\x00\x00\x55\xC4".'{') { + if ($json_decoded = @json_decode(rtrim(substr($atom_data, 6), "\x00"), true)) { + $info['quicktime']['camera'][$atomname] = $json_decoded; + if (($atomname == 'rcif') && isset($info['quicktime']['camera']['rcif']['wxcamera']['rotate'])) { + $info['video']['rotate'] = $info['quicktime']['video']['rotate'] = $info['quicktime']['camera']['rcif']['wxcamera']['rotate']; + } + } else { + $this->warning('Failed to JSON decode atom "'.$atomname.'"'); + $atom_structure['data'] = $atom_data; + } + unset($json_decoded); + } else { + $this->warning('Expecting 55 C4 7B at start of atom "'.$atomname.'", found '.getid3_lib::PrintHexBytes(substr($atom_data, 4, 3)).' instead'); + $atom_structure['data'] = $atom_data; + } + break; + + case 'frea': + // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea + // may contain "scra" (PreviewImage) and/or "thma" (ThumbnailImage) + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); + break; + case 'tima': // subatom to "frea" + // no idea what this does, the one sample file I've seen has a value of 0x00000027 + $atom_structure['data'] = $atom_data; + break; + case 'ver ': // subatom to "frea" + // some kind of version number, the one sample file I've seen has a value of "3.00.073" + $atom_structure['data'] = $atom_data; + break; + case 'thma': // subatom to "frea" -- "ThumbnailImage" + // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea + if (strlen($atom_data) > 0) { + $info['quicktime']['comments']['picture'][] = array('data'=>$atom_data, 'image_mime'=>'image/jpeg'); + } + break; + case 'scra': // subatom to "frea" -- "PreviewImage" + // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea + // but the only sample file I've seen has no useful data here + if (strlen($atom_data) > 0) { + $info['quicktime']['comments']['picture'][] = array('data'=>$atom_data, 'image_mime'=>'image/jpeg'); + } + break; + + + default: + $this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).'), '.$atomsize.' bytes at offset '.$baseoffset); + $atom_structure['data'] = $atom_data; + break; + } } array_pop($atomHierarchy); return $atom_structure; } + /** + * @param string $atom_data + * @param int $baseoffset + * @param array $atomHierarchy + * @param bool $ParseAllPossibleAtoms + * + * @return array|false + */ public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { -//echo 'QuicktimeParseContainerAtom('.substr($atom_data, 4, 4).') @ '.$baseoffset.'

'; $atom_structure = false; $subatomoffset = 0; $subatomcounter = 0; @@ -1741,16 +1880,18 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ } return $atom_structure; } - - $atom_structure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); - + $atom_structure[$subatomcounter++] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); $subatomoffset += $subatomsize; - $subatomcounter++; } return $atom_structure; } - + /** + * @param string $data + * @param int $offset + * + * @return int + */ public function quicktime_read_mp4_descr_length($data, &$offset) { // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html $num_bytes = 0; @@ -1762,7 +1903,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return $length; } - + /** + * @param int $languageid + * + * @return string + */ public function QuicktimeLanguageLookup($languageid) { // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-34353 static $QuicktimeLanguageLookup = array(); @@ -1900,6 +2045,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid'); } + /** + * @param string $codecid + * + * @return string + */ public function QuicktimeVideoCodecLookup($codecid) { static $QuicktimeVideoCodecLookup = array(); if (empty($QuicktimeVideoCodecLookup)) { @@ -1959,6 +2109,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : ''); } + /** + * @param string $codecid + * + * @return mixed|string + */ public function QuicktimeAudioCodecLookup($codecid) { static $QuicktimeAudioCodecLookup = array(); if (empty($QuicktimeAudioCodecLookup)) { @@ -2004,6 +2159,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : ''); } + /** + * @param string $compressionid + * + * @return string + */ public function QuicktimeDCOMLookup($compressionid) { static $QuicktimeDCOMLookup = array(); if (empty($QuicktimeDCOMLookup)) { @@ -2013,6 +2173,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : ''); } + /** + * @param int $colordepthid + * + * @return string + */ public function QuicktimeColorNameLookup($colordepthid) { static $QuicktimeColorNameLookup = array(); if (empty($QuicktimeColorNameLookup)) { @@ -2031,6 +2196,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid'); } + /** + * @param int $stik + * + * @return string + */ public function QuicktimeSTIKLookup($stik) { static $QuicktimeSTIKLookup = array(); if (empty($QuicktimeSTIKLookup)) { @@ -2048,6 +2218,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid'); } + /** + * @param int $audio_profile_id + * + * @return string + */ public function QuicktimeIODSaudioProfileName($audio_profile_id) { static $QuicktimeIODSaudioProfileNameLookup = array(); if (empty($QuicktimeIODSaudioProfileNameLookup)) { @@ -2107,7 +2282,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private'); } - + /** + * @param int $video_profile_id + * + * @return string + */ public function QuicktimeIODSvideoProfileName($video_profile_id) { static $QuicktimeIODSvideoProfileNameLookup = array(); if (empty($QuicktimeIODSvideoProfileNameLookup)) { @@ -2179,7 +2358,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeIODSvideoProfileNameLookup[$video_profile_id]) ? $QuicktimeIODSvideoProfileNameLookup[$video_profile_id] : 'ISO Reserved Profile'); } - + /** + * @param int $rtng + * + * @return string + */ public function QuicktimeContentRatingLookup($rtng) { static $QuicktimeContentRatingLookup = array(); if (empty($QuicktimeContentRatingLookup)) { @@ -2190,6 +2373,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid'); } + /** + * @param int $akid + * + * @return string + */ public function QuicktimeStoreAccountTypeLookup($akid) { static $QuicktimeStoreAccountTypeLookup = array(); if (empty($QuicktimeStoreAccountTypeLookup)) { @@ -2199,6 +2387,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid'); } + /** + * @param int $sfid + * + * @return string + */ public function QuicktimeStoreFrontCodeLookup($sfid) { static $QuicktimeStoreFrontCodeLookup = array(); if (empty($QuicktimeStoreFrontCodeLookup)) { @@ -2228,6 +2421,11 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid'); } + /** + * @param string $atom_data + * + * @return array + */ public function QuicktimeParseNikonNCTG($atom_data) { // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 @@ -2270,10 +2468,10 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ ); $offset = 0; + $data = null; $datalength = strlen($atom_data); $parsed = array(); while ($offset < $datalength) { -//echo getid3_lib::PrintHexBytes(substr($atom_data, $offset, 4)).'
'; $record_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4; $data_size_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; $data_size = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; @@ -2328,7 +2526,7 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($ $offset += ($data_size * 2); break; default: -echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
'; + echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
'; break 2; } @@ -2406,7 +2604,13 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
getid3->info; @@ -2542,39 +2746,47 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
2 && (($lstring[0] == "\xFE" && $lstring[1] == "\xFF") || ($lstring[0] == "\xFF" && $lstring[1] == "\xFE"))) { - //UTF-16 - if (preg_match('/(.*)\x00/', $lstring, $lmatches)){ - $count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000 - return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]); - } else { - return ''; - } - } else { - //UTF-8 - if (preg_match('/(.*)\x00/', $lstring, $lmatches)){ - $count = strlen($lmatches[1]) + 1; //account for trailing \x00 - return $lmatches[1]; - }else { - return ''; - } - - } - } + /** + * @param string $lstring + * @param int $count + * + * @return string + */ + public function LociString($lstring, &$count) { + // Loci strings are UTF-8 or UTF-16 and null (x00/x0000) terminated. UTF-16 has a BOM + // Also need to return the number of bytes the string occupied so additional fields can be extracted + $len = strlen($lstring); + if ($len == 0) { + $count = 0; + return ''; + } + if ($lstring[0] == "\x00") { + $count = 1; + return ''; + } + // check for BOM + if (($len > 2) && ((($lstring[0] == "\xFE") && ($lstring[1] == "\xFF")) || (($lstring[0] == "\xFF") && ($lstring[1] == "\xFE")))) { + // UTF-16 + if (preg_match('/(.*)\x00/', $lstring, $lmatches)) { + $count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000 + return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]); + } else { + return ''; + } + } + // UTF-8 + if (preg_match('/(.*)\x00/', $lstring, $lmatches)) { + $count = strlen($lmatches[1]) + 1; //account for trailing \x00 + return $lmatches[1]; + } + return ''; + } + /** + * @param string $nullterminatedstring + * + * @return string + */ public function NoNullString($nullterminatedstring) { // remove the single null terminator on null terminated strings if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") { @@ -2583,16 +2795,26 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
$value) { $key_history = $history.'/'.$key; @@ -2606,6 +2828,13 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
$value) { $key_history = $history.'/'.$key; @@ -2619,6 +2848,11 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res); @@ -2636,7 +2870,12 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
0) { $ts_res = array(); $this->search_tag_by_key($info['quicktime']['moov'], 'time_scale', 'quicktime/moov', $ts_res); - foreach ($ts_res as $value) { - $prefix = substr($value[0], 0, -12); + foreach ($ts_res as $sub_value) { + $prefix = substr($sub_value[0], 0, -12); if ((substr($stbl_res[0][0], 0, strlen($prefix)) === $prefix) && ($ts_prefix_len < strlen($prefix))) { - $time_scale = $value[1]['time_scale']; + $time_scale = $sub_value[1]['time_scale']; $ts_prefix_len = strlen($prefix); } } diff --git a/wp-includes/ID3/module.audio-video.riff.php b/wp-includes/ID3/module.audio-video.riff.php index 53f93501e2..276f131eaa 100644 --- a/wp-includes/ID3/module.audio-video.riff.php +++ b/wp-includes/ID3/module.audio-video.riff.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.audio-video.riff.php // @@ -27,10 +27,15 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE_ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true); getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, true); -class getid3_riff extends getid3_handler { - +class getid3_riff extends getid3_handler +{ protected $container = 'riff'; // default + /** + * @return bool + * + * @throws getid3_exception + */ public function Analyze() { $info = &$this->getid3->info; @@ -46,6 +51,7 @@ class getid3_riff extends getid3_handler { $thisfile_audio_dataformat = &$thisfile_audio['dataformat']; $thisfile_riff_audio = &$thisfile_riff['audio']; $thisfile_riff_video = &$thisfile_riff['video']; + $thisfile_riff_WAVE = array(); $Original['avdataoffset'] = $info['avdataoffset']; $Original['avdataend'] = $info['avdataend']; @@ -357,6 +363,7 @@ class getid3_riff extends getid3_handler { } $thisfile_riff_WAVE_cart_0['url'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 748, 1024)); $thisfile_riff_WAVE_cart_0['tag_text'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_cart_0['data'], 1772))); + $thisfile_riff['comments']['tag_text'][] = substr($thisfile_riff_WAVE_cart_0['data'], 1772); $thisfile_riff['comments']['artist'][] = $thisfile_riff_WAVE_cart_0['artist']; $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_cart_0['title']; @@ -405,7 +412,6 @@ class getid3_riff extends getid3_handler { 'tracktitle'=>'title', 'category' =>'genre', 'cdtitle' =>'album', - 'tracktitle'=>'title', ); foreach ($tagmapping as $fromkey => $tokey) { if (isset($thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey])) { @@ -613,6 +619,8 @@ class getid3_riff extends getid3_handler { $thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably $thisfile_video['dataformat'] = 'avi'; + $thisfile_riff_video_current = array(); + if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) { $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8; if (isset($thisfile_riff['AVIX'])) { @@ -695,12 +703,13 @@ class getid3_riff extends getid3_handler { 'capturedfile' => 0x00010000, 'copyrighted' => 0x00020010, ); - foreach ($flags as $flag => $value) { + foreach ($flags as $flag => $value) { $thisfile_riff_raw_avih['flags'][$flag] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & $value); } // shortcut $thisfile_riff_video[$streamindex] = array(); + /** @var array $thisfile_riff_video_current */ $thisfile_riff_video_current = &$thisfile_riff_video[$streamindex]; if ($thisfile_riff_raw_avih['dwWidth'] > 0) { @@ -867,7 +876,7 @@ class getid3_riff extends getid3_handler { } } - if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { + if (isset($thisfile_riff_raw_strf_strhfccType_streamindex) && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; if (self::fourccLookup($thisfile_video['fourcc'])) { @@ -914,7 +923,7 @@ class getid3_riff extends getid3_handler { // http://en.wikipedia.org/wiki/CD-DA case 'CDDA': $info['fileformat'] = 'cda'; - unset($info['mime_type']); + unset($info['mime_type']); $thisfile_audio_dataformat = 'cda'; @@ -934,7 +943,7 @@ class getid3_riff extends getid3_handler { $thisfile_riff_CDDA_fmt_0['start_offset_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['start_offset_frame'] / 75; $thisfile_riff_CDDA_fmt_0['playtime_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['playtime_frames'] / 75; - $info['comments']['track'] = $thisfile_riff_CDDA_fmt_0['track_num']; + $info['comments']['track_number'] = $thisfile_riff_CDDA_fmt_0['track_num']; $info['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; // hardcoded data for CD-audio @@ -947,7 +956,7 @@ class getid3_riff extends getid3_handler { } break; - // http://en.wikipedia.org/wiki/AIFF + // http://en.wikipedia.org/wiki/AIFF case 'AIFF': case 'AIFC': $info['fileformat'] = 'aiff'; @@ -1057,7 +1066,7 @@ class getid3_riff extends getid3_handler { if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) { getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_id3v2 = new getid3_id3v2($getid3_temp); $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8; if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) { @@ -1077,6 +1086,7 @@ class getid3_riff extends getid3_handler { $thisfile_audio_dataformat = '8svx'; $thisfile_audio['bits_per_sample'] = 8; $thisfile_audio['channels'] = 1; // overridden below, if need be + $ActualBitsPerSample = 0; if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) { $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8; @@ -1114,7 +1124,7 @@ class getid3_riff extends getid3_handler { break; default: - $this->warning('Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"'); + $this->warning('Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.$thisfile_riff_RIFFsubtype_VHDR_0['sCompression'].'"'); break; } } @@ -1159,7 +1169,7 @@ class getid3_riff extends getid3_handler { getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, true); $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_mpeg = new getid3_mpeg($getid3_temp); $getid3_mpeg->Analyze(); if (empty($getid3_temp->info['error'])) { @@ -1245,7 +1255,7 @@ class getid3_riff extends getid3_handler { getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_id3v2 = new getid3_id3v2($getid3_temp); $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['id3 '][0]['offset'] + 8; if ($thisfile_riff[$RIFFsubtype]['id3 '][0]['valid'] = $getid3_id3v2->Analyze()) { @@ -1372,6 +1382,15 @@ class getid3_riff extends getid3_handler { return true; } + /** + * @param int $startoffset + * @param int $maxoffset + * + * @return array|false + * + * @throws Exception + * @throws getid3_exception + */ public function ParseRIFFAMV($startoffset, $maxoffset) { // AMV files are RIFF-AVI files with parts of the spec deliberately broken, such as chunk size fields hardcoded to zero (because players known in hardware that these fields are always a certain size @@ -1480,7 +1499,13 @@ class getid3_riff extends getid3_handler { return $RIFFchunk; } - + /** + * @param int $startoffset + * @param int $maxoffset + * + * @return array|false + * @throws getid3_exception + */ public function ParseRIFF($startoffset, $maxoffset) { $info = &$this->getid3->info; @@ -1529,7 +1554,7 @@ class getid3_riff extends getid3_handler { // MP3 if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; $getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__); @@ -1551,7 +1576,7 @@ class getid3_riff extends getid3_handler { // AC3 $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; $getid3_ac3 = new getid3_ac3($getid3_temp); @@ -1612,7 +1637,7 @@ class getid3_riff extends getid3_handler { // Probably is MP3 data if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) { $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__); @@ -1629,7 +1654,7 @@ class getid3_riff extends getid3_handler { // This is probably AC-3 data $getid3_temp = new getID3(); if ($isRegularAC3) { - $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; } @@ -1663,7 +1688,7 @@ class getid3_riff extends getid3_handler { // This is probably DTS data $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_dts = new getid3_dts($getid3_temp); $getid3_dts->Analyze(); @@ -1731,8 +1756,77 @@ class getid3_riff extends getid3_handler { // $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunksize)); // break; + case 'scot': + // https://cmsdk.com/node-js/adding-scot-chunk-to-wav-file.html + $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); + $RIFFchunk[$chunkname][$thisindex]['parsed']['alter'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 0, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['attrib'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 1, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['artnum'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 2, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['title'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 4, 43); // "name" in other documentation + $RIFFchunk[$chunkname][$thisindex]['parsed']['copy'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 47, 4); + $RIFFchunk[$chunkname][$thisindex]['parsed']['padd'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 51, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['asclen'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 52, 5); + $RIFFchunk[$chunkname][$thisindex]['parsed']['startseconds'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 57, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['starthundredths'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 59, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['endseconds'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 61, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['endhundreths'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 63, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['sdate'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 65, 6); + $RIFFchunk[$chunkname][$thisindex]['parsed']['kdate'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 71, 6); + $RIFFchunk[$chunkname][$thisindex]['parsed']['start_hr'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 77, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['kill_hr'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 78, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['digital'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 79, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 80, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['stereo'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 82, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['compress'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 83, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['eomstrt'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 84, 4)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['eomlen'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 88, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['attrib2'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 90, 4)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['future1'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 94, 12); + $RIFFchunk[$chunkname][$thisindex]['parsed']['catfontcolor'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 106, 4)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['catcolor'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 110, 4)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['segeompos'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 114, 4)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['vt_startsecs'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 118, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['vt_starthunds'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 120, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['priorcat'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 122, 3); + $RIFFchunk[$chunkname][$thisindex]['parsed']['priorcopy'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 125, 4); + $RIFFchunk[$chunkname][$thisindex]['parsed']['priorpadd'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 129, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['postcat'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 130, 3); + $RIFFchunk[$chunkname][$thisindex]['parsed']['postcopy'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 133, 4); + $RIFFchunk[$chunkname][$thisindex]['parsed']['postpadd'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 137, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['hrcanplay'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 138, 21); + $RIFFchunk[$chunkname][$thisindex]['parsed']['future2'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 159, 108); + $RIFFchunk[$chunkname][$thisindex]['parsed']['artist'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 267, 34); + $RIFFchunk[$chunkname][$thisindex]['parsed']['comment'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 301, 34); // "trivia" in other documentation + $RIFFchunk[$chunkname][$thisindex]['parsed']['intro'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 335, 2); + $RIFFchunk[$chunkname][$thisindex]['parsed']['end'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 337, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['year'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 338, 4); + $RIFFchunk[$chunkname][$thisindex]['parsed']['obsolete2'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 342, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['rec_hr'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 343, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['rdate'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 344, 6); + $RIFFchunk[$chunkname][$thisindex]['parsed']['mpeg_bitrate'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 350, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['pitch'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 352, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['playlevel'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 354, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['lenvalid'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 356, 1); + $RIFFchunk[$chunkname][$thisindex]['parsed']['filelength'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 357, 4)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['newplaylevel'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 361, 2)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['chopsize'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 363, 4)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['vteomovr'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 367, 4)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['desiredlen'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 371, 4)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['triggers'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 375, 4)); + $RIFFchunk[$chunkname][$thisindex]['parsed']['fillout'] = substr($RIFFchunk[$chunkname][$thisindex]['data'], 379, 33); + + foreach (array('title', 'artist', 'comment') as $key) { + if (trim($RIFFchunk[$chunkname][$thisindex]['parsed'][$key])) { + $info['riff']['comments'][$key] = array($RIFFchunk[$chunkname][$thisindex]['parsed'][$key]); + } + } + if ($RIFFchunk[$chunkname][$thisindex]['parsed']['filelength'] && !empty($info['filesize']) && ($RIFFchunk[$chunkname][$thisindex]['parsed']['filelength'] != $info['filesize'])) { + $this->warning('RIFF.WAVE.scot.filelength ('.$RIFFchunk[$chunkname][$thisindex]['parsed']['filelength'].') different from actual filesize ('.$info['filesize'].')'); + } + break; + default: - if (!empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { + if (!empty($LISTchunkParent) && isset($LISTchunkMaxOffset) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; unset($RIFFchunk[$chunkname][$thisindex]['offset']); @@ -1767,6 +1861,11 @@ class getid3_riff extends getid3_handler { return $RIFFchunk; } + /** + * @param string $RIFFdata + * + * @return bool + */ public function ParseRIFFdata(&$RIFFdata) { $info = &$this->getid3->info; if ($RIFFdata) { @@ -1804,6 +1903,12 @@ class getid3_riff extends getid3_handler { return false; } + /** + * @param array $RIFFinfoArray + * @param array $CommentsTargetArray + * + * @return bool + */ public static function parseComments(&$RIFFinfoArray, &$CommentsTargetArray) { $RIFFinfoKeyLookup = array( 'IARL'=>'archivallocation', @@ -1863,8 +1968,14 @@ class getid3_riff extends getid3_handler { return true; } + /** + * @param string $WaveFormatExData + * + * @return array + */ public static function parseWAVEFORMATex($WaveFormatExData) { // shortcut + $WaveFormatEx = array(); $WaveFormatEx['raw'] = array(); $WaveFormatEx_raw = &$WaveFormatEx['raw']; @@ -1888,6 +1999,11 @@ class getid3_riff extends getid3_handler { return $WaveFormatEx; } + /** + * @param string $WavPackChunkData + * + * @return bool + */ public function parseWavPackHeader($WavPackChunkData) { // typedef struct { // char ckID [4]; @@ -1949,6 +2065,12 @@ class getid3_riff extends getid3_handler { return true; } + /** + * @param string $BITMAPINFOHEADER + * @param bool $littleEndian + * + * @return array + */ public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) { $parsed['biSize'] = substr($BITMAPINFOHEADER, 0, 4); // number of bytes required by the BITMAPINFOHEADER structure @@ -1968,6 +2090,12 @@ class getid3_riff extends getid3_handler { return $parsed; } + /** + * @param string $DIVXTAG + * @param bool $raw + * + * @return array + */ public static function ParseDIVXTAG($DIVXTAG, $raw=false) { // structure from "IDivX" source, Form1.frm, by "Greg Frazier of Daemonic Software Group", email: gfrazier@icestorm.net, web: http://dsg.cjb.net/ // source available at http://files.divx-digest.com/download/c663efe7ef8ad2e90bf4af4d3ea6188a/on0SWN2r/edit/IDivX.zip @@ -2014,6 +2142,7 @@ class getid3_riff extends getid3_handler { 5 => 'NC-17', ); + $parsed = array(); $parsed['title'] = trim(substr($DIVXTAG, 0, 32)); $parsed['artist'] = trim(substr($DIVXTAG, 32, 28)); $parsed['year'] = intval(trim(substr($DIVXTAG, 60, 4))); @@ -2029,8 +2158,8 @@ class getid3_riff extends getid3_handler { if (!$raw) { unset($parsed['genre_id'], $parsed['rating_id']); foreach ($parsed as $key => $value) { - if (!$value === '') { - unset($parsed['key']); + if (empty($value)) { + unset($parsed[$key]); } } } @@ -2042,6 +2171,11 @@ class getid3_riff extends getid3_handler { return $parsed; } + /** + * @param string $tagshortname + * + * @return string + */ public static function waveSNDMtagLookup($tagshortname) { $begin = __LINE__; @@ -2065,6 +2199,11 @@ class getid3_riff extends getid3_handler { return getid3_lib::EmbeddedLookup($tagshortname, $begin, __LINE__, __FILE__, 'riff-sndm'); } + /** + * @param int $wFormatTag + * + * @return string + */ public static function wFormatTagLookup($wFormatTag) { $begin = __LINE__; @@ -2234,6 +2373,11 @@ class getid3_riff extends getid3_handler { return getid3_lib::EmbeddedLookup('0x'.str_pad(strtoupper(dechex($wFormatTag)), 4, '0', STR_PAD_LEFT), $begin, __LINE__, __FILE__, 'riff-wFormatTag'); } + /** + * @param string $fourcc + * + * @return string + */ public static function fourccLookup($fourcc) { $begin = __LINE__; @@ -2628,6 +2772,12 @@ class getid3_riff extends getid3_handler { return getid3_lib::EmbeddedLookup($fourcc, $begin, __LINE__, __FILE__, 'riff-fourcc'); } + /** + * @param string $byteword + * @param bool $signed + * + * @return int|float|false + */ private function EitherEndian2Int($byteword, $signed=false) { if ($this->container == 'riff') { return getid3_lib::LittleEndian2Int($byteword, $signed); diff --git a/wp-includes/ID3/module.audio.ac3.php b/wp-includes/ID3/module.audio.ac3.php index 27c328d3d3..364681b632 100644 --- a/wp-includes/ID3/module.audio.ac3.php +++ b/wp-includes/ID3/module.audio.ac3.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.audio.ac3.php // @@ -17,11 +17,21 @@ class getid3_ac3 extends getid3_handler { - private $AC3header = array(); - private $BSIoffset = 0; + /** + * @var array + */ + private $AC3header = array(); - const syncword = 0x0B77; + /** + * @var int + */ + private $BSIoffset = 0; + const syncword = 0x0B77; + + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -187,7 +197,7 @@ class getid3_ac3 extends getid3_handler } elseif ($thisfile_ac3_raw_bsi['bsid'] <= 16) { // E-AC3 -$this->error('E-AC3 parsing is incomplete and experimental in this version of getID3 ('.$this->getid3->version().'). Notably the bitrate calculations are wrong -- value might (or not) be correct, but it is not calculated correctly. Email info@getid3.org if you know how to calculate EAC3 bitrate correctly.'); + $this->error('E-AC3 parsing is incomplete and experimental in this version of getID3 ('.$this->getid3->version().'). Notably the bitrate calculations are wrong -- value might (or not) be correct, but it is not calculated correctly. Email info@getid3.org if you know how to calculate EAC3 bitrate correctly.'); $info['audio']['dataformat'] = 'eac3'; $thisfile_ac3_raw_bsi['strmtyp'] = $this->readHeaderBSI(2); @@ -412,7 +422,7 @@ $this->error('E-AC3 parsing is incomplete and experimental in this version of ge } else { $this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 16. Please submit a support ticket with a sample file.'); - unset($info['ac3']); + unset($info['ac3']); return false; } @@ -431,15 +441,17 @@ $this->error('E-AC3 parsing is incomplete and experimental in this version of ge $thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw_bsi['frmsizecod'], $thisfile_ac3_raw_bsi['fscod']); $thisfile_ac3['bitrate'] = self::bitrateLookup($thisfile_ac3_raw_bsi['frmsizecod']); } elseif (!empty($thisfile_ac3_raw_bsi['frmsiz'])) { -// this isn't right, but it's (usually) close, roughly 5% less than it should be. -// but WHERE is the actual bitrate value stored in EAC3?? email info@getid3.org if you know! + // this isn't right, but it's (usually) close, roughly 5% less than it should be. + // but WHERE is the actual bitrate value stored in EAC3?? email info@getid3.org if you know! $thisfile_ac3['bitrate'] = ($thisfile_ac3_raw_bsi['frmsiz'] + 1) * 16 * 30; // The frmsiz field shall contain a value one less than the overall size of the coded syncframe in 16-bit words. That is, this field may assume a value ranging from 0 to 2047, and these values correspond to syncframe sizes ranging from 1 to 2048. -// kludge-fix to make it approximately the expected value, still not "right": -$thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16000; + // kludge-fix to make it approximately the expected value, still not "right": + $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16000; } $info['audio']['bitrate'] = $thisfile_ac3['bitrate']; - $thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); + if (isset($thisfile_ac3_raw_bsi['bsmod']) && isset($thisfile_ac3_raw_bsi['acmod'])) { + $thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); + } $ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']); foreach($ac3_coding_mode as $key => $value) { $thisfile_ac3[$key] = $value; @@ -470,6 +482,11 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return true; } + /** + * @param int $length + * + * @return float|int + */ private function readHeaderBSI($length) { $data = substr($this->AC3header['bsi'], $this->BSIoffset, $length); $this->BSIoffset += $length; @@ -477,6 +494,11 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return bindec($data); } + /** + * @param int $fscod + * + * @return int|string|false + */ public static function sampleRateCodeLookup($fscod) { static $sampleRateCodeLookup = array( 0 => 48000, @@ -487,6 +509,11 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false); } + /** + * @param int $fscod2 + * + * @return int|string|false + */ public static function sampleRateCodeLookup2($fscod2) { static $sampleRateCodeLookup2 = array( 0 => 24000, @@ -497,6 +524,12 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return (isset($sampleRateCodeLookup2[$fscod2]) ? $sampleRateCodeLookup2[$fscod2] : false); } + /** + * @param int $bsmod + * @param int $acmod + * + * @return string|false + */ public static function serviceTypeLookup($bsmod, $acmod) { static $serviceTypeLookup = array(); if (empty($serviceTypeLookup)) { @@ -518,6 +551,11 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return (isset($serviceTypeLookup[$bsmod][$acmod]) ? $serviceTypeLookup[$bsmod][$acmod] : false); } + /** + * @param int $acmod + * + * @return array|false + */ public static function audioCodingModeLookup($acmod) { // array(channel configuration, # channels (not incl LFE), channel order) static $audioCodingModeLookup = array ( @@ -533,6 +571,11 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false); } + /** + * @param int $cmixlev + * + * @return int|float|string|false + */ public static function centerMixLevelLookup($cmixlev) { static $centerMixLevelLookup; if (empty($centerMixLevelLookup)) { @@ -546,6 +589,11 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return (isset($centerMixLevelLookup[$cmixlev]) ? $centerMixLevelLookup[$cmixlev] : false); } + /** + * @param int $surmixlev + * + * @return int|float|string|false + */ public static function surroundMixLevelLookup($surmixlev) { static $surroundMixLevelLookup; if (empty($surroundMixLevelLookup)) { @@ -559,6 +607,11 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return (isset($surroundMixLevelLookup[$surmixlev]) ? $surroundMixLevelLookup[$surmixlev] : false); } + /** + * @param int $dsurmod + * + * @return string|false + */ public static function dolbySurroundModeLookup($dsurmod) { static $dolbySurroundModeLookup = array( 0 => 'not indicated', @@ -569,12 +622,18 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return (isset($dolbySurroundModeLookup[$dsurmod]) ? $dolbySurroundModeLookup[$dsurmod] : false); } + /** + * @param int $acmod + * @param bool $lfeon + * + * @return array + */ public static function channelsEnabledLookup($acmod, $lfeon) { $lookup = array( - 'ch1'=>(bool) ($acmod == 0), - 'ch2'=>(bool) ($acmod == 0), - 'left'=>(bool) ($acmod > 1), - 'right'=>(bool) ($acmod > 1), + 'ch1'=>($acmod == 0), + 'ch2'=>($acmod == 0), + 'left'=>($acmod > 1), + 'right'=>($acmod > 1), 'center'=>(bool) ($acmod & 0x01), 'surround_mono'=>false, 'surround_left'=>false, @@ -594,6 +653,11 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return $lookup; } + /** + * @param int $compre + * + * @return float|int + */ public static function heavyCompression($compre) { // The first four bits indicate gain changes in 6.02dB increments which can be // implemented with an arithmetic shift operation. The following four bits @@ -623,7 +687,7 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 // -8 -42.14 dB $fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT); - if ($fourbit{0} == '1') { + if ($fourbit[0] == '1') { $log_gain = -8 + bindec(substr($fourbit, 1)); } else { $log_gain = bindec(substr($fourbit, 1)); @@ -644,6 +708,11 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return $log_gain - $lin_gain; } + /** + * @param int $roomtyp + * + * @return string|false + */ public static function roomTypeLookup($roomtyp) { static $roomTypeLookup = array( 0 => 'not indicated', @@ -654,6 +723,12 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return (isset($roomTypeLookup[$roomtyp]) ? $roomTypeLookup[$roomtyp] : false); } + /** + * @param int $frmsizecod + * @param int $fscod + * + * @return int|false + */ public static function frameSizeLookup($frmsizecod, $fscod) { // LSB is whether padding is used or not $padding = (bool) ($frmsizecod & 0x01); @@ -683,13 +758,20 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 18 => array(2560, 2786, 3840) // 640 kbps ); } + $paddingBytes = 0; if (($fscod == 1) && $padding) { // frame lengths are padded by 1 word (16 bits) at 44100 - $frameSizeLookup[$frmsizecod] += 2; + // (fscode==1) means 44100Hz (see sampleRateCodeLookup) + $paddingBytes = 2; } - return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] : false); + return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] + $paddingBytes : false); } + /** + * @param int $frmsizecod + * + * @return int|false + */ public static function bitrateLookup($frmsizecod) { // LSB is whether padding is used or not $padding = (bool) ($frmsizecod & 0x01); @@ -719,6 +801,11 @@ $thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16 return (isset($bitrateLookup[$framesizeid]) ? $bitrateLookup[$framesizeid] : false); } + /** + * @param int $numblkscod + * + * @return int|false + */ public static function blocksPerSyncFrame($numblkscod) { static $blocksPerSyncFrameLookup = array( 0 => 1, diff --git a/wp-includes/ID3/module.audio.dts.php b/wp-includes/ID3/module.audio.dts.php index bdc78f0b5e..9be8f1ba36 100644 --- a/wp-includes/ID3/module.audio.dts.php +++ b/wp-includes/ID3/module.audio.dts.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.audio.dts.php // @@ -21,21 +21,27 @@ class getid3_dts extends getid3_handler { /** - * Default DTS syncword used in native .cpt or .dts formats - */ - const syncword = "\x7F\xFE\x80\x01"; + * Default DTS syncword used in native .cpt or .dts formats. + */ + const syncword = "\x7F\xFE\x80\x01"; + /** + * @var int + */ private $readBinDataOffset = 0; - /** - * Possible syncwords indicating bitstream encoding - */ - public static $syncwords = array( - 0 => "\x7F\xFE\x80\x01", // raw big-endian - 1 => "\xFE\x7F\x01\x80", // raw little-endian - 2 => "\x1F\xFF\xE8\x00", // 14-bit big-endian - 3 => "\xFF\x1F\x00\xE8"); // 14-bit little-endian + /** + * Possible syncwords indicating bitstream encoding. + */ + public static $syncwords = array( + 0 => "\x7F\xFE\x80\x01", // raw big-endian + 1 => "\xFE\x7F\x01\x80", // raw little-endian + 2 => "\x1F\xFF\xE8\x00", // 14-bit big-endian + 3 => "\xFF\x1F\x00\xE8"); // 14-bit little-endian + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'dts'; @@ -45,18 +51,18 @@ class getid3_dts extends getid3_handler // check syncword $sync = substr($DTSheader, 0, 4); - if (($encoding = array_search($sync, self::$syncwords)) !== false) { + if (($encoding = array_search($sync, self::$syncwords)) !== false) { - $info['dts']['raw']['magic'] = $sync; + $info['dts']['raw']['magic'] = $sync; $this->readBinDataOffset = 32; - } elseif ($this->isDependencyFor('matroska')) { + } elseif ($this->isDependencyFor('matroska')) { // Matroska contains DTS without syncword encoded as raw big-endian format $encoding = 0; $this->readBinDataOffset = 0; - } else { + } else { unset($info['fileformat']); return $this->error('Expecting "'.implode('| ', array_map('getid3_lib::PrintHexBytes', self::$syncwords)).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($sync).'"'); @@ -139,6 +145,12 @@ class getid3_dts extends getid3_handler return true; } + /** + * @param string $bin + * @param int $length + * + * @return float|int + */ private function readBinData($bin, $length) { $data = substr($bin, $this->readBinDataOffset, $length); $this->readBinDataOffset += $length; @@ -146,6 +158,11 @@ class getid3_dts extends getid3_handler return bindec($data); } + /** + * @param int $index + * + * @return int|string|false + */ public static function bitrateLookup($index) { static $lookup = array( 0 => 32000, @@ -184,6 +201,11 @@ class getid3_dts extends getid3_handler return (isset($lookup[$index]) ? $lookup[$index] : false); } + /** + * @param int $index + * + * @return int|string|false + */ public static function sampleRateLookup($index) { static $lookup = array( 0 => 'invalid', @@ -206,6 +228,11 @@ class getid3_dts extends getid3_handler return (isset($lookup[$index]) ? $lookup[$index] : false); } + /** + * @param int $index + * + * @return int|false + */ public static function bitPerSampleLookup($index) { static $lookup = array( 0 => 16, @@ -216,6 +243,11 @@ class getid3_dts extends getid3_handler return (isset($lookup[$index]) ? $lookup[$index] : false); } + /** + * @param int $index + * + * @return int|false + */ public static function numChannelsLookup($index) { switch ($index) { case 0: @@ -254,6 +286,11 @@ class getid3_dts extends getid3_handler return false; } + /** + * @param int $index + * + * @return string + */ public static function channelArrangementLookup($index) { static $lookup = array( 0 => 'A', @@ -276,6 +313,12 @@ class getid3_dts extends getid3_handler return (isset($lookup[$index]) ? $lookup[$index] : 'user-defined'); } + /** + * @param int $index + * @param int $version + * + * @return int|false + */ public static function dialogNormalization($index, $version) { switch ($version) { case 7: diff --git a/wp-includes/ID3/module.audio.flac.php b/wp-includes/ID3/module.audio.flac.php index 348cce328d..a37bae9b8f 100644 --- a/wp-includes/ID3/module.audio.flac.php +++ b/wp-includes/ID3/module.audio.flac.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.audio.flac.php // @@ -24,6 +24,9 @@ class getid3_flac extends getid3_handler { const syncword = 'fLaC'; + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -41,22 +44,30 @@ class getid3_flac extends getid3_handler return $this->parseMETAdata(); } + /** + * @return bool + */ public function parseMETAdata() { $info = &$this->getid3->info; do { $BlockOffset = $this->ftell(); $BlockHeader = $this->fread(4); - $LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1)); + $LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1)); // LBFBT = LastBlockFlag + BlockType $LastBlockFlag = (bool) ($LBFBT & 0x80); $BlockType = ($LBFBT & 0x7F); $BlockLength = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3)); $BlockTypeText = self::metaBlockTypeLookup($BlockType); if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) { - $this->error('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file'); + $this->warning('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file'); break; } if ($BlockLength < 1) { + if ($BlockTypeText != 'reserved') { + // probably supposed to be zero-length + $this->warning('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockTypeText.') at offset '.$BlockOffset.' is zero bytes'); + continue; + } $this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid'); break; } @@ -167,7 +178,7 @@ class getid3_flac extends getid3_handler if (isset($info['flac']['STREAMINFO']['audio_signature'])) { if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { - $this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'); + $this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'); } else { $info['md5_data_source'] = ''; @@ -194,12 +205,14 @@ class getid3_flac extends getid3_handler return true; } - private function parseSTREAMINFO($BlockData) { - $info = &$this->getid3->info; - - $info['flac']['STREAMINFO'] = array(); - $streaminfo = &$info['flac']['STREAMINFO']; + /** + * @param string $BlockData + * + * @return array + */ + public static function parseSTREAMINFOdata($BlockData) { + $streaminfo = array(); $streaminfo['min_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2)); $streaminfo['max_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2)); $streaminfo['min_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3)); @@ -211,15 +224,28 @@ class getid3_flac extends getid3_handler $streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1; $streaminfo['samples_stream'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36)); - $streaminfo['audio_signature'] = substr($BlockData, 18, 16); + $streaminfo['audio_signature'] = substr($BlockData, 18, 16); - if (!empty($streaminfo['sample_rate'])) { + return $streaminfo; + } + + /** + * @param string $BlockData + * + * @return bool + */ + private function parseSTREAMINFO($BlockData) { + $info = &$this->getid3->info; + + $info['flac']['STREAMINFO'] = self::parseSTREAMINFOdata($BlockData); + + if (!empty($info['flac']['STREAMINFO']['sample_rate'])) { $info['audio']['bitrate_mode'] = 'vbr'; - $info['audio']['sample_rate'] = $streaminfo['sample_rate']; - $info['audio']['channels'] = $streaminfo['channels']; - $info['audio']['bits_per_sample'] = $streaminfo['bits_per_sample']; - $info['playtime_seconds'] = $streaminfo['samples_stream'] / $streaminfo['sample_rate']; + $info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate']; + $info['audio']['channels'] = $info['flac']['STREAMINFO']['channels']; + $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; + $info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate']; if ($info['playtime_seconds'] > 0) { if (!$this->isDependencyFor('matroska')) { $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; @@ -236,6 +262,11 @@ class getid3_flac extends getid3_handler return true; } + /** + * @param string $BlockData + * + * @return bool + */ private function parseAPPLICATION($BlockData) { $info = &$this->getid3->info; @@ -246,6 +277,11 @@ class getid3_flac extends getid3_handler return true; } + /** + * @param string $BlockData + * + * @return bool + */ private function parseSEEKTABLE($BlockData) { $info = &$this->getid3->info; @@ -275,6 +311,11 @@ class getid3_flac extends getid3_handler return true; } + /** + * @param string $BlockData + * + * @return bool + */ private function parseVORBIS_COMMENT($BlockData) { $info = &$this->getid3->info; @@ -294,6 +335,11 @@ class getid3_flac extends getid3_handler return true; } + /** + * @param string $BlockData + * + * @return bool + */ private function parseCUESHEET($BlockData) { $info = &$this->getid3->info; $offset = 0; @@ -346,9 +392,11 @@ class getid3_flac extends getid3_handler } /** - * Parse METADATA_BLOCK_PICTURE flac structure and extract attachment - * External usage: audio.ogg - */ + * Parse METADATA_BLOCK_PICTURE flac structure and extract attachment + * External usage: audio.ogg + * + * @return bool + */ public function parsePICTURE() { $info = &$this->getid3->info; @@ -380,6 +428,11 @@ class getid3_flac extends getid3_handler return true; } + /** + * @param int $blocktype + * + * @return string + */ public static function metaBlockTypeLookup($blocktype) { static $lookup = array( 0 => 'STREAMINFO', @@ -393,6 +446,11 @@ class getid3_flac extends getid3_handler return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved'); } + /** + * @param int $applicationid + * + * @return string + */ public static function applicationIDLookup($applicationid) { // http://flac.sourceforge.net/id.html static $lookup = array( @@ -423,6 +481,11 @@ class getid3_flac extends getid3_handler return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved'); } + /** + * @param int $type_id + * + * @return string + */ public static function pictureTypeLookup($type_id) { static $lookup = array ( 0 => 'Other', diff --git a/wp-includes/ID3/module.audio.mp3.php b/wp-includes/ID3/module.audio.mp3.php index ed2d84434c..4bd75a0219 100644 --- a/wp-includes/ID3/module.audio.mp3.php +++ b/wp-includes/ID3/module.audio.mp3.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.audio.mp3.php // @@ -24,9 +24,17 @@ define('GETID3_MP3_VALID_CHECK_FRAMES', 35); class getid3_mp3 extends getid3_handler { + /** + * 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-unusable files. + * + * @var bool + */ + public $allow_bruteforce = false; - 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 - + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -35,7 +43,7 @@ class getid3_mp3 extends getid3_handler if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) { if ($this->allow_bruteforce) { $this->error('Rescanning file in BruteForce mode'); - $this->getOnlyMPEGaudioInfoBruteForce($this->getid3->fp, $info); + $this->getOnlyMPEGaudioInfoBruteForce(); } } @@ -152,7 +160,11 @@ class getid3_mp3 extends getid3_handler // Calculate playtime if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) { - $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['audio']['bitrate']; + // https://github.com/JamesHeinrich/getID3/issues/161 + // VBR header frame contains ~0.026s of silent audio data, but is not actually part of the original encoding and should be ignored + $xingVBRheaderFrameLength = ((isset($info['mpeg']['audio']['VBR_frames']) && isset($info['mpeg']['audio']['framelength'])) ? $info['mpeg']['audio']['framelength'] : 0); + + $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset'] - $xingVBRheaderFrameLength) * 8 / $info['audio']['bitrate']; } $info['audio']['encoder_options'] = $this->GuessEncoderOptions(); @@ -160,10 +172,14 @@ class getid3_mp3 extends getid3_handler return true; } - + /** + * @return string + */ public function GuessEncoderOptions() { // shortcuts $info = &$this->getid3->info; + $thisfile_mpeg_audio = array(); + $thisfile_mpeg_audio_lame = array(); if (!empty($info['mpeg']['audio'])) { $thisfile_mpeg_audio = &$info['mpeg']['audio']; if (!empty($thisfile_mpeg_audio['LAME'])) { @@ -178,7 +194,7 @@ class getid3_mp3 extends getid3_handler $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality']; - } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) { + } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && isset($thisfile_mpeg_audio_lame['preset_used_id']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) { $encoder_options = $thisfile_mpeg_audio_lame['preset_used']; @@ -404,7 +420,15 @@ class getid3_mp3 extends getid3_handler return $encoder_options; } - + /** + * @param int $offset + * @param array $info + * @param bool $recursivesearch + * @param bool $ScanAsCBR + * @param bool $FastMPEGheaderScan + * + * @return bool + */ public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; @@ -458,7 +482,6 @@ class getid3_mp3 extends getid3_handler } $thisfile_mpeg_audio = &$info['mpeg']['audio']; - if ($MPEGaudioHeaderValidCache[$head4_key]) { $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray; } else { @@ -562,7 +585,7 @@ class getid3_mp3 extends getid3_handler $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; $thisfile_mpeg_audio['VBR_method'] = 'Fraunhofer'; - $info['audio']['codec'] = 'Fraunhofer'; + $info['audio']['codec'] = 'Fraunhofer'; $SideInfoData = substr($headerstring, 4 + 2, 32); @@ -655,7 +678,7 @@ class getid3_mp3 extends getid3_handler $used_filesize = $thisfile_mpeg_audio['VBR_bytes']; } elseif (!empty($info['filesize'])) { $used_filesize = $info['filesize']; - $used_filesize -= intval(@$info['id3v2']['headerlength']); + $used_filesize -= (isset($info['id3v2']['headerlength']) ? intval($info['id3v2']['headerlength']) : 0); $used_filesize -= (isset($info['id3v1']) ? 128 : 0); $used_filesize -= (isset($info['tag_offset_end']) ? $info['tag_offset_end'] - $info['tag_offset_start'] : 0); $this->warning('MP3.Xing header missing VBR_bytes, assuming MPEG audio portion of file is '.number_format($used_filesize).' bytes'); @@ -678,7 +701,7 @@ class getid3_mp3 extends getid3_handler if ($thisfile_mpeg_audio['xing_flags']['toc']) { $LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100); for ($i = 0; $i < 100; $i++) { - $thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData{$i}); + $thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData[$i]); } } if ($thisfile_mpeg_audio['xing_flags']['vbr_scale']) { @@ -1083,6 +1106,13 @@ class getid3_mp3 extends getid3_handler return true; } + /** + * @param int $offset + * @param int $nextframetestoffset + * @param bool $ScanAsCBR + * + * @return bool + */ public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) { $info = &$this->getid3->info; $firstframetestarray = array('error' => array(), 'warning'=> array(), 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']); @@ -1129,6 +1159,12 @@ class getid3_mp3 extends getid3_handler return true; } + /** + * @param int $offset + * @param bool $deepscan + * + * @return int|false + */ public function FreeFormatFrameLength($offset, $deepscan=false) { $info = &$this->getid3->info; @@ -1137,9 +1173,9 @@ class getid3_mp3 extends getid3_handler $SyncPattern1 = substr($MPEGaudioData, 0, 4); // may be different pattern due to padding - $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) | 0x02).$SyncPattern1{3}; + $SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) | 0x02).$SyncPattern1[3]; if ($SyncPattern2 === $SyncPattern1) { - $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) & 0xFD).$SyncPattern1{3}; + $SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) & 0xFD).$SyncPattern1[3]; } $framelength = false; @@ -1206,6 +1242,9 @@ class getid3_mp3 extends getid3_handler return $framelength; } + /** + * @return bool + */ public function getOnlyMPEGaudioInfoBruteForce() { $MPEGaudioHeaderDecodeCache = array(); $MPEGaudioHeaderValidCache = array(); @@ -1241,9 +1280,9 @@ class getid3_mp3 extends getid3_handler if (strlen($head4) < 4) { break; } - if ($head4{0} != "\xFF") { + if ($head4[0] != "\xFF") { for ($i = 1; $i < 4; $i++) { - if ($head4{$i} == "\xFF") { + if ($head4[$i] == "\xFF") { $this->fseek($i - 4, SEEK_CUR); continue 2; } @@ -1275,7 +1314,7 @@ class getid3_mp3 extends getid3_handler $WhereWeWere = $this->ftell(); $this->fseek($MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); $next4 = $this->fread(4); - if ($next4{0} == "\xFF") { + if ($next4[0] == "\xFF") { if (!isset($MPEGaudioHeaderDecodeCache[$next4])) { $MPEGaudioHeaderDecodeCache[$next4] = self::MPEGaudioHeaderDecode($next4); } @@ -1353,7 +1392,12 @@ class getid3_mp3 extends getid3_handler return true; } - + /** + * @param int $avdataoffset + * @param bool $BitrateHistogram + * + * @return bool + */ public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) { // looks for synch, decodes MPEG audio header @@ -1363,10 +1407,9 @@ class getid3_mp3 extends getid3_handler static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; if (empty($MPEGaudioVersionLookup)) { - $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); - $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); - $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); - + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); } $this->fseek($avdataoffset); @@ -1416,7 +1459,8 @@ class getid3_mp3 extends getid3_handler return false; } - if (($header{$SynchSeekOffset} == "\xFF") && ($header{($SynchSeekOffset + 1)} > "\xE0")) { // synch detected + if (($header[$SynchSeekOffset] == "\xFF") && ($header[($SynchSeekOffset + 1)] > "\xE0")) { // synch detected + $FirstFrameAVDataOffset = null; if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) { $FirstFrameThisfileInfo = $info; $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset; @@ -1440,7 +1484,7 @@ class getid3_mp3 extends getid3_handler $info['audio']['dataformat'] = 'mp3'; break; } - if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) { + if (isset($FirstFrameThisfileInfo) && isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) { if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) { // If there is garbage data between a valid VBR header frame and a sequence // of valid MPEG-audio frames the VBR data is no longer discarded. @@ -1510,7 +1554,7 @@ class getid3_mp3 extends getid3_handler $this->fseek($scan_start_offset[$current_segment]); $buffer_4k = $this->fread(4096); for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) { - if (($buffer_4k{$j} == "\xFF") && ($buffer_4k{($j + 1)} > "\xE0")) { // synch detected + if (($buffer_4k[$j] == "\xFF") && ($buffer_4k[($j + 1)] > "\xE0")) { // synch detected if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) { $calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength']; if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) { @@ -1522,7 +1566,7 @@ class getid3_mp3 extends getid3_handler } } $synchstartoffset = $scan_start_offset[$current_segment]; - while ($this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) { + while (($synchstartoffset < $info['avdataend']) && $this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) { $FastMode = true; $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']]; @@ -1633,17 +1677,25 @@ class getid3_mp3 extends getid3_handler return true; } - + /** + * @return array + */ public static function MPEGaudioVersionArray() { static $MPEGaudioVersion = array('2.5', false, '2', '1'); return $MPEGaudioVersion; } + /** + * @return array + */ public static function MPEGaudioLayerArray() { static $MPEGaudioLayer = array(false, 3, 2, 1); return $MPEGaudioLayer; } + /** + * @return array + */ public static function MPEGaudioBitrateArray() { static $MPEGaudioBitrate; if (empty($MPEGaudioBitrate)) { @@ -1663,6 +1715,9 @@ class getid3_mp3 extends getid3_handler return $MPEGaudioBitrate; } + /** + * @return array + */ public static function MPEGaudioFrequencyArray() { static $MPEGaudioFrequency; if (empty($MPEGaudioFrequency)) { @@ -1675,11 +1730,17 @@ class getid3_mp3 extends getid3_handler return $MPEGaudioFrequency; } + /** + * @return array + */ public static function MPEGaudioChannelModeArray() { static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono'); return $MPEGaudioChannelMode; } + /** + * @return array + */ public static function MPEGaudioModeExtensionArray() { static $MPEGaudioModeExtension; if (empty($MPEGaudioModeExtension)) { @@ -1692,15 +1753,31 @@ class getid3_mp3 extends getid3_handler return $MPEGaudioModeExtension; } + /** + * @return array + */ public static function MPEGaudioEmphasisArray() { static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17'); return $MPEGaudioEmphasis; } + /** + * @param string $head4 + * @param bool $allowBitrate15 + * + * @return bool + */ public static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { return self::MPEGaudioHeaderValid(self::MPEGaudioHeaderDecode($head4), false, $allowBitrate15); } + /** + * @param array $rawarray + * @param bool $echoerrors + * @param bool $allowBitrate15 + * + * @return bool + */ public static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) { return false; @@ -1773,6 +1850,11 @@ class getid3_mp3 extends getid3_handler return true; } + /** + * @param string $Header4Bytes + * + * @return array|false + */ public static function MPEGaudioHeaderDecode($Header4Bytes) { // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM // A - Frame sync (all bits set) @@ -1794,22 +1876,31 @@ class getid3_mp3 extends getid3_handler } $MPEGrawHeader['synch'] = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4; - $MPEGrawHeader['version'] = (ord($Header4Bytes{1}) & 0x18) >> 3; // BB - $MPEGrawHeader['layer'] = (ord($Header4Bytes{1}) & 0x06) >> 1; // CC - $MPEGrawHeader['protection'] = (ord($Header4Bytes{1}) & 0x01); // D - $MPEGrawHeader['bitrate'] = (ord($Header4Bytes{2}) & 0xF0) >> 4; // EEEE - $MPEGrawHeader['sample_rate'] = (ord($Header4Bytes{2}) & 0x0C) >> 2; // FF - $MPEGrawHeader['padding'] = (ord($Header4Bytes{2}) & 0x02) >> 1; // G - $MPEGrawHeader['private'] = (ord($Header4Bytes{2}) & 0x01); // H - $MPEGrawHeader['channelmode'] = (ord($Header4Bytes{3}) & 0xC0) >> 6; // II - $MPEGrawHeader['modeextension'] = (ord($Header4Bytes{3}) & 0x30) >> 4; // JJ - $MPEGrawHeader['copyright'] = (ord($Header4Bytes{3}) & 0x08) >> 3; // K - $MPEGrawHeader['original'] = (ord($Header4Bytes{3}) & 0x04) >> 2; // L - $MPEGrawHeader['emphasis'] = (ord($Header4Bytes{3}) & 0x03); // MM + $MPEGrawHeader['version'] = (ord($Header4Bytes[1]) & 0x18) >> 3; // BB + $MPEGrawHeader['layer'] = (ord($Header4Bytes[1]) & 0x06) >> 1; // CC + $MPEGrawHeader['protection'] = (ord($Header4Bytes[1]) & 0x01); // D + $MPEGrawHeader['bitrate'] = (ord($Header4Bytes[2]) & 0xF0) >> 4; // EEEE + $MPEGrawHeader['sample_rate'] = (ord($Header4Bytes[2]) & 0x0C) >> 2; // FF + $MPEGrawHeader['padding'] = (ord($Header4Bytes[2]) & 0x02) >> 1; // G + $MPEGrawHeader['private'] = (ord($Header4Bytes[2]) & 0x01); // H + $MPEGrawHeader['channelmode'] = (ord($Header4Bytes[3]) & 0xC0) >> 6; // II + $MPEGrawHeader['modeextension'] = (ord($Header4Bytes[3]) & 0x30) >> 4; // JJ + $MPEGrawHeader['copyright'] = (ord($Header4Bytes[3]) & 0x08) >> 3; // K + $MPEGrawHeader['original'] = (ord($Header4Bytes[3]) & 0x04) >> 2; // L + $MPEGrawHeader['emphasis'] = (ord($Header4Bytes[3]) & 0x03); // MM return $MPEGrawHeader; } + /** + * @param int|string $bitrate + * @param string $version + * @param string $layer + * @param bool $padding + * @param int $samplerate + * + * @return int|false + */ public static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { static $AudioFrameLengthCache = array(); @@ -1871,6 +1962,11 @@ class getid3_mp3 extends getid3_handler return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate]; } + /** + * @param float|int $bit_rate + * + * @return int|float|string + */ public static function ClosestStandardMP3Bitrate($bit_rate) { static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000); static $bit_rate_table = array (0=>'-'); @@ -1891,10 +1987,16 @@ class getid3_mp3 extends getid3_handler return $bit_rate_table[$round_bit_rate]; } + /** + * @param string $version + * @param string $channelmode + * + * @return int + */ public static function XingVBRidOffset($version, $channelmode) { static $XingVBRidOffsetCache = array(); - if (empty($XingVBRidOffset)) { - $XingVBRidOffset = array ( + if (empty($XingVBRidOffsetCache)) { + $XingVBRidOffsetCache = array ( '1' => array ('mono' => 0x15, // 4 + 17 = 21 'stereo' => 0x24, // 4 + 32 = 36 'joint stereo' => 0x24, @@ -1914,9 +2016,14 @@ class getid3_mp3 extends getid3_handler ) ); } - return $XingVBRidOffset[$version][$channelmode]; + return $XingVBRidOffsetCache[$version][$channelmode]; } + /** + * @param int $VBRmethodID + * + * @return string + */ public static function LAMEvbrMethodLookup($VBRmethodID) { static $LAMEvbrMethodLookup = array( 0x00 => 'unknown', @@ -1933,6 +2040,11 @@ class getid3_mp3 extends getid3_handler return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : ''); } + /** + * @param int $StereoModeID + * + * @return string + */ public static function LAMEmiscStereoModeLookup($StereoModeID) { static $LAMEmiscStereoModeLookup = array( 0 => 'mono', @@ -1947,6 +2059,11 @@ class getid3_mp3 extends getid3_handler return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : ''); } + /** + * @param int $SourceSampleFrequencyID + * + * @return string + */ public static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { static $LAMEmiscSourceSampleFrequencyLookup = array( 0 => '<= 32 kHz', @@ -1957,6 +2074,11 @@ class getid3_mp3 extends getid3_handler return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : ''); } + /** + * @param int $SurroundInfoID + * + * @return string + */ public static function LAMEsurroundInfoLookup($SurroundInfoID) { static $LAMEsurroundInfoLookup = array( 0 => 'no surround info', @@ -1967,6 +2089,11 @@ class getid3_mp3 extends getid3_handler return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved'); } + /** + * @param array $LAMEtag + * + * @return string + */ public static function LAMEpresetUsedLookup($LAMEtag) { if ($LAMEtag['preset_used_id'] == 0) { diff --git a/wp-includes/ID3/module.audio.ogg.php b/wp-includes/ID3/module.audio.ogg.php index e41c96c4ba..51fee3e1ce 100644 --- a/wp-includes/ID3/module.audio.ogg.php +++ b/wp-includes/ID3/module.audio.ogg.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.audio.ogg.php // @@ -18,7 +18,11 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE class getid3_ogg extends getid3_handler { - // http://xiph.org/vorbis/doc/Vorbis_I_spec.html + /** + * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html + * + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -65,7 +69,7 @@ class getid3_ogg extends getid3_handler } elseif (substr($filedata, 0, 8) == 'OpusHead') { - if( $this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) == false ) { + if ($this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) === false) { return false; } @@ -179,7 +183,7 @@ class getid3_ogg extends getid3_handler if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) { $info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator']; } -$this->warning('Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable'); + $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable'); } elseif (substr($filedata, 0, 8) == "fishead\x00") { @@ -259,9 +263,34 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' $this->error('Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']'); //return false; + } elseif (substr($filedata, 0, 5) == "\x7F".'FLAC') { + // https://xiph.org/flac/ogg_mapping.html + + $info['audio']['dataformat'] = 'flac'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['lossless'] = true; + + $info['ogg']['flac']['header']['version_major'] = ord(substr($filedata, 5, 1)); + $info['ogg']['flac']['header']['version_minor'] = ord(substr($filedata, 6, 1)); + $info['ogg']['flac']['header']['header_packets'] = getid3_lib::BigEndian2Int(substr($filedata, 7, 2)) + 1; // "A two-byte, big-endian binary number signifying the number of header (non-audio) packets, not including this one. This number may be zero (0x0000) to signify 'unknown' but be aware that some decoders may not be able to handle such streams." + $info['ogg']['flac']['header']['magic'] = substr($filedata, 9, 4); + if ($info['ogg']['flac']['header']['magic'] != 'fLaC') { + $this->error('Ogg-FLAC expecting "fLaC", found "'.$info['ogg']['flac']['header']['magic'].'" ('.trim(getid3_lib::PrintHexBytes($info['ogg']['flac']['header']['magic'])).')'); + return false; + } + $info['ogg']['flac']['header']['STREAMINFO_bytes'] = getid3_lib::BigEndian2Int(substr($filedata, 13, 4)); + $info['flac']['STREAMINFO'] = getid3_flac::parseSTREAMINFOdata(substr($filedata, 17, 34)); + if (!empty($info['flac']['STREAMINFO']['sample_rate'])) { + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate']; + $info['audio']['channels'] = $info['flac']['STREAMINFO']['channels']; + $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; + $info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate']; + } + } else { - $this->error('Expecting either "Speex ", "OpusHead" or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"'); + $this->error('Expecting one of "vorbis", "Speex", "OpusHead", "vorbis", "fishhead", "theora", "fLaC" identifier strings, found "'.substr($filedata, 0, 8).'"'); unset($info['ogg']); unset($info['mime_type']); return false; @@ -378,6 +407,13 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' return true; } + /** + * @param string $filedata + * @param int $filedataoffset + * @param array $oggpageinfo + * + * @return bool + */ public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { $info = &$this->getid3->info; $info['audio']['dataformat'] = 'vorbis'; @@ -426,7 +462,15 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' return true; } - // http://tools.ietf.org/html/draft-ietf-codec-oggopus-03 + /** + * @link http://tools.ietf.org/html/draft-ietf-codec-oggopus-03 + * + * @param string $filedata + * @param int $filedataoffset + * @param array $oggpageinfo + * + * @return bool + */ public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { $info = &$this->getid3->info; $info['audio']['dataformat'] = 'opus'; @@ -458,7 +502,7 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' $info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); $filedataoffset += 2; - $info['ogg']['pageheader']['opus']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $info['ogg']['pageheader']['opus']['input_sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); $filedataoffset += 4; //$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); @@ -467,16 +511,19 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' //$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); //$filedataoffset += 1; - $info['opus']['opus_version'] = $info['ogg']['pageheader']['opus']['version']; - $info['opus']['sample_rate'] = $info['ogg']['pageheader']['opus']['sample_rate']; - $info['opus']['out_channel_count'] = $info['ogg']['pageheader']['opus']['out_channel_count']; + $info['opus']['opus_version'] = $info['ogg']['pageheader']['opus']['version']; + $info['opus']['sample_rate_input'] = $info['ogg']['pageheader']['opus']['input_sample_rate']; + $info['opus']['out_channel_count'] = $info['ogg']['pageheader']['opus']['out_channel_count']; - $info['audio']['channels'] = $info['opus']['out_channel_count']; - $info['audio']['sample_rate'] = $info['opus']['sample_rate']; + $info['audio']['channels'] = $info['opus']['out_channel_count']; + $info['audio']['sample_rate_input'] = $info['opus']['sample_rate_input']; + $info['audio']['sample_rate'] = 48000; // "All Opus audio is coded at 48 kHz, and should also be decoded at 48 kHz for playback (unless the target hardware does not support this sampling rate). However, this field may be used to resample the audio back to the original sampling rate, for example, when saving the output to a file." -- https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/structOpusHead.html return true; } - + /** + * @return array|false + */ public function ParseOggPageHeader() { // http://xiph.org/ogg/vorbis/doc/framing.html $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file @@ -489,7 +536,7 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' return false; } if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) { - if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) { + if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === '')) { // get some more data, unless eof, in which case fail return false; } @@ -528,13 +575,19 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' return $oggheader; } - // http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005 + /** + * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005 + * + * @return bool + */ public function ParseVorbisComments() { $info = &$this->getid3->info; $OriginalOffset = $this->ftell(); + $commentdata = null; $commentdataoffset = 0; $VorbisCommentPage = 1; + $CommentStartOffset = 0; switch ($info['audio']['dataformat']) { case 'vorbis': @@ -765,6 +818,11 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' return true; } + /** + * @param int $mode + * + * @return string|null + */ public static function SpeexBandModeLookup($mode) { static $SpeexBandModeLookup = array(); if (empty($SpeexBandModeLookup)) { @@ -775,8 +833,14 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null); } - + /** + * @param array $OggInfoArray + * @param int $SegmentNumber + * + * @return int + */ public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { + $segmentlength = 0; for ($i = 0; $i < $SegmentNumber; $i++) { $segmentlength = 0; foreach ($OggInfoArray['segment_table'] as $key => $value) { @@ -789,7 +853,11 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' return $segmentlength; } - + /** + * @param int $nominal_bitrate + * + * @return float + */ public static function get_quality_from_nominal_bitrate($nominal_bitrate) { // decrease precision @@ -813,6 +881,11 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' return round($qval, 1); // 5 or 4.9 } + /** + * @param int $colorspace_id + * + * @return string|null + */ public static function TheoraColorSpace($colorspace_id) { // http://www.theora.org/doc/Theora.pdf (table 6.3) static $TheoraColorSpaceLookup = array(); @@ -825,6 +898,11 @@ $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 [' return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null); } + /** + * @param int $pixelformat_id + * + * @return string|null + */ public static function TheoraPixelFormat($pixelformat_id) { // http://www.theora.org/doc/Theora.pdf (table 6.4) static $TheoraPixelFormatLookup = array(); diff --git a/wp-includes/ID3/module.tag.apetag.php b/wp-includes/ID3/module.tag.apetag.php index eb081b4967..d56fcbac79 100644 --- a/wp-includes/ID3/module.tag.apetag.php +++ b/wp-includes/ID3/module.tag.apetag.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.tag.apetag.php // @@ -16,9 +16,21 @@ class getid3_apetag extends getid3_handler { - 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 + /** + * 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. + * + * @var int|bool|string + */ + public $inline_attachments = true; + public $overrideendoffset = 0; + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -150,8 +162,8 @@ class getid3_apetag extends getid3_handler switch (strtolower($item_key)) { // http://wiki.hydrogenaud.io/index.php?title=ReplayGain#MP3Gain case 'replaygain_track_gain': - if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { - $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + if (preg_match('#^([\\-\\+][0-9\\.,]{8})( dB)?$#', $thisfile_ape_items_current['data'][0], $matches)) { + $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero! $thisfile_replaygain['track']['originator'] = 'unspecified'; } else { $this->warning('MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'); @@ -159,8 +171,8 @@ class getid3_apetag extends getid3_handler break; case 'replaygain_track_peak': - if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { - $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + if (preg_match('#^([0-9\\.,]{8})$#', $thisfile_ape_items_current['data'][0], $matches)) { + $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero! $thisfile_replaygain['track']['originator'] = 'unspecified'; if ($thisfile_replaygain['track']['peak'] <= 0) { $this->warning('ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'); @@ -171,8 +183,8 @@ class getid3_apetag extends getid3_handler break; case 'replaygain_album_gain': - if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { - $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + if (preg_match('#^([\\-\\+][0-9\\.,]{8})( dB)?$#', $thisfile_ape_items_current['data'][0], $matches)) { + $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero! $thisfile_replaygain['album']['originator'] = 'unspecified'; } else { $this->warning('MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'); @@ -180,8 +192,8 @@ class getid3_apetag extends getid3_handler break; case 'replaygain_album_peak': - if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { - $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + if (preg_match('#^([0-9\\.,]{8})$#', $thisfile_ape_items_current['data'][0], $matches)) { + $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero! $thisfile_replaygain['album']['originator'] = 'unspecified'; if ($thisfile_replaygain['album']['peak'] <= 0) { $this->warning('ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'); @@ -225,7 +237,7 @@ class getid3_apetag extends getid3_handler case 'tracknumber': if (is_array($thisfile_ape_items_current['data'])) { foreach ($thisfile_ape_items_current['data'] as $comment) { - $thisfile_ape['comments']['track'][] = $comment; + $thisfile_ape['comments']['track_number'][] = $comment; } } break; @@ -335,6 +347,11 @@ class getid3_apetag extends getid3_handler return true; } + /** + * @param string $APEheaderFooterData + * + * @return array|false + */ public function parseAPEheaderFooter($APEheaderFooterData) { // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html @@ -359,6 +376,11 @@ class getid3_apetag extends getid3_handler return $headerfooterinfo; } + /** + * @param int $rawflagint + * + * @return array + */ public function parseAPEtagFlags($rawflagint) { // "Note: APE Tags 1.0 do not use any of the APE Tag flags. // All are set to zero on creation and ignored on reading." @@ -374,6 +396,11 @@ class getid3_apetag extends getid3_handler return $flags; } + /** + * @param int $contenttypeid + * + * @return string + */ public function APEcontentTypeFlagLookup($contenttypeid) { static $APEcontentTypeFlagLookup = array( 0 => 'utf-8', @@ -384,6 +411,11 @@ class getid3_apetag extends getid3_handler return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid'); } + /** + * @param string $itemkey + * + * @return bool + */ public function APEtagItemIsUTF8Lookup($itemkey) { static $APEtagItemIsUTF8Lookup = array( 'title', diff --git a/wp-includes/ID3/module.tag.id3v1.php b/wp-includes/ID3/module.tag.id3v1.php index d160e9b44a..0084838b63 100644 --- a/wp-includes/ID3/module.tag.id3v1.php +++ b/wp-includes/ID3/module.tag.id3v1.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// // // // module.tag.id3v1.php // @@ -17,7 +17,9 @@ class getid3_id3v1 extends getid3_handler { - + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -43,9 +45,9 @@ class getid3_id3v1 extends getid3_handler // If second-last byte of comment field is null and last byte of comment field is non-null // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number - if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) { - $ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1)); - $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28); + if (($id3v1tag[125] === "\x00") && ($id3v1tag[126] !== "\x00")) { + $ParsedID3v1['track_number'] = ord(substr($ParsedID3v1['comment'], 29, 1)); + $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28); } $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']); @@ -66,7 +68,7 @@ class getid3_id3v1 extends getid3_handler $ID3v1encoding = 'ISO-8859-1'; foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) { foreach ($valuearray as $key => $value) { - if (preg_match('#^[\\x00-\\x40\\xA8\\B8\\x80-\\xFF]+$#', $value)) { + if (preg_match('#^[\\x00-\\x40\\xA8\\xB8\\x80-\\xFF]+$#', $value)) { foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) { if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) { $ID3v1encoding = $id3v1_bad_encoding; @@ -89,7 +91,7 @@ class getid3_id3v1 extends getid3_handler $ParsedID3v1['year'], (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false), $ParsedID3v1['comment'], - (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : '')); + (!empty($ParsedID3v1['track_number']) ? $ParsedID3v1['track_number'] : '')); $ParsedID3v1['padding_valid'] = true; if ($id3v1tag !== $GoodFormatID3v1tag) { $ParsedID3v1['padding_valid'] = false; @@ -124,10 +126,20 @@ class getid3_id3v1 extends getid3_handler return true; } + /** + * @param string $str + * + * @return string + */ public static function cutfield($str) { return trim(substr($str, 0, strcspn($str, "\x00"))); } + /** + * @param bool $allowSCMPXextended + * + * @return string[] + */ public static function ArrayOfGenres($allowSCMPXextended=false) { static $GenreLookup = array( 0 => 'Blues', @@ -312,6 +324,12 @@ class getid3_id3v1 extends getid3_handler return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup); } + /** + * @param string $genreid + * @param bool $allowSCMPXextended + * + * @return string|false + */ public static function LookupGenreName($genreid, $allowSCMPXextended=true) { switch ($genreid) { case 'RX': @@ -328,6 +346,12 @@ class getid3_id3v1 extends getid3_handler return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); } + /** + * @param string $genre + * @param bool $allowSCMPXextended + * + * @return string|false + */ public static function LookupGenreID($genre, $allowSCMPXextended=false) { $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre)); @@ -339,6 +363,11 @@ class getid3_id3v1 extends getid3_handler return false; } + /** + * @param string $OriginalGenre + * + * @return string|false + */ public static function StandardiseID3v1GenreName($OriginalGenre) { if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) { return self::LookupGenreName($GenreID); @@ -346,6 +375,17 @@ class getid3_id3v1 extends getid3_handler return $OriginalGenre; } + /** + * @param string $title + * @param string $artist + * @param string $album + * @param string $year + * @param int $genreid + * @param string $comment + * @param int|string $track + * + * @return string + */ public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { $ID3v1Tag = 'TAG'; $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT); diff --git a/wp-includes/ID3/module.tag.id3v2.php b/wp-includes/ID3/module.tag.id3v2.php index fc586e8e13..0c7c34bf6f 100644 --- a/wp-includes/ID3/module.tag.id3v2.php +++ b/wp-includes/ID3/module.tag.id3v2.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// /// // // module.tag.id3v2.php // @@ -20,6 +20,9 @@ class getid3_id3v2 extends getid3_handler { public $StartingOffset = 0; + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -56,8 +59,8 @@ class getid3_id3v2 extends getid3_handler $header = $this->fread(10); if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) { - $thisfile_id3v2['majorversion'] = ord($header{3}); - $thisfile_id3v2['minorversion'] = ord($header{4}); + $thisfile_id3v2['majorversion'] = ord($header[3]); + $thisfile_id3v2['minorversion'] = ord($header[4]); // shortcut $id3v2_majorversion = &$thisfile_id3v2['majorversion']; @@ -76,7 +79,7 @@ class getid3_id3v2 extends getid3_handler } - $id3_flags = ord($header{5}); + $id3_flags = ord($header[5]); switch ($id3v2_majorversion) { case 2: // %ab000000 in v2.2 @@ -257,7 +260,7 @@ class getid3_id3v2 extends getid3_handler $thisfile_id3v2['padding']['length'] = strlen($framedata); $thisfile_id3v2['padding']['valid'] = true; for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) { - if ($framedata{$i} != "\x00") { + if ($framedata[$i] != "\x00") { $thisfile_id3v2['padding']['valid'] = false; $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'); @@ -266,6 +269,10 @@ class getid3_id3v2 extends getid3_handler } break; // skip rest of ID3v2 header } + $frame_header = null; + $frame_name = null; + $frame_size = null; + $frame_flags = null; if ($id3v2_majorversion == 2) { // Frame ID $xx xx xx (three characters) // Size $xx xx xx (24-bit integer) @@ -319,7 +326,7 @@ class getid3_id3v2 extends getid3_handler $len = strlen($framedata); for ($i = 0; $i < $len; $i++) { - if ($framedata{$i} != "\x00") { + if ($framedata[$i] != "\x00") { $thisfile_id3v2['padding']['valid'] = false; $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'); @@ -427,11 +434,11 @@ class getid3_id3v2 extends getid3_handler $footer = $this->fread(10); if (substr($footer, 0, 3) == '3DI') { $thisfile_id3v2['footer'] = true; - $thisfile_id3v2['majorversion_footer'] = ord($footer{3}); - $thisfile_id3v2['minorversion_footer'] = ord($footer{4}); + $thisfile_id3v2['majorversion_footer'] = ord($footer[3]); + $thisfile_id3v2['minorversion_footer'] = ord($footer[4]); } if ($thisfile_id3v2['majorversion_footer'] <= 4) { - $id3_flags = ord(substr($footer{5})); + $id3_flags = ord($footer[5]); $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80); $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40); $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20); @@ -452,10 +459,10 @@ class getid3_id3v2 extends getid3_handler unset($key, $value, $genres, $genre); } - if (isset($thisfile_id3v2['comments']['track'])) { - foreach ($thisfile_id3v2['comments']['track'] as $key => $value) { + if (isset($thisfile_id3v2['comments']['track_number'])) { + foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) { if (strstr($value, '/')) { - list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]); + list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]); } } } @@ -498,7 +505,11 @@ class getid3_id3v2 extends getid3_handler return true; } - + /** + * @param string $genrestring + * + * @return array + */ public function ParseID3v2GenreString($genrestring) { // Parse genres into arrays of genreName and genreID // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)' @@ -530,7 +541,7 @@ class getid3_id3v2 extends getid3_handler foreach ($genre_elements as $element) { $element = trim($element); if ($element) { - if (preg_match('#^[0-9]{1,3}#', $element)) { + if (preg_match('#^[0-9]{1,3}$#', $element)) { $clean_genres[] = getid3_id3v1::LookupGenreName($element); } else { $clean_genres[] = str_replace('((', '(', $element); @@ -540,7 +551,11 @@ class getid3_id3v2 extends getid3_handler return $clean_genres; } - + /** + * @param array $parsedFrame + * + * @return bool + */ public function ParseID3v2Frame(&$parsedFrame) { // shortcuts @@ -657,16 +672,14 @@ class getid3_id3v2 extends getid3_handler if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } - $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); - if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { - // if description only contains a BOM or terminator then make it blank - $frame_description = ''; - } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); - $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description)); + $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description'])); $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); + $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) { @@ -678,7 +691,7 @@ class getid3_id3v2 extends getid3_handler //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain - } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame + } elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame // There may only be one text information frame of its kind in an tag. //
@@ -692,10 +705,10 @@ class getid3_id3v2 extends getid3_handler } $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); - if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with / // This of course breaks when an artist name contains slash character, e.g. "AC/DC" @@ -751,47 +764,29 @@ class getid3_id3v2 extends getid3_handler if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } - $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); - if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { - // if description only contains a BOM or terminator then make it blank - $frame_description = ''; - } - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); - - $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { - $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 - } - if ($frame_terminatorpos) { - // there are null bytes after the data - this is not according to spec - // only use data up to first null byte - $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos); - } else { - // no null bytes following data, just use all data - $frame_urldata = (string) $parsedFrame['data']; - } - $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); // according to the frame text encoding + $parsedFrame['url'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1 + $parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); - $parsedFrame['url'] = $frame_urldata; - $parsedFrame['description'] = $frame_description; if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { - $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']); } unset($parsedFrame['data']); - } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames + } elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames // There may only be one URL link frame of its kind in a tag, // except when stated otherwise in the frame description //
// URL - $parsedFrame['url'] = trim($parsedFrame['data']); + $parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { - $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url']; + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']); } unset($parsedFrame['data']); @@ -813,7 +808,7 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset); - // http://www.getid3.org/phpBB3/viewtopic.php?t=1369 + // https://www.getid3.org/phpBB3/viewtopic.php?t=1369 // "this tag typically contains null terminated strings, which are associated in pairs" // "there are users that use the tag incorrectly" $IPLS_parts = array(); @@ -933,6 +928,7 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1)); $parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1)); $parsedFrame['data'] = substr($parsedFrame['data'], 10); + $deviationbitstream = ''; while ($frame_offset < strlen($parsedFrame['data'])) { $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); } @@ -994,19 +990,16 @@ class getid3_id3v2 extends getid3_handler if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } - $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); - if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { - // if description only contains a BOM or terminator then make it blank - $frame_description = ''; - } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); + $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $parsedFrame['language'] = $frame_language; $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); - $parsedFrame['description'] = $frame_description; if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } @@ -1061,7 +1054,7 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator)); - if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) { + if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) { // timestamp probably omitted for first data item } else { $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4)); @@ -1102,19 +1095,16 @@ class getid3_id3v2 extends getid3_handler if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } - $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); - if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { - // if description only contains a BOM or terminator then make it blank - $frame_description = ''; - } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); + $frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $parsedFrame['language'] = $frame_language; $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); - $parsedFrame['description'] = $frame_description; $parsedFrame['data'] = $frame_text; if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); @@ -1407,30 +1397,26 @@ class getid3_id3v2 extends getid3_handler if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } - $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); - if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { - // if description only contains a BOM or terminator then make it blank - $frame_description = ''; - } - $parsedFrame['encodingid'] = $frame_textencoding; - $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); if ($id3v2_majorversion == 2) { - $parsedFrame['imagetype'] = $frame_imagetype; + $parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null; } else { - $parsedFrame['mime'] = $frame_mimetype; + $parsedFrame['mime'] = isset($frame_mimetype) ? $frame_mimetype : null; } - $parsedFrame['picturetypeid'] = $frame_picturetype; - $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); - $parsedFrame['description'] = $frame_description; - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); - $parsedFrame['datalength'] = strlen($parsedFrame['data']); + $parsedFrame['picturetypeid'] = $frame_picturetype; + $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); + $parsedFrame['datalength'] = strlen($parsedFrame['data']); - $parsedFrame['image_mime'] = ''; + $parsedFrame['image_mime'] = ''; $imageinfo = array(); if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) { if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { - $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); + $parsedFrame['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); if ($imagechunkcheck[0]) { $parsedFrame['image_width'] = $imagechunkcheck[0]; } @@ -1446,6 +1432,7 @@ class getid3_id3v2 extends getid3_handler unset($parsedFrame['data']); break; } + $dir = ''; if ($this->getid3->option_save_attachments === true) { // great /* @@ -1533,11 +1520,8 @@ class getid3_id3v2 extends getid3_handler if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } - $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); - if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { - // if description only contains a BOM or terminator then make it blank - $frame_description = ''; - } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset); @@ -1546,7 +1530,6 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['mime'] = $frame_mimetype; $parsedFrame['filename'] = $frame_filename; - $parsedFrame['description'] = $frame_description; unset($parsedFrame['data']); @@ -1616,16 +1599,12 @@ class getid3_id3v2 extends getid3_handler $frame_offset = $frame_terminatorpos + strlen("\x00"); $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); - $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); - if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { - // if description only contains a BOM or terminator then make it blank - $frame_description = ''; - } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); $frame_offset = $frame_terminatorpos + strlen("\x00"); $parsedFrame['ownerid'] = $frame_ownerid; $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); - $parsedFrame['description'] = $frame_description; unset($parsedFrame['data']); @@ -1721,7 +1700,8 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); - $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } @@ -1759,6 +1739,7 @@ class getid3_id3v2 extends getid3_handler $frame_offset += 8; $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding)); unset($parsedFrame['data']); @@ -1817,11 +1798,8 @@ class getid3_id3v2 extends getid3_handler if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } - $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); - if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { - // if description only contains a BOM or terminator then make it blank - $frame_description = ''; - } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); @@ -1838,7 +1816,6 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['receivedasid'] = $frame_receivedasid; $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid); $parsedFrame['sellername'] = $frame_sellername; - $parsedFrame['description'] = $frame_description; $parsedFrame['mime'] = $frame_mimetype; $parsedFrame['logo'] = $frame_sellerlogo; unset($parsedFrame['data']); @@ -2002,9 +1979,9 @@ class getid3_id3v2 extends getid3_handler // Element ID $00 // Start time $xx xx xx xx // End time $xx xx xx xx - // Start offset $xx xx xx xx - // End offset $xx xx xx xx - // + // Start offset $xx xx xx xx + // End offset $xx xx xx xx + // $frame_offset = 0; @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); @@ -2045,7 +2022,7 @@ class getid3_id3v2 extends getid3_handler $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); $subframe['text'] = substr($subframe_rawdata, 1); $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']); - $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));; + $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text'])); switch (substr($encoding_converted_text, 0, 2)) { case "\xFF\xFE": case "\xFE\xFF": @@ -2065,22 +2042,51 @@ class getid3_id3v2 extends getid3_handler break; } - if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) { - if ($subframe['name'] == 'TIT2') { + switch ($subframe['name']) { + case 'TIT2': $parsedFrame['chapter_name'] = $encoding_converted_text; - } elseif ($subframe['name'] == 'TIT3') { + $parsedFrame['subframes'][] = $subframe; + break; + case 'TIT3': $parsedFrame['chapter_description'] = $encoding_converted_text; - } - $parsedFrame['subframes'][] = $subframe; - } else { - $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)'); + $parsedFrame['subframes'][] = $subframe; + break; + case 'WXXX': + list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2); + $parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url']; + $parsedFrame['subframes'][] = $subframe; + break; + case 'APIC': + if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) { + list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches; + $subframe['image_mime'] = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime)); + $subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype); + $subframe['description'] = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description)); + if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) { + // the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16) + // the above regex assumes one byte, if it's actually two then strip the second one here + $subframe_apic_picturedata = substr($subframe_apic_picturedata, 1); + } + $subframe['data'] = $subframe_apic_picturedata; + unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata); + unset($subframe['text'], $parsedFrame['text']); + $parsedFrame['subframes'][] = $subframe; + $parsedFrame['picture_present'] = true; + } else { + $this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format'); + } + break; + default: + $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)'); + break; } } unset($subframe_rawdata, $subframe, $encoding_converted_text); + unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC } $id3v2_chapter_entry = array(); - foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') as $id3v2_chapter_key) { + foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) { if (isset($parsedFrame[$id3v2_chapter_key])) { $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key]; } @@ -2099,7 +2105,7 @@ class getid3_id3v2 extends getid3_handler // CTOC flags %xx // Entry count $xx // Child Element ID $00 /* zero or more child CHAP or CTOC entries */ - // + // $frame_offset = 0; @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); @@ -2181,11 +2187,20 @@ class getid3_id3v2 extends getid3_handler return true; } - + /** + * @param string $data + * + * @return string + */ public function DeUnsynchronise($data) { return str_replace("\xFF\x00", "\xFF", $data); } + /** + * @param int $index + * + * @return string + */ public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) { static $LookupExtendedHeaderRestrictionsTagSizeLimits = array( 0x00 => 'No more than 128 frames and 1 MB total tag size', @@ -2196,6 +2211,11 @@ class getid3_id3v2 extends getid3_handler return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : ''); } + /** + * @param int $index + * + * @return string + */ public function LookupExtendedHeaderRestrictionsTextEncodings($index) { static $LookupExtendedHeaderRestrictionsTextEncodings = array( 0x00 => 'No restrictions', @@ -2204,6 +2224,11 @@ class getid3_id3v2 extends getid3_handler return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : ''); } + /** + * @param int $index + * + * @return string + */ public function LookupExtendedHeaderRestrictionsTextFieldSize($index) { static $LookupExtendedHeaderRestrictionsTextFieldSize = array( 0x00 => 'No restrictions', @@ -2214,6 +2239,11 @@ class getid3_id3v2 extends getid3_handler return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : ''); } + /** + * @param int $index + * + * @return string + */ public function LookupExtendedHeaderRestrictionsImageEncoding($index) { static $LookupExtendedHeaderRestrictionsImageEncoding = array( 0x00 => 'No restrictions', @@ -2222,6 +2252,11 @@ class getid3_id3v2 extends getid3_handler return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : ''); } + /** + * @param int $index + * + * @return string + */ public function LookupExtendedHeaderRestrictionsImageSizeSize($index) { static $LookupExtendedHeaderRestrictionsImageSizeSize = array( 0x00 => 'No restrictions', @@ -2232,6 +2267,11 @@ class getid3_id3v2 extends getid3_handler return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : ''); } + /** + * @param string $currencyid + * + * @return string + */ public function LookupCurrencyUnits($currencyid) { $begin = __LINE__; @@ -2428,7 +2468,11 @@ class getid3_id3v2 extends getid3_handler return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units'); } - + /** + * @param string $currencyid + * + * @return string + */ public function LookupCurrencyCountry($currencyid) { $begin = __LINE__; @@ -2624,8 +2668,12 @@ class getid3_id3v2 extends getid3_handler return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country'); } - - + /** + * @param string $languagecode + * @param bool $casesensitive + * + * @return string + */ public static function LanguageLookup($languagecode, $casesensitive=false) { if (!$casesensitive) { @@ -3081,7 +3129,11 @@ class getid3_id3v2 extends getid3_handler return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode'); } - + /** + * @param int $index + * + * @return string + */ public static function ETCOEventLookup($index) { if (($index >= 0x17) && ($index <= 0xDF)) { return 'reserved for future use'; @@ -3125,6 +3177,11 @@ class getid3_id3v2 extends getid3_handler return (isset($EventLookup[$index]) ? $EventLookup[$index] : ''); } + /** + * @param int $index + * + * @return string + */ public static function SYTLContentTypeLookup($index) { static $SYTLContentTypeLookup = array( 0x00 => 'other', @@ -3141,6 +3198,12 @@ class getid3_id3v2 extends getid3_handler return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : ''); } + /** + * @param int $index + * @param bool $returnarray + * + * @return array|string + */ public static function APICPictureTypeLookup($index, $returnarray=false) { static $APICPictureTypeLookup = array( 0x00 => 'Other', @@ -3171,6 +3234,11 @@ class getid3_id3v2 extends getid3_handler return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : ''); } + /** + * @param int $index + * + * @return string + */ public static function COMRReceivedAsLookup($index) { static $COMRReceivedAsLookup = array( 0x00 => 'Other', @@ -3187,6 +3255,11 @@ class getid3_id3v2 extends getid3_handler return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : ''); } + /** + * @param int $index + * + * @return string + */ public static function RVA2ChannelTypeLookup($index) { static $RVA2ChannelTypeLookup = array( 0x00 => 'Other', @@ -3203,6 +3276,11 @@ class getid3_id3v2 extends getid3_handler return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : ''); } + /** + * @param string $framename + * + * @return string + */ public static function FrameNameLongLookup($framename) { $begin = __LINE__; @@ -3354,7 +3432,7 @@ class getid3_id3v2 extends getid3_handler TYER Year UFI Unique file identifier UFID Unique file identifier - ULT Unsychronised lyric/text transcription + ULT Unsynchronised lyric/text transcription USER Terms of use USLT Unsynchronised lyric/text transcription WAF Official audio file webpage @@ -3386,7 +3464,11 @@ class getid3_id3v2 extends getid3_handler // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html } - + /** + * @param string $framename + * + * @return string + */ public static function FrameNameShortLookup($framename) { $begin = __LINE__; @@ -3538,7 +3620,7 @@ class getid3_id3v2 extends getid3_handler TYER year UFI unique_file_identifier UFID unique_file_identifier - ULT unsychronised_lyric + ULT unsynchronised_lyric USER terms_of_use USLT unsynchronised_lyric WAF url_file @@ -3566,6 +3648,11 @@ class getid3_id3v2 extends getid3_handler return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short'); } + /** + * @param string $encoding + * + * @return string + */ public static function TextEncodingTerminatorLookup($encoding) { // http://www.id3.org/id3v2.4.0-structure.txt // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: @@ -3579,6 +3666,11 @@ class getid3_id3v2 extends getid3_handler return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00"); } + /** + * @param int $encoding + * + * @return string + */ public static function TextEncodingNameLookup($encoding) { // http://www.id3.org/id3v2.4.0-structure.txt // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: @@ -3592,6 +3684,41 @@ class getid3_id3v2 extends getid3_handler return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1'); } + /** + * @param string $string + * @param string $terminator + * + * @return string + */ + public static function RemoveStringTerminator($string, $terminator) { + // Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present. + // https://github.com/JamesHeinrich/getID3/issues/121 + // https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227 + if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) { + $string = substr($string, 0, -strlen($terminator)); + } + return $string; + } + + /** + * @param string $string + * + * @return string + */ + public static function MakeUTF16emptyStringEmpty($string) { + if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { + // if string only contains a BOM or terminator then make it actually an empty string + $string = ''; + } + return $string; + } + + /** + * @param string $framename + * @param int $id3v2majorversion + * + * @return bool|int + */ public static function IsValidID3v2FrameName($framename, $id3v2majorversion) { switch ($id3v2majorversion) { case 2: @@ -3606,12 +3733,19 @@ class getid3_id3v2 extends getid3_handler return false; } + /** + * @param string $numberstring + * @param bool $allowdecimal + * @param bool $allownegative + * + * @return bool + */ public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { for ($i = 0; $i < strlen($numberstring); $i++) { - if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) { - if (($numberstring{$i} == '.') && $allowdecimal) { + if ((chr($numberstring[$i]) < chr('0')) || (chr($numberstring[$i]) > chr('9'))) { + if (($numberstring[$i] == '.') && $allowdecimal) { // allowed - } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) { + } elseif (($numberstring[$i] == '-') && $allownegative && ($i == 0)) { // allowed } else { return false; @@ -3621,6 +3755,11 @@ class getid3_id3v2 extends getid3_handler return true; } + /** + * @param string $datestamp + * + * @return bool + */ public static function IsValidDateStampString($datestamp) { if (strlen($datestamp) != 8) { return false; @@ -3649,10 +3788,20 @@ class getid3_id3v2 extends getid3_handler return true; } + /** + * @param int $majorversion + * + * @return int + */ public static function ID3v2HeaderLength($majorversion) { return (($majorversion == 2) ? 6 : 10); } + /** + * @param string $frame_name + * + * @return string|false + */ public static function ID3v22iTunesBrokenFrameName($frame_name) { // iTunes (multiple versions) has been known to write ID3v2.3 style frames // but use ID3v2.2 frame names, right-padded using either [space] or [null] @@ -3739,3 +3888,4 @@ class getid3_id3v2 extends getid3_handler } } + diff --git a/wp-includes/ID3/module.tag.lyrics3.php b/wp-includes/ID3/module.tag.lyrics3.php index 1645396b12..80e81658c7 100644 --- a/wp-includes/ID3/module.tag.lyrics3.php +++ b/wp-includes/ID3/module.tag.lyrics3.php @@ -1,11 +1,11 @@ // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -// also https://github.com/JamesHeinrich/getID3 // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // ///////////////////////////////////////////////////////////////// /// // // module.tag.lyrics3.php // @@ -17,7 +17,9 @@ class getid3_lyrics3 extends getid3_handler { - + /** + * @return bool + */ public function Analyze() { $info = &$this->getid3->info; @@ -61,7 +63,7 @@ class getid3_lyrics3 extends getid3_handler // Lyrics3v2, no ID3v1, no APE - $lyrics3size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' + $lyrics3size = (int) strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' $lyrics3offset = $info['filesize'] - $lyrics3size; $lyrics3version = 2; @@ -96,7 +98,7 @@ class getid3_lyrics3 extends getid3_handler } - if (isset($lyrics3offset)) { + if (isset($lyrics3offset) && isset($lyrics3version) && isset($lyrics3size)) { $info['avdataend'] = $lyrics3offset; $this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size); @@ -126,6 +128,13 @@ class getid3_lyrics3 extends getid3_handler return true; } + /** + * @param int $endoffset + * @param int $version + * @param int $length + * + * @return bool + */ public function getLyrics3Data($endoffset, $version, $length) { // http://www.volweb.cz/str/tags.htm @@ -142,6 +151,8 @@ class getid3_lyrics3 extends getid3_handler } $rawdata = $this->fread($length); + $ParsedLyrics3 = array(); + $ParsedLyrics3['raw']['lyrics3version'] = $version; $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; $ParsedLyrics3['tag_offset_start'] = $endoffset; @@ -250,6 +261,11 @@ class getid3_lyrics3 extends getid3_handler return true; } + /** + * @param string $rawtimestamp + * + * @return int|false + */ public function Lyrics3Timestamp2Seconds($rawtimestamp) { if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) { return (int) (($regs[1] * 60) + $regs[2]); @@ -257,8 +273,14 @@ class getid3_lyrics3 extends getid3_handler return false; } + /** + * @param array $Lyrics3data + * + * @return bool + */ public function Lyrics3LyricsTimestampParse(&$Lyrics3data) { $lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']); + $notimestamplyricsarray = array(); foreach ($lyricsarray as $key => $lyricline) { $regs = array(); unset($thislinetimestamps); @@ -287,6 +309,11 @@ class getid3_lyrics3 extends getid3_handler return true; } + /** + * @param string $char + * + * @return bool|null + */ public function IntString2Bool($char) { if ($char == '1') { return true; diff --git a/wp-includes/ID3/readme.txt b/wp-includes/ID3/readme.txt index b627cc2dcc..e48bcef917 100644 --- a/wp-includes/ID3/readme.txt +++ b/wp-includes/ID3/readme.txt @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////// /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // -// or http://www.getid3.org // +// or https://www.getid3.org // // also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// @@ -18,9 +18,9 @@ GNU GPL: https://gnu.org/licenses/gpl.html (v3) GNU LGPL: https://gnu.org/licenses/lgpl.html (v3) -Mozilla MPL: http://www.mozilla.org/MPL/2.0/ (v2) +Mozilla MPL: https://www.mozilla.org/MPL/2.0/ (v2) -getID3 Commercial License: http://getid3.org/#gCL (payment required) +getID3 Commercial License: https://www.getid3.org/#gCL (payment required) ***************************************************************** ***************************************************************** @@ -28,10 +28,10 @@ Copies of each of the above licenses are included in the 'licenses' directory of the getID3 distribution. - +---------------------------------------------+ - | If you want to donate, there is a link on | - | http://www.getid3.org for PayPal donations. | - +---------------------------------------------+ + +----------------------------------------------+ + | If you want to donate, there is a link on | + | https://www.getid3.org for PayPal donations. | + +----------------------------------------------+ Quick Start @@ -77,7 +77,7 @@ Reads & parses (to varying degrees): ¤ audio-lossy: * MP3/MP2/MP1 * MPC / Musepack - * Ogg (Vorbis, OggFLAC, Speex) + * Ogg (Vorbis, OggFLAC, Speex, Opus) * AAC / MP4 * AC3 * DTS @@ -145,9 +145,10 @@ Writes: Requirements =========================================================================== -* PHP 4.2.0 up to 5.2.x for getID3() 1.7.x (and earlier) -* PHP 5.0.5 (or higher) for getID3() 1.8.x (and up) -* PHP 5.0.5 (or higher) for getID3() 2.0.x (and up) +* PHP 4.2.0 up to 5.2.x for getID3() 1.7.x (and earlier) +* PHP 5.0.5 (or higher) for getID3() 1.8.x (and up) +* PHP 5.3.0 (or higher) for getID3() 1.9.17 (and up) +* PHP 5.3.0 (or higher) for getID3() 2.0.x (and up) * at least 4MB memory for PHP. 8MB or more is highly recommended. 12MB is required with all modules loaded. @@ -177,24 +178,32 @@ like this: // Copy remote file locally to scan with getID3() $remotefilename = 'http://www.example.com/filename.mp3'; if ($fp_remote = fopen($remotefilename, 'rb')) { - $localtempfilename = tempnam('/tmp', 'getID3'); - if ($fp_local = fopen($localtempfilename, 'wb')) { - while ($buffer = fread($fp_remote, 8192)) { - fwrite($fp_local, $buffer); - } - fclose($fp_local); + $localtempfilename = tempnam('/tmp', 'getID3'); + if ($fp_local = fopen($localtempfilename, 'wb')) { + while ($buffer = fread($fp_remote, 32768)) { + fwrite($fp_local, $buffer); + } + fclose($fp_local); + + $remote_headers = array_change_key_case(get_headers($remotefilename, 1), CASE_LOWER); + $remote_filesize = (isset($remote_headers['content-length']) ? (is_array($remote_headers['content-length']) ? $remote_headers['content-length'][count($remote_headers['content-length']) - 1] : $remote_headers['content-length']) : null); // Initialize getID3 engine $getID3 = new getID3; - $ThisFileInfo = $getID3->analyze($filename); + $ThisFileInfo = $getID3->analyze($localtempfilename, $remote_filesize, basename($remotefilename)); - // Delete temporary file - unlink($localtempfilename); - } - fclose($fp_remote); + // Delete temporary file + unlink($localtempfilename); + } + fclose($fp_remote); } +Note: since v1.9.9-20150212 it is possible a second and third parameter +to $getID3->analyze(), for original filesize and original filename +respectively. This permits you to download only a portion of a large remote +file but get accurate playtime estimates, assuming the format only requires +the beginning of the file for correct format analysis. See /demos/demo.write.php for how to write tags. @@ -292,7 +301,7 @@ could essentially write it today with a one-line function: Future Plans =========================================================================== -http://www.getid3.org/phpBB3/viewforum.php?f=7 +https://www.getid3.org/phpBB3/viewforum.php?f=7 * Better support for MP4 container format * Scan for appended ID3v2 tag at end of file per ID3v2.4 specs (Section 5.0) @@ -334,11 +343,11 @@ http://www.getid3.org/phpBB3/viewforum.php?f=7 * Optional scan-through-frames for AVI verification (thanks rockcohenØmassive-interactive*nl) * Support for TTF (thanks infoØbutterflyx*com) -* Support for DSS (http://www.getid3.org/phpBB3/viewtopic.php?t=171) +* Support for DSS (https://www.getid3.org/phpBB3/viewtopic.php?t=171) * Support for SMAF (http://smaf-yamaha.com/what/demo.html) - http://www.getid3.org/phpBB3/viewtopic.php?t=182 -* Support for AMR (http://www.getid3.org/phpBB3/viewtopic.php?t=195) -* Support for 3gpp (http://www.getid3.org/phpBB3/viewtopic.php?t=195) + https://www.getid3.org/phpBB3/viewtopic.php?t=182 +* Support for AMR (https://www.getid3.org/phpBB3/viewtopic.php?t=195) +* Support for 3gpp (https://www.getid3.org/phpBB3/viewtopic.php?t=195) * Support for ID4 (http://www.wackysoft.cjb.net grizlyY2KØhotmail*com) * Parse XML data returned in Ogg comments * Parse XML data from Quicktime SMIL metafiles (klausrathØmac*com) @@ -374,7 +383,7 @@ http://www.getid3.org/phpBB3/viewforum.php?f=7 Known Bugs/Issues in getID3() that may be fixed eventually =========================================================================== -http://www.getid3.org/phpBB3/viewtopic.php?t=25 +https://www.getid3.org/phpBB3/viewtopic.php?t=25 * Cannot determine bitrate for MPEG video with VBR video data (need documentation) @@ -400,7 +409,7 @@ http://www.getid3.org/phpBB3/viewtopic.php?t=25 Known Bugs/Issues in getID3() that cannot be fixed -------------------------------------------------- -http://www.getid3.org/phpBB3/viewtopic.php?t=25 +https://www.getid3.org/phpBB3/viewtopic.php?t=25 * 32-bit PHP installations only: Files larger than 2GB cannot always be parsed fully by getID3() @@ -430,8 +439,15 @@ http://www.getid3.org/phpBB3/viewtopic.php?t=25 Known Bugs/Issues in other programs ----------------------------------- -http://www.getid3.org/phpBB3/viewtopic.php?t=25 +https://www.getid3.org/phpBB3/viewtopic.php?t=25 +* MusicBrainz Picard (at least up to v1.3.2) writes multiple + ID3v2.3 genres in non-standard forward-slash separated text + rather than parenthesis-numeric+refinement style per the ID3v2.3 + specs. Tags written in ID3v2.4 mode are written correctly. + (detected and worked around by getID3()) +* PZ TagEditor v4.53.408 has been known to insert ID3v2.3 frames + into an existing ID3v2.2 tag which, of course, breaks things * Windows Media Player (up to v11) and iTunes (up to v10+) do not correctly handle ID3v2.3 tags with UTF-16BE+BOM encoding (they assume the data is UTF-16LE+BOM and either @@ -451,6 +467,11 @@ http://www.getid3.org/phpBB3/viewtopic.php?t=25 written just "value" (detected by getID3()) * Oggenc 0.9-rc3 flags the encoded file as ABR whether it's actually ABR or VBR. +* iTunes (versions "v7.0.0.70" is known-guilty, probably + other versions are too) writes ID3v2.3 comment tags using an + ID3v2.2 frame name (3-bytes) null-padded to 4 bytes which is + not valid for ID3v2.3+ + (detected by getID3() since 1.9.12-201603221746) * iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably other versions are too) writes ID3v2.3 comment tags using a frame name 'COM ' which is not valid for ID3v2.3+ (it's an @@ -601,4 +622,6 @@ Reference material: * http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm * http://trac.musepack.net/trac/wiki/SV8Specification * http://wyday.com/cuesharp/specification.php -* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html \ No newline at end of file +* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html +* http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header +* http://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf diff --git a/wp-includes/version.php b/wp-includes/version.php index a407fd7913..824885b532 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -13,7 +13,7 @@ * * @global string $wp_version */ -$wp_version = '5.3-alpha-46111'; +$wp_version = '5.3-alpha-46112'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.