3 if (!function_exists('PrintHexBytes')) {
4 function PrintHexBytes($string) {
6 for ($i = 0; $i < strlen($string); $i++) {
7 $returnstring .= str_pad(dechex(ord(substr($string, $i, 1))), 2, '0', STR_PAD_LEFT).' ';
13 if (!function_exists('PrintTextBytes')) {
14 function PrintTextBytes($string) {
16 for ($i = 0; $i < strlen($string); $i++) {
17 if (ord(substr($string, $i, 1)) <= 31) {
20 $returnstring .= ' '.substr($string, $i, 1).' ';
27 if (!function_exists('table_var_dump')) {
28 function table_var_dump($variable) {
30 switch (gettype($variable)) {
32 $returnstring .= '<table border="1" cellspacing="0" cellpadding="2">';
33 foreach ($variable as $key => $value) {
34 $returnstring .= '<tr><td valign="top"><b>'.str_replace(chr(0), ' ', $key).'</b></td>';
35 $returnstring .= '<td valign="top">'.gettype($value);
36 if (is_array($value)) {
37 $returnstring .= ' ('.count($value).')';
38 } elseif (is_string($value)) {
39 $returnstring .= ' ('.strlen($value).')';
41 if (($key == 'data') && isset($variable['image_mime']) && isset($variable['dataoffset'])) {
42 require_once(GETID3_INCLUDEPATH.'getid3.getimagesize.php');
44 if ($imagechunkcheck = GetDataImageSize($value, $imageinfo)) {
45 $DumpedImageSRC = (!empty($_REQUEST['filename']) ? $_REQUEST['filename'] : '.getid3').'.'.$variable['dataoffset'].'.'.ImageTypesLookup($imagechunkcheck[2]);
46 if ($tempimagefile = fopen($DumpedImageSRC, 'wb')) {
47 fwrite($tempimagefile, $value);
48 fclose($tempimagefile);
50 $returnstring .= '</td><td><img src="'.$DumpedImageSRC.'" width="'.$imagechunkcheck[0].'" height="'.$imagechunkcheck[1].'"></td></tr>';
52 $returnstring .= '</td><td><i>invalid image data</i></td></tr>';
55 $returnstring .= '</td><td>'.table_var_dump($value).'</td></tr>';
58 $returnstring .= '</table>';
62 $returnstring .= ($variable ? 'TRUE' : 'FALSE');
68 $returnstring .= $variable;
73 $returnstring .= string_var_dump($variable);
77 $variable = str_replace(chr(0), ' ', $variable);
78 $varlen = strlen($variable);
79 for ($i = 0; $i < $varlen; $i++) {
80 if (preg_match('#['.chr(0x0A).chr(0x0D).' -;0-9A-Za-z]#', $variable{$i})) {
81 $returnstring .= $variable{$i};
83 $returnstring .= '&#'.str_pad(ord($variable{$i}), 3, '0', STR_PAD_LEFT).';';
86 $returnstring = nl2br($returnstring);
90 require_once(GETID3_INCLUDEPATH.'getid3.getimagesize.php');
92 if (($imagechunkcheck = GetDataImageSize(substr($variable, 0, 32768), $imageinfo)) && ($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
93 $returnstring .= '<table border="1" cellspacing="0" cellpadding="2">';
94 $returnstring .= '<tr><td><b>type</b></td><td>'.ImageTypesLookup($imagechunkcheck[2]).'</td></tr>';
95 $returnstring .= '<tr><td><b>width</b></td><td>'.number_format($imagechunkcheck[0]).' px</td></tr>';
96 $returnstring .= '<tr><td><b>height</b></td><td>'.number_format($imagechunkcheck[1]).' px</td></tr>';
97 $returnstring .= '<tr><td><b>size</b></td><td>'.number_format(strlen($variable)).' bytes</td></tr></table>';
99 $returnstring .= nl2br(htmlspecialchars(str_replace(chr(0), ' ', $variable)));
103 return $returnstring;
107 if (!function_exists('string_var_dump')) {
108 function string_var_dump($variable) {
109 if (version_compare(PHP_VERSION, '4.3.0', '>=')) {
110 return print_r($variable, true);
114 $dumpedvariable = ob_get_contents();
116 return $dumpedvariable;
120 if (!function_exists('fileextension')) {
121 function fileextension($filename, $numextensions=1) {
122 if (strstr($filename, '.')) {
123 $reversedfilename = strrev($filename);
125 for ($i = 0; $i < $numextensions; $i++) {
126 $offset = strpos($reversedfilename, '.', $offset + 1);
127 if ($offset === false) {
131 return strrev(substr($reversedfilename, 0, $offset));
137 if (!function_exists('RemoveAccents')) {
138 function RemoveAccents($string) {
139 // return strtr($string, '
\8a\8c\8e\9a\9c\9e\9f¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ', 'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyy');
140 // Revised version by marksteward@hotmail.com
141 return strtr(strtr($string, '
\8a\8e\9a\9e\9fÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'), array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', '
\8c' => 'OE', '
\9c' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u'));
145 if (!function_exists('MoreNaturalSort')) {
146 function MoreNaturalSort($ar1, $ar2) {
150 $len1 = strlen($ar1);
151 $len2 = strlen($ar2);
152 $shortest = min($len1, $len2);
153 if (substr($ar1, 0, $shortest) === substr($ar2, 0, $shortest)) {
154 // the shorter argument is the beginning of the longer one, like "str" and "string"
157 } elseif ($len1 > $len2) {
162 $ar1 = RemoveAccents(strtolower(trim($ar1)));
163 $ar2 = RemoveAccents(strtolower(trim($ar2)));
164 $translatearray = array('\''=>'', '"'=>'', '_'=>' ', '('=>'', ')'=>'', '-'=>' ', ' '=>' ', '.'=>'', ','=>'');
165 foreach ($translatearray as $key => $val) {
166 $ar1 = str_replace($key, $val, $ar1);
167 $ar2 = str_replace($key, $val, $ar2);
172 } elseif ($ar1 > $ar2) {
179 if (!function_exists('trunc')) {
180 function trunc($floatnumber) {
181 // truncates a floating-point number at the decimal point
182 // returns int (if possible, otherwise float)
183 if ($floatnumber >= 1) {
184 $truncatednumber = floor($floatnumber);
185 } elseif ($floatnumber <= -1) {
186 $truncatednumber = ceil($floatnumber);
188 $truncatednumber = 0;
190 if ($truncatednumber <= pow(2, 30)) {
191 $truncatednumber = (int) $truncatednumber;
193 return $truncatednumber;
197 if (!function_exists('CastAsInt')) {
198 function CastAsInt($floatnum) {
199 // convert to float if not already
200 $floatnum = (float) $floatnum;
202 // convert a float to type int, only if possible
203 if (trunc($floatnum) == $floatnum) {
204 // it's not floating point
205 if ($floatnum <= pow(2, 30)) {
206 // it's within int range
207 $floatnum = (int) $floatnum;
214 if (!function_exists('getmicrotime')) {
215 function getmicrotime() {
216 list($usec, $sec) = explode(' ', microtime());
217 return ((float) $usec + (float) $sec);
221 if (!function_exists('DecimalBinary2Float')) {
222 function DecimalBinary2Float($binarynumerator) {
223 $numerator = Bin2Dec($binarynumerator);
224 $denominator = Bin2Dec(str_repeat('1', strlen($binarynumerator)));
225 return ($numerator / $denominator);
229 if (!function_exists('NormalizeBinaryPoint')) {
230 function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) {
231 // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
232 if (strpos($binarypointnumber, '.') === false) {
233 $binarypointnumber = '0.'.$binarypointnumber;
234 } elseif ($binarypointnumber{0} == '.') {
235 $binarypointnumber = '0'.$binarypointnumber;
238 while (($binarypointnumber{0} != '1') || (substr($binarypointnumber, 1, 1) != '.')) {
239 if (substr($binarypointnumber, 1, 1) == '.') {
241 $binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3);
243 $pointpos = strpos($binarypointnumber, '.');
244 $exponent += ($pointpos - 1);
245 $binarypointnumber = str_replace('.', '', $binarypointnumber);
246 $binarypointnumber = $binarypointnumber{0}.'.'.substr($binarypointnumber, 1);
249 $binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT);
250 return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent);
254 if (!function_exists('Float2BinaryDecimal')) {
255 function Float2BinaryDecimal($floatvalue) {
256 // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
257 $maxbits = 128; // to how many bits of precision should the calculations be taken?
258 $intpart = trunc($floatvalue);
259 $floatpart = abs($floatvalue - $intpart);
260 $pointbitstring = '';
261 while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) {
263 $pointbitstring .= (string) trunc($floatpart);
264 $floatpart -= trunc($floatpart);
266 $binarypointnumber = decbin($intpart).'.'.$pointbitstring;
267 return $binarypointnumber;
271 if (!function_exists('Float2String')) {
272 function Float2String($floatvalue, $bits) {
273 // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html
289 if ($floatvalue >= 0) {
294 $normalizedbinary = NormalizeBinaryPoint(Float2BinaryDecimal($floatvalue), $fractionbits);
295 $biasedexponent = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent
296 $exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT);
297 $fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT);
299 return BigEndian2String(Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false);
303 if (!function_exists('LittleEndian2Float')) {
304 function LittleEndian2Float($byteword) {
305 return BigEndian2Float(strrev($byteword));
309 if (!function_exists('BigEndian2Float')) {
310 function BigEndian2Float($byteword) {
311 // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
312 // http://www.psc.edu/general/software/packages/ieee/ieee.html
313 // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
315 $bitword = BigEndian2Bin($byteword);
316 $signbit = $bitword{0};
318 switch (strlen($byteword) * 8) {
338 $exponentstring = substr($bitword, 1, $exponentbits - 1);
339 $fractionstring = substr($bitword, $exponentbits, $fractionbits);
340 $exponent = Bin2Dec($exponentstring);
341 $fraction = Bin2Dec($fractionstring);
343 if (($exponentbits == 16) && ($fractionbits == 64)) {
345 // As used in Apple AIFF for sample_rate
346 // A bit of a hack, but it works ;)
347 return pow(2, ($exponent - 16382)) * DecimalBinary2Float($fractionstring);
351 if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) {
354 } elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) {
355 if ($signbit == '1') {
356 $floatvalue = '-infinity';
358 $floatvalue = '+infinity';
360 } elseif (($exponent == 0) && ($fraction == 0)) {
361 if ($signbit == '1') {
366 $floatvalue = ($signbit ? 0 : -0);
367 } elseif (($exponent == 0) && ($fraction != 0)) {
368 // These are 'unnormalized' values
369 $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * DecimalBinary2Float($fractionstring);
370 if ($signbit == '1') {
373 } elseif ($exponent != 0) {
374 $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + DecimalBinary2Float($fractionstring));
375 if ($signbit == '1') {
379 return (float) $floatvalue;
383 if (!function_exists('BigEndian2Int')) {
384 function BigEndian2Int($byteword, $synchsafe=false, $signed=false) {
386 $bytewordlen = strlen($byteword);
387 for ($i = 0; $i < $bytewordlen; $i++) {
388 if ($synchsafe) { // disregard MSB, effectively 7-bit bytes
389 $intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7);
391 $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i));
394 if ($signed && !$synchsafe) {
395 // synchsafe ints are not allowed to be signed
396 switch ($bytewordlen) {
401 $signmaskbit = 0x80 << (8 * ($bytewordlen - 1));
402 if ($intvalue & $signmaskbit) {
403 $intvalue = 0 - ($intvalue & ($signmaskbit - 1));
408 die('ERROR: Cannot have signed integers larger than 32-bits in BigEndian2Int()');
412 return CastAsInt($intvalue);
416 if (!function_exists('LittleEndian2Int')) {
417 function LittleEndian2Int($byteword, $signed=false) {
418 return BigEndian2Int(strrev($byteword), false, $signed);
422 if (!function_exists('BigEndian2Bin')) {
423 function BigEndian2Bin($byteword) {
425 $bytewordlen = strlen($byteword);
426 for ($i = 0; $i < $bytewordlen; $i++) {
427 $binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT);
433 if (!function_exists('BigEndian2String')) {
434 function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) {
438 $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF);
442 die('ERROR: Cannot have signed integers larger than 32-bits in BigEndian2String()');
444 $number = $number & (0x80 << (8 * ($minbytes - 1)));
446 while ($number != 0) {
447 $quotient = ($number / ($maskbyte + 1));
448 $intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring;
449 $number = floor($quotient);
451 return str_pad($intstring, $minbytes, chr(0), STR_PAD_LEFT);
455 if (!function_exists('Dec2Bin')) {
456 function Dec2Bin($number) {
457 while ($number >= 256) {
458 $bytes[] = (($number / 256) - (floor($number / 256))) * 256;
459 $number = floor($number / 256);
463 for ($i = 0; $i < count($bytes); $i++) {
464 $binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring;
470 if (!function_exists('Bin2Dec')) {
471 function Bin2Dec($binstring) {
473 for ($i = 0; $i < strlen($binstring); $i++) {
474 $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i);
476 return CastAsInt($decvalue);
480 if (!function_exists('Bin2String')) {
481 function Bin2String($binstring) {
482 // return 'hi' for input of '0110100001101001'
484 $binstringreversed = strrev($binstring);
485 for ($i = 0; $i < strlen($binstringreversed); $i += 8) {
486 $string = chr(Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string;
492 if (!function_exists('LittleEndian2String')) {
493 function LittleEndian2String($number, $minbytes=1, $synchsafe=false) {
495 while ($number > 0) {
497 $intstring = $intstring.chr($number & 127);
500 $intstring = $intstring.chr($number & 255);
504 return str_pad($intstring, $minbytes, chr(0), STR_PAD_RIGHT);
508 if (!function_exists('Bool2IntString')) {
509 function Bool2IntString($intvalue) {
510 return ($intvalue ? '1' : '0');
514 if (!function_exists('IntString2Bool')) {
515 function IntString2Bool($char) {
518 } elseif ($char == '0') {
525 if (!function_exists('InverseBoolean')) {
526 function InverseBoolean($value) {
527 return ($value ? false : true);
531 if (!function_exists('DeUnSynchronise')) {
532 function DeUnSynchronise($data) {
533 return str_replace(chr(0xFF).chr(0x00), chr(0xFF), $data);
537 if (!function_exists('Unsynchronise')) {
538 function Unsynchronise($data) {
539 // Whenever a false synchronisation is found within the tag, one zeroed
540 // byte is inserted after the first false synchronisation byte. The
541 // format of a correct sync that should be altered by ID3 encoders is as
543 // %11111111 111xxxxx
544 // And should be replaced with:
545 // %11111111 00000000 111xxxxx
546 // This has the side effect that all $FF 00 combinations have to be
547 // altered, so they won't be affected by the decoding process. Therefore
548 // all the $FF 00 combinations have to be replaced with the $FF 00 00
549 // combination during the unsynchronisation.
551 $data = str_replace(chr(0xFF).chr(0x00), chr(0xFF).chr(0x00).chr(0x00), $data);
553 for ($i = 0; $i < strlen($data); $i++) {
554 $thischar = $data{$i};
555 $unsyncheddata .= $thischar;
556 if ($thischar == chr(255)) {
557 $nextchar = ord(substr($data, $i + 1, 1));
558 if (($nextchar | 0xE0) == 0xE0) {
559 // previous byte = 11111111, this byte = 111?????
560 $unsyncheddata .= chr(0);
564 return $unsyncheddata;
568 if (!function_exists('is_hash')) {
569 function is_hash($var) {
570 // written by dev-null@christophe.vg
571 // taken from http://www.php.net/manual/en/function.array-merge-recursive.php
572 if (is_array($var)) {
573 $keys = array_keys($var);
575 for ($i = 0; $i < count($keys); $i++) {
576 if (is_string($keys[$i])) {
585 if (!function_exists('array_join_merge')) {
586 function array_join_merge($arr1, $arr2) {
587 // written by dev-null@christophe.vg
588 // taken from http://www.php.net/manual/en/function.array-merge-recursive.php
589 if (is_array($arr1) && is_array($arr2)) {
591 $new_array = array();
593 if (is_hash($arr1) && is_hash($arr2)) {
594 // hashes -> merge based on keys
595 $keys = array_merge(array_keys($arr1), array_keys($arr2));
596 foreach ($keys as $key) {
597 $arr1[$key] = (isset($arr1[$key]) ? $arr1[$key] : '');
598 $arr2[$key] = (isset($arr2[$key]) ? $arr2[$key] : '');
599 $new_array[$key] = array_join_merge($arr1[$key], $arr2[$key]);
602 // two real arrays -> merge
603 $new_array = array_reverse(array_unique(array_reverse(array_merge($arr1,$arr2))));
607 // not the same ... take new one if defined, else the old one stays
608 return $arr2 ? $arr2 : $arr1;
613 if (!function_exists('array_merge_clobber')) {
614 function array_merge_clobber($array1, $array2) {
615 // written by kc@hireability.com
616 // taken from http://www.php.net/manual/en/function.array-merge-recursive.php
617 if (!is_array($array1) || !is_array($array2)) {
621 foreach ($array2 as $key => $val) {
622 if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
623 $newarray[$key] = array_merge_clobber($newarray[$key], $val);
625 $newarray[$key] = $val;
632 if (!function_exists('array_merge_noclobber')) {
633 function array_merge_noclobber($array1, $array2) {
634 if (!is_array($array1) || !is_array($array2)) {
638 foreach ($array2 as $key => $val) {
639 if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
640 $newarray[$key] = array_merge_noclobber($newarray[$key], $val);
641 } elseif (!isset($newarray[$key])) {
642 $newarray[$key] = $val;
649 if (!function_exists('RoughTranslateUnicodeToASCII')) {
650 function RoughTranslateUnicodeToASCII($rawdata, $frame_textencoding) {
651 // rough translation of data for application that can't handle Unicode data
654 switch ($frame_textencoding) {
655 case 0: // ISO-8859-1. Terminated with $00.
656 $asciidata = $rawdata;
659 case 1: // UTF-16 encoded Unicode with BOM. Terminated with $00 00.
660 $asciidata = $rawdata;
661 if (substr($asciidata, 0, 2) == chr(0xFF).chr(0xFE)) {
662 // remove BOM, only if present (it should be, but...)
663 $asciidata = substr($asciidata, 2);
665 if (substr($asciidata, strlen($asciidata) - 2, 2) == chr(0).chr(0)) {
666 $asciidata = substr($asciidata, 0, strlen($asciidata) - 2); // remove terminator, only if present (it should be, but...)
668 for ($i = 0; $i < strlen($asciidata); $i += 2) {
669 if ((ord($asciidata{$i}) <= 0x7F) || (ord($asciidata{$i}) >= 0xA0)) {
670 $tempstring .= $asciidata{$i};
675 $asciidata = $tempstring;
678 case 2: // UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
679 $asciidata = $rawdata;
680 if (substr($asciidata, strlen($asciidata) - 2, 2) == chr(0).chr(0)) {
681 $asciidata = substr($asciidata, 0, strlen($asciidata) - 2); // remove terminator, only if present (it should be, but...)
683 for ($i = 0; $i < strlen($asciidata); $i += 2) {
684 if ((ord($asciidata{$i}) <= 0x7F) || (ord($asciidata{$i}) >= 0xA0)) {
685 $tempstring .= $asciidata{$i};
690 $asciidata = $tempstring;
693 case 3: // UTF-8 encoded Unicode. Terminated with $00.
694 $asciidata = utf8_decode($rawdata);
697 case 255: // Unicode, Big-Endian. Terminated with $00 00.
698 $asciidata = $rawdata;
699 if (substr($asciidata, strlen($asciidata) - 2, 2) == chr(0).chr(0)) {
700 $asciidata = substr($asciidata, 0, strlen($asciidata) - 2); // remove terminator, only if present (it should be, but...)
702 for ($i = 0; ($i + 1) < strlen($asciidata); $i += 2) {
703 if ((ord($asciidata{($i + 1)}) <= 0x7F) || (ord($asciidata{($i + 1)}) >= 0xA0)) {
704 $tempstring .= $asciidata{($i + 1)};
709 $asciidata = $tempstring;
714 // shouldn't happen, but in case $frame_textencoding is not 1 <= $frame_textencoding <= 4
715 // just pass the data through unchanged.
716 $asciidata = $rawdata;
719 if (substr($asciidata, strlen($asciidata) - 1, 1) == chr(0)) {
720 // remove null terminator, if present
721 $asciidata = NoNullString($asciidata);
724 // return str_replace(chr(0), '', $asciidata); // just in case any nulls slipped through
728 if (!function_exists('PlaytimeString')) {
729 function PlaytimeString($playtimeseconds) {
730 $contentseconds = round((($playtimeseconds / 60) - floor($playtimeseconds / 60)) * 60);
731 $contentminutes = floor($playtimeseconds / 60);
732 if ($contentseconds >= 60) {
733 $contentseconds -= 60;
736 return number_format($contentminutes).':'.str_pad($contentseconds, 2, 0, STR_PAD_LEFT);
740 if (!function_exists('CloseMatch')) {
741 function CloseMatch($value1, $value2, $tolerance) {
742 return (abs($value1 - $value2) <= $tolerance);
746 if (!function_exists('ID3v1matchesID3v2')) {
747 function ID3v1matchesID3v2($id3v1, $id3v2) {
749 $requiredindices = array('title', 'artist', 'album', 'year', 'genre', 'comment');
750 foreach ($requiredindices as $requiredindex) {
751 if (!isset($id3v1["$requiredindex"])) {
752 $id3v1["$requiredindex"] = '';
754 if (!isset($id3v2["$requiredindex"])) {
755 $id3v2["$requiredindex"] = '';
759 if (trim($id3v1['title']) != trim(substr($id3v2['title'], 0, 30))) {
762 if (trim($id3v1['artist']) != trim(substr($id3v2['artist'], 0, 30))) {
765 if (trim($id3v1['album']) != trim(substr($id3v2['album'], 0, 30))) {
768 if (trim($id3v1['year']) != trim(substr($id3v2['year'], 0, 4))) {
771 if (trim($id3v1['genre']) != trim($id3v2['genre'])) {
774 if (isset($id3v1['track'])) {
775 if (!isset($id3v1['track']) || (trim($id3v1['track']) != trim($id3v2['track']))) {
778 if (trim($id3v1['comment']) != trim(substr($id3v2['comment'], 0, 28))) {
782 if (trim($id3v1['comment']) != trim(substr($id3v2['comment'], 0, 30))) {
790 if (!function_exists('FILETIMEtoUNIXtime')) {
791 function FILETIMEtoUNIXtime($FILETIME, $round=true) {
792 // FILETIME is a 64-bit unsigned integer representing
793 // the number of 100-nanosecond intervals since January 1, 1601
794 // UNIX timestamp is number of seconds since January 1, 1970
795 // 116444736000000000 = 10000000 * 60 * 60 * 24 * 365 * 369 + 89 leap days
797 return round(($FILETIME - 116444736000000000) / 10000000);
799 return ($FILETIME - 116444736000000000) / 10000000;
803 if (!function_exists('GUIDtoBytestring')) {
804 function GUIDtoBytestring($GUIDstring) {
805 // Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way:
806 // first 4 bytes are in little-endian order
807 // next 2 bytes are appended in little-endian order
808 // next 2 bytes are appended in little-endian order
809 // next 2 bytes are appended in big-endian order
810 // next 6 bytes are appended in big-endian order
812 // AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp is stored as this 16-byte string:
813 // $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp
815 $hexbytecharstring = chr(hexdec(substr($GUIDstring, 6, 2)));
816 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 4, 2)));
817 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 2, 2)));
818 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 0, 2)));
820 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 11, 2)));
821 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 9, 2)));
823 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 16, 2)));
824 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 14, 2)));
826 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 19, 2)));
827 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 21, 2)));
829 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 24, 2)));
830 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 26, 2)));
831 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 28, 2)));
832 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 30, 2)));
833 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 32, 2)));
834 $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 34, 2)));
836 return $hexbytecharstring;
840 if (!function_exists('BytestringToGUID')) {
841 function BytestringToGUID($Bytestring) {
842 $GUIDstring = str_pad(dechex(ord($Bytestring{3})), 2, '0', STR_PAD_LEFT);
843 $GUIDstring .= str_pad(dechex(ord($Bytestring{2})), 2, '0', STR_PAD_LEFT);
844 $GUIDstring .= str_pad(dechex(ord($Bytestring{1})), 2, '0', STR_PAD_LEFT);
845 $GUIDstring .= str_pad(dechex(ord($Bytestring{0})), 2, '0', STR_PAD_LEFT);
847 $GUIDstring .= str_pad(dechex(ord($Bytestring{5})), 2, '0', STR_PAD_LEFT);
848 $GUIDstring .= str_pad(dechex(ord($Bytestring{4})), 2, '0', STR_PAD_LEFT);
850 $GUIDstring .= str_pad(dechex(ord($Bytestring{7})), 2, '0', STR_PAD_LEFT);
851 $GUIDstring .= str_pad(dechex(ord($Bytestring{6})), 2, '0', STR_PAD_LEFT);
853 $GUIDstring .= str_pad(dechex(ord($Bytestring{8})), 2, '0', STR_PAD_LEFT);
854 $GUIDstring .= str_pad(dechex(ord($Bytestring{9})), 2, '0', STR_PAD_LEFT);
856 $GUIDstring .= str_pad(dechex(ord($Bytestring{10})), 2, '0', STR_PAD_LEFT);
857 $GUIDstring .= str_pad(dechex(ord($Bytestring{11})), 2, '0', STR_PAD_LEFT);
858 $GUIDstring .= str_pad(dechex(ord($Bytestring{12})), 2, '0', STR_PAD_LEFT);
859 $GUIDstring .= str_pad(dechex(ord($Bytestring{13})), 2, '0', STR_PAD_LEFT);
860 $GUIDstring .= str_pad(dechex(ord($Bytestring{14})), 2, '0', STR_PAD_LEFT);
861 $GUIDstring .= str_pad(dechex(ord($Bytestring{15})), 2, '0', STR_PAD_LEFT);
863 return strtoupper($GUIDstring);
867 if (!function_exists('BitrateColor')) {
868 function BitrateColor($bitrate) {
869 $bitrate /= 3; // scale from 1-768kbps to 1-256kbps
870 $bitrate--; // scale from 1-256kbps to 0-255kbps
871 $bitrate = max($bitrate, 0);
872 $bitrate = min($bitrate, 255);
873 //$bitrate = max($bitrate, 32);
874 //$bitrate = min($bitrate, 143);
875 //$bitrate = ($bitrate * 2) - 32;
877 $Rcomponent = max(255 - ($bitrate * 2), 0);
878 $Gcomponent = max(($bitrate * 2) - 255, 0);
879 if ($bitrate > 127) {
880 $Bcomponent = max((255 - $bitrate) * 2, 0);
882 $Bcomponent = max($bitrate * 2, 0);
884 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);
888 if (!function_exists('BitrateText')) {
889 function BitrateText($bitrate) {
890 return '<SPAN STYLE="color: #'.BitrateColor($bitrate).'">'.round($bitrate).' kbps</SPAN>';
894 if (!function_exists('image_type_to_mime_type')) {
895 function image_type_to_mime_type($imagetypeid) {
896 // only available in PHP v4.3.0+
897 static $image_type_to_mime_type = array();
898 if (empty($image_type_to_mime_type)) {
899 $image_type_to_mime_type[1] = 'image/gif'; // GIF
900 $image_type_to_mime_type[2] = 'image/jpeg'; // JPEG
901 $image_type_to_mime_type[3] = 'image/png'; // PNG
902 $image_type_to_mime_type[4] = 'application/x-shockwave-flash'; // Flash
903 $image_type_to_mime_type[5] = 'image/psd'; // PSD
904 $image_type_to_mime_type[6] = 'image/bmp'; // BMP
905 $image_type_to_mime_type[7] = 'image/tiff'; // TIFF: little-endian (Intel)
906 $image_type_to_mime_type[8] = 'image/tiff'; // TIFF: big-endian (Motorola)
907 //$image_type_to_mime_type[9] = 'image/jpc'; // JPC
908 //$image_type_to_mime_type[10] = 'image/jp2'; // JPC
909 //$image_type_to_mime_type[11] = 'image/jpx'; // JPC
910 //$image_type_to_mime_type[12] = 'image/jb2'; // JPC
911 $image_type_to_mime_type[13] = 'application/x-shockwave-flash'; // Shockwave
912 $image_type_to_mime_type[14] = 'image/iff'; // IFF
914 return (isset($image_type_to_mime_type[$imagetypeid]) ? $image_type_to_mime_type[$imagetypeid] : 'application/octet-stream');
918 if (!function_exists('utf8_decode')) {
919 // PHP has this function built-in if it's configured with the --with-xml option
920 // This version of the function is only provided in case XML isn't installed
921 function utf8_decode($utf8text) {
922 // http://www.php.net/manual/en/function.utf8-encode.php
923 // bytes bits representation
925 // 2 11 110bbbbb 10bbbbbb
926 // 3 16 1110bbbb 10bbbbbb 10bbbbbb
927 // 4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
929 $utf8length = strlen($utf8text);
931 for ($i = 0; $i < $utf8length; $i++) {
932 if ((ord($utf8text{$i}) & 0x80) == 0) {
933 $decodedtext .= $utf8text{$i};
934 } elseif ((ord($utf8text{$i}) & 0xF0) == 0xF0) {
937 } elseif ((ord($utf8text{$i}) & 0xE0) == 0xE0) {
940 } elseif ((ord($utf8text{$i}) & 0xC0) == 0xC0) {
941 // 2 11 110bbbbb 10bbbbbb
942 $decodedchar = Bin2Dec(substr(Dec2Bin(ord($utf8text{$i})), 3, 5).substr(Dec2Bin(ord($utf8text{($i + 1)})), 2, 6));
943 if ($decodedchar <= 255) {
944 $decodedtext .= chr($decodedchar);
955 if (!function_exists('DateMac2Unix')) {
956 function DateMac2Unix($macdate) {
957 // Macintosh timestamp: seconds since 00:00h January 1, 1904
958 // UNIX timestamp: seconds since 00:00h January 1, 1970
959 return CastAsInt($macdate - 2082844800);
964 if (!function_exists('FixedPoint8_8')) {
965 function FixedPoint8_8($rawdata) {
966 return BigEndian2Int(substr($rawdata, 0, 1)) + (float) (BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8));
971 if (!function_exists('FixedPoint16_16')) {
972 function FixedPoint16_16($rawdata) {
973 return BigEndian2Int(substr($rawdata, 0, 2)) + (float) (BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16));
978 if (!function_exists('FixedPoint2_30')) {
979 function FixedPoint2_30($rawdata) {
980 $binarystring = BigEndian2Bin($rawdata);
981 return Bin2Dec(substr($binarystring, 0, 2)) + (float) (Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30));
986 if (!function_exists('Pascal2String')) {
987 function Pascal2String($pascalstring) {
988 // Pascal strings have 1 byte at the beginning saying how many chars are in the string
989 return substr($pascalstring, 1);
993 if (!function_exists('NoNullString')) {
994 function NoNullString($nullterminatedstring) {
995 // remove the single null terminator on null terminated strings
996 if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === chr(0)) {
997 return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1);
999 return $nullterminatedstring;
1003 if (!function_exists('FileSizeNiceDisplay')) {
1004 function FileSizeNiceDisplay($filesize, $precision=2) {
1005 if ($filesize < 1000) {
1006 $sizeunit = 'bytes';
1012 if ($filesize >= 1000) {
1016 if ($filesize >= 1000) {
1020 return number_format($filesize, $precision).' '.$sizeunit;
1024 if (!function_exists('DOStime2UNIXtime')) {
1025 function DOStime2UNIXtime($DOSdate, $DOStime) {
1027 // Specifies the MS-DOS date. The date is a packed 16-bit value with the following format:
1029 // 0-4 Day of the month (1-31)
1030 // 5-8 Month (1 = January, 2 = February, and so on)
1031 // 9-15 Year offset from 1980 (add 1980 to get actual year)
1033 $UNIXday = ($DOSdate & 0x001F);
1034 $UNIXmonth = (($DOSdate & 0x01E0) >> 5);
1035 $UNIXyear = (($DOSdate & 0xFE00) >> 9) + 1980;
1038 // Specifies the MS-DOS time. The time is a packed 16-bit value with the following format:
1040 // 0-4 Second divided by 2
1041 // 5-10 Minute (0-59)
1042 // 11-15 Hour (0-23 on a 24-hour clock)
1044 $UNIXsecond = ($DOStime & 0x001F) * 2;
1045 $UNIXminute = (($DOStime & 0x07E0) >> 5);
1046 $UNIXhour = (($DOStime & 0xF800) >> 11);
1048 return mktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
1052 if (!function_exists('CreateDeepArray')) {
1053 function CreateDeepArray($ArrayPath, $Separator, $Value) {
1054 // assigns $Value to a nested array path:
1055 // $foo = CreateDeepArray('/path/to/my', '/', 'file.txt')
1057 // $foo = array('path'=>array('to'=>'array('my'=>array('file.txt'))));
1059 // $foo['path']['to']['my'] = 'file.txt';
1060 while ($ArrayPath{0} == $Separator) {
1061 $ArrayPath = substr($ArrayPath, 1);
1063 if (($pos = strpos($ArrayPath, $Separator)) !== false) {
1064 $ReturnedArray[substr($ArrayPath, 0, $pos)] = CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value);
1066 $ReturnedArray["$ArrayPath"] = $Value;
1068 return $ReturnedArray;
1072 if (!function_exists('md5_file')) {
1073 // Allan Hansen <ah@artemis.dk>
1074 // md5_file() exists in PHP 4.2.0.
1075 // The following works under UNIX only, but dies on windows
1076 function md5_file($file) {
1077 if (substr(php_uname(), 0, 7) == 'Windows') {
1078 die('PHP 4.2.0 or newer required for md5_file()');
1081 $file = str_replace('`', '\\`', $file);
1082 if (preg_match("#^([0-9a-f]{32})[ \t\n\r]#i", `md5sum "$file"`, $r)) {
1089 if (!function_exists('md5_data')) {
1090 // Allan Hansen <ah@artemis.dk>
1091 // md5_data() - returns md5sum for a file from startuing position to absolute end position
1093 function md5_data($file, $offset, $end, $invertsign=false) {
1094 // first try and create a temporary file in the same directory as the file being scanned
1095 if (($dataMD5filename = tempnam(dirname($file), preg_replace('#[^[:alnum:]]#i', '', basename($file)))) === false) {
1096 // if that fails, create a temporary file in the system temp directory
1097 if (($dataMD5filename = tempnam('/tmp', 'getID3')) === false) {
1098 // if that fails, create a temporary file in the current directory
1099 if (($dataMD5filename = tempnam('.', preg_replace('#[^[:alnum:]]#i', '', basename($file)))) === false) {
1100 // can't find anywhere to create a temp file, just die
1106 set_time_limit(max(filesize($file) / 1000000, 30));
1108 // copy parts of file
1110 if ($fp = fopen($file, 'rb')) {
1114 if ($MD5fp = fopen($dataMD5filename, 'wb')) {
1118 // Load conversion lookup strings for 8-bit unsigned->signed conversion below
1121 for ($i = 0; $i < 128; $i++) {
1123 $to .= chr($i + 128);
1125 for ($i = 128; $i < 256; $i++) {
1127 $to .= chr($i - 128);
1131 fseek($fp, $offset, SEEK_SET);
1132 $byteslefttowrite = $end - $offset;
1133 while (($byteslefttowrite > 0) && ($buffer = fread($fp, 32768))) {
1135 // Possibly FLAC-specific (?)
1136 // FLAC calculates the MD5sum of the source data of 8-bit files
1137 // not on the actual byte values in the source file, but of those
1138 // values converted from unsigned to signed, or more specifcally,
1139 // with the MSB inverted. ex: 01 -> 81; F5 -> 75; etc
1141 // Therefore, 8-bit WAV data has to be converted before getting the
1142 // md5_data value so as to match the FLAC value
1144 // Flip the MSB for each byte in the buffer before copying
1145 $buffer = strtr($buffer, $from, $to);
1147 $byteswritten = fwrite($MD5fp, $buffer, $byteslefttowrite);
1148 $byteslefttowrite -= $byteswritten;
1151 $md5 = md5_file($dataMD5filename);
1154 $errormessage = ob_get_contents();
1160 $errormessage = ob_get_contents();
1163 unlink($dataMD5filename);
1168 if (!function_exists('TwosCompliment2Decimal')) {
1169 function TwosCompliment2Decimal($BinaryValue) {
1170 // http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html
1171 // First check if the number is negative or positive by looking at the sign bit.
1172 // If it is positive, simply convert it to decimal.
1173 // If it is negative, make it positive by inverting the bits and adding one.
1174 // Then, convert the result to decimal.
1175 // The negative of this number is the value of the original binary.
1177 if ($BinaryValue & 0x80) {
1180 return (0 - ((~$BinaryValue & 0xFF) + 1));
1185 return $BinaryValue;
1192 if (!function_exists('LastArrayElement')) {
1193 function LastArrayElement($MyArray) {
1194 if (!is_array($MyArray)) {
1197 if (empty($MyArray)) {
1200 foreach ($MyArray as $key => $value) {
1206 if (!function_exists('safe_inc')) {
1207 function safe_inc(&$variable, $increment=1) {
1208 if (isset($variable)) {
1209 $variable += $increment;
1211 $variable = $increment;
1217 if (!function_exists('CalculateCompressionRatioVideo')) {
1218 function CalculateCompressionRatioVideo(&$ThisFileInfo) {
1219 if (empty($ThisFileInfo['video'])) {
1222 if (empty($ThisFileInfo['video']['resolution_x']) || empty($ThisFileInfo['video']['resolution_y'])) {
1225 if (empty($ThisFileInfo['video']['bits_per_sample'])) {
1229 switch ($ThisFileInfo['video']['dataformat']) {
1237 $PlaytimeSeconds = 1;
1238 $BitrateCompressed = $ThisFileInfo['filesize'] * 8;
1242 if (!empty($ThisFileInfo['video']['frame_rate'])) {
1243 $FrameRate = $ThisFileInfo['video']['frame_rate'];
1247 if (!empty($ThisFileInfo['playtime_seconds'])) {
1248 $PlaytimeSeconds = $ThisFileInfo['playtime_seconds'];
1252 if (!empty($ThisFileInfo['video']['bitrate'])) {
1253 $BitrateCompressed = $ThisFileInfo['video']['bitrate'];
1259 $BitrateUncompressed = $ThisFileInfo['video']['resolution_x'] * $ThisFileInfo['video']['resolution_y'] * $ThisFileInfo['video']['bits_per_sample'] * $FrameRate;
1261 $ThisFileInfo['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
1266 if (!function_exists('CalculateCompressionRatioAudio')) {
1267 function CalculateCompressionRatioAudio(&$ThisFileInfo) {
1268 if (empty($ThisFileInfo['audio']['bitrate']) || empty($ThisFileInfo['audio']['channels']) || empty($ThisFileInfo['audio']['sample_rate']) || empty($ThisFileInfo['audio']['bits_per_sample'])) {
1271 $ThisFileInfo['audio']['compression_ratio'] = $ThisFileInfo['audio']['bitrate'] / ($ThisFileInfo['audio']['channels'] * $ThisFileInfo['audio']['sample_rate'] * $ThisFileInfo['audio']['bits_per_sample']);
1276 if (!function_exists('IsValidMIMEstring')) {
1277 function IsValidMIMEstring($mimestring) {
1278 if ((strlen($mimestring) >= 3) && (strpos($mimestring, '/') > 0) && (strpos($mimestring, '/') < (strlen($mimestring) - 1))) {
1285 if (!function_exists('IsWithinBitRange')) {
1286 function IsWithinBitRange($number, $maxbits, $signed=false) {
1288 if (($number > (0 - pow(2, $maxbits - 1))) && ($number <= pow(2, $maxbits - 1))) {
1292 if (($number >= 0) && ($number <= pow(2, $maxbits))) {
1300 if (!function_exists('safe_parse_url')) {
1301 function safe_parse_url($url) {
1303 $parts = parse_url($url);
1304 $errormessage = ob_get_contents();
1306 $parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : '');
1307 $parts['host'] = (isset($parts['host']) ? $parts['host'] : '');
1308 $parts['user'] = (isset($parts['user']) ? $parts['user'] : '');
1309 $parts['pass'] = (isset($parts['pass']) ? $parts['pass'] : '');
1310 $parts['path'] = (isset($parts['path']) ? $parts['path'] : '');
1311 $parts['query'] = (isset($parts['query']) ? $parts['query'] : '');
1316 if (!function_exists('IsValidURL')) {
1317 function IsValidURL($url, $allowUserPass=false) {
1321 if ($allowUserPass !== true) {
1322 if (strstr($url, '@')) {
1323 // in the format http://user:pass@example.com or http://user@example.com
1324 // but could easily be somebody incorrectly entering an email address in place of a URL
1328 if ($parts = safe_parse_url($url)) {
1329 if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher')) {
1331 } 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'])) {
1333 } elseif (!preg_match("#^([[:alnum:]-]|[\_])*$#i", $parts['user'], $regs)) {
1335 } elseif (!preg_match("#^([[:alnum:]-]|[\_])*$#i", $parts['pass'], $regs)) {
1337 } elseif (!preg_match("#^[[:alnum:]/_\.@~-]*$#i", $parts['path'], $regs)) {
1339 } elseif (!preg_match("#^[[:alnum:]?&=+:;_()%#/,\.-]*$#i", $parts['query'], $regs)) {
1349 echo '<form action="'.htmlentities($_SERVER['PHP_SELF'], ENT_QUOTES).'" method="get">';
1350 echo 'Enter 4 hex bytes of MPEG-audio header (ie <I>FF FA 92 44</I>)<BR>';
1351 echo '<input type="text" name="HeaderHexBytes" value="'.htmlentities(isset($_POST['HeaderHexBytes']) ? strtoupper($_POST['HeaderHexBytes']) : '', ENT_QUOTES).'" size="11" maxlength="11">';
1352 echo '<input type="submit" name="Analyze" value="Analyze"></form>';
1355 echo '<form action="'.htmlentities($_SERVER['PHP_SELF'], ENT_QUOTES).'" METHOD="get">';
1356 echo 'Generate a MPEG-audio 4-byte header from these values:<BR>';
1357 echo '<table border="0">';
1359 $MPEGgenerateValues = array(
1360 'version'=>array('1', '2', '2.5'),
1361 'layer'=>array('I', 'II', 'III'),
1362 'protection'=>array('Y', 'N'),
1363 'bitrate'=>array('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'),
1364 'frequency'=>array('8000', '11025', '12000', '16000', '22050', '24000', '32000', '44100', '48000'),
1365 'padding'=>array('Y', 'N'),
1366 'private'=>array('Y', 'N'),
1367 'channelmode'=>array('stereo', 'joint stereo', 'dual channel', 'mono'),
1368 'modeextension'=>array('none', 'IS', 'MS', 'IS+MS', '4-31', '8-31', '12-31', '16-31'),
1369 'copyright'=>array('Y', 'N'),
1370 'original'=>array('Y', 'N'),
1371 'emphasis'=>array('none', '50/15ms', 'CCIT J.17')
1374 foreach ($MPEGgenerateValues as $name => $dataarray) {
1375 echo '<tr><th>'.$name.':</th><td><select name="'.$name.'">';
1376 foreach ($dataarray as $key => $value) {
1377 echo '<option'.((isset($_POST["$name"]) && ($_POST["$name"] == $value)) ? ' SELECTED' : '').'>'.$value.'</option>';
1379 echo '</select></td></tr>';
1382 if (isset($_POST['bitrate'])) {
1383 echo '<tr><th>Frame Length:</th><td>'.(int) MPEGaudioFrameLength($_POST['bitrate'], $_POST['version'], $_POST['layer'], (($_POST['padding'] == 'Y') ? '1' : '0'), $_POST['frequency']).'</td></tr>';
1386 echo '<input type="submit" name="Generate" value="Generate"></form>';
1390 if (isset($_POST['Analyze']) && $_POST['HeaderHexBytes']) {
1392 $headerbytearray = explode(' ', $_POST['HeaderHexBytes']);
1393 if (count($headerbytearray) != 4) {
1394 die('Invalid byte pattern');
1397 foreach ($headerbytearray as $textbyte) {
1398 $headerstring .= chr(hexdec($textbyte));
1401 $MP3fileInfo['error'] = '';
1403 $MPEGheaderRawArray = MPEGaudioHeaderDecode(substr($headerstring, 0, 4));
1405 if (MPEGaudioHeaderValid($MPEGheaderRawArray, true)) {
1407 $MP3fileInfo['raw'] = $MPEGheaderRawArray;
1409 $MP3fileInfo['version'] = MPEGaudioVersionLookup($MP3fileInfo['raw']['version']);
1410 $MP3fileInfo['layer'] = MPEGaudioLayerLookup($MP3fileInfo['raw']['layer']);
1411 $MP3fileInfo['protection'] = MPEGaudioCRCLookup($MP3fileInfo['raw']['protection']);
1412 $MP3fileInfo['bitrate'] = MPEGaudioBitrateLookup($MP3fileInfo['version'], $MP3fileInfo['layer'], $MP3fileInfo['raw']['bitrate']);
1413 $MP3fileInfo['frequency'] = MPEGaudioFrequencyLookup($MP3fileInfo['version'], $MP3fileInfo['raw']['sample_rate']);
1414 $MP3fileInfo['padding'] = (bool) $MP3fileInfo['raw']['padding'];
1415 $MP3fileInfo['private'] = (bool) $MP3fileInfo['raw']['private'];
1416 $MP3fileInfo['channelmode'] = MPEGaudioChannelModeLookup($MP3fileInfo['raw']['channelmode']);
1417 $MP3fileInfo['channels'] = (($MP3fileInfo['channelmode'] == 'mono') ? 1 : 2);
1418 $MP3fileInfo['modeextension'] = MPEGaudioModeExtensionLookup($MP3fileInfo['layer'], $MP3fileInfo['raw']['modeextension']);
1419 $MP3fileInfo['copyright'] = (bool) $MP3fileInfo['raw']['copyright'];
1420 $MP3fileInfo['original'] = (bool) $MP3fileInfo['raw']['original'];
1421 $MP3fileInfo['emphasis'] = MPEGaudioEmphasisLookup($MP3fileInfo['raw']['emphasis']);
1423 if ($MP3fileInfo['protection']) {
1424 $MP3fileInfo['crc'] = BigEndian2Int(substr($headerstring, 4, 2));
1427 if ($MP3fileInfo['frequency'] > 0) {
1428 $MP3fileInfo['framelength'] = MPEGaudioFrameLength($MP3fileInfo['bitrate'], $MP3fileInfo['version'], $MP3fileInfo['layer'], (int) $MP3fileInfo['padding'], $MP3fileInfo['frequency']);
1430 if ($MP3fileInfo['bitrate'] != 'free') {
1431 $MP3fileInfo['bitrate'] *= 1000;
1436 $MP3fileInfo['error'] .= "\n".'Invalid MPEG audio header';
1440 if (!$MP3fileInfo['error']) {
1441 unset($MP3fileInfo['error']);
1444 echo table_var_dump($MP3fileInfo);
1446 } elseif (isset($_POST['Generate'])) {
1448 // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM
1450 $headerbitstream = '11111111111'; // A - Frame sync (all bits set)
1452 $MPEGversionLookup = array('2.5'=>'00', '2'=>'10', '1'=>'11');
1453 $headerbitstream .= $MPEGversionLookup[$_POST['version']]; // B - MPEG Audio version ID
1455 $MPEGlayerLookup = array('III'=>'01', 'II'=>'10', 'I'=>'11');
1456 $headerbitstream .= $MPEGlayerLookup[$_POST['layer']]; // C - Layer description
1458 $headerbitstream .= (($_POST['protection'] == 'Y') ? '0' : '1'); // D - Protection bit
1460 $MPEGaudioBitrateLookup['1']['I'] = array('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');
1461 $MPEGaudioBitrateLookup['1']['II'] = array('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');
1462 $MPEGaudioBitrateLookup['1']['III'] = array('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');
1463 $MPEGaudioBitrateLookup['2']['I'] = array('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');
1464 $MPEGaudioBitrateLookup['2']['II'] = array('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');
1465 $MPEGaudioBitrateLookup['2']['III'] = $MPEGaudioBitrateLookup['2']['II'];
1466 $MPEGaudioBitrateLookup['2.5']['I'] = $MPEGaudioBitrateLookup['2']['I'];
1467 $MPEGaudioBitrateLookup['2.5']['II'] = $MPEGaudioBitrateLookup['2']['II'];
1468 $MPEGaudioBitrateLookup['2.5']['III'] = $MPEGaudioBitrateLookup['2']['II'];
1469 if (isset($MPEGaudioBitrateLookup[$_POST['version']][$_POST['layer']][$_POST['bitrate']])) {
1470 $headerbitstream .= $MPEGaudioBitrateLookup[$_POST['version']][$_POST['layer']][$_POST['bitrate']]; // E - Bitrate index
1472 die('Invalid <B>Bitrate</B>');
1475 $MPEGaudioFrequencyLookup['1'] = array('44100'=>'00', '48000'=>'01', '32000'=>'10');
1476 $MPEGaudioFrequencyLookup['2'] = array('22050'=>'00', '24000'=>'01', '16000'=>'10');
1477 $MPEGaudioFrequencyLookup['2.5'] = array('11025'=>'00', '12000'=>'01', '8000'=>'10');
1478 if (isset($MPEGaudioFrequencyLookup[$_POST['version']][$_POST['frequency']])) {
1479 $headerbitstream .= $MPEGaudioFrequencyLookup[$_POST['version']][$_POST['frequency']]; // F - Sampling rate frequency index
1481 die('Invalid <B>Frequency</B>');
1484 $headerbitstream .= (($_POST['padding'] == 'Y') ? '1' : '0'); // G - Padding bit
1486 $headerbitstream .= (($_POST['private'] == 'Y') ? '1' : '0'); // H - Private bit
1488 $MPEGaudioChannelModeLookup = array('stereo'=>'00', 'joint stereo'=>'01', 'dual channel'=>'10', 'mono'=>'11');
1489 $headerbitstream .= $MPEGaudioChannelModeLookup[$_POST['channelmode']]; // I - Channel Mode
1491 $MPEGaudioModeExtensionLookup['I'] = array('4-31'=>'00', '8-31'=>'01', '12-31'=>'10', '16-31'=>'11');
1492 $MPEGaudioModeExtensionLookup['II'] = $MPEGaudioModeExtensionLookup['I'];
1493 $MPEGaudioModeExtensionLookup['III'] = array('none'=>'00', 'IS'=>'01', 'MS'=>'10', 'IS+MS'=>'11');
1494 if ($_POST['channelmode'] != 'joint stereo') {
1495 $headerbitstream .= '00';
1496 } elseif (isset($MPEGaudioModeExtensionLookup[$_POST['layer']][$_POST['modeextension']])) {
1497 $headerbitstream .= $MPEGaudioModeExtensionLookup[$_POST['layer']][$_POST['modeextension']]; // J - Mode extension (Only if Joint stereo)
1499 die('Invalid <B>Mode Extension</B>');
1502 $headerbitstream .= (($_POST['copyright'] == 'Y') ? '1' : '0'); // K - Copyright
1504 $headerbitstream .= (($_POST['original'] == 'Y') ? '1' : '0'); // L - Original
1506 $MPEGaudioEmphasisLookup = array('none'=>'00', '50/15ms'=>'01', 'CCIT J.17'=>'11');
1507 if (isset($MPEGaudioEmphasisLookup[$_POST['emphasis']])) {
1508 $headerbitstream .= $MPEGaudioEmphasisLookup[$_POST['emphasis']]; // M - Emphasis
1510 die('Invalid <B>Emphasis</B>');
1513 echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream, 0, 8))), 2, '0', STR_PAD_LEFT)).' ';
1514 echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream, 8, 8))), 2, '0', STR_PAD_LEFT)).' ';
1515 echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream, 16, 8))), 2, '0', STR_PAD_LEFT)).' ';
1516 echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream, 24, 8))), 2, '0', STR_PAD_LEFT)).'<BR>';
1520 function MPEGaudioVersionLookup($rawversion) {
1521 $MPEGaudioVersionLookup = array('2.5', FALSE, '2', '1');
1522 return (isset($MPEGaudioVersionLookup["$rawversion"]) ? $MPEGaudioVersionLookup["$rawversion"] : FALSE);
1525 function MPEGaudioLayerLookup($rawlayer) {
1526 $MPEGaudioLayerLookup = array(FALSE, 'III', 'II', 'I');
1527 return (isset($MPEGaudioLayerLookup["$rawlayer"]) ? $MPEGaudioLayerLookup["$rawlayer"] : FALSE);
1530 function MPEGaudioBitrateLookup($version, $layer, $rawbitrate) {
1531 static $MPEGaudioBitrateLookup;
1532 if (empty($MPEGaudioBitrateLookup)) {
1533 $MPEGaudioBitrateLookup = MPEGaudioBitrateArray();
1535 return (isset($MPEGaudioBitrateLookup["$version"]["$layer"]["$rawbitrate"]) ? $MPEGaudioBitrateLookup["$version"]["$layer"]["$rawbitrate"] : FALSE);
1538 function MPEGaudioFrequencyLookup($version, $rawfrequency) {
1539 static $MPEGaudioFrequencyLookup;
1540 if (empty($MPEGaudioFrequencyLookup)) {
1541 $MPEGaudioFrequencyLookup = MPEGaudioFrequencyArray();
1543 return (isset($MPEGaudioFrequencyLookup["$version"]["$rawfrequency"]) ? $MPEGaudioFrequencyLookup["$version"]["$rawfrequency"] : FALSE);
1546 function MPEGaudioChannelModeLookup($rawchannelmode) {
1547 $MPEGaudioChannelModeLookup = array('stereo', 'joint stereo', 'dual channel', 'mono');
1548 return (isset($MPEGaudioChannelModeLookup["$rawchannelmode"]) ? $MPEGaudioChannelModeLookup["$rawchannelmode"] : FALSE);
1551 function MPEGaudioModeExtensionLookup($layer, $rawmodeextension) {
1552 $MPEGaudioModeExtensionLookup['I'] = array('4-31', '8-31', '12-31', '16-31');
1553 $MPEGaudioModeExtensionLookup['II'] = array('4-31', '8-31', '12-31', '16-31');
1554 $MPEGaudioModeExtensionLookup['III'] = array('', 'IS', 'MS', 'IS+MS');
1555 return (isset($MPEGaudioModeExtensionLookup["$layer"]["$rawmodeextension"]) ? $MPEGaudioModeExtensionLookup["$layer"]["$rawmodeextension"] : FALSE);
1558 function MPEGaudioEmphasisLookup($rawemphasis) {
1559 $MPEGaudioEmphasisLookup = array('none', '50/15ms', FALSE, 'CCIT J.17');
1560 return (isset($MPEGaudioEmphasisLookup["$rawemphasis"]) ? $MPEGaudioEmphasisLookup["$rawemphasis"] : FALSE);
1563 function MPEGaudioCRCLookup($CRCbit) {
1564 // inverse boolean cast :)
1565 if ($CRCbit == '0') {
1572 /////////////////////////////////////////////////////////////////
1573 /// getID3() by James Heinrich <info@getid3.org> //
1574 // available at http://getid3.sourceforge.net ///
1575 // or http://www.getid3.org ///
1576 /////////////////////////////////////////////////////////////////
1578 // getid3.mp3.php - part of getID3() //
1579 // See getid3.readme.txt for more details //
1581 /////////////////////////////////////////////////////////////////
1583 // number of frames to scan to determine if MPEG-audio sequence is valid
1584 // Lower this number to 5-20 for faster scanning
1585 // Increase this number to 50+ for most accurate detection of valid VBR/CBR
1586 // mpeg-audio streams
1587 define('MPEG_VALID_CHECK_FRAMES', 35);
1589 function getMP3headerFilepointer(&$fd, &$ThisFileInfo) {
1591 getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $ThisFileInfo['avdataoffset']);
1593 if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode'])) {
1594 $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']);
1597 if (((isset($ThisFileInfo['id3v2']) && ($ThisFileInfo['avdataoffset'] > $ThisFileInfo['id3v2']['headerlength'])) || (!isset($ThisFileInfo['id3v2']) && ($ThisFileInfo['avdataoffset'] > 0)))) {
1599 $ThisFileInfo['warning'] .= "\n".'Unknown data before synch ';
1600 if (isset($ThisFileInfo['id3v2']['headerlength'])) {
1601 $ThisFileInfo['warning'] .= '(ID3v2 header ends at '.$ThisFileInfo['id3v2']['headerlength'].', then '.($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']).' bytes garbage, ';
1603 $ThisFileInfo['warning'] .= '(should be at beginning of file, ';
1605 $ThisFileInfo['warning'] .= 'synch detected at '.$ThisFileInfo['avdataoffset'].')';
1606 if ($ThisFileInfo['audio']['bitrate_mode'] == 'cbr') {
1607 if (!empty($ThisFileInfo['id3v2']['headerlength']) && (($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']) == $ThisFileInfo['mpeg']['audio']['framelength'])) {
1608 $ThisFileInfo['warning'] .= '. This is a known problem with some versions of LAME (3.91, 3.92) DLL in CBR mode.';
1609 $ThisFileInfo['audio']['codec'] = 'LAME';
1610 } elseif (empty($ThisFileInfo['id3v2']['headerlength']) && ($ThisFileInfo['avdataoffset'] == $ThisFileInfo['mpeg']['audio']['framelength'])) {
1611 $ThisFileInfo['warning'] .= '. This is a known problem with some versions of LAME (3.91, 3.92) DLL in CBR mode.';
1612 $ThisFileInfo['audio']['codec'] = 'LAME';
1618 if (isset($ThisFileInfo['mpeg']['audio']['layer']) && ($ThisFileInfo['mpeg']['audio']['layer'] == 'II')) {
1619 $ThisFileInfo['audio']['dataformat'] = 'mp2';
1620 } elseif (isset($ThisFileInfo['mpeg']['audio']['layer']) && ($ThisFileInfo['mpeg']['audio']['layer'] == 'I')) {
1621 $ThisFileInfo['audio']['dataformat'] = 'mp1';
1623 if ($ThisFileInfo['fileformat'] == 'mp3') {
1624 switch ($ThisFileInfo['audio']['dataformat']) {
1628 $ThisFileInfo['fileformat'] = $ThisFileInfo['audio']['dataformat'];
1632 $ThisFileInfo['warning'] .= "\n".'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$ThisFileInfo['audio']['dataformat'].'"';
1637 if (empty($ThisFileInfo['fileformat'])) {
1638 $ThisFileInfo['error'] .= "\n".'Synch not found';
1639 unset($ThisFileInfo['fileformat']);
1640 unset($ThisFileInfo['audio']['bitrate_mode']);
1641 unset($ThisFileInfo['avdataoffset']);
1642 unset($ThisFileInfo['avdataend']);
1646 $ThisFileInfo['mime_type'] = 'audio/mpeg';
1647 $ThisFileInfo['audio']['lossless'] = false;
1649 // Calculate playtime
1650 if (!isset($ThisFileInfo['playtime_seconds']) && isset($ThisFileInfo['audio']['bitrate']) && ($ThisFileInfo['audio']['bitrate'] > 0)) {
1651 $ThisFileInfo['playtime_seconds'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 / $ThisFileInfo['audio']['bitrate'];
1654 if (isset($ThisFileInfo['mpeg']['audio']['LAME'])) {
1655 $ThisFileInfo['audio']['codec'] = 'LAME';
1656 if (!empty($ThisFileInfo['mpeg']['audio']['LAME']['long_version'])) {
1657 $ThisFileInfo['audio']['encoder'] = trim($ThisFileInfo['mpeg']['audio']['LAME']['long_version']);
1665 function decodeMPEGaudioHeader($fd, $offset, &$ThisFileInfo, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) {
1667 static $MPEGaudioVersionLookup;
1668 static $MPEGaudioLayerLookup;
1669 static $MPEGaudioBitrateLookup;
1670 static $MPEGaudioFrequencyLookup;
1671 static $MPEGaudioChannelModeLookup;
1672 static $MPEGaudioModeExtensionLookup;
1673 static $MPEGaudioEmphasisLookup;
1674 if (empty($MPEGaudioVersionLookup)) {
1675 $MPEGaudioVersionLookup = MPEGaudioVersionArray();
1676 $MPEGaudioLayerLookup = MPEGaudioLayerArray();
1677 $MPEGaudioBitrateLookup = MPEGaudioBitrateArray();
1678 $MPEGaudioFrequencyLookup = MPEGaudioFrequencyArray();
1679 $MPEGaudioChannelModeLookup = MPEGaudioChannelModeArray();
1680 $MPEGaudioModeExtensionLookup = MPEGaudioModeExtensionArray();
1681 $MPEGaudioEmphasisLookup = MPEGaudioEmphasisArray();
1684 if ($offset >= $ThisFileInfo['avdataend']) {
1685 $ThisFileInfo['error'] .= "\n".'end of file encounter looking for MPEG synch';
1688 fseek($fd, $offset, SEEK_SET);
1689 $headerstring = fread($fd, 1441); // worse-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame
1691 // MP3 audio frame structure:
1692 // $aa $aa $aa $aa [$bb $bb] $cc...
1693 // where $aa..$aa is the four-byte mpeg-audio header (below)
1694 // $bb $bb is the optional 2-byte CRC
1695 // and $cc... is the audio data
1697 $head4 = substr($headerstring, 0, 4);
1699 static $MPEGaudioHeaderDecodeCache = array();
1700 if (isset($MPEGaudioHeaderDecodeCache[$head4])) {
1701 $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4];
1703 $MPEGheaderRawArray = MPEGaudioHeaderDecode($head4);
1704 $MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray;
1707 static $MPEGaudioHeaderValidCache = array();
1710 if (!isset($MPEGaudioHeaderValidCache[$head4])) {
1711 $MPEGaudioHeaderValidCache[$head4] = MPEGaudioHeaderValid($MPEGheaderRawArray);
1714 if ($MPEGaudioHeaderValidCache[$head4]) {
1715 $ThisFileInfo['mpeg']['audio']['raw'] = $MPEGheaderRawArray;
1717 $ThisFileInfo['error'] .= "\n".'Invalid MPEG audio header at offset '.$offset;
1721 if (!$FastMPEGheaderScan) {
1723 $ThisFileInfo['mpeg']['audio']['version'] = $MPEGaudioVersionLookup[$ThisFileInfo['mpeg']['audio']['raw']['version']];
1724 $ThisFileInfo['mpeg']['audio']['layer'] = $MPEGaudioLayerLookup[$ThisFileInfo['mpeg']['audio']['raw']['layer']];
1726 $ThisFileInfo['mpeg']['audio']['channelmode'] = $MPEGaudioChannelModeLookup[$ThisFileInfo['mpeg']['audio']['raw']['channelmode']];
1727 $ThisFileInfo['mpeg']['audio']['channels'] = (($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') ? 1 : 2);
1728 $ThisFileInfo['mpeg']['audio']['sample_rate'] = $MPEGaudioFrequencyLookup[$ThisFileInfo['mpeg']['audio']['version']][$ThisFileInfo['mpeg']['audio']['raw']['sample_rate']];
1729 $ThisFileInfo['mpeg']['audio']['protection'] = !$ThisFileInfo['mpeg']['audio']['raw']['protection'];
1730 $ThisFileInfo['mpeg']['audio']['private'] = (bool) $ThisFileInfo['mpeg']['audio']['raw']['private'];
1731 $ThisFileInfo['mpeg']['audio']['modeextension'] = $MPEGaudioModeExtensionLookup[$ThisFileInfo['mpeg']['audio']['layer']][$ThisFileInfo['mpeg']['audio']['raw']['modeextension']];
1732 $ThisFileInfo['mpeg']['audio']['copyright'] = (bool) $ThisFileInfo['mpeg']['audio']['raw']['copyright'];
1733 $ThisFileInfo['mpeg']['audio']['original'] = (bool) $ThisFileInfo['mpeg']['audio']['raw']['original'];
1734 $ThisFileInfo['mpeg']['audio']['emphasis'] = $MPEGaudioEmphasisLookup[$ThisFileInfo['mpeg']['audio']['raw']['emphasis']];
1736 $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels'];
1737 $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate'];
1739 if ($ThisFileInfo['mpeg']['audio']['protection']) {
1740 $ThisFileInfo['mpeg']['audio']['crc'] = BigEndian2Int(substr($headerstring, 4, 2));
1745 if ($ThisFileInfo['mpeg']['audio']['raw']['bitrate'] == 15) {
1746 // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0
1747 $ThisFileInfo['warning'] .= "\n".'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1';
1748 $ThisFileInfo['mpeg']['audio']['raw']['bitrate'] = 0;
1750 $ThisFileInfo['mpeg']['audio']['padding'] = (bool) $ThisFileInfo['mpeg']['audio']['raw']['padding'];
1751 $ThisFileInfo['mpeg']['audio']['bitrate'] = $MPEGaudioBitrateLookup[$ThisFileInfo['mpeg']['audio']['version']][$ThisFileInfo['mpeg']['audio']['layer']][$ThisFileInfo['mpeg']['audio']['raw']['bitrate']];
1753 if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') && ($offset == $ThisFileInfo['avdataoffset'])) {
1754 // only skip multiple frame check if free-format bitstream found at beginning of file
1755 // otherwise is quite possibly simply corrupted data
1756 $recursivesearch = false;
1759 // For Layer II there are some combinations of bitrate and mode which are not allowed.
1760 if (!$FastMPEGheaderScan && ($ThisFileInfo['mpeg']['audio']['layer'] == 'II')) {
1762 $ThisFileInfo['audio']['dataformat'] = 'mp2';
1763 switch ($ThisFileInfo['mpeg']['audio']['channelmode']) {
1766 if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') || ($ThisFileInfo['mpeg']['audio']['bitrate'] <= 192)) {
1769 $ThisFileInfo['error'] .= "\n".$ThisFileInfo['mpeg']['audio']['bitrate'].'kbps not allowed in Layer II, '.$ThisFileInfo['mpeg']['audio']['channelmode'].'.';
1775 case 'joint stereo':
1776 case 'dual channel':
1777 if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') || ($ThisFileInfo['mpeg']['audio']['bitrate'] == 64) || ($ThisFileInfo['mpeg']['audio']['bitrate'] >= 96)) {
1780 $ThisFileInfo['error'] .= "\n".$ThisFileInfo['mpeg']['audio']['bitrate'].'kbps not allowed in Layer II, '.$ThisFileInfo['mpeg']['audio']['channelmode'].'.';
1790 if ($ThisFileInfo['audio']['sample_rate'] > 0) {
1791 $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']);
1794 if ($ThisFileInfo['mpeg']['audio']['bitrate'] != 'free') {
1796 $ThisFileInfo['audio']['bitrate'] = 1000 * $ThisFileInfo['mpeg']['audio']['bitrate'];
1798 if (isset($ThisFileInfo['mpeg']['audio']['framelength'])) {
1799 $nextframetestoffset = $offset + $ThisFileInfo['mpeg']['audio']['framelength'];
1801 $ThisFileInfo['error'] .= "\n".'Frame at offset('.$offset.') is has an invalid frame length.';
1807 $ExpectedNumberOfAudioBytes = 0;
1809 ////////////////////////////////////////////////////////////////////////////////////
1810 // Variable-bitrate headers
1812 if (substr($headerstring, 4 + 32, 4) == 'VBRI') {
1813 // Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36)
1814 // specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html
1816 $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr';
1817 $ThisFileInfo['mpeg']['audio']['VBR_method'] = 'Fraunhofer';
1818 $ThisFileInfo['audio']['codec'] = 'Fraunhofer';
1820 $SideInfoData = substr($headerstring, 4 + 2, 32);
1822 $FraunhoferVBROffset = 36;
1824 $ThisFileInfo['mpeg']['audio']['VBR_encoder_version'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 4, 2));
1825 $ThisFileInfo['mpeg']['audio']['VBR_encoder_delay'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 6, 2));
1826 $ThisFileInfo['mpeg']['audio']['VBR_quality'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 8, 2));
1827 $ThisFileInfo['mpeg']['audio']['VBR_bytes'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4));
1828 $ThisFileInfo['mpeg']['audio']['VBR_frames'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4));
1829 $ThisFileInfo['mpeg']['audio']['VBR_seek_offsets'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2));
1830 //$ThisFileInfo['mpeg']['audio']['reserved'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 4)); // hardcoded $00 $01 $00 $02 - purpose unknown
1831 $ThisFileInfo['mpeg']['audio']['VBR_seek_offsets_stride'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2));
1833 $ExpectedNumberOfAudioBytes = $ThisFileInfo['mpeg']['audio']['VBR_bytes'];
1835 $previousbyteoffset = $offset;
1836 for ($i = 0; $i < $ThisFileInfo['mpeg']['audio']['VBR_seek_offsets']; $i++) {
1837 $Fraunhofer_OffsetN = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, 2));
1838 $FraunhoferVBROffset += 2;
1839 $ThisFileInfo['mpeg']['audio']['VBR_offsets_relative'][$i] = $Fraunhofer_OffsetN;
1840 $ThisFileInfo['mpeg']['audio']['VBR_offsets_absolute'][$i] = $Fraunhofer_OffsetN + $previousbyteoffset;
1841 $previousbyteoffset += $Fraunhofer_OffsetN;
1847 // Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36)
1848 // depending on MPEG layer and number of channels
1850 if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
1851 if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') {
1853 $VBRidOffset = 4 + 17; // 0x15
1854 $SideInfoData = substr($headerstring, 4 + 2, 17);
1856 // MPEG-1 (stereo, joint-stereo, dual-channel)
1857 $VBRidOffset = 4 + 32; // 0x24
1858 $SideInfoData = substr($headerstring, 4 + 2, 32);
1860 } else { // 2 or 2.5
1861 if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') {
1862 // MPEG-2, MPEG-2.5 (mono)
1863 $VBRidOffset = 4 + 9; // 0x0D
1864 $SideInfoData = substr($headerstring, 4 + 2, 9);
1866 // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel)
1867 $VBRidOffset = 4 + 17; // 0x15
1868 $SideInfoData = substr($headerstring, 4 + 2, 17);
1872 if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) {
1873 // 'Xing' is traditional Xing VBR frame
1874 // 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.)
1876 $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr';
1877 $ThisFileInfo['mpeg']['audio']['VBR_method'] = 'Xing';
1879 $ThisFileInfo['mpeg']['audio']['xing_flags_raw'] = BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4));
1881 $ThisFileInfo['mpeg']['audio']['xing_flags']['frames'] = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000001);
1882 $ThisFileInfo['mpeg']['audio']['xing_flags']['bytes'] = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000002);
1883 $ThisFileInfo['mpeg']['audio']['xing_flags']['toc'] = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000004);
1884 $ThisFileInfo['mpeg']['audio']['xing_flags']['vbr_scale'] = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000008);
1886 if ($ThisFileInfo['mpeg']['audio']['xing_flags']['frames']) {
1887 $ThisFileInfo['mpeg']['audio']['VBR_frames'] = BigEndian2Int(substr($headerstring, $VBRidOffset + 8, 4));
1889 if ($ThisFileInfo['mpeg']['audio']['xing_flags']['bytes']) {
1890 $ThisFileInfo['mpeg']['audio']['VBR_bytes'] = BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4));
1893 if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') && !empty($ThisFileInfo['mpeg']['audio']['VBR_frames']) && !empty($ThisFileInfo['mpeg']['audio']['VBR_bytes'])) {
1894 $framelengthfloat = $ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames'];
1895 if ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') {
1896 // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
1897 $ThisFileInfo['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 12;
1899 // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
1900 $ThisFileInfo['audio']['bitrate'] = (($framelengthfloat - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 144;
1902 $ThisFileInfo['mpeg']['audio']['framelength'] = floor($framelengthfloat);
1905 if ($ThisFileInfo['mpeg']['audio']['xing_flags']['toc']) {
1906 $LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100);
1907 for ($i = 0; $i < 100; $i++) {
1908 $ThisFileInfo['mpeg']['audio']['toc'][$i] = ord($LAMEtocData{$i});
1911 if ($ThisFileInfo['mpeg']['audio']['xing_flags']['vbr_scale']) {
1912 $ThisFileInfo['mpeg']['audio']['VBR_scale'] = BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4));
1915 // http://gabriel.mp3-tech.org/mp3infotag.html
1916 if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') {
1917 $ThisFileInfo['mpeg']['audio']['LAME']['long_version'] = substr($headerstring, $VBRidOffset + 120, 20);
1918 $ThisFileInfo['mpeg']['audio']['LAME']['short_version'] = substr($ThisFileInfo['mpeg']['audio']['LAME']['long_version'], 0, 9);
1919 $ThisFileInfo['mpeg']['audio']['LAME']['long_version'] = rtrim($ThisFileInfo['mpeg']['audio']['LAME']['long_version'], "\x55\xAA");
1921 if ($ThisFileInfo['mpeg']['audio']['LAME']['short_version'] >= 'LAME3.90.') {
1923 // It the LAME tag was only introduced in LAME v3.90
1924 // http://www.hydrogenaudio.org/?act=ST&f=15&t=9933
1926 // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
1927 // are assuming a 'Xing' identifier offset of 0x24, which is the case for
1928 // MPEG-1 non-mono, but not for other combinations
1929 $LAMEtagOffsetContant = $VBRidOffset - 0x24;
1931 // byte $9B VBR Quality
1932 // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
1933 // Actually overwrites original Xing bytes
1934 unset($ThisFileInfo['mpeg']['audio']['VBR_scale']);
1935 $ThisFileInfo['mpeg']['audio']['LAME']['vbr_quality'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));
1937 // bytes $9C-$A4 Encoder short VersionString
1938 $ThisFileInfo['mpeg']['audio']['LAME']['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);
1939 $ThisFileInfo['mpeg']['audio']['LAME']['long_version'] = $ThisFileInfo['mpeg']['audio']['LAME']['short_version'];
1941 // byte $A5 Info Tag revision + VBR method
1942 $LAMEtagRevisionVBRmethod = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));
1944 $ThisFileInfo['mpeg']['audio']['LAME']['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
1945 $ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F;
1946 $ThisFileInfo['mpeg']['audio']['LAME']['vbr_method'] = LAMEvbrMethodLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method']);
1948 // byte $A6 Lowpass filter value
1949 $ThisFileInfo['mpeg']['audio']['LAME']['lowpass_frequency'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;
1951 // bytes $A7-$AE Replay Gain
1952 // http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
1953 // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
1954 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] = BigEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
1955 $ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
1956 $ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));
1958 if ($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] == 0) {
1959 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] = false;
1962 if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] != 0) {
1963 require_once(GETID3_INCLUDEPATH.'getid3.rgad.php');
1965 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['name'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0xE000) >> 13;
1966 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['originator'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0x1C00) >> 10;
1967 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['sign_bit'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0x0200) >> 9;
1968 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['gain_adjust'] = $ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0x01FF;
1969 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['name'] = RGADnameLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['name']);
1970 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['originator'] = RGADoriginatorLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['originator']);
1971 $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']);
1973 if ($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] !== false) {
1974 $ThisFileInfo['replay_gain']['radio']['peak'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'];
1976 $ThisFileInfo['replay_gain']['radio']['originator'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['originator'];
1977 $ThisFileInfo['replay_gain']['radio']['adjustment'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['gain_db'];
1979 if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] != 0) {
1980 require_once(GETID3_INCLUDEPATH.'getid3.rgad.php');
1982 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['name'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0xE000) >> 13;
1983 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['originator'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0x1C00) >> 10;
1984 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['sign_bit'] = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0x0200) >> 9;
1985 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['gain_adjust'] = $ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0x01FF;
1986 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['name'] = RGADnameLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['name']);
1987 $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['originator'] = RGADoriginatorLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['originator']);
1988 $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']);
1990 if ($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] !== false) {
1991 $ThisFileInfo['replay_gain']['audiophile']['peak'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'];
1993 $ThisFileInfo['replay_gain']['audiophile']['originator'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['originator'];
1994 $ThisFileInfo['replay_gain']['audiophile']['adjustment'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['gain_db'];
1998 // byte $AF Encoding flags + ATH Type
1999 $EncodingFlagsATHtype = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
2000 $ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10);
2001 $ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
2002 $ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40);
2003 $ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80);
2004 $ThisFileInfo['mpeg']['audio']['LAME']['ath_type'] = $EncodingFlagsATHtype & 0x0F;
2006 // byte $B0 if ABR {specified bitrate} else {minimal bitrate}
2007 $ABRbitrateMinBitrate = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
2008 if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method'] == 2) { // Average BitRate (ABR)
2009 $ThisFileInfo['mpeg']['audio']['LAME']['bitrate_abr'] = $ABRbitrateMinBitrate;
2010 } elseif ($ABRbitrateMinBitrate > 0) { // Variable BitRate (VBR) - minimum bitrate
2011 $ThisFileInfo['mpeg']['audio']['LAME']['bitrate_min'] = $ABRbitrateMinBitrate;
2014 // bytes $B1-$B3 Encoder delays
2015 $EncoderDelays = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
2016 $ThisFileInfo['mpeg']['audio']['LAME']['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
2017 $ThisFileInfo['mpeg']['audio']['LAME']['end_padding'] = $EncoderDelays & 0x000FFF;
2020 $MiscByte = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
2021 $ThisFileInfo['mpeg']['audio']['LAME']['raw']['noise_shaping'] = ($MiscByte & 0x03);
2022 $ThisFileInfo['mpeg']['audio']['LAME']['raw']['stereo_mode'] = ($MiscByte & 0x1C) >> 2;
2023 $ThisFileInfo['mpeg']['audio']['LAME']['raw']['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
2024 $ThisFileInfo['mpeg']['audio']['LAME']['raw']['source_sample_freq'] = ($MiscByte & 0xC0) >> 6;
2025 $ThisFileInfo['mpeg']['audio']['LAME']['noise_shaping'] = $ThisFileInfo['mpeg']['audio']['LAME']['raw']['noise_shaping'];
2026 $ThisFileInfo['mpeg']['audio']['LAME']['stereo_mode'] = LAMEmiscStereoModeLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['stereo_mode']);
2027 $ThisFileInfo['mpeg']['audio']['LAME']['not_optimal_quality'] = (bool) $ThisFileInfo['mpeg']['audio']['LAME']['raw']['not_optimal_quality'];
2028 $ThisFileInfo['mpeg']['audio']['LAME']['source_sample_freq'] = LAMEmiscSourceSampleFrequencyLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['source_sample_freq']);
2030 // byte $B5 MP3 Gain
2031 $ThisFileInfo['mpeg']['audio']['LAME']['raw']['mp3_gain'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
2032 $ThisFileInfo['mpeg']['audio']['LAME']['mp3_gain_db'] = 1.5 * $ThisFileInfo['mpeg']['audio']['LAME']['raw']['mp3_gain'];
2033 $ThisFileInfo['mpeg']['audio']['LAME']['mp3_gain_factor'] = pow(2, ($ThisFileInfo['mpeg']['audio']['LAME']['mp3_gain_db'] / 6));
2035 // bytes $B6-$B7 Preset and surround info
2036 $PresetSurroundBytes = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
2037 // Reserved = ($PresetSurroundBytes & 0xC000);
2038 $ThisFileInfo['mpeg']['audio']['LAME']['raw']['surround_info'] = ($PresetSurroundBytes & 0x3800);
2039 $ThisFileInfo['mpeg']['audio']['LAME']['surround_info'] = LAMEsurroundInfoLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['surround_info']);
2040 $ThisFileInfo['mpeg']['audio']['LAME']['preset_used_id'] = ($PresetSurroundBytes & 0x07FF);
2042 // bytes $B8-$BB MusicLength
2043 $ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
2044 $ExpectedNumberOfAudioBytes = (($ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'] > 0) ? $ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'] : $ThisFileInfo['mpeg']['audio']['VBR_bytes']);
2046 // bytes $BC-$BD MusicCRC
2047 $ThisFileInfo['mpeg']['audio']['LAME']['music_crc'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));
2049 // bytes $BE-$BF CRC-16 of Info Tag
2050 $ThisFileInfo['mpeg']['audio']['LAME']['lame_tag_crc'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));
2054 if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method'] == 1) {
2056 $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr';
2057 if (empty($ThisFileInfo['mpeg']['audio']['bitrate']) || ($ThisFileInfo['mpeg']['audio']['LAME']['bitrate_min'] != 255)) {
2058 $ThisFileInfo['mpeg']['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['LAME']['bitrate_min'];
2068 // not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header)
2069 $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr';
2070 if ($recursivesearch) {
2071 $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr';
2072 if (RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, true)) {
2073 $recursivesearch = false;
2074 $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr';
2076 if ($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') {
2077 $ThisFileInfo['warning'] .= "\n".'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.';
2085 if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']))) {
2086 if (($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) == 1) {
2087 $ThisFileInfo['warning'] .= "\n".'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)';
2088 } elseif ($ExpectedNumberOfAudioBytes > ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) {
2089 $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)';
2091 $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)';
2095 if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') && empty($ThisFileInfo['audio']['bitrate'])) {
2096 if (($offset == $ThisFileInfo['avdataoffset']) && empty($ThisFileInfo['mpeg']['audio']['VBR_frames'])) {
2097 $framebytelength = FreeFormatFrameLength($fd, $offset, $ThisFileInfo, true);
2098 if ($framebytelength > 0) {
2099 $ThisFileInfo['mpeg']['audio']['framelength'] = $framebytelength;
2100 if ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') {
2101 // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
2102 $ThisFileInfo['audio']['bitrate'] = ((($framebytelength / 4) - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 12;
2104 // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
2105 $ThisFileInfo['audio']['bitrate'] = (($framebytelength - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 144;
2108 $ThisFileInfo['error'] .= "\n".'Error calculating frame length of free-format MP3 without Xing/LAME header';
2113 if (($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') && isset($ThisFileInfo['mpeg']['audio']['VBR_frames']) && ($ThisFileInfo['mpeg']['audio']['VBR_frames'] > 1)) {
2114 $ThisFileInfo['mpeg']['audio']['VBR_frames']--; // don't count the Xing / VBRI frame
2115 if (($ThisFileInfo['mpeg']['audio']['version'] == '1') && ($ThisFileInfo['mpeg']['audio']['layer'] == 'I')) {
2116 $ThisFileInfo['mpeg']['audio']['VBR_bitrate'] = ((($ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 384)) / 1000;
2117 } elseif ((($ThisFileInfo['mpeg']['audio']['version'] == '2') || ($ThisFileInfo['mpeg']['audio']['version'] == '2.5')) && ($ThisFileInfo['mpeg']['audio']['layer'] == 'III')) {
2118 $ThisFileInfo['mpeg']['audio']['VBR_bitrate'] = ((($ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 576)) / 1000;
2120 $ThisFileInfo['mpeg']['audio']['VBR_bitrate'] = ((($ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 1152)) / 1000;
2122 if ($ThisFileInfo['mpeg']['audio']['VBR_bitrate'] > 0) {
2123 $ThisFileInfo['audio']['bitrate'] = 1000 * $ThisFileInfo['mpeg']['audio']['VBR_bitrate'];
2124 $ThisFileInfo['mpeg']['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['VBR_bitrate']; // to avoid confusion
2128 // End variable-bitrate headers
2129 ////////////////////////////////////////////////////////////////////////////////////
2131 if ($recursivesearch) {
2133 if (!RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, $ScanAsCBR)) {
2141 // // experimental side info parsing section - not returning anything useful yet
2143 // $SideInfoBitstream = BigEndian2Bin($SideInfoData);
2144 // $SideInfoOffset = 0;
2146 // if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
2147 // if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') {
2149 // $ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
2150 // $SideInfoOffset += 9;
2151 // $SideInfoOffset += 5;
2153 // // MPEG-1 (stereo, joint-stereo, dual-channel)
2154 // $ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
2155 // $SideInfoOffset += 9;
2156 // $SideInfoOffset += 3;
2158 // } else { // 2 or 2.5
2159 // if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') {
2160 // // MPEG-2, MPEG-2.5 (mono)
2161 // $ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
2162 // $SideInfoOffset += 8;
2163 // $SideInfoOffset += 1;
2165 // // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel)
2166 // $ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
2167 // $SideInfoOffset += 8;
2168 // $SideInfoOffset += 2;
2172 // if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
2173 // for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) {
2174 // for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) {
2175 // $ThisFileInfo['mpeg']['audio']['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2176 // $SideInfoOffset += 2;
2180 // for ($granule = 0; $granule < (($ThisFileInfo['mpeg']['audio']['version'] == '1') ? 2 : 1); $granule++) {
2181 // for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) {
2182 // $ThisFileInfo['mpeg']['audio']['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12);
2183 // $SideInfoOffset += 12;
2184 // $ThisFileInfo['mpeg']['audio']['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
2185 // $SideInfoOffset += 9;
2186 // $ThisFileInfo['mpeg']['audio']['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8);
2187 // $SideInfoOffset += 8;
2188 // if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
2189 // $ThisFileInfo['mpeg']['audio']['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
2190 // $SideInfoOffset += 4;
2192 // $ThisFileInfo['mpeg']['audio']['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
2193 // $SideInfoOffset += 9;
2195 // $ThisFileInfo['mpeg']['audio']['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2196 // $SideInfoOffset += 1;
2198 // if ($ThisFileInfo['mpeg']['audio']['window_switching_flag'][$granule][$channel] == '1') {
2200 // $ThisFileInfo['mpeg']['audio']['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2);
2201 // $SideInfoOffset += 2;
2202 // $ThisFileInfo['mpeg']['audio']['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2203 // $SideInfoOffset += 1;
2205 // for ($region = 0; $region < 2; $region++) {
2206 // $ThisFileInfo['mpeg']['audio']['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
2207 // $SideInfoOffset += 5;
2209 // $ThisFileInfo['mpeg']['audio']['table_select'][$granule][$channel][2] = 0;
2211 // for ($window = 0; $window < 3; $window++) {
2212 // $ThisFileInfo['mpeg']['audio']['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3);
2213 // $SideInfoOffset += 3;
2218 // for ($region = 0; $region < 3; $region++) {
2219 // $ThisFileInfo['mpeg']['audio']['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
2220 // $SideInfoOffset += 5;
2223 // $ThisFileInfo['mpeg']['audio']['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
2224 // $SideInfoOffset += 4;
2225 // $ThisFileInfo['mpeg']['audio']['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3);
2226 // $SideInfoOffset += 3;
2227 // $ThisFileInfo['mpeg']['audio']['block_type'][$granule][$channel] = 0;
2230 // if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
2231 // $ThisFileInfo['mpeg']['audio']['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2232 // $SideInfoOffset += 1;
2234 // $ThisFileInfo['mpeg']['audio']['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2235 // $SideInfoOffset += 1;
2236 // $ThisFileInfo['mpeg']['audio']['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2237 // $SideInfoOffset += 1;
2245 function RecursiveFrameScanning(&$fd, &$ThisFileInfo, &$offset, &$nextframetestoffset, $ScanAsCBR) {
2246 for ($i = 0; $i < MPEG_VALID_CHECK_FRAMES; $i++) {
2247 // check next MPEG_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch
2248 if (($nextframetestoffset + 4) >= $ThisFileInfo['avdataend']) {
2253 $nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']);
2254 if (decodeMPEGaudioHeader($fd, $nextframetestoffset, $nextframetestarray, false)) {
2256 // force CBR mode, used for trying to pick out invalid audio streams with
2257 // valid(?) VBR headers, or VBR streams with no VBR header
2258 if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($ThisFileInfo['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $ThisFileInfo['mpeg']['audio']['bitrate'])) {
2264 // next frame is OK, get ready to check the one after that
2265 if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) {
2266 $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength'];
2268 $ThisFileInfo['error'] .= "\n".'Frame at offset ('.$offset.') is has an invalid frame length.';
2274 // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence
2275 $ThisFileInfo['error'] .= "\n".'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.';
2283 function FreeFormatFrameLength($fd, $offset, &$ThisFileInfo, $deepscan=false) {
2284 fseek($fd, $offset, SEEK_SET);
2285 $MPEGaudioData = fread($fd, 32768);
2287 $SyncPattern1 = substr($MPEGaudioData, 0, 4);
2288 // may be different pattern due to padding
2289 $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) | 0x02).$SyncPattern1{3};
2290 if ($SyncPattern2 === $SyncPattern1) {
2291 $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) & 0xFD).$SyncPattern1{3};
2294 $framelength = false;
2295 $framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4);
2296 $framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4);
2297 if ($framelength1 > 4) {
2298 $framelength = $framelength1;
2300 if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
2301 $framelength = $framelength2;
2303 if (!$framelength) {
2305 // LAME 3.88 has a different value for modeextension on the first frame vs the rest
2306 $framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4);
2307 $framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4);
2309 if ($framelength1 > 4) {
2310 $framelength = $framelength1;
2312 if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
2313 $framelength = $framelength2;
2315 if (!$framelength) {
2316 $ThisFileInfo['error'] .= "\n".'Cannot find next free-format synch pattern ('.PrintHexBytes($SyncPattern1).' or '.PrintHexBytes($SyncPattern2).') after offset '.$offset;
2319 $ThisFileInfo['warning'] .= "\n".'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)';
2320 $ThisFileInfo['audio']['codec'] = 'LAME';
2321 $ThisFileInfo['audio']['encoder'] = 'LAME3.88';
2322 $SyncPattern1 = substr($SyncPattern1, 0, 3);
2323 $SyncPattern2 = substr($SyncPattern2, 0, 3);
2329 $ActualFrameLengthValues = array();
2330 $nextoffset = $offset + $framelength;
2331 while ($nextoffset < ($ThisFileInfo['avdataend'] - 6)) {
2332 fseek($fd, $nextoffset - 1, SEEK_SET);
2333 $NextSyncPattern = fread($fd, 6);
2334 if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) {
2335 // good - found where expected
2336 $ActualFrameLengthValues[] = $framelength;
2337 } elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) {
2338 // ok - found one byte earlier than expected (last frame wasn't padded, first frame was)
2339 $ActualFrameLengthValues[] = ($framelength - 1);
2341 } elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) {
2342 // ok - found one byte later than expected (last frame was padded, first frame wasn't)
2343 $ActualFrameLengthValues[] = ($framelength + 1);
2346 $ThisFileInfo['error'] .= "\n".'Did not find expected free-format sync pattern at offset '.$nextoffset;
2349 $nextoffset += $framelength;
2351 if (count($ActualFrameLengthValues) > 0) {
2352 $framelength = round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues));
2355 return $framelength;
2359 function getOnlyMPEGaudioInfo($fd, &$ThisFileInfo, $avdataoffset, $BitrateHistogram=false) {
2360 // looks for synch, decodes MPEG audio header
2362 fseek($fd, $avdataoffset, SEEK_SET);
2364 $SynchSeekOffset = 0;
2366 if (!defined('CONST_FF')) {
2367 define('CONST_FF', chr(0xFF));
2368 define('CONST_E0', chr(0xE0));
2371 static $MPEGaudioVersionLookup;
2372 static $MPEGaudioLayerLookup;
2373 static $MPEGaudioBitrateLookup;
2374 if (empty($MPEGaudioVersionLookup)) {
2375 $MPEGaudioVersionLookup = MPEGaudioVersionArray();
2376 $MPEGaudioLayerLookup = MPEGaudioLayerArray();
2377 $MPEGaudioBitrateLookup = MPEGaudioBitrateArray();
2381 $header_len = strlen($header) - round(32768 / 2);
2384 if (($SynchSeekOffset > $header_len) && (($avdataoffset + $SynchSeekOffset) < $ThisFileInfo['avdataend']) && !feof($fd)) {
2386 if ($SynchSeekOffset > 131072) {
2387 // if a synch's not found within the first 128k bytes, then give up
2388 $ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch within the first 131072 bytes';
2389 if (isset($ThisFileInfo['audio']['bitrate'])) {
2390 unset($ThisFileInfo['audio']['bitrate']);
2392 if (isset($ThisFileInfo['mpeg']['audio'])) {
2393 unset($ThisFileInfo['mpeg']['audio']);
2395 if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || (count($ThisFileInfo['mpeg']) == 0))) {
2396 unset($ThisFileInfo['mpeg']);
2400 } elseif ($header .= fread($fd, 32768)) {
2403 $header_len = strlen($header) - round(32768 / 2);
2407 $ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch before end of file';
2408 if (isset($ThisFileInfo['audio']['bitrate'])) {
2409 unset($ThisFileInfo['audio']['bitrate']);
2411 if (isset($ThisFileInfo['mpeg']['audio'])) {
2412 unset($ThisFileInfo['mpeg']['audio']);
2414 if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || (count($ThisFileInfo['mpeg']) == 0))) {
2415 unset($ThisFileInfo['mpeg']);
2422 if (($SynchSeekOffset + 1) >= strlen($header)) {
2423 $ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch before end of file';
2427 if (($header{$SynchSeekOffset} == CONST_FF) && ($header{($SynchSeekOffset + 1)} > CONST_E0)) { // synch detected
2429 if (!isset($FirstFrameThisfileInfo) && !isset($ThisFileInfo['mpeg']['audio'])) {
2430 $FirstFrameThisfileInfo = $ThisFileInfo;
2431 $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset;
2432 if (!decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $FirstFrameThisfileInfo, false)) {
2433 // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's
2434 // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below
2435 unset($FirstFrameThisfileInfo);
2438 $dummy = $ThisFileInfo; // only overwrite real data if valid header found
2440 if (decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $dummy, true)) {
2442 $ThisFileInfo = $dummy;
2443 $ThisFileInfo['avdataoffset'] = $avdataoffset + $SynchSeekOffset;
2444 switch ($ThisFileInfo['fileformat']) {
2449 $ThisFileInfo['fileformat'] = 'mp3';
2450 $ThisFileInfo['audio']['dataformat'] = 'mp3';
2452 if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) {
2453 if (!CloseMatch($ThisFileInfo['audio']['bitrate'], $FirstFrameThisfileInfo['audio']['bitrate'], 1)) {
2454 // If there is garbage data between a valid VBR header frame and a sequence
2455 // of valid MPEG-audio frames the VBR data is no longer discarded.
2456 $ThisFileInfo = $FirstFrameThisfileInfo;
2457 $ThisFileInfo['avdataoffset'] = $FirstFrameAVDataOffset;
2458 $ThisFileInfo['fileformat'] = 'mp3';
2459 $ThisFileInfo['audio']['dataformat'] = 'mp3';
2460 $dummy = $ThisFileInfo;
2461 unset($dummy['mpeg']['audio']);
2462 $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength'];
2463 $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset;
2464 if (decodeMPEGaudioHeader($fd, $GarbageOffsetEnd, $dummy, true, true)) {
2466 $ThisFileInfo = $dummy;
2467 $ThisFileInfo['avdataoffset'] = $GarbageOffsetEnd;
2468 $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;
2472 $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.')';
2478 if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode']) && ($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($ThisFileInfo['mpeg']['audio']['VBR_method'])) {
2479 // VBR file with no VBR header
2480 $BitrateHistogram = true;
2483 if ($BitrateHistogram) {
2485 $ThisFileInfo['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0);
2486 $ThisFileInfo['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0);
2488 if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
2489 if ($ThisFileInfo['mpeg']['audio']['layer'] == 'III') {
2490 $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('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);
2491 } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 'II') {
2492 $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('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);
2493 } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') {
2494 $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('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);
2496 } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') {
2497 $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('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);
2499 $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('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);
2502 $dummy = array('error'=>$ThisFileInfo['error'], 'warning'=>$ThisFileInfo['warning'], 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']);
2503 $synchstartoffset = $ThisFileInfo['avdataoffset'];
2506 while (decodeMPEGaudioHeader($fd, $synchstartoffset, $dummy, false, false, $FastMode)) {
2508 $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']];
2510 $ThisFileInfo['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]++;
2511 $ThisFileInfo['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]++;
2512 $ThisFileInfo['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]++;
2513 if (empty($dummy['mpeg']['audio']['framelength'])) {
2514 $ThisFileInfo['warning'] .= "\n".'Invalid/missing framelength in histogram analysis - aborting';
2515 $synchstartoffset += 4;
2518 $synchstartoffset += $dummy['mpeg']['audio']['framelength'];
2523 foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) {
2524 $framecounter += $bitratecount;
2525 if ($bitratevalue != 'free') {
2526 $bittotal += ($bitratevalue * $bitratecount);
2529 if ($framecounter == 0) {
2530 $ThisFileInfo['error'] .= "\n".'Corrupt MP3 file: framecounter == zero';
2533 $ThisFileInfo['mpeg']['audio']['frame_count'] = $framecounter;
2534 $ThisFileInfo['mpeg']['audio']['bitrate'] = 1000 * ($bittotal / $framecounter);
2536 $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate'];
2539 // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently
2540 $distinct_bitrates = 0;
2541 foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) {
2542 if ($bitrate_count > 0) {
2543 $distinct_bitrates++;
2546 if ($distinct_bitrates > 1) {
2547 $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr';
2549 $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr';
2551 $ThisFileInfo['audio']['bitrate_mode'] = $ThisFileInfo['mpeg']['audio']['bitrate_mode'];
2555 break; // exit while()
2560 if (($avdataoffset + $SynchSeekOffset) >= $ThisFileInfo['avdataend']) {
2563 if (empty($ThisFileInfo['mpeg']['audio'])) {
2565 $ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch before end of file';
2566 if (isset($ThisFileInfo['audio']['bitrate'])) {
2567 unset($ThisFileInfo['audio']['bitrate']);
2569 if (isset($ThisFileInfo['mpeg']['audio'])) {
2570 unset($ThisFileInfo['mpeg']['audio']);
2572 if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || empty($ThisFileInfo['mpeg']))) {
2573 unset($ThisFileInfo['mpeg']);
2582 $ThisFileInfo['audio']['bits_per_sample'] = 16;
2583 $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels'];
2584 $ThisFileInfo['audio']['channelmode'] = $ThisFileInfo['mpeg']['audio']['channelmode'];
2585 $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate'];
2590 function MPEGaudioVersionArray() {
2591 static $MPEGaudioVersion = array('2.5', false, '2', '1');
2592 return $MPEGaudioVersion;
2595 function MPEGaudioLayerArray() {
2596 static $MPEGaudioLayer = array(false, 'III', 'II', 'I');
2597 return $MPEGaudioLayer;
2600 function MPEGaudioBitrateArray() {
2601 static $MPEGaudioBitrate;
2602 if (empty($MPEGaudioBitrate)) {
2603 $MPEGaudioBitrate['1']['I'] = array('free', 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448);
2604 $MPEGaudioBitrate['1']['II'] = array('free', 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384);
2605 $MPEGaudioBitrate['1']['III'] = array('free', 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320);
2606 $MPEGaudioBitrate['2']['I'] = array('free', 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256);
2607 $MPEGaudioBitrate['2']['II'] = array('free', 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160);
2608 $MPEGaudioBitrate['2']['III'] = $MPEGaudioBitrate['2']['II'];
2609 $MPEGaudioBitrate['2.5']['I'] = $MPEGaudioBitrate['2']['I'];
2610 $MPEGaudioBitrate['2.5']['II'] = $MPEGaudioBitrate['2']['II'];
2611 $MPEGaudioBitrate['2.5']['III'] = $MPEGaudioBitrate['2']['III'];
2613 return $MPEGaudioBitrate;
2616 function MPEGaudioFrequencyArray() {
2617 static $MPEGaudioFrequency;
2618 if (empty($MPEGaudioFrequency)) {
2619 $MPEGaudioFrequency['1'] = array(44100, 48000, 32000);
2620 $MPEGaudioFrequency['2'] = array(22050, 24000, 16000);
2621 $MPEGaudioFrequency['2.5'] = array(11025, 12000, 8000);
2623 return $MPEGaudioFrequency;
2626 function MPEGaudioChannelModeArray() {
2627 static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono');
2628 return $MPEGaudioChannelMode;
2631 function MPEGaudioModeExtensionArray() {
2632 static $MPEGaudioModeExtension;
2633 if (empty($MPEGaudioModeExtension)) {
2634 $MPEGaudioModeExtension['I'] = array('4-31', '8-31', '12-31', '16-31');
2635 $MPEGaudioModeExtension['II'] = array('4-31', '8-31', '12-31', '16-31');
2636 $MPEGaudioModeExtension['III'] = array('', 'IS', 'MS', 'IS+MS');
2638 return $MPEGaudioModeExtension;
2641 function MPEGaudioEmphasisArray() {
2642 static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17');
2643 return $MPEGaudioEmphasis;
2647 function MPEGaudioHeaderBytesValid($head4) {
2648 return MPEGaudioHeaderValid(MPEGaudioHeaderDecode($head4));
2651 function MPEGaudioHeaderValid($rawarray, $echoerrors=false) {
2653 if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) {
2657 static $MPEGaudioVersionLookup;
2658 static $MPEGaudioLayerLookup;
2659 static $MPEGaudioBitrateLookup;
2660 static $MPEGaudioFrequencyLookup;
2661 static $MPEGaudioChannelModeLookup;
2662 static $MPEGaudioModeExtensionLookup;
2663 static $MPEGaudioEmphasisLookup;
2664 if (empty($MPEGaudioVersionLookup)) {
2665 $MPEGaudioVersionLookup = MPEGaudioVersionArray();
2666 $MPEGaudioLayerLookup = MPEGaudioLayerArray();
2667 $MPEGaudioBitrateLookup = MPEGaudioBitrateArray();
2668 $MPEGaudioFrequencyLookup = MPEGaudioFrequencyArray();
2669 $MPEGaudioChannelModeLookup = MPEGaudioChannelModeArray();
2670 $MPEGaudioModeExtensionLookup = MPEGaudioModeExtensionArray();
2671 $MPEGaudioEmphasisLookup = MPEGaudioEmphasisArray();
2674 if (isset($MPEGaudioVersionLookup[$rawarray['version']])) {
2675 $decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']];
2678 echo "\n".'invalid Version ('.$rawarray['version'].')';
2682 if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) {
2683 $decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']];
2686 echo "\n".'invalid Layer ('.$rawarray['layer'].')';
2690 if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) {
2692 echo "\n".'invalid Bitrate ('.$rawarray['bitrate'].')';
2694 if ($rawarray['bitrate'] == 15) {
2695 // known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0
2696 // let it go through here otherwise file will not be identified
2701 if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) {
2703 echo "\n".'invalid Frequency ('.$rawarray['sample_rate'].')';
2707 if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) {
2709 echo "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')';
2713 if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) {
2715 echo "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')';
2719 if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) {
2721 echo "\n".'invalid Emphasis ('.$rawarray['emphasis'].')';
2725 // These are just either set or not set, you can't mess that up :)
2726 // $rawarray['protection'];
2727 // $rawarray['padding'];
2728 // $rawarray['private'];
2729 // $rawarray['copyright'];
2730 // $rawarray['original'];
2735 function MPEGaudioHeaderDecode($Header4Bytes) {
2736 // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM
2737 // A - Frame sync (all bits set)
2738 // B - MPEG Audio version ID
2739 // C - Layer description
2740 // D - Protection bit
2741 // E - Bitrate index
2742 // F - Sampling rate frequency index
2746 // J - Mode extension (Only if Joint stereo)
2751 if (strlen($Header4Bytes) != 4) {
2755 $MPEGrawHeader['synch'] = (BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4;
2756 $MPEGrawHeader['version'] = (ord($Header4Bytes{1}) & 0x18) >> 3; // BB
2757 $MPEGrawHeader['layer'] = (ord($Header4Bytes{1}) & 0x06) >> 1; // CC
2758 $MPEGrawHeader['protection'] = (ord($Header4Bytes{1}) & 0x01); // D
2759 $MPEGrawHeader['bitrate'] = (ord($Header4Bytes{2}) & 0xF0) >> 4; // EEEE
2760 $MPEGrawHeader['sample_rate'] = (ord($Header4Bytes{2}) & 0x0C) >> 2; // FF
2761 $MPEGrawHeader['padding'] = (ord($Header4Bytes{2}) & 0x02) >> 1; // G
2762 $MPEGrawHeader['private'] = (ord($Header4Bytes{2}) & 0x01); // H
2763 $MPEGrawHeader['channelmode'] = (ord($Header4Bytes{3}) & 0xC0) >> 6; // II
2764 $MPEGrawHeader['modeextension'] = (ord($Header4Bytes{3}) & 0x30) >> 4; // JJ
2765 $MPEGrawHeader['copyright'] = (ord($Header4Bytes{3}) & 0x08) >> 3; // K
2766 $MPEGrawHeader['original'] = (ord($Header4Bytes{3}) & 0x04) >> 2; // L
2767 $MPEGrawHeader['emphasis'] = (ord($Header4Bytes{3}) & 0x03); // MM
2769 return $MPEGrawHeader;
2772 function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) {
2773 static $AudioFrameLengthCache = array();
2775 if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) {
2776 $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false;
2777 if ($bitrate != 'free') {
2779 if ($version == '1') {
2781 if ($layer == 'I') {
2783 // For Layer I slot is 32 bits long
2784 $FrameLengthCoefficient = 48;
2787 } else { // Layer II / III
2789 // for Layer II and Layer III slot is 8 bits long.
2790 $FrameLengthCoefficient = 144;
2795 } else { // MPEG-2 / MPEG-2.5
2797 if ($layer == 'I') {
2799 // For Layer I slot is 32 bits long
2800 $FrameLengthCoefficient = 24;
2803 } elseif ($layer == 'II') {
2805 // for Layer II and Layer III slot is 8 bits long.
2806 $FrameLengthCoefficient = 144;
2811 // for Layer II and Layer III slot is 8 bits long.
2812 $FrameLengthCoefficient = 72;
2819 // FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding
2820 // http://66.96.216.160/cgi-bin/YaBB.pl?board=c&action=display&num=1018474068
2821 // -> [Finding the next frame synch] on www.r3mix.net forums if the above link goes dead
2822 if ($samplerate > 0) {
2823 $NewFramelength = ($FrameLengthCoefficient * $bitrate * 1000) / $samplerate;
2824 $NewFramelength = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer II/III, 4 bytes for Layer I)
2826 $NewFramelength += $SlotLength;
2828 $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength;
2832 return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate];
2835 function LAMEvbrMethodLookup($VBRmethodID) {
2836 static $LAMEvbrMethodLookup = array();
2837 if (empty($LAMEvbrMethodLookup)) {
2838 $LAMEvbrMethodLookup[0x00] = 'unknown';
2839 $LAMEvbrMethodLookup[0x01] = 'cbr';
2840 $LAMEvbrMethodLookup[0x02] = 'abr';
2841 $LAMEvbrMethodLookup[0x03] = 'vbr-old / vbr-rh';
2842 $LAMEvbrMethodLookup[0x04] = 'vbr-mtrh';
2843 $LAMEvbrMethodLookup[0x05] = 'vbr-new / vbr-mt';
2845 return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : '');
2848 function LAMEmiscStereoModeLookup($StereoModeID) {
2849 static $LAMEmiscStereoModeLookup = array();
2850 if (empty($LAMEmiscStereoModeLookup)) {
2851 $LAMEmiscStereoModeLookup[0] = 'mono';
2852 $LAMEmiscStereoModeLookup[1] = 'stereo';
2853 $LAMEmiscStereoModeLookup[2] = 'dual';
2854 $LAMEmiscStereoModeLookup[3] = 'joint';
2855 $LAMEmiscStereoModeLookup[4] = 'forced';
2856 $LAMEmiscStereoModeLookup[5] = 'auto';
2857 $LAMEmiscStereoModeLookup[6] = 'intensity';
2858 $LAMEmiscStereoModeLookup[7] = 'other';
2860 return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : '');
2863 function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) {
2864 static $LAMEmiscSourceSampleFrequencyLookup = array();
2865 if (empty($LAMEmiscSourceSampleFrequencyLookup)) {
2866 $LAMEmiscSourceSampleFrequencyLookup[0] = '<= 32 kHz';
2867 $LAMEmiscSourceSampleFrequencyLookup[1] = '44.1 kHz';
2868 $LAMEmiscSourceSampleFrequencyLookup[2] = '48 kHz';
2869 $LAMEmiscSourceSampleFrequencyLookup[3] = '> 48kHz';
2871 return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : '');
2874 function LAMEsurroundInfoLookup($SurroundInfoID) {
2875 static $LAMEsurroundInfoLookup = array();
2876 if (empty($LAMEsurroundInfoLookup)) {
2877 $LAMEsurroundInfoLookup[0] = 'no surround info';
2878 $LAMEsurroundInfoLookup[1] = 'DPL encoding';
2879 $LAMEsurroundInfoLookup[2] = 'DPL2 encoding';
2880 $LAMEsurroundInfoLookup[3] = 'Ambisonic encoding';
2882 return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved');
2885 for ($i = 0x00; $i <= 0xFF; $i++) {
2886 $head4 = "\xFF\xFE".chr($i)."\x00";
2887 $isvalid = MPEGaudioHeaderBytesValid($head4);
2888 echo '<div style="color: '.($isvalid ? 'green' : 'red').';">'.str_pad(strtoupper(dechex($i)), 2, '0', STR_PAD_LEFT).' = '.htmlentities(chr($i)).' = '.($isvalid ? 'valid' : 'INVALID').'</div>';