1<?php
2/////////////////////////////////////////////////////////////////
3/// getID3() by James Heinrich <info@getid3.org>               //
4//  available at https://github.com/JamesHeinrich/getID3       //
5//            or https://www.getid3.org                        //
6//            or http://getid3.sourceforge.net                 //
7//                                                             //
8// /demo/demo.mp3header.php - part of getID3()                 //
9// Sample script for decoding MP3 header bytes                 //
10//  see readme.txt for more details                            //
11//                                                            ///
12/////////////////////////////////////////////////////////////////
13
14die('For security reasons, this demo has been disabled. It can be enabled by removing line '.__LINE__.' in demos/'.basename(__FILE__));
15
16
17if (!function_exists('PrintHexBytes')) {
18	function PrintHexBytes($string) {
19		$returnstring = '';
20		for ($i = 0; $i < strlen($string); $i++) {
21			$returnstring .= str_pad(dechex(ord(substr($string, $i, 1))), 2, '0', STR_PAD_LEFT).' ';
22		}
23		return $returnstring;
24	}
25}
26
27if (!function_exists('PrintTextBytes')) {
28	function PrintTextBytes($string) {
29		$returnstring = '';
30		for ($i = 0; $i < strlen($string); $i++) {
31			if (ord(substr($string, $i, 1)) <= 31) {
32				$returnstring .= '   ';
33			} else {
34				$returnstring .= ' '.substr($string, $i, 1).' ';
35			}
36		}
37		return $returnstring;
38	}
39}
40
41if (!function_exists('table_var_dump')) {
42	function table_var_dump($variable) {
43		$returnstring = '';
44		switch (gettype($variable)) {
45			case 'array':
46				$returnstring .= '<table border="1" cellspacing="0" cellpadding="2">';
47				foreach ($variable as $key => $value) {
48					$returnstring .= '<tr><td valign="top"><b>'.str_replace(chr(0), ' ', $key).'</b></td>';
49					$returnstring .= '<td valign="top">'.gettype($value);
50					if (is_array($value)) {
51						$returnstring .= '&nbsp;('.count($value).')';
52					} elseif (is_string($value)) {
53						$returnstring .= '&nbsp;('.strlen($value).')';
54					}
55					if (($key == 'data') && isset($variable['image_mime']) && isset($variable['dataoffset'])) {
56						require_once(GETID3_INCLUDEPATH.'getid3.getimagesize.php');
57						$imageinfo = array();
58						if ($imagechunkcheck = GetDataImageSize($value, $imageinfo)) {
59							$DumpedImageSRC = (!empty($_REQUEST['filename']) ? $_REQUEST['filename'] : '.getid3').'.'.$variable['dataoffset'].'.'.image_type_to_mime_type($imagechunkcheck[2]);
60							if ($tempimagefile = fopen($DumpedImageSRC, 'wb')) {
61								fwrite($tempimagefile, $value);
62								fclose($tempimagefile);
63							}
64							$returnstring .= '</td><td><img src="'.$DumpedImageSRC.'" width="'.$imagechunkcheck[0].'" height="'.$imagechunkcheck[1].'"></td></tr>';
65						} else {
66							$returnstring .= '</td><td><i>invalid image data</i></td></tr>';
67						}
68					} else {
69						$returnstring .= '</td><td>'.table_var_dump($value).'</td></tr>';
70					}
71				}
72				$returnstring .= '</table>';
73				break;
74
75			case 'boolean':
76				$returnstring .= ($variable ? 'TRUE' : 'FALSE');
77				break;
78
79			case 'integer':
80			case 'double':
81			case 'float':
82				$returnstring .= $variable;
83				break;
84
85			case 'object':
86			case 'null':
87				$returnstring .= string_var_dump($variable);
88				break;
89
90			case 'string':
91				$variable = str_replace(chr(0), ' ', $variable);
92				$varlen = strlen($variable);
93				for ($i = 0; $i < $varlen; $i++) {
94					if (preg_match('#['.chr(0x0A).chr(0x0D).' -;0-9A-Za-z]#', $variable[$i])) {
95						$returnstring .= $variable[$i];
96					} else {
97						$returnstring .= '&#'.str_pad(ord($variable[$i]), 3, '0', STR_PAD_LEFT).';';
98					}
99				}
100				$returnstring = nl2br($returnstring);
101				break;
102
103			default:
104				require_once(GETID3_INCLUDEPATH.'getid3.getimagesize.php');
105				$imageinfo = array();
106				if (($imagechunkcheck = GetDataImageSize(substr($variable, 0, 32768), $imageinfo)) && ($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
107					$returnstring .= '<table border="1" cellspacing="0" cellpadding="2">';
108					$returnstring .= '<tr><td><b>type</b></td><td>'.image_type_to_mime_type($imagechunkcheck[2]).'</td></tr>';
109					$returnstring .= '<tr><td><b>width</b></td><td>'.number_format($imagechunkcheck[0]).' px</td></tr>';
110					$returnstring .= '<tr><td><b>height</b></td><td>'.number_format($imagechunkcheck[1]).' px</td></tr>';
111					$returnstring .= '<tr><td><b>size</b></td><td>'.number_format(strlen($variable)).' bytes</td></tr></table>';
112				} else {
113					$returnstring .= nl2br(htmlspecialchars(str_replace(chr(0), ' ', $variable)));
114				}
115				break;
116		}
117		return $returnstring;
118	}
119}
120
121if (!function_exists('string_var_dump')) {
122	function string_var_dump($variable) {
123		if (version_compare(PHP_VERSION, '4.3.0', '>=')) {
124			return print_r($variable, true);
125		}
126		ob_start();
127		var_dump($variable);
128		$dumpedvariable = ob_get_contents();
129		ob_end_clean();
130		return $dumpedvariable;
131	}
132}
133
134if (!function_exists('fileextension')) {
135	function fileextension($filename, $numextensions=1) {
136		if (strstr($filename, '.')) {
137			$reversedfilename = strrev($filename);
138			$offset = 0;
139			for ($i = 0; $i < $numextensions; $i++) {
140				$offset = strpos($reversedfilename, '.', $offset + 1);
141				if ($offset === false) {
142					return '';
143				}
144			}
145			return strrev(substr($reversedfilename, 0, $offset));
146		}
147		return '';
148	}
149}
150
151if (!function_exists('RemoveAccents')) {
152	function RemoveAccents($string) {
153		// return strtr($string, 'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ', 'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyy');
154		// Revised version by marksteward@hotmail.com
155		return strtr(strtr($string, 'ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'), array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u'));
156	}
157}
158
159if (!function_exists('MoreNaturalSort')) {
160	function MoreNaturalSort($ar1, $ar2) {
161		if ($ar1 === $ar2) {
162			return 0;
163		}
164		$len1     = strlen($ar1);
165		$len2     = strlen($ar2);
166		$shortest = min($len1, $len2);
167		if (substr($ar1, 0, $shortest) === substr($ar2, 0, $shortest)) {
168			// the shorter argument is the beginning of the longer one, like "str" and "string"
169			if ($len1 < $len2) {
170				return -1;
171			} elseif ($len1 > $len2) {
172				return 1;
173			}
174			return 0;
175		}
176		$ar1 = RemoveAccents(strtolower(trim($ar1)));
177		$ar2 = RemoveAccents(strtolower(trim($ar2)));
178		$translatearray = array('\''=>'', '"'=>'', '_'=>' ', '('=>'', ')'=>'', '-'=>' ', '  '=>' ', '.'=>'', ','=>'');
179		foreach ($translatearray as $key => $val) {
180			$ar1 = str_replace($key, $val, $ar1);
181			$ar2 = str_replace($key, $val, $ar2);
182		}
183
184		if ($ar1 < $ar2) {
185			return -1;
186		} elseif ($ar1 > $ar2) {
187			return 1;
188		}
189		return 0;
190	}
191}
192
193if (!function_exists('trunc')) {
194	function trunc($floatnumber) {
195		// truncates a floating-point number at the decimal point
196		// returns int (if possible, otherwise float)
197		if ($floatnumber >= 1) {
198			$truncatednumber = floor($floatnumber);
199		} elseif ($floatnumber <= -1) {
200			$truncatednumber = ceil($floatnumber);
201		} else {
202			$truncatednumber = 0;
203		}
204		if ($truncatednumber <= pow(2, 30)) {
205			$truncatednumber = (int) $truncatednumber;
206		}
207		return $truncatednumber;
208	}
209}
210
211if (!function_exists('CastAsInt')) {
212	function CastAsInt($floatnum) {
213		// convert to float if not already
214		$floatnum = (float) $floatnum;
215
216		// convert a float to type int, only if possible
217		if (trunc($floatnum) == $floatnum) {
218			// it's not floating point
219			if ($floatnum <= pow(2, 30)) {
220				// it's within int range
221				$floatnum = (int) $floatnum;
222			}
223		}
224		return $floatnum;
225	}
226}
227
228if (!function_exists('getmicrotime')) {
229	function getmicrotime() {
230		list($usec, $sec) = explode(' ', microtime());
231		return ((float) $usec + (float) $sec);
232	}
233}
234
235if (!function_exists('DecimalBinary2Float')) {
236	function DecimalBinary2Float($binarynumerator) {
237		$numerator   = Bin2Dec($binarynumerator);
238		$denominator = Bin2Dec(str_repeat('1', strlen($binarynumerator)));
239		return ($numerator / $denominator);
240	}
241}
242
243if (!function_exists('NormalizeBinaryPoint')) {
244	function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) {
245		// http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
246		if (strpos($binarypointnumber, '.') === false) {
247			$binarypointnumber = '0.'.$binarypointnumber;
248		} elseif ($binarypointnumber[0] == '.') {
249			$binarypointnumber = '0'.$binarypointnumber;
250		}
251		$exponent = 0;
252		while (($binarypointnumber[0] != '1') || (substr($binarypointnumber, 1, 1) != '.')) {
253			if (substr($binarypointnumber, 1, 1) == '.') {
254				$exponent--;
255				$binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3);
256			} else {
257				$pointpos = strpos($binarypointnumber, '.');
258				$exponent += ($pointpos - 1);
259				$binarypointnumber = str_replace('.', '', $binarypointnumber);
260				$binarypointnumber = $binarypointnumber[0].'.'.substr($binarypointnumber, 1);
261			}
262		}
263		$binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT);
264		return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent);
265	}
266}
267
268if (!function_exists('Float2BinaryDecimal')) {
269	function Float2BinaryDecimal($floatvalue) {
270		// http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
271		$maxbits = 128; // to how many bits of precision should the calculations be taken?
272		$intpart   = trunc($floatvalue);
273		$floatpart = abs($floatvalue - $intpart);
274		$pointbitstring = '';
275		while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) {
276			$floatpart *= 2;
277			$pointbitstring .= (string) trunc($floatpart);
278			$floatpart -= trunc($floatpart);
279		}
280		$binarypointnumber = decbin($intpart).'.'.$pointbitstring;
281		return $binarypointnumber;
282	}
283}
284
285if (!function_exists('Float2String')) {
286	function Float2String($floatvalue, $bits) {
287		// http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html
288		switch ($bits) {
289			case 32:
290				$exponentbits = 8;
291				$fractionbits = 23;
292				break;
293
294			case 64:
295				$exponentbits = 11;
296				$fractionbits = 52;
297				break;
298
299			default:
300				return false;
301				break;
302		}
303		if ($floatvalue >= 0) {
304			$signbit = '0';
305		} else {
306			$signbit = '1';
307		}
308		$normalizedbinary  = NormalizeBinaryPoint(Float2BinaryDecimal($floatvalue), $fractionbits);
309		$biasedexponent    = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent
310		$exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT);
311		$fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT);
312
313		return BigEndian2String(Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false);
314	}
315}
316
317if (!function_exists('LittleEndian2Float')) {
318	function LittleEndian2Float($byteword) {
319		return BigEndian2Float(strrev($byteword));
320	}
321}
322
323if (!function_exists('BigEndian2Float')) {
324	function BigEndian2Float($byteword) {
325		// ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
326		// http://www.psc.edu/general/software/packages/ieee/ieee.html
327		// http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
328
329		$bitword = BigEndian2Bin($byteword);
330		$signbit = $bitword[0];
331
332		switch (strlen($byteword) * 8) {
333			case 32:
334				$exponentbits = 8;
335				$fractionbits = 23;
336				break;
337
338			case 64:
339				$exponentbits = 11;
340				$fractionbits = 52;
341				break;
342
343			case 80:
344				$exponentbits = 16;
345				$fractionbits = 64;
346				break;
347
348			default:
349				return false;
350				break;
351		}
352		$exponentstring = substr($bitword, 1, $exponentbits - 1);
353		$fractionstring = substr($bitword, $exponentbits, $fractionbits);
354		$exponent = Bin2Dec($exponentstring);
355		$fraction = Bin2Dec($fractionstring);
356
357		if (($exponentbits == 16) && ($fractionbits == 64)) {
358			// 80-bit
359			// As used in Apple AIFF for sample_rate
360			// A bit of a hack, but it works ;)
361			return pow(2, ($exponent  - 16382)) * DecimalBinary2Float($fractionstring);
362		}
363
364
365		if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) {
366			// Not a Number
367			$floatvalue = false;
368		} elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) {
369			if ($signbit == '1') {
370				$floatvalue = '-infinity';
371			} else {
372				$floatvalue = '+infinity';
373			}
374		} elseif (($exponent == 0) && ($fraction == 0)) {
375			if ($signbit == '1') {
376				$floatvalue = -0;
377			} else {
378				$floatvalue = 0;
379			}
380			$floatvalue = ($signbit ? 0 : -0);
381		} elseif (($exponent == 0) && ($fraction != 0)) {
382			// These are 'unnormalized' values
383			$floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * DecimalBinary2Float($fractionstring);
384			if ($signbit == '1') {
385				$floatvalue *= -1;
386			}
387		} elseif ($exponent != 0) {
388			$floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + DecimalBinary2Float($fractionstring));
389			if ($signbit == '1') {
390				$floatvalue *= -1;
391			}
392		}
393		return (float) $floatvalue;
394	}
395}
396
397if (!function_exists('BigEndian2Int')) {
398	function BigEndian2Int($byteword, $synchsafe=false, $signed=false) {
399		$intvalue = 0;
400		$bytewordlen = strlen($byteword);
401		for ($i = 0; $i < $bytewordlen; $i++) {
402			if ($synchsafe) { // disregard MSB, effectively 7-bit bytes
403				$intvalue = $intvalue | (ord($byteword[$i]) & 0x7F) << (($bytewordlen - 1 - $i) * 7);
404			} else {
405				$intvalue += ord($byteword[$i]) * pow(256, ($bytewordlen - 1 - $i));
406			}
407		}
408		if ($signed && !$synchsafe) {
409			// synchsafe ints are not allowed to be signed
410			switch ($bytewordlen) {
411				case 1:
412				case 2:
413				case 3:
414				case 4:
415					$signmaskbit = 0x80 << (8 * ($bytewordlen - 1));
416					if ($intvalue & $signmaskbit) {
417						$intvalue = 0 - ($intvalue & ($signmaskbit - 1));
418					}
419					break;
420
421				default:
422					die('ERROR: Cannot have signed integers larger than 32-bits in BigEndian2Int()');
423					break;
424			}
425		}
426		return CastAsInt($intvalue);
427	}
428}
429
430if (!function_exists('LittleEndian2Int')) {
431	function LittleEndian2Int($byteword, $signed=false) {
432		return BigEndian2Int(strrev($byteword), false, $signed);
433	}
434}
435
436if (!function_exists('BigEndian2Bin')) {
437	function BigEndian2Bin($byteword) {
438		$binvalue = '';
439		$bytewordlen = strlen($byteword);
440		for ($i = 0; $i < $bytewordlen; $i++) {
441			$binvalue .= str_pad(decbin(ord($byteword[$i])), 8, '0', STR_PAD_LEFT);
442		}
443		return $binvalue;
444	}
445}
446
447if (!function_exists('BigEndian2String')) {
448	function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) {
449		if ($number < 0) {
450			return false;
451		}
452		$maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF);
453		$intstring = '';
454		if ($signed) {
455			if ($minbytes > 4) {
456				die('ERROR: Cannot have signed integers larger than 32-bits in BigEndian2String()');
457			}
458			$number = $number & (0x80 << (8 * ($minbytes - 1)));
459		}
460		while ($number != 0) {
461			$quotient = ($number / ($maskbyte + 1));
462			$intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring;
463			$number = floor($quotient);
464		}
465		return str_pad($intstring, $minbytes, chr(0), STR_PAD_LEFT);
466	}
467}
468
469if (!function_exists('Dec2Bin')) {
470	function Dec2Bin($number) {
471		while ($number >= 256) {
472			$bytes[] = (($number / 256) - (floor($number / 256))) * 256;
473			$number = floor($number / 256);
474		}
475		$bytes[] = $number;
476		$binstring = '';
477		foreach ($bytes as $i => $byte) {
478			$binstring = (($i == count($bytes) - 1) ? decbin($byte) : str_pad(decbin($byte), 8, '0', STR_PAD_LEFT)).$binstring;
479		}
480		return $binstring;
481	}
482}
483
484if (!function_exists('Bin2Dec')) {
485	function Bin2Dec($binstring) {
486		$decvalue = 0;
487		for ($i = 0; $i < strlen($binstring); $i++) {
488			$decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i);
489		}
490		return CastAsInt($decvalue);
491	}
492}
493
494if (!function_exists('Bin2String')) {
495	function Bin2String($binstring) {
496		// return 'hi' for input of '0110100001101001'
497		$string = '';
498		$binstringreversed = strrev($binstring);
499		for ($i = 0; $i < strlen($binstringreversed); $i += 8) {
500			$string = chr(Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string;
501		}
502		return $string;
503	}
504}
505
506if (!function_exists('LittleEndian2String')) {
507	function LittleEndian2String($number, $minbytes=1, $synchsafe=false) {
508		$intstring = '';
509		while ($number > 0) {
510			if ($synchsafe) {
511				$intstring = $intstring.chr($number & 127);
512				$number >>= 7;
513			} else {
514				$intstring = $intstring.chr($number & 255);
515				$number >>= 8;
516			}
517		}
518		return str_pad($intstring, $minbytes, chr(0), STR_PAD_RIGHT);
519	}
520}
521
522if (!function_exists('Bool2IntString')) {
523	function Bool2IntString($intvalue) {
524		return ($intvalue ? '1' : '0');
525	}
526}
527
528if (!function_exists('IntString2Bool')) {
529	function IntString2Bool($char) {
530		if ($char == '1') {
531			return true;
532		} elseif ($char == '0') {
533			return false;
534		}
535		return null;
536	}
537}
538
539if (!function_exists('InverseBoolean')) {
540	function InverseBoolean($value) {
541		return ($value ? false : true);
542	}
543}
544
545if (!function_exists('DeUnSynchronise')) {
546	function DeUnSynchronise($data) {
547		return str_replace(chr(0xFF).chr(0x00), chr(0xFF), $data);
548	}
549}
550
551if (!function_exists('Unsynchronise')) {
552	function Unsynchronise($data) {
553		// Whenever a false synchronisation is found within the tag, one zeroed
554		// byte is inserted after the first false synchronisation byte. The
555		// format of a correct sync that should be altered by ID3 encoders is as
556		// follows:
557		//      %11111111 111xxxxx
558		// And should be replaced with:
559		//      %11111111 00000000 111xxxxx
560		// This has the side effect that all $FF 00 combinations have to be
561		// altered, so they won't be affected by the decoding process. Therefore
562		// all the $FF 00 combinations have to be replaced with the $FF 00 00
563		// combination during the unsynchronisation.
564
565		$data = str_replace(chr(0xFF).chr(0x00), chr(0xFF).chr(0x00).chr(0x00), $data);
566		$unsyncheddata = '';
567		for ($i = 0; $i < strlen($data); $i++) {
568			$thischar = $data[$i];
569			$unsyncheddata .= $thischar;
570			if ($thischar == chr(255)) {
571				$nextchar = ord(substr($data, $i + 1, 1));
572				if (($nextchar | 0xE0) == 0xE0) {
573					// previous byte = 11111111, this byte = 111?????
574					$unsyncheddata .= chr(0);
575				}
576			}
577		}
578		return $unsyncheddata;
579	}
580}
581
582if (!function_exists('is_hash')) {
583	function is_hash($var) {
584		// written by dev-null@christophe.vg
585		// taken from http://www.php.net/manual/en/function.array-merge-recursive.php
586		if (is_array($var)) {
587			$keys = array_keys($var);
588			$all_num = true;
589			foreach ($keys as $key) {
590				if (is_string($key)) {
591					return true;
592				}
593			}
594		}
595		return false;
596	}
597}
598
599if (!function_exists('array_join_merge')) {
600	function array_join_merge($arr1, $arr2) {
601		// written by dev-null@christophe.vg
602		// taken from http://www.php.net/manual/en/function.array-merge-recursive.php
603		if (is_array($arr1) && is_array($arr2)) {
604			// the same -> merge
605			$new_array = array();
606
607			if (is_hash($arr1) && is_hash($arr2)) {
608				// hashes -> merge based on keys
609				$keys = array_merge(array_keys($arr1), array_keys($arr2));
610				foreach ($keys as $key) {
611					$arr1[$key] = (isset($arr1[$key]) ? $arr1[$key] : '');
612					$arr2[$key] = (isset($arr2[$key]) ? $arr2[$key] : '');
613					$new_array[$key] = array_join_merge($arr1[$key], $arr2[$key]);
614				}
615			} else {
616				// two real arrays -> merge
617				$new_array = array_reverse(array_unique(array_reverse(array_merge($arr1,$arr2))));
618			}
619			return $new_array;
620		} else {
621			// not the same ... take new one if defined, else the old one stays
622			return $arr2 ? $arr2 : $arr1;
623		}
624	}
625}
626
627if (!function_exists('array_merge_clobber')) {
628	function array_merge_clobber($array1, $array2) {
629		// written by kc@hireability.com
630		// taken from http://www.php.net/manual/en/function.array-merge-recursive.php
631		if (!is_array($array1) || !is_array($array2)) {
632			return false;
633		}
634		$newarray = $array1;
635		foreach ($array2 as $key => $val) {
636			if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
637				$newarray[$key] = array_merge_clobber($newarray[$key], $val);
638			} else {
639				$newarray[$key] = $val;
640			}
641		}
642		return $newarray;
643	}
644}
645
646if (!function_exists('array_merge_noclobber')) {
647	function array_merge_noclobber($array1, $array2) {
648		if (!is_array($array1) || !is_array($array2)) {
649			return false;
650		}
651		$newarray = $array1;
652		foreach ($array2 as $key => $val) {
653			if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
654				$newarray[$key] = array_merge_noclobber($newarray[$key], $val);
655			} elseif (!isset($newarray[$key])) {
656				$newarray[$key] = $val;
657			}
658		}
659		return $newarray;
660	}
661}
662
663if (!function_exists('RoughTranslateUnicodeToASCII')) {
664	function RoughTranslateUnicodeToASCII($rawdata, $frame_textencoding) {
665		// rough translation of data for application that can't handle Unicode data
666
667		$tempstring = '';
668		switch ($frame_textencoding) {
669			case 0: // ISO-8859-1. Terminated with $00.
670				$asciidata = $rawdata;
671				break;
672
673			case 1: // UTF-16 encoded Unicode with BOM. Terminated with $00 00.
674				$asciidata = $rawdata;
675				if (substr($asciidata, 0, 2) == chr(0xFF).chr(0xFE)) {
676					// remove BOM, only if present (it should be, but...)
677					$asciidata = substr($asciidata, 2);
678				}
679				if (substr($asciidata, strlen($asciidata) - 2, 2) == chr(0).chr(0)) {
680					$asciidata = substr($asciidata, 0, strlen($asciidata) - 2); // remove terminator, only if present (it should be, but...)
681				}
682				for ($i = 0; $i < strlen($asciidata); $i += 2) {
683					if ((ord($asciidata[$i]) <= 0x7F) || (ord($asciidata[$i]) >= 0xA0)) {
684						$tempstring .= $asciidata[$i];
685					} else {
686						$tempstring .= '?';
687					}
688				}
689				$asciidata = $tempstring;
690				break;
691
692			case 2: // UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
693				$asciidata = $rawdata;
694				if (substr($asciidata, strlen($asciidata) - 2, 2) == chr(0).chr(0)) {
695					$asciidata = substr($asciidata, 0, strlen($asciidata) - 2); // remove terminator, only if present (it should be, but...)
696				}
697				for ($i = 0; $i < strlen($asciidata); $i += 2) {
698					if ((ord($asciidata[$i]) <= 0x7F) || (ord($asciidata[$i]) >= 0xA0)) {
699						$tempstring .= $asciidata[$i];
700					} else {
701						$tempstring .= '?';
702					}
703				}
704				$asciidata = $tempstring;
705				break;
706
707			case 3: // UTF-8 encoded Unicode. Terminated with $00.
708				$asciidata = utf8_decode($rawdata);
709				break;
710
711			case 255: // Unicode, Big-Endian. Terminated with $00 00.
712				$asciidata = $rawdata;
713				if (substr($asciidata, strlen($asciidata) - 2, 2) == chr(0).chr(0)) {
714					$asciidata = substr($asciidata, 0, strlen($asciidata) - 2); // remove terminator, only if present (it should be, but...)
715				}
716				for ($i = 0; ($i + 1) < strlen($asciidata); $i += 2) {
717					if ((ord($asciidata[($i + 1)]) <= 0x7F) || (ord($asciidata[($i + 1)]) >= 0xA0)) {
718						$tempstring .= $asciidata[($i + 1)];
719					} else {
720						$tempstring .= '?';
721					}
722				}
723				$asciidata = $tempstring;
724				break;
725
726
727			default:
728				// shouldn't happen, but in case $frame_textencoding is not 1 <= $frame_textencoding <= 4
729				// just pass the data through unchanged.
730				$asciidata = $rawdata;
731				break;
732		}
733		if (substr($asciidata, strlen($asciidata) - 1, 1) == chr(0)) {
734			// remove null terminator, if present
735			$asciidata = NoNullString($asciidata);
736		}
737		return $asciidata;
738		// return str_replace(chr(0), '', $asciidata); // just in case any nulls slipped through
739	}
740}
741
742if (!function_exists('PlaytimeString')) {
743	function PlaytimeString($playtimeseconds) {
744		$contentseconds = round((($playtimeseconds / 60) - floor($playtimeseconds / 60)) * 60);
745		$contentminutes = floor($playtimeseconds / 60);
746		if ($contentseconds >= 60) {
747			$contentseconds -= 60;
748			$contentminutes++;
749		}
750		return number_format($contentminutes).':'.str_pad($contentseconds, 2, 0, STR_PAD_LEFT);
751	}
752}
753
754if (!function_exists('CloseMatch')) {
755	function CloseMatch($value1, $value2, $tolerance) {
756		return (abs($value1 - $value2) <= $tolerance);
757	}
758}
759
760if (!function_exists('ID3v1matchesID3v2')) {
761	function ID3v1matchesID3v2($id3v1, $id3v2) {
762
763		$requiredindices = array('title', 'artist', 'album', 'year', 'genre', 'comment');
764		foreach ($requiredindices as $requiredindex) {
765			if (!isset($id3v1["$requiredindex"])) {
766				$id3v1["$requiredindex"] = '';
767			}
768			if (!isset($id3v2["$requiredindex"])) {
769				$id3v2["$requiredindex"] = '';
770			}
771		}
772
773		if (trim($id3v1['title']) != trim(substr($id3v2['title'], 0, 30))) {
774			return false;
775		}
776		if (trim($id3v1['artist']) != trim(substr($id3v2['artist'], 0, 30))) {
777			return false;
778		}
779		if (trim($id3v1['album']) != trim(substr($id3v2['album'], 0, 30))) {
780			return false;
781		}
782		if (trim($id3v1['year']) != trim(substr($id3v2['year'], 0, 4))) {
783			return false;
784		}
785		if (trim($id3v1['genre']) != trim($id3v2['genre'])) {
786			return false;
787		}
788		if (isset($id3v1['track_number'])) {
789			if (!isset($id3v1['track_number']) || (trim($id3v1['track_number']) != trim($id3v2['track_number']))) {
790				return false;
791			}
792			if (trim($id3v1['comment']) != trim(substr($id3v2['comment'], 0, 28))) {
793				return false;
794			}
795		} else {
796			if (trim($id3v1['comment']) != trim(substr($id3v2['comment'], 0, 30))) {
797				return false;
798			}
799		}
800		return true;
801	}
802}
803
804if (!function_exists('FILETIMEtoUNIXtime')) {
805	function FILETIMEtoUNIXtime($FILETIME, $round=true) {
806		// FILETIME is a 64-bit unsigned integer representing
807		// the number of 100-nanosecond intervals since January 1, 1601
808		// UNIX timestamp is number of seconds since January 1, 1970
809		// 116444736000000000 = 10000000 * 60 * 60 * 24 * 365 * 369 + 89 leap days
810		if ($round) {
811			return round(($FILETIME - 116444736000000000) / 10000000);
812		}
813		return ($FILETIME - 116444736000000000) / 10000000;
814	}
815}
816
817if (!function_exists('GUIDtoBytestring')) {
818	function GUIDtoBytestring($GUIDstring) {
819		// Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way:
820		// first 4 bytes are in little-endian order
821		// next 2 bytes are appended in little-endian order
822		// next 2 bytes are appended in little-endian order
823		// next 2 bytes are appended in big-endian order
824		// next 6 bytes are appended in big-endian order
825
826		// AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp is stored as this 16-byte string:
827		// $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp
828
829		$hexbytecharstring  = chr(hexdec(substr($GUIDstring,  6, 2)));
830		$hexbytecharstring .= chr(hexdec(substr($GUIDstring,  4, 2)));
831		$hexbytecharstring .= chr(hexdec(substr($GUIDstring,  2, 2)));
832		$hexbytecharstring .= chr(hexdec(substr($GUIDstring,  0, 2)));
833
834		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 11, 2)));
835		$hexbytecharstring .= chr(hexdec(substr($GUIDstring,  9, 2)));
836
837		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 16, 2)));
838		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 14, 2)));
839
840		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 19, 2)));
841		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 21, 2)));
842
843		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 24, 2)));
844		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 26, 2)));
845		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 28, 2)));
846		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 30, 2)));
847		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 32, 2)));
848		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 34, 2)));
849
850		return $hexbytecharstring;
851	}
852}
853
854if (!function_exists('BytestringToGUID')) {
855	function BytestringToGUID($Bytestring) {
856		$GUIDstring  = str_pad(dechex(ord($Bytestring[3])),  2, '0', STR_PAD_LEFT);
857		$GUIDstring .= str_pad(dechex(ord($Bytestring[2])),  2, '0', STR_PAD_LEFT);
858		$GUIDstring .= str_pad(dechex(ord($Bytestring[1])),  2, '0', STR_PAD_LEFT);
859		$GUIDstring .= str_pad(dechex(ord($Bytestring[0])),  2, '0', STR_PAD_LEFT);
860		$GUIDstring .= '-';
861		$GUIDstring .= str_pad(dechex(ord($Bytestring[5])),  2, '0', STR_PAD_LEFT);
862		$GUIDstring .= str_pad(dechex(ord($Bytestring[4])),  2, '0', STR_PAD_LEFT);
863		$GUIDstring .= '-';
864		$GUIDstring .= str_pad(dechex(ord($Bytestring[7])),  2, '0', STR_PAD_LEFT);
865		$GUIDstring .= str_pad(dechex(ord($Bytestring[6])),  2, '0', STR_PAD_LEFT);
866		$GUIDstring .= '-';
867		$GUIDstring .= str_pad(dechex(ord($Bytestring[8])),  2, '0', STR_PAD_LEFT);
868		$GUIDstring .= str_pad(dechex(ord($Bytestring[9])),  2, '0', STR_PAD_LEFT);
869		$GUIDstring .= '-';
870		$GUIDstring .= str_pad(dechex(ord($Bytestring[10])), 2, '0', STR_PAD_LEFT);
871		$GUIDstring .= str_pad(dechex(ord($Bytestring[11])), 2, '0', STR_PAD_LEFT);
872		$GUIDstring .= str_pad(dechex(ord($Bytestring[12])), 2, '0', STR_PAD_LEFT);
873		$GUIDstring .= str_pad(dechex(ord($Bytestring[13])), 2, '0', STR_PAD_LEFT);
874		$GUIDstring .= str_pad(dechex(ord($Bytestring[14])), 2, '0', STR_PAD_LEFT);
875		$GUIDstring .= str_pad(dechex(ord($Bytestring[15])), 2, '0', STR_PAD_LEFT);
876
877		return strtoupper($GUIDstring);
878	}
879}
880
881if (!function_exists('BitrateColor')) {
882	function BitrateColor($bitrate) {
883		$bitrate /= 3; // scale from 1-768kbps to 1-256kbps
884		$bitrate--;    // scale from 1-256kbps to 0-255kbps
885		$bitrate = max($bitrate, 0);
886		$bitrate = min($bitrate, 255);
887		//$bitrate = max($bitrate, 32);
888		//$bitrate = min($bitrate, 143);
889		//$bitrate = ($bitrate * 2) - 32;
890
891		$Rcomponent = max(255 - ($bitrate * 2), 0);
892		$Gcomponent = max(($bitrate * 2) - 255, 0);
893		if ($bitrate > 127) {
894			$Bcomponent = max((255 - $bitrate) * 2, 0);
895		} else {
896			$Bcomponent = max($bitrate * 2, 0);
897		}
898		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);
899	}
900}
901
902if (!function_exists('BitrateText')) {
903	function BitrateText($bitrate) {
904		return '<SPAN STYLE="color: #'.BitrateColor($bitrate).'">'.round($bitrate).' kbps</SPAN>';
905	}
906}
907
908if (!function_exists('image_type_to_mime_type')) {
909	function image_type_to_mime_type($imagetypeid) {
910		// only available in PHP v4.3.0+
911		static $image_type_to_mime_type = array();
912		if (empty($image_type_to_mime_type)) {
913			$image_type_to_mime_type[1]  = 'image/gif';                     // GIF
914			$image_type_to_mime_type[2]  = 'image/jpeg';                    // JPEG
915			$image_type_to_mime_type[3]  = 'image/png';                     // PNG
916			$image_type_to_mime_type[4]  = 'application/x-shockwave-flash'; // Flash
917			$image_type_to_mime_type[5]  = 'image/psd';                     // PSD
918			$image_type_to_mime_type[6]  = 'image/bmp';                     // BMP
919			$image_type_to_mime_type[7]  = 'image/tiff';                    // TIFF: little-endian (Intel)
920			$image_type_to_mime_type[8]  = 'image/tiff';                    // TIFF: big-endian (Motorola)
921			//$image_type_to_mime_type[9]  = 'image/jpc';                   // JPC
922			//$image_type_to_mime_type[10] = 'image/jp2';                   // JPC
923			//$image_type_to_mime_type[11] = 'image/jpx';                   // JPC
924			//$image_type_to_mime_type[12] = 'image/jb2';                   // JPC
925			$image_type_to_mime_type[13] = 'application/x-shockwave-flash'; // Shockwave
926			$image_type_to_mime_type[14] = 'image/iff';                     // IFF
927		}
928		return (isset($image_type_to_mime_type[$imagetypeid]) ? $image_type_to_mime_type[$imagetypeid] : 'application/octet-stream');
929	}
930}
931
932if (!function_exists('utf8_decode')) {
933	// PHP has this function built-in if it's configured with the --with-xml option
934	// This version of the function is only provided in case XML isn't installed
935	function utf8_decode($utf8text) {
936		// http://www.php.net/manual/en/function.utf8-encode.php
937		// bytes  bits  representation
938		//   1     7    0bbbbbbb
939		//   2     11   110bbbbb 10bbbbbb
940		//   3     16   1110bbbb 10bbbbbb 10bbbbbb
941		//   4     21   11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
942
943		$utf8length = strlen($utf8text);
944		$decodedtext = '';
945		for ($i = 0; $i < $utf8length; $i++) {
946			if ((ord($utf8text[$i]) & 0x80) == 0) {
947				$decodedtext .= $utf8text[$i];
948			} elseif ((ord($utf8text[$i]) & 0xF0) == 0xF0) {
949				$decodedtext .= '?';
950				$i += 3;
951			} elseif ((ord($utf8text[$i]) & 0xE0) == 0xE0) {
952				$decodedtext .= '?';
953				$i += 2;
954			} elseif ((ord($utf8text[$i]) & 0xC0) == 0xC0) {
955				//   2     11   110bbbbb 10bbbbbb
956				$decodedchar = Bin2Dec(substr(Dec2Bin(ord($utf8text[$i])), 3, 5).substr(Dec2Bin(ord($utf8text[($i + 1)])), 2, 6));
957				if ($decodedchar <= 255) {
958					$decodedtext .= chr($decodedchar);
959				} else {
960					$decodedtext .= '?';
961				}
962				$i += 1;
963			}
964		}
965		return $decodedtext;
966	}
967}
968
969if (!function_exists('DateMac2Unix')) {
970	function DateMac2Unix($macdate) {
971		// Macintosh timestamp: seconds since 00:00h January 1, 1904
972		// UNIX timestamp:      seconds since 00:00h January 1, 1970
973		return CastAsInt($macdate - 2082844800);
974	}
975}
976
977
978if (!function_exists('FixedPoint8_8')) {
979	function FixedPoint8_8($rawdata) {
980		return BigEndian2Int(substr($rawdata, 0, 1)) + (float) (BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8));
981	}
982}
983
984
985if (!function_exists('FixedPoint16_16')) {
986	function FixedPoint16_16($rawdata) {
987		return BigEndian2Int(substr($rawdata, 0, 2)) + (float) (BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16));
988	}
989}
990
991
992if (!function_exists('FixedPoint2_30')) {
993	function FixedPoint2_30($rawdata) {
994		$binarystring = BigEndian2Bin($rawdata);
995		return Bin2Dec(substr($binarystring, 0, 2)) + (float) (Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30));
996	}
997}
998
999
1000if (!function_exists('Pascal2String')) {
1001	function Pascal2String($pascalstring) {
1002		// Pascal strings have 1 byte at the beginning saying how many chars are in the string
1003		return substr($pascalstring, 1);
1004	}
1005}
1006
1007if (!function_exists('NoNullString')) {
1008	function NoNullString($nullterminatedstring) {
1009		// remove the single null terminator on null terminated strings
1010		if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === chr(0)) {
1011			return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1);
1012		}
1013		return $nullterminatedstring;
1014	}
1015}
1016
1017if (!function_exists('FileSizeNiceDisplay')) {
1018	function FileSizeNiceDisplay($filesize, $precision=2) {
1019		if ($filesize < 1000) {
1020			$sizeunit  = 'bytes';
1021			$precision = 0;
1022		} else {
1023			$filesize /= 1024;
1024			$sizeunit = 'kB';
1025		}
1026		if ($filesize >= 1000) {
1027			$filesize /= 1024;
1028			$sizeunit = 'MB';
1029		}
1030		if ($filesize >= 1000) {
1031			$filesize /= 1024;
1032			$sizeunit = 'GB';
1033		}
1034		return number_format($filesize, $precision).' '.$sizeunit;
1035	}
1036}
1037
1038if (!function_exists('DOStime2UNIXtime')) {
1039	function DOStime2UNIXtime($DOSdate, $DOStime) {
1040		// wFatDate
1041		// Specifies the MS-DOS date. The date is a packed 16-bit value with the following format:
1042		// Bits      Contents
1043		// 0-4    Day of the month (1-31)
1044		// 5-8    Month (1 = January, 2 = February, and so on)
1045		// 9-15   Year offset from 1980 (add 1980 to get actual year)
1046
1047		$UNIXday    =  ($DOSdate & 0x001F);
1048		$UNIXmonth  = (($DOSdate & 0x01E0) >> 5);
1049		$UNIXyear   = (($DOSdate & 0xFE00) >> 9) + 1980;
1050
1051		// wFatTime
1052		// Specifies the MS-DOS time. The time is a packed 16-bit value with the following format:
1053		// Bits   Contents
1054		// 0-4    Second divided by 2
1055		// 5-10   Minute (0-59)
1056		// 11-15  Hour (0-23 on a 24-hour clock)
1057
1058		$UNIXsecond =  ($DOStime & 0x001F) * 2;
1059		$UNIXminute = (($DOStime & 0x07E0) >> 5);
1060		$UNIXhour   = (($DOStime & 0xF800) >> 11);
1061
1062		return mktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
1063	}
1064}
1065
1066if (!function_exists('CreateDeepArray')) {
1067	function CreateDeepArray($ArrayPath, $Separator, $Value) {
1068		// assigns $Value to a nested array path:
1069		//   $foo = CreateDeepArray('/path/to/my', '/', 'file.txt')
1070		// is the same as:
1071		//   $foo = array('path'=>array('to'=>'array('my'=>array('file.txt'))));
1072		// or
1073		//   $foo['path']['to']['my'] = 'file.txt';
1074		while ($ArrayPath[0] == $Separator) {
1075			$ArrayPath = substr($ArrayPath, 1);
1076		}
1077		if (($pos = strpos($ArrayPath, $Separator)) !== false) {
1078			$ReturnedArray[substr($ArrayPath, 0, $pos)] = CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value);
1079		} else {
1080			$ReturnedArray["$ArrayPath"] = $Value;
1081		}
1082		return $ReturnedArray;
1083	}
1084}
1085
1086if (!function_exists('md5_data')) {
1087	// Allan Hansen <ah@artemis.dk>
1088	// md5_data() - returns md5sum for a file from startuing position to absolute end position
1089
1090	function md5_data($file, $offset, $end, $invertsign=false) {
1091		// first try and create a temporary file in the same directory as the file being scanned
1092		if (($dataMD5filename = tempnam(dirname($file), preg_replace('#[^[:alnum:]]#i', '', basename($file)))) === false) {
1093			// if that fails, create a temporary file in the system temp directory
1094			if (($dataMD5filename = tempnam('/tmp', 'getID3')) === false) {
1095				// if that fails, create a temporary file in the current directory
1096				if (($dataMD5filename = tempnam('.', preg_replace('#[^[:alnum:]]#i', '', basename($file)))) === false) {
1097					// can't find anywhere to create a temp file, just die
1098					return false;
1099				}
1100			}
1101		}
1102		$md5 = false;
1103		set_time_limit(max(filesize($file) / 1000000, 30));
1104
1105		// copy parts of file
1106		ob_start();
1107		if ($fp = fopen($file, 'rb')) {
1108			ob_end_clean();
1109
1110			ob_start();
1111			if ($MD5fp = fopen($dataMD5filename, 'wb')) {
1112
1113				ob_end_clean();
1114				if ($invertsign) {
1115					// Load conversion lookup strings for 8-bit unsigned->signed conversion below
1116					$from = '';
1117					$to   = '';
1118					for ($i = 0; $i < 128; $i++) {
1119						$from .= chr($i);
1120						$to   .= chr($i + 128);
1121					}
1122					for ($i = 128; $i < 256; $i++) {
1123						$from .= chr($i);
1124						$to   .= chr($i - 128);
1125					}
1126				}
1127
1128				fseek($fp, $offset, SEEK_SET);
1129				$byteslefttowrite = $end - $offset;
1130				while (($byteslefttowrite > 0) && ($buffer = fread($fp, 32768))) {
1131					if ($invertsign) {
1132						// Possibly FLAC-specific (?)
1133						// FLAC calculates the MD5sum of the source data of 8-bit files
1134						// not on the actual byte values in the source file, but of those
1135						// values converted from unsigned to signed, or more specifcally,
1136						// with the MSB inverted. ex: 01 -> 81; F5 -> 75; etc
1137
1138						// Therefore, 8-bit WAV data has to be converted before getting the
1139						// md5_data value so as to match the FLAC value
1140
1141						// Flip the MSB for each byte in the buffer before copying
1142						$buffer = strtr($buffer, $from, $to);
1143					}
1144					$byteswritten = fwrite($MD5fp, $buffer, $byteslefttowrite);
1145					$byteslefttowrite -= $byteswritten;
1146				}
1147				fclose($MD5fp);
1148				$md5 = md5_file($dataMD5filename);
1149
1150			} else {
1151				$errormessage = ob_get_contents();
1152				ob_end_clean();
1153			}
1154			fclose($fp);
1155
1156		} else {
1157			$errormessage = ob_get_contents();
1158			ob_end_clean();
1159		}
1160		unlink($dataMD5filename);
1161		return $md5;
1162	}
1163}
1164
1165if (!function_exists('TwosCompliment2Decimal')) {
1166	function TwosCompliment2Decimal($BinaryValue) {
1167		// http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html
1168		// First check if the number is negative or positive by looking at the sign bit.
1169		// If it is positive, simply convert it to decimal.
1170		// If it is negative, make it positive by inverting the bits and adding one.
1171		// Then, convert the result to decimal.
1172		// The negative of this number is the value of the original binary.
1173
1174		if ($BinaryValue & 0x80) {
1175
1176			// negative number
1177			return (0 - ((~$BinaryValue & 0xFF) + 1));
1178
1179		} else {
1180
1181			// positive number
1182			return $BinaryValue;
1183
1184		}
1185
1186	}
1187}
1188
1189if (!function_exists('LastArrayElement')) {
1190	function LastArrayElement($MyArray) {
1191		if (!is_array($MyArray)) {
1192			return false;
1193		}
1194		if (empty($MyArray)) {
1195			return null;
1196		}
1197		foreach ($MyArray as $key => $value) {
1198		}
1199		return $value;
1200	}
1201}
1202
1203if (!function_exists('safe_inc')) {
1204	function safe_inc(&$variable, $increment=1) {
1205		if (isset($variable)) {
1206			$variable += $increment;
1207		} else {
1208			$variable = $increment;
1209		}
1210		return true;
1211	}
1212}
1213
1214if (!function_exists('CalculateCompressionRatioVideo')) {
1215	function CalculateCompressionRatioVideo(&$ThisFileInfo) {
1216		if (empty($ThisFileInfo['video'])) {
1217			return false;
1218		}
1219		if (empty($ThisFileInfo['video']['resolution_x']) || empty($ThisFileInfo['video']['resolution_y'])) {
1220			return false;
1221		}
1222		if (empty($ThisFileInfo['video']['bits_per_sample'])) {
1223			return false;
1224		}
1225
1226		switch ($ThisFileInfo['video']['dataformat']) {
1227			case 'bmp':
1228			case 'gif':
1229			case 'jpeg':
1230			case 'jpg':
1231			case 'png':
1232			case 'tiff':
1233				$FrameRate = 1;
1234				$PlaytimeSeconds = 1;
1235				$BitrateCompressed = $ThisFileInfo['filesize'] * 8;
1236				break;
1237
1238			default:
1239				if (!empty($ThisFileInfo['video']['frame_rate'])) {
1240					$FrameRate = $ThisFileInfo['video']['frame_rate'];
1241				} else {
1242					return false;
1243				}
1244				if (!empty($ThisFileInfo['playtime_seconds'])) {
1245					$PlaytimeSeconds = $ThisFileInfo['playtime_seconds'];
1246				} else {
1247					return false;
1248				}
1249				if (!empty($ThisFileInfo['video']['bitrate'])) {
1250					$BitrateCompressed = $ThisFileInfo['video']['bitrate'];
1251				} else {
1252					return false;
1253				}
1254				break;
1255		}
1256		$BitrateUncompressed = $ThisFileInfo['video']['resolution_x'] * $ThisFileInfo['video']['resolution_y'] * $ThisFileInfo['video']['bits_per_sample'] * $FrameRate;
1257
1258		$ThisFileInfo['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
1259		return true;
1260	}
1261}
1262
1263if (!function_exists('CalculateCompressionRatioAudio')) {
1264	function CalculateCompressionRatioAudio(&$ThisFileInfo) {
1265		if (empty($ThisFileInfo['audio']['bitrate']) || empty($ThisFileInfo['audio']['channels']) || empty($ThisFileInfo['audio']['sample_rate']) || empty($ThisFileInfo['audio']['bits_per_sample'])) {
1266			return false;
1267		}
1268		$ThisFileInfo['audio']['compression_ratio'] = $ThisFileInfo['audio']['bitrate'] / ($ThisFileInfo['audio']['channels'] * $ThisFileInfo['audio']['sample_rate'] * $ThisFileInfo['audio']['bits_per_sample']);
1269		return true;
1270	}
1271}
1272
1273if (!function_exists('IsValidMIMEstring')) {
1274	function IsValidMIMEstring($mimestring) {
1275		if ((strlen($mimestring) >= 3) && (strpos($mimestring, '/') > 0) && (strpos($mimestring, '/') < (strlen($mimestring) - 1))) {
1276			return true;
1277		}
1278		return false;
1279	}
1280}
1281
1282if (!function_exists('IsWithinBitRange')) {
1283	function IsWithinBitRange($number, $maxbits, $signed=false) {
1284		if ($signed) {
1285			if (($number > (0 - pow(2, $maxbits - 1))) && ($number <= pow(2, $maxbits - 1))) {
1286				return true;
1287			}
1288		} else {
1289			if (($number >= 0) && ($number <= pow(2, $maxbits))) {
1290				return true;
1291			}
1292		}
1293		return false;
1294	}
1295}
1296
1297if (!function_exists('safe_parse_url')) {
1298	function safe_parse_url($url) {
1299		ob_start();
1300		$parts = parse_url($url);
1301		$errormessage = ob_get_contents();
1302		ob_end_clean();
1303		$parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : '');
1304		$parts['host']   = (isset($parts['host'])   ? $parts['host']   : '');
1305		$parts['user']   = (isset($parts['user'])   ? $parts['user']   : '');
1306		$parts['pass']   = (isset($parts['pass'])   ? $parts['pass']   : '');
1307		$parts['path']   = (isset($parts['path'])   ? $parts['path']   : '');
1308		$parts['query']  = (isset($parts['query'])  ? $parts['query']  : '');
1309		return $parts;
1310	}
1311}
1312
1313if (!function_exists('IsValidURL')) {
1314	function IsValidURL($url, $allowUserPass=false) {
1315		if ($url == '') {
1316			return false;
1317		}
1318		if ($allowUserPass !== true) {
1319			if (strstr($url, '@')) {
1320				// in the format http://user:pass@example.com  or http://user@example.com
1321				// but could easily be somebody incorrectly entering an email address in place of a URL
1322				return false;
1323			}
1324		}
1325		if ($parts = safe_parse_url($url)) {
1326			if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher')) {
1327				return false;
1328			} 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'])) {
1329				return false;
1330			} elseif (!preg_match("#^([[:alnum:]-]|[\_])*$#i", $parts['user'], $regs)) {
1331				return false;
1332			} elseif (!preg_match("#^([[:alnum:]-]|[\_])*$#i", $parts['pass'], $regs)) {
1333				return false;
1334			} elseif (!preg_match("#^[[:alnum:]/_\.@~-]*$#i", $parts['path'], $regs)) {
1335				return false;
1336			} elseif (!preg_match("#^[[:alnum:]?&=+:;_()%#/,\.-]*$#i", $parts['query'], $regs)) {
1337				return false;
1338			} else {
1339				return true;
1340			}
1341		}
1342		return false;
1343	}
1344}
1345
1346echo '<form action="'.htmlentities($_SERVER['PHP_SELF'], ENT_QUOTES).'" method="get">';
1347echo 'Enter 4 hex bytes of MPEG-audio header (ie <I>FF FA 92 44</I>)<BR>';
1348echo '<input type="text" name="HeaderHexBytes" value="'.htmlentities(isset($_POST['HeaderHexBytes']) ? strtoupper($_POST['HeaderHexBytes']) : '', ENT_QUOTES).'" size="11" maxlength="11">';
1349echo '<input type="submit" name="Analyze" value="Analyze"></form>';
1350echo '<hr>';
1351
1352echo '<form action="'.htmlentities($_SERVER['PHP_SELF'], ENT_QUOTES).'" method="get">';
1353echo 'Generate a MPEG-audio 4-byte header from these values:<BR>';
1354echo '<table border="0">';
1355
1356$MPEGgenerateValues = array(
1357	'version'       => array('1', '2', '2.5'),
1358	'layer'         => array('I', 'II', 'III'),
1359	'protection'    => array('Y', 'N'),
1360	'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'),
1361	'frequency'     => array('8000', '11025', '12000', '16000', '22050', '24000', '32000', '44100', '48000'),
1362	'padding'       => array('Y', 'N'),
1363	'private'       => array('Y', 'N'),
1364	'channelmode'   => array('stereo', 'joint stereo', 'dual channel', 'mono'),
1365	'modeextension' => array('none', 'IS', 'MS', 'IS+MS', '4-31', '8-31', '12-31', '16-31'),
1366	'copyright'     => array('Y', 'N'),
1367	'original'      => array('Y', 'N'),
1368	'emphasis'      => array('none', '50/15ms', 'CCIT J.17'),
1369);
1370
1371foreach ($MPEGgenerateValues as $name => $dataarray) {
1372	echo '<tr><th>'.$name.':</th><td><select name="'.$name.'">';
1373	foreach ($dataarray as $key => $value) {
1374		echo '<option'.((isset($_POST["$name"]) && ($_POST["$name"] == $value)) ? ' SELECTED' : '').'>'.$value.'</option>';
1375	}
1376	echo '</select></td></tr>';
1377}
1378
1379if (isset($_POST['bitrate'])) {
1380	echo '<tr><th>Frame Length:</th><td>'.(int) MPEGaudioFrameLength($_POST['bitrate'], $_POST['version'], $_POST['layer'], (($_POST['padding'] == 'Y') ? '1' : '0'), $_POST['frequency']).'</td></tr>';
1381}
1382echo '</table>';
1383echo '<input type="submit" name="Generate" value="Generate"></form>';
1384echo '<hr>';
1385
1386
1387if (isset($_POST['Analyze']) && $_POST['HeaderHexBytes']) {
1388
1389	$headerbytearray = explode(' ', $_POST['HeaderHexBytes']);
1390	if (count($headerbytearray) != 4) {
1391		die('Invalid byte pattern');
1392	}
1393	$headerstring = '';
1394	foreach ($headerbytearray as $textbyte) {
1395		$headerstring .= chr(hexdec($textbyte));
1396	}
1397
1398	$MP3fileInfo['error'] = '';
1399
1400	$MPEGheaderRawArray = MPEGaudioHeaderDecode(substr($headerstring, 0, 4));
1401
1402	if (MPEGaudioHeaderValid($MPEGheaderRawArray, true)) {
1403
1404		$MP3fileInfo['raw'] = $MPEGheaderRawArray;
1405
1406		$MP3fileInfo['version']              = MPEGaudioVersionLookup($MP3fileInfo['raw']['version']);
1407		$MP3fileInfo['layer']                = MPEGaudioLayerLookup($MP3fileInfo['raw']['layer']);
1408		$MP3fileInfo['protection']           = MPEGaudioCRCLookup($MP3fileInfo['raw']['protection']);
1409		$MP3fileInfo['bitrate']              = MPEGaudioBitrateLookup($MP3fileInfo['version'], $MP3fileInfo['layer'], $MP3fileInfo['raw']['bitrate']);
1410		$MP3fileInfo['frequency']            = MPEGaudioFrequencyLookup($MP3fileInfo['version'], $MP3fileInfo['raw']['sample_rate']);
1411		$MP3fileInfo['padding']              = (bool) $MP3fileInfo['raw']['padding'];
1412		$MP3fileInfo['private']              = (bool) $MP3fileInfo['raw']['private'];
1413		$MP3fileInfo['channelmode']          = MPEGaudioChannelModeLookup($MP3fileInfo['raw']['channelmode']);
1414		$MP3fileInfo['channels']             = (($MP3fileInfo['channelmode'] == 'mono') ? 1 : 2);
1415		$MP3fileInfo['modeextension']        = MPEGaudioModeExtensionLookup($MP3fileInfo['layer'], $MP3fileInfo['raw']['modeextension']);
1416		$MP3fileInfo['copyright']            = (bool) $MP3fileInfo['raw']['copyright'];
1417		$MP3fileInfo['original']             = (bool) $MP3fileInfo['raw']['original'];
1418		$MP3fileInfo['emphasis']             = MPEGaudioEmphasisLookup($MP3fileInfo['raw']['emphasis']);
1419
1420		if ($MP3fileInfo['protection']) {
1421			$MP3fileInfo['crc'] = BigEndian2Int(substr($headerstring, 4, 2));
1422		}
1423
1424		if ($MP3fileInfo['frequency'] > 0) {
1425			$MP3fileInfo['framelength'] = MPEGaudioFrameLength($MP3fileInfo['bitrate'], $MP3fileInfo['version'], $MP3fileInfo['layer'], (int) $MP3fileInfo['padding'], $MP3fileInfo['frequency']);
1426		}
1427		if ($MP3fileInfo['bitrate'] != 'free') {
1428			$MP3fileInfo['bitrate'] *= 1000;
1429		}
1430
1431	} else {
1432
1433		$MP3fileInfo['error'] .= "\n".'Invalid MPEG audio header';
1434
1435	}
1436
1437	if (!$MP3fileInfo['error']) {
1438		unset($MP3fileInfo['error']);
1439	}
1440
1441	echo table_var_dump($MP3fileInfo);
1442
1443} elseif (isset($_POST['Generate'])) {
1444
1445	// AAAA AAAA  AAAB BCCD  EEEE FFGH  IIJJ KLMM
1446
1447	$headerbitstream  = '11111111111';                               // A - Frame sync (all bits set)
1448
1449	$MPEGversionLookup = array('2.5'=>'00', '2'=>'10', '1'=>'11');
1450	$headerbitstream .= $MPEGversionLookup[$_POST['version']];       // B - MPEG Audio version ID
1451
1452	$MPEGlayerLookup = array('III'=>'01', 'II'=>'10', 'I'=>'11');
1453	$headerbitstream .= $MPEGlayerLookup[$_POST['layer']];           // C - Layer description
1454
1455	$headerbitstream .= (($_POST['protection'] == 'Y') ? '0' : '1'); // D - Protection bit
1456
1457	$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');
1458	$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');
1459	$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');
1460	$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');
1461	$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');
1462	$MPEGaudioBitrateLookup['2']['III']   = $MPEGaudioBitrateLookup['2']['II'];
1463	$MPEGaudioBitrateLookup['2.5']['I']   = $MPEGaudioBitrateLookup['2']['I'];
1464	$MPEGaudioBitrateLookup['2.5']['II']  = $MPEGaudioBitrateLookup['2']['II'];
1465	$MPEGaudioBitrateLookup['2.5']['III'] = $MPEGaudioBitrateLookup['2']['II'];
1466	if (isset($MPEGaudioBitrateLookup[$_POST['version']][$_POST['layer']][$_POST['bitrate']])) {
1467		$headerbitstream .= $MPEGaudioBitrateLookup[$_POST['version']][$_POST['layer']][$_POST['bitrate']]; // E - Bitrate index
1468	} else {
1469		die('Invalid <B>Bitrate</B>');
1470	}
1471
1472	$MPEGaudioFrequencyLookup['1']   = array('44100'=>'00', '48000'=>'01', '32000'=>'10');
1473	$MPEGaudioFrequencyLookup['2']   = array('22050'=>'00', '24000'=>'01', '16000'=>'10');
1474	$MPEGaudioFrequencyLookup['2.5'] = array('11025'=>'00', '12000'=>'01', '8000'=>'10');
1475	if (isset($MPEGaudioFrequencyLookup[$_POST['version']][$_POST['frequency']])) {
1476		$headerbitstream .= $MPEGaudioFrequencyLookup[$_POST['version']][$_POST['frequency']];  // F - Sampling rate frequency index
1477	} else {
1478		die('Invalid <B>Frequency</B>');
1479	}
1480
1481	$headerbitstream .= (($_POST['padding'] == 'Y') ? '1' : '0');            // G - Padding bit
1482
1483	$headerbitstream .= (($_POST['private'] == 'Y') ? '1' : '0');            // H - Private bit
1484
1485	$MPEGaudioChannelModeLookup = array('stereo'=>'00', 'joint stereo'=>'01', 'dual channel'=>'10', 'mono'=>'11');
1486	$headerbitstream .= $MPEGaudioChannelModeLookup[$_POST['channelmode']];  // I - Channel Mode
1487
1488	$MPEGaudioModeExtensionLookup['I']   = array('4-31'=>'00', '8-31'=>'01', '12-31'=>'10', '16-31'=>'11');
1489	$MPEGaudioModeExtensionLookup['II']  = $MPEGaudioModeExtensionLookup['I'];
1490	$MPEGaudioModeExtensionLookup['III'] = array('none'=>'00',   'IS'=>'01',    'MS'=>'10', 'IS+MS'=>'11');
1491	if ($_POST['channelmode'] != 'joint stereo') {
1492		$headerbitstream .= '00';
1493	} elseif (isset($MPEGaudioModeExtensionLookup[$_POST['layer']][$_POST['modeextension']])) {
1494		$headerbitstream .= $MPEGaudioModeExtensionLookup[$_POST['layer']][$_POST['modeextension']];  // J - Mode extension (Only if Joint stereo)
1495	} else {
1496		die('Invalid <B>Mode Extension</B>');
1497	}
1498
1499	$headerbitstream .= (($_POST['copyright'] == 'Y') ? '1' : '0');          // K - Copyright
1500
1501	$headerbitstream .= (($_POST['original']  == 'Y') ? '1' : '0');          // L - Original
1502
1503	$MPEGaudioEmphasisLookup = array('none'=>'00', '50/15ms'=>'01', 'CCIT J.17'=>'11');
1504	if (isset($MPEGaudioEmphasisLookup[$_POST['emphasis']])) {
1505		$headerbitstream .= $MPEGaudioEmphasisLookup[$_POST['emphasis']];    // M - Emphasis
1506	} else {
1507		die('Invalid <B>Emphasis</B>');
1508	}
1509
1510	echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream,  0, 8))), 2, '0', STR_PAD_LEFT)).' ';
1511	echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream,  8, 8))), 2, '0', STR_PAD_LEFT)).' ';
1512	echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream, 16, 8))), 2, '0', STR_PAD_LEFT)).' ';
1513	echo strtoupper(str_pad(dechex(bindec(substr($headerbitstream, 24, 8))), 2, '0', STR_PAD_LEFT)).'<BR>';
1514
1515}
1516
1517function MPEGaudioVersionLookup($rawversion) {
1518	$MPEGaudioVersionLookup = array('2.5', FALSE, '2', '1');
1519	return (isset($MPEGaudioVersionLookup["$rawversion"]) ? $MPEGaudioVersionLookup["$rawversion"] : FALSE);
1520}
1521
1522function MPEGaudioLayerLookup($rawlayer) {
1523	$MPEGaudioLayerLookup = array(FALSE, 'III', 'II', 'I');
1524	return (isset($MPEGaudioLayerLookup["$rawlayer"]) ? $MPEGaudioLayerLookup["$rawlayer"] : FALSE);
1525}
1526
1527function MPEGaudioBitrateLookup($version, $layer, $rawbitrate) {
1528	static $MPEGaudioBitrateLookup;
1529	if (empty($MPEGaudioBitrateLookup)) {
1530		$MPEGaudioBitrateLookup = MPEGaudioBitrateArray();
1531	}
1532	return (isset($MPEGaudioBitrateLookup["$version"]["$layer"]["$rawbitrate"]) ? $MPEGaudioBitrateLookup["$version"]["$layer"]["$rawbitrate"] : FALSE);
1533}
1534
1535function MPEGaudioFrequencyLookup($version, $rawfrequency) {
1536	static $MPEGaudioFrequencyLookup;
1537	if (empty($MPEGaudioFrequencyLookup)) {
1538		$MPEGaudioFrequencyLookup = MPEGaudioFrequencyArray();
1539	}
1540	return (isset($MPEGaudioFrequencyLookup["$version"]["$rawfrequency"]) ? $MPEGaudioFrequencyLookup["$version"]["$rawfrequency"] : FALSE);
1541}
1542
1543function MPEGaudioChannelModeLookup($rawchannelmode) {
1544	$MPEGaudioChannelModeLookup = array('stereo', 'joint stereo', 'dual channel', 'mono');
1545	return (isset($MPEGaudioChannelModeLookup["$rawchannelmode"]) ? $MPEGaudioChannelModeLookup["$rawchannelmode"] : FALSE);
1546}
1547
1548function MPEGaudioModeExtensionLookup($layer, $rawmodeextension) {
1549	$MPEGaudioModeExtensionLookup['I']   = array('4-31', '8-31', '12-31', '16-31');
1550	$MPEGaudioModeExtensionLookup['II']  = array('4-31', '8-31', '12-31', '16-31');
1551	$MPEGaudioModeExtensionLookup['III'] = array('', 'IS', 'MS', 'IS+MS');
1552	return (isset($MPEGaudioModeExtensionLookup["$layer"]["$rawmodeextension"]) ? $MPEGaudioModeExtensionLookup["$layer"]["$rawmodeextension"] : FALSE);
1553}
1554
1555function MPEGaudioEmphasisLookup($rawemphasis) {
1556	$MPEGaudioEmphasisLookup = array('none', '50/15ms', FALSE, 'CCIT J.17');
1557	return (isset($MPEGaudioEmphasisLookup["$rawemphasis"]) ? $MPEGaudioEmphasisLookup["$rawemphasis"] : FALSE);
1558}
1559
1560function MPEGaudioCRCLookup($CRCbit) {
1561	// inverse boolean cast :)
1562	if ($CRCbit == '0') {
1563		return TRUE;
1564	} else {
1565		return FALSE;
1566	}
1567}
1568
1569/////////////////////////////////////////////////////////////////
1570/// getID3() by James Heinrich <info@getid3.org>               //
1571//  available at http://getid3.sourceforge.net                ///
1572//            or https://www.getid3.org                       ///
1573/////////////////////////////////////////////////////////////////
1574//                                                             //
1575// getid3.mp3.php - part of getID3()                           //
1576// See getid3.readme.txt for more details                      //
1577//                                                             //
1578/////////////////////////////////////////////////////////////////
1579
1580// number of frames to scan to determine if MPEG-audio sequence is valid
1581// Lower this number to 5-20 for faster scanning
1582// Increase this number to 50+ for most accurate detection of valid VBR/CBR
1583// mpeg-audio streams
1584define('MPEG_VALID_CHECK_FRAMES', 35);
1585
1586function getMP3headerFilepointer(&$fd, &$ThisFileInfo) {
1587
1588	getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $ThisFileInfo['avdataoffset']);
1589
1590	if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode'])) {
1591		$ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']);
1592	}
1593
1594	if (((isset($ThisFileInfo['id3v2']) && ($ThisFileInfo['avdataoffset'] > $ThisFileInfo['id3v2']['headerlength'])) || (!isset($ThisFileInfo['id3v2']) && ($ThisFileInfo['avdataoffset'] > 0)))) {
1595
1596		$ThisFileInfo['warning'] .= "\n".'Unknown data before synch ';
1597		if (isset($ThisFileInfo['id3v2']['headerlength'])) {
1598			$ThisFileInfo['warning'] .= '(ID3v2 header ends at '.$ThisFileInfo['id3v2']['headerlength'].', then '.($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']).' bytes garbage, ';
1599		} else {
1600			$ThisFileInfo['warning'] .= '(should be at beginning of file, ';
1601		}
1602		$ThisFileInfo['warning'] .= 'synch detected at '.$ThisFileInfo['avdataoffset'].')';
1603		if ($ThisFileInfo['audio']['bitrate_mode'] == 'cbr') {
1604			if (!empty($ThisFileInfo['id3v2']['headerlength']) && (($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']) == $ThisFileInfo['mpeg']['audio']['framelength'])) {
1605				$ThisFileInfo['warning'] .= '. This is a known problem with some versions of LAME (3.91, 3.92) DLL in CBR mode.';
1606				$ThisFileInfo['audio']['codec'] = 'LAME';
1607			} elseif (empty($ThisFileInfo['id3v2']['headerlength']) && ($ThisFileInfo['avdataoffset'] == $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			}
1611		}
1612
1613	}
1614
1615	if (isset($ThisFileInfo['mpeg']['audio']['layer']) && ($ThisFileInfo['mpeg']['audio']['layer'] == 'II')) {
1616		$ThisFileInfo['audio']['dataformat'] = 'mp2';
1617	} elseif (isset($ThisFileInfo['mpeg']['audio']['layer']) && ($ThisFileInfo['mpeg']['audio']['layer'] == 'I')) {
1618		$ThisFileInfo['audio']['dataformat'] = 'mp1';
1619	}
1620	if ($ThisFileInfo['fileformat'] == 'mp3') {
1621		switch ($ThisFileInfo['audio']['dataformat']) {
1622			case 'mp1':
1623			case 'mp2':
1624			case 'mp3':
1625				$ThisFileInfo['fileformat'] = $ThisFileInfo['audio']['dataformat'];
1626				break;
1627
1628			default:
1629				$ThisFileInfo['warning'] .= "\n".'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$ThisFileInfo['audio']['dataformat'].'"';
1630				break;
1631		}
1632	}
1633
1634	if (empty($ThisFileInfo['fileformat'])) {
1635		$ThisFileInfo['error'] .= "\n".'Synch not found';
1636		unset($ThisFileInfo['fileformat']);
1637		unset($ThisFileInfo['audio']['bitrate_mode']);
1638		unset($ThisFileInfo['avdataoffset']);
1639		unset($ThisFileInfo['avdataend']);
1640		return false;
1641	}
1642
1643	$ThisFileInfo['mime_type']         = 'audio/mpeg';
1644	$ThisFileInfo['audio']['lossless'] = false;
1645
1646	// Calculate playtime
1647	if (!isset($ThisFileInfo['playtime_seconds']) && isset($ThisFileInfo['audio']['bitrate']) && ($ThisFileInfo['audio']['bitrate'] > 0)) {
1648		$ThisFileInfo['playtime_seconds'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 / $ThisFileInfo['audio']['bitrate'];
1649	}
1650
1651	if (isset($ThisFileInfo['mpeg']['audio']['LAME'])) {
1652		$ThisFileInfo['audio']['codec'] = 'LAME';
1653		if (!empty($ThisFileInfo['mpeg']['audio']['LAME']['long_version'])) {
1654			$ThisFileInfo['audio']['encoder'] = trim($ThisFileInfo['mpeg']['audio']['LAME']['long_version']);
1655		}
1656	}
1657
1658	return true;
1659}
1660
1661
1662function decodeMPEGaudioHeader($fd, $offset, &$ThisFileInfo, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) {
1663
1664	static $MPEGaudioVersionLookup;
1665	static $MPEGaudioLayerLookup;
1666	static $MPEGaudioBitrateLookup;
1667	static $MPEGaudioFrequencyLookup;
1668	static $MPEGaudioChannelModeLookup;
1669	static $MPEGaudioModeExtensionLookup;
1670	static $MPEGaudioEmphasisLookup;
1671	if (empty($MPEGaudioVersionLookup)) {
1672		$MPEGaudioVersionLookup       = MPEGaudioVersionArray();
1673		$MPEGaudioLayerLookup         = MPEGaudioLayerArray();
1674		$MPEGaudioBitrateLookup       = MPEGaudioBitrateArray();
1675		$MPEGaudioFrequencyLookup     = MPEGaudioFrequencyArray();
1676		$MPEGaudioChannelModeLookup   = MPEGaudioChannelModeArray();
1677		$MPEGaudioModeExtensionLookup = MPEGaudioModeExtensionArray();
1678		$MPEGaudioEmphasisLookup      = MPEGaudioEmphasisArray();
1679	}
1680
1681	if ($offset >= $ThisFileInfo['avdataend']) {
1682		$ThisFileInfo['error'] .= "\n".'end of file encounter looking for MPEG synch';
1683		return false;
1684	}
1685	fseek($fd, $offset, SEEK_SET);
1686	$headerstring = fread($fd, 1441); // worse-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame
1687
1688	// MP3 audio frame structure:
1689	// $aa $aa $aa $aa [$bb $bb] $cc...
1690	// where $aa..$aa is the four-byte mpeg-audio header (below)
1691	// $bb $bb is the optional 2-byte CRC
1692	// and $cc... is the audio data
1693
1694	$head4 = substr($headerstring, 0, 4);
1695
1696	static $MPEGaudioHeaderDecodeCache = array();
1697	if (isset($MPEGaudioHeaderDecodeCache[$head4])) {
1698		$MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4];
1699	} else {
1700		$MPEGheaderRawArray = MPEGaudioHeaderDecode($head4);
1701		$MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray;
1702	}
1703
1704	static $MPEGaudioHeaderValidCache = array();
1705
1706	// Not in cache
1707	if (!isset($MPEGaudioHeaderValidCache[$head4])) {
1708		$MPEGaudioHeaderValidCache[$head4] = MPEGaudioHeaderValid($MPEGheaderRawArray);
1709	}
1710
1711	if ($MPEGaudioHeaderValidCache[$head4]) {
1712		$ThisFileInfo['mpeg']['audio']['raw'] = $MPEGheaderRawArray;
1713	} else {
1714		$ThisFileInfo['error'] .= "\n".'Invalid MPEG audio header at offset '.$offset;
1715		return false;
1716	}
1717
1718	if (!$FastMPEGheaderScan) {
1719
1720		$ThisFileInfo['mpeg']['audio']['version']       = $MPEGaudioVersionLookup[$ThisFileInfo['mpeg']['audio']['raw']['version']];
1721		$ThisFileInfo['mpeg']['audio']['layer']         = $MPEGaudioLayerLookup[$ThisFileInfo['mpeg']['audio']['raw']['layer']];
1722
1723		$ThisFileInfo['mpeg']['audio']['channelmode']   = $MPEGaudioChannelModeLookup[$ThisFileInfo['mpeg']['audio']['raw']['channelmode']];
1724		$ThisFileInfo['mpeg']['audio']['channels']      = (($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') ? 1 : 2);
1725		$ThisFileInfo['mpeg']['audio']['sample_rate']   = $MPEGaudioFrequencyLookup[$ThisFileInfo['mpeg']['audio']['version']][$ThisFileInfo['mpeg']['audio']['raw']['sample_rate']];
1726		$ThisFileInfo['mpeg']['audio']['protection']    = !$ThisFileInfo['mpeg']['audio']['raw']['protection'];
1727		$ThisFileInfo['mpeg']['audio']['private']       = (bool) $ThisFileInfo['mpeg']['audio']['raw']['private'];
1728		$ThisFileInfo['mpeg']['audio']['modeextension'] = $MPEGaudioModeExtensionLookup[$ThisFileInfo['mpeg']['audio']['layer']][$ThisFileInfo['mpeg']['audio']['raw']['modeextension']];
1729		$ThisFileInfo['mpeg']['audio']['copyright']     = (bool) $ThisFileInfo['mpeg']['audio']['raw']['copyright'];
1730		$ThisFileInfo['mpeg']['audio']['original']      = (bool) $ThisFileInfo['mpeg']['audio']['raw']['original'];
1731		$ThisFileInfo['mpeg']['audio']['emphasis']      = $MPEGaudioEmphasisLookup[$ThisFileInfo['mpeg']['audio']['raw']['emphasis']];
1732
1733		$ThisFileInfo['audio']['channels']    = $ThisFileInfo['mpeg']['audio']['channels'];
1734		$ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate'];
1735
1736		if ($ThisFileInfo['mpeg']['audio']['protection']) {
1737			$ThisFileInfo['mpeg']['audio']['crc'] = BigEndian2Int(substr($headerstring, 4, 2));
1738		}
1739
1740	}
1741
1742	if ($ThisFileInfo['mpeg']['audio']['raw']['bitrate'] == 15) {
1743		// http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0
1744		$ThisFileInfo['warning'] .= "\n".'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1';
1745		$ThisFileInfo['mpeg']['audio']['raw']['bitrate'] = 0;
1746	}
1747	$ThisFileInfo['mpeg']['audio']['padding'] = (bool) $ThisFileInfo['mpeg']['audio']['raw']['padding'];
1748	$ThisFileInfo['mpeg']['audio']['bitrate'] = $MPEGaudioBitrateLookup[$ThisFileInfo['mpeg']['audio']['version']][$ThisFileInfo['mpeg']['audio']['layer']][$ThisFileInfo['mpeg']['audio']['raw']['bitrate']];
1749
1750	if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') && ($offset == $ThisFileInfo['avdataoffset'])) {
1751		// only skip multiple frame check if free-format bitstream found at beginning of file
1752		// otherwise is quite possibly simply corrupted data
1753		$recursivesearch = false;
1754	}
1755
1756	// For Layer II there are some combinations of bitrate and mode which are not allowed.
1757	if (!$FastMPEGheaderScan && ($ThisFileInfo['mpeg']['audio']['layer'] == 'II')) {
1758
1759		$ThisFileInfo['audio']['dataformat'] = 'mp2';
1760		switch ($ThisFileInfo['mpeg']['audio']['channelmode']) {
1761
1762			case 'mono':
1763				if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') || ($ThisFileInfo['mpeg']['audio']['bitrate'] <= 192)) {
1764					// these are ok
1765				} else {
1766					$ThisFileInfo['error'] .= "\n".$ThisFileInfo['mpeg']['audio']['bitrate'].'kbps not allowed in Layer II, '.$ThisFileInfo['mpeg']['audio']['channelmode'].'.';
1767					return false;
1768				}
1769				break;
1770
1771			case 'stereo':
1772			case 'joint stereo':
1773			case 'dual channel':
1774				if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') || ($ThisFileInfo['mpeg']['audio']['bitrate'] == 64) || ($ThisFileInfo['mpeg']['audio']['bitrate'] >= 96)) {
1775					// these are ok
1776				} else {
1777					$ThisFileInfo['error'] .= "\n".$ThisFileInfo['mpeg']['audio']['bitrate'].'kbps not allowed in Layer II, '.$ThisFileInfo['mpeg']['audio']['channelmode'].'.';
1778					return false;
1779				}
1780				break;
1781
1782		}
1783
1784	}
1785
1786
1787	if ($ThisFileInfo['audio']['sample_rate'] > 0) {
1788		$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']);
1789	}
1790
1791	if ($ThisFileInfo['mpeg']['audio']['bitrate'] != 'free') {
1792
1793		$ThisFileInfo['audio']['bitrate'] = 1000 * $ThisFileInfo['mpeg']['audio']['bitrate'];
1794
1795		if (isset($ThisFileInfo['mpeg']['audio']['framelength'])) {
1796			$nextframetestoffset = $offset + $ThisFileInfo['mpeg']['audio']['framelength'];
1797		} else {
1798			$ThisFileInfo['error'] .= "\n".'Frame at offset('.$offset.') is has an invalid frame length.';
1799			return false;
1800		}
1801
1802	}
1803
1804	$ExpectedNumberOfAudioBytes = 0;
1805
1806	////////////////////////////////////////////////////////////////////////////////////
1807	// Variable-bitrate headers
1808
1809	if (substr($headerstring, 4 + 32, 4) == 'VBRI') {
1810		// Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36)
1811		// specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html
1812
1813		$ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr';
1814		$ThisFileInfo['mpeg']['audio']['VBR_method']   = 'Fraunhofer';
1815		$ThisFileInfo['audio']['codec']                = 'Fraunhofer';
1816
1817		$SideInfoData = substr($headerstring, 4 + 2, 32);
1818
1819		$FraunhoferVBROffset = 36;
1820
1821		$ThisFileInfo['mpeg']['audio']['VBR_encoder_version']     = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset +  4, 2));
1822		$ThisFileInfo['mpeg']['audio']['VBR_encoder_delay']       = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset +  6, 2));
1823		$ThisFileInfo['mpeg']['audio']['VBR_quality']             = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset +  8, 2));
1824		$ThisFileInfo['mpeg']['audio']['VBR_bytes']               = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4));
1825		$ThisFileInfo['mpeg']['audio']['VBR_frames']              = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4));
1826		$ThisFileInfo['mpeg']['audio']['VBR_seek_offsets']        = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2));
1827		//$ThisFileInfo['mpeg']['audio']['reserved']              = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 4)); // hardcoded $00 $01 $00 $02  - purpose unknown
1828		$ThisFileInfo['mpeg']['audio']['VBR_seek_offsets_stride'] = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2));
1829
1830		$ExpectedNumberOfAudioBytes = $ThisFileInfo['mpeg']['audio']['VBR_bytes'];
1831
1832		$previousbyteoffset = $offset;
1833		for ($i = 0; $i < $ThisFileInfo['mpeg']['audio']['VBR_seek_offsets']; $i++) {
1834			$Fraunhofer_OffsetN = BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, 2));
1835			$FraunhoferVBROffset += 2;
1836			$ThisFileInfo['mpeg']['audio']['VBR_offsets_relative'][$i] = $Fraunhofer_OffsetN;
1837			$ThisFileInfo['mpeg']['audio']['VBR_offsets_absolute'][$i] = $Fraunhofer_OffsetN + $previousbyteoffset;
1838			$previousbyteoffset += $Fraunhofer_OffsetN;
1839		}
1840
1841
1842	} else {
1843
1844		// Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36)
1845		// depending on MPEG layer and number of channels
1846
1847		if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
1848			if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') {
1849				// MPEG-1 (mono)
1850				$VBRidOffset  = 4 + 17; // 0x15
1851				$SideInfoData = substr($headerstring, 4 + 2, 17);
1852			} else {
1853				// MPEG-1 (stereo, joint-stereo, dual-channel)
1854				$VBRidOffset = 4 + 32; // 0x24
1855				$SideInfoData = substr($headerstring, 4 + 2, 32);
1856			}
1857		} else { // 2 or 2.5
1858			if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') {
1859				// MPEG-2, MPEG-2.5 (mono)
1860				$VBRidOffset = 4 + 9;  // 0x0D
1861				$SideInfoData = substr($headerstring, 4 + 2, 9);
1862			} else {
1863				// MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel)
1864				$VBRidOffset = 4 + 17; // 0x15
1865				$SideInfoData = substr($headerstring, 4 + 2, 17);
1866			}
1867		}
1868
1869		if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) {
1870			// 'Xing' is traditional Xing VBR frame
1871			// 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.)
1872
1873			$ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr';
1874			$ThisFileInfo['mpeg']['audio']['VBR_method']   = 'Xing';
1875
1876			$ThisFileInfo['mpeg']['audio']['xing_flags_raw'] = BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4));
1877
1878			$ThisFileInfo['mpeg']['audio']['xing_flags']['frames']    = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000001);
1879			$ThisFileInfo['mpeg']['audio']['xing_flags']['bytes']     = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000002);
1880			$ThisFileInfo['mpeg']['audio']['xing_flags']['toc']       = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000004);
1881			$ThisFileInfo['mpeg']['audio']['xing_flags']['vbr_scale'] = (bool) ($ThisFileInfo['mpeg']['audio']['xing_flags_raw'] & 0x00000008);
1882
1883			if ($ThisFileInfo['mpeg']['audio']['xing_flags']['frames']) {
1884				$ThisFileInfo['mpeg']['audio']['VBR_frames'] = BigEndian2Int(substr($headerstring, $VBRidOffset +  8, 4));
1885			}
1886			if ($ThisFileInfo['mpeg']['audio']['xing_flags']['bytes']) {
1887				$ThisFileInfo['mpeg']['audio']['VBR_bytes']  = BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4));
1888			}
1889
1890			if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') && !empty($ThisFileInfo['mpeg']['audio']['VBR_frames']) && !empty($ThisFileInfo['mpeg']['audio']['VBR_bytes'])) {
1891				$framelengthfloat = $ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames'];
1892				if ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') {
1893					// BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
1894					$ThisFileInfo['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 12;
1895				} else {
1896					// Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
1897					$ThisFileInfo['audio']['bitrate'] = (($framelengthfloat - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 144;
1898				}
1899				$ThisFileInfo['mpeg']['audio']['framelength'] = floor($framelengthfloat);
1900			}
1901
1902			if ($ThisFileInfo['mpeg']['audio']['xing_flags']['toc']) {
1903				$LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100);
1904				for ($i = 0; $i < 100; $i++) {
1905					$ThisFileInfo['mpeg']['audio']['toc'][$i] = ord($LAMEtocData[$i]);
1906				}
1907			}
1908			if ($ThisFileInfo['mpeg']['audio']['xing_flags']['vbr_scale']) {
1909				$ThisFileInfo['mpeg']['audio']['VBR_scale'] = BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4));
1910			}
1911
1912			// http://gabriel.mp3-tech.org/mp3infotag.html
1913			if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') {
1914				$ThisFileInfo['mpeg']['audio']['LAME']['long_version']  = substr($headerstring, $VBRidOffset + 120, 20);
1915				$ThisFileInfo['mpeg']['audio']['LAME']['short_version'] = substr($ThisFileInfo['mpeg']['audio']['LAME']['long_version'], 0, 9);
1916				$ThisFileInfo['mpeg']['audio']['LAME']['long_version']  = rtrim($ThisFileInfo['mpeg']['audio']['LAME']['long_version'], "\x55\xAA");
1917
1918				if ($ThisFileInfo['mpeg']['audio']['LAME']['short_version'] >= 'LAME3.90.') {
1919
1920					// It the LAME tag was only introduced in LAME v3.90
1921					// http://www.hydrogenaudio.org/?act=ST&f=15&t=9933
1922
1923					// Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
1924					// are assuming a 'Xing' identifier offset of 0x24, which is the case for
1925					// MPEG-1 non-mono, but not for other combinations
1926					$LAMEtagOffsetContant = $VBRidOffset - 0x24;
1927
1928					// byte $9B  VBR Quality
1929					// This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
1930					// Actually overwrites original Xing bytes
1931					unset($ThisFileInfo['mpeg']['audio']['VBR_scale']);
1932					$ThisFileInfo['mpeg']['audio']['LAME']['vbr_quality'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));
1933
1934					// bytes $9C-$A4  Encoder short VersionString
1935					$ThisFileInfo['mpeg']['audio']['LAME']['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);
1936					$ThisFileInfo['mpeg']['audio']['LAME']['long_version']  = $ThisFileInfo['mpeg']['audio']['LAME']['short_version'];
1937
1938					// byte $A5  Info Tag revision + VBR method
1939					$LAMEtagRevisionVBRmethod = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));
1940
1941					$ThisFileInfo['mpeg']['audio']['LAME']['tag_revision']      = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
1942					$ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method'] =  $LAMEtagRevisionVBRmethod & 0x0F;
1943					$ThisFileInfo['mpeg']['audio']['LAME']['vbr_method']        = LAMEvbrMethodLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method']);
1944
1945					// byte $A6  Lowpass filter value
1946					$ThisFileInfo['mpeg']['audio']['LAME']['lowpass_frequency'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;
1947
1948					// bytes $A7-$AE  Replay Gain
1949					// http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
1950					// bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
1951					$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] = BigEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
1952					$ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio']      =   BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
1953					$ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] =   BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));
1954
1955					if ($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] == 0) {
1956						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] = false;
1957					}
1958
1959					if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] != 0) {
1960						require_once(GETID3_INCLUDEPATH.'getid3.rgad.php');
1961
1962						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['name']        = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0xE000) >> 13;
1963						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['originator']  = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0x1C00) >> 10;
1964						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['sign_bit']    = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0x0200) >> 9;
1965						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['gain_adjust'] =  $ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_radio'] & 0x01FF;
1966						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['name']       = RGADnameLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['name']);
1967						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['originator'] = RGADoriginatorLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['raw']['originator']);
1968						$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']);
1969
1970						if ($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] !== false) {
1971							$ThisFileInfo['replay_gain']['radio']['peak']   = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'];
1972						}
1973						$ThisFileInfo['replay_gain']['radio']['originator'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['originator'];
1974						$ThisFileInfo['replay_gain']['radio']['adjustment'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['radio']['gain_db'];
1975					}
1976					if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] != 0) {
1977						require_once(GETID3_INCLUDEPATH.'getid3.rgad.php');
1978
1979						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['name']        = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0xE000) >> 13;
1980						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['originator']  = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0x1C00) >> 10;
1981						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['sign_bit']    = ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0x0200) >> 9;
1982						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['gain_adjust'] = $ThisFileInfo['mpeg']['audio']['LAME']['raw']['RGAD_audiophile'] & 0x01FF;
1983						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['name']       = RGADnameLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['name']);
1984						$ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['originator'] = RGADoriginatorLookup($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['raw']['originator']);
1985						$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']);
1986
1987						if ($ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'] !== false) {
1988							$ThisFileInfo['replay_gain']['audiophile']['peak']   = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['peak_amplitude'];
1989						}
1990						$ThisFileInfo['replay_gain']['audiophile']['originator'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['originator'];
1991						$ThisFileInfo['replay_gain']['audiophile']['adjustment'] = $ThisFileInfo['mpeg']['audio']['LAME']['RGAD']['audiophile']['gain_db'];
1992					}
1993
1994
1995					// byte $AF  Encoding flags + ATH Type
1996					$EncodingFlagsATHtype = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
1997					$ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nspsytune']   = (bool) ($EncodingFlagsATHtype & 0x10);
1998					$ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
1999					$ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nogap_next']  = (bool) ($EncodingFlagsATHtype & 0x40);
2000					$ThisFileInfo['mpeg']['audio']['LAME']['encoding_flags']['nogap_prev']  = (bool) ($EncodingFlagsATHtype & 0x80);
2001					$ThisFileInfo['mpeg']['audio']['LAME']['ath_type']                      =         $EncodingFlagsATHtype & 0x0F;
2002
2003					// byte $B0  if ABR {specified bitrate} else {minimal bitrate}
2004					$ABRbitrateMinBitrate = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
2005					if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method'] == 2) { // Average BitRate (ABR)
2006						$ThisFileInfo['mpeg']['audio']['LAME']['bitrate_abr'] = $ABRbitrateMinBitrate;
2007					} elseif ($ABRbitrateMinBitrate > 0) { // Variable BitRate (VBR) - minimum bitrate
2008						$ThisFileInfo['mpeg']['audio']['LAME']['bitrate_min'] = $ABRbitrateMinBitrate;
2009					}
2010
2011					// bytes $B1-$B3  Encoder delays
2012					$EncoderDelays = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
2013					$ThisFileInfo['mpeg']['audio']['LAME']['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
2014					$ThisFileInfo['mpeg']['audio']['LAME']['end_padding']   =  $EncoderDelays & 0x000FFF;
2015
2016					// byte $B4  Misc
2017					$MiscByte = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
2018					$ThisFileInfo['mpeg']['audio']['LAME']['raw']['noise_shaping']       = ($MiscByte & 0x03);
2019					$ThisFileInfo['mpeg']['audio']['LAME']['raw']['stereo_mode']         = ($MiscByte & 0x1C) >> 2;
2020					$ThisFileInfo['mpeg']['audio']['LAME']['raw']['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
2021					$ThisFileInfo['mpeg']['audio']['LAME']['raw']['source_sample_freq']  = ($MiscByte & 0xC0) >> 6;
2022					$ThisFileInfo['mpeg']['audio']['LAME']['noise_shaping']       = $ThisFileInfo['mpeg']['audio']['LAME']['raw']['noise_shaping'];
2023					$ThisFileInfo['mpeg']['audio']['LAME']['stereo_mode']         = LAMEmiscStereoModeLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['stereo_mode']);
2024					$ThisFileInfo['mpeg']['audio']['LAME']['not_optimal_quality'] = (bool) $ThisFileInfo['mpeg']['audio']['LAME']['raw']['not_optimal_quality'];
2025					$ThisFileInfo['mpeg']['audio']['LAME']['source_sample_freq']  = LAMEmiscSourceSampleFrequencyLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['source_sample_freq']);
2026
2027					// byte $B5  MP3 Gain
2028					$ThisFileInfo['mpeg']['audio']['LAME']['raw']['mp3_gain'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
2029					$ThisFileInfo['mpeg']['audio']['LAME']['mp3_gain_db']     = 1.5 * $ThisFileInfo['mpeg']['audio']['LAME']['raw']['mp3_gain'];
2030					$ThisFileInfo['mpeg']['audio']['LAME']['mp3_gain_factor'] = pow(2, ($ThisFileInfo['mpeg']['audio']['LAME']['mp3_gain_db'] / 6));
2031
2032					// bytes $B6-$B7  Preset and surround info
2033					$PresetSurroundBytes = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
2034					// Reserved                                                    = ($PresetSurroundBytes & 0xC000);
2035					$ThisFileInfo['mpeg']['audio']['LAME']['raw']['surround_info'] = ($PresetSurroundBytes & 0x3800);
2036					$ThisFileInfo['mpeg']['audio']['LAME']['surround_info']        = LAMEsurroundInfoLookup($ThisFileInfo['mpeg']['audio']['LAME']['raw']['surround_info']);
2037					$ThisFileInfo['mpeg']['audio']['LAME']['preset_used_id']       = ($PresetSurroundBytes & 0x07FF);
2038
2039					// bytes $B8-$BB  MusicLength
2040					$ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
2041					$ExpectedNumberOfAudioBytes = (($ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'] > 0) ? $ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'] : $ThisFileInfo['mpeg']['audio']['VBR_bytes']);
2042
2043					// bytes $BC-$BD  MusicCRC
2044					$ThisFileInfo['mpeg']['audio']['LAME']['music_crc']    = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));
2045
2046					// bytes $BE-$BF  CRC-16 of Info Tag
2047					$ThisFileInfo['mpeg']['audio']['LAME']['lame_tag_crc'] = BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));
2048
2049
2050					// LAME CBR
2051					if ($ThisFileInfo['mpeg']['audio']['LAME']['raw']['vbr_method'] == 1) {
2052
2053						$ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr';
2054						if (empty($ThisFileInfo['mpeg']['audio']['bitrate']) || ($ThisFileInfo['mpeg']['audio']['LAME']['bitrate_min'] != 255)) {
2055							$ThisFileInfo['mpeg']['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['LAME']['bitrate_min'];
2056						}
2057
2058					}
2059
2060				}
2061			}
2062
2063		} else {
2064
2065			// not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header)
2066			$ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr';
2067			if ($recursivesearch) {
2068				$ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr';
2069				if (RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, true)) {
2070					$recursivesearch = false;
2071					$ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr';
2072				}
2073				if ($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') {
2074					$ThisFileInfo['warning'] .= "\n".'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.';
2075				}
2076			}
2077
2078		}
2079
2080	}
2081
2082	if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']))) {
2083		if (($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) == 1) {
2084			$ThisFileInfo['warning'] .= "\n".'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)';
2085		} elseif ($ExpectedNumberOfAudioBytes > ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) {
2086			$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)';
2087		} else {
2088			$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)';
2089		}
2090	}
2091
2092	if (($ThisFileInfo['mpeg']['audio']['bitrate'] == 'free') && empty($ThisFileInfo['audio']['bitrate'])) {
2093		if (($offset == $ThisFileInfo['avdataoffset']) && empty($ThisFileInfo['mpeg']['audio']['VBR_frames'])) {
2094			$framebytelength = FreeFormatFrameLength($fd, $offset, $ThisFileInfo, true);
2095			if ($framebytelength > 0) {
2096				$ThisFileInfo['mpeg']['audio']['framelength'] = $framebytelength;
2097				if ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') {
2098					// BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
2099					$ThisFileInfo['audio']['bitrate'] = ((($framebytelength / 4) - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 12;
2100				} else {
2101					// Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
2102					$ThisFileInfo['audio']['bitrate'] = (($framebytelength - intval($ThisFileInfo['mpeg']['audio']['padding'])) * $ThisFileInfo['mpeg']['audio']['sample_rate']) / 144;
2103				}
2104			} else {
2105				$ThisFileInfo['error'] .= "\n".'Error calculating frame length of free-format MP3 without Xing/LAME header';
2106			}
2107		}
2108	}
2109
2110	if (($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') && isset($ThisFileInfo['mpeg']['audio']['VBR_frames']) && ($ThisFileInfo['mpeg']['audio']['VBR_frames'] > 1)) {
2111		$ThisFileInfo['mpeg']['audio']['VBR_frames']--; // don't count the Xing / VBRI frame
2112		if (($ThisFileInfo['mpeg']['audio']['version'] == '1') && ($ThisFileInfo['mpeg']['audio']['layer'] == 'I')) {
2113			$ThisFileInfo['mpeg']['audio']['VBR_bitrate'] = ((($ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 384)) / 1000;
2114		} elseif ((($ThisFileInfo['mpeg']['audio']['version'] == '2') || ($ThisFileInfo['mpeg']['audio']['version'] == '2.5')) && ($ThisFileInfo['mpeg']['audio']['layer'] == 'III')) {
2115			$ThisFileInfo['mpeg']['audio']['VBR_bitrate'] = ((($ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 576)) / 1000;
2116		} else {
2117			$ThisFileInfo['mpeg']['audio']['VBR_bitrate'] = ((($ThisFileInfo['mpeg']['audio']['VBR_bytes'] / $ThisFileInfo['mpeg']['audio']['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 1152)) / 1000;
2118		}
2119		if ($ThisFileInfo['mpeg']['audio']['VBR_bitrate'] > 0) {
2120			$ThisFileInfo['audio']['bitrate']         = 1000 * $ThisFileInfo['mpeg']['audio']['VBR_bitrate'];
2121			$ThisFileInfo['mpeg']['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['VBR_bitrate']; // to avoid confusion
2122		}
2123	}
2124
2125	// End variable-bitrate headers
2126	////////////////////////////////////////////////////////////////////////////////////
2127
2128	if ($recursivesearch) {
2129
2130		if (!RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, $ScanAsCBR)) {
2131			return false;
2132		}
2133
2134	}
2135
2136
2137	//if (false) {
2138	//	// experimental side info parsing section - not returning anything useful yet
2139	//
2140	//	$SideInfoBitstream = BigEndian2Bin($SideInfoData);
2141	//	$SideInfoOffset = 0;
2142	//
2143	//	if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
2144	//		if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') {
2145	//			// MPEG-1 (mono)
2146	//			$ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
2147	//			$SideInfoOffset += 9;
2148	//			$SideInfoOffset += 5;
2149	//		} else {
2150	//			// MPEG-1 (stereo, joint-stereo, dual-channel)
2151	//			$ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
2152	//			$SideInfoOffset += 9;
2153	//			$SideInfoOffset += 3;
2154	//		}
2155	//	} else { // 2 or 2.5
2156	//		if ($ThisFileInfo['mpeg']['audio']['channelmode'] == 'mono') {
2157	//			// MPEG-2, MPEG-2.5 (mono)
2158	//			$ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
2159	//			$SideInfoOffset += 8;
2160	//			$SideInfoOffset += 1;
2161	//		} else {
2162	//			// MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel)
2163	//			$ThisFileInfo['mpeg']['audio']['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
2164	//			$SideInfoOffset += 8;
2165	//			$SideInfoOffset += 2;
2166	//		}
2167	//	}
2168	//
2169	//	if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
2170	//		for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) {
2171	//			for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) {
2172	//				$ThisFileInfo['mpeg']['audio']['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2173	//				$SideInfoOffset += 2;
2174	//			}
2175	//		}
2176	//	}
2177	//	for ($granule = 0; $granule < (($ThisFileInfo['mpeg']['audio']['version'] == '1') ? 2 : 1); $granule++) {
2178	//		for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) {
2179	//			$ThisFileInfo['mpeg']['audio']['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12);
2180	//			$SideInfoOffset += 12;
2181	//			$ThisFileInfo['mpeg']['audio']['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
2182	//			$SideInfoOffset += 9;
2183	//			$ThisFileInfo['mpeg']['audio']['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8);
2184	//			$SideInfoOffset += 8;
2185	//			if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
2186	//				$ThisFileInfo['mpeg']['audio']['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
2187	//				$SideInfoOffset += 4;
2188	//			} else {
2189	//				$ThisFileInfo['mpeg']['audio']['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
2190	//				$SideInfoOffset += 9;
2191	//			}
2192	//			$ThisFileInfo['mpeg']['audio']['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2193	//			$SideInfoOffset += 1;
2194	//
2195	//			if ($ThisFileInfo['mpeg']['audio']['window_switching_flag'][$granule][$channel] == '1') {
2196	//
2197	//				$ThisFileInfo['mpeg']['audio']['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2);
2198	//				$SideInfoOffset += 2;
2199	//				$ThisFileInfo['mpeg']['audio']['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2200	//				$SideInfoOffset += 1;
2201	//
2202	//				for ($region = 0; $region < 2; $region++) {
2203	//					$ThisFileInfo['mpeg']['audio']['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
2204	//					$SideInfoOffset += 5;
2205	//				}
2206	//				$ThisFileInfo['mpeg']['audio']['table_select'][$granule][$channel][2] = 0;
2207	//
2208	//				for ($window = 0; $window < 3; $window++) {
2209	//					$ThisFileInfo['mpeg']['audio']['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3);
2210	//					$SideInfoOffset += 3;
2211	//				}
2212	//
2213	//			} else {
2214	//
2215	//				for ($region = 0; $region < 3; $region++) {
2216	//					$ThisFileInfo['mpeg']['audio']['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
2217	//					$SideInfoOffset += 5;
2218	//				}
2219	//
2220	//				$ThisFileInfo['mpeg']['audio']['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
2221	//				$SideInfoOffset += 4;
2222	//				$ThisFileInfo['mpeg']['audio']['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3);
2223	//				$SideInfoOffset += 3;
2224	//				$ThisFileInfo['mpeg']['audio']['block_type'][$granule][$channel] = 0;
2225	//			}
2226	//
2227	//			if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
2228	//				$ThisFileInfo['mpeg']['audio']['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2229	//				$SideInfoOffset += 1;
2230	//			}
2231	//			$ThisFileInfo['mpeg']['audio']['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2232	//			$SideInfoOffset += 1;
2233	//			$ThisFileInfo['mpeg']['audio']['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
2234	//			$SideInfoOffset += 1;
2235	//		}
2236	//	}
2237	//}
2238
2239	return true;
2240}
2241
2242function RecursiveFrameScanning(&$fd, &$ThisFileInfo, &$offset, &$nextframetestoffset, $ScanAsCBR) {
2243	for ($i = 0; $i < MPEG_VALID_CHECK_FRAMES; $i++) {
2244		// check next MPEG_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch
2245		if (($nextframetestoffset + 4) >= $ThisFileInfo['avdataend']) {
2246			// end of file
2247			return true;
2248		}
2249
2250		$nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']);
2251		if (decodeMPEGaudioHeader($fd, $nextframetestoffset, $nextframetestarray, false)) {
2252			if ($ScanAsCBR) {
2253				// force CBR mode, used for trying to pick out invalid audio streams with
2254				// valid(?) VBR headers, or VBR streams with no VBR header
2255				if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($ThisFileInfo['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $ThisFileInfo['mpeg']['audio']['bitrate'])) {
2256					return false;
2257				}
2258			}
2259
2260
2261			// next frame is OK, get ready to check the one after that
2262			if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) {
2263				$nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength'];
2264			} else {
2265				$ThisFileInfo['error'] .= "\n".'Frame at offset ('.$offset.') is has an invalid frame length.';
2266				return false;
2267			}
2268
2269		} else {
2270
2271			// next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence
2272			$ThisFileInfo['error'] .= "\n".'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.';
2273
2274			return false;
2275		}
2276	}
2277	return true;
2278}
2279
2280function FreeFormatFrameLength($fd, $offset, &$ThisFileInfo, $deepscan=false) {
2281	fseek($fd, $offset, SEEK_SET);
2282	$MPEGaudioData = fread($fd, 32768);
2283
2284	$SyncPattern1 = substr($MPEGaudioData, 0, 4);
2285	// may be different pattern due to padding
2286	$SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) | 0x02).$SyncPattern1[3];
2287	if ($SyncPattern2 === $SyncPattern1) {
2288		$SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) & 0xFD).$SyncPattern1[3];
2289	}
2290
2291	$framelength = false;
2292	$framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4);
2293	$framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4);
2294	if ($framelength1 > 4) {
2295		$framelength = $framelength1;
2296	}
2297	if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
2298		$framelength = $framelength2;
2299	}
2300	if (!$framelength) {
2301
2302		// LAME 3.88 has a different value for modeextension on the first frame vs the rest
2303		$framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4);
2304		$framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4);
2305
2306		if ($framelength1 > 4) {
2307			$framelength = $framelength1;
2308		}
2309		if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
2310			$framelength = $framelength2;
2311		}
2312		if (!$framelength) {
2313			$ThisFileInfo['error'] .= "\n".'Cannot find next free-format synch pattern ('.PrintHexBytes($SyncPattern1).' or '.PrintHexBytes($SyncPattern2).') after offset '.$offset;
2314			return false;
2315		} else {
2316			$ThisFileInfo['warning'] .= "\n".'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)';
2317			$ThisFileInfo['audio']['codec']   = 'LAME';
2318			$ThisFileInfo['audio']['encoder'] = 'LAME3.88';
2319			$SyncPattern1 = substr($SyncPattern1, 0, 3);
2320			$SyncPattern2 = substr($SyncPattern2, 0, 3);
2321		}
2322	}
2323
2324	if ($deepscan) {
2325
2326		$ActualFrameLengthValues = array();
2327		$nextoffset = $offset + $framelength;
2328		while ($nextoffset < ($ThisFileInfo['avdataend'] - 6)) {
2329			fseek($fd, $nextoffset - 1, SEEK_SET);
2330			$NextSyncPattern = fread($fd, 6);
2331			if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) {
2332				// good - found where expected
2333				$ActualFrameLengthValues[] = $framelength;
2334			} elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) {
2335				// ok - found one byte earlier than expected (last frame wasn't padded, first frame was)
2336				$ActualFrameLengthValues[] = ($framelength - 1);
2337				$nextoffset--;
2338			} elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) {
2339				// ok - found one byte later than expected (last frame was padded, first frame wasn't)
2340				$ActualFrameLengthValues[] = ($framelength + 1);
2341				$nextoffset++;
2342			} else {
2343				$ThisFileInfo['error'] .= "\n".'Did not find expected free-format sync pattern at offset '.$nextoffset;
2344				return false;
2345			}
2346			$nextoffset += $framelength;
2347		}
2348		if (count($ActualFrameLengthValues) > 0) {
2349			$framelength = round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues));
2350		}
2351	}
2352	return $framelength;
2353}
2354
2355
2356function getOnlyMPEGaudioInfo($fd, &$ThisFileInfo, $avdataoffset, $BitrateHistogram=false) {
2357	// looks for synch, decodes MPEG audio header
2358
2359	fseek($fd, $avdataoffset, SEEK_SET);
2360	$header = '';
2361	$SynchSeekOffset = 0;
2362
2363	if (!defined('CONST_FF')) {
2364		define('CONST_FF', chr(0xFF));
2365		define('CONST_E0', chr(0xE0));
2366	}
2367
2368	static $MPEGaudioVersionLookup;
2369	static $MPEGaudioLayerLookup;
2370	static $MPEGaudioBitrateLookup;
2371	if (empty($MPEGaudioVersionLookup)) {
2372		$MPEGaudioVersionLookup = MPEGaudioVersionArray();
2373		$MPEGaudioLayerLookup   = MPEGaudioLayerArray();
2374		$MPEGaudioBitrateLookup = MPEGaudioBitrateArray();
2375
2376	}
2377
2378	$header_len = strlen($header) - round(32768 / 2);
2379	while (true) {
2380
2381		if (($SynchSeekOffset > $header_len) && (($avdataoffset + $SynchSeekOffset)  < $ThisFileInfo['avdataend']) && !feof($fd)) {
2382
2383			if ($SynchSeekOffset > 131072) {
2384				// if a synch's not found within the first 128k bytes, then give up
2385				$ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch within the first 131072 bytes';
2386				if (isset($ThisFileInfo['audio']['bitrate'])) {
2387					unset($ThisFileInfo['audio']['bitrate']);
2388				}
2389				if (isset($ThisFileInfo['mpeg']['audio'])) {
2390					unset($ThisFileInfo['mpeg']['audio']);
2391				}
2392				if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || (count($ThisFileInfo['mpeg']) == 0))) {
2393					unset($ThisFileInfo['mpeg']);
2394				}
2395				return false;
2396
2397			} elseif ($header .= fread($fd, 32768)) {
2398
2399				// great
2400				$header_len = strlen($header) - round(32768 / 2);
2401
2402			} else {
2403
2404				$ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch before end of file';
2405				if (isset($ThisFileInfo['audio']['bitrate'])) {
2406					unset($ThisFileInfo['audio']['bitrate']);
2407				}
2408				if (isset($ThisFileInfo['mpeg']['audio'])) {
2409					unset($ThisFileInfo['mpeg']['audio']);
2410				}
2411				if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || (count($ThisFileInfo['mpeg']) == 0))) {
2412					unset($ThisFileInfo['mpeg']);
2413				}
2414				return false;
2415
2416			}
2417		}
2418
2419		if (($SynchSeekOffset + 1) >= strlen($header)) {
2420			$ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch before end of file';
2421			return false;
2422		}
2423
2424		if (($header[$SynchSeekOffset] == CONST_FF) && ($header[($SynchSeekOffset + 1)] > CONST_E0)) { // synch detected
2425
2426			if (!isset($FirstFrameThisfileInfo) && !isset($ThisFileInfo['mpeg']['audio'])) {
2427				$FirstFrameThisfileInfo = $ThisFileInfo;
2428				$FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset;
2429				if (!decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $FirstFrameThisfileInfo, false)) {
2430					// if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's
2431					// garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below
2432					unset($FirstFrameThisfileInfo);
2433				}
2434			}
2435			$dummy = $ThisFileInfo; // only overwrite real data if valid header found
2436
2437			if (decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $dummy, true)) {
2438
2439				$ThisFileInfo = $dummy;
2440				$ThisFileInfo['avdataoffset'] = $avdataoffset + $SynchSeekOffset;
2441				switch ($ThisFileInfo['fileformat']) {
2442					case '':
2443					case 'id3':
2444					case 'ape':
2445					case 'mp3':
2446						$ThisFileInfo['fileformat']               = 'mp3';
2447						$ThisFileInfo['audio']['dataformat']      = 'mp3';
2448				}
2449				if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) {
2450					if (!CloseMatch($ThisFileInfo['audio']['bitrate'], $FirstFrameThisfileInfo['audio']['bitrate'], 1)) {
2451						// If there is garbage data between a valid VBR header frame and a sequence
2452						// of valid MPEG-audio frames the VBR data is no longer discarded.
2453						$ThisFileInfo = $FirstFrameThisfileInfo;
2454						$ThisFileInfo['avdataoffset']        = $FirstFrameAVDataOffset;
2455						$ThisFileInfo['fileformat']          = 'mp3';
2456						$ThisFileInfo['audio']['dataformat'] = 'mp3';
2457						$dummy                               = $ThisFileInfo;
2458						unset($dummy['mpeg']['audio']);
2459						$GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength'];
2460						$GarbageOffsetEnd   = $avdataoffset + $SynchSeekOffset;
2461						if (decodeMPEGaudioHeader($fd, $GarbageOffsetEnd, $dummy, true, true)) {
2462
2463							$ThisFileInfo = $dummy;
2464							$ThisFileInfo['avdataoffset'] = $GarbageOffsetEnd;
2465							$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;
2466
2467						} else {
2468
2469							$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.')';
2470
2471						}
2472					}
2473				}
2474
2475				if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode']) && ($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($ThisFileInfo['mpeg']['audio']['VBR_method'])) {
2476					// VBR file with no VBR header
2477					$BitrateHistogram = true;
2478				}
2479
2480				if ($BitrateHistogram) {
2481
2482					$ThisFileInfo['mpeg']['audio']['stereo_distribution']  = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0);
2483					$ThisFileInfo['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0);
2484
2485					if ($ThisFileInfo['mpeg']['audio']['version'] == '1') {
2486						if ($ThisFileInfo['mpeg']['audio']['layer'] == 'III') {
2487							$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);
2488						} elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 'II') {
2489							$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);
2490						} elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') {
2491							$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);
2492						}
2493					} elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 'I') {
2494						$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);
2495					} else {
2496						$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);
2497					}
2498
2499					$dummy = array('error'=>$ThisFileInfo['error'], 'warning'=>$ThisFileInfo['warning'], 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']);
2500					$synchstartoffset = $ThisFileInfo['avdataoffset'];
2501
2502					$FastMode = false;
2503					while (decodeMPEGaudioHeader($fd, $synchstartoffset, $dummy, false, false, $FastMode)) {
2504						$FastMode = true;
2505						$thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']];
2506
2507						$ThisFileInfo['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]++;
2508						$ThisFileInfo['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]++;
2509						$ThisFileInfo['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]++;
2510						if (empty($dummy['mpeg']['audio']['framelength'])) {
2511							$ThisFileInfo['warning'] .= "\n".'Invalid/missing framelength in histogram analysis - aborting';
2512$synchstartoffset += 4;
2513//							return false;
2514						}
2515						$synchstartoffset += $dummy['mpeg']['audio']['framelength'];
2516					}
2517
2518					$bittotal     = 0;
2519					$framecounter = 0;
2520					foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) {
2521						$framecounter += $bitratecount;
2522						if ($bitratevalue != 'free') {
2523							$bittotal += ($bitratevalue * $bitratecount);
2524						}
2525					}
2526					if ($framecounter == 0) {
2527						$ThisFileInfo['error'] .= "\n".'Corrupt MP3 file: framecounter == zero';
2528						return false;
2529					}
2530					$ThisFileInfo['mpeg']['audio']['frame_count'] = $framecounter;
2531					$ThisFileInfo['mpeg']['audio']['bitrate']     = 1000 * ($bittotal / $framecounter);
2532
2533					$ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate'];
2534
2535
2536					// Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently
2537					$distinct_bitrates = 0;
2538					foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) {
2539						if ($bitrate_count > 0) {
2540							$distinct_bitrates++;
2541						}
2542					}
2543					if ($distinct_bitrates > 1) {
2544						$ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr';
2545					} else {
2546						$ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr';
2547					}
2548					$ThisFileInfo['audio']['bitrate_mode'] = $ThisFileInfo['mpeg']['audio']['bitrate_mode'];
2549
2550				}
2551
2552				break; // exit while()
2553			}
2554		}
2555
2556		$SynchSeekOffset++;
2557		if (($avdataoffset + $SynchSeekOffset) >= $ThisFileInfo['avdataend']) {
2558			// end of file/data
2559
2560			if (empty($ThisFileInfo['mpeg']['audio'])) {
2561
2562				$ThisFileInfo['error'] .= "\n".'could not find valid MPEG synch before end of file';
2563				if (isset($ThisFileInfo['audio']['bitrate'])) {
2564					unset($ThisFileInfo['audio']['bitrate']);
2565				}
2566				if (isset($ThisFileInfo['mpeg']['audio'])) {
2567					unset($ThisFileInfo['mpeg']['audio']);
2568				}
2569				if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || empty($ThisFileInfo['mpeg']))) {
2570					unset($ThisFileInfo['mpeg']);
2571				}
2572				return false;
2573
2574			}
2575			break;
2576		}
2577
2578	}
2579	$ThisFileInfo['audio']['bits_per_sample'] = 16;
2580	$ThisFileInfo['audio']['channels']        = $ThisFileInfo['mpeg']['audio']['channels'];
2581	$ThisFileInfo['audio']['channelmode']     = $ThisFileInfo['mpeg']['audio']['channelmode'];
2582	$ThisFileInfo['audio']['sample_rate']     = $ThisFileInfo['mpeg']['audio']['sample_rate'];
2583	return true;
2584}
2585
2586
2587function MPEGaudioVersionArray() {
2588	static $MPEGaudioVersion = array('2.5', false, '2', '1');
2589	return $MPEGaudioVersion;
2590}
2591
2592function MPEGaudioLayerArray() {
2593	static $MPEGaudioLayer = array(false, 'III', 'II', 'I');
2594	return $MPEGaudioLayer;
2595}
2596
2597function MPEGaudioBitrateArray() {
2598	static $MPEGaudioBitrate;
2599	if (empty($MPEGaudioBitrate)) {
2600		$MPEGaudioBitrate['1']['I']     = array('free', 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448);
2601		$MPEGaudioBitrate['1']['II']    = array('free', 32, 48, 56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320, 384);
2602		$MPEGaudioBitrate['1']['III']   = array('free', 32, 40, 48,  56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320);
2603		$MPEGaudioBitrate['2']['I']     = array('free', 32, 48, 56,  64,  80,  96, 112, 128, 144, 160, 176, 192, 224, 256);
2604		$MPEGaudioBitrate['2']['II']    = array('free',  8, 16, 24,  32,  40,  48,  56,  64,  80,  96, 112, 128, 144, 160);
2605		$MPEGaudioBitrate['2']['III']   = $MPEGaudioBitrate['2']['II'];
2606		$MPEGaudioBitrate['2.5']['I']   = $MPEGaudioBitrate['2']['I'];
2607		$MPEGaudioBitrate['2.5']['II']  = $MPEGaudioBitrate['2']['II'];
2608		$MPEGaudioBitrate['2.5']['III'] = $MPEGaudioBitrate['2']['III'];
2609	}
2610	return $MPEGaudioBitrate;
2611}
2612
2613function MPEGaudioFrequencyArray() {
2614	static $MPEGaudioFrequency;
2615	if (empty($MPEGaudioFrequency)) {
2616		$MPEGaudioFrequency['1']   = array(44100, 48000, 32000);
2617		$MPEGaudioFrequency['2']   = array(22050, 24000, 16000);
2618		$MPEGaudioFrequency['2.5'] = array(11025, 12000,  8000);
2619	}
2620	return $MPEGaudioFrequency;
2621}
2622
2623function MPEGaudioChannelModeArray() {
2624	static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono');
2625	return $MPEGaudioChannelMode;
2626}
2627
2628function MPEGaudioModeExtensionArray() {
2629	static $MPEGaudioModeExtension;
2630	if (empty($MPEGaudioModeExtension)) {
2631		$MPEGaudioModeExtension['I']   = array('4-31', '8-31', '12-31', '16-31');
2632		$MPEGaudioModeExtension['II']  = array('4-31', '8-31', '12-31', '16-31');
2633		$MPEGaudioModeExtension['III'] = array('', 'IS', 'MS', 'IS+MS');
2634	}
2635	return $MPEGaudioModeExtension;
2636}
2637
2638function MPEGaudioEmphasisArray() {
2639	static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17');
2640	return $MPEGaudioEmphasis;
2641}
2642
2643
2644function MPEGaudioHeaderBytesValid($head4) {
2645	return MPEGaudioHeaderValid(MPEGaudioHeaderDecode($head4));
2646}
2647
2648function MPEGaudioHeaderValid($rawarray, $echoerrors=false) {
2649
2650	if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) {
2651		return false;
2652	}
2653
2654	static $MPEGaudioVersionLookup;
2655	static $MPEGaudioLayerLookup;
2656	static $MPEGaudioBitrateLookup;
2657	static $MPEGaudioFrequencyLookup;
2658	static $MPEGaudioChannelModeLookup;
2659	static $MPEGaudioModeExtensionLookup;
2660	static $MPEGaudioEmphasisLookup;
2661	if (empty($MPEGaudioVersionLookup)) {
2662		$MPEGaudioVersionLookup       = MPEGaudioVersionArray();
2663		$MPEGaudioLayerLookup         = MPEGaudioLayerArray();
2664		$MPEGaudioBitrateLookup       = MPEGaudioBitrateArray();
2665		$MPEGaudioFrequencyLookup     = MPEGaudioFrequencyArray();
2666		$MPEGaudioChannelModeLookup   = MPEGaudioChannelModeArray();
2667		$MPEGaudioModeExtensionLookup = MPEGaudioModeExtensionArray();
2668		$MPEGaudioEmphasisLookup      = MPEGaudioEmphasisArray();
2669	}
2670
2671	if (isset($MPEGaudioVersionLookup[$rawarray['version']])) {
2672		$decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']];
2673	} else {
2674		if ($echoerrors) {
2675			echo "\n".'invalid Version ('.$rawarray['version'].')';
2676		}
2677		return false;
2678	}
2679	if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) {
2680		$decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']];
2681	} else {
2682		if ($echoerrors) {
2683			echo "\n".'invalid Layer ('.$rawarray['layer'].')';
2684		}
2685		return false;
2686	}
2687	if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) {
2688		if ($echoerrors) {
2689			echo "\n".'invalid Bitrate ('.$rawarray['bitrate'].')';
2690		}
2691		if ($rawarray['bitrate'] == 15) {
2692			// known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0
2693			// let it go through here otherwise file will not be identified
2694		} else {
2695			return false;
2696		}
2697	}
2698	if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) {
2699		if ($echoerrors) {
2700			echo "\n".'invalid Frequency ('.$rawarray['sample_rate'].')';
2701		}
2702		return false;
2703	}
2704	if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) {
2705		if ($echoerrors) {
2706			echo "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')';
2707		}
2708		return false;
2709	}
2710	if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) {
2711		if ($echoerrors) {
2712			echo "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')';
2713		}
2714		return false;
2715	}
2716	if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) {
2717		if ($echoerrors) {
2718			echo "\n".'invalid Emphasis ('.$rawarray['emphasis'].')';
2719		}
2720		return false;
2721	}
2722	// These are just either set or not set, you can't mess that up :)
2723	// $rawarray['protection'];
2724	// $rawarray['padding'];
2725	// $rawarray['private'];
2726	// $rawarray['copyright'];
2727	// $rawarray['original'];
2728
2729	return true;
2730}
2731
2732function MPEGaudioHeaderDecode($Header4Bytes) {
2733	// AAAA AAAA  AAAB BCCD  EEEE FFGH  IIJJ KLMM
2734	// A - Frame sync (all bits set)
2735	// B - MPEG Audio version ID
2736	// C - Layer description
2737	// D - Protection bit
2738	// E - Bitrate index
2739	// F - Sampling rate frequency index
2740	// G - Padding bit
2741	// H - Private bit
2742	// I - Channel Mode
2743	// J - Mode extension (Only if Joint stereo)
2744	// K - Copyright
2745	// L - Original
2746	// M - Emphasis
2747
2748	if (strlen($Header4Bytes) != 4) {
2749		return false;
2750	}
2751
2752	$MPEGrawHeader['synch']         = (BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4;
2753	$MPEGrawHeader['version']       = (ord($Header4Bytes[1]) & 0x18) >> 3; //    BB
2754	$MPEGrawHeader['layer']         = (ord($Header4Bytes[1]) & 0x06) >> 1; //      CC
2755	$MPEGrawHeader['protection']    = (ord($Header4Bytes[1]) & 0x01);      //        D
2756	$MPEGrawHeader['bitrate']       = (ord($Header4Bytes[2]) & 0xF0) >> 4; // EEEE
2757	$MPEGrawHeader['sample_rate']   = (ord($Header4Bytes[2]) & 0x0C) >> 2; //     FF
2758	$MPEGrawHeader['padding']       = (ord($Header4Bytes[2]) & 0x02) >> 1; //       G
2759	$MPEGrawHeader['private']       = (ord($Header4Bytes[2]) & 0x01);      //        H
2760	$MPEGrawHeader['channelmode']   = (ord($Header4Bytes[3]) & 0xC0) >> 6; // II
2761	$MPEGrawHeader['modeextension'] = (ord($Header4Bytes[3]) & 0x30) >> 4; //   JJ
2762	$MPEGrawHeader['copyright']     = (ord($Header4Bytes[3]) & 0x08) >> 3; //     K
2763	$MPEGrawHeader['original']      = (ord($Header4Bytes[3]) & 0x04) >> 2; //      L
2764	$MPEGrawHeader['emphasis']      = (ord($Header4Bytes[3]) & 0x03);      //       MM
2765
2766	return $MPEGrawHeader;
2767}
2768
2769function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) {
2770	static $AudioFrameLengthCache = array();
2771
2772	if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) {
2773		$AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false;
2774		if ($bitrate != 'free') {
2775
2776			if ($version == '1') {
2777
2778				if ($layer == 'I') {
2779
2780					// For Layer I slot is 32 bits long
2781					$FrameLengthCoefficient = 48;
2782					$SlotLength = 4;
2783
2784				} else { // Layer II / III
2785
2786					// for Layer II and Layer III slot is 8 bits long.
2787					$FrameLengthCoefficient = 144;
2788					$SlotLength = 1;
2789
2790				}
2791
2792			} else { // MPEG-2 / MPEG-2.5
2793
2794				if ($layer == 'I') {
2795
2796					// For Layer I slot is 32 bits long
2797					$FrameLengthCoefficient = 24;
2798					$SlotLength = 4;
2799
2800				} elseif ($layer == 'II') {
2801
2802					// for Layer II and Layer III slot is 8 bits long.
2803					$FrameLengthCoefficient = 144;
2804					$SlotLength = 1;
2805
2806				} else { // III
2807
2808					// for Layer II and Layer III slot is 8 bits long.
2809					$FrameLengthCoefficient = 72;
2810					$SlotLength = 1;
2811
2812				}
2813
2814			}
2815
2816			// FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding
2817			// http://66.96.216.160/cgi-bin/YaBB.pl?board=c&action=display&num=1018474068
2818			// -> [Finding the next frame synch] on www.r3mix.net forums if the above link goes dead
2819			if ($samplerate > 0) {
2820				$NewFramelength  = ($FrameLengthCoefficient * $bitrate * 1000) / $samplerate;
2821				$NewFramelength  = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer II/III, 4 bytes for Layer I)
2822				if ($padding) {
2823					$NewFramelength += $SlotLength;
2824				}
2825				$AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength;
2826			}
2827		}
2828	}
2829	return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate];
2830}
2831
2832function LAMEvbrMethodLookup($VBRmethodID) {
2833	static $LAMEvbrMethodLookup = array();
2834	if (empty($LAMEvbrMethodLookup)) {
2835		$LAMEvbrMethodLookup[0x00] = 'unknown';
2836		$LAMEvbrMethodLookup[0x01] = 'cbr';
2837		$LAMEvbrMethodLookup[0x02] = 'abr';
2838		$LAMEvbrMethodLookup[0x03] = 'vbr-old / vbr-rh';
2839		$LAMEvbrMethodLookup[0x04] = 'vbr-mtrh';
2840		$LAMEvbrMethodLookup[0x05] = 'vbr-new / vbr-mt';
2841	}
2842	return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : '');
2843}
2844
2845function LAMEmiscStereoModeLookup($StereoModeID) {
2846	static $LAMEmiscStereoModeLookup = array();
2847	if (empty($LAMEmiscStereoModeLookup)) {
2848		$LAMEmiscStereoModeLookup[0] = 'mono';
2849		$LAMEmiscStereoModeLookup[1] = 'stereo';
2850		$LAMEmiscStereoModeLookup[2] = 'dual';
2851		$LAMEmiscStereoModeLookup[3] = 'joint';
2852		$LAMEmiscStereoModeLookup[4] = 'forced';
2853		$LAMEmiscStereoModeLookup[5] = 'auto';
2854		$LAMEmiscStereoModeLookup[6] = 'intensity';
2855		$LAMEmiscStereoModeLookup[7] = 'other';
2856	}
2857	return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : '');
2858}
2859
2860function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) {
2861	static $LAMEmiscSourceSampleFrequencyLookup = array();
2862	if (empty($LAMEmiscSourceSampleFrequencyLookup)) {
2863		$LAMEmiscSourceSampleFrequencyLookup[0] = '<= 32 kHz';
2864		$LAMEmiscSourceSampleFrequencyLookup[1] = '44.1 kHz';
2865		$LAMEmiscSourceSampleFrequencyLookup[2] = '48 kHz';
2866		$LAMEmiscSourceSampleFrequencyLookup[3] = '> 48kHz';
2867	}
2868	return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : '');
2869}
2870
2871function LAMEsurroundInfoLookup($SurroundInfoID) {
2872	static $LAMEsurroundInfoLookup = array();
2873	if (empty($LAMEsurroundInfoLookup)) {
2874		$LAMEsurroundInfoLookup[0] = 'no surround info';
2875		$LAMEsurroundInfoLookup[1] = 'DPL encoding';
2876		$LAMEsurroundInfoLookup[2] = 'DPL2 encoding';
2877		$LAMEsurroundInfoLookup[3] = 'Ambisonic encoding';
2878	}
2879	return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved');
2880}
2881
2882for ($i = 0x00; $i <= 0xFF; $i++) {
2883	$head4 = "\xFF\xFE".chr($i)."\x00";
2884	$isvalid = MPEGaudioHeaderBytesValid($head4);
2885	echo '<div style="color: '.($isvalid ? 'green' : 'red').';">'.str_pad(strtoupper(dechex($i)), 2, '0', STR_PAD_LEFT).' = '.htmlentities(chr($i)).' = '.($isvalid ? 'valid' : 'INVALID').'</div>';
2886}
2887