'; foreach ($variable as $key => $value) { $returnstring .= ''.str_replace(chr(0), ' ', $key).''; $returnstring .= ''.gettype($value); if (is_array($value)) { $returnstring .= ' ('.count($value).')'; } elseif (is_string($value)) { $returnstring .= ' ('.strlen($value).')'; } if (($key == 'data') && isset($variable['image_mime']) && isset($variable['dataoffset'])) { require_once(GETID3_INCLUDEPATH.'getid3.getimagesize.php'); $imageinfo = []; if ($imagechunkcheck = GetDataImageSize($value, $imageinfo)) { $DumpedImageSRC = (!empty($_REQUEST['filename']) ? $_REQUEST['filename'] : '.getid3').'.'.$variable['dataoffset'].'.'.ImageTypesLookup($imagechunkcheck[2]); if ($tempimagefile = fopen($DumpedImageSRC, 'wb')) { fwrite($tempimagefile, $value); fclose($tempimagefile); } $returnstring .= ''; } else { $returnstring .= 'invalid image data'; } } else { $returnstring .= ''.table_var_dump($value).''; } } $returnstring .= ''; break; case 'boolean': $returnstring .= ($variable ? 'TRUE' : 'FALSE'); break; case 'integer': case 'double': case 'float': $returnstring .= $variable; break; case 'object': case 'null': $returnstring .= string_var_dump($variable); break; case 'string': $variable = str_replace(chr(0), ' ', $variable); $varlen = strlen($variable); for ($i = 0; $i < $varlen; $i++) { if (preg_match('#['.chr(0x0A).chr(0x0D).' -;0-9A-Za-z]#', $variable{$i})) { $returnstring .= $variable{$i}; } else { $returnstring .= '&#'.str_pad(ord($variable{$i}), 3, '0', STR_PAD_LEFT).';'; } } $returnstring = nl2br($returnstring); break; default: require_once(GETID3_INCLUDEPATH.'getid3.getimagesize.php'); $imageinfo = []; if (($imagechunkcheck = GetDataImageSize(substr($variable, 0, 32768), $imageinfo)) && ($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { $returnstring .= ''; $returnstring .= ''; $returnstring .= ''; $returnstring .= ''; $returnstring .= '
type'.ImageTypesLookup($imagechunkcheck[2]).'
width'.number_format($imagechunkcheck[0]).' px
height'.number_format($imagechunkcheck[1]).' px
size'.number_format(strlen($variable)).' bytes
'; } else { $returnstring .= nl2br(htmlspecialchars(str_replace(chr(0), ' ', $variable))); } break; } return $returnstring; } } if (!function_exists('string_var_dump')) { function string_var_dump($variable) { if (version_compare(PHP_VERSION, '4.3.0', '>=')) { return print_r($variable, true); } ob_start(); var_dump($variable); $dumpedvariable = ob_get_contents(); ob_end_clean(); return $dumpedvariable; } } if (!function_exists('fileextension')) { function fileextension($filename, $numextensions = 1) { if (strstr($filename, '.')) { $reversedfilename = strrev($filename); $offset = 0; for ($i = 0; $i < $numextensions; $i++) { $offset = strpos($reversedfilename, '.', $offset + 1); if ($offset === false) { return ''; } } return strrev(substr($reversedfilename, 0, $offset)); } return ''; } } if (!function_exists('RemoveAccents')) { function RemoveAccents($string) { // return strtr($string, 'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ', 'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyy'); // Revised version by marksteward@hotmail.com return strtr(strtr($string, 'ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'), ['Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u']); } } if (!function_exists('MoreNaturalSort')) { function MoreNaturalSort($ar1, $ar2) { if ($ar1 === $ar2) { return 0; } $len1 = strlen($ar1); $len2 = strlen($ar2); $shortest = min($len1, $len2); if (substr($ar1, 0, $shortest) === substr($ar2, 0, $shortest)) { // the shorter argument is the beginning of the longer one, like "str" and "string" if ($len1 < $len2) { return -1; } elseif ($len1 > $len2) { return 1; } return 0; } $ar1 = RemoveAccents(strtolower(trim($ar1))); $ar2 = RemoveAccents(strtolower(trim($ar2))); $translatearray = ['\''=>'', '"'=>'', '_'=>' ', '('=>'', ')'=>'', '-'=>' ', ' '=>' ', '.'=>'', ','=>'']; foreach ($translatearray as $key => $val) { $ar1 = str_replace($key, $val, $ar1); $ar2 = str_replace($key, $val, $ar2); } if ($ar1 < $ar2) { return -1; } elseif ($ar1 > $ar2) { return 1; } return 0; } } if (!function_exists('trunc')) { 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) { $truncatednumber = ceil($floatnumber); } else { $truncatednumber = 0; } if ($truncatednumber <= pow(2, 30)) { $truncatednumber = (int) $truncatednumber; } return $truncatednumber; } } if (!function_exists('CastAsInt')) { function CastAsInt($floatnum) { // convert to float if not already $floatnum = (float) $floatnum; // convert a float to type int, only if possible if (trunc($floatnum) == $floatnum) { // it's not floating point if ($floatnum <= pow(2, 30)) { // it's within int range $floatnum = (int) $floatnum; } } return $floatnum; } } if (!function_exists('getmicrotime')) { function getmicrotime() { list($usec, $sec) = explode(' ', microtime()); return ((float) $usec + (float) $sec); } } if (!function_exists('DecimalBinary2Float')) { function DecimalBinary2Float($binarynumerator) { $numerator = Bin2Dec($binarynumerator); $denominator = Bin2Dec(str_repeat('1', strlen($binarynumerator))); return ($numerator / $denominator); } } if (!function_exists('NormalizeBinaryPoint')) { 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} == '.') { $binarypointnumber = '0'.$binarypointnumber; } $exponent = 0; while (($binarypointnumber{0} != '1') || (substr($binarypointnumber, 1, 1) != '.')) { if (substr($binarypointnumber, 1, 1) == '.') { $exponent--; $binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3); } else { $pointpos = strpos($binarypointnumber, '.'); $exponent += ($pointpos - 1); $binarypointnumber = str_replace('.', '', $binarypointnumber); $binarypointnumber = $binarypointnumber{0}.'.'.substr($binarypointnumber, 1); } } $binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT); return ['normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent]; } } if (!function_exists('Float2BinaryDecimal')) { 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 = trunc($floatvalue); $floatpart = abs($floatvalue - $intpart); $pointbitstring = ''; while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) { $floatpart *= 2; $pointbitstring .= (string) trunc($floatpart); $floatpart -= trunc($floatpart); } $binarypointnumber = decbin($intpart).'.'.$pointbitstring; return $binarypointnumber; } } if (!function_exists('Float2String')) { function Float2String($floatvalue, $bits) { // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html switch ($bits) { case 32: $exponentbits = 8; $fractionbits = 23; break; case 64: $exponentbits = 11; $fractionbits = 52; break; default: return false; break; } if ($floatvalue >= 0) { $signbit = '0'; } else { $signbit = '1'; } $normalizedbinary = NormalizeBinaryPoint(Float2BinaryDecimal($floatvalue), $fractionbits); $biasedexponent = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent $exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT); $fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT); return BigEndian2String(Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false); } } if (!function_exists('LittleEndian2Float')) { function LittleEndian2Float($byteword) { return BigEndian2Float(strrev($byteword)); } } if (!function_exists('BigEndian2Float')) { 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 = BigEndian2Bin($byteword); $signbit = $bitword{0}; switch (strlen($byteword) * 8) { case 32: $exponentbits = 8; $fractionbits = 23; break; case 64: $exponentbits = 11; $fractionbits = 52; break; case 80: $exponentbits = 16; $fractionbits = 64; break; default: return false; break; } $exponentstring = substr($bitword, 1, $exponentbits - 1); $fractionstring = substr($bitword, $exponentbits, $fractionbits); $exponent = Bin2Dec($exponentstring); $fraction = Bin2Dec($fractionstring); if (($exponentbits == 16) && ($fractionbits == 64)) { // 80-bit // As used in Apple AIFF for sample_rate // A bit of a hack, but it works ;) return pow(2, ($exponent - 16382)) * DecimalBinary2Float($fractionstring); } if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) { // Not a Number $floatvalue = false; } elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) { if ($signbit == '1') { $floatvalue = '-infinity'; } else { $floatvalue = '+infinity'; } } elseif (($exponent == 0) && ($fraction == 0)) { if ($signbit == '1') { $floatvalue = -0; } else { $floatvalue = 0; } $floatvalue = ($signbit ? 0 : -0); } elseif (($exponent == 0) && ($fraction != 0)) { // These are 'unnormalized' values $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * DecimalBinary2Float($fractionstring); if ($signbit == '1') { $floatvalue *= -1; } } elseif ($exponent != 0) { $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + DecimalBinary2Float($fractionstring)); if ($signbit == '1') { $floatvalue *= -1; } } return (float) $floatvalue; } } if (!function_exists('BigEndian2Int')) { function BigEndian2Int($byteword, $synchsafe = false, $signed = false) { $intvalue = 0; $bytewordlen = strlen($byteword); for ($i = 0; $i < $bytewordlen; $i++) { if ($synchsafe) { // disregard MSB, effectively 7-bit bytes $intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); } else { $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i)); } } if ($signed && !$synchsafe) { // synchsafe ints are not allowed to be signed switch ($bytewordlen) { case 1: case 2: case 3: case 4: $signmaskbit = 0x80 << (8 * ($bytewordlen - 1)); if ($intvalue & $signmaskbit) { $intvalue = 0 - ($intvalue & ($signmaskbit - 1)); } break; default: die('ERROR: Cannot have signed integers larger than 32-bits in BigEndian2Int()'); break; } } return CastAsInt($intvalue); } } if (!function_exists('LittleEndian2Int')) { function LittleEndian2Int($byteword, $signed = false) { return BigEndian2Int(strrev($byteword), false, $signed); } } if (!function_exists('BigEndian2Bin')) { 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); } return $binvalue; } } if (!function_exists('BigEndian2String')) { function BigEndian2String($number, $minbytes = 1, $synchsafe = false, $signed = false) { if ($number < 0) { return false; } $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF); $intstring = ''; if ($signed) { if ($minbytes > 4) { die('ERROR: Cannot have signed integers larger than 32-bits in BigEndian2String()'); } $number = $number & (0x80 << (8 * ($minbytes - 1))); } while ($number != 0) { $quotient = ($number / ($maskbyte + 1)); $intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring; $number = floor($quotient); } return str_pad($intstring, $minbytes, chr(0), STR_PAD_LEFT); } } if (!function_exists('Dec2Bin')) { function Dec2Bin($number) { while ($number >= 256) { $bytes[] = (($number / 256) - (floor($number / 256))) * 256; $number = floor($number / 256); } $bytes[] = $number; $binstring = ''; for ($i = 0; $i < count($bytes); $i++) { $binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring; } return $binstring; } } if (!function_exists('Bin2Dec')) { function Bin2Dec($binstring) { $decvalue = 0; for ($i = 0; $i < strlen($binstring); $i++) { $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i); } return CastAsInt($decvalue); } } if (!function_exists('Bin2String')) { function Bin2String($binstring) { // return 'hi' for input of '0110100001101001' $string = ''; $binstringreversed = strrev($binstring); for ($i = 0; $i < strlen($binstringreversed); $i += 8) { $string = chr(Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string; } return $string; } } if (!function_exists('LittleEndian2String')) { function LittleEndian2String($number, $minbytes = 1, $synchsafe = false) { $intstring = ''; while ($number > 0) { if ($synchsafe) { $intstring = $intstring.chr($number & 127); $number >>= 7; } else { $intstring = $intstring.chr($number & 255); $number >>= 8; } } return str_pad($intstring, $minbytes, chr(0), STR_PAD_RIGHT); } } if (!function_exists('Bool2IntString')) { function Bool2IntString($intvalue) { return ($intvalue ? '1' : '0'); } } if (!function_exists('IntString2Bool')) { function IntString2Bool($char) { if ($char == '1') { return true; } elseif ($char == '0') { return false; } return null; } } if (!function_exists('InverseBoolean')) { function InverseBoolean($value) { return ($value ? false : true); } } if (!function_exists('DeUnSynchronise')) { function DeUnSynchronise($data) { return str_replace(chr(0xFF).chr(0x00), chr(0xFF), $data); } } if (!function_exists('Unsynchronise')) { function Unsynchronise($data) { // Whenever a false synchronisation is found within the tag, one zeroed // byte is inserted after the first false synchronisation byte. The // format of a correct sync that should be altered by ID3 encoders is as // follows: // %11111111 111xxxxx // And should be replaced with: // %11111111 00000000 111xxxxx // This has the side effect that all $FF 00 combinations have to be // altered, so they won't be affected by the decoding process. Therefore // all the $FF 00 combinations have to be replaced with the $FF 00 00 // combination during the unsynchronisation. $data = str_replace(chr(0xFF).chr(0x00), chr(0xFF).chr(0x00).chr(0x00), $data); $unsyncheddata = ''; for ($i = 0; $i < strlen($data); $i++) { $thischar = $data{$i}; $unsyncheddata .= $thischar; if ($thischar == chr(255)) { $nextchar = ord(substr($data, $i + 1, 1)); if (($nextchar | 0xE0) == 0xE0) { // previous byte = 11111111, this byte = 111????? $unsyncheddata .= chr(0); } } } return $unsyncheddata; } } if (!function_exists('is_hash')) { function is_hash($var) { // written by dev-null@christophe.vg // taken from http://www.php.net/manual/en/function.array-merge-recursive.php if (is_array($var)) { $keys = array_keys($var); $all_num = true; for ($i = 0; $i < count($keys); $i++) { if (is_string($keys[$i])) { return true; } } } return false; } } if (!function_exists('array_join_merge')) { function array_join_merge($arr1, $arr2) { // written by dev-null@christophe.vg // taken from http://www.php.net/manual/en/function.array-merge-recursive.php if (is_array($arr1) && is_array($arr2)) { // the same -> merge $new_array = []; if (is_hash($arr1) && is_hash($arr2)) { // hashes -> merge based on keys $keys = array_merge(array_keys($arr1), array_keys($arr2)); foreach ($keys as $key) { $arr1[$key] = (isset($arr1[$key]) ? $arr1[$key] : ''); $arr2[$key] = (isset($arr2[$key]) ? $arr2[$key] : ''); $new_array[$key] = array_join_merge($arr1[$key], $arr2[$key]); } } else { // two real arrays -> merge $new_array = array_reverse(array_unique(array_reverse(array_merge($arr1, $arr2)))); } return $new_array; } else { // not the same ... take new one if defined, else the old one stays return $arr2 ? $arr2 : $arr1; } } } if (!function_exists('array_merge_clobber')) { function array_merge_clobber($array1, $array2) { // written by kc@hireability.com // taken from http://www.php.net/manual/en/function.array-merge-recursive.php if (!is_array($array1) || !is_array($array2)) { return false; } $newarray = $array1; foreach ($array2 as $key => $val) { if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { $newarray[$key] = array_merge_clobber($newarray[$key], $val); } else { $newarray[$key] = $val; } } return $newarray; } } if (!function_exists('array_merge_noclobber')) { function array_merge_noclobber($array1, $array2) { if (!is_array($array1) || !is_array($array2)) { return false; } $newarray = $array1; foreach ($array2 as $key => $val) { if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { $newarray[$key] = array_merge_noclobber($newarray[$key], $val); } elseif (!isset($newarray[$key])) { $newarray[$key] = $val; } } return $newarray; } } if (!function_exists('RoughTranslateUnicodeToASCII')) { function RoughTranslateUnicodeToASCII($rawdata, $frame_textencoding) { // rough translation of data for application that can't handle Unicode data $tempstring = ''; switch ($frame_textencoding) { case 0: // ISO-8859-1. Terminated with $00. $asciidata = $rawdata; break; case 1: // UTF-16 encoded Unicode with BOM. Terminated with $00 00. $asciidata = $rawdata; if (substr($asciidata, 0, 2) == chr(0xFF).chr(0xFE)) { // remove BOM, only if present (it should be, but...) $asciidata = substr($asciidata, 2); } if (substr($asciidata, strlen($asciidata) - 2, 2) == chr(0).chr(0)) { $asciidata = substr($asciidata, 0, strlen($asciidata) - 2); // remove terminator, only if present (it should be, but...) } for ($i = 0; $i < strlen($asciidata); $i += 2) { if ((ord($asciidata{$i}) <= 0x7F) || (ord($asciidata{$i}) >= 0xA0)) { $tempstring .= $asciidata{$i}; } else { $tempstring .= '?'; } } $asciidata = $tempstring; break; case 2: // UTF-16BE encoded Unicode without BOM. Terminated with $00 00. $asciidata = $rawdata; if (substr($asciidata, strlen($asciidata) - 2, 2) == chr(0).chr(0)) { $asciidata = substr($asciidata, 0, strlen($asciidata) - 2); // remove terminator, only if present (it should be, but...) } for ($i = 0; $i < strlen($asciidata); $i += 2) { if ((ord($asciidata{$i}) <= 0x7F) || (ord($asciidata{$i}) >= 0xA0)) { $tempstring .= $asciidata{$i}; } else { $tempstring .= '?'; } } $asciidata = $tempstring; break; case 3: // UTF-8 encoded Unicode. Terminated with $00. $asciidata = utf8_decode($rawdata); break; case 255: // Unicode, Big-Endian. Terminated with $00 00. $asciidata = $rawdata; if (substr($asciidata, strlen($asciidata) - 2, 2) == chr(0).chr(0)) { $asciidata = substr($asciidata, 0, strlen($asciidata) - 2); // remove terminator, only if present (it should be, but...) } for ($i = 0; ($i + 1) < strlen($asciidata); $i += 2) { if ((ord($asciidata{($i + 1)}) <= 0x7F) || (ord($asciidata{($i + 1)}) >= 0xA0)) { $tempstring .= $asciidata{($i + 1)}; } else { $tempstring .= '?'; } } $asciidata = $tempstring; break; default: // shouldn't happen, but in case $frame_textencoding is not 1 <= $frame_textencoding <= 4 // just pass the data through unchanged. $asciidata = $rawdata; break; } if (substr($asciidata, strlen($asciidata) - 1, 1) == chr(0)) { // remove null terminator, if present $asciidata = NoNullString($asciidata); } return $asciidata; // return str_replace(chr(0), '', $asciidata); // just in case any nulls slipped through } } if (!function_exists('PlaytimeString')) { function PlaytimeString($playtimeseconds) { $contentseconds = round((($playtimeseconds / 60) - floor($playtimeseconds / 60)) * 60); $contentminutes = floor($playtimeseconds / 60); if ($contentseconds >= 60) { $contentseconds -= 60; $contentminutes++; } return number_format($contentminutes).':'.str_pad($contentseconds, 2, 0, STR_PAD_LEFT); } } if (!function_exists('CloseMatch')) { function CloseMatch($value1, $value2, $tolerance) { return (abs($value1 - $value2) <= $tolerance); } } if (!function_exists('ID3v1matchesID3v2')) { function ID3v1matchesID3v2($id3v1, $id3v2) { $requiredindices = ['title', 'artist', 'album', 'year', 'genre', 'comment']; foreach ($requiredindices as $requiredindex) { if (!isset($id3v1["$requiredindex"])) { $id3v1["$requiredindex"] = ''; } if (!isset($id3v2["$requiredindex"])) { $id3v2["$requiredindex"] = ''; } } if (trim($id3v1['title']) != trim(substr($id3v2['title'], 0, 30))) { return false; } if (trim($id3v1['artist']) != trim(substr($id3v2['artist'], 0, 30))) { return false; } if (trim($id3v1['album']) != trim(substr($id3v2['album'], 0, 30))) { return false; } if (trim($id3v1['year']) != trim(substr($id3v2['year'], 0, 4))) { return false; } if (trim($id3v1['genre']) != trim($id3v2['genre'])) { return false; } if (isset($id3v1['track'])) { if (!isset($id3v1['track']) || (trim($id3v1['track']) != trim($id3v2['track']))) { return false; } if (trim($id3v1['comment']) != trim(substr($id3v2['comment'], 0, 28))) { return false; } } else { if (trim($id3v1['comment']) != trim(substr($id3v2['comment'], 0, 30))) { return false; } } return true; } } if (!function_exists('FILETIMEtoUNIXtime')) { function FILETIMEtoUNIXtime($FILETIME, $round = true) { // FILETIME is a 64-bit unsigned integer representing // the number of 100-nanosecond intervals since January 1, 1601 // UNIX timestamp is number of seconds since January 1, 1970 // 116444736000000000 = 10000000 * 60 * 60 * 24 * 365 * 369 + 89 leap days if ($round) { return round(($FILETIME - 116444736000000000) / 10000000); } return ($FILETIME - 116444736000000000) / 10000000; } } if (!function_exists('GUIDtoBytestring')) { function GUIDtoBytestring($GUIDstring) { // Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way: // first 4 bytes are in little-endian order // next 2 bytes are appended in little-endian order // next 2 bytes are appended in little-endian order // next 2 bytes are appended in big-endian order // next 6 bytes are appended in big-endian order // AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp is stored as this 16-byte string: // $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp $hexbytecharstring = chr(hexdec(substr($GUIDstring, 6, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 4, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 2, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 0, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 11, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 9, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 16, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 14, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 19, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 21, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 24, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 26, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 28, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 30, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 32, 2))); $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 34, 2))); return $hexbytecharstring; } } if (!function_exists('BytestringToGUID')) { 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 .= '-'; $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 .= '-'; $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); return strtoupper($GUIDstring); } } if (!function_exists('BitrateColor')) { function BitrateColor($bitrate) { $bitrate /= 3; // scale from 1-768kbps to 1-256kbps $bitrate--; // scale from 1-256kbps to 0-255kbps $bitrate = max($bitrate, 0); $bitrate = min($bitrate, 255); //$bitrate = max($bitrate, 32); //$bitrate = min($bitrate, 143); //$bitrate = ($bitrate * 2) - 32; $Rcomponent = max(255 - ($bitrate * 2), 0); $Gcomponent = max(($bitrate * 2) - 255, 0); if ($bitrate > 127) { $Bcomponent = max((255 - $bitrate) * 2, 0); } else { $Bcomponent = max($bitrate * 2, 0); } return str_pad(dechex($Rcomponent), 2, '0', STR_PAD_LEFT).str_pad(dechex($Gcomponent), 2, '0', STR_PAD_LEFT).str_pad(dechex($Bcomponent), 2, '0', STR_PAD_LEFT); } } if (!function_exists('BitrateText')) { function BitrateText($bitrate) { return ''.round($bitrate).' kbps'; } } if (!function_exists('image_type_to_mime_type')) { function image_type_to_mime_type($imagetypeid) { // only available in PHP v4.3.0+ static $image_type_to_mime_type = []; if (empty($image_type_to_mime_type)) { $image_type_to_mime_type[1] = 'image/gif'; // GIF $image_type_to_mime_type[2] = 'image/jpeg'; // JPEG $image_type_to_mime_type[3] = 'image/png'; // PNG $image_type_to_mime_type[4] = 'application/x-shockwave-flash'; // Flash $image_type_to_mime_type[5] = 'image/psd'; // PSD $image_type_to_mime_type[6] = 'image/bmp'; // BMP $image_type_to_mime_type[7] = 'image/tiff'; // TIFF: little-endian (Intel) $image_type_to_mime_type[8] = 'image/tiff'; // TIFF: big-endian (Motorola) //$image_type_to_mime_type[9] = 'image/jpc'; // JPC //$image_type_to_mime_type[10] = 'image/jp2'; // JPC //$image_type_to_mime_type[11] = 'image/jpx'; // JPC //$image_type_to_mime_type[12] = 'image/jb2'; // JPC $image_type_to_mime_type[13] = 'application/x-shockwave-flash'; // Shockwave $image_type_to_mime_type[14] = 'image/iff'; // IFF } return (isset($image_type_to_mime_type[$imagetypeid]) ? $image_type_to_mime_type[$imagetypeid] : 'application/octet-stream'); } } if (!function_exists('utf8_decode')) { // PHP has this function built-in if it's configured with the --with-xml option // This version of the function is only provided in case XML isn't installed function utf8_decode($utf8text) { // http://www.php.net/manual/en/function.utf8-encode.php // bytes bits representation // 1 7 0bbbbbbb // 2 11 110bbbbb 10bbbbbb // 3 16 1110bbbb 10bbbbbb 10bbbbbb // 4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb $utf8length = strlen($utf8text); $decodedtext = ''; for ($i = 0; $i < $utf8length; $i++) { if ((ord($utf8text{$i}) & 0x80) == 0) { $decodedtext .= $utf8text{$i}; } elseif ((ord($utf8text{$i}) & 0xF0) == 0xF0) { $decodedtext .= '?'; $i += 3; } elseif ((ord($utf8text{$i}) & 0xE0) == 0xE0) { $decodedtext .= '?'; $i += 2; } elseif ((ord($utf8text{$i}) & 0xC0) == 0xC0) { // 2 11 110bbbbb 10bbbbbb $decodedchar = Bin2Dec(substr(Dec2Bin(ord($utf8text{$i})), 3, 5).substr(Dec2Bin(ord($utf8text{($i + 1)})), 2, 6)); if ($decodedchar <= 255) { $decodedtext .= chr($decodedchar); } else { $decodedtext .= '?'; } $i += 1; } } return $decodedtext; } } if (!function_exists('DateMac2Unix')) { function DateMac2Unix($macdate) { // Macintosh timestamp: seconds since 00:00h January 1, 1904 // UNIX timestamp: seconds since 00:00h January 1, 1970 return CastAsInt($macdate - 2082844800); } } if (!function_exists('FixedPoint8_8')) { function FixedPoint8_8($rawdata) { return BigEndian2Int(substr($rawdata, 0, 1)) + (float) (BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); } } if (!function_exists('FixedPoint16_16')) { function FixedPoint16_16($rawdata) { return BigEndian2Int(substr($rawdata, 0, 2)) + (float) (BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); } } if (!function_exists('FixedPoint2_30')) { function FixedPoint2_30($rawdata) { $binarystring = BigEndian2Bin($rawdata); return Bin2Dec(substr($binarystring, 0, 2)) + (float) (Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30)); } } if (!function_exists('Pascal2String')) { function Pascal2String($pascalstring) { // Pascal strings have 1 byte at the beginning saying how many chars are in the string return substr($pascalstring, 1); } } if (!function_exists('NoNullString')) { function NoNullString($nullterminatedstring) { // remove the single null terminator on null terminated strings if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === chr(0)) { return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1); } return $nullterminatedstring; } } if (!function_exists('FileSizeNiceDisplay')) { function FileSizeNiceDisplay($filesize, $precision = 2) { if ($filesize < 1000) { $sizeunit = 'bytes'; $precision = 0; } else { $filesize /= 1024; $sizeunit = 'kB'; } if ($filesize >= 1000) { $filesize /= 1024; $sizeunit = 'MB'; } if ($filesize >= 1000) { $filesize /= 1024; $sizeunit = 'GB'; } return number_format($filesize, $precision).' '.$sizeunit; } } if (!function_exists('DOStime2UNIXtime')) { function DOStime2UNIXtime($DOSdate, $DOStime) { // wFatDate // Specifies the MS-DOS date. The date is a packed 16-bit value with the following format: // Bits Contents // 0-4 Day of the month (1-31) // 5-8 Month (1 = January, 2 = February, and so on) // 9-15 Year offset from 1980 (add 1980 to get actual year) $UNIXday = ($DOSdate & 0x001F); $UNIXmonth = (($DOSdate & 0x01E0) >> 5); $UNIXyear = (($DOSdate & 0xFE00) >> 9) + 1980; // wFatTime // Specifies the MS-DOS time. The time is a packed 16-bit value with the following format: // Bits Contents // 0-4 Second divided by 2 // 5-10 Minute (0-59) // 11-15 Hour (0-23 on a 24-hour clock) $UNIXsecond = ($DOStime & 0x001F) * 2; $UNIXminute = (($DOStime & 0x07E0) >> 5); $UNIXhour = (($DOStime & 0xF800) >> 11); return mktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear); } } if (!function_exists('CreateDeepArray')) { function CreateDeepArray($ArrayPath, $Separator, $Value) { // assigns $Value to a nested array path: // $foo = CreateDeepArray('/path/to/my', '/', 'file.txt') // is the same as: // $foo = array('path'=>array('to'=>'array('my'=>array('file.txt')))); // or // $foo['path']['to']['my'] = 'file.txt'; while ($ArrayPath{0} == $Separator) { $ArrayPath = substr($ArrayPath, 1); } if (($pos = strpos($ArrayPath, $Separator)) !== false) { $ReturnedArray[substr($ArrayPath, 0, $pos)] = CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value); } else { $ReturnedArray["$ArrayPath"] = $Value; } return $ReturnedArray; } } if (!function_exists('md5_file')) { // Allan Hansen // md5_file() exists in PHP 4.2.0. // The following works under UNIX only, but dies on windows function md5_file($file) { if (substr(php_uname(), 0, 7) == 'Windows') { die('PHP 4.2.0 or newer required for md5_file()'); } $file = str_replace('`', '\\`', $file); if (preg_match("#^([0-9a-f]{32})[ \t\n\r]#i", `md5sum "$file"`, $r)) { return $r[1]; } return false; } } if (!function_exists('md5_data')) { // Allan Hansen // md5_data() - returns md5sum for a file from startuing position to absolute end position function md5_data($file, $offset, $end, $invertsign = false) { // first try and create a temporary file in the same directory as the file being scanned if (($dataMD5filename = tempnam(dirname($file), preg_replace('#[^[:alnum:]]#i', '', basename($file)))) === false) { // if that fails, create a temporary file in the system temp directory if (($dataMD5filename = tempnam('/tmp', 'getID3')) === false) { // if that fails, create a temporary file in the current directory if (($dataMD5filename = tempnam('.', preg_replace('#[^[:alnum:]]#i', '', basename($file)))) === false) { // can't find anywhere to create a temp file, just die return false; } } } $md5 = false; set_time_limit(max(filesize($file) / 1000000, 30)); // copy parts of file ob_start(); if ($fp = fopen($file, 'rb')) { ob_end_clean(); ob_start(); if ($MD5fp = fopen($dataMD5filename, 'wb')) { ob_end_clean(); if ($invertsign) { // Load conversion lookup strings for 8-bit unsigned->signed conversion below $from = ''; $to = ''; for ($i = 0; $i < 128; $i++) { $from .= chr($i); $to .= chr($i + 128); } for ($i = 128; $i < 256; $i++) { $from .= chr($i); $to .= chr($i - 128); } } fseek($fp, $offset, SEEK_SET); $byteslefttowrite = $end - $offset; while (($byteslefttowrite > 0) && ($buffer = fread($fp, 32768))) { if ($invertsign) { // Possibly FLAC-specific (?) // FLAC calculates the MD5sum of the source data of 8-bit files // not on the actual byte values in the source file, but of those // values converted from unsigned to signed, or more specifcally, // with the MSB inverted. ex: 01 -> 81; F5 -> 75; etc // Therefore, 8-bit WAV data has to be converted before getting the // md5_data value so as to match the FLAC value // Flip the MSB for each byte in the buffer before copying $buffer = strtr($buffer, $from, $to); } $byteswritten = fwrite($MD5fp, $buffer, $byteslefttowrite); $byteslefttowrite -= $byteswritten; } fclose($MD5fp); $md5 = md5_file($dataMD5filename); } else { $errormessage = ob_get_contents(); ob_end_clean(); } fclose($fp); } else { $errormessage = ob_get_contents(); ob_end_clean(); } unlink($dataMD5filename); return $md5; } } if (!function_exists('TwosCompliment2Decimal')) { function TwosCompliment2Decimal($BinaryValue) { // http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html // First check if the number is negative or positive by looking at the sign bit. // If it is positive, simply convert it to decimal. // If it is negative, make it positive by inverting the bits and adding one. // Then, convert the result to decimal. // The negative of this number is the value of the original binary. if ($BinaryValue & 0x80) { // negative number return (0 - ((~$BinaryValue & 0xFF) + 1)); } else { // positive number return $BinaryValue; } } } if (!function_exists('LastArrayElement')) { function LastArrayElement($MyArray) { if (!is_array($MyArray)) { return false; } if (empty($MyArray)) { return null; } foreach ($MyArray as $key => $value) { } return $value; } } if (!function_exists('safe_inc')) { function safe_inc(&$variable, $increment = 1) { if (isset($variable)) { $variable += $increment; } else { $variable = $increment; } return true; } } if (!function_exists('CalculateCompressionRatioVideo')) { function CalculateCompressionRatioVideo(&$ThisFileInfo) { if (empty($ThisFileInfo['video'])) { return false; } if (empty($ThisFileInfo['video']['resolution_x']) || empty($ThisFileInfo['video']['resolution_y'])) { return false; } if (empty($ThisFileInfo['video']['bits_per_sample'])) { return false; } switch ($ThisFileInfo['video']['dataformat']) { case 'bmp': case 'gif': case 'jpeg': case 'jpg': case 'png': case 'tiff': $FrameRate = 1; $PlaytimeSeconds = 1; $BitrateCompressed = $ThisFileInfo['filesize'] * 8; break; default: if (!empty($ThisFileInfo['video']['frame_rate'])) { $FrameRate = $ThisFileInfo['video']['frame_rate']; } else { return false; } if (!empty($ThisFileInfo['playtime_seconds'])) { $PlaytimeSeconds = $ThisFileInfo['playtime_seconds']; } else { return false; } if (!empty($ThisFileInfo['video']['bitrate'])) { $BitrateCompressed = $ThisFileInfo['video']['bitrate']; } else { return false; } break; } $BitrateUncompressed = $ThisFileInfo['video']['resolution_x'] * $ThisFileInfo['video']['resolution_y'] * $ThisFileInfo['video']['bits_per_sample'] * $FrameRate; $ThisFileInfo['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed; return true; } } if (!function_exists('CalculateCompressionRatioAudio')) { function CalculateCompressionRatioAudio(&$ThisFileInfo) { if (empty($ThisFileInfo['audio']['bitrate']) || empty($ThisFileInfo['audio']['channels']) || empty($ThisFileInfo['audio']['sample_rate']) || empty($ThisFileInfo['audio']['bits_per_sample'])) { return false; } $ThisFileInfo['audio']['compression_ratio'] = $ThisFileInfo['audio']['bitrate'] / ($ThisFileInfo['audio']['channels'] * $ThisFileInfo['audio']['sample_rate'] * $ThisFileInfo['audio']['bits_per_sample']); return true; } } if (!function_exists('IsValidMIMEstring')) { function IsValidMIMEstring($mimestring) { if ((strlen($mimestring) >= 3) && (strpos($mimestring, '/') > 0) && (strpos($mimestring, '/') < (strlen($mimestring) - 1))) { return true; } return false; } } if (!function_exists('IsWithinBitRange')) { function IsWithinBitRange($number, $maxbits, $signed = false) { if ($signed) { if (($number > (0 - pow(2, $maxbits - 1))) && ($number <= pow(2, $maxbits - 1))) { return true; } } else { if (($number >= 0) && ($number <= pow(2, $maxbits))) { return true; } } return false; } } if (!function_exists('safe_parse_url')) { function safe_parse_url($url) { ob_start(); $parts = parse_url($url); $errormessage = ob_get_contents(); ob_end_clean(); $parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : ''); $parts['host'] = (isset($parts['host']) ? $parts['host'] : ''); $parts['user'] = (isset($parts['user']) ? $parts['user'] : ''); $parts['pass'] = (isset($parts['pass']) ? $parts['pass'] : ''); $parts['path'] = (isset($parts['path']) ? $parts['path'] : ''); $parts['query'] = (isset($parts['query']) ? $parts['query'] : ''); return $parts; } } if (!function_exists('IsValidURL')) { function IsValidURL($url, $allowUserPass = false) { if ($url == '') { return false; } if ($allowUserPass !== true) { if (strstr($url, '@')) { // in the format http://user:pass@example.com or http://user@example.com // but could easily be somebody incorrectly entering an email address in place of a URL return false; } } if ($parts = safe_parse_url($url)) { if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher')) { return false; } elseif (!preg_match("#^[[:alnum:]]([-.]?[0-9a-z])*\.[a-z]{2,3}#i$", $parts['host'], $regs) && !preg_match('#^[0-9]{1,3}(\.[0-9]{1,3}){3}$#', $parts['host'])) { return false; } elseif (!preg_match("#^([[:alnum:]-]|[\_])*$#i", $parts['user'], $regs)) { return false; } elseif (!preg_match("#^([[:alnum:]-]|[\_])*$#i", $parts['pass'], $regs)) { return false; } elseif (!preg_match("#^[[:alnum:]/_\.@~-]*$#i", $parts['path'], $regs)) { return false; } elseif (!preg_match("#^[[:alnum:]?&=+:;_()%#/,\.-]*$#i", $parts['query'], $regs)) { return false; } else { return true; } } return false; } } echo '
'; echo 'Enter 4 hex bytes of MPEG-audio header (ie FF FA 92 44)
'; echo ''; echo '
'; echo '
'; echo '
'; echo 'Generate a MPEG-audio 4-byte header from these values:
'; echo ''; $MPEGgenerateValues = [ 'version'=>['1', '2', '2.5'], 'layer'=>['I', 'II', 'III'], 'protection'=>['Y', 'N'], 'bitrate'=>['free', '8', '16', '24', '32', '40', '48', '56', '64', '80', '96', '112', '128', '144', '160', '176', '192', '224', '256', '288', '320', '352', '384', '416', '448'], 'frequency'=>['8000', '11025', '12000', '16000', '22050', '24000', '32000', '44100', '48000'], 'padding'=>['Y', 'N'], 'private'=>['Y', 'N'], 'channelmode'=>['stereo', 'joint stereo', 'dual channel', 'mono'], 'modeextension'=>['none', 'IS', 'MS', 'IS+MS', '4-31', '8-31', '12-31', '16-31'], 'copyright'=>['Y', 'N'], 'original'=>['Y', 'N'], 'emphasis'=>['none', '50/15ms', 'CCIT J.17'] ]; foreach ($MPEGgenerateValues as $name => $dataarray) { echo ''; } if (isset($_POST['bitrate'])) { echo ''; } echo '
'.$name.':
Frame Length:'.(int) MPEGaudioFrameLength($_POST['bitrate'], $_POST['version'], $_POST['layer'], (($_POST['padding'] == 'Y') ? '1' : '0'), $_POST['frequency']).'
'; echo '
'; echo '
'; if (isset($_POST['Analyze']) && $_POST['HeaderHexBytes']) { $headerbytearray = explode(' ', $_POST['HeaderHexBytes']); if (count($headerbytearray) != 4) { die('Invalid byte pattern'); } $headerstring = ''; foreach ($headerbytearray as $textbyte) { $headerstring .= chr(hexdec($textbyte)); } $MP3fileInfo['error'] = ''; $MPEGheaderRawArray = MPEGaudioHeaderDecode(substr($headerstring, 0, 4)); if (MPEGaudioHeaderValid($MPEGheaderRawArray, true)) { $MP3fileInfo['raw'] = $MPEGheaderRawArray; $MP3fileInfo['version'] = MPEGaudioVersionLookup($MP3fileInfo['raw']['version']); $MP3fileInfo['layer'] = MPEGaudioLayerLookup($MP3fileInfo['raw']['layer']); $MP3fileInfo['protection'] = MPEGaudioCRCLookup($MP3fileInfo['raw']['protection']); $MP3fileInfo['bitrate'] = MPEGaudioBitrateLookup($MP3fileInfo['version'], $MP3fileInfo['layer'], $MP3fileInfo['raw']['bitrate']); $MP3fileInfo['frequency'] = MPEGaudioFrequencyLookup($MP3fileInfo['version'], $MP3fileInfo['raw']['sample_rate']); $MP3fileInfo['padding'] = (bool) $MP3fileInfo['raw']['padding']; $MP3fileInfo['private'] = (bool) $MP3fileInfo['raw']['private']; $MP3fileInfo['channelmode'] = MPEGaudioChannelModeLookup($MP3fileInfo['raw']['channelmode']); $MP3fileInfo['channels'] = (($MP3fileInfo['channelmode'] == 'mono') ? 1 : 2); $MP3fileInfo['modeextension'] = MPEGaudioModeExtensionLookup($MP3fileInfo['layer'], $MP3fileInfo['raw']['modeextension']); $MP3fileInfo['copyright'] = (bool) $MP3fileInfo['raw']['copyright']; $MP3fileInfo['original'] = (bool) $MP3fileInfo['raw']['original']; $MP3fileInfo['emphasis'] = MPEGaudioEmphasisLookup($MP3fileInfo['raw']['emphasis']); if ($MP3fileInfo['protection']) { $MP3fileInfo['crc'] = BigEndian2Int(substr($headerstring, 4, 2)); } if ($MP3fileInfo['frequency'] > 0) { $MP3fileInfo['framelength'] = MPEGaudioFrameLength($MP3fileInfo['bitrate'], $MP3fileInfo['version'], $MP3fileInfo['layer'], (int) $MP3fileInfo['padding'], $MP3fileInfo['frequency']); } if ($MP3fileInfo['bitrate'] != 'free') { $MP3fileInfo['bitrate'] *= 1000; } } else { $MP3fileInfo['error'] .= "\n".'Invalid MPEG audio header'; } if (!$MP3fileInfo['error']) { unset($MP3fileInfo['error']); } echo table_var_dump($MP3fileInfo); } elseif (isset($_POST['Generate'])) { // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM $headerbitstream = '11111111111'; // A - Frame sync (all bits set) $MPEGversionLookup = ['2.5'=>'00', '2'=>'10', '1'=>'11']; $headerbitstream .= $MPEGversionLookup[$_POST['version']]; // B - MPEG Audio version ID $MPEGlayerLookup = ['III'=>'01', 'II'=>'10', 'I'=>'11']; $headerbitstream .= $MPEGlayerLookup[$_POST['layer']]; // C - Layer description $headerbitstream .= (($_POST['protection'] == 'Y') ? '0' : '1'); // D - Protection bit $MPEGaudioBitrateLookup['1']['I'] = ['free'=>'0000', '32'=>'0001', '64'=>'0010', '96'=>'0011', '128'=>'0100', '160'=>'0101', '192'=>'0110', '224'=>'0111', '256'=>'1000', '288'=>'1001', '320'=>'1010', '352'=>'1011', '384'=>'1100', '416'=>'1101', '448'=>'1110']; $MPEGaudioBitrateLookup['1']['II'] = ['free'=>'0000', '32'=>'0001', '48'=>'0010', '56'=>'0011', '64'=>'0100', '80'=>'0101', '96'=>'0110', '112'=>'0111', '128'=>'1000', '160'=>'1001', '192'=>'1010', '224'=>'1011', '256'=>'1100', '320'=>'1101', '384'=>'1110']; $MPEGaudioBitrateLookup['1']['III'] = ['free'=>'0000', '32'=>'0001', '40'=>'0010', '48'=>'0011', '56'=>'0100', '64'=>'0101', '80'=>'0110', '96'=>'0111', '112'=>'1000', '128'=>'1001', '160'=>'1010', '192'=>'1011', '224'=>'1100', '256'=>'1101', '320'=>'1110']; $MPEGaudioBitrateLookup['2']['I'] = ['free'=>'0000', '32'=>'0001', '48'=>'0010', '56'=>'0011', '64'=>'0100', '80'=>'0101', '96'=>'0110', '112'=>'0111', '128'=>'1000', '144'=>'1001', '160'=>'1010', '176'=>'1011', '192'=>'1100', '224'=>'1101', '256'=>'1110']; $MPEGaudioBitrateLookup['2']['II'] = ['free'=>'0000', '8'=>'0001', '16'=>'0010', '24'=>'0011', '32'=>'0100', '40'=>'0101', '48'=>'0110', '56'=>'0111', '64'=>'1000', '80'=>'1001', '96'=>'1010', '112'=>'1011', '128'=>'1100', '144'=>'1101', '160'=>'1110']; $MPEGaudioBitrateLookup['2']['III'] = $MPEGaudioBitrateLookup['2']['II']; $MPEGaudioBitrateLookup['2.5']['I'] = $MPEGaudioBitrateLookup['2']['I']; $MPEGaudioBitrateLookup['2.5']['II'] = $MPEGaudioBitrateLookup['2']['II']; $MPEGaudioBitrateLookup['2.5']['III'] = $MPEGaudioBitrateLookup['2']['II']; if (isset($MPEGaudioBitrateLookup[$_POST['version']][$_POST['layer']][$_POST['bitrate']])) { $headerbitstream .= $MPEGaudioBitrateLookup[$_POST['version']][$_POST['layer']][$_POST['bitrate']]; // E - Bitrate index } else { die('Invalid Bitrate'); } $MPEGaudioFrequencyLookup['1'] = ['44100'=>'00', '48000'=>'01', '32000'=>'10']; $MPEGaudioFrequencyLookup['2'] = ['22050'=>'00', '24000'=>'01', '16000'=>'10']; $MPEGaudioFrequencyLookup['2.5'] = ['11025'=>'00', '12000'=>'01', '8000'=>'10']; if (isset($MPEGaudioFrequencyLookup[$_POST['version']][$_POST['frequency']])) { $headerbitstream .= $MPEGaudioFrequencyLookup[$_POST['version']][$_POST['frequency']]; // F - Sampling rate frequency index } else { die('Invalid Frequency'); } $headerbitstream .= (($_POST['padding'] == 'Y') ? '1' : '0'); // G - Padding bit $headerbitstream .= (($_POST['private'] == 'Y') ? '1' : '0'); // H - Private bit $MPEGaudioChannelModeLookup = ['stereo'=>'00', 'joint stereo'=>'01', 'dual channel'=>'10', 'mono'=>'11']; $headerbitstream .= $MPEGaudioChannelModeLookup[$_POST['channelmode']]; // I - Channel Mode $MPEGaudioModeExtensionLookup['I'] = ['4-31'=>'00', '8-31'=>'01', '12-31'=>'10', '16-31'=>'11']; $MPEGaudioModeExtensionLookup['II'] = $MPEGaudioModeExtensionLookup['I']; $MPEGaudioModeExtensionLookup['III'] = ['none'=>'00', 'IS'=>'01', 'MS'=>'10', 'IS+MS'=>'11']; if ($_POST['channelmode'] != 'joint stereo') { $headerbitstream .= '00'; } elseif (isset($MPEGaudioModeExtensionLookup[$_POST['layer']][$_POST['modeextension']])) { $headerbitstream .= $MPEGaudioModeExtensionLookup[$_POST['layer']][$_POST['modeextension']]; // J - Mode extension (Only if Joint stereo) } else { die('Invalid Mode Extension'); } $headerbitstream .= (($_POST['copyright'] == 'Y') ? '1' : '0'); // K - Copyright $headerbitstream .= (($_POST['original'] == 'Y') ? '1' : '0'); // L - Original $MPEGaudioEmphasisLookup = ['none'=>'00', '50/15ms'=>'01', 'CCIT J.17'=>'11']; if (isset($MPEGaudioEmphasisLookup[$_POST['emphasis']])) { $headerbitstream .= $MPEGaudioEmphasisLookup[$_POST['emphasis']]; // M - Emphasis } else { die('Invalid Emphasis'); } echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream, 0, 8))), 2, '0', STR_PAD_LEFT)).' '; echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream, 8, 8))), 2, '0', STR_PAD_LEFT)).' '; echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream, 16, 8))), 2, '0', STR_PAD_LEFT)).' '; echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream, 24, 8))), 2, '0', STR_PAD_LEFT)).'
'; } function MPEGaudioVersionLookup($rawversion) { $MPEGaudioVersionLookup = ['2.5', false, '2', '1']; return (isset($MPEGaudioVersionLookup["$rawversion"]) ? $MPEGaudioVersionLookup["$rawversion"] : false); } function MPEGaudioLayerLookup($rawlayer) { $MPEGaudioLayerLookup = [false, 'III', 'II', 'I']; return (isset($MPEGaudioLayerLookup["$rawlayer"]) ? $MPEGaudioLayerLookup["$rawlayer"] : false); } function MPEGaudioBitrateLookup($version, $layer, $rawbitrate) { static $MPEGaudioBitrateLookup; if (empty($MPEGaudioBitrateLookup)) { $MPEGaudioBitrateLookup = MPEGaudioBitrateArray(); } return (isset($MPEGaudioBitrateLookup["$version"]["$layer"]["$rawbitrate"]) ? $MPEGaudioBitrateLookup["$version"]["$layer"]["$rawbitrate"] : false); } function MPEGaudioFrequencyLookup($version, $rawfrequency) { static $MPEGaudioFrequencyLookup; if (empty($MPEGaudioFrequencyLookup)) { $MPEGaudioFrequencyLookup = MPEGaudioFrequencyArray(); } return (isset($MPEGaudioFrequencyLookup["$version"]["$rawfrequency"]) ? $MPEGaudioFrequencyLookup["$version"]["$rawfrequency"] : false); } function MPEGaudioChannelModeLookup($rawchannelmode) { $MPEGaudioChannelModeLookup = ['stereo', 'joint stereo', 'dual channel', 'mono']; return (isset($MPEGaudioChannelModeLookup["$rawchannelmode"]) ? $MPEGaudioChannelModeLookup["$rawchannelmode"] : false); } function MPEGaudioModeExtensionLookup($layer, $rawmodeextension) { $MPEGaudioModeExtensionLookup['I'] = ['4-31', '8-31', '12-31', '16-31']; $MPEGaudioModeExtensionLookup['II'] = ['4-31', '8-31', '12-31', '16-31']; $MPEGaudioModeExtensionLookup['III'] = ['', 'IS', 'MS', 'IS+MS']; return (isset($MPEGaudioModeExtensionLookup["$layer"]["$rawmodeextension"]) ? $MPEGaudioModeExtensionLookup["$layer"]["$rawmodeextension"] : false); } function MPEGaudioEmphasisLookup($rawemphasis) { $MPEGaudioEmphasisLookup = ['none', '50/15ms', false, 'CCIT J.17']; return (isset($MPEGaudioEmphasisLookup["$rawemphasis"]) ? $MPEGaudioEmphasisLookup["$rawemphasis"] : false); } function MPEGaudioCRCLookup($CRCbit) { // inverse boolean cast :) if ($CRCbit == '0') { return true; } else { return false; } } ///////////////////////////////////////////////////////////////// /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net /// // or http://www.getid3.org /// ///////////////////////////////////////////////////////////////// // // // getid3.mp3.php - part of getID3() // // See getid3.readme.txt for more details // // // ///////////////////////////////////////////////////////////////// // number of frames to scan to determine if MPEG-audio sequence is valid // Lower this number to 5-20 for faster scanning // Increase this number to 50+ for most accurate detection of valid VBR/CBR // mpeg-audio streams define('MPEG_VALID_CHECK_FRAMES', 35); function getMP3headerFilepointer(&$fd, &$ThisFileInfo) { getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $ThisFileInfo['avdataoffset']); if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode'])) { $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); } if (((isset($ThisFileInfo['id3v2']) && ($ThisFileInfo['avdataoffset'] > $ThisFileInfo['id3v2']['headerlength'])) || (!isset($ThisFileInfo['id3v2']) && ($ThisFileInfo['avdataoffset'] > 0)))) { $ThisFileInfo['warning'] .= "\n".'Unknown data before synch '; if (isset($ThisFileInfo['id3v2']['headerlength'])) { $ThisFileInfo['warning'] .= '(ID3v2 header ends at '.$ThisFileInfo['id3v2']['headerlength'].', then '.($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']).' bytes garbage, '; } else { $ThisFileInfo['warning'] .= '(should be at beginning of file, '; } $ThisFileInfo['warning'] .= 'synch detected at '.$ThisFileInfo['avdataoffset'].')'; if ($ThisFileInfo['audio']['bitrate_mode'] == 'cbr') { if (!empty($ThisFileInfo['id3v2']['headerlength']) && (($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']) == $ThisFileInfo['mpeg']['audio']['framelength'])) { $ThisFileInfo['warning'] .= '. This is a known problem with some versions of LAME (3.91, 3.92) DLL in CBR mode.'; $ThisFileInfo['audio']['codec'] = 'LAME'; } elseif (empty($ThisFileInfo['id3v2']['headerlength']) && ($ThisFileInfo['avdataoffset'] == $ThisFileInfo['mpeg']['audio']['framelength'])) { $ThisFileInfo['warning'] .= '. This is a known problem with some versions of LAME (3.91, 3.92) DLL in CBR mode.'; $ThisFileInfo['audio']['codec'] = 'LAME'; } } } if (isset($ThisFileInfo['mpeg']['audio']['layer']) && ($ThisFileInfo['mpeg']['audio']['layer'] == 'II')) { $ThisFileInfo['audio']['dataformat'] = 'mp2'; } elseif (isset($ThisFileInfo['mpeg']['audio']['layer']) && ($ThisFileInfo['mpeg']['audio']['layer'] == 'I')) { $ThisFileInfo['audio']['dataformat'] = 'mp1'; } if ($ThisFileInfo['fileformat'] == 'mp3') { switch ($ThisFileInfo['audio']['dataformat']) { case 'mp1': case 'mp2': case 'mp3': $ThisFileInfo['fileformat'] = $ThisFileInfo['audio']['dataformat']; break; default: $ThisFileInfo['warning'] .= "\n".'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$ThisFileInfo['audio']['dataformat'].'"'; break; } } if (empty($ThisFileInfo['fileformat'])) { $ThisFileInfo['error'] .= "\n".'Synch not found'; unset($ThisFileInfo['fileformat']); unset($ThisFileInfo['audio']['bitrate_mode']); unset($ThisFileInfo['avdataoffset']); unset($ThisFileInfo['avdataend']); return false; } $ThisFileInfo['mime_type'] = 'audio/mpeg'; $ThisFileInfo['audio']['lossless'] = false; // Calculate playtime if (!isset($ThisFileInfo['playtime_seconds']) && isset($ThisFileInfo['audio']['bitrate']) && ($ThisFileInfo['audio']['bitrate'] > 0)) { $ThisFileInfo['playtime_seconds'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 / $ThisFileInfo['audio']['bitrate']; } if (isset($ThisFileInfo['mpeg']['audio']['LAME'])) { $ThisFileInfo['audio']['codec'] = 'LAME'; if (!empty($ThisFileInfo['mpeg']['audio']['LAME']['long_version'])) { $ThisFileInfo['audio']['encoder'] = trim($ThisFileInfo['mpeg']['audio']['LAME']['long_version']); } } return true; } function decodeMPEGaudioHeader($fd, $offset, &$ThisFileInfo, $recursivesearch = true, $ScanAsCBR = false, $FastMPEGheaderScan = false) { static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; static $MPEGaudioFrequencyLookup; static $MPEGaudioChannelModeLookup; static $MPEGaudioModeExtensionLookup; static $MPEGaudioEmphasisLookup; if (empty($MPEGaudioVersionLookup)) { $MPEGaudioVersionLookup = MPEGaudioVersionArray(); $MPEGaudioLayerLookup = MPEGaudioLayerArray(); $MPEGaudioBitrateLookup = MPEGaudioBitrateArray(); $MPEGaudioFrequencyLookup = MPEGaudioFrequencyArray(); $MPEGaudioChannelModeLookup = MPEGaudioChannelModeArray(); $MPEGaudioModeExtensionLookup = MPEGaudioModeExtensionArray(); $MPEGaudioEmphasisLookup = MPEGaudioEmphasisArray(); } if ($offset >= $ThisFileInfo['avdataend']) { $ThisFileInfo['error'] .= "\n".'end of file encounter looking for MPEG synch'; return false; } fseek($fd, $offset, SEEK_SET); $headerstring = fread($fd, 1441); // worse-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame // MP3 audio frame structure: // $aa $aa $aa $aa [$bb $bb] $cc... // where $aa..$aa is the four-byte mpeg-audio header (below) // $bb $bb is the optional 2-byte CRC // and $cc... is the audio data $head4 = substr($headerstring, 0, 4); static $MPEGaudioHeaderDecodeCache = []; if (isset($MPEGaudioHeaderDecodeCache[$head4])) { $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4]; } else { $MPEGheaderRawArray = MPEGaudioHeaderDecode($head4); $MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray; } static $MPEGaudioHeaderValidCache = []; // Not in cache if (!isset($MPEGaudioHeaderValidCache[$head4])) { $MPEGaudioHeaderValidCache[$head4] = MPEGaudioHeaderValid($MPEGheaderRawArray); } if ($MPEGaudioHeaderValidCache[$head4]) { $ThisFileInfo['mpeg']['audio']['raw'] = $MPEGheaderRawArray; } else { $ThisFileInfo['error'] .= "\n".'Invalid MPEG audio header at offset '.$offset; return false; } if (!$FastMPEGheaderScan) { $ThisFileInfo['mpeg']['audio']['version'] = $MPEGaudioVersionLookup[$ThisFileInfo['mpeg']['audio']['raw']['version']]; $ThisFileInfo['mpeg']['audio']['layer'] = $MPEGaudioLayerLookup[$ThisFileInfo['mpeg']['audio']['raw']['layer']]; $ThisFileInfo['mpeg']['audio']['channelmode'] = $MPEGaudioChannelModeLookup[$ThisFileInfo['mpeg']['audio']['raw']['channelmode']]; $ThisFileInfo['mpeg']['audio']['channels'] = (($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') ? 1 : 2); $ThisFileInfo['mpeg']['audio']['sample_rate'] = $MPEGaudioFrequencyLookup[$ThisFileInfo['mpeg']['audio']['version']][$ThisFileInfo['mpeg']['audio']['raw']['sample_rate']]; $ThisFileInfo['mpeg']['audio']['protection'] = !$ThisFileInfo['mpeg']['audio']['raw']['protection']; $ThisFileInfo['mpeg']['audio']['private'] = (bool) $ThisFileInfo['mpeg']['audio']['raw']['private']; $ThisFileInfo['mpeg']['audio']['modeextension'] = $MPEGaudioModeExtensionLookup[$ThisFileInfo['mpeg']['audio']['layer']][$ThisFileInfo['mpeg']['audio']['raw']['modeextension']]; $ThisFileInfo['mpeg']['audio']['copyright'] = (bool) $ThisFileInfo['mpeg']['audio']['raw']['copyright']; $ThisFileInfo['mpeg']['audio']['original'] = (bool) $ThisFileInfo['mpeg']['audio']['raw']['original']; $ThisFileInfo['mpeg']['audio']['emphasis'] = $MPEGaudioEmphasisLookup[$ThisFileInfo['mpeg']['audio']['raw']['emphasis']]; $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; if ($ThisFileInfo['mpeg']['audio']['protection']) { $ThisFileInfo['mpeg']['audio']['crc'] = BigEndian2Int(substr($headerstring, 4, 2)); } } if ($ThisFileInfo['mpeg']['audio']['raw']['bitrate'] == 15) { // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0 $ThisFileInfo['warning'] .= "\n".'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1'; $ThisFileInfo['mpeg']['audio']['raw']['bitrate'] = 0; } $ThisFileInfo['mpeg']['audio']['padding'] = (bool) $ThisFileInfo['mpeg']['audio']['raw']['padding']; $ThisFileInfo['mpeg']['audio']['bitrate'] = $MPEGaudioBitrateLookup[$ThisFileInfo['mpeg']['audio']['version']][$ThisFileInfo['mpeg']['audio']['layer']][$ThisFileInfo['mpeg']['audio']['raw']['bitrate']]; if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') && ($offset == $ThisFileInfo['avdataoffset'])) { // only skip multiple frame check if free-format bitstream found at beginning of file // otherwise is quite possibly simply corrupted data $recursivesearch = false; } // For Layer II there are some combinations of bitrate and mode which are not allowed. if (!$FastMPEGheaderScan && ($ThisFileInfo['mpeg']['audio']['layer'] == 'II')) { $ThisFileInfo['audio']['dataformat'] = 'mp2'; switch ($ThisFileInfo['mpeg']['audio']['channelmode']) { case 'mono': if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') || ($ThisFileInfo['mpeg']['audio']['bitrate'] <= 192)) { // these are ok } else { $ThisFileInfo['error'] .= "\n".$ThisFileInfo['mpeg']['audio']['bitrate'].'kbps not allowed in Layer II, '.$ThisFileInfo['mpeg']['audio']['channelmode'].'.'; return false; } break; case 'stereo': case 'joint stereo': case 'dual channel': if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') || ($ThisFileInfo['mpeg']['audio']['bitrate'] == 64) || ($ThisFileInfo['mpeg']['audio']['bitrate'] >= 96)) { // these are ok } else { $ThisFileInfo['error'] .= "\n".$ThisFileInfo['mpeg']['audio']['bitrate'].'kbps not allowed in Layer II, '.$ThisFileInfo['mpeg']['audio']['channelmode'].'.'; return false; } break; } } if ($ThisFileInfo['audio']['sample_rate'] > 0) { $ThisFileInfo['mpeg']['audio']['framelength'] = MPEGaudioFrameLength($ThisFileInfo['mpeg']['audio']['bitrate'], $ThisFileInfo['mpeg']['audio']['version'], $ThisFileInfo['mpeg']['audio']['layer'], (int) $ThisFileInfo['mpeg']['audio']['padding'], $ThisFileInfo['audio']['sample_rate']); } if ($ThisFileInfo['mpeg']['audio']['bitrate'] != 'free') { $ThisFileInfo['audio']['bitrate'] = 1000 * $ThisFileInfo['mpeg']['audio']['bitrate']; if (isset($ThisFileInfo['mpeg']['audio']['framelength'])) { $nextframetestoffset = $offset + $ThisFileInfo['mpeg']['audio']['framelength']; } else { $ThisFileInfo['error'] .= "\n".'Frame at offset('.$offset.') is has an invalid frame length.'; return false; } } $ExpectedNumberOfAudioBytes = 0; //////////////////////////////////////////////////////////////////////////////////// // Variable-bitrate headers if (substr($headerstring, 4 + 32, 4) == 'VBRI') { // Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36) // specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr'; $ThisFileInfo['mpeg']['audio']['VBR_method'] = 'Fraunhofer'; $ThisFileInfo['audio']['codec'] = 'Fraunhofer'; $SideInfoData = substr($headerstring, 4 + 2, 32); $FraunhoferVBROffset = 36; $ThisFileInfo['mpeg']['audio']['VBR_encoder_version'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 4, 2)); $ThisFileInfo['mpeg']['audio']['VBR_encoder_delay'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 6, 2)); $ThisFileInfo['mpeg']['audio']['VBR_quality'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 8, 2)); $ThisFileInfo['mpeg']['audio']['VBR_bytes'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4)); $ThisFileInfo['mpeg']['audio']['VBR_frames'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4)); $ThisFileInfo['mpeg']['audio']['VBR_seek_offsets'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2)); //$ThisFileInfo['mpeg']['audio']['reserved'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 4)); // hardcoded $00 $01 $00 $02 - purpose unknown $ThisFileInfo['mpeg']['audio']['VBR_seek_offsets_stride'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2)); $ExpectedNumberOfAudioBytes = $ThisFileInfo['mpeg']['audio']['VBR_bytes']; $previousbyteoffset = $offset; for ($i = 0; $i < $ThisFileInfo['mpeg']['audio']['VBR_seek_offsets']; $i++) { $Fraunhofer_OffsetN = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, 2)); $FraunhoferVBROffset += 2; $ThisFileInfo['mpeg']['audio']['VBR_offsets_relative'][$i] = $Fraunhofer_OffsetN; $ThisFileInfo['mpeg']['audio']['VBR_offsets_absolute'][$i] = $Fraunhofer_OffsetN + $previousbyteoffset; $previousbyteoffset += $Fraunhofer_OffsetN; } } else { // Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36) // depending on MPEG layer and number of channels if ($ThisFileInfo['mpeg']['audio']['version'] == '1') { if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') { // MPEG-1 (mono) $VBRidOffset = 4 + 17; // 0x15 $SideInfoData = substr($headerstring, 4 + 2, 17); } else { // MPEG-1 (stereo, joint-stereo, dual-channel) $VBRidOffset = 4 + 32; // 0x24 $SideInfoData = substr($headerstring, 4 + 2, 32); } } else { // 2 or 2.5 if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') { // MPEG-2, MPEG-2.5 (mono) $VBRidOffset = 4 + 9; // 0x0D $SideInfoData = substr($headerstring, 4 + 2, 9); } else { // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel) $VBRidOffset = 4 + 17; // 0x15 $SideInfoData = substr($headerstring, 4 + 2, 17); } } if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) { // 'Xing' is traditional Xing VBR frame // 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.) $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr'; $ThisFileInfo['mpeg']['audio']['VBR_method'] = 'Xing'; $ThisFileInfo['mpeg']['audio']['xing_flags_raw'] = BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4)); $ThisFileInfo['mpeg']['audio']['xing_flags']['frames'] = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000001); $ThisFileInfo['mpeg']['audio']['xing_flags']['bytes'] = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000002); $ThisFileInfo['mpeg']['audio']['xing_flags']['toc'] = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000004); $ThisFileInfo['mpeg']['audio']['xing_flags']['vbr_scale'] = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000008); if ($ThisFileInfo['mpeg']['audio']['xing_flags']['frames']) { $ThisFileInfo['mpeg']['audio']['VBR_frames'] = BigEndian2Int(substr($headerstring, $VBRidOffset + 8, 4)); } if ($ThisFileInfo['mpeg']['audio']['xing_flags']['bytes']) { $ThisFileInfo['mpeg']['audio']['VBR_bytes'] = BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4)); } if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') && !empty($ThisFileInfo['mpeg']['audio']['VBR_frames']) && !empty($ThisFileInfo['mpeg']['audio']['VBR_bytes'])) { $framelengthfloat = $ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames']; if ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') { // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 $ThisFileInfo['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 12; } else { // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 $ThisFileInfo['audio']['bitrate'] = (($framelengthfloat - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 144; } $ThisFileInfo['mpeg']['audio']['framelength'] = floor($framelengthfloat); } if ($ThisFileInfo['mpeg']['audio']['xing_flags']['toc']) { $LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100); for ($i = 0; $i < 100; $i++) { $ThisFileInfo['mpeg']['audio']['toc'][$i] = ord($LAMEtocData{$i}); } } if ($ThisFileInfo['mpeg']['audio']['xing_flags']['vbr_scale']) { $ThisFileInfo['mpeg']['audio']['VBR_scale'] = BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4)); } // http://gabriel.mp3-tech.org/mp3infotag.html if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') { $ThisFileInfo['mpeg']['audio']['LAME']['long_version'] = substr($headerstring, $VBRidOffset + 120, 20); $ThisFileInfo['mpeg']['audio']['LAME']['short_version'] = substr($ThisFileInfo['mpeg']['audio']['LAME']['long_version'], 0, 9); $ThisFileInfo['mpeg']['audio']['LAME']['long_version'] = rtrim($ThisFileInfo['mpeg']['audio']['LAME']['long_version'], "\x55\xAA"); if ($ThisFileInfo['mpeg']['audio']['LAME']['short_version'] >= 'LAME3.90.') { // It the LAME tag was only introduced in LAME v3.90 // http://www.hydrogenaudio.org/?act=ST&f=15&t=9933 // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html // are assuming a 'Xing' identifier offset of 0x24, which is the case for // MPEG-1 non-mono, but not for other combinations $LAMEtagOffsetContant = $VBRidOffset - 0x24; // byte $9B VBR Quality // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications. // Actually overwrites original Xing bytes unset($ThisFileInfo['mpeg']['audio']['VBR_scale']); $ThisFileInfo['mpeg']['audio']['LAME']['vbr_quality'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1)); // bytes $9C-$A4 Encoder short VersionString $ThisFileInfo['mpeg']['audio']['LAME']['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9); $ThisFileInfo['mpeg']['audio']['LAME']['long_version'] = $ThisFileInfo['mpeg']['audio']['LAME']['short_version']; // byte $A5 Info Tag revision + VBR method $LAMEtagRevisionVBRmethod = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1)); $ThisFileInfo['mpeg']['audio']['LAME']['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4; $ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F; $ThisFileInfo['mpeg']['audio']['LAME']['vbr_method'] = LAMEvbrMethodLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method']); // byte $A6 Lowpass filter value $ThisFileInfo['mpeg']['audio']['LAME']['lowpass_frequency'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100; // bytes $A7-$AE Replay Gain // http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude" $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] = BigEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4)); $ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2)); $ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2)); if ($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] == 0) { $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] = false; } if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] != 0) { require_once(GETID3_INCLUDEPATH.'getid3.rgad.php'); $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['name'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0xE000) >> 13; $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['originator'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0x1C00) >> 10; $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['sign_bit'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0x0200) >> 9; $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['gain_adjust'] = $ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0x01FF; $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['name'] = RGADnameLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['name']); $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['originator'] = RGADoriginatorLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['originator']); $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['gain_db'] = RGADadjustmentLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['gain_adjust'], $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['sign_bit']); if ($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] !== false) { $ThisFileInfo['replay_gain']['radio']['peak'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude']; } $ThisFileInfo['replay_gain']['radio']['originator'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['originator']; $ThisFileInfo['replay_gain']['radio']['adjustment'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['gain_db']; } if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] != 0) { require_once(GETID3_INCLUDEPATH.'getid3.rgad.php'); $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['name'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0xE000) >> 13; $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['originator'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0x1C00) >> 10; $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['sign_bit'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0x0200) >> 9; $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['gain_adjust'] = $ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0x01FF; $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['name'] = RGADnameLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['name']); $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['originator'] = RGADoriginatorLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['originator']); $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['gain_db'] = RGADadjustmentLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['gain_adjust'], $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['sign_bit']); if ($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] !== false) { $ThisFileInfo['replay_gain']['audiophile']['peak'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude']; } $ThisFileInfo['replay_gain']['audiophile']['originator'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['originator']; $ThisFileInfo['replay_gain']['audiophile']['adjustment'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['gain_db']; } // byte $AF Encoding flags + ATH Type $EncodingFlagsATHtype = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1)); $ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10); $ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20); $ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40); $ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80); $ThisFileInfo['mpeg']['audio']['LAME']['ath_type'] = $EncodingFlagsATHtype & 0x0F; // byte $B0 if ABR {specified bitrate} else {minimal bitrate} $ABRbitrateMinBitrate = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1)); if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method'] == 2) { // Average BitRate (ABR) $ThisFileInfo['mpeg']['audio']['LAME']['bitrate_abr'] = $ABRbitrateMinBitrate; } elseif ($ABRbitrateMinBitrate > 0) { // Variable BitRate (VBR) - minimum bitrate $ThisFileInfo['mpeg']['audio']['LAME']['bitrate_min'] = $ABRbitrateMinBitrate; } // bytes $B1-$B3 Encoder delays $EncoderDelays = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3)); $ThisFileInfo['mpeg']['audio']['LAME']['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12; $ThisFileInfo['mpeg']['audio']['LAME']['end_padding'] = $EncoderDelays & 0x000FFF; // byte $B4 Misc $MiscByte = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1)); $ThisFileInfo['mpeg']['audio']['LAME']['raw']['noise_shaping'] = ($MiscByte & 0x03); $ThisFileInfo['mpeg']['audio']['LAME']['raw']['stereo_mode'] = ($MiscByte & 0x1C) >> 2; $ThisFileInfo['mpeg']['audio']['LAME']['raw']['not_optimal_quality'] = ($MiscByte & 0x20) >> 5; $ThisFileInfo['mpeg']['audio']['LAME']['raw']['source_sample_freq'] = ($MiscByte & 0xC0) >> 6; $ThisFileInfo['mpeg']['audio']['LAME']['noise_shaping'] = $ThisFileInfo['mpeg']['audio']['LAME']['raw']['noise_shaping']; $ThisFileInfo['mpeg']['audio']['LAME']['stereo_mode'] = LAMEmiscStereoModeLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['stereo_mode']); $ThisFileInfo['mpeg']['audio']['LAME']['not_optimal_quality'] = (bool) $ThisFileInfo['mpeg']['audio']['LAME']['raw']['not_optimal_quality']; $ThisFileInfo['mpeg']['audio']['LAME']['source_sample_freq'] = LAMEmiscSourceSampleFrequencyLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['source_sample_freq']); // byte $B5 MP3 Gain $ThisFileInfo['mpeg']['audio']['LAME']['raw']['mp3_gain'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true); $ThisFileInfo['mpeg']['audio']['LAME']['mp3_gain_db'] = 1.5 * $ThisFileInfo['mpeg']['audio']['LAME']['raw']['mp3_gain']; $ThisFileInfo['mpeg']['audio']['LAME']['mp3_gain_factor'] = pow(2, ($ThisFileInfo['mpeg']['audio']['LAME']['mp3_gain_db'] / 6)); // bytes $B6-$B7 Preset and surround info $PresetSurroundBytes = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2)); // Reserved = ($PresetSurroundBytes & 0xC000); $ThisFileInfo['mpeg']['audio']['LAME']['raw']['surround_info'] = ($PresetSurroundBytes & 0x3800); $ThisFileInfo['mpeg']['audio']['LAME']['surround_info'] = LAMEsurroundInfoLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['surround_info']); $ThisFileInfo['mpeg']['audio']['LAME']['preset_used_id'] = ($PresetSurroundBytes & 0x07FF); // bytes $B8-$BB MusicLength $ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4)); $ExpectedNumberOfAudioBytes = (($ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'] > 0) ? $ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'] : $ThisFileInfo['mpeg']['audio']['VBR_bytes']); // bytes $BC-$BD MusicCRC $ThisFileInfo['mpeg']['audio']['LAME']['music_crc'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2)); // bytes $BE-$BF CRC-16 of Info Tag $ThisFileInfo['mpeg']['audio']['LAME']['lame_tag_crc'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2)); // LAME CBR if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method'] == 1) { $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr'; if (empty($ThisFileInfo['mpeg']['audio']['bitrate']) || ($ThisFileInfo['mpeg']['audio']['LAME']['bitrate_min'] != 255)) { $ThisFileInfo['mpeg']['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['LAME']['bitrate_min']; } } } } } else { // not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header) $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr'; if ($recursivesearch) { $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr'; if (RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, true)) { $recursivesearch = false; $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr'; } if ($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') { $ThisFileInfo['warning'] .= "\n".'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.'; } } } } if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']))) { if (($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) == 1) { $ThisFileInfo['warning'] .= "\n".'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'; } elseif ($ExpectedNumberOfAudioBytes > ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) { $ThisFileInfo['warning'] .= "\n".'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])).' bytes)'; } else { $ThisFileInfo['warning'] .= "\n".'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' ('.(($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; } } if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') && empty($ThisFileInfo['audio']['bitrate'])) { if (($offset == $ThisFileInfo['avdataoffset']) && empty($ThisFileInfo['mpeg']['audio']['VBR_frames'])) { $framebytelength = FreeFormatFrameLength($fd, $offset, $ThisFileInfo, true); if ($framebytelength > 0) { $ThisFileInfo['mpeg']['audio']['framelength'] = $framebytelength; if ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') { // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 $ThisFileInfo['audio']['bitrate'] = ((($framebytelength / 4) - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 12; } else { // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 $ThisFileInfo['audio']['bitrate'] = (($framebytelength - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 144; } } else { $ThisFileInfo['error'] .= "\n".'Error calculating frame length of free-format MP3 without Xing/LAME header'; } } } if (($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') && isset($ThisFileInfo['mpeg']['audio']['VBR_frames']) && ($ThisFileInfo['mpeg']['audio']['VBR_frames'] > 1)) { $ThisFileInfo['mpeg']['audio']['VBR_frames']--; // don't count the Xing / VBRI frame if (($ThisFileInfo['mpeg']['audio']['version'] == '1') && ($ThisFileInfo['mpeg']['audio']['layer'] == 'I')) { $ThisFileInfo['mpeg']['audio']['VBR_bitrate'] = ((($ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 384)) / 1000; } elseif ((($ThisFileInfo['mpeg']['audio']['version'] == '2') || ($ThisFileInfo['mpeg']['audio']['version'] == '2.5')) && ($ThisFileInfo['mpeg']['audio']['layer'] == 'III')) { $ThisFileInfo['mpeg']['audio']['VBR_bitrate'] = ((($ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 576)) / 1000; } else { $ThisFileInfo['mpeg']['audio']['VBR_bitrate'] = ((($ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 1152)) / 1000; } if ($ThisFileInfo['mpeg']['audio']['VBR_bitrate'] > 0) { $ThisFileInfo['audio']['bitrate'] = 1000 * $ThisFileInfo['mpeg']['audio']['VBR_bitrate']; $ThisFileInfo['mpeg']['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['VBR_bitrate']; // to avoid confusion } } // End variable-bitrate headers //////////////////////////////////////////////////////////////////////////////////// if ($recursivesearch) { if (!RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, $ScanAsCBR)) { return false; } } //if (false) { // // experimental side info parsing section - not returning anything useful yet // // $SideInfoBitstream = BigEndian2Bin($SideInfoData); // $SideInfoOffset = 0; // // if ($ThisFileInfo['mpeg']['audio']['version'] == '1') { // if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') { // // MPEG-1 (mono) // $ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9); // $SideInfoOffset += 9; // $SideInfoOffset += 5; // } else { // // MPEG-1 (stereo, joint-stereo, dual-channel) // $ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9); // $SideInfoOffset += 9; // $SideInfoOffset += 3; // } // } else { // 2 or 2.5 // if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') { // // MPEG-2, MPEG-2.5 (mono) // $ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8); // $SideInfoOffset += 8; // $SideInfoOffset += 1; // } else { // // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel) // $ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8); // $SideInfoOffset += 8; // $SideInfoOffset += 2; // } // } // // if ($ThisFileInfo['mpeg']['audio']['version'] == '1') { // for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) { // for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) { // $ThisFileInfo['mpeg']['audio']['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1); // $SideInfoOffset += 2; // } // } // } // for ($granule = 0; $granule < (($ThisFileInfo['mpeg']['audio']['version'] == '1') ? 2 : 1); $granule++) { // for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) { // $ThisFileInfo['mpeg']['audio']['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12); // $SideInfoOffset += 12; // $ThisFileInfo['mpeg']['audio']['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); // $SideInfoOffset += 9; // $ThisFileInfo['mpeg']['audio']['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8); // $SideInfoOffset += 8; // if ($ThisFileInfo['mpeg']['audio']['version'] == '1') { // $ThisFileInfo['mpeg']['audio']['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4); // $SideInfoOffset += 4; // } else { // $ThisFileInfo['mpeg']['audio']['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); // $SideInfoOffset += 9; // } // $ThisFileInfo['mpeg']['audio']['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); // $SideInfoOffset += 1; // // if ($ThisFileInfo['mpeg']['audio']['window_switching_flag'][$granule][$channel] == '1') { // // $ThisFileInfo['mpeg']['audio']['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2); // $SideInfoOffset += 2; // $ThisFileInfo['mpeg']['audio']['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); // $SideInfoOffset += 1; // // for ($region = 0; $region < 2; $region++) { // $ThisFileInfo['mpeg']['audio']['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5); // $SideInfoOffset += 5; // } // $ThisFileInfo['mpeg']['audio']['table_select'][$granule][$channel][2] = 0; // // for ($window = 0; $window < 3; $window++) { // $ThisFileInfo['mpeg']['audio']['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3); // $SideInfoOffset += 3; // } // // } else { // // for ($region = 0; $region < 3; $region++) { // $ThisFileInfo['mpeg']['audio']['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5); // $SideInfoOffset += 5; // } // // $ThisFileInfo['mpeg']['audio']['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4); // $SideInfoOffset += 4; // $ThisFileInfo['mpeg']['audio']['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3); // $SideInfoOffset += 3; // $ThisFileInfo['mpeg']['audio']['block_type'][$granule][$channel] = 0; // } // // if ($ThisFileInfo['mpeg']['audio']['version'] == '1') { // $ThisFileInfo['mpeg']['audio']['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); // $SideInfoOffset += 1; // } // $ThisFileInfo['mpeg']['audio']['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); // $SideInfoOffset += 1; // $ThisFileInfo['mpeg']['audio']['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); // $SideInfoOffset += 1; // } // } //} return true; } function RecursiveFrameScanning(&$fd, &$ThisFileInfo, &$offset, &$nextframetestoffset, $ScanAsCBR) { for ($i = 0; $i < MPEG_VALID_CHECK_FRAMES; $i++) { // check next MPEG_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch if (($nextframetestoffset + 4) >= $ThisFileInfo['avdataend']) { // end of file return true; } $nextframetestarray = ['error'=>'', 'warning'=>'', 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']]; if (decodeMPEGaudioHeader($fd, $nextframetestoffset, $nextframetestarray, false)) { if ($ScanAsCBR) { // force CBR mode, used for trying to pick out invalid audio streams with // valid(?) VBR headers, or VBR streams with no VBR header if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($ThisFileInfo['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $ThisFileInfo['mpeg']['audio']['bitrate'])) { return false; } } // next frame is OK, get ready to check the one after that if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) { $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength']; } else { $ThisFileInfo['error'] .= "\n".'Frame at offset ('.$offset.') is has an invalid frame length.'; return false; } } else { // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence $ThisFileInfo['error'] .= "\n".'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.'; return false; } } return true; } function FreeFormatFrameLength($fd, $offset, &$ThisFileInfo, $deepscan = false) { fseek($fd, $offset, SEEK_SET); $MPEGaudioData = fread($fd, 32768); $SyncPattern1 = substr($MPEGaudioData, 0, 4); // may be different pattern due to padding $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}; } $framelength = false; $framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4); $framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4); if ($framelength1 > 4) { $framelength = $framelength1; } if (($framelength2 > 4) && ($framelength2 < $framelength1)) { $framelength = $framelength2; } if (!$framelength) { // LAME 3.88 has a different value for modeextension on the first frame vs the rest $framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4); $framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4); if ($framelength1 > 4) { $framelength = $framelength1; } if (($framelength2 > 4) && ($framelength2 < $framelength1)) { $framelength = $framelength2; } if (!$framelength) { $ThisFileInfo['error'] .= "\n".'Cannot find next free-format synch pattern ('.PrintHexBytes($SyncPattern1).' or '.PrintHexBytes($SyncPattern2).') after offset '.$offset; return false; } else { $ThisFileInfo['warning'] .= "\n".'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)'; $ThisFileInfo['audio']['codec'] = 'LAME'; $ThisFileInfo['audio']['encoder'] = 'LAME3.88'; $SyncPattern1 = substr($SyncPattern1, 0, 3); $SyncPattern2 = substr($SyncPattern2, 0, 3); } } if ($deepscan) { $ActualFrameLengthValues = []; $nextoffset = $offset + $framelength; while ($nextoffset < ($ThisFileInfo['avdataend'] - 6)) { fseek($fd, $nextoffset - 1, SEEK_SET); $NextSyncPattern = fread($fd, 6); if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) { // good - found where expected $ActualFrameLengthValues[] = $framelength; } elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) { // ok - found one byte earlier than expected (last frame wasn't padded, first frame was) $ActualFrameLengthValues[] = ($framelength - 1); $nextoffset--; } elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) { // ok - found one byte later than expected (last frame was padded, first frame wasn't) $ActualFrameLengthValues[] = ($framelength + 1); $nextoffset++; } else { $ThisFileInfo['error'] .= "\n".'Did not find expected free-format sync pattern at offset '.$nextoffset; return false; } $nextoffset += $framelength; } if (count($ActualFrameLengthValues) > 0) { $framelength = round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues)); } } return $framelength; } function getOnlyMPEGaudioInfo($fd, &$ThisFileInfo, $avdataoffset, $BitrateHistogram = false) { // looks for synch, decodes MPEG audio header fseek($fd, $avdataoffset, SEEK_SET); $header = ''; $SynchSeekOffset = 0; if (!defined('CONST_FF')) { define('CONST_FF', chr(0xFF)); define('CONST_E0', chr(0xE0)); } static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; if (empty($MPEGaudioVersionLookup)) { $MPEGaudioVersionLookup = MPEGaudioVersionArray(); $MPEGaudioLayerLookup = MPEGaudioLayerArray(); $MPEGaudioBitrateLookup = MPEGaudioBitrateArray(); } $header_len = strlen($header) - round(32768 / 2); while (true) { if (($SynchSeekOffset > $header_len) && (($avdataoffset + $SynchSeekOffset) < $ThisFileInfo['avdataend']) && !feof($fd)) { if ($SynchSeekOffset > 131072) { // if a synch's not found within the first 128k bytes, then give up $ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch within the first 131072 bytes'; if (isset($ThisFileInfo['audio']['bitrate'])) { unset($ThisFileInfo['audio']['bitrate']); } if (isset($ThisFileInfo['mpeg']['audio'])) { unset($ThisFileInfo['mpeg']['audio']); } if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || (count($ThisFileInfo['mpeg']) == 0))) { unset($ThisFileInfo['mpeg']); } return false; } elseif ($header .= fread($fd, 32768)) { // great $header_len = strlen($header) - round(32768 / 2); } else { $ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch before end of file'; if (isset($ThisFileInfo['audio']['bitrate'])) { unset($ThisFileInfo['audio']['bitrate']); } if (isset($ThisFileInfo['mpeg']['audio'])) { unset($ThisFileInfo['mpeg']['audio']); } if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || (count($ThisFileInfo['mpeg']) == 0))) { unset($ThisFileInfo['mpeg']); } return false; } } if (($SynchSeekOffset + 1) >= strlen($header)) { $ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch before end of file'; return false; } if (($header{$SynchSeekOffset} == CONST_FF) && ($header{($SynchSeekOffset + 1)} > CONST_E0)) { // synch detected if (!isset($FirstFrameThisfileInfo) && !isset($ThisFileInfo['mpeg']['audio'])) { $FirstFrameThisfileInfo = $ThisFileInfo; $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset; if (!decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $FirstFrameThisfileInfo, false)) { // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below unset($FirstFrameThisfileInfo); } } $dummy = $ThisFileInfo; // only overwrite real data if valid header found if (decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $dummy, true)) { $ThisFileInfo = $dummy; $ThisFileInfo['avdataoffset'] = $avdataoffset + $SynchSeekOffset; switch ($ThisFileInfo['fileformat']) { case '': case 'id3': case 'ape': case 'mp3': $ThisFileInfo['fileformat'] = 'mp3'; $ThisFileInfo['audio']['dataformat'] = 'mp3'; } if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) { if (!CloseMatch($ThisFileInfo['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. $ThisFileInfo = $FirstFrameThisfileInfo; $ThisFileInfo['avdataoffset'] = $FirstFrameAVDataOffset; $ThisFileInfo['fileformat'] = 'mp3'; $ThisFileInfo['audio']['dataformat'] = 'mp3'; $dummy = $ThisFileInfo; unset($dummy['mpeg']['audio']); $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength']; $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset; if (decodeMPEGaudioHeader($fd, $GarbageOffsetEnd, $dummy, true, true)) { $ThisFileInfo = $dummy; $ThisFileInfo['avdataoffset'] = $GarbageOffsetEnd; $ThisFileInfo['warning'] .= "\n".'apparently-valid VBR header not used because could not find '.MPEG_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd; } else { $ThisFileInfo['warning'] .= "\n".'using data from VBR header even though could not find '.MPEG_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')'; } } } if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode']) && ($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($ThisFileInfo['mpeg']['audio']['VBR_method'])) { // VBR file with no VBR header $BitrateHistogram = true; } if ($BitrateHistogram) { $ThisFileInfo['mpeg']['audio']['stereo_distribution'] = ['stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0]; $ThisFileInfo['mpeg']['audio']['version_distribution'] = ['1'=>0, '2'=>0, '2.5'=>0]; if ($ThisFileInfo['mpeg']['audio']['version'] == '1') { if ($ThisFileInfo['mpeg']['audio']['layer'] == 'III') { $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = ['free'=>0, 32=>0, 40=>0, 48=>0, 56=>0, 64=>0, 80=>0, 96=>0, 112=>0, 128=>0, 160=>0, 192=>0, 224=>0, 256=>0, 320=>0]; } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 'II') { $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = ['free'=>0, 32=>0, 48=>0, 56=>0, 64=>0, 80=>0, 96=>0, 112=>0, 128=>0, 160=>0, 192=>0, 224=>0, 256=>0, 320=>0, 384=>0]; } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') { $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = ['free'=>0, 32=>0, 64=>0, 96=>0, 128=>0, 160=>0, 192=>0, 224=>0, 256=>0, 288=>0, 320=>0, 352=>0, 384=>0, 416=>0, 448=>0]; } } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') { $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = ['free'=>0, 32=>0, 48=>0, 56=>0, 64=>0, 80=>0, 96=>0, 112=>0, 128=>0, 144=>0, 160=>0, 176=>0, 192=>0, 224=>0, 256=>0]; } else { $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = ['free'=>0, 8=>0, 16=>0, 24=>0, 32=>0, 40=>0, 48=>0, 56=>0, 64=>0, 80=>0, 96=>0, 112=>0, 128=>0, 144=>0, 160=>0]; } $dummy = ['error'=>$ThisFileInfo['error'], 'warning'=>$ThisFileInfo['warning'], 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']]; $synchstartoffset = $ThisFileInfo['avdataoffset']; $FastMode = false; while (decodeMPEGaudioHeader($fd, $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']]; $ThisFileInfo['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]++; $ThisFileInfo['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]++; $ThisFileInfo['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]++; if (empty($dummy['mpeg']['audio']['framelength'])) { $ThisFileInfo['warning'] .= "\n".'Invalid/missing framelength in histogram analysis - aborting'; $synchstartoffset += 4; // return false; } $synchstartoffset += $dummy['mpeg']['audio']['framelength']; } $bittotal = 0; $framecounter = 0; foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) { $framecounter += $bitratecount; if ($bitratevalue != 'free') { $bittotal += ($bitratevalue * $bitratecount); } } if ($framecounter == 0) { $ThisFileInfo['error'] .= "\n".'Corrupt MP3 file: framecounter == zero'; return false; } $ThisFileInfo['mpeg']['audio']['frame_count'] = $framecounter; $ThisFileInfo['mpeg']['audio']['bitrate'] = 1000 * ($bittotal / $framecounter); $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently $distinct_bitrates = 0; foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) { if ($bitrate_count > 0) { $distinct_bitrates++; } } if ($distinct_bitrates > 1) { $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr'; } else { $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr'; } $ThisFileInfo['audio']['bitrate_mode'] = $ThisFileInfo['mpeg']['audio']['bitrate_mode']; } break; // exit while() } } $SynchSeekOffset++; if (($avdataoffset + $SynchSeekOffset) >= $ThisFileInfo['avdataend']) { // end of file/data if (empty($ThisFileInfo['mpeg']['audio'])) { $ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch before end of file'; if (isset($ThisFileInfo['audio']['bitrate'])) { unset($ThisFileInfo['audio']['bitrate']); } if (isset($ThisFileInfo['mpeg']['audio'])) { unset($ThisFileInfo['mpeg']['audio']); } if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || empty($ThisFileInfo['mpeg']))) { unset($ThisFileInfo['mpeg']); } return false; } break; } } $ThisFileInfo['audio']['bits_per_sample'] = 16; $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; $ThisFileInfo['audio']['channelmode'] = $ThisFileInfo['mpeg']['audio']['channelmode']; $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; return true; } function MPEGaudioVersionArray() { static $MPEGaudioVersion = ['2.5', false, '2', '1']; return $MPEGaudioVersion; } function MPEGaudioLayerArray() { static $MPEGaudioLayer = [false, 'III', 'II', 'I']; return $MPEGaudioLayer; } function MPEGaudioBitrateArray() { static $MPEGaudioBitrate; if (empty($MPEGaudioBitrate)) { $MPEGaudioBitrate['1']['I'] = ['free', 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448]; $MPEGaudioBitrate['1']['II'] = ['free', 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384]; $MPEGaudioBitrate['1']['III'] = ['free', 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]; $MPEGaudioBitrate['2']['I'] = ['free', 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256]; $MPEGaudioBitrate['2']['II'] = ['free', 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]; $MPEGaudioBitrate['2']['III'] = $MPEGaudioBitrate['2']['II']; $MPEGaudioBitrate['2.5']['I'] = $MPEGaudioBitrate['2']['I']; $MPEGaudioBitrate['2.5']['II'] = $MPEGaudioBitrate['2']['II']; $MPEGaudioBitrate['2.5']['III'] = $MPEGaudioBitrate['2']['III']; } return $MPEGaudioBitrate; } function MPEGaudioFrequencyArray() { static $MPEGaudioFrequency; if (empty($MPEGaudioFrequency)) { $MPEGaudioFrequency['1'] = [44100, 48000, 32000]; $MPEGaudioFrequency['2'] = [22050, 24000, 16000]; $MPEGaudioFrequency['2.5'] = [11025, 12000, 8000]; } return $MPEGaudioFrequency; } function MPEGaudioChannelModeArray() { static $MPEGaudioChannelMode = ['stereo', 'joint stereo', 'dual channel', 'mono']; return $MPEGaudioChannelMode; } function MPEGaudioModeExtensionArray() { static $MPEGaudioModeExtension; if (empty($MPEGaudioModeExtension)) { $MPEGaudioModeExtension['I'] = ['4-31', '8-31', '12-31', '16-31']; $MPEGaudioModeExtension['II'] = ['4-31', '8-31', '12-31', '16-31']; $MPEGaudioModeExtension['III'] = ['', 'IS', 'MS', 'IS+MS']; } return $MPEGaudioModeExtension; } function MPEGaudioEmphasisArray() { static $MPEGaudioEmphasis = ['none', '50/15ms', false, 'CCIT J.17']; return $MPEGaudioEmphasis; } function MPEGaudioHeaderBytesValid($head4) { return MPEGaudioHeaderValid(MPEGaudioHeaderDecode($head4)); } function MPEGaudioHeaderValid($rawarray, $echoerrors = false) { if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) { return false; } static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; static $MPEGaudioFrequencyLookup; static $MPEGaudioChannelModeLookup; static $MPEGaudioModeExtensionLookup; static $MPEGaudioEmphasisLookup; if (empty($MPEGaudioVersionLookup)) { $MPEGaudioVersionLookup = MPEGaudioVersionArray(); $MPEGaudioLayerLookup = MPEGaudioLayerArray(); $MPEGaudioBitrateLookup = MPEGaudioBitrateArray(); $MPEGaudioFrequencyLookup = MPEGaudioFrequencyArray(); $MPEGaudioChannelModeLookup = MPEGaudioChannelModeArray(); $MPEGaudioModeExtensionLookup = MPEGaudioModeExtensionArray(); $MPEGaudioEmphasisLookup = MPEGaudioEmphasisArray(); } if (isset($MPEGaudioVersionLookup[$rawarray['version']])) { $decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']]; } else { if ($echoerrors) { echo "\n".'invalid Version ('.$rawarray['version'].')'; } return false; } if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) { $decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']]; } else { if ($echoerrors) { echo "\n".'invalid Layer ('.$rawarray['layer'].')'; } return false; } if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) { if ($echoerrors) { echo "\n".'invalid Bitrate ('.$rawarray['bitrate'].')'; } if ($rawarray['bitrate'] == 15) { // known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0 // let it go through here otherwise file will not be identified } else { return false; } } if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) { if ($echoerrors) { echo "\n".'invalid Frequency ('.$rawarray['sample_rate'].')'; } return false; } if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) { if ($echoerrors) { echo "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')'; } return false; } if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) { if ($echoerrors) { echo "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')'; } return false; } if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) { if ($echoerrors) { echo "\n".'invalid Emphasis ('.$rawarray['emphasis'].')'; } return false; } // These are just either set or not set, you can't mess that up :) // $rawarray['protection']; // $rawarray['padding']; // $rawarray['private']; // $rawarray['copyright']; // $rawarray['original']; return true; } function MPEGaudioHeaderDecode($Header4Bytes) { // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM // A - Frame sync (all bits set) // B - MPEG Audio version ID // C - Layer description // D - Protection bit // E - Bitrate index // F - Sampling rate frequency index // G - Padding bit // H - Private bit // I - Channel Mode // J - Mode extension (Only if Joint stereo) // K - Copyright // L - Original // M - Emphasis if (strlen($Header4Bytes) != 4) { return false; } $MPEGrawHeader['synch'] = (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 return $MPEGrawHeader; } function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { static $AudioFrameLengthCache = []; if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) { $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false; if ($bitrate != 'free') { if ($version == '1') { if ($layer == 'I') { // For Layer I slot is 32 bits long $FrameLengthCoefficient = 48; $SlotLength = 4; } else { // Layer II / III // for Layer II and Layer III slot is 8 bits long. $FrameLengthCoefficient = 144; $SlotLength = 1; } } else { // MPEG-2 / MPEG-2.5 if ($layer == 'I') { // For Layer I slot is 32 bits long $FrameLengthCoefficient = 24; $SlotLength = 4; } elseif ($layer == 'II') { // for Layer II and Layer III slot is 8 bits long. $FrameLengthCoefficient = 144; $SlotLength = 1; } else { // III // for Layer II and Layer III slot is 8 bits long. $FrameLengthCoefficient = 72; $SlotLength = 1; } } // FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding // http://66.96.216.160/cgi-bin/YaBB.pl?board=c&action=display&num=1018474068 // -> [Finding the next frame synch] on www.r3mix.net forums if the above link goes dead if ($samplerate > 0) { $NewFramelength = ($FrameLengthCoefficient * $bitrate * 1000) / $samplerate; $NewFramelength = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer II/III, 4 bytes for Layer I) if ($padding) { $NewFramelength += $SlotLength; } $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength; } } } return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate]; } function LAMEvbrMethodLookup($VBRmethodID) { static $LAMEvbrMethodLookup = []; if (empty($LAMEvbrMethodLookup)) { $LAMEvbrMethodLookup[0x00] = 'unknown'; $LAMEvbrMethodLookup[0x01] = 'cbr'; $LAMEvbrMethodLookup[0x02] = 'abr'; $LAMEvbrMethodLookup[0x03] = 'vbr-old / vbr-rh'; $LAMEvbrMethodLookup[0x04] = 'vbr-mtrh'; $LAMEvbrMethodLookup[0x05] = 'vbr-new / vbr-mt'; } return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : ''); } function LAMEmiscStereoModeLookup($StereoModeID) { static $LAMEmiscStereoModeLookup = []; if (empty($LAMEmiscStereoModeLookup)) { $LAMEmiscStereoModeLookup[0] = 'mono'; $LAMEmiscStereoModeLookup[1] = 'stereo'; $LAMEmiscStereoModeLookup[2] = 'dual'; $LAMEmiscStereoModeLookup[3] = 'joint'; $LAMEmiscStereoModeLookup[4] = 'forced'; $LAMEmiscStereoModeLookup[5] = 'auto'; $LAMEmiscStereoModeLookup[6] = 'intensity'; $LAMEmiscStereoModeLookup[7] = 'other'; } return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : ''); } function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { static $LAMEmiscSourceSampleFrequencyLookup = []; if (empty($LAMEmiscSourceSampleFrequencyLookup)) { $LAMEmiscSourceSampleFrequencyLookup[0] = '<= 32 kHz'; $LAMEmiscSourceSampleFrequencyLookup[1] = '44.1 kHz'; $LAMEmiscSourceSampleFrequencyLookup[2] = '48 kHz'; $LAMEmiscSourceSampleFrequencyLookup[3] = '> 48kHz'; } return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : ''); } function LAMEsurroundInfoLookup($SurroundInfoID) { static $LAMEsurroundInfoLookup = []; if (empty($LAMEsurroundInfoLookup)) { $LAMEsurroundInfoLookup[0] = 'no surround info'; $LAMEsurroundInfoLookup[1] = 'DPL encoding'; $LAMEsurroundInfoLookup[2] = 'DPL2 encoding'; $LAMEsurroundInfoLookup[3] = 'Ambisonic encoding'; } return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved'); } for ($i = 0x00; $i <= 0xFF; $i++) { $head4 = "\xFF\xFE".chr($i)."\x00"; $isvalid = MPEGaudioHeaderBytesValid($head4); echo '
'.str_pad(strtoupper(dechex($i)), 2, '0', STR_PAD_LEFT).' = '.htmlentities(chr($i)).' = '.($isvalid ? 'valid' : 'INVALID').'
'; }