wp/wp-includes/ID3/getid3.lib.php
changeset 16 a86126ab1dd4
parent 7 cf61fcea0001
child 18 be944660c56a
--- a/wp/wp-includes/ID3/getid3.lib.php	Tue Oct 22 16:11:46 2019 +0200
+++ b/wp/wp-includes/ID3/getid3.lib.php	Tue Dec 15 13:49:49 2020 +0100
@@ -1,27 +1,34 @@
 <?php
+
 /////////////////////////////////////////////////////////////////
 /// getID3() by James Heinrich <info@getid3.org>               //
-//  available at http://getid3.sourceforge.net                 //
-//            or http://www.getid3.org                         //
-//          also https://github.com/JamesHeinrich/getID3       //
-/////////////////////////////////////////////////////////////////
+//  available at https://github.com/JamesHeinrich/getID3       //
+//            or https://www.getid3.org                        //
+//            or http://getid3.sourceforge.net                 //
 //                                                             //
 // getid3.lib.php - part of getID3()                           //
-// See readme.txt for more details                             //
+//  see readme.txt for more details                            //
 //                                                            ///
 /////////////////////////////////////////////////////////////////
 
 
 class getid3_lib
 {
-
+	/**
+	 * @param string      $string
+	 * @param bool        $hex
+	 * @param bool        $spaces
+	 * @param string|bool $htmlencoding
+	 *
+	 * @return string
+	 */
 	public static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') {
 		$returnstring = '';
 		for ($i = 0; $i < strlen($string); $i++) {
 			if ($hex) {
-				$returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT);
+				$returnstring .= str_pad(dechex(ord($string[$i])), 2, '0', STR_PAD_LEFT);
 			} else {
-				$returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : '¤');
+				$returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string[$i]) ? $string[$i] : '¤');
 			}
 			if ($spaces) {
 				$returnstring .= ' ';
@@ -36,9 +43,14 @@
 		return $returnstring;
 	}
 
+	/**
+	 * Truncates a floating-point number at the decimal point.
+	 *
+	 * @param float $floatnumber
+	 *
+	 * @return float|int returns int (if possible, otherwise float)
+	 */
 	public static function trunc($floatnumber) {
-		// truncates a floating-point number at the decimal point
-		// returns int (if possible, otherwise float)
 		if ($floatnumber >= 1) {
 			$truncatednumber = floor($floatnumber);
 		} elseif ($floatnumber <= -1) {
@@ -52,7 +64,12 @@
 		return $truncatednumber;
 	}
 
-
+	/**
+	 * @param int|null $variable
+	 * @param int      $increment
+	 *
+	 * @return bool
+	 */
 	public static function safe_inc(&$variable, $increment=1) {
 		if (isset($variable)) {
 			$variable += $increment;
@@ -62,6 +79,11 @@
 		return true;
 	}
 
+	/**
+	 * @param int|float $floatnum
+	 *
+	 * @return int|float
+	 */
 	public static function CastAsInt($floatnum) {
 		// convert to float if not already
 		$floatnum = (float) $floatnum;
@@ -77,6 +99,11 @@
 		return $floatnum;
 	}
 
+	/**
+	 * @param int $num
+	 *
+	 * @return bool
+	 */
 	public static function intValueSupported($num) {
 		// check if integers are 64-bit
 		static $hasINT64 = null;
@@ -87,34 +114,49 @@
 			}
 		}
 		// if integers are 64-bit - no other check required
-		if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) {
+		if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) { // phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_minFound
 			return true;
 		}
 		return false;
 	}
 
+	/**
+	 * @param string $fraction
+	 *
+	 * @return float
+	 */
 	public static function DecimalizeFraction($fraction) {
 		list($numerator, $denominator) = explode('/', $fraction);
 		return $numerator / ($denominator ? $denominator : 1);
 	}
 
-
+	/**
+	 * @param string $binarynumerator
+	 *
+	 * @return float
+	 */
 	public static function DecimalBinary2Float($binarynumerator) {
 		$numerator   = self::Bin2Dec($binarynumerator);
 		$denominator = self::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator)));
 		return ($numerator / $denominator);
 	}
 
-
+	/**
+	 * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
+	 *
+	 * @param string $binarypointnumber
+	 * @param int    $maxbits
+	 *
+	 * @return array
+	 */
 	public static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) {
-		// http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
 		if (strpos($binarypointnumber, '.') === false) {
 			$binarypointnumber = '0.'.$binarypointnumber;
-		} elseif ($binarypointnumber{0} == '.') {
+		} elseif ($binarypointnumber[0] == '.') {
 			$binarypointnumber = '0'.$binarypointnumber;
 		}
 		$exponent = 0;
-		while (($binarypointnumber{0} != '1') || (substr($binarypointnumber, 1, 1) != '.')) {
+		while (($binarypointnumber[0] != '1') || (substr($binarypointnumber, 1, 1) != '.')) {
 			if (substr($binarypointnumber, 1, 1) == '.') {
 				$exponent--;
 				$binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3);
@@ -122,16 +164,21 @@
 				$pointpos = strpos($binarypointnumber, '.');
 				$exponent += ($pointpos - 1);
 				$binarypointnumber = str_replace('.', '', $binarypointnumber);
-				$binarypointnumber = $binarypointnumber{0}.'.'.substr($binarypointnumber, 1);
+				$binarypointnumber = $binarypointnumber[0].'.'.substr($binarypointnumber, 1);
 			}
 		}
 		$binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT);
 		return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent);
 	}
 
-
+	/**
+	 * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
+	 *
+	 * @param float $floatvalue
+	 *
+	 * @return string
+	 */
 	public static function Float2BinaryDecimal($floatvalue) {
-		// http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
 		$maxbits = 128; // to how many bits of precision should the calculations be taken?
 		$intpart   = self::trunc($floatvalue);
 		$floatpart = abs($floatvalue - $intpart);
@@ -145,9 +192,17 @@
 		return $binarypointnumber;
 	}
 
-
+	/**
+	 * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html
+	 *
+	 * @param float $floatvalue
+	 * @param int $bits
+	 *
+	 * @return string|false
+	 */
 	public static function Float2String($floatvalue, $bits) {
-		// http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html
+		$exponentbits = 0;
+		$fractionbits = 0;
 		switch ($bits) {
 			case 32:
 				$exponentbits = 8;
@@ -161,7 +216,6 @@
 
 			default:
 				return false;
-				break;
 		}
 		if ($floatvalue >= 0) {
 			$signbit = '0';
@@ -176,22 +230,34 @@
 		return self::BigEndian2String(self::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false);
 	}
 
-
+	/**
+	 * @param string $byteword
+	 *
+	 * @return float|false
+	 */
 	public static function LittleEndian2Float($byteword) {
 		return self::BigEndian2Float(strrev($byteword));
 	}
 
-
+	/**
+	 * ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
+	 *
+	 * @link http://www.psc.edu/general/software/packages/ieee/ieee.html
+	 * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
+	 *
+	 * @param string $byteword
+	 *
+	 * @return float|false
+	 */
 	public static function BigEndian2Float($byteword) {
-		// ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
-		// http://www.psc.edu/general/software/packages/ieee/ieee.html
-		// http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
-
 		$bitword = self::BigEndian2Bin($byteword);
 		if (!$bitword) {
 			return 0;
 		}
-		$signbit = $bitword{0};
+		$signbit = $bitword[0];
+		$floatvalue = 0;
+		$exponentbits = 0;
+		$fractionbits = 0;
 
 		switch (strlen($byteword) * 8) {
 			case 32:
@@ -208,7 +274,7 @@
 				// 80-bit Apple SANE format
 				// http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/
 				$exponentstring = substr($bitword, 1, 15);
-				$isnormalized = intval($bitword{16});
+				$isnormalized = intval($bitword[16]);
 				$fractionstring = substr($bitword, 17, 63);
 				$exponent = pow(2, self::Bin2Dec($exponentstring) - 16383);
 				$fraction = $isnormalized + self::DecimalBinary2Float($fractionstring);
@@ -217,11 +283,9 @@
 					$floatvalue *= -1;
 				}
 				return $floatvalue;
-				break;
 
 			default:
 				return false;
-				break;
 		}
 		$exponentstring = substr($bitword, 1, $exponentbits);
 		$fractionstring = substr($bitword, $exponentbits + 1, $fractionbits);
@@ -259,7 +323,14 @@
 		return (float) $floatvalue;
 	}
 
-
+	/**
+	 * @param string $byteword
+	 * @param bool   $synchsafe
+	 * @param bool   $signed
+	 *
+	 * @return int|float|false
+	 * @throws Exception
+	 */
 	public static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) {
 		$intvalue = 0;
 		$bytewordlen = strlen($byteword);
@@ -269,9 +340,9 @@
 		for ($i = 0; $i < $bytewordlen; $i++) {
 			if ($synchsafe) { // disregard MSB, effectively 7-bit bytes
 				//$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); // faster, but runs into problems past 2^31 on 32-bit systems
-				$intvalue += (ord($byteword{$i}) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7);
+				$intvalue += (ord($byteword[$i]) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7);
 			} else {
-				$intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i));
+				$intvalue += ord($byteword[$i]) * pow(256, ($bytewordlen - 1 - $i));
 			}
 		}
 		if ($signed && !$synchsafe) {
@@ -288,25 +359,48 @@
 		return self::CastAsInt($intvalue);
 	}
 
-
+	/**
+	 * @param string $byteword
+	 * @param bool   $signed
+	 *
+	 * @return int|float|false
+	 */
 	public static function LittleEndian2Int($byteword, $signed=false) {
 		return self::BigEndian2Int(strrev($byteword), false, $signed);
 	}
 
+	/**
+	 * @param string $byteword
+	 *
+	 * @return string
+	 */
 	public static function LittleEndian2Bin($byteword) {
 		return self::BigEndian2Bin(strrev($byteword));
 	}
 
+	/**
+	 * @param string $byteword
+	 *
+	 * @return string
+	 */
 	public static function BigEndian2Bin($byteword) {
 		$binvalue = '';
 		$bytewordlen = strlen($byteword);
 		for ($i = 0; $i < $bytewordlen; $i++) {
-			$binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT);
+			$binvalue .= str_pad(decbin(ord($byteword[$i])), 8, '0', STR_PAD_LEFT);
 		}
 		return $binvalue;
 	}
 
-
+	/**
+	 * @param int  $number
+	 * @param int  $minbytes
+	 * @param bool $synchsafe
+	 * @param bool $signed
+	 *
+	 * @return string
+	 * @throws Exception
+	 */
 	public static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) {
 		if ($number < 0) {
 			throw new Exception('ERROR: self::BigEndian2String() does not support negative numbers');
@@ -327,7 +421,11 @@
 		return str_pad($intstring, $minbytes, "\x00", STR_PAD_LEFT);
 	}
 
-
+	/**
+	 * @param int $number
+	 *
+	 * @return string
+	 */
 	public static function Dec2Bin($number) {
 		while ($number >= 256) {
 			$bytes[] = (($number / 256) - (floor($number / 256))) * 256;
@@ -341,11 +439,16 @@
 		return $binstring;
 	}
 
-
+	/**
+	 * @param string $binstring
+	 * @param bool   $signed
+	 *
+	 * @return int|float
+	 */
 	public static function Bin2Dec($binstring, $signed=false) {
 		$signmult = 1;
 		if ($signed) {
-			if ($binstring{0} == '1') {
+			if ($binstring[0] == '1') {
 				$signmult = -1;
 			}
 			$binstring = substr($binstring, 1);
@@ -357,7 +460,11 @@
 		return self::CastAsInt($decvalue * $signmult);
 	}
 
-
+	/**
+	 * @param string $binstring
+	 *
+	 * @return string
+	 */
 	public static function Bin2String($binstring) {
 		// return 'hi' for input of '0110100001101001'
 		$string = '';
@@ -368,7 +475,13 @@
 		return $string;
 	}
 
-
+	/**
+	 * @param int  $number
+	 * @param int  $minbytes
+	 * @param bool $synchsafe
+	 *
+	 * @return string
+	 */
 	public static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) {
 		$intstring = '';
 		while ($number > 0) {
@@ -383,7 +496,12 @@
 		return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT);
 	}
 
-
+	/**
+	 * @param mixed $array1
+	 * @param mixed $array2
+	 *
+	 * @return array|false
+	 */
 	public static function array_merge_clobber($array1, $array2) {
 		// written by kcØhireability*com
 		// taken from http://www.php.net/manual/en/function.array-merge-recursive.php
@@ -401,7 +519,12 @@
 		return $newarray;
 	}
 
-
+	/**
+	 * @param mixed $array1
+	 * @param mixed $array2
+	 *
+	 * @return array|false
+	 */
 	public static function array_merge_noclobber($array1, $array2) {
 		if (!is_array($array1) || !is_array($array2)) {
 			return false;
@@ -417,6 +540,12 @@
 		return $newarray;
 	}
 
+	/**
+	 * @param mixed $array1
+	 * @param mixed $array2
+	 *
+	 * @return array|false|null
+	 */
 	public static function flipped_array_merge_noclobber($array1, $array2) {
 		if (!is_array($array1) || !is_array($array2)) {
 			return false;
@@ -431,7 +560,11 @@
 		return array_flip($newarray);
 	}
 
-
+	/**
+	 * @param array $theArray
+	 *
+	 * @return bool
+	 */
 	public static function ksort_recursive(&$theArray) {
 		ksort($theArray);
 		foreach ($theArray as $key => $value) {
@@ -442,6 +575,12 @@
 		return true;
 	}
 
+	/**
+	 * @param string $filename
+	 * @param int    $numextensions
+	 *
+	 * @return string
+	 */
 	public static function fileextension($filename, $numextensions=1) {
 		if (strstr($filename, '.')) {
 			$reversedfilename = strrev($filename);
@@ -457,7 +596,11 @@
 		return '';
 	}
 
-
+	/**
+	 * @param int $seconds
+	 *
+	 * @return string
+	 */
 	public static function PlaytimeString($seconds) {
 		$sign = (($seconds < 0) ? '-' : '');
 		$seconds = round(abs($seconds));
@@ -467,30 +610,53 @@
 		return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT);
 	}
 
-
+	/**
+	 * @param int $macdate
+	 *
+	 * @return int|float
+	 */
 	public static function DateMac2Unix($macdate) {
 		// Macintosh timestamp: seconds since 00:00h January 1, 1904
 		// UNIX timestamp:      seconds since 00:00h January 1, 1970
 		return self::CastAsInt($macdate - 2082844800);
 	}
 
-
+	/**
+	 * @param string $rawdata
+	 *
+	 * @return float
+	 */
 	public static function FixedPoint8_8($rawdata) {
 		return self::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (self::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8));
 	}
 
-
+	/**
+	 * @param string $rawdata
+	 *
+	 * @return float
+	 */
 	public static function FixedPoint16_16($rawdata) {
 		return self::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (self::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16));
 	}
 
-
+	/**
+	 * @param string $rawdata
+	 *
+	 * @return float
+	 */
 	public static function FixedPoint2_30($rawdata) {
 		$binarystring = self::BigEndian2Bin($rawdata);
 		return self::Bin2Dec(substr($binarystring, 0, 2)) + (float) (self::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30));
 	}
 
 
+	/**
+	 * @param string $ArrayPath
+	 * @param string $Separator
+	 * @param mixed $Value
+	 *
+	 * @return array
+	 */
 	public static function CreateDeepArray($ArrayPath, $Separator, $Value) {
 		// assigns $Value to a nested array path:
 		//   $foo = self::CreateDeepArray('/path/to/my', '/', 'file.txt')
@@ -507,12 +673,18 @@
 		return $ReturnedArray;
 	}
 
+	/**
+	 * @param array $arraydata
+	 * @param bool  $returnkey
+	 *
+	 * @return int|false
+	 */
 	public static function array_max($arraydata, $returnkey=false) {
 		$maxvalue = false;
-		$maxkey = false;
+		$maxkey   = false;
 		foreach ($arraydata as $key => $value) {
 			if (!is_array($value)) {
-				if ($value > $maxvalue) {
+				if (($maxvalue === false) || ($value > $maxvalue)) {
 					$maxvalue = $value;
 					$maxkey = $key;
 				}
@@ -521,12 +693,18 @@
 		return ($returnkey ? $maxkey : $maxvalue);
 	}
 
+	/**
+	 * @param array $arraydata
+	 * @param bool  $returnkey
+	 *
+	 * @return int|false
+	 */
 	public static function array_min($arraydata, $returnkey=false) {
 		$minvalue = false;
-		$minkey = false;
+		$minkey   = false;
 		foreach ($arraydata as $key => $value) {
 			if (!is_array($value)) {
-				if ($value > $minvalue) {
+				if (($minvalue === false) || ($value < $minvalue)) {
 					$minvalue = $value;
 					$minkey = $key;
 				}
@@ -535,6 +713,11 @@
 		return ($returnkey ? $minkey : $minvalue);
 	}
 
+	/**
+	 * @param string $XMLstring
+	 *
+	 * @return array|false
+	 */
 	public static function XML2array($XMLstring) {
 		if (function_exists('simplexml_load_string') && function_exists('libxml_disable_entity_loader')) {
 			// http://websec.io/2012/08/27/Preventing-XEE-in-PHP.html
@@ -548,106 +731,68 @@
 		return false;
 	}
 
+	/**
+	* @param SimpleXMLElement|array|mixed $XMLobject
+	*
+	* @return mixed
+	*/
 	public static function SimpleXMLelement2array($XMLobject) {
 		if (!is_object($XMLobject) && !is_array($XMLobject)) {
 			return $XMLobject;
 		}
-		$XMLarray = (is_object($XMLobject) ? get_object_vars($XMLobject) : $XMLobject);
+		$XMLarray = $XMLobject instanceof SimpleXMLElement ? get_object_vars($XMLobject) : $XMLobject;
 		foreach ($XMLarray as $key => $value) {
 			$XMLarray[$key] = self::SimpleXMLelement2array($value);
 		}
 		return $XMLarray;
 	}
 
-
-	// Allan Hansen <ahØartemis*dk>
-	// self::md5_data() - returns md5sum for a file from startuing position to absolute end position
+	/**
+	 * Returns checksum for a file from starting position to absolute end position.
+	 *
+	 * @param string $file
+	 * @param int    $offset
+	 * @param int    $end
+	 * @param string $algorithm
+	 *
+	 * @return string|false
+	 * @throws getid3_exception
+	 */
 	public static function hash_data($file, $offset, $end, $algorithm) {
-		static $tempdir = '';
 		if (!self::intValueSupported($end)) {
 			return false;
 		}
-		switch ($algorithm) {
-			case 'md5':
-				$hash_function = 'md5_file';
-				$unix_call     = 'md5sum';
-				$windows_call  = 'md5sum.exe';
-				$hash_length   = 32;
-				break;
-
-			case 'sha1':
-				$hash_function = 'sha1_file';
-				$unix_call     = 'sha1sum';
-				$windows_call  = 'sha1sum.exe';
-				$hash_length   = 40;
-				break;
-
-			default:
-				throw new Exception('Invalid algorithm ('.$algorithm.') in self::hash_data()');
-				break;
-		}
-		$size = $end - $offset;
-		while (true) {
-			if (GETID3_OS_ISWINDOWS) {
-
-				// It seems that sha1sum.exe for Windows only works on physical files, does not accept piped data
-				// Fall back to create-temp-file method:
-				if ($algorithm == 'sha1') {
-					break;
-				}
-
-				$RequiredFiles = array('cygwin1.dll', 'head.exe', 'tail.exe', $windows_call);
-				foreach ($RequiredFiles as $required_file) {
-					if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) {
-						// helper apps not available - fall back to old method
-						break 2;
-					}
-				}
-				$commandline  = GETID3_HELPERAPPSDIR.'head.exe -c '.$end.' '.escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $file)).' | ';
-				$commandline .= GETID3_HELPERAPPSDIR.'tail.exe -c '.$size.' | ';
-				$commandline .= GETID3_HELPERAPPSDIR.$windows_call;
-
-			} else {
-
-				$commandline  = 'head -c'.$end.' '.escapeshellarg($file).' | ';
-				$commandline .= 'tail -c'.$size.' | ';
-				$commandline .= $unix_call;
-
-			}
-			if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
-				//throw new Exception('PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm');
-				break;
-			}
-			return substr(`$commandline`, 0, $hash_length);
+		if (!in_array($algorithm, array('md5', 'sha1'))) {
+			throw new getid3_exception('Invalid algorithm ('.$algorithm.') in self::hash_data()');
 		}
 
-		if (empty($tempdir)) {
-			// yes this is ugly, feel free to suggest a better way
-			require_once(dirname(__FILE__).'/getid3.php');
-			$getid3_temp = new getID3();
-			$tempdir = $getid3_temp->tempdir;
-			unset($getid3_temp);
-		}
-		// try to create a temporary file in the system temp directory - invalid dirname should force to system temp dir
-		if (($data_filename = tempnam($tempdir, 'gI3')) === false) {
-			// can't find anywhere to create a temp file, just fail
-			return false;
-		}
+		$size = $end - $offset;
 
-		// Init
-		$result = false;
+		$fp = fopen($file, 'rb');
+		fseek($fp, $offset);
+		$ctx = hash_init($algorithm);
+		while ($size > 0) {
+			$buffer = fread($fp, min($size, getID3::FREAD_BUFFER_SIZE));
+			hash_update($ctx, $buffer);
+			$size -= getID3::FREAD_BUFFER_SIZE;
+		}
+		$hash = hash_final($ctx);
+		fclose($fp);
 
-		// copy parts of file
-		try {
-			self::CopyFileParts($file, $data_filename, $offset, $end - $offset);
-			$result = $hash_function($data_filename);
-		} catch (Exception $e) {
-			throw new Exception('self::CopyFileParts() failed in getid_lib::hash_data(): '.$e->getMessage());
-		}
-		unlink($data_filename);
-		return $result;
+		return $hash;
 	}
 
+	/**
+	 * @param string $filename_source
+	 * @param string $filename_dest
+	 * @param int    $offset
+	 * @param int    $length
+	 *
+	 * @return bool
+	 * @throws Exception
+	 *
+	 * @deprecated Unused, may be removed in future versions of getID3
+	 */
 	public static function CopyFileParts($filename_source, $filename_dest, $offset, $length) {
 		if (!self::intValueSupported($offset + $length)) {
 			throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit');
@@ -660,21 +805,25 @@
 						$byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite);
 						$byteslefttowrite -= $byteswritten;
 					}
+					fclose($fp_dest);
 					return true;
 				} else {
+					fclose($fp_src);
 					throw new Exception('failed to seek to offset '.$offset.' in '.$filename_source);
 				}
-				fclose($fp_dest);
 			} else {
 				throw new Exception('failed to create file for writing '.$filename_dest);
 			}
-			fclose($fp_src);
 		} else {
 			throw new Exception('failed to open file for reading '.$filename_source);
 		}
-		return false;
 	}
 
+	/**
+	 * @param int $charval
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_int_utf8($charval) {
 		if ($charval < 128) {
 			// 0bbbbbbb
@@ -698,7 +847,14 @@
 		return $newcharstring;
 	}
 
-	// ISO-8859-1 => UTF-8
+	/**
+	 * ISO-8859-1 => UTF-8
+	 *
+	 * @param string $string
+	 * @param bool   $bom
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_iso88591_utf8($string, $bom=false) {
 		if (function_exists('utf8_encode')) {
 			return utf8_encode($string);
@@ -709,42 +865,68 @@
 			$newcharstring .= "\xEF\xBB\xBF";
 		}
 		for ($i = 0; $i < strlen($string); $i++) {
-			$charval = ord($string{$i});
+			$charval = ord($string[$i]);
 			$newcharstring .= self::iconv_fallback_int_utf8($charval);
 		}
 		return $newcharstring;
 	}
 
-	// ISO-8859-1 => UTF-16BE
+	/**
+	 * ISO-8859-1 => UTF-16BE
+	 *
+	 * @param string $string
+	 * @param bool   $bom
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_iso88591_utf16be($string, $bom=false) {
 		$newcharstring = '';
 		if ($bom) {
 			$newcharstring .= "\xFE\xFF";
 		}
 		for ($i = 0; $i < strlen($string); $i++) {
-			$newcharstring .= "\x00".$string{$i};
+			$newcharstring .= "\x00".$string[$i];
 		}
 		return $newcharstring;
 	}
 
-	// ISO-8859-1 => UTF-16LE
+	/**
+	 * ISO-8859-1 => UTF-16LE
+	 *
+	 * @param string $string
+	 * @param bool   $bom
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_iso88591_utf16le($string, $bom=false) {
 		$newcharstring = '';
 		if ($bom) {
 			$newcharstring .= "\xFF\xFE";
 		}
 		for ($i = 0; $i < strlen($string); $i++) {
-			$newcharstring .= $string{$i}."\x00";
+			$newcharstring .= $string[$i]."\x00";
 		}
 		return $newcharstring;
 	}
 
-	// ISO-8859-1 => UTF-16LE (BOM)
+	/**
+	 * ISO-8859-1 => UTF-16LE (BOM)
+	 *
+	 * @param string $string
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_iso88591_utf16($string) {
 		return self::iconv_fallback_iso88591_utf16le($string, true);
 	}
 
-	// UTF-8 => ISO-8859-1
+	/**
+	 * UTF-8 => ISO-8859-1
+	 *
+	 * @param string $string
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_utf8_iso88591($string) {
 		if (function_exists('utf8_decode')) {
 			return utf8_decode($string);
@@ -754,27 +936,27 @@
 		$offset = 0;
 		$stringlength = strlen($string);
 		while ($offset < $stringlength) {
-			if ((ord($string{$offset}) | 0x07) == 0xF7) {
+			if ((ord($string[$offset]) | 0x07) == 0xF7) {
 				// 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
-				$charval = ((ord($string{($offset + 0)}) & 0x07) << 18) &
-						   ((ord($string{($offset + 1)}) & 0x3F) << 12) &
-						   ((ord($string{($offset + 2)}) & 0x3F) <<  6) &
-							(ord($string{($offset + 3)}) & 0x3F);
+				$charval = ((ord($string[($offset + 0)]) & 0x07) << 18) &
+						   ((ord($string[($offset + 1)]) & 0x3F) << 12) &
+						   ((ord($string[($offset + 2)]) & 0x3F) <<  6) &
+							(ord($string[($offset + 3)]) & 0x3F);
 				$offset += 4;
-			} elseif ((ord($string{$offset}) | 0x0F) == 0xEF) {
+			} elseif ((ord($string[$offset]) | 0x0F) == 0xEF) {
 				// 1110bbbb 10bbbbbb 10bbbbbb
-				$charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) &
-						   ((ord($string{($offset + 1)}) & 0x3F) <<  6) &
-							(ord($string{($offset + 2)}) & 0x3F);
+				$charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) &
+						   ((ord($string[($offset + 1)]) & 0x3F) <<  6) &
+							(ord($string[($offset + 2)]) & 0x3F);
 				$offset += 3;
-			} elseif ((ord($string{$offset}) | 0x1F) == 0xDF) {
+			} elseif ((ord($string[$offset]) | 0x1F) == 0xDF) {
 				// 110bbbbb 10bbbbbb
-				$charval = ((ord($string{($offset + 0)}) & 0x1F) <<  6) &
-							(ord($string{($offset + 1)}) & 0x3F);
+				$charval = ((ord($string[($offset + 0)]) & 0x1F) <<  6) &
+							(ord($string[($offset + 1)]) & 0x3F);
 				$offset += 2;
-			} elseif ((ord($string{$offset}) | 0x7F) == 0x7F) {
+			} elseif ((ord($string[$offset]) | 0x7F) == 0x7F) {
 				// 0bbbbbbb
-				$charval = ord($string{$offset});
+				$charval = ord($string[$offset]);
 				$offset += 1;
 			} else {
 				// error? throw some kind of warning here?
@@ -788,7 +970,14 @@
 		return $newcharstring;
 	}
 
-	// UTF-8 => UTF-16BE
+	/**
+	 * UTF-8 => UTF-16BE
+	 *
+	 * @param string $string
+	 * @param bool   $bom
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_utf8_utf16be($string, $bom=false) {
 		$newcharstring = '';
 		if ($bom) {
@@ -797,27 +986,27 @@
 		$offset = 0;
 		$stringlength = strlen($string);
 		while ($offset < $stringlength) {
-			if ((ord($string{$offset}) | 0x07) == 0xF7) {
+			if ((ord($string[$offset]) | 0x07) == 0xF7) {
 				// 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
-				$charval = ((ord($string{($offset + 0)}) & 0x07) << 18) &
-						   ((ord($string{($offset + 1)}) & 0x3F) << 12) &
-						   ((ord($string{($offset + 2)}) & 0x3F) <<  6) &
-							(ord($string{($offset + 3)}) & 0x3F);
+				$charval = ((ord($string[($offset + 0)]) & 0x07) << 18) &
+						   ((ord($string[($offset + 1)]) & 0x3F) << 12) &
+						   ((ord($string[($offset + 2)]) & 0x3F) <<  6) &
+							(ord($string[($offset + 3)]) & 0x3F);
 				$offset += 4;
-			} elseif ((ord($string{$offset}) | 0x0F) == 0xEF) {
+			} elseif ((ord($string[$offset]) | 0x0F) == 0xEF) {
 				// 1110bbbb 10bbbbbb 10bbbbbb
-				$charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) &
-						   ((ord($string{($offset + 1)}) & 0x3F) <<  6) &
-							(ord($string{($offset + 2)}) & 0x3F);
+				$charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) &
+						   ((ord($string[($offset + 1)]) & 0x3F) <<  6) &
+							(ord($string[($offset + 2)]) & 0x3F);
 				$offset += 3;
-			} elseif ((ord($string{$offset}) | 0x1F) == 0xDF) {
+			} elseif ((ord($string[$offset]) | 0x1F) == 0xDF) {
 				// 110bbbbb 10bbbbbb
-				$charval = ((ord($string{($offset + 0)}) & 0x1F) <<  6) &
-							(ord($string{($offset + 1)}) & 0x3F);
+				$charval = ((ord($string[($offset + 0)]) & 0x1F) <<  6) &
+							(ord($string[($offset + 1)]) & 0x3F);
 				$offset += 2;
-			} elseif ((ord($string{$offset}) | 0x7F) == 0x7F) {
+			} elseif ((ord($string[$offset]) | 0x7F) == 0x7F) {
 				// 0bbbbbbb
-				$charval = ord($string{$offset});
+				$charval = ord($string[$offset]);
 				$offset += 1;
 			} else {
 				// error? throw some kind of warning here?
@@ -831,7 +1020,14 @@
 		return $newcharstring;
 	}
 
-	// UTF-8 => UTF-16LE
+	/**
+	 * UTF-8 => UTF-16LE
+	 *
+	 * @param string $string
+	 * @param bool   $bom
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_utf8_utf16le($string, $bom=false) {
 		$newcharstring = '';
 		if ($bom) {
@@ -840,27 +1036,27 @@
 		$offset = 0;
 		$stringlength = strlen($string);
 		while ($offset < $stringlength) {
-			if ((ord($string{$offset}) | 0x07) == 0xF7) {
+			if ((ord($string[$offset]) | 0x07) == 0xF7) {
 				// 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
-				$charval = ((ord($string{($offset + 0)}) & 0x07) << 18) &
-						   ((ord($string{($offset + 1)}) & 0x3F) << 12) &
-						   ((ord($string{($offset + 2)}) & 0x3F) <<  6) &
-							(ord($string{($offset + 3)}) & 0x3F);
+				$charval = ((ord($string[($offset + 0)]) & 0x07) << 18) &
+						   ((ord($string[($offset + 1)]) & 0x3F) << 12) &
+						   ((ord($string[($offset + 2)]) & 0x3F) <<  6) &
+							(ord($string[($offset + 3)]) & 0x3F);
 				$offset += 4;
-			} elseif ((ord($string{$offset}) | 0x0F) == 0xEF) {
+			} elseif ((ord($string[$offset]) | 0x0F) == 0xEF) {
 				// 1110bbbb 10bbbbbb 10bbbbbb
-				$charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) &
-						   ((ord($string{($offset + 1)}) & 0x3F) <<  6) &
-							(ord($string{($offset + 2)}) & 0x3F);
+				$charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) &
+						   ((ord($string[($offset + 1)]) & 0x3F) <<  6) &
+							(ord($string[($offset + 2)]) & 0x3F);
 				$offset += 3;
-			} elseif ((ord($string{$offset}) | 0x1F) == 0xDF) {
+			} elseif ((ord($string[$offset]) | 0x1F) == 0xDF) {
 				// 110bbbbb 10bbbbbb
-				$charval = ((ord($string{($offset + 0)}) & 0x1F) <<  6) &
-							(ord($string{($offset + 1)}) & 0x3F);
+				$charval = ((ord($string[($offset + 0)]) & 0x1F) <<  6) &
+							(ord($string[($offset + 1)]) & 0x3F);
 				$offset += 2;
-			} elseif ((ord($string{$offset}) | 0x7F) == 0x7F) {
+			} elseif ((ord($string[$offset]) | 0x7F) == 0x7F) {
 				// 0bbbbbbb
-				$charval = ord($string{$offset});
+				$charval = ord($string[$offset]);
 				$offset += 1;
 			} else {
 				// error? maybe throw some warning here?
@@ -874,12 +1070,24 @@
 		return $newcharstring;
 	}
 
-	// UTF-8 => UTF-16LE (BOM)
+	/**
+	 * UTF-8 => UTF-16LE (BOM)
+	 *
+	 * @param string $string
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_utf8_utf16($string) {
 		return self::iconv_fallback_utf8_utf16le($string, true);
 	}
 
-	// UTF-16BE => UTF-8
+	/**
+	 * UTF-16BE => UTF-8
+	 *
+	 * @param string $string
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_utf16be_utf8($string) {
 		if (substr($string, 0, 2) == "\xFE\xFF") {
 			// strip BOM
@@ -893,7 +1101,13 @@
 		return $newcharstring;
 	}
 
-	// UTF-16LE => UTF-8
+	/**
+	 * UTF-16LE => UTF-8
+	 *
+	 * @param string $string
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_utf16le_utf8($string) {
 		if (substr($string, 0, 2) == "\xFF\xFE") {
 			// strip BOM
@@ -907,7 +1121,13 @@
 		return $newcharstring;
 	}
 
-	// UTF-16BE => ISO-8859-1
+	/**
+	 * UTF-16BE => ISO-8859-1
+	 *
+	 * @param string $string
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_utf16be_iso88591($string) {
 		if (substr($string, 0, 2) == "\xFE\xFF") {
 			// strip BOM
@@ -921,7 +1141,13 @@
 		return $newcharstring;
 	}
 
-	// UTF-16LE => ISO-8859-1
+	/**
+	 * UTF-16LE => ISO-8859-1
+	 *
+	 * @param string $string
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_utf16le_iso88591($string) {
 		if (substr($string, 0, 2) == "\xFF\xFE") {
 			// strip BOM
@@ -935,7 +1161,13 @@
 		return $newcharstring;
 	}
 
-	// UTF-16 (BOM) => ISO-8859-1
+	/**
+	 * UTF-16 (BOM) => ISO-8859-1
+	 *
+	 * @param string $string
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_utf16_iso88591($string) {
 		$bom = substr($string, 0, 2);
 		if ($bom == "\xFE\xFF") {
@@ -946,7 +1178,13 @@
 		return $string;
 	}
 
-	// UTF-16 (BOM) => UTF-8
+	/**
+	 * UTF-16 (BOM) => UTF-8
+	 *
+	 * @param string $string
+	 *
+	 * @return string
+	 */
 	public static function iconv_fallback_utf16_utf8($string) {
 		$bom = substr($string, 0, 2);
 		if ($bom == "\xFE\xFF") {
@@ -957,14 +1195,32 @@
 		return $string;
 	}
 
+	/**
+	 * @param string $in_charset
+	 * @param string $out_charset
+	 * @param string $string
+	 *
+	 * @return string
+	 * @throws Exception
+	 */
 	public static function iconv_fallback($in_charset, $out_charset, $string) {
 
 		if ($in_charset == $out_charset) {
 			return $string;
 		}
 
-		// mb_convert_encoding() availble
+		// mb_convert_encoding() available
 		if (function_exists('mb_convert_encoding')) {
+			if ((strtoupper($in_charset) == 'UTF-16') && (substr($string, 0, 2) != "\xFE\xFF") && (substr($string, 0, 2) != "\xFF\xFE")) {
+				// if BOM missing, mb_convert_encoding will mishandle the conversion, assume UTF-16BE and prepend appropriate BOM
+				$string = "\xFF\xFE".$string;
+			}
+			if ((strtoupper($in_charset) == 'UTF-16') && (strtoupper($out_charset) == 'UTF-8')) {
+				if (($string == "\xFF\xFE") || ($string == "\xFE\xFF")) {
+					// if string consists of only BOM, mb_convert_encoding will return the BOM unmodified
+					return '';
+				}
+			}
 			if ($converted_string = @mb_convert_encoding($string, $out_charset, $in_charset)) {
 				switch ($out_charset) {
 					case 'ISO-8859-1':
@@ -974,9 +1230,9 @@
 				return $converted_string;
 			}
 			return $string;
-		}
-		// iconv() availble
-		else if (function_exists('iconv')) {
+
+		// iconv() available
+		} elseif (function_exists('iconv')) {
 			if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) {
 				switch ($out_charset) {
 					case 'ISO-8859-1':
@@ -1017,6 +1273,12 @@
 		throw new Exception('PHP does not has mb_convert_encoding() or iconv() support - cannot convert from '.$in_charset.' to '.$out_charset);
 	}
 
+	/**
+	 * @param mixed  $data
+	 * @param string $charset
+	 *
+	 * @return mixed
+	 */
 	public static function recursiveMultiByteCharString2HTML($data, $charset='ISO-8859-1') {
 		if (is_string($data)) {
 			return self::MultiByteCharString2HTML($data, $charset);
@@ -1031,6 +1293,12 @@
 		return $data;
 	}
 
+	/**
+	 * @param string|int|float $string
+	 * @param string           $charset
+	 *
+	 * @return string
+	 */
 	public static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') {
 		$string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string
 		$HTMLstring = '';
@@ -1069,22 +1337,22 @@
 			case 'utf-8':
 				$strlen = strlen($string);
 				for ($i = 0; $i < $strlen; $i++) {
-					$char_ord_val = ord($string{$i});
+					$char_ord_val = ord($string[$i]);
 					$charval = 0;
 					if ($char_ord_val < 0x80) {
 						$charval = $char_ord_val;
 					} elseif ((($char_ord_val & 0xF0) >> 4) == 0x0F  &&  $i+3 < $strlen) {
 						$charval  = (($char_ord_val & 0x07) << 18);
-						$charval += ((ord($string{++$i}) & 0x3F) << 12);
-						$charval += ((ord($string{++$i}) & 0x3F) << 6);
-						$charval +=  (ord($string{++$i}) & 0x3F);
+						$charval += ((ord($string[++$i]) & 0x3F) << 12);
+						$charval += ((ord($string[++$i]) & 0x3F) << 6);
+						$charval +=  (ord($string[++$i]) & 0x3F);
 					} elseif ((($char_ord_val & 0xE0) >> 5) == 0x07  &&  $i+2 < $strlen) {
 						$charval  = (($char_ord_val & 0x0F) << 12);
-						$charval += ((ord($string{++$i}) & 0x3F) << 6);
-						$charval +=  (ord($string{++$i}) & 0x3F);
+						$charval += ((ord($string[++$i]) & 0x3F) << 6);
+						$charval +=  (ord($string[++$i]) & 0x3F);
 					} elseif ((($char_ord_val & 0xC0) >> 6) == 0x03  &&  $i+1 < $strlen) {
 						$charval  = (($char_ord_val & 0x1F) << 6);
-						$charval += (ord($string{++$i}) & 0x3F);
+						$charval += (ord($string[++$i]) & 0x3F);
 					}
 					if (($charval >= 32) && ($charval <= 127)) {
 						$HTMLstring .= htmlentities(chr($charval));
@@ -1123,8 +1391,11 @@
 		return $HTMLstring;
 	}
 
-
-
+	/**
+	 * @param int $namecode
+	 *
+	 * @return string
+	 */
 	public static function RGADnameLookup($namecode) {
 		static $RGADname = array();
 		if (empty($RGADname)) {
@@ -1136,7 +1407,11 @@
 		return (isset($RGADname[$namecode]) ? $RGADname[$namecode] : '');
 	}
 
-
+	/**
+	 * @param int $originatorcode
+	 *
+	 * @return string
+	 */
 	public static function RGADoriginatorLookup($originatorcode) {
 		static $RGADoriginator = array();
 		if (empty($RGADoriginator)) {
@@ -1149,16 +1424,27 @@
 		return (isset($RGADoriginator[$originatorcode]) ? $RGADoriginator[$originatorcode] : '');
 	}
 
-
+	/**
+	 * @param int $rawadjustment
+	 * @param int $signbit
+	 *
+	 * @return float
+	 */
 	public static function RGADadjustmentLookup($rawadjustment, $signbit) {
-		$adjustment = $rawadjustment / 10;
+		$adjustment = (float) $rawadjustment / 10;
 		if ($signbit == 1) {
 			$adjustment *= -1;
 		}
-		return (float) $adjustment;
+		return $adjustment;
 	}
 
-
+	/**
+	 * @param int $namecode
+	 * @param int $originatorcode
+	 * @param int $replaygain
+	 *
+	 * @return string
+	 */
 	public static function RGADgainString($namecode, $originatorcode, $replaygain) {
 		if ($replaygain < 0) {
 			$signbit = '1';
@@ -1174,12 +1460,31 @@
 		return $gainstring;
 	}
 
+	/**
+	 * @param float $amplitude
+	 *
+	 * @return float
+	 */
 	public static function RGADamplitude2dB($amplitude) {
 		return 20 * log10($amplitude);
 	}
 
-
+	/**
+	 * @param string $imgData
+	 * @param array  $imageinfo
+	 *
+	 * @return array|false
+	 */
 	public static function GetDataImageSize($imgData, &$imageinfo=array()) {
+		if (PHP_VERSION_ID >= 50400) {
+			$GetDataImageSize = @getimagesizefromstring($imgData, $imageinfo);
+			if ($GetDataImageSize === false || !isset($GetDataImageSize[0], $GetDataImageSize[1])) {
+				return false;
+			}
+			$GetDataImageSize['height'] = $GetDataImageSize[0];
+			$GetDataImageSize['width'] = $GetDataImageSize[1];
+			return $GetDataImageSize;
+		}
 		static $tempdir = '';
 		if (empty($tempdir)) {
 			if (function_exists('sys_get_temp_dir')) {
@@ -1188,12 +1493,11 @@
 
 			// yes this is ugly, feel free to suggest a better way
 			if (include_once(dirname(__FILE__).'/getid3.php')) {
-				if ($getid3_temp = new getID3()) {
-					if ($getid3_temp_tempdir = $getid3_temp->tempdir) {
-						$tempdir = $getid3_temp_tempdir;
-					}
-					unset($getid3_temp, $getid3_temp_tempdir);
+				$getid3_temp = new getID3();
+				if ($getid3_temp_tempdir = $getid3_temp->tempdir) {
+					$tempdir = $getid3_temp_tempdir;
 				}
+				unset($getid3_temp, $getid3_temp_tempdir);
 			}
 		}
 		$GetDataImageSize = false;
@@ -1213,36 +1517,32 @@
 		return $GetDataImageSize;
 	}
 
+	/**
+	 * @param string $mime_type
+	 *
+	 * @return string
+	 */
 	public static function ImageExtFromMime($mime_type) {
 		// temporary way, works OK for now, but should be reworked in the future
 		return str_replace(array('image/', 'x-', 'jpeg'), array('', '', 'jpg'), $mime_type);
 	}
 
-	public static function ImageTypesLookup($imagetypeid) {
-		static $ImageTypesLookup = array();
-		if (empty($ImageTypesLookup)) {
-			$ImageTypesLookup[1]  = 'gif';
-			$ImageTypesLookup[2]  = 'jpeg';
-			$ImageTypesLookup[3]  = 'png';
-			$ImageTypesLookup[4]  = 'swf';
-			$ImageTypesLookup[5]  = 'psd';
-			$ImageTypesLookup[6]  = 'bmp';
-			$ImageTypesLookup[7]  = 'tiff (little-endian)';
-			$ImageTypesLookup[8]  = 'tiff (big-endian)';
-			$ImageTypesLookup[9]  = 'jpc';
-			$ImageTypesLookup[10] = 'jp2';
-			$ImageTypesLookup[11] = 'jpx';
-			$ImageTypesLookup[12] = 'jb2';
-			$ImageTypesLookup[13] = 'swc';
-			$ImageTypesLookup[14] = 'iff';
-		}
-		return (isset($ImageTypesLookup[$imagetypeid]) ? $ImageTypesLookup[$imagetypeid] : '');
-	}
-
-	public static function CopyTagsToComments(&$ThisFileInfo) {
-
+	/**
+	 * @param array $ThisFileInfo
+	 * @param bool  $option_tags_html default true (just as in the main getID3 class)
+	 *
+	 * @return bool
+	 */
+	public static function CopyTagsToComments(&$ThisFileInfo, $option_tags_html=true) {
 		// Copy all entries from ['tags'] into common ['comments']
 		if (!empty($ThisFileInfo['tags'])) {
+			if (isset($ThisFileInfo['tags']['id3v1'])) {
+				// bubble ID3v1 to the end, if present to aid in detecting bad ID3v1 encodings
+				$ID3v1 = $ThisFileInfo['tags']['id3v1'];
+				unset($ThisFileInfo['tags']['id3v1']);
+				$ThisFileInfo['tags']['id3v1'] = $ID3v1;
+				unset($ID3v1);
+			}
 			foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) {
 				foreach ($tagarray as $tagname => $tagdata) {
 					foreach ($tagdata as $key => $value) {
@@ -1261,6 +1561,13 @@
 										break 2;
 									}
 								}
+								if (function_exists('mb_convert_encoding')) {
+									if (trim($value) == trim(substr(mb_convert_encoding($existingvalue, $ThisFileInfo['id3v1']['encoding'], $ThisFileInfo['encoding']), 0, 30))) {
+										// value stored in ID3v1 appears to be probably the multibyte value transliterated (badly) into ISO-8859-1 in ID3v1.
+										// As an example, Foobar2000 will do this if you tag a file with Chinese or Arabic or Cyrillic or something that doesn't fit into ISO-8859-1 the ID3v1 will consist of mostly "?" characters, one per multibyte unrepresentable character
+										break 2;
+									}
+								}
 
 							} elseif (!is_array($value)) {
 
@@ -1269,7 +1576,6 @@
 									$oldvaluelength = strlen(trim($existingvalue));
 									if ((strlen($existingvalue) > 10) && ($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) {
 										$ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
-										//break 2;
 										break;
 									}
 								}
@@ -1280,7 +1586,7 @@
 								if (!is_int($key) && !ctype_digit($key)) {
 									$ThisFileInfo['comments'][$tagname][$key] = $value;
 								} else {
-									if (isset($ThisFileInfo['comments'][$tagname])) {
+									if (!isset($ThisFileInfo['comments'][$tagname])) {
 										$ThisFileInfo['comments'][$tagname] = array($value);
 									} else {
 										$ThisFileInfo['comments'][$tagname][] = $value;
@@ -1304,19 +1610,21 @@
 				}
 			}
 
-			// Copy to ['comments_html']
-			if (!empty($ThisFileInfo['comments'])) {
-				foreach ($ThisFileInfo['comments'] as $field => $values) {
-					if ($field == 'picture') {
-						// pictures can take up a lot of space, and we don't need multiple copies of them
-						// let there be a single copy in [comments][picture], and not elsewhere
-						continue;
-					}
-					foreach ($values as $index => $value) {
-						if (is_array($value)) {
-							$ThisFileInfo['comments_html'][$field][$index] = $value;
-						} else {
-							$ThisFileInfo['comments_html'][$field][$index] = str_replace('&#0;', '', self::MultiByteCharString2HTML($value, $ThisFileInfo['encoding']));
+			if ($option_tags_html) {
+				// Copy ['comments'] to ['comments_html']
+				if (!empty($ThisFileInfo['comments'])) {
+					foreach ($ThisFileInfo['comments'] as $field => $values) {
+						if ($field == 'picture') {
+							// pictures can take up a lot of space, and we don't need multiple copies of them
+							// let there be a single copy in [comments][picture], and not elsewhere
+							continue;
+						}
+						foreach ($values as $index => $value) {
+							if (is_array($value)) {
+								$ThisFileInfo['comments_html'][$field][$index] = $value;
+							} else {
+								$ThisFileInfo['comments_html'][$field][$index] = str_replace('&#0;', '', self::MultiByteCharString2HTML($value, $ThisFileInfo['encoding']));
+							}
 						}
 					}
 				}
@@ -1326,7 +1634,15 @@
 		return true;
 	}
 
-
+	/**
+	 * @param string $key
+	 * @param int    $begin
+	 * @param int    $end
+	 * @param string $file
+	 * @param string $name
+	 *
+	 * @return string
+	 */
 	public static function EmbeddedLookup($key, $begin, $end, $file, $name) {
 
 		// Cached
@@ -1373,6 +1689,14 @@
 		return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : '');
 	}
 
+	/**
+	 * @param string $filename
+	 * @param string $sourcefile
+	 * @param bool   $DieOnFailure
+	 *
+	 * @return bool
+	 * @throws Exception
+	 */
 	public static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) {
 		global $GETID3_ERRORARRAY;
 
@@ -1393,10 +1717,20 @@
 		return false;
 	}
 
+	/**
+	 * @param string $string
+	 *
+	 * @return string
+	 */
 	public static function trimNullByte($string) {
 		return trim($string, "\x00");
 	}
 
+	/**
+	 * @param string $path
+	 *
+	 * @return float|bool
+	 */
 	public static function getFileSizeSyscall($path) {
 		$filesize = false;
 
@@ -1421,13 +1755,44 @@
 		return $filesize;
 	}
 
+	/**
+	 * @param string $filename
+	 *
+	 * @return string|false
+	 */
+	public static function truepath($filename) {
+		// 2017-11-08: this could use some improvement, patches welcome
+		if (preg_match('#^(\\\\\\\\|//)[a-z0-9]#i', $filename, $matches)) {
+			// PHP's built-in realpath function does not work on UNC Windows shares
+			$goodpath = array();
+			foreach (explode('/', str_replace('\\', '/', $filename)) as $part) {
+				if ($part == '.') {
+					continue;
+				}
+				if ($part == '..') {
+					if (count($goodpath)) {
+						array_pop($goodpath);
+					} else {
+						// cannot step above this level, already at top level
+						return false;
+					}
+				} else {
+					$goodpath[] = $part;
+				}
+			}
+			return implode(DIRECTORY_SEPARATOR, $goodpath);
+		}
+		return realpath($filename);
+	}
 
 	/**
-	* Workaround for Bug #37268 (https://bugs.php.net/bug.php?id=37268)
-	* @param string $path A path.
-	* @param string $suffix If the name component ends in suffix this will also be cut off.
-	* @return string
-	*/
+	 * Workaround for Bug #37268 (https://bugs.php.net/bug.php?id=37268)
+	 *
+	 * @param string $path A path.
+	 * @param string $suffix If the name component ends in suffix this will also be cut off.
+	 *
+	 * @return string
+	 */
 	public static function mb_basename($path, $suffix = null) {
 		$splited = preg_split('#/#', rtrim($path, '/ '));
 		return substr(basename('X'.$splited[count($splited) - 1], $suffix), 1);