diff --git a/lib/getid3/extension.cache.dbm.php b/lib/getid3/extension.cache.dbm.php
index 010af103eb668691673110dd827e69e4310a615e..bf94ef1874d0fbb3c43ecb820bf4c6d033980ce8 100644
--- a/lib/getid3/extension.cache.dbm.php
+++ b/lib/getid3/extension.cache.dbm.php
@@ -224,6 +224,7 @@ class getID3_cached_dbm extends getID3
 	 */
 	public function analyze($filename, $filesize=null, $original_filename='', $fp=null) {
 
+		$key = null;
 		if (file_exists($filename)) {
 
 			// Calc key     filename::mod_time::size    - should be unique
diff --git a/lib/getid3/getid3.lib.php b/lib/getid3/getid3.lib.php
index 916c43053c0889fb0df7c72c2a692f610419b73c..a24a0652525f14bcc9b0d3dd0901e754c3b3dd36 100644
--- a/lib/getid3/getid3.lib.php
+++ b/lib/getid3/getid3.lib.php
@@ -242,7 +242,7 @@ class getid3_lib
 	/**
 	 * ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
 	 *
-	 * @link http://www.psc.edu/general/software/packages/ieee/ieee.html
+	 * @link https://web.archive.org/web/20120325162206/http://www.psc.edu/general/software/packages/ieee/ieee.php
 	 * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
 	 *
 	 * @param string $byteword
@@ -294,12 +294,12 @@ class getid3_lib
 
 		if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) {
 			// Not a Number
-			$floatvalue = false;
+			$floatvalue = NAN;
 		} elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) {
 			if ($signbit == '1') {
-				$floatvalue = '-infinity';
+				$floatvalue = -INF;
 			} else {
-				$floatvalue = '+infinity';
+				$floatvalue = INF;
 			}
 		} elseif (($exponent == 0) && ($fraction == 0)) {
 			if ($signbit == '1') {
@@ -427,14 +427,20 @@ class getid3_lib
 	 * @return string
 	 */
 	public static function Dec2Bin($number) {
+		if (!is_numeric($number)) {
+			// https://github.com/JamesHeinrich/getID3/issues/299
+			trigger_error('TypeError: Dec2Bin(): Argument #1 ($number) must be numeric, '.gettype($number).' given', E_USER_WARNING);
+			return '';
+		}
+		$bytes = array();
 		while ($number >= 256) {
-			$bytes[] = (($number / 256) - (floor($number / 256))) * 256;
+			$bytes[] = (int) (($number / 256) - (floor($number / 256))) * 256;
 			$number = floor($number / 256);
 		}
-		$bytes[] = $number;
+		$bytes[] = (int) $number;
 		$binstring = '';
-		for ($i = 0; $i < count($bytes); $i++) {
-			$binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring;
+		foreach ($bytes as $i => $byte) {
+			$binstring = (($i == count($bytes) - 1) ? decbin($byte) : str_pad(decbin($byte), 8, '0', STR_PAD_LEFT)).$binstring;
 		}
 		return $binstring;
 	}
@@ -665,6 +671,7 @@ class getid3_lib
 		// or
 		//   $foo['path']['to']['my'] = 'file.txt';
 		$ArrayPath = ltrim($ArrayPath, $Separator);
+		$ReturnedArray = array();
 		if (($pos = strpos($ArrayPath, $Separator)) !== false) {
 			$ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value);
 		} else {
@@ -722,10 +729,12 @@ class getid3_lib
 		if (function_exists('simplexml_load_string') && function_exists('libxml_disable_entity_loader')) {
 			// http://websec.io/2012/08/27/Preventing-XEE-in-PHP.html
 			// https://core.trac.wordpress.org/changeset/29378
-			$loader = libxml_disable_entity_loader(true);
+			// This function has been deprecated in PHP 8.0 because in libxml 2.9.0, external entity loading is
+			// disabled by default, but is still needed when LIBXML_NOENT is used.
+			$loader = @libxml_disable_entity_loader(true);
 			$XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', LIBXML_NOENT);
 			$return = self::SimpleXMLelement2array($XMLobject);
-			libxml_disable_entity_loader($loader);
+			@libxml_disable_entity_loader($loader);
 			return $return;
 		}
 		return false;
@@ -1536,12 +1545,21 @@ class getid3_lib
 	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);
+
+			// Some tag types can only support limited character sets and may contain data in non-standard encoding (usually ID3v1)
+			// and/or poorly-transliterated tag values that are also in tag formats that do support full-range character sets
+			// To make the output more user-friendly, process the potentially-problematic tag formats last to enhance the chance that
+			// the first entries in [comments] are the most correct and the "bad" ones (if any) come later.
+			// https://github.com/JamesHeinrich/getID3/issues/338
+			$processLastTagTypes = array('id3v1','riff');
+			foreach ($processLastTagTypes as $processLastTagType) {
+				if (isset($ThisFileInfo['tags'][$processLastTagType])) {
+					// bubble ID3v1 to the end, if present to aid in detecting bad ID3v1 encodings
+					$temp = $ThisFileInfo['tags'][$processLastTagType];
+					unset($ThisFileInfo['tags'][$processLastTagType]);
+					$ThisFileInfo['tags'][$processLastTagType] = $temp;
+					unset($temp);
+				}
 			}
 			foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) {
 				foreach ($tagarray as $tagname => $tagdata) {
@@ -1560,20 +1578,30 @@ class getid3_lib
 										// new value is identical but shorter-than (or equal-length to) one already in comments - skip
 										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;
+
+									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)) {
 
-								$newvaluelength = strlen(trim($value));
+								$newvaluelength   =    strlen(trim($value));
+								$newvaluelengthMB = mb_strlen(trim($value));
 								foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) {
-									$oldvaluelength = strlen(trim($existingvalue));
+									$oldvaluelength   =    strlen(trim($existingvalue));
+									$oldvaluelengthMB = mb_strlen(trim($existingvalue));
+									if (($newvaluelengthMB == $oldvaluelengthMB) && ($existingvalue == getid3_lib::iconv_fallback('UTF-8', 'ASCII', $value))) {
+										// https://github.com/JamesHeinrich/getID3/issues/338
+										// check for tags containing extended characters that may have been forced into limited-character storage (e.g. UTF8 values into ASCII)
+										// which will usually display unrepresentable characters as "?"
+										$ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
+										break;
+									}
 									if ((strlen($existingvalue) > 10) && ($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) {
 										$ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
 										break;
@@ -1599,14 +1627,16 @@ class getid3_lib
 			}
 
 			// attempt to standardize spelling of returned keys
-			$StandardizeFieldNames = array(
-				'tracknumber' => 'track_number',
-				'track'       => 'track_number',
-			);
-			foreach ($StandardizeFieldNames as $badkey => $goodkey) {
-				if (array_key_exists($badkey, $ThisFileInfo['comments']) && !array_key_exists($goodkey, $ThisFileInfo['comments'])) {
-					$ThisFileInfo['comments'][$goodkey] = $ThisFileInfo['comments'][$badkey];
-					unset($ThisFileInfo['comments'][$badkey]);
+			if (!empty($ThisFileInfo['comments'])) {
+				$StandardizeFieldNames = array(
+					'tracknumber' => 'track_number',
+					'track'       => 'track_number',
+				);
+				foreach ($StandardizeFieldNames as $badkey => $goodkey) {
+					if (array_key_exists($badkey, $ThisFileInfo['comments']) && !array_key_exists($goodkey, $ThisFileInfo['comments'])) {
+						$ThisFileInfo['comments'][$goodkey] = $ThisFileInfo['comments'][$badkey];
+						unset($ThisFileInfo['comments'][$badkey]);
+					}
 				}
 			}
 
@@ -1732,6 +1762,7 @@ class getid3_lib
 	 * @return float|bool
 	 */
 	public static function getFileSizeSyscall($path) {
+		$commandline = null;
 		$filesize = false;
 
 		if (GETID3_OS_ISWINDOWS) {
@@ -1793,7 +1824,7 @@ class getid3_lib
 	 *
 	 * @return string
 	 */
-	public static function mb_basename($path, $suffix = null) {
+	public static function mb_basename($path, $suffix = '') {
 		$splited = preg_split('#/#', rtrim($path, '/ '));
 		return substr(basename('X'.$splited[count($splited) - 1], $suffix), 1);
 	}
diff --git a/lib/getid3/getid3.php b/lib/getid3/getid3.php
index bce97fb73344e45d0d38895bd3a8a1bbc3f93318..c4be4845a5c7ec3085aaa030370359a049786bc9 100644
--- a/lib/getid3/getid3.php
+++ b/lib/getid3/getid3.php
@@ -17,10 +17,6 @@ if (!defined('GETID3_OS_ISWINDOWS')) {
 if (!defined('GETID3_INCLUDEPATH')) {
 	define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
 }
-// Workaround Bug #39923 (https://bugs.php.net/bug.php?id=39923)
-if (!defined('IMG_JPG') && defined('IMAGETYPE_JPEG')) {
-	define('IMG_JPG', IMAGETYPE_JPEG);
-}
 if (!defined('ENT_SUBSTITUTE')) { // PHP5.3 adds ENT_IGNORE, PHP5.4 adds ENT_SUBSTITUTE
 	define('ENT_SUBSTITUTE', (defined('ENT_IGNORE') ? ENT_IGNORE : 8));
 }
@@ -57,7 +53,7 @@ if ($open_basedir) {
 		if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) {
 			$basedir .= DIRECTORY_SEPARATOR;
 		}
-		if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) {
+		if (strpos($temp_dir, $basedir) === 0) {
 			$found_valid_tempdir = true;
 			break;
 		}
@@ -214,6 +210,140 @@ class getID3
 	 */
 	public $option_fread_buffer_size = 32768;
 
+
+
+	// module-specific options
+
+	/** archive.rar
+	 * if true use PHP RarArchive extension, if false (non-extension parsing not yet written in getID3)
+	 *
+	 * @var bool
+	 */
+	public $options_archive_rar_use_php_rar_extension = true;
+
+	/** archive.gzip
+	 * Optional file list - disable for speed.
+	 * Decode gzipped files, if possible, and parse recursively (.tar.gz for example).
+	 *
+	 * @var bool
+	 */
+	public $options_archive_gzip_parse_contents = false;
+
+	/** audio.midi
+	 * if false only parse most basic information, much faster for some files but may be inaccurate
+	 *
+	 * @var bool
+	 */
+	public $options_audio_midi_scanwholefile = true;
+
+	/** audio.mp3
+	 * Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow,
+	 * unrecommended, but may provide data from otherwise-unusable files.
+	 *
+	 * @var bool
+	 */
+	public $options_audio_mp3_allow_bruteforce = false;
+
+	/** audio.mp3
+	 * number of frames to scan to determine if MPEG-audio sequence is valid
+	 * Lower this number to 5-20 for faster scanning
+	 * Increase this number to 50+ for most accurate detection of valid VBR/CBR mpeg-audio streams
+	 *
+	 * @var int
+	 */
+	public $options_audio_mp3_mp3_valid_check_frames = 50;
+
+	/** audio.wavpack
+	 * Avoid scanning all frames (break after finding ID_RIFF_HEADER and ID_CONFIG_BLOCK,
+	 * significantly faster for very large files but other data may be missed
+	 *
+	 * @var bool
+	 */
+	public $options_audio_wavpack_quick_parsing = false;
+
+	/** audio-video.flv
+	 * Break out of the loop if too many frames have been scanned; only scan this
+	 * many if meta frame does not contain useful duration.
+	 *
+	 * @var int
+	 */
+	public $options_audiovideo_flv_max_frames = 100000;
+
+	/** audio-video.matroska
+	 * If true, do not return information about CLUSTER chunks, since there's a lot of them
+	 * and they're not usually useful [default: TRUE].
+	 *
+	 * @var bool
+	 */
+	public $options_audiovideo_matroska_hide_clusters    = true;
+
+	/** audio-video.matroska
+	 * True to parse the whole file, not only header [default: FALSE].
+	 *
+	 * @var bool
+	 */
+	public $options_audiovideo_matroska_parse_whole_file = false;
+
+	/** audio-video.quicktime
+	 * return all parsed data from all atoms if true, otherwise just returned parsed metadata
+	 *
+	 * @var bool
+	 */
+	public $options_audiovideo_quicktime_ReturnAtomData  = false;
+
+	/** audio-video.quicktime
+	 * return all parsed data from all atoms if true, otherwise just returned parsed metadata
+	 *
+	 * @var bool
+	 */
+	public $options_audiovideo_quicktime_ParseAllPossibleAtoms = false;
+
+	/** audio-video.swf
+	 * return all parsed tags if true, otherwise do not return tags not parsed by getID3
+	 *
+	 * @var bool
+	 */
+	public $options_audiovideo_swf_ReturnAllTagData = false;
+
+	/** graphic.bmp
+	 * return BMP palette
+	 *
+	 * @var bool
+	 */
+	public $options_graphic_bmp_ExtractPalette = false;
+
+	/** graphic.bmp
+	 * return image data
+	 *
+	 * @var bool
+	 */
+	public $options_graphic_bmp_ExtractData    = false;
+
+	/** graphic.png
+	 * If data chunk is larger than this do not read it completely (getID3 only needs the first
+	 * few dozen bytes for parsing).
+	 *
+	 * @var int
+	 */
+	public $options_graphic_png_max_data_bytes = 10000000;
+
+	/** misc.pdf
+	 * return full details of PDF Cross-Reference Table (XREF)
+	 *
+	 * @var bool
+	 */
+	public $options_misc_pdf_returnXREF = false;
+
+	/** misc.torrent
+	 * Assume all .torrent files are less than 1MB and just read entire thing into memory for easy processing.
+	 * Override this value if you need to process files larger than 1MB
+	 *
+	 * @var int
+	 */
+	public $options_misc_torrent_max_torrent_filesize = 1048576;
+
+
+
 	// Public variables
 
 	/**
@@ -257,7 +387,7 @@ class getID3
 	 */
 	protected $startup_warning = '';
 
-	const VERSION           = '1.9.20-202006061653';
+	const VERSION           = '1.9.21-202109171300';
 	const FREAD_BUFFER_SIZE = 32768;
 
 	const ATTACHMENTS_NONE   = false;
@@ -633,6 +763,18 @@ class getID3
 				return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
 			}
 			$class = new $class_name($this);
+
+			// set module-specific options
+			foreach (get_object_vars($this) as $getid3_object_vars_key => $getid3_object_vars_value) {
+				if (preg_match('#^options_([^_]+)_([^_]+)_(.+)$#i', $getid3_object_vars_key, $matches)) {
+					list($dummy, $GOVgroup, $GOVmodule, $GOVsetting) = $matches;
+					$GOVgroup = (($GOVgroup == 'audiovideo') ? 'audio-video' : $GOVgroup); // variable names can only contain 0-9a-z_ so standardize here
+					if (($GOVgroup == $determined_format['group']) && ($GOVmodule == $determined_format['module'])) {
+						$class->$GOVsetting = $getid3_object_vars_value;
+					}
+				}
+			}
+
 			$class->Analyze();
 			unset($class);
 
@@ -1351,6 +1493,16 @@ class getID3
 							'fail_ape'  => 'ERROR',
 						),
 
+				// TORRENT             - .torrent
+				'torrent' => array(
+							'pattern'   => '^(d8\\:announce|d7\\:comment)',
+							'group'     => 'misc',
+							'module'    => 'torrent',
+							'mime_type' => 'application/x-bittorrent',
+							'fail_id3'  => 'ERROR',
+							'fail_ape'  => 'ERROR',
+						),
+
 				 // CUE  - data       - CUEsheet (index to single-file disc images)
 				 'cue' => array(
 							'pattern'   => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
@@ -1485,7 +1637,7 @@ class getID3
 						if (is_string($value)) {
 							$value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
 						}
-						if ($value) {
+						if (isset($value) && $value !== "") {
 							if (!is_numeric($key)) {
 								$this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
 							} else {
@@ -2091,19 +2243,25 @@ abstract class getid3_handler
 					$this->data_string_position = $this->data_string_length + $bytes;
 					break;
 			}
-			return 0;
-		} else {
-			$pos = $bytes;
-			if ($whence == SEEK_CUR) {
-				$pos = $this->ftell() + $bytes;
-			} elseif ($whence == SEEK_END) {
-				$pos = $this->getid3->info['filesize'] + $bytes;
-			}
-			if (!getid3_lib::intValueSupported($pos)) {
-				throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
-			}
+			return 0; // fseek returns 0 on success
+		}
+
+		$pos = $bytes;
+		if ($whence == SEEK_CUR) {
+			$pos = $this->ftell() + $bytes;
+		} elseif ($whence == SEEK_END) {
+			$pos = $this->getid3->info['filesize'] + $bytes;
+		}
+		if (!getid3_lib::intValueSupported($pos)) {
+			throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
+		}
+
+		// https://github.com/JamesHeinrich/getID3/issues/327
+		$result = fseek($this->getid3->fp, $bytes, $whence);
+		if ($result !== 0) { // fseek returns 0 on success
+			throw new getid3_exception('cannot fseek('.$pos.'). resource/stream does not appear to support seeking', 10);
 		}
-		return fseek($this->getid3->fp, $bytes, $whence);
+		return $result;
 	}
 
 	/**
@@ -2219,6 +2377,8 @@ abstract class getid3_handler
 	 * @throws getid3_exception
 	 */
 	public function saveAttachment($name, $offset, $length, $image_mime=null) {
+		$fp_dest = null;
+		$dest = null;
 		try {
 
 			// do not extract at all
diff --git a/lib/getid3/module.archive.gzip.php b/lib/getid3/module.archive.gzip.php
index 18d49f38a364604fdfee5c450f01c5b66cf52853..d75778110205399cdc5ce40e25ae2d35bec842c6 100644
--- a/lib/getid3/module.archive.gzip.php
+++ b/lib/getid3/module.archive.gzip.php
@@ -27,12 +27,11 @@ class getid3_gzip extends getid3_handler
 {
 	/**
 	 * Optional file list - disable for speed.
-	 *
 	 * Decode gzipped files, if possible, and parse recursively (.tar.gz for example).
 	 *
 	 * @var bool
 	 */
-	public $option_gzip_parse_contents = false;
+	public $parse_contents = false;
 
 	/**
 	 * @return bool
@@ -59,7 +58,7 @@ class getid3_gzip extends getid3_handler
 		$num_members = 0;
 		while (true) {
 			$is_wrong_members = false;
-			$num_members = intval(count($arr_members));
+			$num_members = count($arr_members);
 			for ($i = 0; $i < $num_members; $i++) {
 				if (strlen($arr_members[$i]) == 0) {
 					continue;
@@ -84,13 +83,13 @@ class getid3_gzip extends getid3_handler
 
 		$fpointer = 0;
 		$idx = 0;
-		for ($i = 0; $i < $num_members; $i++) {
-			if (strlen($arr_members[$i]) == 0) {
+		foreach ($arr_members as $member) {
+			if (strlen($member) == 0) {
 				continue;
 			}
 			$thisInfo = &$info['gzip']['member_header'][++$idx];
 
-			$buff = "\x1F\x8B\x08".$arr_members[$i];
+			$buff = "\x1F\x8B\x08". $member;
 
 			$attr = unpack($unpack_header, substr($buff, 0, $start_length));
 			$thisInfo['filemtime']      = getid3_lib::LittleEndian2Int($attr['mtime']);
@@ -200,7 +199,7 @@ class getid3_gzip extends getid3_handler
 
 			$info['gzip']['files'] = getid3_lib::array_merge_clobber($info['gzip']['files'], getid3_lib::CreateDeepArray($thisInfo['filename'], '/', $thisInfo['filesize']));
 
-			if ($this->option_gzip_parse_contents) {
+			if ($this->parse_contents) {
 				// Try to inflate GZip
 				$csize = 0;
 				$inflated = '';
diff --git a/lib/getid3/module.archive.rar.php b/lib/getid3/module.archive.rar.php
index 4d40160d4b03ea81f4bd7a190f506d8ba4154488..f80219c38f47d5d5bcfd2e8a9cde30a458842045 100644
--- a/lib/getid3/module.archive.rar.php
+++ b/lib/getid3/module.archive.rar.php
@@ -21,9 +21,11 @@ if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that acce
 class getid3_rar extends getid3_handler
 {
 	/**
+	 * if true use PHP RarArchive extension, if false (non-extension parsing not yet written in getID3)
+	 *
 	 * @var bool
 	 */
-	public $option_use_rar_extension = false;
+	public $use_php_rar_extension = true;
 
 	/**
 	 * @return bool
@@ -33,7 +35,7 @@ class getid3_rar extends getid3_handler
 
 		$info['fileformat'] = 'rar';
 
-		if ($this->option_use_rar_extension === true) {
+		if ($this->use_php_rar_extension === true) {
 			if (function_exists('rar_open')) {
 				if ($rp = rar_open($info['filenamepath'])) {
 					$info['rar']['files'] = array();
@@ -50,7 +52,7 @@ class getid3_rar extends getid3_handler
 				$this->error('RAR support does not appear to be available in this PHP installation');
 			}
 		} else {
-			$this->error('PHP-RAR processing has been disabled (set $getid3_rar->option_use_rar_extension=true to enable)');
+			$this->error('PHP-RAR processing has been disabled (set $getid3_rar->use_php_rar_extension=true to enable)');
 		}
 		return false;
 
diff --git a/lib/getid3/module.archive.tar.php b/lib/getid3/module.archive.tar.php
index 5b4aa56695c90941db5ead5cd20e70d6b2481130..f8aed7952e13995fb1fec766c56f26d5c4450620 100644
--- a/lib/getid3/module.archive.tar.php
+++ b/lib/getid3/module.archive.tar.php
@@ -141,6 +141,9 @@ class getid3_tar extends getid3_handler
 		else                    $type='u'; // UNKNOWN
 
 		// Determine permissions
+		$owner            = array();
+		$group            = array();
+		$world            = array();
 		$owner['read']    = (($mode & 00400) ? 'r' : '-');
 		$owner['write']   = (($mode & 00200) ? 'w' : '-');
 		$owner['execute'] = (($mode & 00100) ? 'x' : '-');
diff --git a/lib/getid3/module.archive.xz.php b/lib/getid3/module.archive.xz.php
index 1557bfb57ea3ad398010d637f4ad7c9aeb05d7e6..dbbc067ba7b9c33451a7944da15220871f07536a 100644
--- a/lib/getid3/module.archive.xz.php
+++ b/lib/getid3/module.archive.xz.php
@@ -20,7 +20,6 @@ if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that acce
 
 class getid3_xz extends getid3_handler
 {
-
 	/**
 	 * @return bool
 	 */
diff --git a/lib/getid3/module.archive.zip.php b/lib/getid3/module.archive.zip.php
index 1ce3fb75a74512957ed3a37157c2d1e8134d435d..ef99c1410be7137dd8ca2ebd7d878ddc503cd97c 100644
--- a/lib/getid3/module.archive.zip.php
+++ b/lib/getid3/module.archive.zip.php
@@ -121,11 +121,11 @@ class getid3_zip extends getid3_handler
 					    !empty($info['zip']['files']['docProps']['core.xml'])) {
 							// http://technet.microsoft.com/en-us/library/cc179224.aspx
 							$info['fileformat'] = 'zip.msoffice';
-							if (!empty($ThisFileInfo['zip']['files']['ppt'])) {
+							if (!empty($info['zip']['files']['ppt'])) {
 								$info['mime_type'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
-							} elseif (!empty($ThisFileInfo['zip']['files']['xl'])) {
+							} elseif (!empty($info['zip']['files']['xl'])) {
 								$info['mime_type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
-							} elseif (!empty($ThisFileInfo['zip']['files']['word'])) {
+							} elseif (!empty($info['zip']['files']['word'])) {
 								$info['mime_type'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
 							}
 					}
@@ -231,6 +231,7 @@ class getid3_zip extends getid3_handler
 	 * @return array|false
 	 */
 	public function ZIPparseLocalFileHeader() {
+		$LocalFileHeader = array();
 		$LocalFileHeader['offset'] = $this->ftell();
 
 		$ZIPlocalFileHeader = $this->fread(30);
@@ -329,6 +330,7 @@ class getid3_zip extends getid3_handler
 	 * @return array|false
 	 */
 	public function ZIPparseCentralDirectory() {
+		$CentralDirectory = array();
 		$CentralDirectory['offset'] = $this->ftell();
 
 		$ZIPcentralDirectory = $this->fread(46);
@@ -388,6 +390,7 @@ class getid3_zip extends getid3_handler
 	 * @return array|false
 	 */
 	public function ZIPparseEndOfCentralDirectory() {
+		$EndOfCentralDirectory = array();
 		$EndOfCentralDirectory['offset'] = $this->ftell();
 
 		$ZIPendOfCentralDirectory = $this->fread(22);
diff --git a/lib/getid3/module.audio-video.asf.php b/lib/getid3/module.audio-video.asf.php
index fce923c0272ec1dcb7eee895581f8b1d0fbdf55a..85edb7d755e68524165dfae4d50fefd31dfed576 100644
--- a/lib/getid3/module.audio-video.asf.php
+++ b/lib/getid3/module.audio-video.asf.php
@@ -93,6 +93,7 @@ class getid3_asf extends getid3_handler
 		$offset = 0;
 		$thisfile_asf_streambitratepropertiesobject = array();
 		$thisfile_asf_codeclistobject = array();
+		$StreamPropertiesObjectData = array();
 
 		for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) {
 			$NextObjectGUID = substr($ASFHeaderData, $offset, 16);
@@ -283,7 +284,7 @@ class getid3_asf extends getid3_handler
 					$thisfile_asf_headerextensionobject['reserved_2']          = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
 					$offset += 2;
 					if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) {
-						$this->warning('header_extension_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']).') does not match expected value of "6"');
+						$this->warning('header_extension_object.reserved_2 ('.$thisfile_asf_headerextensionobject['reserved_2'].') does not match expected value of "6"');
 						//return false;
 						break;
 					}
@@ -535,7 +536,7 @@ class getid3_asf extends getid3_handler
 					$thisfile_asf_markerobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
 					$offset += 2;
 					if ($thisfile_asf_markerobject['reserved_2'] != 0) {
-						$this->warning('marker_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_markerobject['reserved_2']).') does not match expected value of "0"');
+						$this->warning('marker_object.reserved_2 ('.$thisfile_asf_markerobject['reserved_2'].') does not match expected value of "0"');
 						break;
 					}
 					$thisfile_asf_markerobject['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
@@ -1193,7 +1194,7 @@ class getid3_asf extends getid3_handler
 					$thisfile_asf_dataobject['reserved']           = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 2));
 					$offset += 2;
 					if ($thisfile_asf_dataobject['reserved'] != 0x0101) {
-						$this->warning('data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"');
+						$this->warning('data_object.reserved (0x'.sprintf('%04X', $thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"');
 						//return false;
 						break;
 					}
diff --git a/lib/getid3/module.audio-video.flv.php b/lib/getid3/module.audio-video.flv.php
index 7e684072e35f89bd36674a08ab4075d3972af52a..3b59e596e331c142c91bd281046f05d450ca72cf 100644
--- a/lib/getid3/module.audio-video.flv.php
+++ b/lib/getid3/module.audio-video.flv.php
@@ -161,6 +161,7 @@ class getid3_flv extends getid3_handler
 						$info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
 
 						$FLVvideoHeader = $this->fread(11);
+						$PictureSizeEnc = array();
 
 						if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
 							// this code block contributed by: moysevichØgmail*com
diff --git a/lib/getid3/module.audio-video.ivf.php b/lib/getid3/module.audio-video.ivf.php
index 791b88bc9fd947daf834fe021415bbb0f93dd3d1..293cc99b6ed6461e646309c8400ec88fbe9d3f56 100644
--- a/lib/getid3/module.audio-video.ivf.php
+++ b/lib/getid3/module.audio-video.ivf.php
@@ -56,6 +56,7 @@ class getid3_ivf extends getid3_handler
 			$info['video']['codec']           =         $info['ivf']['header']['fourcc'];
 
 			$info['ivf']['frame_count'] = 0;
+			$timestamp                  = 0;
 			while (!$this->feof()) {
 				if ($frameheader = $this->fread(12)) {
 					$framesize = getid3_lib::LittleEndian2Int(substr($frameheader, 0, 4)); // size of frame in bytes (not including the 12-byte header)
diff --git a/lib/getid3/module.audio-video.matroska.php b/lib/getid3/module.audio-video.matroska.php
index a2851085907ca7d0c2d6419427b8d0ac77f43d95..128e614e2c5cceddd9ce591b2b359d1c964b5a93 100644
--- a/lib/getid3/module.audio-video.matroska.php
+++ b/lib/getid3/module.audio-video.matroska.php
@@ -224,14 +224,14 @@ class getid3_matroska extends getid3_handler
 	 *
 	 * @var bool
 	 */
-	public static $hide_clusters    = true;
+	public $hide_clusters    = true;
 
 	/**
 	 * True to parse the whole file, not only header [default: FALSE].
 	 *
 	 * @var bool
 	 */
-	public static $parse_whole_file = false;
+	public $parse_whole_file = false;
 
 	/*
 	 * Private parser settings/placeholders.
@@ -586,7 +586,7 @@ class getid3_matroska extends getid3_handler
 					$info['matroska']['segment'][0]['length'] = $top_element['length'];
 
 					while ($this->getEBMLelement($element_data, $top_element['end'])) {
-						if ($element_data['id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required
+						if ($element_data['id'] != EBML_ID_CLUSTER || !$this->hide_clusters) { // collect clusters only if required
 							$info['matroska']['segments'][] = $element_data;
 						}
 						switch ($element_data['id']) {
@@ -618,7 +618,7 @@ class getid3_matroska extends getid3_handler
 												$this->warning('seek_entry[target_id] unexpectedly not set at '.$seek_entry['offset']);
 												break;
 											}
-											if (($seek_entry['target_id'] != EBML_ID_CLUSTER) || !self::$hide_clusters) { // collect clusters only if required
+											if (($seek_entry['target_id'] != EBML_ID_CLUSTER) || !$this->hide_clusters) { // collect clusters only if required
 												$info['matroska']['seek'][] = $seek_entry;
 											}
 											break;
@@ -905,7 +905,7 @@ class getid3_matroska extends getid3_handler
 								break;
 
 							case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams.
-								if (self::$hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway
+								if ($this->hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway
 									$this->current_offset = $element_data['end'];
 									break;
 								}
@@ -1246,12 +1246,12 @@ class getid3_matroska extends getid3_handler
 									}
 									$this->current_offset = $subelement['end'];
 								}
-								if (!self::$hide_clusters) {
+								if (!$this->hide_clusters) {
 									$info['matroska']['cluster'][] = $cluster_entry;
 								}
 
 								// check to see if all the data we need exists already, if so, break out of the loop
-								if (!self::$parse_whole_file) {
+								if (!$this->parse_whole_file) {
 									if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) {
 										if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) {
 											if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) {
diff --git a/lib/getid3/module.audio-video.mpeg.php b/lib/getid3/module.audio-video.mpeg.php
index 5ca4120b1602665845eaa633d410ac3afc26d5df..0504da197c3680da3993090b59b7ee10dd7d62dd 100644
--- a/lib/getid3/module.audio-video.mpeg.php
+++ b/lib/getid3/module.audio-video.mpeg.php
@@ -546,6 +546,7 @@ echo 'average_File_bitrate = '.number_format(array_sum($vbr_bitrates) / count($v
 		$VideoBitrate = max(min($VideoBitrate / 1000, 10000), 10); // limit to range of 10kbps -  10Mbps (beyond that curves flatten anyways, no big loss)
 
 
+		$OverheadMultiplierByBitrate      = array();
 		//OMBB[audiobitrate]              = array(video-10kbps,       video-100kbps,      video-1000kbps,     video-10000kbps)
 		$OverheadMultiplierByBitrate[32]  = array(0, 0.9676287944368530, 0.9802276264360310, 0.9844916183244460, 0.9852821845179940);
 		$OverheadMultiplierByBitrate[48]  = array(0, 0.9779100089209830, 0.9787770035359320, 0.9846738664076130, 0.9852683013799960);
diff --git a/lib/getid3/module.audio-video.quicktime.php b/lib/getid3/module.audio-video.quicktime.php
index c176e5a03ce01ab4282f7e4cc2f8f48c7162e15a..2250e1c7934bc771269cfdf1251492541915a471 100644
--- a/lib/getid3/module.audio-video.quicktime.php
+++ b/lib/getid3/module.audio-video.quicktime.php
@@ -24,7 +24,18 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE_
 class getid3_quicktime extends getid3_handler
 {
 
-	public $ReturnAtomData        = true;
+	/** audio-video.quicktime
+	 * return all parsed data from all atoms if true, otherwise just returned parsed metadata
+	 *
+	 * @var bool
+	 */
+	public $ReturnAtomData        = false;
+
+	/** audio-video.quicktime
+	 * return all parsed data from all atoms if true, otherwise just returned parsed metadata
+	 *
+	 * @var bool
+	 */
 	public $ParseAllPossibleAtoms = false;
 
 	/**
@@ -169,7 +180,7 @@ class getid3_quicktime extends getid3_handler
 			}
 		}
 
-		if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) {
+		if (!isset($info['bitrate']) && !empty($info['playtime_seconds'])) {
 			$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
 		}
 		if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) {
@@ -559,15 +570,28 @@ class getid3_quicktime extends getid3_handler
 											default:
 												$atom_structure['data'] = substr($boxdata, 8);
 												if ($atomname == 'covr') {
-													// not a foolproof check, but better than nothing
-													if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
-														$atom_structure['image_mime'] = 'image/jpeg';
-													} elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
-														$atom_structure['image_mime'] = 'image/png';
-													} elseif (preg_match('#^GIF#', $atom_structure['data'])) {
-														$atom_structure['image_mime'] = 'image/gif';
+													if (!empty($atom_structure['data'])) {
+														$atom_structure['image_mime'] = 'image/unknown'; // provide default MIME type to ensure array keys exist
+														if (function_exists('getimagesizefromstring') && ($getimagesize = getimagesizefromstring($atom_structure['data'])) && !empty($getimagesize['mime'])) {
+															$atom_structure['image_mime'] = $getimagesize['mime'];
+														} else {
+															// if getimagesizefromstring is not available, or fails for some reason, fall back to simple detection of common image formats
+															$ImageFormatSignatures = array(
+																'image/jpeg' => "\xFF\xD8\xFF",
+																'image/png'  => "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A",
+																'image/gif'  => 'GIF',
+															);
+															foreach ($ImageFormatSignatures as $mime => $image_format_signature) {
+																if (substr($atom_structure['data'], 0, strlen($image_format_signature)) == $image_format_signature) {
+																	$atom_structure['image_mime'] = $mime;
+																	break;
+																}
+															}
+														}
+														$info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_structure['data'], 'description'=>'cover');
+													} else {
+														$this->warning('Unknown empty "covr" image at offset '.$baseoffset);
 													}
-													$info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_structure['data'], 'description'=>'cover');
 												}
 												break;
 
@@ -727,11 +751,13 @@ class getid3_quicktime extends getid3_handler
 					$atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag'];
 					$atom_structure['flags']['slide_show']   = (bool) $atom_structure['slide_show_flag'];
 
-					$ptv_lookup[0] = 'normal';
-					$ptv_lookup[1] = 'double';
-					$ptv_lookup[2] = 'half';
-					$ptv_lookup[3] = 'full';
-					$ptv_lookup[4] = 'current';
+					$ptv_lookup = array(
+						0 => 'normal',
+						1 => 'double',
+						2 => 'half',
+						3 => 'full',
+						4 => 'current'
+					);
 					if (isset($ptv_lookup[$atom_structure['display_size_raw']])) {
 						$atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
 					} else {
@@ -907,13 +933,13 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
 										$atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66,  2));
 										$atom_structure['sample_description_table'][$i]['video_color_table_id']    =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68,  2));
 
-										$atom_structure['sample_description_table'][$i]['video_pixel_color_type']  = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color');
+										$atom_structure['sample_description_table'][$i]['video_pixel_color_type']  = (((int) $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color');
 										$atom_structure['sample_description_table'][$i]['video_pixel_color_name']  = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']);
 
 										if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') {
 											$info['quicktime']['video']['codec_fourcc']        = $atom_structure['sample_description_table'][$i]['data_format'];
 											$info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
-											$info['quicktime']['video']['codec']               = (($atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']);
+											$info['quicktime']['video']['codec']               = (((int) $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']);
 											$info['quicktime']['video']['color_depth']         = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'];
 											$info['quicktime']['video']['color_depth_name']    = $atom_structure['sample_description_table'][$i]['video_pixel_color_name'];
 
@@ -1597,33 +1623,61 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
 					break;
 
 				case 'NCDT':
-					// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
+					// https://exiftool.org/TagNames/Nikon.html
 					// Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
 					$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms);
 					break;
 				case 'NCTH': // Nikon Camera THumbnail image
 				case 'NCVW': // Nikon Camera preVieW image
-					// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
+				case 'NCM1': // Nikon Camera preview iMage 1
+				case 'NCM2': // Nikon Camera preview iMage 2
+					// https://exiftool.org/TagNames/Nikon.html
 					if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) {
+						$descriptions = array(
+							'NCTH' => 'Nikon Camera Thumbnail Image',
+							'NCVW' => 'Nikon Camera Preview Image',
+							'NCM1' => 'Nikon Camera Preview Image 1',
+							'NCM2' => 'Nikon Camera Preview Image 2',
+						);
 						$atom_structure['data'] = $atom_data;
 						$atom_structure['image_mime'] = 'image/jpeg';
-						$atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image'));
-						$info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']);
+						$atom_structure['description'] = isset($descriptions[$atomname]) ? $descriptions[$atomname] : 'Nikon preview image';
+						$info['quicktime']['comments']['picture'][] = array(
+							'image_mime' => $atom_structure['image_mime'],
+							'data' => $atom_data,
+							'description' => $atom_structure['description']
+						);
 					}
 					break;
-				case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
-					$atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data);
+				case 'NCTG': // Nikon - https://exiftool.org/TagNames/Nikon.html#NCTG
+					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.nikon-nctg.php', __FILE__, true);
+					$nikonNCTG = new getid3_tag_nikon_nctg($this->getid3);
+
+					$atom_structure['data'] = $nikonNCTG->parse($atom_data);
+					break;
+				case 'NCHD': // Nikon:MakerNoteVersion  - https://exiftool.org/TagNames/Nikon.html
+					$makerNoteVersion = '';
+					for ($i = 0, $iMax = strlen($atom_data); $i < $iMax; ++$i) {
+						if (ord($atom_data[$i]) >= 0x00 && ord($atom_data[$i]) <= 0x1F) {
+							$makerNoteVersion .= ' '.ord($atom_data[$i]);
+						} else {
+							$makerNoteVersion .= $atom_data[$i];
+						}
+					}
+					$makerNoteVersion = rtrim($makerNoteVersion, "\x00");
+					$atom_structure['data'] = array(
+						'MakerNoteVersion' => $makerNoteVersion
+					);
 					break;
-				case 'NCHD': // Nikon:MakerNoteVersion  - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
-				case 'NCDB': // Nikon                   - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
-				case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html
+				case 'NCDB': // Nikon                   - https://exiftool.org/TagNames/Nikon.html
+				case 'CNCV': // Canon:CompressorVersion - https://exiftool.org/TagNames/Canon.html
 					$atom_structure['data'] = $atom_data;
 					break;
 
 				case "\x00\x00\x00\x00":
 					// some kind of metacontainer, may contain a big data dump such as:
 					// mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4
-					// http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt
+					// https://xhelmboyx.tripod.com/formats/qti-layout.txt
 
 					$atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
 					$atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
@@ -1720,6 +1774,7 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
 									'unknown_data'   => array(),
 									'debug_list'     => '',      // Used to debug variables stored as comma delimited strings
 							);
+							$debug_structure = array();
 							$debug_structure['debug_items'] = array();
 							// Can start loop here to decode all sensor data in 32 Byte chunks:
 							foreach (str_split($atom_SENSOR_data, 32) as $sensor_key => $sensor_data) {
@@ -2038,7 +2093,7 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
 	 * @return array|false
 	 */
 	public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
-		$atom_structure  = false;
+		$atom_structure = array();
 		$subatomoffset  = 0;
 		$subatomcounter = 0;
 		if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) {
@@ -2056,17 +2111,22 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
 					$subatomoffset += 4;
 					continue;
 				}
-				return $atom_structure;
+				break;
 			}
 			if (strlen($subatomdata) < ($subatomsize - 8)) {
 			    // we don't have enough data to decode the subatom.
 			    // this may be because we are refusing to parse large subatoms, or it may be because this atom had its size set too large
 			    // so we passed in the start of a following atom incorrectly?
-			    return $atom_structure;
+			    break;
 			}
 			$atom_structure[$subatomcounter++] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms);
 			$subatomoffset += $subatomsize;
 		}
+
+		if (empty($atom_structure)) {
+			return false;
+		}
+
 		return $atom_structure;
 	}
 
@@ -2551,8 +2611,9 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
 		static $QuicktimeContentRatingLookup = array();
 		if (empty($QuicktimeContentRatingLookup)) {
 			$QuicktimeContentRatingLookup[0]  = 'None';
+			$QuicktimeContentRatingLookup[1]  = 'Explicit';
 			$QuicktimeContentRatingLookup[2]  = 'Clean';
-			$QuicktimeContentRatingLookup[4]  = 'Explicit';
+			$QuicktimeContentRatingLookup[4]  = 'Explicit (old)';
 		}
 		return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid');
 	}
@@ -2605,189 +2666,6 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
 		return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid');
 	}
 
-	/**
-	 * @param string $atom_data
-	 *
-	 * @return array
-	 */
-	public function QuicktimeParseNikonNCTG($atom_data) {
-		// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
-		// Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
-		// Data is stored as records of:
-		// * 4 bytes record type
-		// * 2 bytes size of data field type:
-		//     0x0001 = flag   (size field *= 1-byte)
-		//     0x0002 = char   (size field *= 1-byte)
-		//     0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
-		//     0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
-		//     0x0005 = float  (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
-		//     0x0007 = bytes  (size field *= 1-byte), values are stored as ??????
-		//     0x0008 = ?????  (size field *= 2-byte), values are stored as ??????
-		// * 2 bytes data size field
-		// * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15")
-		// all integers are stored BigEndian
-
-		$NCTGtagName = array(
-			0x00000001 => 'Make',
-			0x00000002 => 'Model',
-			0x00000003 => 'Software',
-			0x00000011 => 'CreateDate',
-			0x00000012 => 'DateTimeOriginal',
-			0x00000013 => 'FrameCount',
-			0x00000016 => 'FrameRate',
-			0x00000022 => 'FrameWidth',
-			0x00000023 => 'FrameHeight',
-			0x00000032 => 'AudioChannels',
-			0x00000033 => 'AudioBitsPerSample',
-			0x00000034 => 'AudioSampleRate',
-			0x02000001 => 'MakerNoteVersion',
-			0x02000005 => 'WhiteBalance',
-			0x0200000b => 'WhiteBalanceFineTune',
-			0x0200001e => 'ColorSpace',
-			0x02000023 => 'PictureControlData',
-			0x02000024 => 'WorldTime',
-			0x02000032 => 'UnknownInfo',
-			0x02000083 => 'LensType',
-			0x02000084 => 'Lens',
-		);
-
-		$offset = 0;
-		$data = null;
-		$datalength = strlen($atom_data);
-		$parsed = array();
-		while ($offset < $datalength) {
-			$record_type       = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4));  $offset += 4;
-			$data_size_type    = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2));  $offset += 2;
-			$data_size         = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2));  $offset += 2;
-			switch ($data_size_type) {
-				case 0x0001: // 0x0001 = flag   (size field *= 1-byte)
-					$data = getid3_lib::BigEndian2Int(substr($atom_data, $offset, $data_size * 1));
-					$offset += ($data_size * 1);
-					break;
-				case 0x0002: // 0x0002 = char   (size field *= 1-byte)
-					$data = substr($atom_data, $offset, $data_size * 1);
-					$offset += ($data_size * 1);
-					$data = rtrim($data, "\x00");
-					break;
-				case 0x0003: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
-					$data = '';
-					for ($i = $data_size - 1; $i >= 0; $i--) {
-						$data .= substr($atom_data, $offset + ($i * 2), 2);
-					}
-					$data = getid3_lib::BigEndian2Int($data);
-					$offset += ($data_size * 2);
-					break;
-				case 0x0004: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
-					$data = '';
-					for ($i = $data_size - 1; $i >= 0; $i--) {
-						$data .= substr($atom_data, $offset + ($i * 4), 4);
-					}
-					$data = getid3_lib::BigEndian2Int($data);
-					$offset += ($data_size * 4);
-					break;
-				case 0x0005: // 0x0005 = float  (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
-					$data = array();
-					for ($i = 0; $i < $data_size; $i++) {
-						$numerator    = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 0, 4));
-						$denomninator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 4, 4));
-						if ($denomninator == 0) {
-							$data[$i] = false;
-						} else {
-							$data[$i] = (double) $numerator / $denomninator;
-						}
-					}
-					$offset += (8 * $data_size);
-					if (count($data) == 1) {
-						$data = $data[0];
-					}
-					break;
-				case 0x0007: // 0x0007 = bytes  (size field *= 1-byte), values are stored as ??????
-					$data = substr($atom_data, $offset, $data_size * 1);
-					$offset += ($data_size * 1);
-					break;
-				case 0x0008: // 0x0008 = ?????  (size field *= 2-byte), values are stored as ??????
-					$data = substr($atom_data, $offset, $data_size * 2);
-					$offset += ($data_size * 2);
-					break;
-				default:
-					echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br>';
-					break 2;
-			}
-
-			switch ($record_type) {
-				case 0x00000011: // CreateDate
-				case 0x00000012: // DateTimeOriginal
-					$data = strtotime($data);
-					break;
-				case 0x0200001e: // ColorSpace
-					switch ($data) {
-						case 1:
-							$data = 'sRGB';
-							break;
-						case 2:
-							$data = 'Adobe RGB';
-							break;
-					}
-					break;
-				case 0x02000023: // PictureControlData
-					$PictureControlAdjust = array(0=>'default', 1=>'quick', 2=>'full');
-					$FilterEffect = array(0x80=>'off', 0x81=>'yellow', 0x82=>'orange',    0x83=>'red', 0x84=>'green',  0xff=>'n/a');
-					$ToningEffect = array(0x80=>'b&w', 0x81=>'sepia',  0x82=>'cyanotype', 0x83=>'red', 0x84=>'yellow', 0x85=>'green', 0x86=>'blue-green', 0x87=>'blue', 0x88=>'purple-blue', 0x89=>'red-purple', 0xff=>'n/a');
-					$data = array(
-						'PictureControlVersion'     =>                           substr($data,  0,  4),
-						'PictureControlName'        =>                     rtrim(substr($data,  4, 20), "\x00"),
-						'PictureControlBase'        =>                     rtrim(substr($data, 24, 20), "\x00"),
-						//'?'                       =>                           substr($data, 44,  4),
-						'PictureControlAdjust'      => $PictureControlAdjust[ord(substr($data, 48,  1))],
-						'PictureControlQuickAdjust' =>                       ord(substr($data, 49,  1)),
-						'Sharpness'                 =>                       ord(substr($data, 50,  1)),
-						'Contrast'                  =>                       ord(substr($data, 51,  1)),
-						'Brightness'                =>                       ord(substr($data, 52,  1)),
-						'Saturation'                =>                       ord(substr($data, 53,  1)),
-						'HueAdjustment'             =>                       ord(substr($data, 54,  1)),
-						'FilterEffect'              =>         $FilterEffect[ord(substr($data, 55,  1))],
-						'ToningEffect'              =>         $ToningEffect[ord(substr($data, 56,  1))],
-						'ToningSaturation'          =>                       ord(substr($data, 57,  1)),
-					);
-					break;
-				case 0x02000024: // WorldTime
-					// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#WorldTime
-					// timezone is stored as offset from GMT in minutes
-					$timezone = getid3_lib::BigEndian2Int(substr($data, 0, 2));
-					if ($timezone & 0x8000) {
-						$timezone = 0 - (0x10000 - $timezone);
-					}
-					$timezone /= 60;
-
-					$dst = (bool) getid3_lib::BigEndian2Int(substr($data, 2, 1));
-					switch (getid3_lib::BigEndian2Int(substr($data, 3, 1))) {
-						case 2:
-							$datedisplayformat = 'D/M/Y'; break;
-						case 1:
-							$datedisplayformat = 'M/D/Y'; break;
-						case 0:
-						default:
-							$datedisplayformat = 'Y/M/D'; break;
-					}
-
-					$data = array('timezone'=>floatval($timezone), 'dst'=>$dst, 'display'=>$datedisplayformat);
-					break;
-				case 0x02000083: // LensType
-					$data = array(
-						//'_'  => $data,
-						'mf' => (bool) ($data & 0x01),
-						'd'  => (bool) ($data & 0x02),
-						'g'  => (bool) ($data & 0x04),
-						'vr' => (bool) ($data & 0x08),
-					);
-					break;
-			}
-			$tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x'.str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT));
-			$parsed[$tag_name] = $data;
-		}
-		return $parsed;
-	}
-
 	/**
 	 * @param string $keyname
 	 * @param string|array $data
diff --git a/lib/getid3/module.audio-video.riff.php b/lib/getid3/module.audio-video.riff.php
index cdf553386bc34c96dce88fe49d42fdd39b3808a0..6ef1116ad13ea49279e873dbc7e36a379597449c 100644
--- a/lib/getid3/module.audio-video.riff.php
+++ b/lib/getid3/module.audio-video.riff.php
@@ -56,6 +56,7 @@ class getid3_riff extends getid3_handler
 		$thisfile_riff_video       = &$thisfile_riff['video'];
 		$thisfile_riff_WAVE        = array();
 
+		$Original                 = array();
 		$Original['avdataoffset'] = $info['avdataoffset'];
 		$Original['avdataend']    = $info['avdataend'];
 
@@ -296,9 +297,18 @@ class getid3_riff extends getid3_handler
 					// shortcut
 					$thisfile_riff_WAVE_bext_0 = &$thisfile_riff_WAVE['bext'][0];
 
-					$thisfile_riff_WAVE_bext_0['title']          =                         trim(substr($thisfile_riff_WAVE_bext_0['data'],   0, 256));
-					$thisfile_riff_WAVE_bext_0['author']         =                         trim(substr($thisfile_riff_WAVE_bext_0['data'], 256,  32));
-					$thisfile_riff_WAVE_bext_0['reference']      =                         trim(substr($thisfile_riff_WAVE_bext_0['data'], 288,  32));
+					$thisfile_riff_WAVE_bext_0['title']          =                              substr($thisfile_riff_WAVE_bext_0['data'],   0, 256);
+					$thisfile_riff_WAVE_bext_0['author']         =                              substr($thisfile_riff_WAVE_bext_0['data'], 256,  32);
+					$thisfile_riff_WAVE_bext_0['reference']      =                              substr($thisfile_riff_WAVE_bext_0['data'], 288,  32);
+					foreach (array('title','author','reference') as $bext_key) {
+						// Some software (notably Logic Pro) may not blank existing data before writing a null-terminated string to the offsets
+						// assigned for text fields, resulting in a null-terminated string (or possibly just a single null) followed by garbage
+						// Keep only string as far as first null byte, discard rest of fixed-width data
+						// https://github.com/JamesHeinrich/getID3/issues/263
+						$null_terminator_offset = strpos($thisfile_riff_WAVE_bext_0[$bext_key], "\x00");
+						$thisfile_riff_WAVE_bext_0[$bext_key] = substr($thisfile_riff_WAVE_bext_0[$bext_key], 0, $null_terminator_offset);
+					}
+
 					$thisfile_riff_WAVE_bext_0['origin_date']    =                              substr($thisfile_riff_WAVE_bext_0['data'], 320,  10);
 					$thisfile_riff_WAVE_bext_0['origin_time']    =                              substr($thisfile_riff_WAVE_bext_0['data'], 330,   8);
 					$thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338,   8));
@@ -307,6 +317,7 @@ class getid3_riff extends getid3_handler
 					$thisfile_riff_WAVE_bext_0['coding_history'] =         explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601)));
 					if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) {
 						if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) {
+							$bext_timestamp = array();
 							list($dummy, $bext_timestamp['year'], $bext_timestamp['month'],  $bext_timestamp['day'])    = $matches_bext_date;
 							list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time;
 							$thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']);
@@ -451,7 +462,62 @@ class getid3_riff extends getid3_handler
 					}
 				}
 
+				if (isset($thisfile_riff_WAVE['guan'][0]['data'])) {
+					// shortcut
+					$thisfile_riff_WAVE_guan_0 = &$thisfile_riff_WAVE['guan'][0];
+					if (!empty($thisfile_riff_WAVE_guan_0['data']) && (substr($thisfile_riff_WAVE_guan_0['data'], 0, 14) == 'GUANO|Version:')) {
+						$thisfile_riff['guano'] = array();
+						foreach (explode("\n", $thisfile_riff_WAVE_guan_0['data']) as $line) {
+							if ($line) {
+								@list($key, $value) = explode(':', $line, 2);
+								if (substr($value, 0, 3) == '[{"') {
+									if ($decoded = @json_decode($value, true)) {
+										if (!empty($decoded) && (count($decoded) == 1)) {
+											$value = $decoded[0];
+										} else {
+											$value = $decoded;
+										}
+									}
+								}
+								$thisfile_riff['guano'] = array_merge_recursive($thisfile_riff['guano'], getid3_lib::CreateDeepArray($key, '|', $value));
+							}
+						}
+
+						// https://www.wildlifeacoustics.com/SCHEMA/GUANO.html
+						foreach ($thisfile_riff['guano'] as $key => $value) {
+							switch ($key) {
+								case 'Loc Position':
+									if (preg_match('#^([\\+\\-]?[0-9]+\\.[0-9]+) ([\\+\\-]?[0-9]+\\.[0-9]+)$#', $value, $matches)) {
+										list($dummy, $latitude, $longitude) = $matches;
+										$thisfile_riff['comments']['gps_latitude'][0]  = floatval($latitude);
+										$thisfile_riff['comments']['gps_longitude'][0] = floatval($longitude);
+										$thisfile_riff['guano'][$key] = floatval($latitude).' '.floatval($longitude);
+									}
+									break;
+								case 'Loc Elevation': // Elevation/altitude above mean sea level in meters
+									$thisfile_riff['comments']['gps_altitude'][0] = floatval($value);
+									$thisfile_riff['guano'][$key] = (float) $value;
+									break;
+								case 'Filter HP':        // High-pass filter frequency in kHz
+								case 'Filter LP':        // Low-pass filter frequency in kHz
+								case 'Humidity':         // Relative humidity as a percentage
+								case 'Length':           // Recording length in seconds
+								case 'Loc Accuracy':     // Estimated Position Error in meters
+								case 'Temperature Ext':  // External temperature in degrees Celsius outside the recorder's housing
+								case 'Temperature Int':  // Internal temperature in degrees Celsius inside the recorder's housing
+									$thisfile_riff['guano'][$key] = (float) $value;
+									break;
+								case 'Samplerate':       // Recording sample rate, Hz
+								case 'TE':               // Time-expansion factor. If not specified, then 1 (no time-expansion a.k.a. direct-recording) is assumed.
+									$thisfile_riff['guano'][$key] = (int) $value;
+									break;
+							}
+						}
 
+					} else {
+						$this->warning('RIFF.guan data not in expected format');
+					}
+				}
 
 				if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) {
 					$thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate'];
@@ -733,6 +799,7 @@ class getid3_riff extends getid3_handler
 				}
 				if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) {
 					if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) {
+						$thisfile_riff_raw_strf_strhfccType_streamindex = null;
 						for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) {
 							if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) {
 								$strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'];
@@ -1069,7 +1136,7 @@ class getid3_riff extends getid3_handler
 				if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) {
 					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
 					$getid3_temp = new getID3();
-					$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp);
+					$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
 					$getid3_id3v2 = new getid3_id3v2($getid3_temp);
 					$getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8;
 					if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) {
@@ -1172,7 +1239,7 @@ class getid3_riff extends getid3_handler
 					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, true);
 
 					$getid3_temp = new getID3();
-					$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp);
+					$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
 					$getid3_mpeg = new getid3_mpeg($getid3_temp);
 					$getid3_mpeg->Analyze();
 					if (empty($getid3_temp->info['error'])) {
@@ -1258,7 +1325,7 @@ class getid3_riff extends getid3_handler
 					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
 
 					$getid3_temp = new getID3();
-					$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp);
+					$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
 					$getid3_id3v2 = new getid3_id3v2($getid3_temp);
 					$getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['id3 '][0]['offset'] + 8;
 					if ($thisfile_riff[$RIFFsubtype]['id3 '][0]['valid'] = $getid3_id3v2->Analyze()) {
@@ -1514,6 +1581,9 @@ class getid3_riff extends getid3_handler
 
 		$RIFFchunk = false;
 		$FoundAllChunksWeNeed = false;
+		$LISTchunkParent = null;
+		$LISTchunkMaxOffset = null;
+		$AC3syncwordBytes = pack('n', getid3_ac3::syncword); // 0x0B77 -> "\x0B\x77"
 
 		try {
 			$this->fseek($startoffset);
@@ -1557,7 +1627,7 @@ class getid3_riff extends getid3_handler
 										// MP3
 										if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) {
 											$getid3_temp = new getID3();
-											$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp);
+											$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
 											$getid3_temp->info['avdataoffset'] = $this->ftell() - 4;
 											$getid3_temp->info['avdataend']    = $this->ftell() + $AudioChunkSize;
 											$getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__);
@@ -1575,11 +1645,10 @@ class getid3_riff extends getid3_handler
 											unset($getid3_temp, $getid3_mp3);
 										}
 
-									} elseif (strpos($FirstFourBytes, getid3_ac3::syncword) === 0) {
-
+									} elseif (strpos($FirstFourBytes, $AC3syncwordBytes) === 0) {
 										// AC3
 										$getid3_temp = new getID3();
-										$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp);
+										$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
 										$getid3_temp->info['avdataoffset'] = $this->ftell() - 4;
 										$getid3_temp->info['avdataend']    = $this->ftell() + $AudioChunkSize;
 										$getid3_ac3 = new getid3_ac3($getid3_temp);
@@ -1640,7 +1709,7 @@ class getid3_riff extends getid3_handler
 									// Probably is MP3 data
 									if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) {
 										$getid3_temp = new getID3();
-										$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp);
+										$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
 										$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
 										$getid3_temp->info['avdataend']    = $info['avdataend'];
 										$getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__);
@@ -1652,12 +1721,12 @@ class getid3_riff extends getid3_handler
 										unset($getid3_temp, $getid3_mp3);
 									}
 
-								} elseif (($isRegularAC3 = (substr($testData, 0, 2) == getid3_ac3::syncword)) || substr($testData, 8, 2) == strrev(getid3_ac3::syncword)) {
+								} elseif (($isRegularAC3 = (substr($testData, 0, 2) == $AC3syncwordBytes)) || substr($testData, 8, 2) == strrev($AC3syncwordBytes)) {
 
 									// This is probably AC-3 data
 									$getid3_temp = new getID3();
 									if ($isRegularAC3) {
-										$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp);
+										$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
 										$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
 										$getid3_temp->info['avdataend']    = $info['avdataend'];
 									}
@@ -1673,6 +1742,8 @@ class getid3_riff extends getid3_handler
 											$ac3_data .= substr($testData, 8 + $i + 1, 1);
 											$ac3_data .= substr($testData, 8 + $i + 0, 1);
 										}
+										$getid3_ac3->getid3->info['avdataoffset'] = 0;
+										$getid3_ac3->getid3->info['avdataend']    = strlen($ac3_data);
 										$getid3_ac3->AnalyzeString($ac3_data);
 									}
 
@@ -1691,7 +1762,7 @@ class getid3_riff extends getid3_handler
 
 									// This is probably DTS data
 									$getid3_temp = new getID3();
-									$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp);
+									$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
 									$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
 									$getid3_dts = new getid3_dts($getid3_temp);
 									$getid3_dts->Analyze();
@@ -1732,6 +1803,8 @@ class getid3_riff extends getid3_handler
 							case 'indx':
 							case 'MEXT':
 							case 'DISP':
+							case 'wamd':
+							case 'guan':
 								// always read data in
 							case 'JUNK':
 								// should be: never read data in
@@ -2076,6 +2149,7 @@ class getid3_riff extends getid3_handler
 	 */
 	public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) {
 
+		$parsed                    = array();
 		$parsed['biSize']          = substr($BITMAPINFOHEADER,  0, 4); // number of bytes required by the BITMAPINFOHEADER structure
 		$parsed['biWidth']         = substr($BITMAPINFOHEADER,  4, 4); // width of the bitmap in pixels
 		$parsed['biHeight']        = substr($BITMAPINFOHEADER,  8, 4); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner
diff --git a/lib/getid3/module.audio-video.swf.php b/lib/getid3/module.audio-video.swf.php
index fc4a5c526589e693590dc153275db8f8ce84538f..a9b2c1a657e1a6091e2548e08b708c57bfcf9d2f 100644
--- a/lib/getid3/module.audio-video.swf.php
+++ b/lib/getid3/module.audio-video.swf.php
@@ -20,6 +20,11 @@ if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that acce
 
 class getid3_swf extends getid3_handler
 {
+	/**
+	 * return all parsed tags if true, otherwise do not return tags not parsed by getID3
+	 *
+	 * @var bool
+	 */
 	public $ReturnAllTagData = false;
 
 	/**
@@ -115,7 +120,7 @@ class getid3_swf extends getid3_handler
 				$CurrentOffset += 4;
 			}
 
-			unset($TagData);
+			$TagData           = array();
 			$TagData['offset'] = $CurrentOffset;
 			$TagData['size']   = $TagLength;
 			$TagData['id']     = $TagID;
diff --git a/lib/getid3/module.audio.dsdiff.php b/lib/getid3/module.audio.dsdiff.php
index bed2afca5976e8ebc872e20aaf0243da9741b662..20fb0d9a8abd6667157ced9b79260c4a2e685ea7 100644
--- a/lib/getid3/module.audio.dsdiff.php
+++ b/lib/getid3/module.audio.dsdiff.php
@@ -46,9 +46,10 @@ class getid3_dsdiff extends getid3_handler
 		$info['audio']['bits_per_sample'] = 1;
 
 		$info['dsdiff'] = array();
+		$thisChunk = null;
 		while (!$this->feof() && ($ChunkHeader = $this->fread(12))) {
 			if (strlen($ChunkHeader) < 12) {
-				$this->error('Expecting chunk header at offset '.$thisChunk['offset'].', found insufficient data in file, aborting parsing');
+				$this->error('Expecting chunk header at offset '.(isset($thisChunk['offset']) ? $thisChunk['offset'] : 'N/A').', found insufficient data in file, aborting parsing');
 				break;
 			}
 			$thisChunk = array();
@@ -209,7 +210,7 @@ class getid3_dsdiff extends getid3_handler
 
 					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
 					$getid3_temp = new getID3();
-					$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp);
+					$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
 					$getid3_id3v2 = new getid3_id3v2($getid3_temp);
 					$getid3_id3v2->StartingOffset = $this->ftell();
 					if ($thisChunk['valid'] = $getid3_id3v2->Analyze()) {
@@ -292,7 +293,7 @@ class getid3_dsdiff extends getid3_handler
 	}
 
 	/**
-	 * @param int $cmtType
+	 * @param int $markType
 	 *
 	 * @return string
 	 */
diff --git a/lib/getid3/module.audio.flac.php b/lib/getid3/module.audio.flac.php
index 1cea4364c2a0e8d7a4195466ac793a8be875caf8..014061da943c2b337830b5d55d6140ffa6a06cfe 100644
--- a/lib/getid3/module.audio.flac.php
+++ b/lib/getid3/module.audio.flac.php
@@ -402,6 +402,7 @@ class getid3_flac extends getid3_handler
 	public function parsePICTURE() {
 		$info = &$this->getid3->info;
 
+		$picture = array();
 		$picture['typeid']         = getid3_lib::BigEndian2Int($this->fread(4));
 		$picture['picturetype']    = self::pictureTypeLookup($picture['typeid']);
 		$picture['image_mime']     = $this->fread(getid3_lib::BigEndian2Int($this->fread(4)));
diff --git a/lib/getid3/module.audio.lpac.php b/lib/getid3/module.audio.lpac.php
index f49e2440992047136a48ddd6ad72be6b3324ae34..6656a1410dd7e99f52d6e58339cbd916dbaef23b 100644
--- a/lib/getid3/module.audio.lpac.php
+++ b/lib/getid3/module.audio.lpac.php
@@ -34,6 +34,7 @@ class getid3_lpac extends getid3_handler
 			$this->error('Expected "LPAC" at offset '.$info['avdataoffset'].', found "'.$StreamMarker.'"');
 			return false;
 		}
+		$flags = array();
 		$info['avdataoffset'] += 14;
 
 		$info['fileformat']            = 'lpac';
diff --git a/lib/getid3/module.audio.midi.php b/lib/getid3/module.audio.midi.php
index eaef9cbaf9c839549245c041b56cf3e0c82539bc..d5fe81a2bd27acd89d4211f040c4b7be80b41662 100644
--- a/lib/getid3/module.audio.midi.php
+++ b/lib/getid3/module.audio.midi.php
@@ -24,6 +24,8 @@ define('GETID3_MIDI_MAGIC_MTRK', 'MTrk'); // MIDI track header magic
 class getid3_midi extends getid3_handler
 {
 	/**
+	 * if false only parse most basic information, much faster for some files but may be inaccurate
+	 *
 	 * @var bool
 	 */
 	public $scanwholefile = true;
@@ -61,6 +63,7 @@ class getid3_midi extends getid3_handler
 		$thisfile_midi_raw['ticksperqnote'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
 		$offset += 2;
 
+		$trackdataarray = array();
 		for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) {
 			while ((strlen($MIDIdata) - $offset) < 8) {
 				if ($buffer = $this->fread($this->getid3->fread_buffer_size())) {
@@ -85,7 +88,7 @@ class getid3_midi extends getid3_handler
 			}
 		}
 
-		if (!isset($trackdataarray) || !is_array($trackdataarray)) {
+		if (!is_array($trackdataarray) || count($trackdataarray) === 0) {
 			$this->error('Cannot find MIDI track information');
 			unset($thisfile_midi);
 			unset($info['fileformat']);
diff --git a/lib/getid3/module.audio.mp3.php b/lib/getid3/module.audio.mp3.php
index 26b28068f8b50f2c35efde8d8d0832745e3c1b70..1f5a566b831cf514194ff4cc4fd4c30b702f989a 100644
--- a/lib/getid3/module.audio.mp3.php
+++ b/lib/getid3/module.audio.mp3.php
@@ -18,12 +18,6 @@ if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that acce
 	exit;
 }
 
-// number of frames to scan to determine if MPEG-audio sequence is valid
-// Lower this number to 5-20 for faster scanning
-// Increase this number to 50+ for most accurate detection of valid VBR/CBR
-// mpeg-audio streams
-define('GETID3_MP3_VALID_CHECK_FRAMES', 35);
-
 
 class getid3_mp3 extends getid3_handler
 {
@@ -35,6 +29,15 @@ class getid3_mp3 extends getid3_handler
 	 */
 	public $allow_bruteforce = false;
 
+	/**
+	 * number of frames to scan to determine if MPEG-audio sequence is valid
+	 * Lower this number to 5-20 for faster scanning
+	 * Increase this number to 50+ for most accurate detection of valid VBR/CBR mpeg-audio streams
+	 *
+	 * @var int
+	 */
+	public $mp3_valid_check_frames = 50;
+
 	/**
 	 * @return bool
 	 */
@@ -55,6 +58,7 @@ class getid3_mp3 extends getid3_handler
 			$info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
 		}
 
+		$CurrentDataLAMEversionString = null;
 		if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) {
 
 			$synchoffsetwarning = 'Unknown data before synch ';
@@ -121,6 +125,12 @@ class getid3_mp3 extends getid3_handler
 				if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) {
 					$PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3"  "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)"
 					if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) {
+						if (!empty($info['audio']['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version']) && ($info['audio']['encoder'] == $info['mpeg']['audio']['LAME']['short_version'])) {
+							if (preg_match('#^LAME[0-9\\.]+#', $PossiblyLongerLAMEversion_NewString, $matches)) {
+								// "LAME3.100" -> "LAME3.100.1", but avoid including "(alpha)" and similar
+								$info['mpeg']['audio']['LAME']['short_version'] = $matches[0];
+							}
+						}
 						$info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString;
 					}
 				}
@@ -295,7 +305,7 @@ class getid3_mp3 extends getid3_handler
 		} elseif (!empty($info['audio']['bitrate'])) {
 
 			if ($info['audio']['bitrate_mode'] == 'cbr') {
-				$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
+				$encoder_options = strtoupper($info['audio']['bitrate_mode']).round($info['audio']['bitrate'] / 1000);
 			} else {
 				$encoder_options = strtoupper($info['audio']['bitrate_mode']);
 			}
@@ -488,7 +498,7 @@ class getid3_mp3 extends getid3_handler
 		if ($MPEGaudioHeaderValidCache[$head4_key]) {
 			$thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray;
 		} else {
-			$this->error('Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset);
+			$this->warning('Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset);
 			return false;
 		}
 
@@ -722,197 +732,200 @@ class getid3_mp3 extends getid3_handler
 
 					$thisfile_mpeg_audio_lame['long_version']  = substr($headerstring, $VBRidOffset + 120, 20);
 					$thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9);
-					$thisfile_mpeg_audio_lame['numeric_version'] = str_replace('LAME', '', $thisfile_mpeg_audio_lame['short_version']);
-					if (preg_match('#^LAME([0-9\\.a-z]+)#', $thisfile_mpeg_audio_lame['long_version'], $matches)) {
+
+					//$thisfile_mpeg_audio_lame['numeric_version'] = str_replace('LAME', '', $thisfile_mpeg_audio_lame['short_version']);
+					$thisfile_mpeg_audio_lame['numeric_version'] = '';
+					if (preg_match('#^LAME([0-9\\.a-z]*)#', $thisfile_mpeg_audio_lame['long_version'], $matches)) {
 						$thisfile_mpeg_audio_lame['short_version']   = $matches[0];
 						$thisfile_mpeg_audio_lame['numeric_version'] = $matches[1];
 					}
-					foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) {
-						$thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number);
-					}
-
-					//if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') {
-					if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207
-
-						// extra 11 chars are not part of version string when LAMEtag present
-						unset($thisfile_mpeg_audio_lame['long_version']);
-
-						// It the LAME tag was only introduced in LAME v3.90
-						// http://www.hydrogenaudio.org/?act=ST&f=15&t=9933
-
-						// Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
-						// are assuming a 'Xing' identifier offset of 0x24, which is the case for
-						// MPEG-1 non-mono, but not for other combinations
-						$LAMEtagOffsetContant = $VBRidOffset - 0x24;
-
-						// shortcuts
-						$thisfile_mpeg_audio_lame['RGAD']    = array('track'=>array(), 'album'=>array());
-						$thisfile_mpeg_audio_lame_RGAD       = &$thisfile_mpeg_audio_lame['RGAD'];
-						$thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track'];
-						$thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album'];
-						$thisfile_mpeg_audio_lame['raw'] = array();
-						$thisfile_mpeg_audio_lame_raw    = &$thisfile_mpeg_audio_lame['raw'];
-
-						// byte $9B  VBR Quality
-						// This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
-						// Actually overwrites original Xing bytes
-						unset($thisfile_mpeg_audio['VBR_scale']);
-						$thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));
-
-						// bytes $9C-$A4  Encoder short VersionString
-						$thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);
-
-						// byte $A5  Info Tag revision + VBR method
-						$LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));
-
-						$thisfile_mpeg_audio_lame['tag_revision']   = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
-						$thisfile_mpeg_audio_lame_raw['vbr_method'] =  $LAMEtagRevisionVBRmethod & 0x0F;
-						$thisfile_mpeg_audio_lame['vbr_method']     = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']);
-						$thisfile_mpeg_audio['bitrate_mode']        = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr'
-
-						// byte $A6  Lowpass filter value
-						$thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;
-
-						// bytes $A7-$AE  Replay Gain
-						// http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
-						// bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
-						if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') {
-							// LAME 3.94a16 and later - 9.23 fixed point
-							// ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375
-							$thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608);
-						} else {
-							// LAME 3.94a15 and earlier - 32-bit floating point
-							// Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15
-							$thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
-						}
-						if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) {
-							unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
-						} else {
-							$thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
+					if (strlen($thisfile_mpeg_audio_lame['numeric_version']) > 0) {
+						foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) {
+							$thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number);
 						}
+						//if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') {
+						if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207
+
+							// extra 11 chars are not part of version string when LAMEtag present
+							unset($thisfile_mpeg_audio_lame['long_version']);
+
+							// It the LAME tag was only introduced in LAME v3.90
+							// http://www.hydrogenaudio.org/?act=ST&f=15&t=9933
+
+							// Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
+							// are assuming a 'Xing' identifier offset of 0x24, which is the case for
+							// MPEG-1 non-mono, but not for other combinations
+							$LAMEtagOffsetContant = $VBRidOffset - 0x24;
+
+							// shortcuts
+							$thisfile_mpeg_audio_lame['RGAD']    = array('track'=>array(), 'album'=>array());
+							$thisfile_mpeg_audio_lame_RGAD       = &$thisfile_mpeg_audio_lame['RGAD'];
+							$thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track'];
+							$thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album'];
+							$thisfile_mpeg_audio_lame['raw'] = array();
+							$thisfile_mpeg_audio_lame_raw    = &$thisfile_mpeg_audio_lame['raw'];
+
+							// byte $9B  VBR Quality
+							// This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
+							// Actually overwrites original Xing bytes
+							unset($thisfile_mpeg_audio['VBR_scale']);
+							$thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));
+
+							// bytes $9C-$A4  Encoder short VersionString
+							$thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);
+
+							// byte $A5  Info Tag revision + VBR method
+							$LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));
+
+							$thisfile_mpeg_audio_lame['tag_revision']   = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
+							$thisfile_mpeg_audio_lame_raw['vbr_method'] =  $LAMEtagRevisionVBRmethod & 0x0F;
+							$thisfile_mpeg_audio_lame['vbr_method']     = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']);
+							$thisfile_mpeg_audio['bitrate_mode']        = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr'
+
+							// byte $A6  Lowpass filter value
+							$thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;
+
+							// bytes $A7-$AE  Replay Gain
+							// http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
+							// bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
+							if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') {
+								// LAME 3.94a16 and later - 9.23 fixed point
+								// ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375
+								$thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608);
+							} else {
+								// LAME 3.94a15 and earlier - 32-bit floating point
+								// Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15
+								$thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
+							}
+							if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) {
+								unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
+							} else {
+								$thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
+							}
 
-						$thisfile_mpeg_audio_lame_raw['RGAD_track']      =   getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
-						$thisfile_mpeg_audio_lame_raw['RGAD_album']      =   getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));
+							$thisfile_mpeg_audio_lame_raw['RGAD_track']      =   getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
+							$thisfile_mpeg_audio_lame_raw['RGAD_album']      =   getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));
 
 
-						if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) {
+							if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) {
 
-							$thisfile_mpeg_audio_lame_RGAD_track['raw']['name']        = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13;
-							$thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']  = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10;
-							$thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']    = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9;
-							$thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] =  $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF;
-							$thisfile_mpeg_audio_lame_RGAD_track['name']       = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']);
-							$thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']);
-							$thisfile_mpeg_audio_lame_RGAD_track['gain_db']    = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']);
+								$thisfile_mpeg_audio_lame_RGAD_track['raw']['name']        = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13;
+								$thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']  = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10;
+								$thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']    = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9;
+								$thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] =  $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF;
+								$thisfile_mpeg_audio_lame_RGAD_track['name']       = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']);
+								$thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']);
+								$thisfile_mpeg_audio_lame_RGAD_track['gain_db']    = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']);
 
-							if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
-								$info['replay_gain']['track']['peak']   = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
+								if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
+									$info['replay_gain']['track']['peak']   = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
+								}
+								$info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator'];
+								$info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db'];
+							} else {
+								unset($thisfile_mpeg_audio_lame_RGAD['track']);
 							}
-							$info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator'];
-							$info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db'];
-						} else {
-							unset($thisfile_mpeg_audio_lame_RGAD['track']);
-						}
-						if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) {
-
-							$thisfile_mpeg_audio_lame_RGAD_album['raw']['name']        = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13;
-							$thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']  = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10;
-							$thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']    = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9;
-							$thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF;
-							$thisfile_mpeg_audio_lame_RGAD_album['name']       = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']);
-							$thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']);
-							$thisfile_mpeg_audio_lame_RGAD_album['gain_db']    = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']);
-
-							if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
-								$info['replay_gain']['album']['peak']   = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
+							if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) {
+
+								$thisfile_mpeg_audio_lame_RGAD_album['raw']['name']        = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13;
+								$thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']  = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10;
+								$thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']    = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9;
+								$thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF;
+								$thisfile_mpeg_audio_lame_RGAD_album['name']       = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']);
+								$thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']);
+								$thisfile_mpeg_audio_lame_RGAD_album['gain_db']    = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']);
+
+								if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
+									$info['replay_gain']['album']['peak']   = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
+								}
+								$info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator'];
+								$info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db'];
+							} else {
+								unset($thisfile_mpeg_audio_lame_RGAD['album']);
+							}
+							if (empty($thisfile_mpeg_audio_lame_RGAD)) {
+								unset($thisfile_mpeg_audio_lame['RGAD']);
 							}
-							$info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator'];
-							$info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db'];
-						} else {
-							unset($thisfile_mpeg_audio_lame_RGAD['album']);
-						}
-						if (empty($thisfile_mpeg_audio_lame_RGAD)) {
-							unset($thisfile_mpeg_audio_lame['RGAD']);
-						}
 
 
-						// byte $AF  Encoding flags + ATH Type
-						$EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
-						$thisfile_mpeg_audio_lame['encoding_flags']['nspsytune']   = (bool) ($EncodingFlagsATHtype & 0x10);
-						$thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
-						$thisfile_mpeg_audio_lame['encoding_flags']['nogap_next']  = (bool) ($EncodingFlagsATHtype & 0x40);
-						$thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']  = (bool) ($EncodingFlagsATHtype & 0x80);
-						$thisfile_mpeg_audio_lame['ath_type']                      =         $EncodingFlagsATHtype & 0x0F;
-
-						// byte $B0  if ABR {specified bitrate} else {minimal bitrate}
-						$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
-						if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR)
-							$thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
-						} elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR)
-							// ignore
-						} elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate
-							$thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
-						}
+							// byte $AF  Encoding flags + ATH Type
+							$EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
+							$thisfile_mpeg_audio_lame['encoding_flags']['nspsytune']   = (bool) ($EncodingFlagsATHtype & 0x10);
+							$thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
+							$thisfile_mpeg_audio_lame['encoding_flags']['nogap_next']  = (bool) ($EncodingFlagsATHtype & 0x40);
+							$thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']  = (bool) ($EncodingFlagsATHtype & 0x80);
+							$thisfile_mpeg_audio_lame['ath_type']                      =         $EncodingFlagsATHtype & 0x0F;
+
+							// byte $B0  if ABR {specified bitrate} else {minimal bitrate}
+							$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
+							if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR)
+								$thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
+							} elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR)
+								// ignore
+							} elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate
+								$thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
+							}
 
-						// bytes $B1-$B3  Encoder delays
-						$EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
-						$thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
-						$thisfile_mpeg_audio_lame['end_padding']   =  $EncoderDelays & 0x000FFF;
-
-						// byte $B4  Misc
-						$MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
-						$thisfile_mpeg_audio_lame_raw['noise_shaping']       = ($MiscByte & 0x03);
-						$thisfile_mpeg_audio_lame_raw['stereo_mode']         = ($MiscByte & 0x1C) >> 2;
-						$thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
-						$thisfile_mpeg_audio_lame_raw['source_sample_freq']  = ($MiscByte & 0xC0) >> 6;
-						$thisfile_mpeg_audio_lame['noise_shaping']       = $thisfile_mpeg_audio_lame_raw['noise_shaping'];
-						$thisfile_mpeg_audio_lame['stereo_mode']         = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']);
-						$thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality'];
-						$thisfile_mpeg_audio_lame['source_sample_freq']  = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']);
-
-						// byte $B5  MP3 Gain
-						$thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
-						$thisfile_mpeg_audio_lame['mp3_gain_db']     = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain'];
-						$thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6));
-
-						// bytes $B6-$B7  Preset and surround info
-						$PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
-						// Reserved                                                    = ($PresetSurroundBytes & 0xC000);
-						$thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800);
-						$thisfile_mpeg_audio_lame['surround_info']     = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']);
-						$thisfile_mpeg_audio_lame['preset_used_id']    = ($PresetSurroundBytes & 0x07FF);
-						$thisfile_mpeg_audio_lame['preset_used']       = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame);
-						if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) {
-							$this->warning('Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org');
-						}
-						if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) {
-							// this may change if 3.90.4 ever comes out
-							$thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3';
-						}
+							// bytes $B1-$B3  Encoder delays
+							$EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
+							$thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
+							$thisfile_mpeg_audio_lame['end_padding']   =  $EncoderDelays & 0x000FFF;
+
+							// byte $B4  Misc
+							$MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
+							$thisfile_mpeg_audio_lame_raw['noise_shaping']       = ($MiscByte & 0x03);
+							$thisfile_mpeg_audio_lame_raw['stereo_mode']         = ($MiscByte & 0x1C) >> 2;
+							$thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
+							$thisfile_mpeg_audio_lame_raw['source_sample_freq']  = ($MiscByte & 0xC0) >> 6;
+							$thisfile_mpeg_audio_lame['noise_shaping']       = $thisfile_mpeg_audio_lame_raw['noise_shaping'];
+							$thisfile_mpeg_audio_lame['stereo_mode']         = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']);
+							$thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality'];
+							$thisfile_mpeg_audio_lame['source_sample_freq']  = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']);
+
+							// byte $B5  MP3 Gain
+							$thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
+							$thisfile_mpeg_audio_lame['mp3_gain_db']     = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain'];
+							$thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6));
+
+							// bytes $B6-$B7  Preset and surround info
+							$PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
+							// Reserved                                                    = ($PresetSurroundBytes & 0xC000);
+							$thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800);
+							$thisfile_mpeg_audio_lame['surround_info']     = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']);
+							$thisfile_mpeg_audio_lame['preset_used_id']    = ($PresetSurroundBytes & 0x07FF);
+							$thisfile_mpeg_audio_lame['preset_used']       = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame);
+							if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) {
+								$this->warning('Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org');
+							}
+							if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) {
+								// this may change if 3.90.4 ever comes out
+								$thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3';
+							}
 
-						// bytes $B8-$BB  MusicLength
-						$thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
-						$ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']);
+							// bytes $B8-$BB  MusicLength
+							$thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
+							$ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']);
 
-						// bytes $BC-$BD  MusicCRC
-						$thisfile_mpeg_audio_lame['music_crc']    = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));
+							// bytes $BC-$BD  MusicCRC
+							$thisfile_mpeg_audio_lame['music_crc']    = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));
 
-						// bytes $BE-$BF  CRC-16 of Info Tag
-						$thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));
+							// bytes $BE-$BF  CRC-16 of Info Tag
+							$thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));
 
 
-						// LAME CBR
-						if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) {
+							// LAME CBR
+							if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) {
 
-							$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
-							$thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']);
-							$info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
-							//if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) {
-							//	$thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min'];
-							//}
+								$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
+								$thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']);
+								$info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
+								//if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) {
+								//	$thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min'];
+								//}
 
-						}
+							}
 
+						}
 					}
 				}
 
@@ -1009,6 +1022,22 @@ class getid3_mp3 extends getid3_handler
 			if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) {
 				return false;
 			}
+			if (!empty($this->getid3->info['mp3_validity_check_bitrates']) && !empty($thisfile_mpeg_audio['bitrate_mode']) && ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') && !empty($thisfile_mpeg_audio['VBR_bitrate'])) {
+				// https://github.com/JamesHeinrich/getID3/issues/287
+				if (count(array_keys($this->getid3->info['mp3_validity_check_bitrates'])) == 1) {
+					list($cbr_bitrate_in_short_scan) = array_keys($this->getid3->info['mp3_validity_check_bitrates']);
+					$deviation_cbr_from_header_bitrate = abs($thisfile_mpeg_audio['VBR_bitrate'] - $cbr_bitrate_in_short_scan) / $cbr_bitrate_in_short_scan;
+					if ($deviation_cbr_from_header_bitrate < 0.01) {
+						// VBR header bitrate may differ slightly from true bitrate of frames, perhaps accounting for overhead of VBR header frame itself?
+						// If measured CBR bitrate is within 1% of specified bitrate in VBR header then assume that file is truly CBR
+						$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
+						//$this->warning('VBR header ignored, assuming CBR '.round($cbr_bitrate_in_short_scan / 1000).'kbps based on scan of '.$this->mp3_valid_check_frames.' frames');
+					}
+				}
+			}
+			if (isset($this->getid3->info['mp3_validity_check_bitrates'])) {
+				unset($this->getid3->info['mp3_validity_check_bitrates']);
+			}
 
 		}
 
@@ -1130,8 +1159,9 @@ class getid3_mp3 extends getid3_handler
 		$firstframetestarray = array('error' => array(), 'warning'=> array(), 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']);
 		$this->decodeMPEGaudioHeader($offset, $firstframetestarray, false);
 
-		for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) {
-			// check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch
+		$info['mp3_validity_check_bitrates'] = array();
+		for ($i = 0; $i < $this->mp3_valid_check_frames; $i++) {
+			// check next (default: 50) frames for validity, to make sure we haven't run across a false synch
 			if (($nextframetestoffset + 4) >= $info['avdataend']) {
 				// end of file
 				return true;
@@ -1139,6 +1169,7 @@ class getid3_mp3 extends getid3_handler
 
 			$nextframetestarray = array('error' => array(), 'warning' => array(), 'avdataend' => $info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
 			if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) {
+				getid3_lib::safe_inc($info['mp3_validity_check_bitrates'][$nextframetestarray['mpeg']['audio']['bitrate']]);
 				if ($ScanAsCBR) {
 					// force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header
 					if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) {
@@ -1273,6 +1304,7 @@ class getid3_mp3 extends getid3_handler
 		$LongMPEGbitrateLookup        = array();
 		$LongMPEGpaddingLookup        = array();
 		$LongMPEGfrequencyLookup      = array();
+		$Distribution                 = array();
 		$Distribution['bitrate']      = array();
 		$Distribution['frequency']    = array();
 		$Distribution['layer']        = array();
@@ -1433,6 +1465,9 @@ class getid3_mp3 extends getid3_handler
 		$header = $this->fread($sync_seek_buffer_size);
 		$sync_seek_buffer_size = strlen($header);
 		$SynchSeekOffset = 0;
+		$SyncSeekAttempts = 0;
+		$SyncSeekAttemptsMax = 1000;
+		$FirstFrameThisfileInfo = null;
 		while ($SynchSeekOffset < $sync_seek_buffer_size) {
 			if ((($avdataoffset + $SynchSeekOffset)  < $info['avdataend']) && !feof($this->getid3->fp)) {
 
@@ -1471,7 +1506,24 @@ class getid3_mp3 extends getid3_handler
 				return false;
 			}
 
-			if (($header[$SynchSeekOffset] == "\xFF") && ($header[($SynchSeekOffset + 1)] > "\xE0")) { // synch detected
+			if (($header[$SynchSeekOffset] == "\xFF") && ($header[($SynchSeekOffset + 1)] > "\xE0")) { // possible synch detected
+				if (++$SyncSeekAttempts >= $SyncSeekAttemptsMax) {
+					// https://github.com/JamesHeinrich/getID3/issues/286
+					// corrupt files claiming to be MP3, with a large number of 0xFF bytes near the beginning, can cause this loop to take a very long time
+					// should have escape condition to avoid spending too much time scanning a corrupt file
+					// if a synch's not found within the first 128k bytes, then give up
+					$this->error('Could not find valid MPEG audio synch after scanning '.$SyncSeekAttempts.' candidate offsets');
+					if (isset($info['audio']['bitrate'])) {
+						unset($info['audio']['bitrate']);
+					}
+					if (isset($info['mpeg']['audio'])) {
+						unset($info['mpeg']['audio']);
+					}
+					if (empty($info['mpeg'])) {
+						unset($info['mpeg']);
+					}
+					return false;
+				}
 				$FirstFrameAVDataOffset = null;
 				if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) {
 					$FirstFrameThisfileInfo = $info;
@@ -1511,9 +1563,9 @@ class getid3_mp3 extends getid3_handler
 							if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) {
 								$info = $dummy;
 								$info['avdataoffset'] = $GarbageOffsetEnd;
-								$this->warning('apparently-valid VBR header not used because could not find '.GETID3_MP3_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);
+								$this->warning('apparently-valid VBR header not used because could not find '.$this->mp3_valid_check_frames.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd);
 							} else {
-								$this->warning('using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')');
+								$this->warning('using data from VBR header even though could not find '.$this->mp3_valid_check_frames.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')');
 							}
 						}
 					}
@@ -1558,6 +1610,7 @@ class getid3_mp3 extends getid3_handler
 						$pct_data_scanned = 0;
 						for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) {
 							$frames_scanned_this_segment = 0;
+							$scan_start_offset = array();
 							if ($this->ftell() >= $info['avdataend']) {
 								break;
 							}
@@ -1887,6 +1940,7 @@ class getid3_mp3 extends getid3_handler
 			return false;
 		}
 
+		$MPEGrawHeader = array();
 		$MPEGrawHeader['synch']         = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4;
 		$MPEGrawHeader['version']       = (ord($Header4Bytes[1]) & 0x18) >> 3; //    BB
 		$MPEGrawHeader['layer']         = (ord($Header4Bytes[1]) & 0x06) >> 1; //      CC
diff --git a/lib/getid3/module.audio.mpc.php b/lib/getid3/module.audio.mpc.php
index 4f6f5c89b2272cdd96bea4c2ea988e318cb1fa6f..d7f2d99f9afe57735a581cc994e4fd4111bb5f16 100644
--- a/lib/getid3/module.audio.mpc.php
+++ b/lib/getid3/module.audio.mpc.php
@@ -348,6 +348,7 @@ class getid3_mpc extends getid3_handler
 		$info['avdataoffset'] += $thisfile_mpc_header['size'];
 
 		// Most of this code adapted from Jurgen Faul's MPEGplus source code - thanks Jurgen! :)
+		$HeaderDWORD = array();
 		$HeaderDWORD[0] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 0, 4));
 		$HeaderDWORD[1] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 4, 4));
 
diff --git a/lib/getid3/module.audio.ogg.php b/lib/getid3/module.audio.ogg.php
index fe092d9d94d190bafb504bfa610296abe0699adb..5786fd0d5970fc06d3f33c642ea264b29b9c4f38 100644
--- a/lib/getid3/module.audio.ogg.php
+++ b/lib/getid3/module.audio.ogg.php
@@ -529,6 +529,7 @@ class getid3_ogg extends getid3_handler
 	 */
 	public function ParseOggPageHeader() {
 		// http://xiph.org/ogg/vorbis/doc/framing.html
+		$oggheader = array();
 		$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
 
 		$filedata = $this->fread($this->getid3->fread_buffer_size());
@@ -680,35 +681,39 @@ class getid3_ogg extends getid3_handler
 
 				$VorbisCommentPage++;
 
-				$oggpageinfo = $this->ParseOggPageHeader();
-				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
+				if ($oggpageinfo = $this->ParseOggPageHeader()) {
+					$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 
-				// First, save what we haven't read yet
-				$AsYetUnusedData = substr($commentdata, $commentdataoffset);
+					// First, save what we haven't read yet
+					$AsYetUnusedData = substr($commentdata, $commentdataoffset);
 
-				// Then take that data off the end
-				$commentdata     = substr($commentdata, 0, $commentdataoffset);
+					// Then take that data off the end
+					$commentdata     = substr($commentdata, 0, $commentdataoffset);
+
+					// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
+					$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
+					$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 
-				// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
-				$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
-				$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
+					// Finally, stick the unused data back on the end
+					$commentdata .= $AsYetUnusedData;
 
-				// Finally, stick the unused data back on the end
-				$commentdata .= $AsYetUnusedData;
+					//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
+					if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
+						$this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
+						break;
+					}
+					$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
+					if ($readlength <= 0) {
+						$this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
+						break;
+					}
+					$commentdata .= $this->fread($readlength);
 
-				//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
-				if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
-					$this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
-					break;
-				}
-				$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
-				if ($readlength <= 0) {
-					$this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
+					//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
+				} else {
+					$this->warning('failed to ParseOggPageHeader() at offset '.$this->ftell());
 					break;
 				}
-				$commentdata .= $this->fread($readlength);
-
-				//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
 			}
 			$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
 			$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
diff --git a/lib/getid3/module.audio.optimfrog.php b/lib/getid3/module.audio.optimfrog.php
index 7e888ccdd4c138f880e94d1be714fb2bb359c7b1..d84c2c48e0124637bc7c96bf8b077f01b3a8540b 100644
--- a/lib/getid3/module.audio.optimfrog.php
+++ b/lib/getid3/module.audio.optimfrog.php
@@ -188,6 +188,7 @@ class getid3_optimfrog extends getid3_handler
 				case 'COMP':
 					// unlike other block types, there CAN be multiple COMP blocks
 
+					$COMPdata           = array();
 					$COMPdata['offset'] = $BlockOffset;
 					$COMPdata['size']   = $BlockSize;
 
diff --git a/lib/getid3/module.audio.shorten.php b/lib/getid3/module.audio.shorten.php
index 533208faf23a2e70ab98b6c86d3f8c730a2b710d..673a485856af4ebd2fce56034f2a6e9dc2a3b38f 100644
--- a/lib/getid3/module.audio.shorten.php
+++ b/lib/getid3/module.audio.shorten.php
@@ -17,6 +17,7 @@
 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
 	exit;
 }
+
 class getid3_shorten extends getid3_handler
 {
 	/**
diff --git a/lib/getid3/module.audio.tak.php b/lib/getid3/module.audio.tak.php
index 5d4ada56762876920118e6c961289683977bf04b..0391d05bc12b8701967ae5cc9acee2bae22fb4e0 100644
--- a/lib/getid3/module.audio.tak.php
+++ b/lib/getid3/module.audio.tak.php
@@ -20,7 +20,9 @@ if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that acce
 
 class getid3_tak extends getid3_handler
 {
-
+	/**
+	 * @return bool
+	 */
 	public function Analyze() {
 		$info = &$this->getid3->info;
 
diff --git a/lib/getid3/module.audio.wavpack.php b/lib/getid3/module.audio.wavpack.php
index acb0d3eed0f7d7f8c111932cf546ff63d16871d8..dfeb85d435bae9cfeaee93b992ff198b6ab9e76e 100644
--- a/lib/getid3/module.audio.wavpack.php
+++ b/lib/getid3/module.audio.wavpack.php
@@ -19,6 +19,14 @@ if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that acce
 }
 class getid3_wavpack extends getid3_handler
 {
+	/**
+	 * Avoid scanning all frames (break after finding ID_RIFF_HEADER and ID_CONFIG_BLOCK,
+	 * significantly faster for very large files but other data may be missed
+	 *
+	 * @var bool
+	 */
+	public $quick_parsing = false;
+
 	/**
 	 * @return bool
 	 */
@@ -27,6 +35,7 @@ class getid3_wavpack extends getid3_handler
 
 		$this->fseek($info['avdataoffset']);
 
+		$found_blocks = array();
 		while (true) {
 
 			$wavpackheader = $this->fread(32);
@@ -158,6 +167,7 @@ class getid3_wavpack extends getid3_handler
 				$metablock['id'] = ord($metablockheader[0]);
 				$metablock['function_id'] = ($metablock['id'] & 0x3F);
 				$metablock['function_name'] = $this->WavPackMetablockNameLookup($metablock['function_id']);
+				$found_blocks[$metablock['function_name']] = (isset($found_blocks[$metablock['function_name']]) ? $found_blocks[$metablock['function_name']] : 0) + 1; // cannot use getid3_lib::safe_inc without warnings(?)
 
 				// The 0x20 bit in the id of the meta subblocks (which is defined as
 				// ID_OPTIONAL_DATA) is a permanent part of the id. The idea is that
@@ -237,6 +247,10 @@ class getid3_wavpack extends getid3_handler
 
 							// Safe RIFF header in case there's a RIFF footer later
 							$metablockRIFFheader = $metablock['data'];
+							if ($this->quick_parsing && !empty($found_blocks['RIFF header']) && !empty($found_blocks['Config Block'])) {
+								$this->warning('WavPack quick-parsing enabled (faster at parsing large fules, but may miss some data)');
+								break 2;
+							}
 							break;
 
 
@@ -317,6 +331,10 @@ class getid3_wavpack extends getid3_handler
 							} elseif (isset($info['audio']['encoder_options'])) {
 								unset($info['audio']['encoder_options']);
 							}
+							if ($this->quick_parsing && !empty($found_blocks['RIFF header']) && !empty($found_blocks['Config Block'])) {
+								$this->warning('WavPack quick-parsing enabled (faster at parsing large fules, but may miss some data)');
+								break 2;
+							}
 							break;
 
 
@@ -369,7 +387,6 @@ class getid3_wavpack extends getid3_handler
 			$info['audio']['dataformat']  = 'wvc';
 
 		}
-
 		return true;
 	}
 
diff --git a/lib/getid3/module.graphic.bmp.php b/lib/getid3/module.graphic.bmp.php
index 276688d4d8cd012376c11f2a0b880e62e5a8918a..8456570112f00b7925eef3946e2d1b21e0c882cf 100644
--- a/lib/getid3/module.graphic.bmp.php
+++ b/lib/getid3/module.graphic.bmp.php
@@ -20,7 +20,18 @@ if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that acce
 
 class getid3_bmp extends getid3_handler
 {
+	/**
+	 * return BMP palette
+	 *
+	 * @var bool
+	 */
 	public $ExtractPalette = false;
+
+	/**
+	 * return image data
+	 *
+	 * @var bool
+	 */
 	public $ExtractData    = false;
 
 	/**
@@ -498,6 +509,7 @@ class getid3_bmp extends getid3_handler
 					switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
 						case 4:
 							$pixelcounter = 0;
+							$paletteindexes = array();
 							while ($pixeldataoffset < strlen($BMPpixelData)) {
 								$firstbyte  = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
 								$secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
@@ -533,7 +545,6 @@ class getid3_bmp extends getid3_handler
 											// of color indexes that follow. Subsequent bytes contain color indexes in their
 											// high- and low-order 4 bits, one color index for each pixel. In absolute mode,
 											// each run must be aligned on a word boundary.
-											unset($paletteindexes);
 											$paletteindexes = array();
 											for ($i = 0; $i < ceil($secondbyte / 2); $i++) {
 												$paletteindexbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
diff --git a/lib/getid3/module.graphic.jpg.php b/lib/getid3/module.graphic.jpg.php
index 5079119d29ba54873299b8c8335800dfc5a2d448..c6ffad7e7087a88a6f2d08f9bf55dfb0377cf7e0 100644
--- a/lib/getid3/module.graphic.jpg.php
+++ b/lib/getid3/module.graphic.jpg.php
@@ -62,7 +62,7 @@ class getid3_jpg extends getid3_handler
 
 		$returnOK = false;
 		switch ($type) {
-			case IMG_JPG:
+			case IMAGETYPE_JPEG:
 				$info['video']['resolution_x'] = $width;
 				$info['video']['resolution_y'] = $height;
 
@@ -71,17 +71,14 @@ class getid3_jpg extends getid3_handler
 						if (substr($imageinfo['APP1'], 0, 4) == 'Exif') {
 							//$this->warning('known issue: https://bugs.php.net/bug.php?id=62523');
 							//return false;
-							set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) {
+							set_error_handler(function($errno, $errstr, $errfile, $errline) { // https://github.com/JamesHeinrich/getID3/issues/275
 								if (!(error_reporting() & $errno)) {
 									// error is not specified in the error_reporting setting, so we ignore it
 									return false;
 								}
-
-								$errcontext['info']['warning'][] = 'Error parsing EXIF data ('.$errstr.')';
+								$this->warning('Error parsing EXIF data ('.$errstr.')');
 							});
-
 							$info['jpg']['exif'] = exif_read_data($info['filenamepath'], null, true, false);
-
 							restore_error_handler();
 						} else {
 							$this->warning('exif_read_data() cannot parse non-EXIF data in APP1 (expected "Exif", found "'.substr($imageinfo['APP1'], 0, 4).'")');
diff --git a/lib/getid3/module.graphic.pcd.php b/lib/getid3/module.graphic.pcd.php
index 85fb4c96ca3ac78807d4f59854e6e5f3b1cd322f..6f2aaac60731c5c98dfddac58fb58625e9d54ae1 100644
--- a/lib/getid3/module.graphic.pcd.php
+++ b/lib/getid3/module.graphic.pcd.php
@@ -54,6 +54,7 @@ class getid3_pcd extends getid3_handler
 
 		} elseif ($this->ExtractData > 0) {
 
+			$PCD_levels    = array();
 			$PCD_levels[1] = array( 192,  128, 0x02000); // BASE/16
 			$PCD_levels[2] = array( 384,  256, 0x0B800); // BASE/4
 			$PCD_levels[3] = array( 768,  512, 0x30000); // BASE
diff --git a/lib/getid3/module.graphic.png.php b/lib/getid3/module.graphic.png.php
index 018062c3811922b733d2edb99c8bc6c680ec4e8a..e29eeb79e647d2ae5df36775d28331d60ae0b29c 100644
--- a/lib/getid3/module.graphic.png.php
+++ b/lib/getid3/module.graphic.png.php
@@ -57,6 +57,7 @@ class getid3_png extends getid3_handler
 		}
 
 		while ((($this->ftell() - (strlen($PNGfiledata) - $offset)) < $info['filesize'])) {
+			$chunk = array();
 			$chunk['data_length'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4));
 			if ($chunk['data_length'] === false) {
 				$this->error('Failed to read data_length at offset '.$offset);
@@ -66,7 +67,13 @@ class getid3_png extends getid3_handler
 			$truncated_data = false;
 			while (((strlen($PNGfiledata) - $offset) < ($chunk['data_length'] + 4)) && ($this->ftell() < $info['filesize'])) {
 				if (strlen($PNGfiledata) < $this->max_data_bytes) {
-					$PNGfiledata .= $this->fread($this->getid3->fread_buffer_size());
+					$str = $this->fread($this->getid3->fread_buffer_size());
+					if (strlen($str) > 0) {
+						$PNGfiledata .= $str;
+					} else {
+						$this->warning('At offset '.$offset.' chunk "'.substr($PNGfiledata, $offset, 4).'" no more data to read, data chunk will be truncated at '.(strlen($PNGfiledata) - 8).' bytes');
+						break;
+					}
 				} else {
 					$this->warning('At offset '.$offset.' chunk "'.substr($PNGfiledata, $offset, 4).'" exceeded max_data_bytes value of '.$this->max_data_bytes.', data chunk will be truncated at '.(strlen($PNGfiledata) - 8).' bytes');
 					break;
diff --git a/lib/getid3/module.graphic.tiff.php b/lib/getid3/module.graphic.tiff.php
index 5d4049474bb90ec23868e43e02f4f810d41669fa..e4f3cf7bdbc6aeb23e9840ab06dffc45d06223a3 100644
--- a/lib/getid3/module.graphic.tiff.php
+++ b/lib/getid3/module.graphic.tiff.php
@@ -125,6 +125,10 @@ class getid3_tiff extends getid3_handler
 		}
 
 		foreach ($info['tiff']['ifd'] as $IFDid => $IFDarray) {
+			if(!isset($IFDarray['fields'])) {
+				continue;
+			}
+
 			foreach ($IFDarray['fields'] as $key => $fieldarray) {
 				switch ($fieldarray['raw']['tag']) {
 					case 256: // ImageWidth
diff --git a/lib/getid3/module.misc.cue.php b/lib/getid3/module.misc.cue.php
index a66e2eb2548ff9aaebc92faf4d6f049117aeac9c..5082f2fb59f5b9b1d1e37fc39e4df7c4b95e196c 100644
--- a/lib/getid3/module.misc.cue.php
+++ b/lib/getid3/module.misc.cue.php
@@ -94,10 +94,10 @@ class getid3_cue extends getid3_handler
 	{
 		//-1 means still global, all others are track specific
 		$track_on = -1;
+		$currentFile = null;
 
-		for ($i=0; $i < count($file); $i++)
-		{
-			list($key) = explode(' ', strtolower($file[$i]), 2);
+		foreach ($file as $line) {
+			list($key) = explode(' ', strtolower($line), 2);
 			switch ($key)
 			{
 				case 'catalog':
@@ -106,25 +106,25 @@ class getid3_cue extends getid3_handler
 				case 'performer':
 				case 'songwriter':
 				case 'title':
-					$this->parseString($file[$i], $track_on);
+					$this->parseString($line, $track_on);
 					break;
 				case 'file':
-					$currentFile = $this->parseFile($file[$i]);
+					$currentFile = $this->parseFile($line);
 					break;
 				case 'flags':
-					$this->parseFlags($file[$i], $track_on);
+					$this->parseFlags($line, $track_on);
 					break;
 				case 'index':
 				case 'postgap':
 				case 'pregap':
-					$this->parseIndex($file[$i], $track_on);
+					$this->parseIndex($line, $track_on);
 					break;
 				case 'rem':
-					$this->parseComment($file[$i], $track_on);
+					$this->parseComment($line, $track_on);
 					break;
 				case 'track':
 					$track_on++;
-					$this->parseTrack($file[$i], $track_on);
+					$this->parseTrack($line, $track_on);
 					if (isset($currentFile)) // if there's a file
 					{
 						$this->cuesheet['tracks'][$track_on]['datafile'] = $currentFile;
@@ -132,7 +132,7 @@ class getid3_cue extends getid3_handler
 					break;
 				default:
 					//save discarded junk and place string[] with track it was found in
-					$this->parseGarbage($file[$i], $track_on);
+					$this->parseGarbage($line, $track_on);
 					break;
 			}
 		}
diff --git a/lib/getid3/module.misc.iso.php b/lib/getid3/module.misc.iso.php
index efca73aa13b115810e2d431ac8d35cf2344982a0..939bd631e1e55d825260eee4bb74da03eb404608 100644
--- a/lib/getid3/module.misc.iso.php
+++ b/lib/getid3/module.misc.iso.php
@@ -53,8 +53,10 @@ class getid3_iso extends getid3_handler
 		$this->ParsePathTable();
 
 		$info['iso']['files'] = array();
-		foreach ($info['iso']['path_table']['directories'] as $directorynum => $directorydata) {
-			$info['iso']['directories'][$directorynum] = $this->ParseDirectoryRecord($directorydata);
+		if (!empty($info['iso']['path_table']['directories'])) {
+			foreach ($info['iso']['path_table']['directories'] as $directorynum => $directorydata) {
+				$info['iso']['directories'][$directorynum] = $this->ParseDirectoryRecord($directorydata);
+			}
 		}
 
 		return true;
@@ -250,6 +252,7 @@ class getid3_iso extends getid3_handler
 
 		$offset = 0;
 		$pathcounter = 1;
+		$FullPathArray = array();
 		while ($offset < $PathTableSize) {
 			// shortcut
 			$info['iso']['path_table']['directories'][$pathcounter] = array();
diff --git a/lib/getid3/module.misc.pdf.php b/lib/getid3/module.misc.pdf.php
index 1aca6ae35225628febbc025e6de79e5aaa1c7719..975fe3dfb4d8c40940e008cfbff143207a485dbc 100644
--- a/lib/getid3/module.misc.pdf.php
+++ b/lib/getid3/module.misc.pdf.php
@@ -20,14 +20,18 @@ if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that acce
 
 class getid3_pdf extends getid3_handler
 {
-	public $returnXREF = false; // return full details of PDF Cross-Reference Table (XREF)
+	/** misc.pdf
+	 * return full details of PDF Cross-Reference Table (XREF)
+	 *
+	 * @var bool
+	 */
+	public $returnXREF = false;
 
 	/**
 	 * @return bool
 	 */
 	public function Analyze() {
 		$info = &$this->getid3->info;
-
 		$this->fseek(0);
 		if (preg_match('#^%PDF-([0-9\\.]+)$#', rtrim($this->fgets()), $matches)) {
 			$info['pdf']['header']['version'] = floatval($matches[1]);
@@ -62,39 +66,45 @@ class getid3_pdf extends getid3_handler
 							}
 						}
 					}
+
+					asort($info['pdf']['xref']['offset']);
+					$maxObjLengths = array();
+					$prevOffset = 0;
+					$prevObjNum = 0;
+					foreach ($info['pdf']['xref']['offset'] as $objectNumber => $offset) {
+						// walk through all listed offsets to calculate the maximum possible length for each known object
+						if ($prevObjNum) {
+							$maxObjLengths[$prevObjNum] = $offset - $prevOffset;
+						}
+						$prevOffset = $offset;
+						$prevObjNum = $objectNumber;
+					}
+					ksort($maxObjLengths);
 					foreach ($info['pdf']['xref']['offset'] as $objectNumber => $offset) {
 						if ($info['pdf']['xref']['entry'][$objectNumber] == 'f') {
 							// "free" object means "deleted", ignore
 							continue;
 						}
-						$this->fseek($offset);
-						$line = rtrim($this->fgets());
-						if (preg_match('#^'.$objectNumber.' ([0-9]+) obj#', $line, $matches)) {
-							if (strlen($line) > strlen($matches[0])) {
-								// object header line not actually on its own line, rewind file pointer to start reading data
-								$this->fseek($offset + strlen($matches[0]));
-							}
-							$objectData  = '';
-							while (true) {
-								$line = $this->fgets();
-								if (rtrim($line) == 'endobj') {
-									break;
-								}
-								$objectData .= $line;
-							}
-							if (preg_match('#^<<[\r\n\s]*(/Type|/Pages|/Parent [0-9]+ [0-9]+ [A-Z]|/Count [0-9]+|/Kids *\\[[0-9A-Z ]+\\]|[\r\n\s])+[\r\n\s]*>>#', $objectData, $matches)) {
-								if (preg_match('#/Count ([0-9]+)#', $objectData, $matches)) {
-									$info['pdf']['pages'] = (int) $matches[1];
-									break; // for now this is the only data we're looking for in the PDF not need to loop through every object in the file (and a large PDF may contain MANY objects). And it MAY be possible that there are other objects elsewhere in the file that define additional (or removed?) pages
+						if (($maxObjLengths[$objectNumber] > 0) && ($maxObjLengths[$objectNumber] < $this->getid3->option_fread_buffer_size)) {
+							// ignore object that are zero-size or >32kB, they are unlikely to contain information we're interested in
+							$this->fseek($offset);
+							$objBlob = $this->fread($maxObjLengths[$objectNumber]);
+							if (preg_match('#^'.$objectNumber.'[\\x00 \\r\\n\\t]*([0-9]+)[\\x00 \\r\\n\\t]*obj[\\x00 \\r\\n\\t]*(.*)(endobj)?[\\x00 \\r\\n\\t]*$#s', $objBlob, $matches)) {
+								list($dummy, $generation, $objectData) = $matches;
+								if (preg_match('#^<<[\r\n\s]*(/Type|/Pages|/Parent [0-9]+ [0-9]+ [A-Z]|/Count [0-9]+|/Kids *\\[[0-9A-Z ]+\\]|[\r\n\s])+[\r\n\s]*>>#', $objectData, $matches)) {
+									if (preg_match('#/Count ([0-9]+)#', $objectData, $matches)) {
+										$info['pdf']['pages'] = (int) $matches[1];
+										break; // for now this is the only data we're looking for in the PDF not need to loop through every object in the file (and a large PDF may contain MANY objects). And it MAY be possible that there are other objects elsewhere in the file that define additional (or removed?) pages
+									}
 								}
+							} else {
+								$this->error('Unexpected structure "'.substr($objBlob, 0, 100).'" at offset '.$offset);
+								break;
 							}
-						} else {
-							$this->error('Unexpected structure "'.$line.'" at offset '.$offset);
-							break;
 						}
 					}
 					if (!$this->returnXREF) {
-						unset($info['pdf']['xref']['offset'], $info['pdf']['xref']['generation'], $info['pdf']['xref']['entry']);
+						unset($info['pdf']['xref']['offset'], $info['pdf']['xref']['generation'], $info['pdf']['xref']['entry'], $info['pdf']['xref']['xref_offsets']);
 					}
 
 				} else {
@@ -123,6 +133,7 @@ class getid3_pdf extends getid3_handler
 
 			$info['pdf']['xref']['xref_offsets'][$XREFoffset] = $XREFoffset;
 			list($firstObjectNumber, $XREFcount) = explode(' ', rtrim($this->fgets()));
+			$firstObjectNumber = (int) $firstObjectNumber;
 			$XREFcount = (int) $XREFcount;
 			$info['pdf']['xref']['count'] = $XREFcount + (!empty($info['pdf']['xref']['count']) ? $info['pdf']['xref']['count'] : 0);
 			for ($i = 0; $i < $XREFcount; $i++) {
diff --git a/lib/getid3/module.misc.torrent.php b/lib/getid3/module.misc.torrent.php
new file mode 100644
index 0000000000000000000000000000000000000000..1e25f95f0fa09673f2d1751c6751c4f73b14222c
--- /dev/null
+++ b/lib/getid3/module.misc.torrent.php
@@ -0,0 +1,247 @@
+<?php
+
+/////////////////////////////////////////////////////////////////
+/// getID3() by James Heinrich <info@getid3.org>               //
+//  available at https://github.com/JamesHeinrich/getID3       //
+//            or https://www.getid3.org                        //
+//            or http://getid3.sourceforge.net                 //
+//  see readme.txt for more details                            //
+/////////////////////////////////////////////////////////////////
+//                                                             //
+// module.misc.torrent.php                                     //
+// module for analyzing .torrent files                         //
+// dependencies: NONE                                          //
+//                                                            ///
+/////////////////////////////////////////////////////////////////
+
+if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
+	exit;
+}
+
+class getid3_torrent extends getid3_handler
+{
+	/**
+	 * Assume all .torrent files are less than 1MB and just read entire thing into memory for easy processing.
+	 * Override this value if you need to process files larger than 1MB
+	 *
+	 * @var int
+	 */
+	public $max_torrent_filesize = 1048576;
+
+	/**
+	 * calculated InfoHash (SHA1 of the entire "info" Dictionary)
+	 *
+	 * @var string
+	 */
+	private $infohash = '';
+
+	const PIECE_HASHLENGTH = 20; // number of bytes the SHA1 hash is for each piece
+
+	/**
+	 * @return bool
+	 */
+	public function Analyze() {
+		$info = &$this->getid3->info;
+		$filesize = $info['avdataend'] - $info['avdataoffset'];
+		if ($filesize > $this->max_torrent_filesize) {  //
+			$this->error('File larger ('.number_format($filesize).' bytes) than $max_torrent_filesize ('.number_format($this->max_torrent_filesize).' bytes), increase getid3_torrent->max_torrent_filesize if needed');
+			return false;
+		}
+		$this->fseek($info['avdataoffset']);
+		$TORRENT = $this->fread($filesize);
+		$offset = 0;
+		if (!preg_match('#^(d8\\:announce|d7\\:comment)#', $TORRENT)) {
+			$this->error('Expecting "d8:announce" or "d7:comment" at '.$info['avdataoffset'].', found "'.substr($TORRENT, $offset, 12).'" instead.');
+			return false;
+		}
+		$info['fileformat'] = 'torrent';
+
+		$info['torrent'] = $this->NextEntity($TORRENT, $offset);
+		if ($this->infohash) {
+			$info['torrent']['infohash'] = $this->infohash;
+		}
+
+		if (empty($info['torrent']['info']['length']) && !empty($info['torrent']['info']['files'][0]['length'])) {
+			$info['torrent']['info']['length'] = 0;
+			foreach ($info['torrent']['info']['files'] as $key => $filedetails) {
+				$info['torrent']['info']['length'] += $filedetails['length'];
+			}
+		}
+		if (!empty($info['torrent']['info']['length']) && !empty($info['torrent']['info']['piece length']) && !empty($info['torrent']['info']['pieces'])) {
+			$num_pieces_size = ceil($info['torrent']['info']['length'] / $info['torrent']['info']['piece length']);
+			$num_pieces_hash = strlen($info['torrent']['info']['pieces']) / getid3_torrent::PIECE_HASHLENGTH; // should be concatenated 20-byte SHA1 hashes
+			if ($num_pieces_hash == $num_pieces_size) {
+				$info['torrent']['info']['piece_hash'] = array();
+				for ($i = 0; $i < $num_pieces_size; $i++) {
+					$info['torrent']['info']['piece_hash'][$i] = '';
+					for ($j = 0; $j < getid3_torrent::PIECE_HASHLENGTH; $j++) {
+						$info['torrent']['info']['piece_hash'][$i] .= sprintf('%02x', ord($info['torrent']['info']['pieces'][(($i * getid3_torrent::PIECE_HASHLENGTH) + $j)]));
+					}
+				}
+				unset($info['torrent']['info']['pieces']);
+			} else {
+				$this->warning('found '.$num_pieces_size.' pieces based on file/chunk size; found '.$num_pieces_hash.' pieces in hash table');
+			}
+		}
+		if (!empty($info['torrent']['info']['name']) && !empty($info['torrent']['info']['length']) && !isset($info['torrent']['info']['files'])) {
+			// single-file torrent
+			$info['torrent']['files'] = array($info['torrent']['info']['name'] => $info['torrent']['info']['length']);
+		} elseif (!empty($info['torrent']['info']['files'])) {
+			// multi-file torrent
+			$info['torrent']['files'] = array();
+			foreach ($info['torrent']['info']['files'] as $key => $filedetails) {
+				$info['torrent']['files'][implode('/', $filedetails['path'])] = $filedetails['length'];
+			}
+		} else {
+			$this->warning('no files found');
+		}
+
+		return true;
+	}
+
+	/**
+	 * @return string|array|int|bool
+	 */
+	public function NextEntity(&$TORRENT, &$offset) {
+		// https://fileformats.fandom.com/wiki/Torrent_file
+		// https://en.wikipedia.org/wiki/Torrent_file
+		// https://en.wikipedia.org/wiki/Bencode
+
+		if ($offset >= strlen($TORRENT)) {
+			$this->error('cannot read beyond end of file '.$offset);
+			return false;
+		}
+		$type = $TORRENT[$offset++];
+		if ($type == 'i') {
+
+			// Integers are stored as i<integer>e:
+			//   i90e
+			$value = $this->ReadSequentialDigits($TORRENT, $offset, true);
+			if ($TORRENT[$offset++] == 'e') {
+//echo '<li>int: '.$value.'</li>';
+				return (int) $value;
+			}
+			$this->error('unexpected('.__LINE__.') input "'.$value.'" at offset '.($offset - 1));
+			return false;
+
+		} elseif ($type == 'd') {
+
+			// Dictionaries are stored as d[key1][value1][key2][value2][...]e. Keys and values appear alternately.
+			// Keys must be strings and must be ordered alphabetically.
+			// For example, {apple-red, lemon-yellow, violet-blue, banana-yellow} is stored as:
+			//   d5:apple3:red6:banana6:yellow5:lemon6:yellow6:violet4:bluee
+			$values = array();
+//echo 'DICTIONARY @ '.$offset.'<ul>';
+			$info_dictionary_start = null; // dummy declaration to prevent "Variable might not be defined" warnings
+			while (true) {
+				if ($TORRENT[$offset] === 'e') {
+					break;
+				}
+				$thisentry = array();
+				$key = $this->NextEntity($TORRENT, $offset);
+				if ($key == 'info') {
+					$info_dictionary_start = $offset;
+				}
+				if ($key === false) {
+					$this->error('unexpected('.__LINE__.') input at offset '.$offset);
+					return false;
+				}
+				$value = $this->NextEntity($TORRENT, $offset);
+				if ($key == 'info') {
+					$info_dictionary_end = $offset;
+					$this->infohash = sha1(substr($TORRENT, $info_dictionary_start, $info_dictionary_end - $info_dictionary_start));
+				}
+				if ($value === false) {
+					$this->error('unexpected('.__LINE__.') input at offset '.$offset);
+					return false;
+				}
+				$values[$key] = $value;
+			}
+			if ($TORRENT[$offset++] == 'e') {
+//echo '</ul>';
+				return $values;
+			}
+			$this->error('unexpected('.__LINE__.') input "'.$TORRENT[($offset - 1)].'" at offset '.($offset - 1));
+			return false;
+
+		} elseif ($type == 'l') {
+
+//echo 'LIST @ '.$offset.'<ul>';
+			// Lists are stored as l[value 1][value2][value3][...]e. For example, {spam, eggs, cheeseburger} is stored as:
+			//	l4:spam4:eggs12:cheeseburgere
+			$values = array();
+			while (true) {
+				if ($TORRENT[$offset] === 'e') {
+					break;
+				}
+				$NextEntity = $this->NextEntity($TORRENT, $offset);
+				if ($NextEntity === false) {
+					$this->error('unexpected('.__LINE__.') input at offset '.($offset - 1));
+					return false;
+				}
+				$values[] = $NextEntity;
+			}
+			if ($TORRENT[$offset++] == 'e') {
+//echo '</ul>';
+				return $values;
+			}
+			$this->error('unexpected('.__LINE__.') input "'.$TORRENT[($offset - 1)].'" at offset '.($offset - 1));
+			return false;
+
+		} elseif (ctype_digit($type)) {
+
+			// Strings are stored as <length of string>:<string>:
+			//   4:wiki
+			$length = $type;
+			while (true) {
+				$char = $TORRENT[$offset++];
+				if ($char == ':') {
+					break;
+				} elseif (!ctype_digit($char)) {
+					$this->error('unexpected('.__LINE__.') input "'.$char.'" at offset '.($offset - 1));
+					return false;
+				}
+				$length .= $char;
+			}
+			if (($offset + $length) > strlen($TORRENT)) {
+				$this->error('string at offset '.$offset.' claims to be '.$length.' bytes long but only '.(strlen($TORRENT) - $offset).' bytes of data left in file');
+				return false;
+			}
+			$string = substr($TORRENT, $offset, $length);
+			$offset += $length;
+//echo '<li>string: '.$string.'</li>';
+			return (string) $string;
+
+		} else {
+
+			$this->error('unexpected('.__LINE__.') input "'.$type.'" at offset '.($offset - 1));
+			return false;
+
+		}
+	}
+
+	/**
+	 * @return string
+	 */
+	public function ReadSequentialDigits(&$TORRENT, &$offset, $allow_negative=false) {
+		$start_offset = $offset;
+		$value = '';
+		while (true) {
+			$char = $TORRENT[$offset++];
+			if (!ctype_digit($char)) {
+				if ($allow_negative && ($char == '-') && (strlen($value) == 0)) {
+					// allow negative-sign if first character and $allow_negative enabled
+				} else {
+					$offset--;
+					break;
+				}
+			}
+			$value .= $char;
+		}
+		if (($value[0] === '0') && ($value !== '0')) {
+			$this->warning('illegal zero-padded number "'.$value.'" at offset '.$start_offset);
+		}
+		return $value;
+	}
+
+}
diff --git a/lib/getid3/module.tag.apetag.php b/lib/getid3/module.tag.apetag.php
index 26be982c73a25c1ad31b51fa893b9b0e51eb0de5..c5502133fcbb31f2aa435165a2ab231443f1413e 100644
--- a/lib/getid3/module.tag.apetag.php
+++ b/lib/getid3/module.tag.apetag.php
@@ -360,6 +360,7 @@ class getid3_apetag extends getid3_handler
 		// http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
 
 		// shortcut
+		$headerfooterinfo = array();
 		$headerfooterinfo['raw'] = array();
 		$headerfooterinfo_raw = &$headerfooterinfo['raw'];
 
@@ -389,6 +390,7 @@ class getid3_apetag extends getid3_handler
 		// "Note: APE Tags 1.0 do not use any of the APE Tag flags.
 		// All are set to zero on creation and ignored on reading."
 		// http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags
+		$flags                      = array();
 		$flags['header']            = (bool) ($rawflagint & 0x80000000);
 		$flags['footer']            = (bool) ($rawflagint & 0x40000000);
 		$flags['this_is_header']    = (bool) ($rawflagint & 0x20000000);
diff --git a/lib/getid3/module.tag.id3v1.php b/lib/getid3/module.tag.id3v1.php
index 16dcf253b655d39be217499ccdf7c22e8c9c56e5..b1de25784bb92982fb3d80bcd05afe87978dade0 100644
--- a/lib/getid3/module.tag.id3v1.php
+++ b/lib/getid3/module.tag.id3v1.php
@@ -31,14 +31,22 @@ class getid3_id3v1 extends getid3_handler
 			return false;
 		}
 
-		$this->fseek(-256, SEEK_END);
-		$preid3v1 = $this->fread(128);
-		$id3v1tag = $this->fread(128);
+		if($info['filesize'] < 256) {
+			$this->fseek(-128, SEEK_END);
+			$preid3v1 = '';
+			$id3v1tag = $this->fread(128);
+		} else {
+			$this->fseek(-256, SEEK_END);
+			$preid3v1 = $this->fread(128);
+			$id3v1tag = $this->fread(128);
+		}
+
 
 		if (substr($id3v1tag, 0, 3) == 'TAG') {
 
 			$info['avdataend'] = $info['filesize'] - 128;
 
+			$ParsedID3v1            = array();
 			$ParsedID3v1['title']   = $this->cutfield(substr($id3v1tag,   3, 30));
 			$ParsedID3v1['artist']  = $this->cutfield(substr($id3v1tag,  33, 30));
 			$ParsedID3v1['album']   = $this->cutfield(substr($id3v1tag,  63, 30));
@@ -297,6 +305,50 @@ class getid3_id3v1 extends getid3_handler
 			145  => 'Anime',
 			146  => 'JPop',
 			147  => 'Synthpop',
+			148 => 'Abstract',
+			149 => 'Art Rock',
+			150 => 'Baroque',
+			151 => 'Bhangra',
+			152 => 'Big Beat',
+			153 => 'Breakbeat',
+			154 => 'Chillout',
+			155 => 'Downtempo',
+			156 => 'Dub',
+			157 => 'EBM',
+			158 => 'Eclectic',
+			159 => 'Electro',
+			160 => 'Electroclash',
+			161 => 'Emo',
+			162 => 'Experimental',
+			163 => 'Garage',
+			164 => 'Global',
+			165 => 'IDM',
+			166 => 'Illbient',
+			167 => 'Industro-Goth',
+			168 => 'Jam Band',
+			169 => 'Krautrock',
+			170 => 'Leftfield',
+			171 => 'Lounge',
+			172 => 'Math Rock',
+			173 => 'New Romantic',
+			174 => 'Nu-Breakz',
+			175 => 'Post-Punk',
+			176 => 'Post-Rock',
+			177 => 'Psytrance',
+			178 => 'Shoegaze',
+			179 => 'Space Rock',
+			180 => 'Trop Rock',
+			181 => 'World Music',
+			182 => 'Neoclassical',
+			183 => 'Audiobook',
+			184 => 'Audio Theatre',
+			185 => 'Neue Deutsche Welle',
+			186 => 'Podcast',
+			187 => 'Indie-Rock',
+			188 => 'G-Funk',
+			189 => 'Dubstep',
+			190 => 'Garage Rock',
+			191 => 'Psybient',
 
 			255  => 'Unknown',
 
diff --git a/lib/getid3/module.tag.id3v2.php b/lib/getid3/module.tag.id3v2.php
index 85dd7a147a1482c7957691ae2d050eb1df2d7d9f..99e56a5fdb066b33a615d93945a0629c643876ab 100644
--- a/lib/getid3/module.tag.id3v2.php
+++ b/lib/getid3/module.tag.id3v2.php
@@ -345,7 +345,7 @@ class getid3_id3v2 extends getid3_handler
 				}
 				if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
 
-					unset($parsedFrame);
+					$parsedFrame                    = array();
 					$parsedFrame['frame_name']      = $frame_name;
 					$parsedFrame['frame_flags_raw'] = $frame_flags;
 					$parsedFrame['data']            = substr($framedata, 0, $frame_size);
@@ -978,7 +978,7 @@ class getid3_id3v2 extends getid3_handler
 
 
 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
-				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
+				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {    // 4.9   ULT  Unsynchronised lyric/text transcription
 			//   There may be more than one 'Unsynchronised lyrics/text transcription' frame
 			//   in each tag, but only one with the same language and content descriptor.
 			// <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
@@ -994,24 +994,28 @@ class getid3_id3v2 extends getid3_handler
 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 				$frame_textencoding_terminator = "\x00";
 			}
-			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
-			$frame_offset += 3;
-			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
-			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
-				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
-			}
-			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
-			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
-			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
-			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
+			if (strlen($parsedFrame['data']) >= (4 + strlen($frame_textencoding_terminator))) {  // shouldn't be an issue but badly-written files have been spotted in the wild with not only no contents but also missing the required language field, see https://github.com/JamesHeinrich/getID3/issues/315
+				$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
+				$frame_offset += 3;
+				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
+				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
+					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
+				}
+				$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
+				$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
+				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
+				$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
 
-			$parsedFrame['encodingid']   = $frame_textencoding;
-			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
+				$parsedFrame['encodingid']   = $frame_textencoding;
+				$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
 
-			$parsedFrame['language']     = $frame_language;
-			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
-			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
-				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
+				$parsedFrame['language']     = $frame_language;
+				$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
+				if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
+					$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
+				}
+			} else {
+				$this->warning('Invalid data in frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset']);
 			}
 			unset($parsedFrame['data']);
 
@@ -1370,6 +1374,8 @@ class getid3_id3v2 extends getid3_handler
 				$frame_textencoding_terminator = "\x00";
 			}
 
+			$frame_imagetype = null;
+			$frame_mimetype = null;
 			if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
 				$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
 				if (strtolower($frame_imagetype) == 'ima') {
@@ -1956,18 +1962,14 @@ class getid3_id3v2 extends getid3_handler
 			$frame_offset = 0;
 			$parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
 			$frame_offset += 4;
-			$rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
-			$frame_offset += 2;
-			$rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
-			$frame_offset += 2;
-			$parsedFrame['raw']['track']['name']       = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
-			$parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
-			$parsedFrame['raw']['track']['signbit']    = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
-			$parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
-			$parsedFrame['raw']['album']['name']       = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
-			$parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
-			$parsedFrame['raw']['album']['signbit']    = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
-			$parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
+			foreach (array('track','album') as $rgad_entry_type) {
+				$rg_adjustment_word = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
+				$frame_offset += 2;
+				$parsedFrame['raw'][$rgad_entry_type]['name']       = ($rg_adjustment_word & 0xE000) >> 13;
+				$parsedFrame['raw'][$rgad_entry_type]['originator'] = ($rg_adjustment_word & 0x1C00) >> 10;
+				$parsedFrame['raw'][$rgad_entry_type]['signbit']    = ($rg_adjustment_word & 0x0200) >>  9;
+				$parsedFrame['raw'][$rgad_entry_type]['adjustment'] = ($rg_adjustment_word & 0x0100);
+			}
 			$parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
 			$parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
 			$parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
@@ -2444,7 +2446,8 @@ class getid3_id3v2 extends getid3_handler
 			TMM	Manats
 			TND	Dinars
 			TOP	Pa'anga
-			TRL	Liras
+			TRL	Liras (old)
+			TRY	Liras
 			TTD	Dollars
 			TVD	Tuvalu Dollars
 			TWD	New Dollars
@@ -2645,6 +2648,7 @@ class getid3_id3v2 extends getid3_handler
 			TND	Tunisia
 			TOP	Tonga
 			TRL	Turkey
+			TRY	Turkey
 			TTD	Trinidad and Tobago
 			TVD	Tuvalu
 			TWD	Taiwan
diff --git a/lib/getid3/module.tag.lyrics3.php b/lib/getid3/module.tag.lyrics3.php
index b2375057e79106a00c552ac2f0e6c2c76440cf20..c8b2cf6305984d68dd29ae291d521507dd037134 100644
--- a/lib/getid3/module.tag.lyrics3.php
+++ b/lib/getid3/module.tag.lyrics3.php
@@ -33,6 +33,9 @@ class getid3_lyrics3 extends getid3_handler
 		}
 
 		$this->fseek((0 - 128 - 9 - 6), SEEK_END);          // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
+		$lyrics3offset = null;
+		$lyrics3version = null;
+		$lyrics3size   = null;
 		$lyrics3_id3v1 = $this->fread(128 + 9 + 6);
 		$lyrics3lsz    = (int) substr($lyrics3_id3v1, 0, 6); // Lyrics3size
 		$lyrics3end    = substr($lyrics3_id3v1,  6,   9); // LYRICSEND or LYRICS200
diff --git a/lib/getid3/module.tag.nikon-nctg.php b/lib/getid3/module.tag.nikon-nctg.php
new file mode 100644
index 0000000000000000000000000000000000000000..46e2f21d895551d5bf10f8d612cb2d9b01efe2d2
--- /dev/null
+++ b/lib/getid3/module.tag.nikon-nctg.php
@@ -0,0 +1,1448 @@
+<?php
+
+/////////////////////////////////////////////////////////////////
+/// getID3() by James Heinrich <info@getid3.org>               //
+//  available at https://github.com/JamesHeinrich/getID3       //
+//            or https://www.getid3.org                        //
+//            or http://getid3.sourceforge.net                 //
+//  see readme.txt for more details                            //
+/////////////////////////////////////////////////////////////////
+//                                                             //
+// module.tag.nikon-nctg.php                                   //
+//                                                             //
+/////////////////////////////////////////////////////////////////
+
+/**
+ * Module for analyzing Nikon NCTG metadata in MOV files
+ *
+ * @author Pavel Starosek <starosekpd@gmail.com>
+ * @author Phil Harvey <philharvey66@gmail.com>
+ *
+ * @link https://exiftool.org/TagNames/Nikon.html#NCTG
+ * @link https://github.com/exiftool/exiftool/blob/master/lib/Image/ExifTool/Nikon.pm
+ * @link https://leo-van-stee.github.io/
+ */
+class getid3_tag_nikon_nctg
+{
+	const EXIF_TYPE_UINT8 = 0x0001;
+	const EXIF_TYPE_CHAR = 0x0002;
+	const EXIF_TYPE_UINT16 = 0x0003;
+	const EXIF_TYPE_UINT32 = 0x0004;
+	const EXIF_TYPE_URATIONAL = 0x0005;
+	const EXIF_TYPE_INT8 = 0x0006;
+	const EXIF_TYPE_RAW = 0x0007;
+	const EXIF_TYPE_INT16 = 0x0008;
+	const EXIF_TYPE_INT32 = 0x0009;
+	const EXIF_TYPE_RATIONAL = 0x000A;
+
+	protected static $exifTypeSizes = array(
+		self::EXIF_TYPE_UINT8 => 1,
+		self::EXIF_TYPE_CHAR => 1,
+		self::EXIF_TYPE_UINT16 => 2,
+		self::EXIF_TYPE_UINT32 => 4,
+		self::EXIF_TYPE_URATIONAL => 8,
+		self::EXIF_TYPE_INT8 => 1,
+		self::EXIF_TYPE_RAW => 1,
+		self::EXIF_TYPE_INT16 => 2,
+		self::EXIF_TYPE_INT32 => 4,
+		self::EXIF_TYPE_RATIONAL => 8,
+	);
+
+	protected static $exposurePrograms = array(
+		0 => 'Not Defined',
+		1 => 'Manual',
+		2 => 'Program AE',
+		3 => 'Aperture-priority AE',
+		4 => 'Shutter speed priority AE',
+		5 => 'Creative (Slow speed)',
+		6 => 'Action (High speed)',
+		7 => 'Portrait',
+		8 => 'Landscape'
+	);
+
+	protected static $meteringModes = array(
+		0 => 'Unknown',
+		1 => 'Average',
+		2 => 'Center-weighted average',
+		3 => 'Spot',
+		4 => 'Multi-spot',
+		5 => 'Multi-segment',
+		6 => 'Partial',
+		255 => 'Other'
+	);
+
+	protected static $cropHiSpeeds = array(
+		0  => 'Off',
+		1  => '1.3x Crop',
+		2  => 'DX Crop',
+		3  => '5:4 Crop',
+		4  => '3:2 Crop',
+		6  => '16:9 Crop',
+		8  => '2.7x Crop',
+		9  => 'DX Movie Crop',
+		10 => '1.3x Movie Crop',
+		11 => 'FX Uncropped',
+		12 => 'DX Uncropped',
+		15 => '1.5x Movie Crop',
+		17 => '1:1 Crop'
+	);
+
+	protected static $colorSpaces = array(
+		1 => 'sRGB',
+		2 => 'Adobe RGB'
+	);
+
+	protected static $vibrationReductions = array(
+		1 => 'On',
+		2 => 'Off'
+	);
+
+	protected static $VRModes = array(
+		0 => 'Normal',
+		1 => 'On (1)',
+		2 => 'Active',
+		3 => 'Sport'
+	);
+
+	protected static $activeDLightnings = array(
+		0  => 'Off',
+		1  => 'Low',
+		3  => 'Normal',
+		5  => 'High',
+		7  => 'Extra High',
+		8  => 'Extra High 1',
+		9  => 'Extra High 2',
+		10 => 'Extra High 3',
+		11 => 'Extra High 4',
+		65535 => 'Auto'
+	);
+
+	protected static $pictureControlDataAdjusts = array(
+		0 => 'default',
+		1 => 'quick',
+		2 => 'full'
+	);
+
+	protected static $pictureControlDataFilterEffects = array(
+		0x80 => 'off',
+		0x81 => 'yellow',
+		0x82 => 'orange',
+		0x83 => 'red',
+		0x84 => 'green',
+		0xff => 'n/a'
+	);
+
+	protected static $pictureControlDataToningEffects = array(
+		0x80 => 'b&w',
+		0x81 => 'sepia',
+		0x82 => 'cyanotype',
+		0x83 => 'red',
+		0x84 => 'yellow',
+		0x85 => 'green',
+		0x86 => 'blue-green',
+		0x87 => 'blue',
+		0x88 => 'purple-blue',
+		0x89 => 'red-purple',
+		0xff => 'n/a'
+	);
+
+	protected static $isoInfoExpansions = array(
+		0x0000 => 'Off',
+		0x0101 => 'Hi 0.3',
+		0x0102 => 'Hi 0.5',
+		0x0103 => 'Hi 0.7',
+		0x0104 => 'Hi 1.0',
+		0x0105 => 'Hi 1.3',
+		0x0106 => 'Hi 1.5',
+		0x0107 => 'Hi 1.7',
+		0x0108 => 'Hi 2.0',
+		0x0109 => 'Hi 2.3',
+		0x010a => 'Hi 2.5',
+		0x010b => 'Hi 2.7',
+		0x010c => 'Hi 3.0',
+		0x010d => 'Hi 3.3',
+		0x010e => 'Hi 3.5',
+		0x010f => 'Hi 3.7',
+		0x0110 => 'Hi 4.0',
+		0x0111 => 'Hi 4.3',
+		0x0112 => 'Hi 4.5',
+		0x0113 => 'Hi 4.7',
+		0x0114 => 'Hi 5.0',
+		0x0201 => 'Lo 0.3',
+		0x0202 => 'Lo 0.5',
+		0x0203 => 'Lo 0.7',
+		0x0204 => 'Lo 1.0',
+	);
+
+	protected static $isoInfoExpansions2 = array(
+		0x0000 => 'Off',
+		0x0101 => 'Hi 0.3',
+		0x0102 => 'Hi 0.5',
+		0x0103 => 'Hi 0.7',
+		0x0104 => 'Hi 1.0',
+		0x0105 => 'Hi 1.3',
+		0x0106 => 'Hi 1.5',
+		0x0107 => 'Hi 1.7',
+		0x0108 => 'Hi 2.0',
+		0x0201 => 'Lo 0.3',
+		0x0202 => 'Lo 0.5',
+		0x0203 => 'Lo 0.7',
+		0x0204 => 'Lo 1.0',
+	);
+
+	protected static $vignetteControls = array(
+		0 => 'Off',
+		1 => 'Low',
+		3 => 'Normal',
+		5 => 'High'
+	);
+
+	protected static $flashModes = array(
+		0 => 'Did Not Fire',
+		1 => 'Fired, Manual',
+		3 => 'Not Ready',
+		7 => 'Fired, External',
+		8 => 'Fired, Commander Mode',
+		9 => 'Fired, TTL Mode',
+		18 => 'LED Light'
+	);
+
+	protected static $flashInfoSources = array(
+		0 => 'None',
+		1 => 'External',
+		2 => 'Internal'
+	);
+
+	protected static $flashInfoExternalFlashFirmwares = array(
+		'0 0' => 'n/a',
+		'1 1' => '1.01 (SB-800 or Metz 58 AF-1)',
+		'1 3' => '1.03 (SB-800)',
+		'2 1' => '2.01 (SB-800)',
+		'2 4' => '2.04 (SB-600)',
+		'2 5' => '2.05 (SB-600)',
+		'3 1' => '3.01 (SU-800 Remote Commander)',
+		'4 1' => '4.01 (SB-400)',
+		'4 2' => '4.02 (SB-400)',
+		'4 4' => '4.04 (SB-400)',
+		'5 1' => '5.01 (SB-900)',
+		'5 2' => '5.02 (SB-900)',
+		'6 1' => '6.01 (SB-700)',
+		'7 1' => '7.01 (SB-910)',
+	);
+
+	protected static $flashInfoExternalFlashFlags = array(
+		0 => 'Fired',
+		2 => 'Bounce Flash',
+		4 => 'Wide Flash Adapter',
+		5 => 'Dome Diffuser',
+	);
+
+	protected static $flashInfoExternalFlashStatuses = array(
+		0 => 'Flash Not Attached',
+		1 => 'Flash Attached',
+	);
+
+	protected static $flashInfoExternalFlashReadyStates = array(
+		0 => 'n/a',
+		1 => 'Ready',
+		6 => 'Not Ready',
+	);
+
+	protected static $flashInfoGNDistances = array(
+		0 => 0,        19 => '2.8 m',
+		1 => '0.1 m',  20 => '3.2 m',
+		2 => '0.2 m',  21 => '3.6 m',
+		3 => '0.3 m',  22 => '4.0 m',
+		4 => '0.4 m',  23 => '4.5 m',
+		5 => '0.5 m',  24 => '5.0 m',
+		6 => '0.6 m',  25 => '5.6 m',
+		7 => '0.7 m',  26 => '6.3 m',
+		8 => '0.8 m',  27 => '7.1 m',
+		9 => '0.9 m',  28 => '8.0 m',
+		10 => '1.0 m',  29 => '9.0 m',
+		11 => '1.1 m',  30 => '10.0 m',
+		12 => '1.3 m',  31 => '11.0 m',
+		13 => '1.4 m',  32 => '13.0 m',
+		14 => '1.6 m',  33 => '14.0 m',
+		15 => '1.8 m',  34 => '16.0 m',
+		16 => '2.0 m',  35 => '18.0 m',
+		17 => '2.2 m',  36 => '20.0 m',
+		18 => '2.5 m',  255 => 'n/a'
+	);
+
+	protected static $flashInfoControlModes = array(
+		0x00 => 'Off',
+		0x01 => 'iTTL-BL',
+		0x02 => 'iTTL',
+		0x03 => 'Auto Aperture',
+		0x04 => 'Automatic',
+		0x05 => 'GN (distance priority)',
+		0x06 => 'Manual',
+		0x07 => 'Repeating Flash',
+	);
+
+	protected static $flashInfoColorFilters = array(
+		0 => 'None',
+		1 => 'FL-GL1 or SZ-2FL Fluorescent',
+		2 => 'FL-GL2',
+		9 => 'TN-A1 or SZ-2TN Incandescent',
+		10 => 'TN-A2',
+		65 => 'Red',
+		66 => 'Blue',
+		67 => 'Yellow',
+		68 => 'Amber',
+	);
+
+	protected static $highISONoiseReductions = array(
+		0 => 'Off',
+		1 => 'Minimal',
+		2 => 'Low',
+		3 => 'Medium Low',
+		4 => 'Normal',
+		5 => 'Medium High',
+		6 => 'High'
+	);
+
+	protected static $AFInfo2ContrastDetectAFChoices = array(
+		0 => 'Off',
+		1 => 'On',
+		2 => 'On (2)'
+	);
+
+	protected static $AFInfo2AFAreaModesWithoutContrastDetectAF = array(
+		0 => 'Single Area',
+		1 => 'Dynamic Area',
+		2 => 'Dynamic Area (closest subject)',
+		3 => 'Group Dynamic',
+		4 => 'Dynamic Area (9 points)',
+		5 => 'Dynamic Area (21 points)',
+		6 => 'Dynamic Area (51 points)',
+		7 => 'Dynamic Area (51 points, 3D-tracking)',
+		8 => 'Auto-area',
+		9 => 'Dynamic Area (3D-tracking)',
+		10 => 'Single Area (wide)',
+		11 => 'Dynamic Area (wide)',
+		12 => 'Dynamic Area (wide, 3D-tracking)',
+		13 => 'Group Area',
+		14 => 'Dynamic Area (25 points)',
+		15 => 'Dynamic Area (72 points)',
+		16 => 'Group Area (HL)',
+		17 => 'Group Area (VL)',
+		18 => 'Dynamic Area (49 points)',
+		128 => 'Single',
+		129 => 'Auto (41 points)',
+		130 => 'Subject Tracking (41 points)',
+		131 => 'Face Priority (41 points)',
+		192 => 'Pinpoint',
+		193 => 'Single',
+		195 => 'Wide (S)',
+		196 => 'Wide (L)',
+		197 => 'Auto',
+	);
+
+	protected static $AFInfo2AFAreaModesWithContrastDetectAF = array(
+		0 => 'Contrast-detect',
+		1 => 'Contrast-detect (normal area)',
+		2 => 'Contrast-detect (wide area)',
+		3 => 'Contrast-detect (face priority)',
+		4 => 'Contrast-detect (subject tracking)',
+		128 => 'Single',
+		129 => 'Auto (41 points)',
+		130 => 'Subject Tracking (41 points)',
+		131 => 'Face Priority (41 points)',
+		192 => 'Pinpoint',
+		193 => 'Single',
+		194 => 'Dynamic',
+		195 => 'Wide (S)',
+		196 => 'Wide (L)',
+		197 => 'Auto',
+		198 => 'Auto (People)',
+		199 => 'Auto (Animal)',
+		200 => 'Normal-area AF',
+		201 => 'Wide-area AF',
+		202 => 'Face-priority AF',
+		203 => 'Subject-tracking AF',
+	);
+
+	protected static $AFInfo2PhaseDetectAFChoices = array(
+		0 => 'Off',
+		1 => 'On (51-point)',
+		2 => 'On (11-point)',
+		3 => 'On (39-point)',
+		4 => 'On (73-point)',
+		5 => 'On (5)',
+		6 => 'On (105-point)',
+		7 => 'On (153-point)',
+		8 => 'On (81-point)',
+		9 => 'On (105-point)',
+	);
+
+	protected static $NikkorZLensIDS = array(
+		1 => 'Nikkor Z 24-70mm f/4 S',
+		2 => 'Nikkor Z 14-30mm f/4 S',
+		4 => 'Nikkor Z 35mm f/1.8 S',
+		8 => 'Nikkor Z 58mm f/0.95 S Noct',
+		9 => 'Nikkor Z 50mm f/1.8 S',
+		11 => 'Nikkor Z DX 16-50mm f/3.5-6.3 VR',
+		12 => 'Nikkor Z DX 50-250mm f/4.5-6.3 VR',
+		13 => 'Nikkor Z 24-70mm f/2.8 S',
+		14 => 'Nikkor Z 85mm f/1.8 S',
+		15 => 'Nikkor Z 24mm f/1.8 S',
+		16 => 'Nikkor Z 70-200mm f/2.8 VR S',
+		17 => 'Nikkor Z 20mm f/1.8 S',
+		18 => 'Nikkor Z 24-200mm f/4-6.3 VR',
+		21 => 'Nikkor Z 50mm f/1.2 S',
+		22 => 'Nikkor Z 24-50mm f/4-6.3',
+		23 => 'Nikkor Z 14-24mm f/2.8 S',
+	);
+
+	protected static $nikonTextEncodings = array(
+		1 => 'UTF-8',
+		2 => 'UTF-16'
+	);
+
+	/**
+	 * Ref 4
+	 *
+	 * @var int[][]
+	 */
+	protected static $decodeTables = array(
+		array(
+			0xc1,0xbf,0x6d,0x0d,0x59,0xc5,0x13,0x9d,0x83,0x61,0x6b,0x4f,0xc7,0x7f,0x3d,0x3d,
+			0x53,0x59,0xe3,0xc7,0xe9,0x2f,0x95,0xa7,0x95,0x1f,0xdf,0x7f,0x2b,0x29,0xc7,0x0d,
+			0xdf,0x07,0xef,0x71,0x89,0x3d,0x13,0x3d,0x3b,0x13,0xfb,0x0d,0x89,0xc1,0x65,0x1f,
+			0xb3,0x0d,0x6b,0x29,0xe3,0xfb,0xef,0xa3,0x6b,0x47,0x7f,0x95,0x35,0xa7,0x47,0x4f,
+			0xc7,0xf1,0x59,0x95,0x35,0x11,0x29,0x61,0xf1,0x3d,0xb3,0x2b,0x0d,0x43,0x89,0xc1,
+			0x9d,0x9d,0x89,0x65,0xf1,0xe9,0xdf,0xbf,0x3d,0x7f,0x53,0x97,0xe5,0xe9,0x95,0x17,
+			0x1d,0x3d,0x8b,0xfb,0xc7,0xe3,0x67,0xa7,0x07,0xf1,0x71,0xa7,0x53,0xb5,0x29,0x89,
+			0xe5,0x2b,0xa7,0x17,0x29,0xe9,0x4f,0xc5,0x65,0x6d,0x6b,0xef,0x0d,0x89,0x49,0x2f,
+			0xb3,0x43,0x53,0x65,0x1d,0x49,0xa3,0x13,0x89,0x59,0xef,0x6b,0xef,0x65,0x1d,0x0b,
+			0x59,0x13,0xe3,0x4f,0x9d,0xb3,0x29,0x43,0x2b,0x07,0x1d,0x95,0x59,0x59,0x47,0xfb,
+			0xe5,0xe9,0x61,0x47,0x2f,0x35,0x7f,0x17,0x7f,0xef,0x7f,0x95,0x95,0x71,0xd3,0xa3,
+			0x0b,0x71,0xa3,0xad,0x0b,0x3b,0xb5,0xfb,0xa3,0xbf,0x4f,0x83,0x1d,0xad,0xe9,0x2f,
+			0x71,0x65,0xa3,0xe5,0x07,0x35,0x3d,0x0d,0xb5,0xe9,0xe5,0x47,0x3b,0x9d,0xef,0x35,
+			0xa3,0xbf,0xb3,0xdf,0x53,0xd3,0x97,0x53,0x49,0x71,0x07,0x35,0x61,0x71,0x2f,0x43,
+			0x2f,0x11,0xdf,0x17,0x97,0xfb,0x95,0x3b,0x7f,0x6b,0xd3,0x25,0xbf,0xad,0xc7,0xc5,
+			0xc5,0xb5,0x8b,0xef,0x2f,0xd3,0x07,0x6b,0x25,0x49,0x95,0x25,0x49,0x6d,0x71,0xc7
+		),
+		array(
+			0xa7,0xbc,0xc9,0xad,0x91,0xdf,0x85,0xe5,0xd4,0x78,0xd5,0x17,0x46,0x7c,0x29,0x4c,
+			0x4d,0x03,0xe9,0x25,0x68,0x11,0x86,0xb3,0xbd,0xf7,0x6f,0x61,0x22,0xa2,0x26,0x34,
+			0x2a,0xbe,0x1e,0x46,0x14,0x68,0x9d,0x44,0x18,0xc2,0x40,0xf4,0x7e,0x5f,0x1b,0xad,
+			0x0b,0x94,0xb6,0x67,0xb4,0x0b,0xe1,0xea,0x95,0x9c,0x66,0xdc,0xe7,0x5d,0x6c,0x05,
+			0xda,0xd5,0xdf,0x7a,0xef,0xf6,0xdb,0x1f,0x82,0x4c,0xc0,0x68,0x47,0xa1,0xbd,0xee,
+			0x39,0x50,0x56,0x4a,0xdd,0xdf,0xa5,0xf8,0xc6,0xda,0xca,0x90,0xca,0x01,0x42,0x9d,
+			0x8b,0x0c,0x73,0x43,0x75,0x05,0x94,0xde,0x24,0xb3,0x80,0x34,0xe5,0x2c,0xdc,0x9b,
+			0x3f,0xca,0x33,0x45,0xd0,0xdb,0x5f,0xf5,0x52,0xc3,0x21,0xda,0xe2,0x22,0x72,0x6b,
+			0x3e,0xd0,0x5b,0xa8,0x87,0x8c,0x06,0x5d,0x0f,0xdd,0x09,0x19,0x93,0xd0,0xb9,0xfc,
+			0x8b,0x0f,0x84,0x60,0x33,0x1c,0x9b,0x45,0xf1,0xf0,0xa3,0x94,0x3a,0x12,0x77,0x33,
+			0x4d,0x44,0x78,0x28,0x3c,0x9e,0xfd,0x65,0x57,0x16,0x94,0x6b,0xfb,0x59,0xd0,0xc8,
+			0x22,0x36,0xdb,0xd2,0x63,0x98,0x43,0xa1,0x04,0x87,0x86,0xf7,0xa6,0x26,0xbb,0xd6,
+			0x59,0x4d,0xbf,0x6a,0x2e,0xaa,0x2b,0xef,0xe6,0x78,0xb6,0x4e,0xe0,0x2f,0xdc,0x7c,
+			0xbe,0x57,0x19,0x32,0x7e,0x2a,0xd0,0xb8,0xba,0x29,0x00,0x3c,0x52,0x7d,0xa8,0x49,
+			0x3b,0x2d,0xeb,0x25,0x49,0xfa,0xa3,0xaa,0x39,0xa7,0xc5,0xa7,0x50,0x11,0x36,0xfb,
+			0xc6,0x67,0x4a,0xf5,0xa5,0x12,0x65,0x7e,0xb0,0xdf,0xaf,0x4e,0xb3,0x61,0x7f,0x2f
+		)
+	);
+
+	/**
+	 * @var getID3
+	 */
+	private $getid3;
+
+	public function __construct(getID3 $getid3)
+	{
+		$this->getid3 = $getid3;
+	}
+
+	/**
+	 * Get a copy of all NCTG tags extracted from the video
+	 *
+	 * @param string $atomData
+	 *
+	 * @return array<string, mixed>
+	 */
+	public function parse($atomData) {
+		// Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
+		// Data is stored as records of:
+		// * 4 bytes record type
+		// * 2 bytes size of data field type:
+		//     0x0001 = flag / unsigned byte      (size field *= 1-byte)
+		//     0x0002 = char / ascii strings      (size field *= 1-byte)
+		//     0x0003 = DWORD+ / unsigned short   (size field *= 2-byte), values are stored CDAB
+		//     0x0004 = QWORD+ / unsigned long    (size field *= 4-byte), values are stored EFGHABCD
+		//     0x0005 = float / unsigned rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+		//     0x0006 = signed byte               (size field *= 1-byte)
+		//     0x0007 = raw bytes                 (size field *= 1-byte)
+		//     0x0008 = signed short              (size field *= 2-byte), values are stored as CDAB
+		//     0x0009 = signed long               (size field *= 4-byte), values are stored as EFGHABCD
+		//     0x000A = float / signed rational   (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+		// * 2 bytes data size field
+		// * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15")
+		// all integers are stored BigEndian
+
+		$NCTGtagName = array(
+			0x00000001 => 'Make',
+			0x00000002 => 'Model',
+			0x00000003 => 'Software',
+			0x00000011 => 'CreateDate',
+			0x00000012 => 'DateTimeOriginal',
+			0x00000013 => 'FrameCount',
+			0x00000016 => 'FrameRate',
+			0x00000019 => 'TimeZone',
+			0x00000022 => 'FrameWidth',
+			0x00000023 => 'FrameHeight',
+			0x00000032 => 'AudioChannels',
+			0x00000033 => 'AudioBitsPerSample',
+			0x00000034 => 'AudioSampleRate',
+			0x00001002 => 'NikonDateTime',
+			0x00001013 => 'ElectronicVR',
+			0x0110829a => 'ExposureTime',
+			0x0110829d => 'FNumber',
+			0x01108822 => 'ExposureProgram',
+			0x01109204 => 'ExposureCompensation',
+			0x01109207 => 'MeteringMode',
+			0x0110920a => 'FocalLength', // mm
+			0x0110a431 => 'SerialNumber',
+			0x0110a432 => 'LensInfo',
+			0x0110a433 => 'LensMake',
+			0x0110a434 => 'LensModel',
+			0x0110a435 => 'LensSerialNumber',
+			0x01200000 => 'GPSVersionID',
+			0x01200001 => 'GPSLatitudeRef',
+			0x01200002 => 'GPSLatitude',
+			0x01200003 => 'GPSLongitudeRef',
+			0x01200004 => 'GPSLongitude',
+			0x01200005 => 'GPSAltitudeRef', // 0 = Above Sea Level, 1 = Below Sea Level
+			0x01200006 => 'GPSAltitude',
+			0x01200007 => 'GPSTimeStamp',
+			0x01200008 => 'GPSSatellites',
+			0x01200010 => 'GPSImgDirectionRef', // M = Magnetic North, T = True North
+			0x01200011 => 'GPSImgDirection',
+			0x01200012 => 'GPSMapDatum',
+			0x0120001d => 'GPSDateStamp',
+			0x02000001 => 'MakerNoteVersion',
+			0x02000005 => 'WhiteBalance',
+			0x02000007 => 'FocusMode',
+			0x0200000b => 'WhiteBalanceFineTune',
+			0x0200001b => 'CropHiSpeed',
+			0x0200001e => 'ColorSpace',
+			0x0200001f => 'VRInfo',
+			0x02000022 => 'ActiveDLighting',
+			0x02000023 => 'PictureControlData',
+			0x02000024 => 'WorldTime',
+			0x02000025 => 'ISOInfo',
+			0x0200002a => 'VignetteControl',
+			0x0200002c => 'UnknownInfo',
+			0x02000032 => 'UnknownInfo2',
+			0x02000039 => 'LocationInfo',
+			0x02000083 => 'LensType',
+			0x02000084 => 'Lens',
+			0x02000087 => 'FlashMode',
+			0x02000098 => 'LensData',
+			0x020000a7 => 'ShutterCount',
+			0x020000a8 => 'FlashInfo',
+			0x020000ab => 'VariProgram',
+			0x020000b1 => 'HighISONoiseReduction',
+			0x020000b7 => 'AFInfo2',
+			0x020000c3 => 'BarometerInfo',
+		);
+
+		$firstPassNeededTags = array(
+			0x00000002, // Model
+			0x0110a431, // SerialNumber
+			0x020000a7, // ShutterCount
+		);
+
+		$datalength = strlen($atomData);
+		$parsed = array();
+		$model = $serialNumber = $shutterCount = null;
+		for ($pass = 0; $pass < 2; ++$pass) {
+			$offset = 0;
+			$parsed = array();
+			$data = null;
+			while ($offset < $datalength) {
+				$record_type = getid3_lib::BigEndian2Int(substr($atomData, $offset, 4));
+				$offset += 4;
+				$data_size_type = getid3_lib::BigEndian2Int(substr($atomData, $offset, 2));
+				$data_size = static::$exifTypeSizes[$data_size_type];
+				$offset += 2;
+				$data_count = getid3_lib::BigEndian2Int(substr($atomData, $offset, 2));
+				$offset += 2;
+				$data = array();
+
+				if ($pass === 0 && !in_array($record_type, $firstPassNeededTags, true)) {
+					$offset += $data_count * $data_size;
+					continue;
+				}
+
+				switch ($data_size_type) {
+					case self::EXIF_TYPE_UINT8: // 0x0001 = flag / unsigned byte   (size field *= 1-byte)
+						for ($i = 0; $i < $data_count; ++$i) {
+							$data[] = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
+						}
+						$offset += ($data_count * $data_size);
+						break;
+					case self::EXIF_TYPE_CHAR: // 0x0002 = char / ascii strings  (size field *= 1-byte)
+						$data = substr($atomData, $offset, $data_count * $data_size);
+						$offset += ($data_count * $data_size);
+						$data = rtrim($data, "\x00");
+						break;
+					case self::EXIF_TYPE_UINT16: // 0x0003 = DWORD+ / unsigned short (size field *= 2-byte), values are stored CDAB
+						for ($i = 0; $i < $data_count; ++$i) {
+							$data[] = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
+						}
+						$offset += ($data_count * $data_size);
+						break;
+					case self::EXIF_TYPE_UINT32: // 0x0004 = QWORD+ / unsigned long (size field *= 4-byte), values are stored EFGHABCD
+						// нужно проверить FrameCount
+						for ($i = 0; $i < $data_count; ++$i) {
+							$data[] = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
+						}
+						$offset += ($data_count * $data_size);
+						break;
+					case self::EXIF_TYPE_URATIONAL: // 0x0005 = float / unsigned rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+						for ($i = 0; $i < $data_count; ++$i) {
+							$numerator = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 0, 4));
+							$denomninator = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 4, 4));
+							if ($denomninator == 0) {
+								$data[] = false;
+							} else {
+								$data[] = (float)$numerator / $denomninator;
+							}
+						}
+						$offset += ($data_size * $data_count);
+						break;
+					case self::EXIF_TYPE_INT8: // 0x0006 = bytes / signed byte  (size field *= 1-byte)
+						// NOT TESTED
+						for ($i = 0; $i < $data_count; ++$i) {
+							$data[] = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size), false, true);
+						}
+						$offset += ($data_count * $data_size);
+						break;
+					case self::EXIF_TYPE_RAW: // 0x0007 = raw bytes  (size field *= 1-byte)
+						$data = substr($atomData, $offset, $data_count * $data_size);
+						$offset += ($data_count * $data_size);
+						break;
+					case self::EXIF_TYPE_INT16: // 0x0008 = signed short (size field *= 2-byte), values are stored as CDAB
+						for ($i = 0; $i < $data_count; ++$i) {
+							$value = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size));
+							if ($value >= 0x8000) {
+								$value -= 0x10000;
+							}
+							$data[] = $value;
+						}
+						$offset += ($data_count * $data_size);
+						break;
+					case self::EXIF_TYPE_INT32: // 0x0009 = signed long (size field *= 4-byte), values are stored as EFGHABCD
+						// NOT TESTED
+						for ($i = 0; $i < $data_count; ++$i) {
+							$data = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size), $data_size), false, true);
+						}
+						$offset += ($data_count * $data_size);
+						break;
+					case self::EXIF_TYPE_RATIONAL: // 0x000A = float / signed rational (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
+						// NOT TESTED
+						for ($i = 0; $i < $data_count; ++$i) {
+							$numerator = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 0, 4), false, true);
+							$denomninator = getid3_lib::BigEndian2Int(substr($atomData, $offset + ($i * $data_size) + 4, 4), false, true);
+							if ($denomninator == 0) {
+								$data[] = false;
+							} else {
+								$data[] = (float)$numerator / $denomninator;
+							}
+						}
+						$offset += ($data_size * $data_count);
+						if (count($data) == 1) {
+							$data = $data[0];
+						}
+						break;
+					default:
+						$this->getid3->warning('QuicktimeParseNikonNCTG()::unknown $data_size_type: ' . $data_size_type);
+						break 2;
+				}
+
+				if (is_array($data) && count($data) === 1) {
+					$data = $data[0];
+				}
+
+				switch ($record_type) {
+					case 0x00000002:
+						$model = $data;
+						break;
+					case 0x00000013: // FrameCount
+						if (is_array($data) && count($data) === 2 && $data[1] == 0) {
+							$data = $data[0];
+						}
+						break;
+					case 0x00000011: // CreateDate
+					case 0x00000012: // DateTimeOriginal
+					case 0x00001002: // NikonDateTime
+						$data = strtotime($data);
+						break;
+					case 0x00001013: // ElectronicVR
+						$data = (bool) $data;
+						break;
+					case 0x0110829a: // ExposureTime
+						// Print exposure time as a fraction
+						/** @var float $data */
+						if ($data < 0.25001 && $data > 0) {
+							$data = sprintf("1/%d", intval(0.5 + 1 / $data));
+						}
+						break;
+					case 0x01109204: // ExposureCompensation
+						$data = $this->printFraction($data);
+						break;
+					case 0x01108822: // ExposureProgram
+						$data = isset(static::$exposurePrograms[$data]) ? static::$exposurePrograms[$data] : $data;
+						break;
+					case 0x01109207: // MeteringMode
+						$data = isset(static::$meteringModes[$data]) ? static::$meteringModes[$data] : $data;
+						break;
+					case 0x0110a431: // SerialNumber
+						$serialNumber = $this->serialKey($data, $model);
+						break;
+					case 0x01200000: // GPSVersionID
+						$parsed['GPS']['computed']['version'] = 'v'.implode('.', $data);
+						break;
+					case 0x01200002: // GPSLatitude
+						if (is_array($data)) {
+							$direction_multiplier = ((isset($parsed['GPSLatitudeRef']) && ($parsed['GPSLatitudeRef'] === 'S')) ? -1 : 1);
+							$parsed['GPS']['computed']['latitude'] = $direction_multiplier * ($data[0] + ($data[1] / 60) + ($data[2] / 3600));
+						}
+						break;
+					case 0x01200004: // GPSLongitude
+						if (is_array($data)) {
+							$direction_multiplier = ((isset($parsed['GPSLongitudeRef']) && ($parsed['GPSLongitudeRef'] === 'W')) ? -1 : 1);
+							$parsed['GPS']['computed']['longitude'] = $direction_multiplier * ($data[0] + ($data[1] / 60) + ($data[2] / 3600));
+						}
+						break;
+					case 0x01200006:  // GPSAltitude
+						if (isset($parsed['GPSAltitudeRef'])) {
+							$direction_multiplier = (!empty($parsed['GPSAltitudeRef']) ? -1 : 1); // 0 = above sea level; 1 = below sea level
+							$parsed['GPS']['computed']['altitude'] = $direction_multiplier * $data;
+						}
+						break;
+					case 0x0120001d: // GPSDateStamp
+						if (isset($parsed['GPSTimeStamp']) && is_array($parsed['GPSTimeStamp']) && $data !== '') {
+							$explodedDate = explode(':', $data);
+							$parsed['GPS']['computed']['timestamp'] = gmmktime($parsed['GPSTimeStamp'][0], $parsed['GPSTimeStamp'][1], $parsed['GPSTimeStamp'][2], $explodedDate[1], $explodedDate[2], $explodedDate[0]);
+						}
+						break;
+					case 0x02000001: // MakerNoteVersion
+						$data = ltrim(substr($data, 0, 2) . '.' . substr($data, 2, 2), '0');
+						break;
+					case 0x0200001b: // CropHiSpeed
+						if (is_array($data) && count($data) === 7) {
+							$name = isset(static::$cropHiSpeeds[$data[0]]) ? static::$cropHiSpeeds[$data[0]] : sprintf('Unknown (%d)', $data[0]);
+							$data = array(
+								'Name' => $name,
+								'OriginalWidth' => $data[1],
+								'OriginalHeight' => $data[2],
+								'CroppedWidth' => $data[3],
+								'CroppedHeight' => $data[4],
+								'PixelXPosition' => $data[5],
+								'PixelYPosition' => $data[6],
+							);
+						}
+						break;
+					case 0x0200001e: // ColorSpace
+						$data = isset(static::$colorSpaces[$data]) ? static::$colorSpaces[$data] : $data;
+						break;
+					case 0x0200001f: // VRInfo
+						$data = array(
+							'VRInfoVersion' => substr($data, 0, 4),
+							'VibrationReduction' => isset(static::$vibrationReductions[ord(substr($data, 4, 1))])
+								? static::$vibrationReductions[ord(substr($data, 4, 1))]
+								: null,
+							'VRMode' => static::$VRModes[ord(substr($data, 6, 1))],
+						);
+						break;
+					case 0x02000022: // ActiveDLighting
+						$data = isset(static::$activeDLightnings[$data]) ? static::$activeDLightnings[$data] : $data;
+						break;
+					case 0x02000023: // PictureControlData
+						switch (substr($data, 0, 2)) {
+							case '01':
+								$data = array(
+									'PictureControlVersion' => substr($data, 0, 4),
+									'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"),
+									'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"),
+									//'?'                       =>                            substr($data, 44,  4),
+									'PictureControlAdjust' => static::$pictureControlDataAdjusts[ord(substr($data, 48, 1))],
+									'PictureControlQuickAdjust' => $this->printPC(ord(substr($data, 49, 1)) - 0x80),
+									'Sharpness' => $this->printPC(ord(substr($data, 50, 1)) - 0x80, 'No Sharpening', '%d'),
+									'Contrast' => $this->printPC(ord(substr($data, 51, 1)) - 0x80),
+									'Brightness' => $this->printPC(ord(substr($data, 52, 1)) - 0x80),
+									'Saturation' => $this->printPC(ord(substr($data, 53, 1)) - 0x80),
+									'HueAdjustment' => $this->printPC(ord(substr($data, 54, 1)) - 0x80, 'None'),
+									'FilterEffect' => static::$pictureControlDataFilterEffects[ord(substr($data, 55, 1))],
+									'ToningEffect' => static::$pictureControlDataToningEffects[ord(substr($data, 56, 1))],
+									'ToningSaturation' => $this->printPC(ord(substr($data, 57, 1)) - 0x80),
+								);
+								break;
+							case '02':
+								$data = array(
+									'PictureControlVersion' => substr($data, 0, 4),
+									'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"),
+									'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"),
+									//'?'                       =>                            substr($data, 44,  4),
+									'PictureControlAdjust' => static::$pictureControlDataAdjusts[ord(substr($data, 48, 1))],
+									'PictureControlQuickAdjust' => $this->printPC(ord(substr($data, 49, 1)) - 0x80),
+									'Sharpness' => $this->printPC(ord(substr($data, 51, 1)) - 0x80, 'None', '%.2f', 4),
+									'Clarity' => $this->printPC(ord(substr($data, 53, 1)) - 0x80, 'None', '%.2f', 4),
+									'Contrast' => $this->printPC(ord(substr($data, 55, 1)) - 0x80, 'None', '%.2f', 4),
+									'Brightness' => $this->printPC(ord(substr($data, 57, 1)) - 0x80, 'Normal', '%.2f', 4),
+									'Saturation' => $this->printPC(ord(substr($data, 59, 1)) - 0x80, 'None', '%.2f', 4),
+									'Hue' => $this->printPC(ord(substr($data, 61, 1)) - 0x80, 'None', '%.2f', 4),
+									'FilterEffect' => static::$pictureControlDataFilterEffects[ord(substr($data, 63, 1))],
+									'ToningEffect' => static::$pictureControlDataToningEffects[ord(substr($data, 64, 1))],
+									'ToningSaturation' => $this->printPC(ord(substr($data, 65, 1)) - 0x80, 'None', '%.2f', 4),
+								);
+								break;
+							case '03':
+								$data = array(
+									'PictureControlVersion' => substr($data, 0, 4),
+									'PictureControlName' => rtrim(substr($data, 8, 20), "\x00"),
+									'PictureControlBase' => rtrim(substr($data, 28, 20), "\x00"),
+									'PictureControlAdjust' => static::$pictureControlDataAdjusts[ord(substr($data, 54, 1))],
+									'PictureControlQuickAdjust' => $this->printPC(ord(substr($data, 55, 1)) - 0x80),
+									'Sharpness' => $this->printPC(ord(substr($data, 57, 1)) - 0x80, 'None', '%.2f', 4),
+									'MidRangeSharpness' => $this->printPC(ord(substr($data, 59, 1)) - 0x80, 'None', '%.2f', 4),
+									'Clarity' => $this->printPC(ord(substr($data, 61, 1)) - 0x80, 'None', '%.2f', 4),
+									'Contrast' => $this->printPC(ord(substr($data, 63, 1)) - 0x80, 'None', '%.2f', 4),
+									'Brightness' => $this->printPC(ord(substr($data, 65, 1)) - 0x80, 'Normal', '%.2f', 4),
+									'Saturation' => $this->printPC(ord(substr($data, 67, 1)) - 0x80, 'None', '%.2f', 4),
+									'Hue' => $this->printPC(ord(substr($data, 69, 1)) - 0x80, 'None', '%.2f', 4),
+									'FilterEffect' => static::$pictureControlDataFilterEffects[ord(substr($data, 71, 1))],
+									'ToningEffect' => static::$pictureControlDataToningEffects[ord(substr($data, 72, 1))],
+									'ToningSaturation' => $this->printPC(ord(substr($data, 73, 1)) - 0x80, 'None', '%.2f', 4),
+								);
+								break;
+							default:
+								$data = array(
+									'PictureControlVersion' => substr($data, 0, 4),
+								);
+								break;
+						}
+						break;
+					case 0x02000024: // WorldTime
+						// https://exiftool.org/TagNames/Nikon.html#WorldTime
+						// timezone is stored as offset from GMT in minutes
+						$timezone = getid3_lib::BigEndian2Int(substr($data, 0, 2));
+						if ($timezone & 0x8000) {
+							$timezone = 0 - (0x10000 - $timezone);
+						}
+						$hours = (int)abs($timezone / 60);
+						$minutes = abs($timezone) - $hours * 60;
+
+						$dst = (bool)getid3_lib::BigEndian2Int(substr($data, 2, 1));
+						switch (getid3_lib::BigEndian2Int(substr($data, 3, 1))) {
+							case 2:
+								$datedisplayformat = 'D/M/Y';
+								break;
+							case 1:
+								$datedisplayformat = 'M/D/Y';
+								break;
+							case 0:
+							default:
+								$datedisplayformat = 'Y/M/D';
+								break;
+						}
+
+						$data = array(
+							'timezone' => sprintf('%s%02d:%02d', $timezone >= 0 ? '+' : '-', $hours, $minutes),
+							'dst' => $dst,
+							'display' => $datedisplayformat
+						);
+						break;
+					case 0x02000025: // ISOInfo
+						$data = array(
+							'ISO' => (int)ceil(100 * pow(2, ord(substr($data, 0, 1)) / 12 - 5)),
+							'ISOExpansion' => static::$isoInfoExpansions[getid3_lib::BigEndian2Int(substr($data, 4, 2))],
+							'ISO2' => (int)ceil(100 * pow(2, ord(substr($data, 6, 1)) / 12 - 5)),
+							'ISOExpansion2' => static::$isoInfoExpansions2[getid3_lib::BigEndian2Int(substr($data, 10, 2))]
+						);
+						break;
+					case 0x0200002a: // VignetteControl
+						$data = isset(static::$vignetteControls[$data]) ? static::$vignetteControls[$data] : $data;
+						break;
+					case 0x0200002c: // UnknownInfo
+						$data = array(
+							'UnknownInfoVersion' => substr($data, 0, 4),
+						);
+						break;
+					case 0x02000032: // UnknownInfo2
+						$data = array(
+							'UnknownInfo2Version' => substr($data, 0, 4),
+						);
+						break;
+					case 0x02000039: // LocationInfo
+						$encoding = isset(static::$nikonTextEncodings[ord(substr($data, 4, 1))])
+							? static::$nikonTextEncodings[ord(substr($data, 4, 1))]
+							: null;
+						$data = array(
+							'LocationInfoVersion' => substr($data, 0, 4),
+							'TextEncoding' => $encoding,
+							'CountryCode' => trim(substr($data, 5, 3), "\x00"),
+							'POILevel' => ord(substr($data, 8, 1)),
+							'Location' => getid3_lib::iconv_fallback($encoding, $this->getid3->info['encoding'], substr($data, 9, 70)),
+						);
+						break;
+					case 0x02000083: // LensType
+						if ($data) {
+							$decodedBits = array(
+								'1' => (bool) (($data >> 4) & 1),
+								'MF' => (bool) (($data >> 0) & 1),
+								'D' => (bool) (($data >> 1) & 1),
+								'E' => (bool) (($data >> 6) & 1),
+								'G' => (bool) (($data >> 2) & 1),
+								'VR' => (bool) (($data >> 3) & 1),
+								'[7]' => (bool) (($data >> 7) & 1), // AF-P?
+								'[8]' => (bool) (($data >> 5) & 1) // FT-1?
+							);
+							if ($decodedBits['D'] === true && $decodedBits['G'] === true) {
+								$decodedBits['D'] = false;
+							}
+						} else {
+							$decodedBits = array('AF' => true);
+						}
+						$data = $decodedBits;
+						break;
+					case 0x0110a432: // LensInfo
+					case 0x02000084: // Lens
+						if (count($data) !== 4) {
+							break;
+						}
+
+						$value = $data[0];
+						if ($data[1] && $data[1] !== $data[0]) {
+							$value .= '-' . $data[1];
+						}
+						$value .= 'mm f/' . $data[2];
+						if ($data[3] && $data[3] !== $data[2]) {
+							$value .= '-' . $data[3];
+						}
+						$data = $value;
+						break;
+					case 0x02000087: // FlashMode
+						$data = isset(static::$flashModes[$data]) ? static::$flashModes[$data] : $data;
+						break;
+					case 0x02000098: // LensData
+						$version = substr($data, 0, 4);
+
+						switch ($version) {
+							case '0100':
+								$data = array(
+									'LensDataVersion'       => $version,
+									'LensIDNumber'          => ord(substr($data, 6, 1)),
+									'LensFStops'            => ord(substr($data, 7, 1)) / 12,
+									'MinFocalLength'        => 5 * pow(2, ord(substr($data, 8, 1)) / 24), // mm
+									'MaxFocalLength'        => 5 * pow(2, ord(substr($data, 9, 1)) / 24), // mm
+									'MaxApertureAtMinFocal' => pow(2, ord(substr($data, 10, 1)) / 24),
+									'MaxApertureAtMaxFocal' => pow(2, ord(substr($data, 11, 1)) / 24),
+									'MCUVersion'            => ord(substr($data, 12, 1)),
+								);
+								break;
+							case '0101':
+							case '0201':
+							case '0202':
+							case '0203':
+								$isEncrypted = $version !== '0101';
+								if ($isEncrypted) {
+									$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+								}
+
+								$data = array(
+									'LensDataVersion' => $version,
+									'ExitPupilPosition' => ord(substr($data, 4, 1)) > 0 ? 2048 / ord(substr($data, 4, 1)) : 0, // mm
+									'AFAperture' => pow(2, ord(substr($data, 5, 1)) / 24),
+									'FocusPosition' => '0x' . str_pad(strtoupper(dechex(ord(substr($data, 8, 1)))), 2, '0', STR_PAD_LEFT),
+									'FocusDistance' => 0.01 * pow(10, ord(substr($data, 9, 1)) / 40), // m
+									'FocalLength' => 5 * pow(2, ord(substr($data, 10, 1)) / 24), // mm
+									'LensIDNumber' => ord(substr($data, 11, 1)),
+									'LensFStops' => ord(substr($data, 12, 1)) / 12,
+									'MinFocalLength' => 5 * pow(2, ord(substr($data, 13, 1)) / 24), // mm
+									'MaxFocalLength' => 5 * pow(2, ord(substr($data, 14, 1)) / 24), // mm
+									'MaxApertureAtMinFocal' => pow(2, ord(substr($data, 15, 1)) / 24),
+									'MaxApertureAtMaxFocal' => pow(2, ord(substr($data, 16, 1)) / 24),
+									'MCUVersion' => ord(substr($data, 17, 1)),
+									'EffectiveMaxAperture' => pow(2, ord(substr($data, 18, 1)) / 24),
+								);
+								break;
+							case '0204':
+								$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+								$data = array(
+									'LensDataVersion' => $version,
+									'ExitPupilPosition' => ord(substr($data, 4, 1)) > 0 ? 2048 / ord(substr($data, 4, 1)) : 0, // mm
+									'AFAperture' => pow(2, ord(substr($data, 5, 1)) / 24),
+									'FocusPosition' => '0x' . str_pad(strtoupper(dechex(ord(substr($data, 8, 1)))), 2, '0', STR_PAD_LEFT),
+									'FocusDistance' => 0.01 * pow(10, ord(substr($data, 10, 1)) / 40), // m
+									'FocalLength' => 5 * pow(2, ord(substr($data, 11, 1)) / 24), // mm
+									'LensIDNumber' => ord(substr($data, 12, 1)),
+									'LensFStops' => ord(substr($data, 13, 1)) / 12,
+									'MinFocalLength' => 5 * pow(2, ord(substr($data, 14, 1)) / 24), // mm
+									'MaxFocalLength' => 5 * pow(2, ord(substr($data, 15, 1)) / 24), // mm
+									'MaxApertureAtMinFocal' => pow(2, ord(substr($data, 16, 1)) / 24),
+									'MaxApertureAtMaxFocal' => pow(2, ord(substr($data, 17, 1)) / 24),
+									'MCUVersion' => ord(substr($data, 18, 1)),
+									'EffectiveMaxAperture' => pow(2, ord(substr($data, 19, 1)) / 24),
+								);
+								break;
+							case '0400':
+							case '0401':
+								$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+								$data = array(
+									'LensDataVersion' => $version,
+									'LensModel' => substr($data, 394, 64),
+								);
+								break;
+							case '0402':
+								$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+								$data = array(
+									'LensDataVersion' => $version,
+									'LensModel' => substr($data, 395, 64),
+								);
+								break;
+							case '0403':
+								$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+								$data = array(
+									'LensDataVersion' => $version,
+									'LensModel' => substr($data, 684, 64),
+								);
+								break;
+							case '0800':
+							case '0801':
+								$data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+								$newData = array(
+									'LensDataVersion' => $version,
+								);
+
+								if (!preg_match('#^.\0+#s', substr($data, 3, 17))) {
+									$newData['ExitPupilPosition'] = ord(substr($data, 4, 1)) > 0 ? 2048 / ord(substr($data, 4, 1)) : 0; // mm
+									$newData['AFAperture'] = pow(2, ord(substr($data, 5, 1)) / 24);
+									$newData['FocusPosition'] = '0x' . str_pad(strtoupper(dechex(ord(substr($data, 9, 1)))), 2, '0', STR_PAD_LEFT);
+									$newData['FocusDistance'] = 0.01 * pow(10, ord(substr($data, 11, 1)) / 40); // m
+									$newData['FocalLength'] = 5 * pow(2, ord(substr($data, 12, 1)) / 24); // mm
+									$newData['LensIDNumber'] = ord(substr($data, 13, 1));
+									$newData['LensFStops'] = ord(substr($data, 14, 1)) / 12;
+									$newData['MinFocalLength'] = 5 * pow(2, ord(substr($data, 15, 1)) / 24); // mm
+									$newData['MaxFocalLength'] = 5 * pow(2, ord(substr($data, 16, 1)) / 24); // mm
+									$newData['MaxApertureAtMinFocal'] = pow(2, ord(substr($data, 17, 1)) / 24);
+									$newData['MaxApertureAtMaxFocal'] = pow(2, ord(substr($data, 18, 1)) / 24);
+									$newData['MCUVersion'] = ord(substr($data, 19, 1));
+									$newData['EffectiveMaxAperture'] = pow(2, ord(substr($data, 20, 1)) / 24);
+								}
+
+								if (!preg_match('#^.\0+#s', substr($data, 47, 17))) {
+									$newData['LensID'] = static::$NikkorZLensIDS[getid3_lib::LittleEndian2Int(substr($data, 48, 2))];
+									$newData['MaxAperture'] = pow(2, (getid3_lib::LittleEndian2Int(substr($data, 54, 2)) / 384 - 1));
+									$newData['FNumber'] = pow(2, (getid3_lib::LittleEndian2Int(substr($data, 56, 2)) / 384 - 1));
+									$newData['FocalLength'] = getid3_lib::LittleEndian2Int(substr($data, 60, 2)); // mm
+									$newData['FocusDistance'] = 0.01 * pow(10, ord(substr($data, 79, 1)) / 40); // m
+								}
+
+								$data = $newData;
+								break;
+							default:
+								// $data = $this->decryptLensInfo($data, $serialNumber, $shutterCount, 4);
+
+								$data = array(
+									'LensDataVersion' => $version,
+								);
+								break;
+						}
+						break;
+					case 0x020000a7: // ShutterCount
+						$shutterCount = $data;
+						break;
+					case 0x020000a8: // FlashInfo
+						$version = substr($data, 0, 4);
+
+						switch ($version) {
+							case '0100':
+							case '0101':
+								$data = array(
+									'FlashInfoVersion' => substr($data, 0, 4),
+									'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+									'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+									'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
+									'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
+									'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
+									'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 10, 1)) / 6) * 100)) : 0,
+									'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
+									'FlashFocalLength' => ord(substr($data, 11, 1)), // mm
+									'RepeatingFlashRate' => ord(substr($data, 12, 1)), // Hz
+									'RepeatingFlashCount' => ord(substr($data, 13, 1)),
+									'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 14, 1))],
+									'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 15, 1)) & 0x0F],
+									'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 16, 1)) & 0x0F],
+									'FlashGroupAOutput' => (ord(substr($data, 15, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 17, 1)) / 6) * 100)) : 0,
+									'FlashGroupACompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 17, 1), false, true) / 6),
+									'FlashGroupBOutput' => (ord(substr($data, 16, 1)) & 0xF0) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 18, 1)) / 6) * 100)) : 0,
+									'FlashGroupBCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 18, 1), false, true) / 6),
+								);
+								break;
+							case '0102':
+								$data = array(
+									'FlashInfoVersion' => substr($data, 0, 4),
+									'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+									'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+									'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
+									'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
+									'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
+									'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 10, 1)) / 6) * 100)) : 0,
+									'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
+									'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
+									'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
+									'RepeatingFlashCount' => ord(substr($data, 14, 1)),
+									'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 15, 1))],
+									'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 16, 1)) & 0x0F],
+									'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0xF0],
+									'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
+									'FlashGroupAOutput' => (ord(substr($data, 16, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 18, 1)) / 6) * 100)) : 0,
+									'FlashGroupACompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 18, 1), false, true) / 6),
+									'FlashGroupBOutput' => (ord(substr($data, 17, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 19, 1)) / 6) * 100)) : 0,
+									'FlashGroupBCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 19, 1), false, true) / 6),
+									'FlashGroupCOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 20, 1)) / 6) * 100)) : 0,
+									'FlashGroupCCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 20, 1), false, true) / 6),
+								);
+								break;
+							case '0103':
+							case '0104':
+							case '0105':
+								$data = array(
+									'FlashInfoVersion' => substr($data, 0, 4),
+									'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+									'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+									'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
+									'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
+									'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
+									'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 10, 1)) / 6) * 100)) : 0,
+									'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
+									'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
+									'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
+									'RepeatingFlashCount' => ord(substr($data, 14, 1)),
+									'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 15, 1))],
+									'FlashColorFilter' => static::$flashInfoColorFilters[ord(substr($data, 16, 1))],
+									'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
+									'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0xF0],
+									'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0x0F],
+									'FlashGroupAOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 19, 1)) / 6) * 100)) : 0,
+									'FlashGroupACompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 19, 1), false, true) / 6),
+									'FlashGroupBOutput' => (ord(substr($data, 18, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 20, 1)) / 6) * 100)) : 0,
+									'FlashGroupBCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 20, 1), false, true) / 6),
+									'FlashGroupCOutput' => (ord(substr($data, 18, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 21, 1)) / 6) * 100)) : 0,
+									'FlashGroupCCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 21, 1), false, true) / 6),
+									'ExternalFlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 27, 1), false, true) / 6),
+									'FlashExposureComp3' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 29, 1), false, true) / 6),
+									'FlashExposureComp4' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 39, 1), false, true) / 6),
+								);
+								break;
+							case '0106':
+								$data = array(
+									'FlashInfoVersion' => substr($data, 0, 4),
+									'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+									'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+									'ExternalFlashFlags' => $this->externalFlashFlagsLookup(ord(substr($data, 8, 1))),
+									'FlashCommanderMode' => (bool)(ord(substr($data, 9, 1)) & 0x80),
+									'FlashControlMode' => static::$flashInfoControlModes[ord(substr($data, 9, 1)) & 0x7F],
+									'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
+									'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
+									'RepeatingFlashCount' => ord(substr($data, 14, 1)),
+									'FlashGNDistance' => self::$flashInfoGNDistances[ord(substr($data, 15, 1))],
+									'FlashColorFilter' => static::$flashInfoColorFilters[ord(substr($data, 16, 1))],
+									'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
+									'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0xF0],
+									'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0x0F],
+									'FlashOutput' => (ord(substr($data, 9, 1)) & 0x7F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 39, 1)) / 6) * 100)) : 0,
+									'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 39, 1), false, true) / 6),
+									'FlashGroupAOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 40, 1)) / 6) * 100)) : 0,
+									'FlashGroupACompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 40, 1), false, true) / 6),
+									'FlashGroupBOutput' => (ord(substr($data, 18, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 41, 1)) / 6) * 100)) : 0,
+									'FlashGroupBCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 41, 1), false, true) / 6),
+									'FlashGroupCOutput' => (ord(substr($data, 18, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 42, 1)) / 6) * 100)) : 0,
+									'FlashGroupCCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 42, 1), false, true) / 6),
+								);
+								break;
+							case '0107':
+							case '0108':
+								$data = array(
+									'FlashInfoVersion' => substr($data, 0, 4),
+									'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+									'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+									'ExternalFlashZoomOverride' => (bool)(ord(substr($data, 8, 1)) & 0x80),
+									'ExternalFlashStatus' => static::$flashInfoExternalFlashStatuses[ord(substr($data, 8, 1)) & 0x01],
+									'ExternalFlashReadyState' => static::$flashInfoExternalFlashReadyStates[ord(substr($data, 9, 1)) & 0x07],
+									'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 10, 1), false, true) / 6),
+									'FlashFocalLength' => ord(substr($data, 12, 1)), // mm
+									'RepeatingFlashRate' => ord(substr($data, 13, 1)), // Hz
+									'RepeatingFlashCount' => ord(substr($data, 14, 1)),
+									'FlashGNDistance' => static::$flashInfoGNDistances[ord(substr($data, 15, 1))],
+									'FlashGroupAControlMode' => static::$flashInfoControlModes[ord(substr($data, 17, 1)) & 0x0F],
+									'FlashGroupBControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0xF0],
+									'FlashGroupCControlMode' => static::$flashInfoControlModes[ord(substr($data, 18, 1)) & 0x0F],
+									'FlashGroupAOutput' => (ord(substr($data, 17, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 40, 1)) / 6) * 100)) : 0,
+									'FlashGroupACompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 40, 1), false, true) / 6),
+									'FlashGroupBOutput' => (ord(substr($data, 18, 1)) & 0xF0) >= 0x60 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 41, 1)) / 6) * 100)) : 0,
+									'FlashGroupBCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 41, 1), false, true) / 6),
+									'FlashGroupCOutput' => (ord(substr($data, 18, 1)) & 0x0F) >= 0x06 ? sprintf('%.0f%%', pow(2, (-ord(substr($data, 42, 1)) / 6) * 100)) : 0,
+									'FlashGroupCCompensation' => sprintf('%+.1f', -getid3_lib::BigEndian2Int(substr($data, 42, 1), false, true) / 6),
+								);
+								break;
+							case '0300':
+								$data = array(
+									'FlashInfoVersion' => substr($data, 0, 4),
+									'FlashSource' => static::$flashInfoSources[ord(substr($data, 4, 1))],
+									'ExternalFlashFirmware' => $this->flashFirmwareLookup(ord(substr($data, 6, 1)), ord(substr($data, 7, 1))),
+									'FlashCompensation' => $this->printFraction(-getid3_lib::BigEndian2Int(substr($data, 27, 1), false, true) / 6),
+								);
+								break;
+							default:
+								$data = array(
+									'FlashInfoVersion' => substr($data, 0, 4),
+								);
+								break;
+						}
+						break;
+					case 0x020000b1: // HighISONoiseReduction
+						$data = isset(static::$highISONoiseReductions[$data]) ? static::$highISONoiseReductions[$data] : $data;
+						break;
+					case 0x020000b7: // AFInfo2
+						$avInfo2Version = substr($data, 0, 4);
+						$contrastDetectAF = ord(substr($data, 4, 1));
+						$phaseDetectAF = ord(substr($data, 6, 1));
+						$rows = array(
+							'AFInfo2Version' => $avInfo2Version,
+							'ContrastDetectAF' => static::$AFInfo2ContrastDetectAFChoices[$contrastDetectAF],
+							'AFAreaMode' => $contrastDetectAF
+								? static::$AFInfo2AFAreaModesWithContrastDetectAF[ord(substr($data, 5, 1))]
+								: static::$AFInfo2AFAreaModesWithoutContrastDetectAF[ord(substr($data, 5, 1))],
+							'PhaseDetectAF' => static::$AFInfo2PhaseDetectAFChoices[$phaseDetectAF],
+						);
+
+						if ($avInfo2Version === '0100') {
+							$rows['AFImageWidth']            = getid3_lib::BigEndian2Int(substr($data, 16, 2));
+							$rows['AFImageHeight']           = getid3_lib::BigEndian2Int(substr($data, 18, 2));
+							$rows['AFAreaXPosition']         = getid3_lib::BigEndian2Int(substr($data, 20, 2));
+							$rows['AFAreaYPosition']         = getid3_lib::BigEndian2Int(substr($data, 22, 2));
+							$rows['AFAreaWidth']             = getid3_lib::BigEndian2Int(substr($data, 24, 2));
+							$rows['AFAreaHeight']            = getid3_lib::BigEndian2Int(substr($data, 26, 2));
+							$rows['ContrastDetectAFInFocus'] = (bool)ord(substr($data, 28, 1));
+						} elseif (strpos($avInfo2Version, '03') === 0) {
+							$rows['AFImageWidth']  = getid3_lib::BigEndian2Int(substr($data, 42, 2));
+							$rows['AFImageHeight'] = getid3_lib::BigEndian2Int(substr($data, 44, 2));
+							if ($contrastDetectAF === 2
+								|| ($contrastDetectAF === 1 && $avInfo2Version === '0301')
+							) {
+								$rows['AFAreaXPosition'] = getid3_lib::BigEndian2Int(substr($data, 46, 2));
+								$rows['AFAreaYPosition'] = getid3_lib::BigEndian2Int(substr($data, 48, 2));
+							}
+							$rows['AFAreaWidth']  = getid3_lib::BigEndian2Int(substr($data, 50, 2));
+							$rows['AFAreaHeight'] = getid3_lib::BigEndian2Int(substr($data, 52, 2));
+						} elseif ($contrastDetectAF === 1 && $avInfo2Version === '0101') {
+							$rows['AFImageWidth']            = getid3_lib::BigEndian2Int(substr($data, 70, 2));
+							$rows['AFImageHeight']           = getid3_lib::BigEndian2Int(substr($data, 72, 2));
+							$rows['AFAreaXPosition']         = getid3_lib::BigEndian2Int(substr($data, 74, 2));
+							$rows['AFAreaYPosition']         = getid3_lib::BigEndian2Int(substr($data, 76, 2));
+							$rows['AFAreaWidth']             = getid3_lib::BigEndian2Int(substr($data, 78, 2));
+							$rows['AFAreaHeight']            = getid3_lib::BigEndian2Int(substr($data, 80, 2));
+							$rows['ContrastDetectAFInFocus'] = (bool) ord(substr($data, 82, 1));
+						}
+
+						$data = $rows;
+						break;
+					case 0x020000c3: // BarometerInfo
+						$data = array(
+							'BarometerInfoVersion' => substr($data, 0, 4),
+							'Altitude' => getid3_lib::BigEndian2Int(substr($data, 6, 4), false, true), // m
+						);
+						break;
+				}
+				$tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x' . str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT));
+
+				$parsed[$tag_name] = $data;
+			}
+		}
+
+		return $parsed;
+	}
+
+	/**
+	 * @param int $value          0x80 subtracted
+	 * @param string $normalName  'Normal' (0 value) string
+	 * @param string|null $format format string for numbers (default '%+d'), 3) v2 divisor
+	 * @param int|null $div
+	 *
+	 * @return string
+	 */
+	protected function printPC($value, $normalName = 'Normal', $format = '%+d', $div = 1) {
+		switch ($value) {
+			case 0:
+				return $normalName;
+			case 0x7f:
+				return 'n/a';
+			case -0x80:
+				return 'Auto';
+			case -0x7f:
+				return 'User';
+		}
+
+		return sprintf($format, $value / $div);
+	}
+
+	/**
+	 * @param int|float $value
+	 *
+	 * @return string
+	 */
+	protected function printFraction($value) {
+		if (!$value) {
+			return '0';
+		} elseif ((int) $value /$value > 0.999) {
+			return sprintf("%+d", (int) $value);
+		} elseif ((int) ($value * 2) / ($value * 2) > 0.999) {
+			return sprintf("%+d/2", (int) ($value * 2));
+		} elseif ((int) ($value * 3) / ($value * 3) > 0.999) {
+			return sprintf("%+d/3", (int) ($value * 3));
+		}
+
+		return sprintf("%+.3g", $value);
+	}
+
+	/**
+	 * @param int $firstByte
+	 * @param int $secondByte
+	 *
+	 * @return string
+	 */
+	protected function flashFirmwareLookup($firstByte, $secondByte)
+	{
+		$indexKey = $firstByte.' '.$secondByte;
+		if (isset(static::$flashInfoExternalFlashFirmwares[$indexKey])) {
+			return static::$flashInfoExternalFlashFirmwares[$indexKey];
+		}
+
+		return sprintf('%d.%.2d (Unknown model)', $firstByte, $secondByte);
+	}
+
+	/**
+	 * @param int $flags
+	 *
+	 * @return string[]|string
+	 */
+	protected function externalFlashFlagsLookup($flags)
+	{
+		$result = array();
+		foreach (static::$flashInfoExternalFlashFlags as $bit => $value) {
+			if (($flags >> $bit) & 1) {
+				$result[] = $value;
+			}
+		}
+
+		return $result;
+	}
+
+	/**
+	 * @param string $data
+	 * @param mixed|null $serialNumber
+	 * @param mixed|null $shutterCount
+	 * @param int $decryptStart
+	 *
+	 * @return false|string
+	 */
+	protected function decryptLensInfo(
+		$data,
+		$serialNumber = null,
+		$shutterCount = null,
+		$decryptStart = 0
+	) {
+		if (null === $serialNumber && null === $shutterCount) {
+			return false;
+		}
+
+		if (!is_int($serialNumber) || !is_int($shutterCount)) {
+			if (null !== $serialNumber && null !== $shutterCount) {
+				$this->getid3->warning('Invalid '.(!is_int($serialNumber) ? 'SerialNumber' : 'ShutterCount'));
+			} else {
+				$this->getid3->warning('Cannot decrypt Nikon tags because '.(null === $serialNumber ? 'SerialNumber' : 'ShutterCount').' key is not defined.');
+			}
+
+			return false;
+		}
+
+		$start = $decryptStart;
+		$length = strlen($data) - $start;
+
+		return $this->decrypt($data, $serialNumber, $shutterCount, $start, $length);
+	}
+
+	/**
+	 * Decrypt Nikon data block
+	 *
+	 * @param string $data
+	 * @param int $serialNumber
+	 * @param int $count
+	 * @param int $start
+	 * @param int $length
+	 *
+	 * @return string
+	 */
+	protected function decrypt($data, $serialNumber, $count, $start = 0, $length = null)
+	{
+		$maxLen = strlen($data) - $start;
+		if (null === $length || $length > $maxLen) {
+			$length = $maxLen;
+		}
+
+		if ($length <= 0) {
+			return $data;
+		}
+
+		$key = 0;
+		for ($i = 0; $i < 4; ++$i) {
+			$key ^= ($count >> ($i * 8)) & 0xFF;
+		}
+		$ci = static::$decodeTables[0][$serialNumber & 0xff];
+		$cj = static::$decodeTables[1][$key];
+		$ck = 0x60;
+		$unpackedData = array();
+		for ($i = $start; $i < $length + $start; ++$i) {
+			$cj = ($cj + $ci * $ck) & 0xff;
+			$ck = ($ck + 1) & 0xff;
+			$unpackedData[] = ord($data[$i]) ^ $cj;
+		}
+
+		$end = $start + $length;
+		$pre = $start ? substr($data, 0, $start) : '';
+		$post = $end < strlen($data) ? substr($data, $end) : '';
+
+		return $pre . implode('', array_map('chr', $unpackedData)) . $post;
+	}
+
+	/**
+	 * Get serial number for use as a decryption key
+	 *
+	 * @param string $serialNumber
+	 * @param string|null $model
+	 *
+	 * @return int|null
+	 */
+	protected function serialKey($serialNumber, $model = null)
+	{
+		if (empty($serialNumber) || ctype_digit($serialNumber)) {
+			return (int) $serialNumber;
+		}
+
+		if (null !== $model && preg_match('#\bD50$#', $model)) {
+			return 0x22;
+		}
+
+		return 0x60;
+	}
+}
diff --git a/lib/getid3/module.tag.xmp.php b/lib/getid3/module.tag.xmp.php
index db29601a3c68f5fcd7b6771436688d0d765bdbad..23c0d1abc1b7232cf44bafd921b19374ca95660e 100644
--- a/lib/getid3/module.tag.xmp.php
+++ b/lib/getid3/module.tag.xmp.php
@@ -81,7 +81,7 @@ class Image_XMP
 	* Reads all the JPEG header segments from an JPEG image file into an array
 	*
 	* @param string $filename - the filename of the JPEG file to read
-	* @return array|boolean  $headerdata - Array of JPEG header segments,
+	* @return array|false  $headerdata - Array of JPEG header segments,
 	*                        FALSE - if headers could not be read
 	*/
 	public function _get_jpeg_header_data($filename)
@@ -192,8 +192,8 @@ class Image_XMP
 	* Retrieves XMP information from an APP1 JPEG segment and returns the raw XML text as a string.
 	*
 	* @param string $filename - the filename of the JPEG file to read
-	* @return string|boolean $xmp_data - the string of raw XML text,
-	*                        FALSE - if an APP 1 XMP segment could not be found, or if an error occured
+	* @return string|false $xmp_data - the string of raw XML text,
+	*                        FALSE - if an APP 1 XMP segment could not be found, or if an error occurred
 	*/
 	public function _get_XMP_text($filename)
 	{
@@ -201,22 +201,25 @@ class Image_XMP
 		$jpeg_header_data = $this->_get_jpeg_header_data($filename);
 
 		//Cycle through the header segments
-		for ($i = 0; $i < count($jpeg_header_data); $i++)
-		{
-			// If we find an APP1 header,
-			if (strcmp($jpeg_header_data[$i]['SegName'], 'APP1') == 0)
-			{
-				// And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/\x00) ,
-				if (strncmp($jpeg_header_data[$i]['SegData'], 'http://ns.adobe.com/xap/1.0/'."\x00", 29) == 0)
-				{
-					// Found a XMP/RDF block
-					// Return the XMP text
-					$xmp_data = substr($jpeg_header_data[$i]['SegData'], 29);
-
-					return trim($xmp_data); // trim() should not be neccesary, but some files found in the wild with null-terminated block (known samples from Apple Aperture) causes problems elsewhere (see https://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153)
+		if (is_array($jpeg_header_data) && count($jpeg_header_data) > 0) {
+			foreach ($jpeg_header_data as $segment) {
+				// If we find an APP1 header,
+				if (strcmp($segment['SegName'], 'APP1') === 0) {
+					// And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/\x00) ,
+					if (strncmp($segment['SegData'], 'http://ns.adobe.com/xap/1.0/' . "\x00", 29) === 0) {
+						// Found a XMP/RDF block
+						// Return the XMP text
+						$xmp_data = substr($segment['SegData'], 29);
+
+						// trim() should not be necessary, but some files found in the wild with null-terminated block
+						// (known samples from Apple Aperture) causes problems elsewhere
+						// (see https://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153)
+						return trim($xmp_data);
+					}
 				}
 			}
 		}
+
 		return false;
 	}
 
@@ -225,7 +228,7 @@ class Image_XMP
 	* which contains all the XMP (XML) information.
 	*
 	* @param string $xmltext - a string containing the XMP data (XML) to be parsed
-	* @return array|boolean $xmp_array - an array containing all xmp details retrieved,
+	* @return array|false $xmp_array - an array containing all xmp details retrieved,
 	*                       FALSE - couldn't parse the XMP data.
 	*/
 	public function read_XMP_array_from_text($xmltext)
diff --git a/lib/getid3/write.id3v2.php b/lib/getid3/write.id3v2.php
index ae2313eda11db94421ea8e91dcbe7674999a20a4..7385a459815d8fab0c6be18fe7c865b242514189 100644
--- a/lib/getid3/write.id3v2.php
+++ b/lib/getid3/write.id3v2.php
@@ -1984,8 +1984,8 @@ class getid3_write_id3v2
 		if (is_array($var)) {
 			$keys = array_keys($var);
 			$all_num = true;
-			for ($i = 0; $i < count($keys); $i++) {
-				if (is_string($keys[$i])) {
+			foreach ($keys as $key) {
+				if (is_string($key)) {
 					return true;
 				}
 			}
diff --git a/lib/getid3/write.php b/lib/getid3/write.php
index 73eea6db84f150c954b15afd12f7d35b2c15259f..1167e0f76df3312c59f1e24da4769f98b78d0977 100644
--- a/lib/getid3/write.php
+++ b/lib/getid3/write.php
@@ -576,6 +576,7 @@ class getid3_writetags
 	public function FormatDataForID3v2($id3v2_majorversion) {
 		$tag_data_id3v2 = array();
 
+		$ID3v2_text_encoding_lookup    = array();
 		$ID3v2_text_encoding_lookup[2] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
 		$ID3v2_text_encoding_lookup[3] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
 		$ID3v2_text_encoding_lookup[4] = array('ISO-8859-1'=>0, 'UTF-16'=>1, 'UTF-16BE'=>2, 'UTF-8'=>3);
@@ -669,6 +670,7 @@ class getid3_writetags
 								if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'UTF-8')) {
 									do {
 										// if UTF-8 string does not include any characters above chr(127) then it is identical to ISO-8859-1
+										$value = (string) $value; // prevent warnings/errors if $value is a non-string (e.g. integer,float)
 										for ($i = 0; $i < strlen($value); $i++) {
 											if (ord($value[$i]) > 127) {
 												break 2;
@@ -753,6 +755,7 @@ class getid3_writetags
 	 * @return array
 	 */
 	public function FormatDataForReal() {
+		$tag_data_real              = array();
 		$tag_data_real['title']     = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE']    ) ? $this->tag_data['TITLE']     : array())));
 		$tag_data_real['artist']    = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST']   ) ? $this->tag_data['ARTIST']    : array())));
 		$tag_data_real['copyright'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COPYRIGHT']) ? $this->tag_data['COPYRIGHT'] : array())));
diff --git a/lib/getid3/write.real.php b/lib/getid3/write.real.php
index 38c0648c738b448c8cf911e26a55d0dd94d43d5b..492cc4bb56823e9cef8da7ee65862611990937d0 100644
--- a/lib/getid3/write.real.php
+++ b/lib/getid3/write.real.php
@@ -282,6 +282,7 @@ class getid3_write_real
 				fclose($fp_source);
 				return false;
 			}
+			$oldChunkInfo = array();
 			foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) {
 				$oldChunkInfo[$chunkarray['name']] = $chunkarray;
 			}