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