diff --git a/src/wp-includes/ID3/getid3.lib.php b/src/wp-includes/ID3/getid3.lib.php
index 76e2854..1931bb3 100644
--- a/src/wp-includes/ID3/getid3.lib.php
+++ b/src/wp-includes/ID3/getid3.lib.php
@@ -293,6 +293,9 @@ class getid3_lib
 		return self::BigEndian2Int(strrev($byteword), false, $signed);
 	}
 
+	public static function LittleEndian2Bin($byteword) {
+		return self::BigEndian2Bin(strrev($byteword));
+	}
 
 	public static function BigEndian2Bin($byteword) {
 		$binvalue = '';
@@ -414,6 +417,20 @@ class getid3_lib
 		return $newarray;
 	}
 
+	public static function flipped_array_merge_noclobber($array1, $array2) {
+		if (!is_array($array1) || !is_array($array2)) {
+			return false;
+		}
+		# naturally, this only works non-recursively
+		$newarray = array_flip($array1);
+		foreach (array_flip($array2) as $key => $val) {
+			if (!isset($newarray[$key])) {
+				$newarray[$key] = count($newarray);
+			}
+		}
+		return array_flip($newarray);
+	}
+
 
 	public static function ksort_recursive(&$theArray) {
 		ksort($theArray);
@@ -946,8 +963,20 @@ class getid3_lib
 			return $string;
 		}
 
+		// mb_convert_encoding() availble
+		if (function_exists('mb_convert_encoding')) {
+			if ($converted_string = @mb_convert_encoding($string, $out_charset, $in_charset)) {
+				switch ($out_charset) {
+					case 'ISO-8859-1':
+						$converted_string = rtrim($converted_string, "\x00");
+						break;
+				}
+				return $converted_string;
+			}
+			return $string;
+		}
 		// iconv() availble
-		if (function_exists('iconv')) {
+		else if (function_exists('iconv')) {
 			if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) {
 				switch ($out_charset) {
 					case 'ISO-8859-1':
@@ -963,7 +992,7 @@ class getid3_lib
 		}
 
 
-		// iconv() not available
+		// neither mb_convert_encoding or iconv() is available
 		static $ConversionFunctionList = array();
 		if (empty($ConversionFunctionList)) {
 			$ConversionFunctionList['ISO-8859-1']['UTF-8']    = 'iconv_fallback_iso88591_utf8';
@@ -985,7 +1014,7 @@ class getid3_lib
 			$ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)];
 			return self::$ConversionFunction($string);
 		}
-		throw new Exception('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset);
+		throw new Exception('PHP does not has mb_convert_encoding() or iconv() support - cannot convert from '.$in_charset.' to '.$out_charset);
 	}
 
 	public static function recursiveMultiByteCharString2HTML($data, $charset='ISO-8859-1') {
@@ -1006,38 +1035,38 @@ class getid3_lib
 		$string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string
 		$HTMLstring = '';
 
-		switch ($charset) {
+		switch (strtolower($charset)) {
 			case '1251':
 			case '1252':
 			case '866':
 			case '932':
 			case '936':
 			case '950':
-			case 'BIG5':
-			case 'BIG5-HKSCS':
+			case 'big5':
+			case 'big5-hkscs':
 			case 'cp1251':
 			case 'cp1252':
 			case 'cp866':
-			case 'EUC-JP':
-			case 'EUCJP':
-			case 'GB2312':
+			case 'euc-jp':
+			case 'eucjp':
+			case 'gb2312':
 			case 'ibm866':
-			case 'ISO-8859-1':
-			case 'ISO-8859-15':
-			case 'ISO8859-1':
-			case 'ISO8859-15':
-			case 'KOI8-R':
+			case 'iso-8859-1':
+			case 'iso-8859-15':
+			case 'iso8859-1':
+			case 'iso8859-15':
+			case 'koi8-r':
 			case 'koi8-ru':
 			case 'koi8r':
-			case 'Shift_JIS':
-			case 'SJIS':
+			case 'shift_jis':
+			case 'sjis':
 			case 'win-1251':
-			case 'Windows-1251':
-			case 'Windows-1252':
+			case 'windows-1251':
+			case 'windows-1252':
 				$HTMLstring = htmlentities($string, ENT_COMPAT, $charset);
 				break;
 
-			case 'UTF-8':
+			case 'utf-8':
 				$strlen = strlen($string);
 				for ($i = 0; $i < $strlen; $i++) {
 					$char_ord_val = ord($string{$i});
@@ -1065,7 +1094,7 @@ class getid3_lib
 				}
 				break;
 
-			case 'UTF-16LE':
+			case 'utf-16le':
 				for ($i = 0; $i < strlen($string); $i += 2) {
 					$charval = self::LittleEndian2Int(substr($string, $i, 2));
 					if (($charval >= 32) && ($charval <= 127)) {
@@ -1076,7 +1105,7 @@ class getid3_lib
 				}
 				break;
 
-			case 'UTF-16BE':
+			case 'utf-16be':
 				for ($i = 0; $i < strlen($string); $i += 2) {
 					$charval = self::BigEndian2Int(substr($string, $i, 2));
 					if (($charval >= 32) && ($charval <= 127)) {
@@ -1153,11 +1182,19 @@ class getid3_lib
 	public static function GetDataImageSize($imgData, &$imageinfo=array()) {
 		static $tempdir = '';
 		if (empty($tempdir)) {
+			if (function_exists('sys_get_temp_dir')) {
+				$tempdir = sys_get_temp_dir(); // https://github.com/JamesHeinrich/getID3/issues/52
+			}
+
 			// yes this is ugly, feel free to suggest a better way
-			require_once(dirname(__FILE__).'/getid3.php');
-			$getid3_temp = new getID3();
-			$tempdir = $getid3_temp->tempdir;
-			unset($getid3_temp);
+			if (include_once(dirname(__FILE__).'/getid3.php')) {
+				if ($getid3_temp = new getID3()) {
+					if ($getid3_temp_tempdir = $getid3_temp->tempdir) {
+						$tempdir = $getid3_temp_tempdir;
+					}
+					unset($getid3_temp, $getid3_temp_tempdir);
+				}
+			}
 		}
 		$GetDataImageSize = false;
 		if ($tempfilename = tempnam($tempdir, 'gI3')) {
@@ -1165,6 +1202,9 @@ class getid3_lib
 				fwrite($tmp, $imgData);
 				fclose($tmp);
 				$GetDataImageSize = @getimagesize($tempfilename, $imageinfo);
+				if (($GetDataImageSize === false) || !isset($GetDataImageSize[0]) || !isset($GetDataImageSize[1])) {
+					return false;
+				}
 				$GetDataImageSize['height'] = $GetDataImageSize[0];
 				$GetDataImageSize['width']  = $GetDataImageSize[1];
 			}
@@ -1237,10 +1277,14 @@ class getid3_lib
 							}
 							if (is_array($value) || empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) {
 								$value = (is_string($value) ? trim($value) : $value);
-								if (!is_numeric($key)) {
+								if (!is_int($key) && !ctype_digit($key)) {
 									$ThisFileInfo['comments'][$tagname][$key] = $value;
 								} else {
-									$ThisFileInfo['comments'][$tagname][]     = $value;
+									if (isset($ThisFileInfo['comments'][$tagname])) {
+										$ThisFileInfo['comments'][$tagname] = array($value);
+									} else {
+										$ThisFileInfo['comments'][$tagname][] = $value;
+									}
 								}
 							}
 						}
@@ -1248,6 +1292,18 @@ 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]);
+				}
+			}
+
 			// Copy to ['comments_html']
 			if (!empty($ThisFileInfo['comments'])) {
 				foreach ($ThisFileInfo['comments'] as $field => $values) {
diff --git a/src/wp-includes/ID3/getid3.php b/src/wp-includes/ID3/getid3.php
index 714fb02..7732c19 100644
--- a/src/wp-includes/ID3/getid3.php
+++ b/src/wp-includes/ID3/getid3.php
@@ -22,6 +22,9 @@ if (!defined('GETID3_INCLUDEPATH')) {
 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));
+}
 
 // attempt to define temp dir as something flexible but reliable
 $temp_dir = ini_get('upload_tmp_dir');
@@ -109,7 +112,7 @@ class getID3
 	protected $startup_error   = '';
 	protected $startup_warning = '';
 
-	const VERSION           = '1.9.9-20141121';
+	const VERSION           = '1.9.14-201706111222';
 	const FREAD_BUFFER_SIZE = 32768;
 
 	const ATTACHMENTS_NONE   = false;
@@ -120,19 +123,19 @@ class getID3
 
 		// Check memory
 		$this->memory_limit = ini_get('memory_limit');
-		if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) {
+		if (preg_match('#([0-9]+) ?M#i', $this->memory_limit, $matches)) {
 			// could be stored as "16M" rather than 16777216 for example
 			$this->memory_limit = $matches[1] * 1048576;
-		} elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
+		} elseif (preg_match('#([0-9]+) ?G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
 			// could be stored as "2G" rather than 2147483648 for example
 			$this->memory_limit = $matches[1] * 1073741824;
 		}
 		if ($this->memory_limit <= 0) {
 			// memory limits probably disabled
 		} elseif ($this->memory_limit <= 4194304) {
-			$this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini';
+			$this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'."\n";
 		} elseif ($this->memory_limit <= 12582912) {
-			$this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini';
+			$this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'."\n";
 		}
 
 		// Check safe_mode off
@@ -140,27 +143,30 @@ class getID3
 			$this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
 		}
 
-		if (intval(ini_get('mbstring.func_overload')) > 0) {
-			$this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.');
+		if (($mbstring_func_overload = ini_get('mbstring.func_overload')) && ($mbstring_func_overload & 0x02)) {
+			// http://php.net/manual/en/mbstring.overload.php
+			// "mbstring.func_overload in php.ini is a positive value that represents a combination of bitmasks specifying the categories of functions to be overloaded. It should be set to 1 to overload the mail() function. 2 for string functions, 4 for regular expression functions"
+			// getID3 cannot run when string functions are overloaded. It doesn't matter if mail() or ereg* functions are overloaded since getID3 does not use those.
+			$this->startup_error .= 'WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", getID3 cannot run with this setting (bitmask 2 (string functions) cannot be set). Recommended to disable entirely.'."\n";
 		}
 
 		// Check for magic_quotes_runtime
 		if (function_exists('get_magic_quotes_runtime')) {
 			if (get_magic_quotes_runtime()) {
-				return $this->startup_error('magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).');
+				$this->startup_error .= 'magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'."\n";
 			}
 		}
 
 		// Check for magic_quotes_gpc
 		if (function_exists('magic_quotes_gpc')) {
 			if (get_magic_quotes_gpc()) {
-				return $this->startup_error('magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).');
+				$this->startup_error .= 'magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'."\n";
 			}
 		}
 
 		// Load support library
 		if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
-			$this->startup_error .= 'getid3.lib.php is missing or corrupt';
+			$this->startup_error .= 'getid3.lib.php is missing or corrupt'."\n";
 		}
 
 		if ($this->option_max_2gb_check === null) {
@@ -179,7 +185,7 @@ class getID3
 			$helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path
 
 			if (!is_dir($helperappsdir)) {
-				$this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist';
+				$this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'."\n";
 			} elseif (strpos(realpath($helperappsdir), ' ') !== false) {
 				$DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir));
 				$path_so_far = array();
@@ -199,7 +205,7 @@ class getID3
 								}
 							}
 						} else {
-							$this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.';
+							$this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'."\n";
 						}
 					}
 					$path_so_far[] = $value;
@@ -209,6 +215,11 @@ class getID3
 			define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR);
 		}
 
+		if (!empty($this->startup_error)) {
+			echo $this->startup_error;
+			throw new getid3_exception($this->startup_error);
+		}
+
 		return true;
 	}
 
@@ -236,13 +247,15 @@ class getID3
 	}
 
 
-	public function openfile($filename) {
+	public function openfile($filename, $filesize=null) {
 		try {
 			if (!empty($this->startup_error)) {
 				throw new getid3_exception($this->startup_error);
 			}
 			if (!empty($this->startup_warning)) {
-				$this->warning($this->startup_warning);
+				foreach (explode("\n", $this->startup_warning) as $startup_warning) {
+					$this->warning($startup_warning);
+				}
 			}
 
 			// init result array and set parameters
@@ -252,12 +265,12 @@ class getID3
 			$this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false);
 
 			// remote files not supported
-			if (preg_match('/^(ht|f)tp:\/\//', $filename)) {
+			if (preg_match('#^(ht|f)tp://#', $filename)) {
 				throw new getid3_exception('Remote files are not supported - please copy the file locally first');
 			}
 
 			$filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
-			$filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename);
+			$filename = preg_replace('#(?<!gs:)('.preg_quote(DIRECTORY_SEPARATOR).'{2,})#', DIRECTORY_SEPARATOR, $filename);
 
 			// open local file
 			//if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see http://www.getid3.org/phpBB3/viewtopic.php?t=1720
@@ -280,7 +293,7 @@ class getID3
 				throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')');
 			}
 
-			$this->info['filesize'] = filesize($filename);
+			$this->info['filesize'] = (!is_null($filesize) ? $filesize : filesize($filename));
 			// set redundant parameters - might be needed in some include file
 			// filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion
 			$filename = str_replace('\\', '/', $filename);
@@ -288,6 +301,17 @@ class getID3
 			$this->info['filename']     = getid3_lib::mb_basename($filename);
 			$this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename'];
 
+			// set more parameters
+			$this->info['avdataoffset']        = 0;
+			$this->info['avdataend']           = $this->info['filesize'];
+			$this->info['fileformat']          = '';                // filled in later
+			$this->info['audio']['dataformat'] = '';                // filled in later, unset if not used
+			$this->info['video']['dataformat'] = '';                // filled in later, unset if not used
+			$this->info['tags']                = array();           // filled in later, unset if not used
+			$this->info['error']               = array();           // filled in later, unset if not used
+			$this->info['warning']             = array();           // filled in later, unset if not used
+			$this->info['comments']            = array();           // filled in later, unset if not used
+			$this->info['encoding']            = $this->encoding;   // required by id3v2 and iso modules - can be unset at the end if desired
 
 			// option_max_2gb_check
 			if ($this->option_max_2gb_check) {
@@ -314,18 +338,6 @@ class getID3
 				}
 			}
 
-			// set more parameters
-			$this->info['avdataoffset']        = 0;
-			$this->info['avdataend']           = $this->info['filesize'];
-			$this->info['fileformat']          = '';                // filled in later
-			$this->info['audio']['dataformat'] = '';                // filled in later, unset if not used
-			$this->info['video']['dataformat'] = '';                // filled in later, unset if not used
-			$this->info['tags']                = array();           // filled in later, unset if not used
-			$this->info['error']               = array();           // filled in later, unset if not used
-			$this->info['warning']             = array();           // filled in later, unset if not used
-			$this->info['comments']            = array();           // filled in later, unset if not used
-			$this->info['encoding']            = $this->encoding;   // required by id3v2 and iso modules - can be unset at the end if desired
-
 			return true;
 
 		} catch (Exception $e) {
@@ -335,9 +347,9 @@ class getID3
 	}
 
 	// public: analyze file
-	public function analyze($filename) {
+	public function analyze($filename, $filesize=null, $original_filename='') {
 		try {
-			if (!$this->openfile($filename)) {
+			if (!$this->openfile($filename, $filesize)) {
 				return $this->info;
 			}
 
@@ -382,7 +394,7 @@ class getID3
 			$formattest = fread($this->fp, 32774);
 
 			// determine format
-			$determined_format = $this->GetFileFormat($formattest, $filename);
+			$determined_format = $this->GetFileFormat($formattest, ($original_filename ? $original_filename : $filename));
 
 			// unable to determine file format
 			if (!$determined_format) {
@@ -419,14 +431,14 @@ class getID3
 				return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
 			}
 
-			// module requires iconv support
+			// module requires mb_convert_encoding/iconv support
 			// Check encoding/iconv support
-			if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) {
-				$errormessage = 'iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. ';
+			if (!empty($determined_format['iconv_req']) && !function_exists('mb_convert_encoding') && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) {
+				$errormessage = 'mb_convert_encoding() or iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. ';
 				if (GETID3_OS_ISWINDOWS) {
-					$errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32';
+					$errormessage .= 'PHP does not have mb_convert_encoding() or iconv() support. Please enable php_mbstring.dll / php_iconv.dll in php.ini, and copy php_mbstring.dll / iconv.dll from c:/php/dlls to c:/windows/system32';
 				} else {
-					$errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch';
+					$errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch';
 				}
 				return $this->error($errormessage);
 			}
@@ -561,7 +573,7 @@ class getID3
 
 				// AC-3   - audio      - Dolby AC-3 / Dolby Digital
 				'ac3'  => array(
-							'pattern'   => '^\x0B\x77',
+							'pattern'   => '^\\x0B\\x77',
 							'group'     => 'audio',
 							'module'    => 'ac3',
 							'mime_type' => 'audio/ac3',
@@ -579,7 +591,7 @@ class getID3
 /*
 				// AA   - audio       - Audible Audiobook
 				'aa'   => array(
-							'pattern'   => '^.{4}\x57\x90\x75\x36',
+							'pattern'   => '^.{4}\\x57\\x90\\x75\\x36',
 							'group'     => 'audio',
 							'module'    => 'aa',
 							'mime_type' => 'audio/audible',
@@ -587,7 +599,7 @@ class getID3
 */
 				// AAC  - audio       - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
 				'adts' => array(
-							'pattern'   => '^\xFF[\xF0-\xF1\xF8-\xF9]',
+							'pattern'   => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]',
 							'group'     => 'audio',
 							'module'    => 'aac',
 							'mime_type' => 'application/octet-stream',
@@ -597,7 +609,7 @@ class getID3
 
 				// AU   - audio       - NeXT/Sun AUdio (AU)
 				'au'   => array(
-							'pattern'   => '^\.snd',
+							'pattern'   => '^\\.snd',
 							'group'     => 'audio',
 							'module'    => 'au',
 							'mime_type' => 'audio/basic',
@@ -605,7 +617,7 @@ class getID3
 
 				// AMR  - audio       - Adaptive Multi Rate
 				'amr'  => array(
-							'pattern'   => '^\x23\x21AMR\x0A', // #!AMR[0A]
+							'pattern'   => '^\\x23\\x21AMR\\x0A', // #!AMR[0A]
 							'group'     => 'audio',
 							'module'    => 'amr',
 							'mime_type' => 'audio/amr',
@@ -621,15 +633,23 @@ class getID3
 
 				// BONK - audio       - Bonk v0.9+
 				'bonk' => array(
-							'pattern'   => '^\x00(BONK|INFO|META| ID3)',
+							'pattern'   => '^\\x00(BONK|INFO|META| ID3)',
 							'group'     => 'audio',
 							'module'    => 'bonk',
 							'mime_type' => 'audio/xmms-bonk',
 						),
 
+				// DSF  - audio       - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital
+				'dsf'  => array(
+							'pattern'   => '^DSD ',  // including trailing space: 44 53 44 20
+							'group'     => 'audio',
+							'module'    => 'dsf',
+							'mime_type' => 'audio/dsd',
+						),
+
 				// DSS  - audio       - Digital Speech Standard
 				'dss'  => array(
-							'pattern'   => '^[\x02-\x03]ds[s2]',
+							'pattern'   => '^[\\x02-\\x06]ds[s2]',
 							'group'     => 'audio',
 							'module'    => 'dss',
 							'mime_type' => 'application/octet-stream',
@@ -637,7 +657,7 @@ class getID3
 
 				// DTS  - audio       - Dolby Theatre System
 				'dts'  => array(
-							'pattern'   => '^\x7F\xFE\x80\x01',
+							'pattern'   => '^\\x7F\\xFE\\x80\\x01',
 							'group'     => 'audio',
 							'module'    => 'dts',
 							'mime_type' => 'audio/dts',
@@ -722,7 +742,7 @@ class getID3
 
 				// MPC  - audio       - Musepack / MPEGplus
 				'mpc'  => array(
-							'pattern'   => '^(MPCK|MP\+|[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0])',
+							'pattern'   => '^(MPCK|MP\\+|[\\x00\\x01\\x10\\x11\\x40\\x41\\x50\\x51\\x80\\x81\\x90\\x91\\xC0\\xC1\\xD0\\xD1][\\x20-\\x37][\\x00\\x20\\x40\\x60\\x80\\xA0\\xC0\\xE0])',
 							'group'     => 'audio',
 							'module'    => 'mpc',
 							'mime_type' => 'audio/x-musepack',
@@ -730,7 +750,7 @@ class getID3
 
 				// MP3  - audio       - MPEG-audio Layer 3 (very similar to AAC-ADTS)
 				'mp3'  => array(
-							'pattern'   => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\x0B\x10-\x1B\x20-\x2B\x30-\x3B\x40-\x4B\x50-\x5B\x60-\x6B\x70-\x7B\x80-\x8B\x90-\x9B\xA0-\xAB\xB0-\xBB\xC0-\xCB\xD0-\xDB\xE0-\xEB\xF0-\xFB]',
+							'pattern'   => '^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\x0B\\x10-\\x1B\\x20-\\x2B\\x30-\\x3B\\x40-\\x4B\\x50-\\x5B\\x60-\\x6B\\x70-\\x7B\\x80-\\x8B\\x90-\\x9B\\xA0-\\xAB\\xB0-\\xBB\\xC0-\\xCB\\xD0-\\xDB\\xE0-\\xEB\\xF0-\\xFB]',
 							'group'     => 'audio',
 							'module'    => 'mp3',
 							'mime_type' => 'audio/mpeg',
@@ -738,7 +758,7 @@ class getID3
 
 				// OFR  - audio       - OptimFROG
 				'ofr'  => array(
-							'pattern'   => '^(\*RIFF|OFR)',
+							'pattern'   => '^(\\*RIFF|OFR)',
 							'group'     => 'audio',
 							'module'    => 'optimfrog',
 							'mime_type' => 'application/octet-stream',
@@ -764,7 +784,7 @@ class getID3
 
 				// TTA  - audio       - TTA Lossless Audio Compressor (http://tta.corecodec.org)
 				'tta'  => array(
-							'pattern'   => '^TTA',  // could also be '^TTA(\x01|\x02|\x03|2|1)'
+							'pattern'   => '^TTA',  // could also be '^TTA(\\x01|\\x02|\\x03|2|1)'
 							'group'     => 'audio',
 							'module'    => 'tta',
 							'mime_type' => 'application/octet-stream',
@@ -799,7 +819,7 @@ class getID3
 
 				// ASF  - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
 				'asf'  => array(
-							'pattern'   => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C',
+							'pattern'   => '^\\x30\\x26\\xB2\\x75\\x8E\\x66\\xCF\\x11\\xA6\\xD9\\x00\\xAA\\x00\\x62\\xCE\\x6C',
 							'group'     => 'audio-video',
 							'module'    => 'asf',
 							'mime_type' => 'video/x-ms-asf',
@@ -816,7 +836,7 @@ class getID3
 
 				// FLV  - audio/video - FLash Video
 				'flv' => array(
-							'pattern'   => '^FLV\x01',
+							'pattern'   => '^FLV[\\x01]',
 							'group'     => 'audio-video',
 							'module'    => 'flv',
 							'mime_type' => 'video/x-flv',
@@ -824,7 +844,7 @@ class getID3
 
 				// MKAV - audio/video - Mastroka
 				'matroska' => array(
-							'pattern'   => '^\x1A\x45\xDF\xA3',
+							'pattern'   => '^\\x1A\\x45\\xDF\\xA3',
 							'group'     => 'audio-video',
 							'module'    => 'matroska',
 							'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
@@ -832,7 +852,7 @@ class getID3
 
 				// MPEG - audio/video - MPEG (Moving Pictures Experts Group)
 				'mpeg' => array(
-							'pattern'   => '^\x00\x00\x01(\xBA|\xB3)',
+							'pattern'   => '^\\x00\\x00\\x01[\\xB3\\xBA]',
 							'group'     => 'audio-video',
 							'module'    => 'mpeg',
 							'mime_type' => 'video/mpeg',
@@ -869,13 +889,13 @@ class getID3
 							'pattern'   => '^(RIFF|SDSS|FORM)',
 							'group'     => 'audio-video',
 							'module'    => 'riff',
-							'mime_type' => 'audio/x-wave',
+							'mime_type' => 'audio/x-wav',
 							'fail_ape'  => 'WARNING',
 						),
 
 				// Real - audio/video - RealAudio, RealVideo
 				'real' => array(
-							'pattern'   => '^(\\.RMF|\\.ra)',
+							'pattern'   => '^\\.(RMF|ra)',
 							'group'     => 'audio-video',
 							'module'    => 'real',
 							'mime_type' => 'audio/x-realaudio',
@@ -891,7 +911,7 @@ class getID3
 
 				// TS - audio/video - MPEG-2 Transport Stream
 				'ts' => array(
-							'pattern'   => '^(\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G".  Check for at least 10 packets matching this pattern
+							'pattern'   => '^(\\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G".  Check for at least 10 packets matching this pattern
 							'group'     => 'audio-video',
 							'module'    => 'ts',
 							'mime_type' => 'video/MP2T',
@@ -922,7 +942,7 @@ class getID3
 
 				// JPEG - still image - Joint Photographic Experts Group (JPEG)
 				'jpg'  => array(
-							'pattern'   => '^\xFF\xD8\xFF',
+							'pattern'   => '^\\xFF\\xD8\\xFF',
 							'group'     => 'graphic',
 							'module'    => 'jpg',
 							'mime_type' => 'image/jpeg',
@@ -932,7 +952,7 @@ class getID3
 
 				// PCD  - still image - Kodak Photo CD
 				'pcd'  => array(
-							'pattern'   => '^.{2048}PCD_IPI\x00',
+							'pattern'   => '^.{2048}PCD_IPI\\x00',
 							'group'     => 'graphic',
 							'module'    => 'pcd',
 							'mime_type' => 'image/x-photo-cd',
@@ -943,7 +963,7 @@ class getID3
 
 				// PNG  - still image - Portable Network Graphics (PNG)
 				'png'  => array(
-							'pattern'   => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A',
+							'pattern'   => '^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A',
 							'group'     => 'graphic',
 							'module'    => 'png',
 							'mime_type' => 'image/png',
@@ -954,7 +974,7 @@ class getID3
 
 				// SVG  - still image - Scalable Vector Graphics (SVG)
 				'svg'  => array(
-							'pattern'   => '(<!DOCTYPE svg PUBLIC |xmlns="http:\/\/www\.w3\.org\/2000\/svg")',
+							'pattern'   => '(<!DOCTYPE svg PUBLIC |xmlns="http://www\\.w3\\.org/2000/svg")',
 							'group'     => 'graphic',
 							'module'    => 'svg',
 							'mime_type' => 'image/svg+xml',
@@ -965,7 +985,7 @@ class getID3
 
 				// TIFF - still image - Tagged Information File Format (TIFF)
 				'tiff' => array(
-							'pattern'   => '^(II\x2A\x00|MM\x00\x2A)',
+							'pattern'   => '^(II\\x2A\\x00|MM\\x00\\x2A)',
 							'group'     => 'graphic',
 							'module'    => 'tiff',
 							'mime_type' => 'image/tiff',
@@ -976,7 +996,7 @@ class getID3
 
 				// EFAX - still image - eFax (TIFF derivative)
 				'efax'  => array(
-							'pattern'   => '^\xDC\xFE',
+							'pattern'   => '^\\xDC\\xFE',
 							'group'     => 'graphic',
 							'module'    => 'efax',
 							'mime_type' => 'image/efax',
@@ -1000,7 +1020,7 @@ class getID3
 
 				// RAR  - data        - RAR compressed data
 				'rar'  => array(
-							'pattern'   => '^Rar\!',
+							'pattern'   => '^Rar\\!',
 							'group'     => 'archive',
 							'module'    => 'rar',
 							'mime_type' => 'application/octet-stream',
@@ -1010,7 +1030,7 @@ class getID3
 
 				// SZIP - audio/data  - SZIP compressed data
 				'szip' => array(
-							'pattern'   => '^SZ\x0A\x04',
+							'pattern'   => '^SZ\\x0A\\x04',
 							'group'     => 'archive',
 							'module'    => 'szip',
 							'mime_type' => 'application/octet-stream',
@@ -1020,7 +1040,7 @@ class getID3
 
 				// TAR  - data        - TAR compressed data
 				'tar'  => array(
-							'pattern'   => '^.{100}[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20\x00]{12}[0-9\x20\x00]{12}',
+							'pattern'   => '^.{100}[0-9\\x20]{7}\\x00[0-9\\x20]{7}\\x00[0-9\\x20]{7}\\x00[0-9\\x20\\x00]{12}[0-9\\x20\\x00]{12}',
 							'group'     => 'archive',
 							'module'    => 'tar',
 							'mime_type' => 'application/x-tar',
@@ -1030,7 +1050,7 @@ class getID3
 
 				// GZIP  - data        - GZIP compressed data
 				'gz'  => array(
-							'pattern'   => '^\x1F\x8B\x08',
+							'pattern'   => '^\\x1F\\x8B\\x08',
 							'group'     => 'archive',
 							'module'    => 'gzip',
 							'mime_type' => 'application/x-gzip',
@@ -1040,7 +1060,7 @@ class getID3
 
 				// ZIP  - data         - ZIP compressed data
 				'zip'  => array(
-							'pattern'   => '^PK\x03\x04',
+							'pattern'   => '^PK\\x03\\x04',
 							'group'     => 'archive',
 							'module'    => 'zip',
 							'mime_type' => 'application/zip',
@@ -1053,7 +1073,7 @@ class getID3
 
 				// PAR2 - data        - Parity Volume Set Specification 2.0
 				'par2' => array (
-							'pattern'   => '^PAR2\x00PKT',
+							'pattern'   => '^PAR2\\x00PKT',
 							'group'     => 'misc',
 							'module'    => 'par2',
 							'mime_type' => 'application/octet-stream',
@@ -1063,7 +1083,7 @@ class getID3
 
 				// PDF  - data        - Portable Document Format
 				'pdf'  => array(
-							'pattern'   => '^\x25PDF',
+							'pattern'   => '^\\x25PDF',
 							'group'     => 'misc',
 							'module'    => 'pdf',
 							'mime_type' => 'application/pdf',
@@ -1073,7 +1093,7 @@ class getID3
 
 				// MSOFFICE  - data   - ZIP compressed data
 				'msoffice' => array(
-							'pattern'   => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
+							'pattern'   => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
 							'group'     => 'misc',
 							'module'    => 'msoffice',
 							'mime_type' => 'application/octet-stream',
@@ -1114,14 +1134,14 @@ class getID3
 		}
 
 
-		if (preg_match('#\.mp[123a]$#i', $filename)) {
+		if (preg_match('#\\.mp[123a]$#i', $filename)) {
 			// Too many mp3 encoders on the market put gabage in front of mpeg files
 			// use assume format on these if format detection failed
 			$GetFileFormatArray = $this->GetFileFormatArray();
 			$info = $GetFileFormatArray['mp3'];
 			$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
 			return $info;
-		} elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
+		} elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
 			// there's not really a useful consistent "magic" at the beginning of .cue files to identify them
 			// so until I think of something better, just go by filename if all other format checks fail
 			// and verify there's at least one instance of "TRACK xx AUDIO" in the file
@@ -1222,13 +1242,14 @@ class getID3
 					continue;
 				}
 
+				$this->CharConvert($this->info['tags'][$tag_name], $this->info[$comment_name]['encoding']);           // only copy gets converted!
+
 				if ($this->option_tags_html) {
 					foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
-						$this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $encoding);
+						$this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']);
 					}
 				}
 
-				$this->CharConvert($this->info['tags'][$tag_name], $encoding);           // only copy gets converted!
 			}
 
 		}
@@ -1352,8 +1373,8 @@ class getID3
 
 				if (!empty($VorbisCommentError)) {
 
-					$this->info['warning'][]         = 'Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError;
-					$this->info[$algorithm.'_data']  = false;
+					$this->warning('Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError);
+					$this->info[$algorithm.'_data'] = false;
 
 				} else {
 
@@ -1582,6 +1603,17 @@ class getID3
 		return true;
 	}
 
+    public static function is_writable ($filename) {
+        $ret = is_writable($filename);
+
+        if (!$ret) {
+            $perms = fileperms($filename);
+            $ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002);
+        }
+
+        return $ret;
+    }
+
 }
 
 
@@ -1661,7 +1693,23 @@ abstract class getid3_handler {
 		if (!getid3_lib::intValueSupported($pos)) {
 			throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10);
 		}
-		return fread($this->getid3->fp, $bytes);
+
+		//return fread($this->getid3->fp, $bytes);
+		/*
+		* http://www.getid3.org/phpBB3/viewtopic.php?t=1930
+		* "I found out that the root cause for the problem was how getID3 uses the PHP system function fread().
+		* It seems to assume that fread() would always return as many bytes as were requested.
+		* However, according the PHP manual (http://php.net/manual/en/function.fread.php), this is the case only with regular local files, but not e.g. with Linux pipes.
+		* The call may return only part of the requested data and a new call is needed to get more."
+		*/
+		$contents = '';
+		do {
+			$part = fread($this->getid3->fp, $bytes);
+			$partLength  = strlen($part);
+			$bytes      -= $partLength;
+			$contents   .= $part;
+		} while (($bytes > 0) && ($partLength > 0));
+		return $contents;
 	}
 
 	protected function fseek($bytes, $whence=SEEK_SET) {
@@ -1741,7 +1789,7 @@ abstract class getid3_handler {
 
 				// set up destination path
 				$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
-				if (!is_dir($dir) || !is_writable($dir)) { // check supplied directory
+				if (!is_dir($dir) || !getID3::is_writable($dir)) { // check supplied directory
 					throw new Exception('supplied path ('.$dir.') does not exist, or is not writable');
 				}
 				$dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : '');
diff --git a/src/wp-includes/ID3/module.audio-video.asf.php b/src/wp-includes/ID3/module.audio-video.asf.php
index ee61d7d..23d3a0e 100644
--- a/src/wp-includes/ID3/module.audio-video.asf.php
+++ b/src/wp-includes/ID3/module.audio-video.asf.php
@@ -266,14 +266,14 @@ class getid3_asf extends getid3_handler {
 					$offset += 16;
 					$thisfile_asf_headerextensionobject['reserved_1_guid']     = $this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']);
 					if ($thisfile_asf_headerextensionobject['reserved_1'] != GETID3_ASF_Reserved_1) {
-						$info['warning'][] = 'header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')';
+						$this->warning('header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')');
 						//return false;
 						break;
 					}
 					$thisfile_asf_headerextensionobject['reserved_2']          = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
 					$offset += 2;
 					if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) {
-						$info['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 ('.getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']).') does not match expected value of "6"');
 						//return false;
 						break;
 					}
@@ -316,7 +316,7 @@ class getid3_asf extends getid3_handler {
 					$offset += 16;
 					$thisfile_asf_codeclistobject['reserved_guid']             = $this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']);
 					if ($thisfile_asf_codeclistobject['reserved'] != $this->GUIDtoBytestring('86D15241-311D-11D0-A3A4-00A0C90348F6')) {
-						$info['warning'][] = 'codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}';
+						$this->warning('codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}');
 						//return false;
 						break;
 					}
@@ -349,7 +349,7 @@ class getid3_asf extends getid3_handler {
 						if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { // audio codec
 
 							if (strpos($thisfile_asf_codeclistobject_codecentries_current['description'], ',') === false) {
-								$info['warning'][] = '[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-seperated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"';
+								$this->warning('[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-separated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"');
 							} else {
 
 								list($AudioCodecBitrate, $AudioCodecFrequency, $AudioCodecChannels) = explode(',', $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']));
@@ -412,7 +412,7 @@ class getid3_asf extends getid3_handler {
 										break;
 
 									default:
-										$info['warning'][] = 'unknown frequency: "'.$AudioCodecFrequency.'" ('.$this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']).')';
+										$this->warning('unknown frequency: "'.$AudioCodecFrequency.'" ('.$this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']).')');
 										break;
 								}
 
@@ -458,7 +458,7 @@ class getid3_asf extends getid3_handler {
 					$offset += 16;
 					$thisfile_asf_scriptcommandobject['reserved_guid']        = $this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']);
 					if ($thisfile_asf_scriptcommandobject['reserved'] != $this->GUIDtoBytestring('4B1ACBE3-100B-11D0-A39B-00A0C90348F6')) {
-						$info['warning'][] = 'script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}';
+						$this->warning('script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}');
 						//return false;
 						break;
 					}
@@ -517,7 +517,7 @@ class getid3_asf extends getid3_handler {
 					$offset += 16;
 					$thisfile_asf_markerobject['reserved_guid']        = $this->BytestringToGUID($thisfile_asf_markerobject['reserved']);
 					if ($thisfile_asf_markerobject['reserved'] != $this->GUIDtoBytestring('4CFEDB20-75F6-11CF-9C0F-00A0C90349CB')) {
-						$info['warning'][] = 'marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved_1']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}';
+						$this->warning('marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved_1']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}');
 						break;
 					}
 					$thisfile_asf_markerobject['markers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
@@ -525,7 +525,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) {
-						$info['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 ('.getid3_lib::PrintHexBytes($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));
@@ -576,7 +576,7 @@ class getid3_asf extends getid3_handler {
 					$thisfile_asf_bitratemutualexclusionobject['reserved_guid']        = $this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']);
 					$offset += 16;
 					if (($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Bitrate) && ($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Unknown)) {
-						$info['warning'][] = 'bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or  "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}';
+						$this->warning('bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or  "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}');
 						//return false;
 						break;
 					}
@@ -637,7 +637,7 @@ class getid3_asf extends getid3_handler {
 							break;
 
 						default:
-							$info['warning'][] = 'error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['reserved']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or  "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}';
+							$this->warning('error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['reserved']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or  "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}');
 							//return false;
 							break;
 					}
@@ -761,7 +761,7 @@ class getid3_asf extends getid3_handler {
 								break;
 
 							default:
-								$info['warning'][] = 'extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')';
+								$this->warning('extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')');
 								//return false;
 								break;
 						}
@@ -962,9 +962,9 @@ class getid3_asf extends getid3_handler {
 				default:
 					// Implementations shall ignore any standard or non-standard object that they do not know how to handle.
 					if ($this->GUIDname($NextObjectGUIDtext)) {
-						$info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8);
+						$this->warning('unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8));
 					} else {
-						$info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8);
+						$this->warning('unknown GUID {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8));
 					}
 					$offset += ($NextObjectSize - 16 - 8);
 					break;
@@ -1183,7 +1183,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) {
-						$info['warning'][] = 'data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"';
+						$this->warning('data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"');
 						//return false;
 						break;
 					}
@@ -1319,9 +1319,9 @@ class getid3_asf extends getid3_handler {
 				default:
 					// Implementations shall ignore any standard or non-standard object that they do not know how to handle.
 					if ($this->GUIDname($NextObjectGUIDtext)) {
-						$info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8);
+						$this->warning('unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8));
 					} else {
-						$info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.($this->ftell() - 16 - 8);
+						$this->warning('unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.($this->ftell() - 16 - 8));
 					}
 					$this->fseek(($NextObjectSize - 16 - 8), SEEK_CUR);
 					break;
@@ -1405,7 +1405,7 @@ class getid3_asf extends getid3_handler {
 						break;
 
 					default:
-						$info['warning'][] = 'Unknown streamtype: [codec_list_object][codec_entries]['.$streamnumber.'][type_raw] == '.$streamdata['type_raw'];
+						$this->warning('Unknown streamtype: [codec_list_object][codec_entries]['.$streamnumber.'][type_raw] == '.$streamdata['type_raw']);
 						break;
 
 				}
@@ -1917,9 +1917,9 @@ class getid3_asf extends getid3_handler {
 				default:
 					$unhandled_sections++;
 					if ($this->GUIDname($thisObject['guid_text'])) {
-						$this->getid3->info['warning'][] = 'unhandled Header Extension Object GUID "'.$this->GUIDname($thisObject['guid_text']).'" {'.$thisObject['guid_text'].'} at offset '.($offset - 16 - 8);
+						$this->warning('unhandled Header Extension Object GUID "'.$this->GUIDname($thisObject['guid_text']).'" {'.$thisObject['guid_text'].'} at offset '.($offset - 16 - 8));
 					} else {
-						$this->getid3->info['warning'][] = 'unknown Header Extension Object GUID {'.$thisObject['guid_text'].'} in at offset '.($offset - 16 - 8);
+						$this->warning('unknown Header Extension Object GUID {'.$thisObject['guid_text'].'} in at offset '.($offset - 16 - 8));
 					}
 					break;
 			}
diff --git a/src/wp-includes/ID3/module.audio-video.flv.php b/src/wp-includes/ID3/module.audio-video.flv.php
index c5fbd4b..661c77c 100644
--- a/src/wp-includes/ID3/module.audio-video.flv.php
+++ b/src/wp-includes/ID3/module.audio-video.flv.php
@@ -93,7 +93,7 @@ class getid3_flv extends getid3_handler {
 		$TypeFlags                          = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
 
 		if ($info['flv']['header']['signature'] != self::magic) {
-			$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"';
+			$this->error('Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"');
 			unset($info['flv'], $info['fileformat']);
 			return false;
 		}
@@ -541,7 +541,7 @@ class AMFReader {
 			// Long string
 			default:
 				$value = '(unknown or unsupported data type)';
-			break;
+				break;
 		}
 
 		return $value;
diff --git a/src/wp-includes/ID3/module.audio-video.matroska.php b/src/wp-includes/ID3/module.audio-video.matroska.php
index f2cc5ac..825a22e 100644
--- a/src/wp-includes/ID3/module.audio-video.matroska.php
+++ b/src/wp-includes/ID3/module.audio-video.matroska.php
@@ -234,7 +234,7 @@ class getid3_matroska extends getid3_handler
 		try {
 			$this->parseEBML($info);
 		} catch (Exception $e) {
-			$info['error'][] = 'EBML parser: '.$e->getMessage();
+			$this->error('EBML parser: '.$e->getMessage());
 		}
 
 		// calculate playtime
@@ -330,11 +330,13 @@ class getid3_matroska extends getid3_handler
 								break;
 
 							case 'A_AC3':
+							case 'A_EAC3':
 							case 'A_DTS':
 							case 'A_MPEG/L3':
 							case 'A_MPEG/L2':
 							case 'A_FLAC':
-								getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']).'.php', __FILE__, true);
+								$module_dataformat = ($track_info['dataformat'] == 'mp2' ? 'mp3' : ($track_info['dataformat'] == 'eac3' ? 'ac3' : $track_info['dataformat']));
+								getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.$module_dataformat.'.php', __FILE__, true);
 
 								if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) {
 									$this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set');
@@ -352,7 +354,7 @@ class getid3_matroska extends getid3_handler
 								}
 
 								// analyze
-								$class = 'getid3_'.($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']);
+								$class = 'getid3_'.$module_dataformat;
 								$header_data_key = $track_info['dataformat'][0] == 'm' ? 'mpeg' : $track_info['dataformat'];
 								$getid3_audio = new $class($getid3_temp, __CLASS__);
 								if ($track_info['dataformat'] == 'flac') {
@@ -457,6 +459,7 @@ class getid3_matroska extends getid3_handler
 
 							default:
 								$this->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"');
+								break;
 						}
 
 						$info['audio']['streams'][] = $track_info;
@@ -524,6 +527,7 @@ class getid3_matroska extends getid3_handler
 
 							default:
 								$this->unhandledElement('header', __LINE__, $element_data);
+								break;
 						}
 
 						unset($element_data['offset'], $element_data['end']);
@@ -562,15 +566,20 @@ class getid3_matroska extends getid3_handler
 
 													default:
 														$this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry);												}
+														break;
 											}
-
-											if ($seek_entry['target_id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required
+											if (!isset($seek_entry['target_id'])) {
+												$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
 												$info['matroska']['seek'][] = $seek_entry;
 											}
 											break;
 
 										default:
 											$this->unhandledElement('seekhead', __LINE__, $seek_entry);
+											break;
 									}
 								}
 								break;
@@ -653,6 +662,7 @@ class getid3_matroska extends getid3_handler
 
 																default:
 																	$this->unhandledElement('track.video', __LINE__, $sub_subelement);
+																	break;
 															}
 														}
 														break;
@@ -678,6 +688,7 @@ class getid3_matroska extends getid3_handler
 
 																default:
 																	$this->unhandledElement('track.audio', __LINE__, $sub_subelement);
+																	break;
 															}
 														}
 														break;
@@ -713,6 +724,7 @@ class getid3_matroska extends getid3_handler
 
 																						default:
 																							$this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement);
+																							break;
 																					}
 																				}
 																				break;
@@ -736,24 +748,28 @@ class getid3_matroska extends getid3_handler
 
 																						default:
 																							$this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement);
+																							break;
 																					}
 																				}
 																				break;
 
 																			default:
 																				$this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement);
+																				break;
 																		}
 																	}
 																	break;
 
 																default:
 																	$this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement);
+																	break;
 															}
 														}
 														break;
 
 													default:
 														$this->unhandledElement('track', __LINE__, $subelement);
+														break;
 												}
 											}
 
@@ -762,6 +778,7 @@ class getid3_matroska extends getid3_handler
 
 										default:
 											$this->unhandledElement('tracks', __LINE__, $track_entry);
+											break;
 									}
 								}
 								break;
@@ -825,6 +842,7 @@ class getid3_matroska extends getid3_handler
 
 													default:
 														$this->unhandledElement('info.chaptertranslate', __LINE__, $sub_subelement);
+														break;
 												}
 											}
 											$info_entry[$subelement['id_name']] = $chaptertranslate_entry;
@@ -832,6 +850,7 @@ class getid3_matroska extends getid3_handler
 
 										default:
 											$this->unhandledElement('info', __LINE__, $subelement);
+											break;
 									}
 								}
 								$info['matroska']['info'][] = $info_entry;
@@ -868,6 +887,7 @@ class getid3_matroska extends getid3_handler
 
 																default:
 																	$this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement);
+																	break;
 															}
 														}
 														$cuepoint_entry[$sub_subelement['id_name']][] = $cuetrackpositions_entry;
@@ -879,6 +899,7 @@ class getid3_matroska extends getid3_handler
 
 													default:
 														$this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement);
+														break;
 												}
 											}
 											$cues_entry[] = $cuepoint_entry;
@@ -886,6 +907,7 @@ class getid3_matroska extends getid3_handler
 
 										default:
 											$this->unhandledElement('cues', __LINE__, $subelement);
+											break;
 									}
 								}
 								$info['matroska']['cues'] = $cues_entry;
@@ -927,6 +949,7 @@ class getid3_matroska extends getid3_handler
 
 																default:
 																	$this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement);
+																	break;
 															}
 														}
 														$tag_entry[$sub_subelement['id_name']] = $targets_entry;
@@ -938,6 +961,7 @@ class getid3_matroska extends getid3_handler
 
 													default:
 														$this->unhandledElement('tags.tag', __LINE__, $sub_subelement);
+														break;
 												}
 											}
 											$tags_entry[] = $tag_entry;
@@ -945,6 +969,7 @@ class getid3_matroska extends getid3_handler
 
 										default:
 											$this->unhandledElement('tags', __LINE__, $subelement);
+											break;
 									}
 								}
 								$info['matroska']['tags'] = $tags_entry;
@@ -985,6 +1010,7 @@ class getid3_matroska extends getid3_handler
 
 													default:
 														$this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement);
+														break;
 												}
 											}
 											$info['matroska']['attachments'][] = $attachedfile_entry;
@@ -992,6 +1018,7 @@ class getid3_matroska extends getid3_handler
 
 										default:
 											$this->unhandledElement('attachments', __LINE__, $subelement);
+											break;
 									}
 								}
 								break;
@@ -1051,6 +1078,7 @@ class getid3_matroska extends getid3_handler
 
 																			default:
 																				$this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement);
+																				break;
 																		}
 																	}
 																	$chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry;
@@ -1070,6 +1098,7 @@ class getid3_matroska extends getid3_handler
 
 																			default:
 																				$this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement);
+																				break;
 																		}
 																	}
 																	$chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry;
@@ -1077,6 +1106,7 @@ class getid3_matroska extends getid3_handler
 
 																default:
 																	$this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement);
+																	break;
 															}
 														}
 														$editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry;
@@ -1084,6 +1114,7 @@ class getid3_matroska extends getid3_handler
 
 													default:
 														$this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement);
+														break;
 												}
 											}
 											$info['matroska']['chapters'][] = $editionentry_entry;
@@ -1091,6 +1122,7 @@ class getid3_matroska extends getid3_handler
 
 										default:
 											$this->unhandledElement('chapters', __LINE__, $subelement);
+											break;
 									}
 								}
 								break;
@@ -1119,6 +1151,7 @@ class getid3_matroska extends getid3_handler
 
 													default:
 														$this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement);
+														break;
 												}
 											}
 											$cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks;
@@ -1149,6 +1182,7 @@ class getid3_matroska extends getid3_handler
 
 													default:
 														$this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement);
+														break;
 												}
 											}
 											$cluster_entry[$subelement['id_name']][] = $cluster_block_group;
@@ -1160,6 +1194,7 @@ class getid3_matroska extends getid3_handler
 
 										default:
 											$this->unhandledElement('cluster', __LINE__, $subelement);
+											break;
 									}
 									$this->current_offset = $subelement['end'];
 								}
@@ -1181,12 +1216,14 @@ class getid3_matroska extends getid3_handler
 
 							default:
 								$this->unhandledElement('segment', __LINE__, $element_data);
+								break;
 						}
 					}
 					break;
 
 				default:
 					$this->unhandledElement('root', __LINE__, $top_element);
+					break;
 			}
 		}
 	}
@@ -1339,6 +1376,7 @@ class getid3_matroska extends getid3_handler
 
 				default:
 					$this->unhandledElement('tag.simpletag', __LINE__, $element);
+					break;
 			}
 		}
 
@@ -1490,6 +1528,7 @@ class getid3_matroska extends getid3_handler
 			$CodecIDlist['A_AAC']            = 'aac';
 			$CodecIDlist['A_AAC/MPEG2/LC']   = 'aac';
 			$CodecIDlist['A_AC3']            = 'ac3';
+			$CodecIDlist['A_EAC3']           = 'eac3';
 			$CodecIDlist['A_DTS']            = 'dts';
 			$CodecIDlist['A_FLAC']           = 'flac';
 			$CodecIDlist['A_MPEG/L1']        = 'mp1';
diff --git a/src/wp-includes/ID3/module.audio-video.quicktime.php b/src/wp-includes/ID3/module.audio-video.quicktime.php
index 482c091..6f29ca4 100644
--- a/src/wp-includes/ID3/module.audio-video.quicktime.php
+++ b/src/wp-includes/ID3/module.audio-video.quicktime.php
@@ -35,10 +35,10 @@ class getid3_quicktime extends getid3_handler
 
 		$offset      = 0;
 		$atomcounter = 0;
-		$atom_data_read_buffer_size = ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 2) : $this->getid3->option_fread_buffer_size * 1024); // allow [default: 32MB] if PHP configured with no memory_limit
+		$atom_data_read_buffer_size = max($this->getid3->option_fread_buffer_size * 1024, ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 4) : 1024)); // set read buffer to 25% of PHP memory limit (if one is specified), otherwise use option_fread_buffer_size [default: 32MB]
 		while ($offset < $info['avdataend']) {
 			if (!getid3_lib::intValueSupported($offset)) {
-				$info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions';
+				$this->error('Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions');
 				break;
 			}
 			$this->fseek($offset);
@@ -57,7 +57,7 @@ class getid3_quicktime extends getid3_handler
 			$info['quicktime'][$atomname]['offset'] = $offset;
 
 			if (($offset + $atomsize) > $info['avdataend']) {
-				$info['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)';
+				$this->error('Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)');
 				return false;
 			}
 
@@ -81,6 +81,81 @@ class getid3_quicktime extends getid3_handler
 			unset($info['avdataend_tmp']);
 		}
 
+		if (!empty($info['quicktime']['comments']['chapters']) && is_array($info['quicktime']['comments']['chapters']) && (count($info['quicktime']['comments']['chapters']) > 0)) {
+			$durations = $this->quicktime_time_to_sample_table($info);
+			for ($i = 0; $i < count($info['quicktime']['comments']['chapters']); $i++) {
+				$bookmark = array();
+				$bookmark['title'] = $info['quicktime']['comments']['chapters'][$i];
+				if (isset($durations[$i])) {
+					$bookmark['duration_sample'] = $durations[$i]['sample_duration'];
+					if ($i > 0) {
+						$bookmark['start_sample'] = $info['quicktime']['bookmarks'][($i - 1)]['start_sample'] + $info['quicktime']['bookmarks'][($i - 1)]['duration_sample'];
+					} else {
+						$bookmark['start_sample'] = 0;
+					}
+					if ($time_scale = $this->quicktime_bookmark_time_scale($info)) {
+						$bookmark['duration_seconds'] = $bookmark['duration_sample'] / $time_scale;
+						$bookmark['start_seconds']    = $bookmark['start_sample']    / $time_scale;
+					}
+				}
+				$info['quicktime']['bookmarks'][] = $bookmark;
+			}
+		}
+
+		if (isset($info['quicktime']['temp_meta_key_names'])) {
+			unset($info['quicktime']['temp_meta_key_names']);
+		}
+
+		if (!empty($info['quicktime']['comments']['location.ISO6709'])) {
+			// https://en.wikipedia.org/wiki/ISO_6709
+			foreach ($info['quicktime']['comments']['location.ISO6709'] as $ISO6709string) {
+				$latitude  = false;
+				$longitude = false;
+				$altitude  = false;
+				if (preg_match('#^([\\+\\-])([0-9]{2}|[0-9]{4}|[0-9]{6})(\\.[0-9]+)?([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?(([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?)?/$#', $ISO6709string, $matches)) {
+					@list($dummy, $lat_sign, $lat_deg, $lat_deg_dec, $lon_sign, $lon_deg, $lon_deg_dec, $dummy, $alt_sign, $alt_deg, $alt_deg_dec) = $matches;
+
+					if (strlen($lat_deg) == 2) {        // [+-]DD.D
+						$latitude = floatval(ltrim($lat_deg, '0').$lat_deg_dec);
+					} elseif (strlen($lat_deg) == 4) {  // [+-]DDMM.M
+						$latitude = floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0').$lat_deg_dec / 60);
+					} elseif (strlen($lat_deg) == 6) {  // [+-]DDMMSS.S
+						$latitude = floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec / 3600);
+					}
+
+					if (strlen($lon_deg) == 3) {        // [+-]DDD.D
+						$longitude = floatval(ltrim($lon_deg, '0').$lon_deg_dec);
+					} elseif (strlen($lon_deg) == 5) {  // [+-]DDDMM.M
+						$longitude = floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0').$lon_deg_dec / 60);
+					} elseif (strlen($lon_deg) == 7) {  // [+-]DDDMMSS.S
+						$longitude = floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec / 3600);
+					}
+
+					if (strlen($alt_deg) == 3) {        // [+-]DDD.D
+						$altitude = floatval(ltrim($alt_deg, '0').$alt_deg_dec);
+					} elseif (strlen($alt_deg) == 5) {  // [+-]DDDMM.M
+						$altitude = floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0').$alt_deg_dec / 60);
+					} elseif (strlen($alt_deg) == 7) {  // [+-]DDDMMSS.S
+						$altitude = floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec / 3600);
+					}
+
+					if ($latitude !== false) {
+						$info['quicktime']['comments']['gps_latitude'][]  = (($lat_sign == '-') ? -1 : 1) * floatval($latitude);
+					}
+					if ($longitude !== false) {
+						$info['quicktime']['comments']['gps_longitude'][] = (($lon_sign == '-') ? -1 : 1) * floatval($longitude);
+					}
+					if ($altitude !== false) {
+						$info['quicktime']['comments']['gps_altitude'][]  = (($alt_sign == '-') ? -1 : 1) * floatval($altitude);
+					}
+				}
+				if ($latitude === false) {
+					$this->warning('location.ISO6709 string not parsed correctly: "'.$ISO6709string.'", please submit as a bug');
+				}
+				break;
+			}
+		}
+
 		if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) {
 			$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
 		}
@@ -98,10 +173,14 @@ class getid3_quicktime extends getid3_handler
 				}
 			}
 		}
-		if (($info['audio']['dataformat'] == 'mp4') && empty($info['video']['resolution_x'])) {
+		if ($info['audio']['dataformat'] == 'mp4') {
 			$info['fileformat'] = 'mp4';
-			$info['mime_type']  = 'audio/mp4';
-			unset($info['video']['dataformat']);
+			if (empty($info['video']['resolution_x'])) {
+				$info['mime_type']  = 'audio/mp4';
+				unset($info['video']['dataformat']);
+			} else {
+				$info['mime_type']  = 'video/mp4';
+			}
 		}
 
 		if (!$this->ReturnAtomData) {
@@ -120,6 +199,7 @@ class getid3_quicktime extends getid3_handler
 
 	public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
 		// http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm
+		// https://code.google.com/p/mp4v2/wiki/iTunesMetadata
 
 		$info = &$this->getid3->info;
 
@@ -222,81 +302,88 @@ class getid3_quicktime extends getid3_handler
 				break;
 
 
+			case "\xA9".'alb': // ALBum
+			case "\xA9".'ART': //
+			case "\xA9".'art': // ARTist
+			case "\xA9".'aut': //
+			case "\xA9".'cmt': // CoMmenT
+			case "\xA9".'com': // COMposer
+			case "\xA9".'cpy': //
+			case "\xA9".'day': // content created year
+			case "\xA9".'dir': //
+			case "\xA9".'ed1': //
+			case "\xA9".'ed2': //
+			case "\xA9".'ed3': //
+			case "\xA9".'ed4': //
+			case "\xA9".'ed5': //
+			case "\xA9".'ed6': //
+			case "\xA9".'ed7': //
+			case "\xA9".'ed8': //
+			case "\xA9".'ed9': //
+			case "\xA9".'enc': //
+			case "\xA9".'fmt': //
+			case "\xA9".'gen': // GENre
+			case "\xA9".'grp': // GRouPing
+			case "\xA9".'hst': //
+			case "\xA9".'inf': //
+			case "\xA9".'lyr': // LYRics
+			case "\xA9".'mak': //
+			case "\xA9".'mod': //
+			case "\xA9".'nam': // full NAMe
+			case "\xA9".'ope': //
+			case "\xA9".'PRD': //
+			case "\xA9".'prf': //
+			case "\xA9".'req': //
+			case "\xA9".'src': //
+			case "\xA9".'swr': //
+			case "\xA9".'too': // encoder
+			case "\xA9".'trk': // TRacK
+			case "\xA9".'url': //
+			case "\xA9".'wrn': //
+			case "\xA9".'wrt': // WRiTer
+			case '----': // itunes specific
 			case 'aART': // Album ARTist
+			case 'akID': // iTunes store account type
+			case 'apID': // Purchase Account
+			case 'atID': //
 			case 'catg': // CaTeGory
+			case 'cmID': //
+			case 'cnID': //
 			case 'covr': // COVeR artwork
 			case 'cpil': // ComPILation
 			case 'cprt': // CoPyRighT
 			case 'desc': // DESCription
 			case 'disk': // DISK number
 			case 'egid': // Episode Global ID
+			case 'geID': //
 			case 'gnre': // GeNRE
+			case 'hdvd': // HD ViDeo
 			case 'keyw': // KEYWord
-			case 'ldes':
+			case 'ldes': // Long DEScription
 			case 'pcst': // PodCaST
 			case 'pgap': // GAPless Playback
+			case 'plID': //
 			case 'purd': // PURchase Date
 			case 'purl': // Podcast URL
-			case 'rati':
-			case 'rndu':
-			case 'rpdu':
+			case 'rati': //
+			case 'rndu': //
+			case 'rpdu': //
 			case 'rtng': // RaTiNG
-			case 'stik':
+			case 'sfID': // iTunes store country
+			case 'soaa': // SOrt Album Artist
+			case 'soal': // SOrt ALbum
+			case 'soar': // SOrt ARtist
+			case 'soco': // SOrt COmposer
+			case 'sonm': // SOrt NaMe
+			case 'sosn': // SOrt Show Name
+			case 'stik': //
 			case 'tmpo': // TeMPO (BPM)
 			case 'trkn': // TRacK Number
+			case 'tven': // tvEpisodeID
 			case 'tves': // TV EpiSode
 			case 'tvnn': // TV Network Name
 			case 'tvsh': // TV SHow Name
 			case 'tvsn': // TV SeasoN
-			case 'akID': // iTunes store account type
-			case 'apID':
-			case 'atID':
-			case 'cmID':
-			case 'cnID':
-			case 'geID':
-			case 'plID':
-			case 'sfID': // iTunes store country
-			case "\xA9".'alb': // ALBum
-			case "\xA9".'art': // ARTist
-			case "\xA9".'ART':
-			case "\xA9".'aut':
-			case "\xA9".'cmt': // CoMmenT
-			case "\xA9".'com': // COMposer
-			case "\xA9".'cpy':
-			case "\xA9".'day': // content created year
-			case "\xA9".'dir':
-			case "\xA9".'ed1':
-			case "\xA9".'ed2':
-			case "\xA9".'ed3':
-			case "\xA9".'ed4':
-			case "\xA9".'ed5':
-			case "\xA9".'ed6':
-			case "\xA9".'ed7':
-			case "\xA9".'ed8':
-			case "\xA9".'ed9':
-			case "\xA9".'enc':
-			case "\xA9".'fmt':
-			case "\xA9".'gen': // GENre
-			case "\xA9".'grp': // GRouPing
-			case "\xA9".'hst':
-			case "\xA9".'inf':
-			case "\xA9".'lyr': // LYRics
-			case "\xA9".'mak':
-			case "\xA9".'mod':
-			case "\xA9".'nam': // full NAMe
-			case "\xA9".'ope':
-			case "\xA9".'PRD':
-			case "\xA9".'prd':
-			case "\xA9".'prf':
-			case "\xA9".'req':
-			case "\xA9".'src':
-			case "\xA9".'swr':
-			case "\xA9".'too': // encoder
-			case "\xA9".'trk': // TRacK
-			case "\xA9".'url':
-			case "\xA9".'wrn':
-			case "\xA9".'wrt': // WRiTer
-			case '----': // itunes specific
 				if ($atom_parent == 'udta') {
 					// User data atom handler
 					$atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
@@ -318,7 +405,7 @@ class getid3_quicktime extends getid3_handler
 							$boxsmalltype =                           substr($atom_data, $atomoffset + 2, 2);
 							$boxsmalldata =                           substr($atom_data, $atomoffset + 4, $boxsmallsize);
 							if ($boxsmallsize <= 1) {
-								$info['warning'][] = 'Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset);
+								$this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
 								$atom_structure['data'] = null;
 								$atomoffset = strlen($atom_data);
 								break;
@@ -328,7 +415,7 @@ class getid3_quicktime extends getid3_handler
 									$atom_structure['data'] = $boxsmalldata;
 									break;
 								default:
-									$info['warning'][] = 'Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset;
+									$this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset);
 									$atom_structure['data'] = $atom_data;
 									break;
 							}
@@ -340,7 +427,7 @@ class getid3_quicktime extends getid3_handler
 							$boxtype =                           substr($atom_data, $atomoffset + 4, 4);
 							$boxdata =                           substr($atom_data, $atomoffset + 8, $boxsize - 8);
 							if ($boxsize <= 1) {
-								$info['warning'][] = 'Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset);
+								$this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
 								$atom_structure['data'] = null;
 								$atomoffset = strlen($atom_data);
 								break;
@@ -361,17 +448,21 @@ class getid3_quicktime extends getid3_handler
 										case 21: // tmpo/cpil flag
 											switch ($atomname) {
 												case 'cpil':
+												case 'hdvd':
 												case 'pcst':
 												case 'pgap':
+													// 8-bit integer (boolean)
 													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
 													break;
 
 												case 'tmpo':
+													// 16-bit integer
 													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2));
 													break;
 
 												case 'disk':
 												case 'trkn':
+													// binary
 													$num       = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2));
 													$num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2));
 													$atom_structure['data']  = empty($num) ? '' : $num;
@@ -379,21 +470,25 @@ class getid3_quicktime extends getid3_handler
 													break;
 
 												case 'gnre':
+													// enum
 													$GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
 													$atom_structure['data']    = getid3_id3v1::LookupGenreName($GenreID - 1);
 													break;
 
 												case 'rtng':
+													// 8-bit integer
 													$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
 													$atom_structure['data']    = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]);
 													break;
 
 												case 'stik':
+													// 8-bit integer (enum)
 													$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
 													$atom_structure['data']    = $this->QuicktimeSTIKLookup($atom_structure[$atomname]);
 													break;
 
 												case 'sfID':
+													// 32-bit integer
 													$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
 													$atom_structure['data']    = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]);
 													break;
@@ -403,7 +498,30 @@ class getid3_quicktime extends getid3_handler
 													$atom_structure['data'] = substr($boxdata, 8);
 													break;
 
+												case 'plID':
+													// 64-bit integer
+													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8));
+													break;
+
+												case 'covr':
+													$atom_structure['data'] = substr($boxdata, 8);
+													// 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';
+													}
+													break;
+
+												case 'atID':
+												case 'cnID':
+												case 'geID':
+												case 'tves':
+												case 'tvsn':
 												default:
+													// 32-bit integer
 													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
 											}
 											break;
@@ -414,9 +532,9 @@ class getid3_quicktime extends getid3_handler
 											$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'])) {
+												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'])) {
+												} 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';
@@ -428,7 +546,7 @@ class getid3_quicktime extends getid3_handler
 									break;
 
 								default:
-									$info['warning'][] = 'Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset;
+									$this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset);
 									$atom_structure['data'] = $atom_data;
 
 							}
@@ -476,7 +594,7 @@ class getid3_quicktime extends getid3_handler
 				if ($UncompressedHeader = @gzuncompress($CompressedFileData)) {
 					$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms);
 				} else {
-					$info['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atom_structure['offset'];
+					$this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']);
 				}
 				break;
 
@@ -595,7 +713,7 @@ class getid3_quicktime extends getid3_handler
 				if (isset($ptv_lookup[$atom_structure['display_size_raw']])) {
 					$atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
 				} else {
-					$info['warning'][] = 'unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')';
+					$this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')');
 				}
 				break;
 
@@ -604,6 +722,20 @@ class getid3_quicktime extends getid3_handler
 				$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
 				$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
 				$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
+
+				// see: https://github.com/JamesHeinrich/getID3/issues/111
+				// Some corrupt files have been known to have high bits set in the number_entries field
+				// This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000
+				// Workaround: mask off the upper byte and throw a warning if it's nonzero
+				if ($atom_structure['number_entries'] > 0x000FFFFF) {
+					if ($atom_structure['number_entries'] > 0x00FFFFFF) {
+						$this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Ignoring upper byte and interpreting this as 0x'.getid3_lib::PrintHexBytes(substr($atom_data, 5, 3), true, false).' = '.($atom_structure['number_entries'] & 0x00FFFFFF));
+						$atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF);
+					} else {
+						$this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Please report this to info@getid3.org referencing bug report #111');
+					}
+				}
+
 				$stsdEntriesDataOffset = 8;
 				for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
 					$atom_structure['sample_description_table'][$i]['size']             = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4));
@@ -801,7 +933,7 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 
 				$max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']);
 				if ($max_stts_entries_to_scan < $atom_structure['number_entries']) {
-					$info['warning'][] = 'QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).';
+					$this->warning('QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).');
 				}
 				for ($i = 0; $i < $max_stts_entries_to_scan; $i++) {
 					$atom_structure['time_to_sample_table'][$i]['sample_count']    = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
@@ -928,13 +1060,13 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 				for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
 					$atom_structure['data_references'][$i]['size']                    = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4));
 					$drefDataOffset += 4;
-					$atom_structure['data_references'][$i]['type']                    =               substr($atom_data, $drefDataOffset, 4);
+					$atom_structure['data_references'][$i]['type']                    =                           substr($atom_data, $drefDataOffset, 4);
 					$drefDataOffset += 4;
 					$atom_structure['data_references'][$i]['version']                 = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 1));
 					$drefDataOffset += 1;
 					$atom_structure['data_references'][$i]['flags_raw']               = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 3)); // hardcoded: 0x0000
 					$drefDataOffset += 3;
-					$atom_structure['data_references'][$i]['data']                    =               substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));
+					$atom_structure['data_references'][$i]['data']                    =                           substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));
 					$drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3);
 
 					$atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001);
@@ -1001,10 +1133,10 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 				$atom_structure['quality']               = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2));
 
 				if ($atom_structure['time_scale'] == 0) {
-					$info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero';
+					$this->error('Corrupt Quicktime file: mdhd.time_scale == zero');
 					return false;
 				}
-				$info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
+				$info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
 
 				$atom_structure['creation_time_unix']    = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
 				$atom_structure['modify_time_unix']      = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
@@ -1019,7 +1151,7 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 			case 'pnot': // Preview atom
 				$atom_structure['modification_date']      = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4)); // "standard Macintosh format"
 				$atom_structure['version_number']         = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2)); // hardcoded: 0x00
-				$atom_structure['atom_type']              =               substr($atom_data,  6, 4);        // usually: 'PICT'
+				$atom_structure['atom_type']              =                           substr($atom_data,  6, 4);        // usually: 'PICT'
 				$atom_structure['atom_index']             = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01
 
 				$atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']);
@@ -1029,7 +1161,7 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 			case 'crgn': // Clipping ReGioN atom
 				$atom_structure['region_size']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 2)); // The Region size, Region boundary box,
 				$atom_structure['boundary_box']  = getid3_lib::BigEndian2Int(substr($atom_data,  2, 8)); // and Clipping region data fields
-				$atom_structure['clipping_data'] =               substr($atom_data, 10);           // constitute a QuickDraw region.
+				$atom_structure['clipping_data'] =                           substr($atom_data, 10);           // constitute a QuickDraw region.
 				break;
 
 
@@ -1115,12 +1247,12 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 				$atom_structure['next_track_id']      =   getid3_lib::BigEndian2Int(substr($atom_data, 96, 4));
 
 				if ($atom_structure['time_scale'] == 0) {
-					$info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero';
+					$this->error('Corrupt Quicktime file: mvhd.time_scale == zero');
 					return false;
 				}
 				$atom_structure['creation_time_unix']        = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
 				$atom_structure['modify_time_unix']          = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
-				$info['quicktime']['time_scale']    = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
+				$info['quicktime']['time_scale']    = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
 				$info['quicktime']['display_scale'] = $atom_structure['matrix_a'];
 				$info['playtime_seconds']           = $atom_structure['duration'] / $atom_structure['time_scale'];
 				break;
@@ -1240,14 +1372,20 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 				}
 
 				// check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field
-				while  (($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2)))
+				while (($mdat_offset < (strlen($atom_data) - 8))
+					&& ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2)))
 					&& ($chapter_string_length < 1000)
 					&& ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2))
-					&& preg_match('#^[\x20-\xFF]+$#', substr($atom_data, $mdat_offset + 2, $chapter_string_length), $chapter_matches)) {
+					&& preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) {
+						list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches;
 						$mdat_offset += (2 + $chapter_string_length);
-						@$info['quicktime']['comments']['chapters'][] = $chapter_matches[0];
-				}
+						@$info['quicktime']['comments']['chapters'][] = $chapter_string;
 
+						// "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled)
+						if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8
+							$mdat_offset += 12;
+						}
+				}
 
 
 				if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) {
@@ -1265,7 +1403,7 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 						$getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false);
 						if (!empty($getid3_temp->info['warning'])) {
 							foreach ($getid3_temp->info['warning'] as $value) {
-								$info['warning'][] = $value;
+								$this->warning($value);
 							}
 						}
 						if (!empty($getid3_temp->info['mpeg'])) {
@@ -1368,7 +1506,7 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 						$info['quicktime']['comments']['gps_altitude'][] = floatval($altitude);
 					}
 				} else {
-					$info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.';
+					$this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.');
 				}
 				break;
 
@@ -1397,26 +1535,183 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 				break;
 
 			case "\x00\x00\x00\x00":
-			case 'meta': // METAdata atom
 				// 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
 
-	            $atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
-	            $atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
-	            $atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
+				$atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
+				$atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
+				$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
 				//$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
 				break;
 
+			case 'meta': // METAdata atom
+				// https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html
+
+				$atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
+				$atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
+				$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
+				break;
+
 			case 'data': // metaDATA atom
+				static $metaDATAkey = 1; // real ugly, but so is the QuickTime structure that stores keys and values in different multinested locations that are hard to relate to each other
 				// seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data
 				$atom_structure['language'] =                           substr($atom_data, 4 + 0, 2);
 				$atom_structure['unknown']  = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2));
 				$atom_structure['data']     =                           substr($atom_data, 4 + 4);
+				$atom_structure['key_name'] = @$info['quicktime']['temp_meta_key_names'][$metaDATAkey++];
+
+				if ($atom_structure['key_name'] && $atom_structure['data']) {
+					@$info['quicktime']['comments'][str_replace('com.apple.quicktime.', '', $atom_structure['key_name'])][] = $atom_structure['data'];
+				}
 				break;
 
+			case 'keys': // KEYS that may be present in the metadata atom.
+				// https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21
+				// The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom.
+				// This list is indexed starting with 1; 0 is a reserved index value. The metadata item keys atom is a full atom with an atom type of "keys".
+				$atom_structure['version']       = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
+				$atom_structure['flags_raw']     = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
+				$atom_structure['entry_count']   = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
+				$keys_atom_offset = 8;
+				for ($i = 1; $i <= $atom_structure['entry_count']; $i++) {
+					$atom_structure['keys'][$i]['key_size']      = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4));
+					$atom_structure['keys'][$i]['key_namespace'] =                           substr($atom_data, $keys_atom_offset + 4, 4);
+					$atom_structure['keys'][$i]['key_value']     =                           substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8);
+					$keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace
+
+					$info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value'];
+				}
+				break;
+
+			case 'gps ':
+				// https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730
+				// The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data.
+				// The first row is version/metadata/notsure, I skip that.
+				// The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file.
+
+				$GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size
+				if (strlen($atom_data) > 0) {
+					if ((strlen($atom_data) % $GPS_rowsize) == 0) {
+						$atom_structure['gps_toc'] = array();
+						foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) {
+							$atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize));
+						}
+
+						$atom_structure['gps_entries'] = array();
+						$previous_offset = $this->ftell();
+						foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) {
+							if ($key == 0) {
+								// "The first row is version/metadata/notsure, I skip that."
+								continue;
+							}
+							$this->fseek($gps_pointer['offset']);
+							$GPS_free_data = $this->fread($gps_pointer['size']);
+
+							/*
+							// 2017-05-10: I see some of the data, notably the Hour-Minute-Second, but cannot reconcile the rest of the data. However, the NMEA "GPRMC" line is there and relatively easy to parse, so I'm using that instead
+
+							// https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730
+							// The structure of the GPS data atom (the 'free' atoms mentioned above) is following:
+							// hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from('<IIIIIIssssfff',data, 48)
+							// For those unfamiliar with python struct:
+							// I = int
+							// s = is string (size 1, in this case)
+							// f = float
+
+							//$atom_structure['gps_entries'][$key] = unpack('Vhour/Vminute/Vsecond/Vyear/Vmonth/Vday/Vactive/Vlatitude_b/Vlongitude_b/Vunknown2/flatitude/flongitude/fspeed', substr($GPS_free_data, 48));
+							*/
+
+							// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
+							// $GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67
+							// $GPRMC,002454,A,3553.5295,N,13938.6570,E,0.0,43.1,180700,7.1,W,A*3F
+							// $GPRMC,094347.000,A,5342.0061,N,00737.9908,W,0.01,156.75,140217,,,A*7D
+							if (preg_match('#\\$GPRMC,([0-9\\.]*),([AV]),([0-9\\.]*),([NS]),([0-9\\.]*),([EW]),([0-9\\.]*),([0-9\\.]*),([0-9]*),([0-9\\.]*),([EW]?)(,[A])?(\\*[0-9A-F]{2})#', $GPS_free_data, $matches)) {
+								$GPS_this_GPRMC = array();
+								list(
+									$GPS_this_GPRMC['raw']['gprmc'],
+									$GPS_this_GPRMC['raw']['timestamp'],
+									$GPS_this_GPRMC['raw']['status'],
+									$GPS_this_GPRMC['raw']['latitude'],
+									$GPS_this_GPRMC['raw']['latitude_direction'],
+									$GPS_this_GPRMC['raw']['longitude'],
+									$GPS_this_GPRMC['raw']['longitude_direction'],
+									$GPS_this_GPRMC['raw']['knots'],
+									$GPS_this_GPRMC['raw']['angle'],
+									$GPS_this_GPRMC['raw']['datestamp'],
+									$GPS_this_GPRMC['raw']['variation'],
+									$GPS_this_GPRMC['raw']['variation_direction'],
+									$dummy,
+									$GPS_this_GPRMC['raw']['checksum'],
+								) = $matches;
+
+								$hour   = substr($GPS_this_GPRMC['raw']['timestamp'], 0, 2);
+								$minute = substr($GPS_this_GPRMC['raw']['timestamp'], 2, 2);
+								$second = substr($GPS_this_GPRMC['raw']['timestamp'], 4, 2);
+								$ms     = substr($GPS_this_GPRMC['raw']['timestamp'], 6);    // may contain decimal seconds
+								$day   = substr($GPS_this_GPRMC['raw']['datestamp'], 0, 2);
+								$month = substr($GPS_this_GPRMC['raw']['datestamp'], 2, 2);
+								$year  = substr($GPS_this_GPRMC['raw']['datestamp'], 4, 2);
+								$year += (($year > 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess
+								$GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms;
+
+								$GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void
+
+								foreach (array('latitude','longitude') as $latlon) {
+									preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches);
+									list($dummy, $deg, $min) = $matches;
+									$GPS_this_GPRMC[$latlon] = $deg + ($min / 60);
+								}
+								$GPS_this_GPRMC['latitude']  *= (($GPS_this_GPRMC['raw']['latitude_direction']  == 'S') ? -1 : 1);
+								$GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1);
+
+								$GPS_this_GPRMC['heading']    = $GPS_this_GPRMC['raw']['angle'];
+								$GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots'];
+								$GPS_this_GPRMC['speed_kmh']  = $GPS_this_GPRMC['raw']['knots'] * 1.852;
+								if ($GPS_this_GPRMC['raw']['variation']) {
+									$GPS_this_GPRMC['variation']  = $GPS_this_GPRMC['raw']['variation'];
+									$GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1);
+								}
+
+								$atom_structure['gps_entries'][$key] = $GPS_this_GPRMC;
+
+								@$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array(
+									'latitude'  => $GPS_this_GPRMC['latitude'],
+									'longitude' => $GPS_this_GPRMC['longitude'],
+									'speed_kmh' => $GPS_this_GPRMC['speed_kmh'],
+									'heading'   => $GPS_this_GPRMC['heading'],
+								);
+
+							} else {
+								$this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']);
+							}
+						}
+						$this->fseek($previous_offset);
+
+					} else {
+						$this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset);
+					}
+				} else {
+					$this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset);
+				}
+				break;
+
+			case 'loci':// 3GP location (El Loco)
+                                $info['quicktime']['comments']['gps_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
+                                $info['quicktime']['comments']['gps_lang'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
+                                $loffset = 0;
+                                $info['quicktime']['comments']['gps_location'] = $this->LociString(substr($atom_data, 6), $loffset);
+                                $loci_data=substr($atom_data, 6 + $loffset);
+                                $info['quicktime']['comments']['gps_role'] = getid3_lib::BigEndian2Int(substr($loci_data, 0, 1));
+                                $info['quicktime']['comments']['gps_longitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4));
+                                $info['quicktime']['comments']['gps_latitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4));
+                                $info['quicktime']['comments']['gps_altitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4));
+                                $info['quicktime']['comments']['gps_body'] = $this->LociString(substr($loci_data, 13), $loffset);
+                                $info['quicktime']['comments']['gps_notes'] = $this->LociString(substr($loci_data, 13 + $loffset), $loffset);
+                                break;
+
 			default:
-				$info['warning'][] = 'Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset;
+				$this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset);
 				$atom_structure['data'] = $atom_data;
 				break;
 		}
@@ -1440,6 +1735,10 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 				// Furthermore, for historical reasons the list of atoms is optionally
 				// terminated by a 32-bit integer set to 0. If you are writing a program
 				// to read user data atoms, you should allow for the terminating 0.
+				if (strlen($atom_data) > 12) {
+					$subatomoffset += 4;
+					continue;
+				}
 				return $atom_structure;
 			}
 
@@ -1753,56 +2052,56 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
 		static $QuicktimeIODSaudioProfileNameLookup = array();
 		if (empty($QuicktimeIODSaudioProfileNameLookup)) {
 			$QuicktimeIODSaudioProfileNameLookup = array(
-			    0x00 => 'ISO Reserved (0x00)',
-			    0x01 => 'Main Audio Profile @ Level 1',
-			    0x02 => 'Main Audio Profile @ Level 2',
-			    0x03 => 'Main Audio Profile @ Level 3',
-			    0x04 => 'Main Audio Profile @ Level 4',
-			    0x05 => 'Scalable Audio Profile @ Level 1',
-			    0x06 => 'Scalable Audio Profile @ Level 2',
-			    0x07 => 'Scalable Audio Profile @ Level 3',
-			    0x08 => 'Scalable Audio Profile @ Level 4',
-			    0x09 => 'Speech Audio Profile @ Level 1',
-			    0x0A => 'Speech Audio Profile @ Level 2',
-			    0x0B => 'Synthetic Audio Profile @ Level 1',
-			    0x0C => 'Synthetic Audio Profile @ Level 2',
-			    0x0D => 'Synthetic Audio Profile @ Level 3',
-			    0x0E => 'High Quality Audio Profile @ Level 1',
-			    0x0F => 'High Quality Audio Profile @ Level 2',
-			    0x10 => 'High Quality Audio Profile @ Level 3',
-			    0x11 => 'High Quality Audio Profile @ Level 4',
-			    0x12 => 'High Quality Audio Profile @ Level 5',
-			    0x13 => 'High Quality Audio Profile @ Level 6',
-			    0x14 => 'High Quality Audio Profile @ Level 7',
-			    0x15 => 'High Quality Audio Profile @ Level 8',
-			    0x16 => 'Low Delay Audio Profile @ Level 1',
-			    0x17 => 'Low Delay Audio Profile @ Level 2',
-			    0x18 => 'Low Delay Audio Profile @ Level 3',
-			    0x19 => 'Low Delay Audio Profile @ Level 4',
-			    0x1A => 'Low Delay Audio Profile @ Level 5',
-			    0x1B => 'Low Delay Audio Profile @ Level 6',
-			    0x1C => 'Low Delay Audio Profile @ Level 7',
-			    0x1D => 'Low Delay Audio Profile @ Level 8',
-			    0x1E => 'Natural Audio Profile @ Level 1',
-			    0x1F => 'Natural Audio Profile @ Level 2',
-			    0x20 => 'Natural Audio Profile @ Level 3',
-			    0x21 => 'Natural Audio Profile @ Level 4',
-			    0x22 => 'Mobile Audio Internetworking Profile @ Level 1',
-			    0x23 => 'Mobile Audio Internetworking Profile @ Level 2',
-			    0x24 => 'Mobile Audio Internetworking Profile @ Level 3',
-			    0x25 => 'Mobile Audio Internetworking Profile @ Level 4',
-			    0x26 => 'Mobile Audio Internetworking Profile @ Level 5',
-			    0x27 => 'Mobile Audio Internetworking Profile @ Level 6',
-			    0x28 => 'AAC Profile @ Level 1',
-			    0x29 => 'AAC Profile @ Level 2',
-			    0x2A => 'AAC Profile @ Level 4',
-			    0x2B => 'AAC Profile @ Level 5',
-			    0x2C => 'High Efficiency AAC Profile @ Level 2',
-			    0x2D => 'High Efficiency AAC Profile @ Level 3',
-			    0x2E => 'High Efficiency AAC Profile @ Level 4',
-			    0x2F => 'High Efficiency AAC Profile @ Level 5',
-			    0xFE => 'Not part of MPEG-4 audio profiles',
-			    0xFF => 'No audio capability required',
+				0x00 => 'ISO Reserved (0x00)',
+				0x01 => 'Main Audio Profile @ Level 1',
+				0x02 => 'Main Audio Profile @ Level 2',
+				0x03 => 'Main Audio Profile @ Level 3',
+				0x04 => 'Main Audio Profile @ Level 4',
+				0x05 => 'Scalable Audio Profile @ Level 1',
+				0x06 => 'Scalable Audio Profile @ Level 2',
+				0x07 => 'Scalable Audio Profile @ Level 3',
+				0x08 => 'Scalable Audio Profile @ Level 4',
+				0x09 => 'Speech Audio Profile @ Level 1',
+				0x0A => 'Speech Audio Profile @ Level 2',
+				0x0B => 'Synthetic Audio Profile @ Level 1',
+				0x0C => 'Synthetic Audio Profile @ Level 2',
+				0x0D => 'Synthetic Audio Profile @ Level 3',
+				0x0E => 'High Quality Audio Profile @ Level 1',
+				0x0F => 'High Quality Audio Profile @ Level 2',
+				0x10 => 'High Quality Audio Profile @ Level 3',
+				0x11 => 'High Quality Audio Profile @ Level 4',
+				0x12 => 'High Quality Audio Profile @ Level 5',
+				0x13 => 'High Quality Audio Profile @ Level 6',
+				0x14 => 'High Quality Audio Profile @ Level 7',
+				0x15 => 'High Quality Audio Profile @ Level 8',
+				0x16 => 'Low Delay Audio Profile @ Level 1',
+				0x17 => 'Low Delay Audio Profile @ Level 2',
+				0x18 => 'Low Delay Audio Profile @ Level 3',
+				0x19 => 'Low Delay Audio Profile @ Level 4',
+				0x1A => 'Low Delay Audio Profile @ Level 5',
+				0x1B => 'Low Delay Audio Profile @ Level 6',
+				0x1C => 'Low Delay Audio Profile @ Level 7',
+				0x1D => 'Low Delay Audio Profile @ Level 8',
+				0x1E => 'Natural Audio Profile @ Level 1',
+				0x1F => 'Natural Audio Profile @ Level 2',
+				0x20 => 'Natural Audio Profile @ Level 3',
+				0x21 => 'Natural Audio Profile @ Level 4',
+				0x22 => 'Mobile Audio Internetworking Profile @ Level 1',
+				0x23 => 'Mobile Audio Internetworking Profile @ Level 2',
+				0x24 => 'Mobile Audio Internetworking Profile @ Level 3',
+				0x25 => 'Mobile Audio Internetworking Profile @ Level 4',
+				0x26 => 'Mobile Audio Internetworking Profile @ Level 5',
+				0x27 => 'Mobile Audio Internetworking Profile @ Level 6',
+				0x28 => 'AAC Profile @ Level 1',
+				0x29 => 'AAC Profile @ Level 2',
+				0x2A => 'AAC Profile @ Level 4',
+				0x2B => 'AAC Profile @ Level 5',
+				0x2C => 'High Efficiency AAC Profile @ Level 2',
+				0x2D => 'High Efficiency AAC Profile @ Level 3',
+				0x2E => 'High Efficiency AAC Profile @ Level 4',
+				0x2F => 'High Efficiency AAC Profile @ Level 5',
+				0xFE => 'Not part of MPEG-4 audio profiles',
+				0xFF => 'No audio capability required',
 			);
 		}
 		return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private');
@@ -2111,8 +2410,18 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br
 	public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') {
 		static $handyatomtranslatorarray = array();
 		if (empty($handyatomtranslatorarray)) {
+			// http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
+			// http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
+			// http://atomicparsley.sourceforge.net/mpeg-4files.html
+			// https://code.google.com/p/mp4v2/wiki/iTunesMetadata
+			$handyatomtranslatorarray["\xA9".'alb'] = 'album';               // iTunes 4.0
+			$handyatomtranslatorarray["\xA9".'ART'] = 'artist';
+			$handyatomtranslatorarray["\xA9".'art'] = 'artist';              // iTunes 4.0
+			$handyatomtranslatorarray["\xA9".'aut'] = 'author';
+			$handyatomtranslatorarray["\xA9".'cmt'] = 'comment';             // iTunes 4.0
+			$handyatomtranslatorarray["\xA9".'com'] = 'comment';
 			$handyatomtranslatorarray["\xA9".'cpy'] = 'copyright';
-			$handyatomtranslatorarray["\xA9".'day'] = 'creation_date';    // iTunes 4.0
+			$handyatomtranslatorarray["\xA9".'day'] = 'creation_date';       // iTunes 4.0
 			$handyatomtranslatorarray["\xA9".'dir'] = 'director';
 			$handyatomtranslatorarray["\xA9".'ed1'] = 'edit1';
 			$handyatomtranslatorarray["\xA9".'ed2'] = 'edit2';
@@ -2123,64 +2432,60 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br
 			$handyatomtranslatorarray["\xA9".'ed7'] = 'edit7';
 			$handyatomtranslatorarray["\xA9".'ed8'] = 'edit8';
 			$handyatomtranslatorarray["\xA9".'ed9'] = 'edit9';
+			$handyatomtranslatorarray["\xA9".'enc'] = 'encoded_by';
 			$handyatomtranslatorarray["\xA9".'fmt'] = 'format';
+			$handyatomtranslatorarray["\xA9".'gen'] = 'genre';               // iTunes 4.0
+			$handyatomtranslatorarray["\xA9".'grp'] = 'grouping';            // iTunes 4.2
+			$handyatomtranslatorarray["\xA9".'hst'] = 'host_computer';
 			$handyatomtranslatorarray["\xA9".'inf'] = 'information';
+			$handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics';              // iTunes 5.0
+			$handyatomtranslatorarray["\xA9".'mak'] = 'make';
+			$handyatomtranslatorarray["\xA9".'mod'] = 'model';
+			$handyatomtranslatorarray["\xA9".'nam'] = 'title';               // iTunes 4.0
+			$handyatomtranslatorarray["\xA9".'ope'] = 'composer';
 			$handyatomtranslatorarray["\xA9".'prd'] = 'producer';
+			$handyatomtranslatorarray["\xA9".'PRD'] = 'product';
 			$handyatomtranslatorarray["\xA9".'prf'] = 'performers';
 			$handyatomtranslatorarray["\xA9".'req'] = 'system_requirements';
 			$handyatomtranslatorarray["\xA9".'src'] = 'source_credit';
-			$handyatomtranslatorarray["\xA9".'wrt'] = 'writer';
-
-			// http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
-			$handyatomtranslatorarray["\xA9".'nam'] = 'title';           // iTunes 4.0
-			$handyatomtranslatorarray["\xA9".'cmt'] = 'comment';         // iTunes 4.0
-			$handyatomtranslatorarray["\xA9".'wrn'] = 'warning';
-			$handyatomtranslatorarray["\xA9".'hst'] = 'host_computer';
-			$handyatomtranslatorarray["\xA9".'mak'] = 'make';
-			$handyatomtranslatorarray["\xA9".'mod'] = 'model';
-			$handyatomtranslatorarray["\xA9".'PRD'] = 'product';
 			$handyatomtranslatorarray["\xA9".'swr'] = 'software';
-			$handyatomtranslatorarray["\xA9".'aut'] = 'author';
-			$handyatomtranslatorarray["\xA9".'ART'] = 'artist';
+			$handyatomtranslatorarray["\xA9".'too'] = 'encoding_tool';       // iTunes 4.0
 			$handyatomtranslatorarray["\xA9".'trk'] = 'track';
-			$handyatomtranslatorarray["\xA9".'alb'] = 'album';           // iTunes 4.0
-			$handyatomtranslatorarray["\xA9".'com'] = 'comment';
-			$handyatomtranslatorarray["\xA9".'gen'] = 'genre';           // iTunes 4.0
-			$handyatomtranslatorarray["\xA9".'ope'] = 'composer';
 			$handyatomtranslatorarray["\xA9".'url'] = 'url';
-			$handyatomtranslatorarray["\xA9".'enc'] = 'encoder';
-
-			// http://atomicparsley.sourceforge.net/mpeg-4files.html
-			$handyatomtranslatorarray["\xA9".'art'] = 'artist';           // iTunes 4.0
+			$handyatomtranslatorarray["\xA9".'wrn'] = 'warning';
+			$handyatomtranslatorarray["\xA9".'wrt'] = 'composer';
 			$handyatomtranslatorarray['aART'] = 'album_artist';
-			$handyatomtranslatorarray['trkn'] = 'track_number';     // iTunes 4.0
-			$handyatomtranslatorarray['disk'] = 'disc_number';      // iTunes 4.0
-			$handyatomtranslatorarray['gnre'] = 'genre';            // iTunes 4.0
-			$handyatomtranslatorarray["\xA9".'too'] = 'encoder';          // iTunes 4.0
-			$handyatomtranslatorarray['tmpo'] = 'bpm';              // iTunes 4.0
-			$handyatomtranslatorarray['cprt'] = 'copyright';        // iTunes 4.0?
-			$handyatomtranslatorarray['cpil'] = 'compilation';      // iTunes 4.0
-			$handyatomtranslatorarray['covr'] = 'picture';          // iTunes 4.0
-			$handyatomtranslatorarray['rtng'] = 'rating';           // iTunes 4.0
-			$handyatomtranslatorarray["\xA9".'grp'] = 'grouping';         // iTunes 4.2
-			$handyatomtranslatorarray['stik'] = 'stik';             // iTunes 4.9
-			$handyatomtranslatorarray['pcst'] = 'podcast';          // iTunes 4.9
-			$handyatomtranslatorarray['catg'] = 'category';         // iTunes 4.9
-			$handyatomtranslatorarray['keyw'] = 'keyword';          // iTunes 4.9
-			$handyatomtranslatorarray['purl'] = 'podcast_url';      // iTunes 4.9
-			$handyatomtranslatorarray['egid'] = 'episode_guid';     // iTunes 4.9
-			$handyatomtranslatorarray['desc'] = 'description';      // iTunes 5.0
-			$handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics';           // iTunes 5.0
-			$handyatomtranslatorarray['tvnn'] = 'tv_network_name';  // iTunes 6.0
-			$handyatomtranslatorarray['tvsh'] = 'tv_show_name';     // iTunes 6.0
-			$handyatomtranslatorarray['tvsn'] = 'tv_season';        // iTunes 6.0
-			$handyatomtranslatorarray['tves'] = 'tv_episode';       // iTunes 6.0
-			$handyatomtranslatorarray['purd'] = 'purchase_date';    // iTunes 6.0.2
-			$handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0
-
-			// http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
-
-
+			$handyatomtranslatorarray['apID'] = 'purchase_account';
+			$handyatomtranslatorarray['catg'] = 'category';            // iTunes 4.9
+			$handyatomtranslatorarray['covr'] = 'picture';             // iTunes 4.0
+			$handyatomtranslatorarray['cpil'] = 'compilation';         // iTunes 4.0
+			$handyatomtranslatorarray['cprt'] = 'copyright';           // iTunes 4.0?
+			$handyatomtranslatorarray['desc'] = 'description';         // iTunes 5.0
+			$handyatomtranslatorarray['disk'] = 'disc_number';         // iTunes 4.0
+			$handyatomtranslatorarray['egid'] = 'episode_guid';        // iTunes 4.9
+			$handyatomtranslatorarray['gnre'] = 'genre';               // iTunes 4.0
+			$handyatomtranslatorarray['hdvd'] = 'hd_video';            // iTunes 4.0
+			$handyatomtranslatorarray['ldes'] = 'description_long';    //
+			$handyatomtranslatorarray['keyw'] = 'keyword';             // iTunes 4.9
+			$handyatomtranslatorarray['pcst'] = 'podcast';             // iTunes 4.9
+			$handyatomtranslatorarray['pgap'] = 'gapless_playback';    // iTunes 7.0
+			$handyatomtranslatorarray['purd'] = 'purchase_date';       // iTunes 6.0.2
+			$handyatomtranslatorarray['purl'] = 'podcast_url';         // iTunes 4.9
+			$handyatomtranslatorarray['rtng'] = 'rating';              // iTunes 4.0
+			$handyatomtranslatorarray['soaa'] = 'sort_album_artist';   //
+			$handyatomtranslatorarray['soal'] = 'sort_album';          //
+			$handyatomtranslatorarray['soar'] = 'sort_artist';         //
+			$handyatomtranslatorarray['soco'] = 'sort_composer';       //
+			$handyatomtranslatorarray['sonm'] = 'sort_title';          //
+			$handyatomtranslatorarray['sosn'] = 'sort_show';           //
+			$handyatomtranslatorarray['stik'] = 'stik';                // iTunes 4.9
+			$handyatomtranslatorarray['tmpo'] = 'bpm';                 // iTunes 4.0
+			$handyatomtranslatorarray['trkn'] = 'track_number';        // iTunes 4.0
+			$handyatomtranslatorarray['tven'] = 'tv_episode_id';       //
+			$handyatomtranslatorarray['tves'] = 'tv_episode';          // iTunes 6.0
+			$handyatomtranslatorarray['tvnn'] = 'tv_network_name';     // iTunes 6.0
+			$handyatomtranslatorarray['tvsh'] = 'tv_show_name';        // iTunes 6.0
+			$handyatomtranslatorarray['tvsn'] = 'tv_season';           // iTunes 6.0
 
 			// boxnames:
 			/*
@@ -2225,11 +2530,51 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br
 					$data = array('data'=>$data, 'image_mime'=>$image_mime);
 				}
 			}
-			$info['quicktime']['comments'][$comment_key][] = $data;
+			$gooddata = array($data);
+			if ($comment_key == 'genre') {
+				// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
+				$gooddata = explode(';', $data);
+			}
+			foreach ($gooddata as $data) {
+				$info['quicktime']['comments'][$comment_key][] = $data;
+			}
 		}
 		return true;
 	}
 
+    public function LociString($lstring, &$count) {
+            // Loci strings are UTF-8 or UTF-16 and null (x00/x0000) terminated. UTF-16 has a BOM
+            // Also need to return the number of bytes the string occupied so additional fields can be extracted
+            $len = strlen($lstring);
+            if ($len == 0) {
+                $count = 0;
+                return '';
+            }
+            if ($lstring[0] == "\x00") {
+                $count = 1;
+                return '';
+            }
+            //check for BOM
+            if ($len > 2 && (($lstring[0] == "\xFE" && $lstring[1] == "\xFF") || ($lstring[0] == "\xFF" && $lstring[1] == "\xFE"))) {
+                //UTF-16
+                if (preg_match('/(.*)\x00/', $lstring, $lmatches)){
+                     $count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000
+                    return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]);
+                } else {
+                    return '';
+                }
+            } else {
+                //UTF-8
+                if (preg_match('/(.*)\x00/', $lstring, $lmatches)){
+                    $count = strlen($lmatches[1]) + 1; //account for trailing \x00
+                    return $lmatches[1];
+                }else {
+                    return '';
+                }
+
+            }
+        }
+
 	public function NoNullString($nullterminatedstring) {
 		// remove the single null terminator on null terminated strings
 		if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") {
@@ -2243,4 +2588,79 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br
 		return substr($pascalstring, 1);
 	}
 
+
+	/*
+	// helper functions for m4b audiobook chapters
+	// code by Steffen Hartmann 2015-Nov-08
+	*/
+	public function search_tag_by_key($info, $tag, $history, &$result) {
+		foreach ($info as $key => $value) {
+			$key_history = $history.'/'.$key;
+			if ($key === $tag) {
+				$result[] = array($key_history, $info);
+			} else {
+				if (is_array($value)) {
+					$this->search_tag_by_key($value, $tag, $key_history, $result);
+				}
+			}
+		}
+	}
+
+	public function search_tag_by_pair($info, $k, $v, $history, &$result) {
+		foreach ($info as $key => $value) {
+			$key_history = $history.'/'.$key;
+			if (($key === $k) && ($value === $v)) {
+				$result[] = array($key_history, $info);
+			} else {
+				if (is_array($value)) {
+					$this->search_tag_by_pair($value, $k, $v, $key_history, $result);
+				}
+			}
+		}
+	}
+
+	public function quicktime_time_to_sample_table($info) {
+		$res = array();
+		$this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res);
+		foreach ($res as $value) {
+			$stbl_res = array();
+			$this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res);
+			if (count($stbl_res) > 0) {
+				$stts_res = array();
+				$this->search_tag_by_key($value[1], 'time_to_sample_table', $value[0], $stts_res);
+				if (count($stts_res) > 0) {
+					return $stts_res[0][1]['time_to_sample_table'];
+				}
+			}
+		}
+		return array();
+	}
+
+	function quicktime_bookmark_time_scale($info) {
+		$time_scale = '';
+		$ts_prefix_len = 0;
+		$res = array();
+		$this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res);
+		foreach ($res as $value) {
+			$stbl_res = array();
+			$this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res);
+			if (count($stbl_res) > 0) {
+				$ts_res = array();
+				$this->search_tag_by_key($info['quicktime']['moov'], 'time_scale', 'quicktime/moov', $ts_res);
+				foreach ($ts_res as $value) {
+					$prefix = substr($value[0], 0, -12);
+					if ((substr($stbl_res[0][0], 0, strlen($prefix)) === $prefix) && ($ts_prefix_len < strlen($prefix))) {
+						$time_scale = $value[1]['time_scale'];
+						$ts_prefix_len = strlen($prefix);
+					}
+				}
+			}
+		}
+		return $time_scale;
+	}
+	/*
+	// END helper functions for m4b audiobook chapters
+	*/
+
+
 }
diff --git a/src/wp-includes/ID3/module.audio-video.riff.php b/src/wp-includes/ID3/module.audio-video.riff.php
index e8ba944..53f9350 100644
--- a/src/wp-includes/ID3/module.audio-video.riff.php
+++ b/src/wp-includes/ID3/module.audio-video.riff.php
@@ -190,7 +190,7 @@ class getid3_riff extends getid3_handler {
 					$thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']);
 					$thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag'];
 					if (!isset($thisfile_riff_audio[$streamindex]['bitrate']) || ($thisfile_riff_audio[$streamindex]['bitrate'] == 0)) {
-						$info['error'][] = 'Corrupt RIFF file: bitrate_audio == zero';
+						$this->error('Corrupt RIFF file: bitrate_audio == zero');
 						return false;
 					}
 					$thisfile_riff_raw['fmt '] = $thisfile_riff_audio[$streamindex]['raw'];
@@ -199,7 +199,7 @@ class getid3_riff extends getid3_handler {
 
 					$thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]);
 					if (substr($thisfile_audio['codec'], 0, strlen('unknown: 0x')) == 'unknown: 0x') {
-						$info['warning'][] = 'Audio codec = '.$thisfile_audio['codec'];
+						$this->warning('Audio codec = '.$thisfile_audio['codec']);
 					}
 					$thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate'];
 
@@ -302,10 +302,10 @@ class getid3_riff extends getid3_handler {
 							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']);
 						} else {
-							$info['warning'][] = 'RIFF.WAVE.BEXT.origin_time is invalid';
+							$this->warning('RIFF.WAVE.BEXT.origin_time is invalid');
 						}
 					} else {
-						$info['warning'][] = 'RIFF.WAVE.BEXT.origin_date is invalid';
+						$this->warning('RIFF.WAVE.BEXT.origin_date is invalid');
 					}
 					$thisfile_riff['comments']['author'][] = $thisfile_riff_WAVE_bext_0['author'];
 					$thisfile_riff['comments']['title'][]  = $thisfile_riff_WAVE_bext_0['title'];
@@ -385,10 +385,10 @@ class getid3_riff extends getid3_handler {
 						$SNDM_thisTagOffset += $SNDM_thisTagDataSize;
 
 						if ($SNDM_thisTagSize != (4 + 4 + 2 + 2 + $SNDM_thisTagDataSize)) {
-							$info['warning'][] = 'RIFF.WAVE.SNDM.data contains tag not expected length (expected: '.$SNDM_thisTagSize.', found: '.(4 + 4 + 2 + 2 + $SNDM_thisTagDataSize).') at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')';
+							$this->warning('RIFF.WAVE.SNDM.data contains tag not expected length (expected: '.$SNDM_thisTagSize.', found: '.(4 + 4 + 2 + 2 + $SNDM_thisTagDataSize).') at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')');
 							break;
 						} elseif ($SNDM_thisTagSize <= 0) {
-							$info['warning'][] = 'RIFF.WAVE.SNDM.data contains zero-size tag at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')';
+							$this->warning('RIFF.WAVE.SNDM.data contains zero-size tag at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')');
 							break;
 						}
 						$SNDM_startoffset += $SNDM_thisTagSize;
@@ -397,7 +397,7 @@ class getid3_riff extends getid3_handler {
 						if ($parsedkey = self::waveSNDMtagLookup($SNDM_thisTagKey)) {
 							$thisfile_riff_WAVE_SNDM_0['parsed'][$parsedkey] = $SNDM_thisTagDataText;
 						} else {
-							$info['warning'][] = 'RIFF.WAVE.SNDM contains unknown tag "'.$SNDM_thisTagKey.'" at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')';
+							$this->warning('RIFF.WAVE.SNDM contains unknown tag "'.$SNDM_thisTagKey.'" at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')');
 						}
 					}
 
@@ -428,13 +428,15 @@ class getid3_riff extends getid3_handler {
 						}
 						if (isset($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO']) && !empty($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) && !empty($thisfile_riff_WAVE['iXML'][0]['timecode_rate'])) {
 							$samples_since_midnight = floatval(ltrim($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HI'].$parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO'], '0'));
-							$thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] = $samples_since_midnight / $parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE'];
+							$timestamp_sample_rate = (is_array($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) ? max($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) : $parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']); // XML could possibly contain more than one TIMESTAMP_SAMPLE_RATE tag, returning as array instead of integer [why? does it make sense? perhaps doesn't matter but getID3 needs to deal with it] - see https://github.com/JamesHeinrich/getID3/issues/105
+							$thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] = $samples_since_midnight / $timestamp_sample_rate;
 							$h = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds']       / 3600);
 							$m = floor(($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600))      / 60);
 							$s = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60));
 							$f =       ($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60) - $s) * $thisfile_riff_WAVE['iXML'][0]['timecode_rate'];
 							$thisfile_riff_WAVE['iXML'][0]['timecode_string']       = sprintf('%02d:%02d:%02d:%05.2f', $h, $m, $s,       $f);
 							$thisfile_riff_WAVE['iXML'][0]['timecode_string_round'] = sprintf('%02d:%02d:%02d:%02d',   $h, $m, $s, round($f));
+							unset($samples_since_midnight, $timestamp_sample_rate, $h, $m, $s, $f);
 						}
 						unset($parsedXML);
 					}
@@ -570,7 +572,7 @@ class getid3_riff extends getid3_handler {
 								// byte, in which case - skip warning
 							} else {
 								// Short by more than one byte, throw warning
-								$info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)';
+								$this->warning('Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)');
 								$info['avdataend'] = $info['filesize'];
 							}
 							break;
@@ -579,11 +581,11 @@ class getid3_riff extends getid3_handler {
 							if ((($info['avdataend'] - $info['filesize']) == 1) && (($thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2) == 0) && ((($info['filesize'] - $info['avdataoffset']) % 2) == 1)) {
 								// output file appears to be incorrectly *not* padded to nearest WORD boundary
 								// Output less severe warning
-								$info['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)';
+								$this->warning('File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)');
 								$info['avdataend'] = $info['filesize'];
 							} else {
 								// Short by more than one byte, throw warning
-								$info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)';
+								$this->warning('Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)');
 								$info['avdataend'] = $info['filesize'];
 							}
 							break;
@@ -592,7 +594,7 @@ class getid3_riff extends getid3_handler {
 				if (!empty($info['mpeg']['audio']['LAME']['audio_bytes'])) {
 					if ((($info['avdataend'] - $info['avdataoffset']) - $info['mpeg']['audio']['LAME']['audio_bytes']) == 1) {
 						$info['avdataend']--;
-						$info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored';
+						$this->warning('Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored');
 					}
 				}
 				if (isset($thisfile_audio_dataformat) && ($thisfile_audio_dataformat == 'ac3')) {
@@ -619,7 +621,7 @@ class getid3_riff extends getid3_handler {
 						$info['avdataend'] = $thisfile_riff['AVI ']['movi']['offset'] + $thisfile_riff['AVI ']['movi']['size'];
 					}
 					if ($info['avdataend'] > $info['filesize']) {
-						$info['warning'][] = 'Probably truncated file - expecting '.($info['avdataend'] - $info['avdataoffset']).' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($info['avdataend'] - $info['filesize']).' bytes)';
+						$this->warning('Probably truncated file - expecting '.($info['avdataend'] - $info['avdataoffset']).' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($info['avdataend'] - $info['filesize']).' bytes)');
 						$info['avdataend'] = $info['filesize'];
 					}
 				}
@@ -660,7 +662,7 @@ class getid3_riff extends getid3_handler {
 
 					$thisfile_riff_raw_avih['dwMicroSecPerFrame']    = $this->EitherEndian2Int(substr($avihData,  0, 4)); // frame display rate (or 0L)
 					if ($thisfile_riff_raw_avih['dwMicroSecPerFrame'] == 0) {
-						$info['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero';
+						$this->error('Corrupt RIFF file: avih.dwMicroSecPerFrame == zero');
 						return false;
 					}
 
@@ -858,7 +860,7 @@ class getid3_riff extends getid3_handler {
 											break;
 
 										default:
-											$info['warning'][] = 'Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"';
+											$this->warning('Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"');
 											break;
 
 									}
@@ -963,7 +965,7 @@ class getid3_riff extends getid3_handler {
 							// structures rounded to 2-byte boundary, but dumb encoders
 							// forget to pad end of file to make this actually work
 						} else {
-							$info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found';
+							$this->warning('Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found');
 						}
 						$info['avdataend'] = $info['filesize'];
 					}
@@ -1020,7 +1022,7 @@ class getid3_riff extends getid3_handler {
 					}
 					$thisfile_audio['sample_rate']     = $thisfile_riff_audio['sample_rate'];
 					if ($thisfile_audio['sample_rate'] == 0) {
-						$info['error'][] = 'Corrupted AIFF file: sample_rate == zero';
+						$this->error('Corrupted AIFF file: sample_rate == zero');
 						return false;
 					}
 					$info['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate'];
@@ -1080,7 +1082,7 @@ class getid3_riff extends getid3_handler {
 					$info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8;
 					$info['avdataend']    = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size'];
 					if ($info['avdataend'] > $info['filesize']) {
-						$info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found';
+						$this->warning('Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found');
 					}
 				}
 
@@ -1112,7 +1114,7 @@ class getid3_riff extends getid3_handler {
 							break;
 
 						default:
-							$info['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"';
+							$this->warning('Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"');
 							break;
 					}
 				}
@@ -1130,7 +1132,7 @@ class getid3_riff extends getid3_handler {
 							break;
 
 						default:
-							$info['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"';
+							$this->warning('Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"');
 							break;
 					}
 
@@ -1170,9 +1172,59 @@ class getid3_riff extends getid3_handler {
 				}
 				break;
 
+			case 'WEBP':
+				// https://developers.google.com/speed/webp/docs/riff_container
+				// https://tools.ietf.org/html/rfc6386
+				// https://chromium.googlesource.com/webm/libwebp/+/master/doc/webp-lossless-bitstream-spec.txt
+				$info['fileformat'] = 'webp';
+				$info['mime_type']  = 'image/webp';
+
+				if (!empty($thisfile_riff['WEBP']['VP8 '][0]['size'])) {
+					$old_offset = $this->ftell();
+					$this->fseek($thisfile_riff['WEBP']['VP8 '][0]['offset'] + 8); // 4 bytes "VP8 " + 4 bytes chunk size
+					$WEBP_VP8_header = $this->fread(10);
+					$this->fseek($old_offset);
+					if (substr($WEBP_VP8_header, 3, 3) == "\x9D\x01\x2A") {
+						$thisfile_riff['WEBP']['VP8 '][0]['keyframe']   = !(getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 0, 3)) & 0x800000);
+						$thisfile_riff['WEBP']['VP8 '][0]['version']    =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 0, 3)) & 0x700000) >> 20;
+						$thisfile_riff['WEBP']['VP8 '][0]['show_frame'] =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 0, 3)) & 0x080000);
+						$thisfile_riff['WEBP']['VP8 '][0]['data_bytes'] =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 0, 3)) & 0x07FFFF) >>  0;
+
+						$thisfile_riff['WEBP']['VP8 '][0]['scale_x']    =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 6, 2)) & 0xC000) >> 14;
+						$thisfile_riff['WEBP']['VP8 '][0]['width']      =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 6, 2)) & 0x3FFF);
+						$thisfile_riff['WEBP']['VP8 '][0]['scale_y']    =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 8, 2)) & 0xC000) >> 14;
+						$thisfile_riff['WEBP']['VP8 '][0]['height']     =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 8, 2)) & 0x3FFF);
+
+						$info['video']['resolution_x'] = $thisfile_riff['WEBP']['VP8 '][0]['width'];
+						$info['video']['resolution_y'] = $thisfile_riff['WEBP']['VP8 '][0]['height'];
+					} else {
+						$this->error('Expecting 9D 01 2A at offset '.($thisfile_riff['WEBP']['VP8 '][0]['offset'] + 8 + 3).', found "'.getid3_lib::PrintHexBytes(substr($WEBP_VP8_header, 3, 3)).'"');
+					}
+
+				}
+				if (!empty($thisfile_riff['WEBP']['VP8L'][0]['size'])) {
+					$old_offset = $this->ftell();
+					$this->fseek($thisfile_riff['WEBP']['VP8L'][0]['offset'] + 8); // 4 bytes "VP8L" + 4 bytes chunk size
+					$WEBP_VP8L_header = $this->fread(10);
+					$this->fseek($old_offset);
+					if (substr($WEBP_VP8L_header, 0, 1) == "\x2F") {
+						$width_height_flags = getid3_lib::LittleEndian2Bin(substr($WEBP_VP8L_header, 1, 4));
+						$thisfile_riff['WEBP']['VP8L'][0]['width']         =        bindec(substr($width_height_flags, 18, 14)) + 1;
+						$thisfile_riff['WEBP']['VP8L'][0]['height']        =        bindec(substr($width_height_flags,  4, 14)) + 1;
+						$thisfile_riff['WEBP']['VP8L'][0]['alpha_is_used'] = (bool) bindec(substr($width_height_flags,  3,  1));
+						$thisfile_riff['WEBP']['VP8L'][0]['version']       =        bindec(substr($width_height_flags,  0,  3));
+
+						$info['video']['resolution_x'] = $thisfile_riff['WEBP']['VP8L'][0]['width'];
+						$info['video']['resolution_y'] = $thisfile_riff['WEBP']['VP8L'][0]['height'];
+					} else {
+						$this->error('Expecting 2F at offset '.($thisfile_riff['WEBP']['VP8L'][0]['offset'] + 8).', found "'.getid3_lib::PrintHexBytes(substr($WEBP_VP8L_header, 0, 1)).'"');
+					}
+
+				}
+				break;
 
 			default:
-				$info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead';
+				$this->error('Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA|WEBP), found "'.$RIFFsubtype.'" instead');
 				//unset($info['fileformat']);
 		}
 
@@ -1185,7 +1237,7 @@ class getid3_riff extends getid3_handler {
 				foreach ($ID3v2_keys_bad as $ID3v2_key_bad) {
 					if (isset($thisfile_riff[$RIFFsubtype][$ID3v2_key_bad]) && !array_key_exists($ID3v2_key_good, $thisfile_riff[$RIFFsubtype])) {
 						$thisfile_riff[$RIFFsubtype][$ID3v2_key_good] = $thisfile_riff[$RIFFsubtype][$ID3v2_key_bad];
-						$info['warning'][] = 'mapping "'.$ID3v2_key_bad.'" chunk to "'.$ID3v2_key_good.'"';
+						$this->warning('mapping "'.$ID3v2_key_bad.'" chunk to "'.$ID3v2_key_good.'"');
 					}
 				}
 
@@ -1509,7 +1561,7 @@ class getid3_riff extends getid3_handler {
 											$info['ac3']     = $getid3_temp->info['ac3'];
 											if (!empty($getid3_temp->info['warning'])) {
 												foreach ($getid3_temp->info['warning'] as $key => $value) {
-													$info['warning'][] = $value;
+													$this->warning($value);
 												}
 											}
 										}
@@ -2583,4 +2635,4 @@ class getid3_riff extends getid3_handler {
 		return getid3_lib::BigEndian2Int($byteword, false, $signed);
 	}
 
-}
\ No newline at end of file
+}
diff --git a/src/wp-includes/ID3/module.audio.ac3.php b/src/wp-includes/ID3/module.audio.ac3.php
index 2dc52f4..27c328d 100644
--- a/src/wp-includes/ID3/module.audio.ac3.php
+++ b/src/wp-includes/ID3/module.audio.ac3.php
@@ -20,7 +20,7 @@ class getid3_ac3 extends getid3_handler
     private $AC3header = array();
     private $BSIoffset = 0;
 
-    const syncword = "\x0B\x77";
+    const syncword = 0x0B77;
 
 	public function Analyze() {
 		$info = &$this->getid3->info;
@@ -55,182 +55,418 @@ class getid3_ac3 extends getid3_handler
 		// } /* end of syncinfo */
 
 		$this->fseek($info['avdataoffset']);
-		$this->AC3header['syncinfo'] = $this->fread(5);
+		$tempAC3header = $this->fread(100); // should be enough to cover all data, there are some variable-length fields...?
+		$this->AC3header['syncinfo']  =     getid3_lib::BigEndian2Int(substr($tempAC3header, 0, 2));
+		$this->AC3header['bsi']       =     getid3_lib::BigEndian2Bin(substr($tempAC3header, 2));
+		$thisfile_ac3_raw_bsi['bsid'] = (getid3_lib::LittleEndian2Int(substr($tempAC3header, 5, 1)) & 0xF8) >> 3; // AC3 and E-AC3 put the "bsid" version identifier in the same place, but unfortnately the 4 bytes between the syncword and the version identifier are interpreted differently, so grab it here so the following code structure can make sense
+		unset($tempAC3header);
 
-		if (strpos($this->AC3header['syncinfo'], self::syncword) === 0) {
-			$thisfile_ac3_raw['synchinfo']['synchword'] = self::syncword;
-			$offset = 2;
-		} else {
+		if ($this->AC3header['syncinfo'] !== self::syncword) {
 			if (!$this->isDependencyFor('matroska')) {
 				unset($info['fileformat'], $info['ac3']);
-				return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($this->AC3header['syncinfo'], 0, 2)).'"');
+				return $this->error('Expecting "'.dechex(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.dechex($this->AC3header['syncinfo']).'"');
 			}
-			$offset = 0;
-			$this->fseek(-2, SEEK_CUR);
 		}
 
 		$info['audio']['dataformat']   = 'ac3';
 		$info['audio']['bitrate_mode'] = 'cbr';
 		$info['audio']['lossless']     = false;
 
-		$thisfile_ac3_raw['synchinfo']['crc1']       = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], $offset, 2));
-		$ac3_synchinfo_fscod_frmsizecod              = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], ($offset + 2), 1));
-		$thisfile_ac3_raw['synchinfo']['fscod']      = ($ac3_synchinfo_fscod_frmsizecod & 0xC0) >> 6;
-		$thisfile_ac3_raw['synchinfo']['frmsizecod'] = ($ac3_synchinfo_fscod_frmsizecod & 0x3F);
+		if ($thisfile_ac3_raw_bsi['bsid'] <= 8) {
 
-		$thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']);
-		if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) {
-			$info['audio']['sample_rate'] = $thisfile_ac3['sample_rate'];
-		}
+			$thisfile_ac3_raw_bsi['crc1']       = getid3_lib::Bin2Dec($this->readHeaderBSI(16));
+			$thisfile_ac3_raw_bsi['fscod']      =                     $this->readHeaderBSI(2);   // 5.4.1.3
+			$thisfile_ac3_raw_bsi['frmsizecod'] =                     $this->readHeaderBSI(6);   // 5.4.1.4
+			if ($thisfile_ac3_raw_bsi['frmsizecod'] > 37) { // binary: 100101 - see Table 5.18 Frame Size Code Table (1 word = 16 bits)
+				$this->warning('Unexpected ac3.bsi.frmsizecod value: '.$thisfile_ac3_raw_bsi['frmsizecod'].', bitrate not set correctly');
+			}
 
-		$thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']);
-		$thisfile_ac3['bitrate']      = self::bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']);
-		$info['audio']['bitrate'] = $thisfile_ac3['bitrate'];
+			$thisfile_ac3_raw_bsi['bsid']  = $this->readHeaderBSI(5); // we already know this from pre-parsing the version identifier, but re-read it to let the bitstream flow as intended
+			$thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3);
+			$thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3);
 
-		$this->AC3header['bsi'] = getid3_lib::BigEndian2Bin($this->fread(15));
-		$ac3_bsi_offset = 0;
+			if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) {
+				// If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream.
+				$thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2);
+				$thisfile_ac3['center_mix_level'] = self::centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']);
+			}
 
-		$thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5);
-		if ($thisfile_ac3_raw_bsi['bsid'] > 8) {
-			// Decoders which can decode version 8 will thus be able to decode version numbers less than 8.
-			// If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used.
-			// Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8.
-			$this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8');
-		    unset($info['ac3']);
-			return false;
-		}
+			if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) {
+				// If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream.
+				$thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2);
+				$thisfile_ac3['surround_mix_level'] = self::surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']);
+			}
 
-		$thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3);
-		$thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3);
+			if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) {
+				// When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround.
+				$thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2);
+				$thisfile_ac3['dolby_surround_mode'] = self::dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']);
+			}
 
-		$thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']);
-		$ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']);
-		foreach($ac3_coding_mode as $key => $value) {
-			$thisfile_ac3[$key] = $value;
-		}
-		switch ($thisfile_ac3_raw_bsi['acmod']) {
-			case 0:
-			case 1:
-				$info['audio']['channelmode'] = 'mono';
-				break;
-			case 3:
-			case 4:
-				$info['audio']['channelmode'] = 'stereo';
-				break;
-			default:
-				$info['audio']['channelmode'] = 'surround';
-				break;
-		}
-		$info['audio']['channels'] = $thisfile_ac3['num_channels'];
+			$thisfile_ac3_raw_bsi['flags']['lfeon'] = (bool) $this->readHeaderBSI(1);
 
-		if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) {
-			// If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream.
-			$thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2);
-			$thisfile_ac3['center_mix_level'] = self::centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']);
-		}
+			// This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31.
+			// The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
+			$thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5);                 // 5.4.2.8 dialnorm: Dialogue Normalization, 5 Bits
 
-		if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) {
-			// If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream.
-			$thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2);
-			$thisfile_ac3['surround_mix_level'] = self::surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']);
-		}
+			$thisfile_ac3_raw_bsi['flags']['compr'] = (bool) $this->readHeaderBSI(1);       // 5.4.2.9 compre: Compression Gain Word Exists, 1 Bit
+			if ($thisfile_ac3_raw_bsi['flags']['compr']) {
+				$thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8);                // 5.4.2.10 compr: Compression Gain Word, 8 Bits
+				$thisfile_ac3['heavy_compression'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr']);
+			}
 
-		if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) {
-			// When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround.
-			$thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2);
-			$thisfile_ac3['dolby_surround_mode'] = self::dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']);
-		}
+			$thisfile_ac3_raw_bsi['flags']['langcod'] = (bool) $this->readHeaderBSI(1);     // 5.4.2.11 langcode: Language Code Exists, 1 Bit
+			if ($thisfile_ac3_raw_bsi['flags']['langcod']) {
+				$thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8);              // 5.4.2.12 langcod: Language Code, 8 Bits
+			}
 
-		$thisfile_ac3_raw_bsi['lfeon'] = (bool) $this->readHeaderBSI(1);
-		$thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['lfeon'];
-		if ($thisfile_ac3_raw_bsi['lfeon']) {
-			//$info['audio']['channels']++;
-			$info['audio']['channels'] .= '.1';
-		}
+			$thisfile_ac3_raw_bsi['flags']['audprodinfo'] = (bool) $this->readHeaderBSI(1);  // 5.4.2.13 audprodie: Audio Production Information Exists, 1 Bit
+			if ($thisfile_ac3_raw_bsi['flags']['audprodinfo']) {
+				$thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5);             // 5.4.2.14 mixlevel: Mixing Level, 5 Bits
+				$thisfile_ac3_raw_bsi['roomtyp']  = $this->readHeaderBSI(2);             // 5.4.2.15 roomtyp: Room Type, 2 Bits
 
-		$thisfile_ac3['channels_enabled'] = self::channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']);
+				$thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB';
+				$thisfile_ac3['room_type']    = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']);
+			}
 
-		// This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31.
-		// The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
-		$thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5);
-		$thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB';
 
-		$thisfile_ac3_raw_bsi['compre_flag'] = (bool) $this->readHeaderBSI(1);
-		if ($thisfile_ac3_raw_bsi['compre_flag']) {
-			$thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8);
-			$thisfile_ac3['heavy_compression'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr']);
-		}
+			$thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5);                // 5.4.2.16 dialnorm2: Dialogue Normalization, ch2, 5 Bits
+			$thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB';  // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31. The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
 
-		$thisfile_ac3_raw_bsi['langcode_flag'] = (bool) $this->readHeaderBSI(1);
-		if ($thisfile_ac3_raw_bsi['langcode_flag']) {
-			$thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8);
-		}
+			$thisfile_ac3_raw_bsi['flags']['compr2'] = (bool) $this->readHeaderBSI(1);       // 5.4.2.17 compr2e: Compression Gain Word Exists, ch2, 1 Bit
+			if ($thisfile_ac3_raw_bsi['flags']['compr2']) {
+				$thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8);               // 5.4.2.18 compr2: Compression Gain Word, ch2, 8 Bits
+				$thisfile_ac3['heavy_compression2'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr2']);
+			}
 
-		$thisfile_ac3_raw_bsi['audprodie'] = (bool) $this->readHeaderBSI(1);
-		if ($thisfile_ac3_raw_bsi['audprodie']) {
-			$thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5);
-			$thisfile_ac3_raw_bsi['roomtyp']  = $this->readHeaderBSI(2);
+			$thisfile_ac3_raw_bsi['flags']['langcod2'] = (bool) $this->readHeaderBSI(1);    // 5.4.2.19 langcod2e: Language Code Exists, ch2, 1 Bit
+			if ($thisfile_ac3_raw_bsi['flags']['langcod2']) {
+				$thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8);             // 5.4.2.20 langcod2: Language Code, ch2, 8 Bits
+			}
 
-			$thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB';
-			$thisfile_ac3['room_type']    = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']);
-		}
+			$thisfile_ac3_raw_bsi['flags']['audprodinfo2'] = (bool) $this->readHeaderBSI(1); // 5.4.2.21 audprodi2e: Audio Production Information Exists, ch2, 1 Bit
+			if ($thisfile_ac3_raw_bsi['flags']['audprodinfo2']) {
+				$thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5);            // 5.4.2.22 mixlevel2: Mixing Level, ch2, 5 Bits
+				$thisfile_ac3_raw_bsi['roomtyp2']  = $this->readHeaderBSI(2);            // 5.4.2.23 roomtyp2: Room Type, ch2, 2 Bits
+
+				$thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB';
+				$thisfile_ac3['room_type2']    = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']);
+			}
 
-		if ($thisfile_ac3_raw_bsi['acmod'] == 0x00) {
-			// If acmod is 0, then two completely independent program channels (dual mono)
-			// are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case,
-			// a number of additional items are present in BSI or audblk to fully describe Ch2.
+			$thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1);         // 5.4.2.24 copyrightb: Copyright Bit, 1 Bit
 
-			// This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31.
-			// The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
-			$thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5);
-			$thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB';
+			$thisfile_ac3_raw_bsi['original']  = (bool) $this->readHeaderBSI(1);         // 5.4.2.25 origbs: Original Bit Stream, 1 Bit
 
-			$thisfile_ac3_raw_bsi['compre_flag2'] = (bool) $this->readHeaderBSI(1);
-			if ($thisfile_ac3_raw_bsi['compre_flag2']) {
-				$thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8);
-				$thisfile_ac3['heavy_compression2'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr2']);
+			$thisfile_ac3_raw_bsi['flags']['timecod1'] = $this->readHeaderBSI(2);            // 5.4.2.26 timecod1e, timcode2e: Time Code (first and second) Halves Exist, 2 Bits
+			if ($thisfile_ac3_raw_bsi['flags']['timecod1'] & 0x01) {
+				$thisfile_ac3_raw_bsi['timecod1'] = $this->readHeaderBSI(14);            // 5.4.2.27 timecod1: Time code first half, 14 bits
+				$thisfile_ac3['timecode1'] = 0;
+				$thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x3E00) >>  9) * 3600;  // The first 5 bits of this 14-bit field represent the time in hours, with valid values of 0�23
+				$thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x01F8) >>  3) *   60;  // The next 6 bits represent the time in minutes, with valid values of 0�59
+				$thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x0003) >>  0) *    8;  // The final 3 bits represents the time in 8 second increments, with valid values of 0�7 (representing 0, 8, 16, ... 56 seconds)
 			}
-
-			$thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) $this->readHeaderBSI(1);
-			if ($thisfile_ac3_raw_bsi['langcode_flag2']) {
-				$thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8);
+			if ($thisfile_ac3_raw_bsi['flags']['timecod1'] & 0x02) {
+				$thisfile_ac3_raw_bsi['timecod2'] = $this->readHeaderBSI(14);            // 5.4.2.28 timecod2: Time code second half, 14 bits
+				$thisfile_ac3['timecode2'] = 0;
+				$thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x3800) >> 11) *   1;              // The first 3 bits of this 14-bit field represent the time in seconds, with valid values from 0�7 (representing 0-7 seconds)
+				$thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x07C0) >>  6) *  (1 / 30);        // The next 5 bits represents the time in frames, with valid values from 0�29 (one frame = 1/30th of a second)
+				$thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x003F) >>  0) * ((1 / 30) / 60);  // The final 6 bits represents fractions of 1/64 of a frame, with valid values from 0�63
 			}
 
-			$thisfile_ac3_raw_bsi['audprodie2'] = (bool) $this->readHeaderBSI(1);
-			if ($thisfile_ac3_raw_bsi['audprodie2']) {
-				$thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5);
-				$thisfile_ac3_raw_bsi['roomtyp2']  = $this->readHeaderBSI(2);
+			$thisfile_ac3_raw_bsi['flags']['addbsi'] = (bool) $this->readHeaderBSI(1);
+			if ($thisfile_ac3_raw_bsi['flags']['addbsi']) {
+				$thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6) + 1; // This 6-bit code, which exists only if addbside is a 1, indicates the length in bytes of additional bit stream information. The valid range of addbsil is 0�63, indicating 1�64 additional bytes, respectively.
 
-				$thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB';
-				$thisfile_ac3['room_type2']    = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']);
+				$this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin($this->fread($thisfile_ac3_raw_bsi['addbsi_length']));
+
+				$thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8);
+				$this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8;
 			}
 
-		}
 
-		$thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1);
+		} elseif ($thisfile_ac3_raw_bsi['bsid'] <= 16) { // E-AC3
 
-		$thisfile_ac3_raw_bsi['original']  = (bool) $this->readHeaderBSI(1);
 
-		$thisfile_ac3_raw_bsi['timecode1_flag'] = (bool) $this->readHeaderBSI(1);
-		if ($thisfile_ac3_raw_bsi['timecode1_flag']) {
-			$thisfile_ac3_raw_bsi['timecode1'] = $this->readHeaderBSI(14);
-		}
+$this->error('E-AC3 parsing is incomplete and experimental in this version of getID3 ('.$this->getid3->version().'). Notably the bitrate calculations are wrong -- value might (or not) be correct, but it is not calculated correctly. Email info@getid3.org if you know how to calculate EAC3 bitrate correctly.');
+			$info['audio']['dataformat'] = 'eac3';
+
+			$thisfile_ac3_raw_bsi['strmtyp']          =        $this->readHeaderBSI(2);
+			$thisfile_ac3_raw_bsi['substreamid']      =        $this->readHeaderBSI(3);
+			$thisfile_ac3_raw_bsi['frmsiz']           =        $this->readHeaderBSI(11);
+			$thisfile_ac3_raw_bsi['fscod']            =        $this->readHeaderBSI(2);
+			if ($thisfile_ac3_raw_bsi['fscod'] == 3) {
+				$thisfile_ac3_raw_bsi['fscod2']       =        $this->readHeaderBSI(2);
+				$thisfile_ac3_raw_bsi['numblkscod'] = 3; // six blocks per syncframe
+			} else {
+				$thisfile_ac3_raw_bsi['numblkscod']   =        $this->readHeaderBSI(2);
+			}
+			$thisfile_ac3['bsi']['blocks_per_sync_frame'] = self::blocksPerSyncFrame($thisfile_ac3_raw_bsi['numblkscod']);
+			$thisfile_ac3_raw_bsi['acmod']            =        $this->readHeaderBSI(3);
+			$thisfile_ac3_raw_bsi['flags']['lfeon']   = (bool) $this->readHeaderBSI(1);
+			$thisfile_ac3_raw_bsi['bsid']             =        $this->readHeaderBSI(5); // we already know this from pre-parsing the version identifier, but re-read it to let the bitstream flow as intended
+			$thisfile_ac3_raw_bsi['dialnorm']         =        $this->readHeaderBSI(5);
+			$thisfile_ac3_raw_bsi['flags']['compr']       = (bool) $this->readHeaderBSI(1);
+			if ($thisfile_ac3_raw_bsi['flags']['compr']) {
+				$thisfile_ac3_raw_bsi['compr']        =        $this->readHeaderBSI(8);
+			}
+			if ($thisfile_ac3_raw_bsi['acmod'] == 0) { // if 1+1 mode (dual mono, so some items need a second value)
+				$thisfile_ac3_raw_bsi['dialnorm2']    =        $this->readHeaderBSI(5);
+				$thisfile_ac3_raw_bsi['flags']['compr2']  = (bool) $this->readHeaderBSI(1);
+				if ($thisfile_ac3_raw_bsi['flags']['compr2']) {
+					$thisfile_ac3_raw_bsi['compr2']   =        $this->readHeaderBSI(8);
+				}
+			}
+			if ($thisfile_ac3_raw_bsi['strmtyp'] == 1) { // if dependent stream
+				$thisfile_ac3_raw_bsi['flags']['chanmap'] = (bool) $this->readHeaderBSI(1);
+				if ($thisfile_ac3_raw_bsi['flags']['chanmap']) {
+					$thisfile_ac3_raw_bsi['chanmap']  =        $this->readHeaderBSI(8);
+				}
+			}
+			$thisfile_ac3_raw_bsi['flags']['mixmdat']     = (bool) $this->readHeaderBSI(1);
+			if ($thisfile_ac3_raw_bsi['flags']['mixmdat']) { // Mixing metadata
+				if ($thisfile_ac3_raw_bsi['acmod'] > 2) { // if more than 2 channels
+					$thisfile_ac3_raw_bsi['dmixmod']  =        $this->readHeaderBSI(2);
+				}
+				if (($thisfile_ac3_raw_bsi['acmod'] & 0x01) && ($thisfile_ac3_raw_bsi['acmod'] > 2)) { // if three front channels exist
+					$thisfile_ac3_raw_bsi['ltrtcmixlev'] =        $this->readHeaderBSI(3);
+					$thisfile_ac3_raw_bsi['lorocmixlev'] =        $this->readHeaderBSI(3);
+				}
+				if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) { // if a surround channel exists
+					$thisfile_ac3_raw_bsi['ltrtsurmixlev'] =        $this->readHeaderBSI(3);
+					$thisfile_ac3_raw_bsi['lorosurmixlev'] =        $this->readHeaderBSI(3);
+				}
+				if ($thisfile_ac3_raw_bsi['flags']['lfeon']) { // if the LFE channel exists
+					$thisfile_ac3_raw_bsi['flags']['lfemixlevcod'] = (bool) $this->readHeaderBSI(1);
+					if ($thisfile_ac3_raw_bsi['flags']['lfemixlevcod']) {
+						$thisfile_ac3_raw_bsi['lfemixlevcod']  =        $this->readHeaderBSI(5);
+					}
+				}
+				if ($thisfile_ac3_raw_bsi['strmtyp'] == 0) { // if independent stream
+					$thisfile_ac3_raw_bsi['flags']['pgmscl'] = (bool) $this->readHeaderBSI(1);
+					if ($thisfile_ac3_raw_bsi['flags']['pgmscl']) {
+						$thisfile_ac3_raw_bsi['pgmscl']  =        $this->readHeaderBSI(6);
+					}
+					if ($thisfile_ac3_raw_bsi['acmod'] == 0) { // if 1+1 mode (dual mono, so some items need a second value)
+						$thisfile_ac3_raw_bsi['flags']['pgmscl2'] = (bool) $this->readHeaderBSI(1);
+						if ($thisfile_ac3_raw_bsi['flags']['pgmscl2']) {
+							$thisfile_ac3_raw_bsi['pgmscl2']  =        $this->readHeaderBSI(6);
+						}
+					}
+					$thisfile_ac3_raw_bsi['flags']['extpgmscl'] = (bool) $this->readHeaderBSI(1);
+					if ($thisfile_ac3_raw_bsi['flags']['extpgmscl']) {
+						$thisfile_ac3_raw_bsi['extpgmscl']  =        $this->readHeaderBSI(6);
+					}
+					$thisfile_ac3_raw_bsi['mixdef']  =        $this->readHeaderBSI(2);
+					if ($thisfile_ac3_raw_bsi['mixdef'] == 1) { // mixing option 2
+						$thisfile_ac3_raw_bsi['premixcmpsel']  = (bool) $this->readHeaderBSI(1);
+						$thisfile_ac3_raw_bsi['drcsrc']        = (bool) $this->readHeaderBSI(1);
+						$thisfile_ac3_raw_bsi['premixcmpscl']  =        $this->readHeaderBSI(3);
+					} elseif ($thisfile_ac3_raw_bsi['mixdef'] == 2) { // mixing option 3
+						$thisfile_ac3_raw_bsi['mixdata']       =        $this->readHeaderBSI(12);
+					} elseif ($thisfile_ac3_raw_bsi['mixdef'] == 3) { // mixing option 4
+						$mixdefbitsread = 0;
+						$thisfile_ac3_raw_bsi['mixdeflen']     =        $this->readHeaderBSI(5); $mixdefbitsread += 5;
+						$thisfile_ac3_raw_bsi['flags']['mixdata2'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+						if ($thisfile_ac3_raw_bsi['flags']['mixdata2']) {
+							$thisfile_ac3_raw_bsi['premixcmpsel']  = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							$thisfile_ac3_raw_bsi['drcsrc']        = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							$thisfile_ac3_raw_bsi['premixcmpscl']  =        $this->readHeaderBSI(3); $mixdefbitsread += 3;
+							$thisfile_ac3_raw_bsi['flags']['extpgmlscl']   = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							if ($thisfile_ac3_raw_bsi['flags']['extpgmlscl']) {
+								$thisfile_ac3_raw_bsi['extpgmlscl']    =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
+							}
+							$thisfile_ac3_raw_bsi['flags']['extpgmcscl']   = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							if ($thisfile_ac3_raw_bsi['flags']['extpgmcscl']) {
+								$thisfile_ac3_raw_bsi['extpgmcscl']    =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
+							}
+							$thisfile_ac3_raw_bsi['flags']['extpgmrscl']   = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							if ($thisfile_ac3_raw_bsi['flags']['extpgmrscl']) {
+								$thisfile_ac3_raw_bsi['extpgmrscl']    =        $this->readHeaderBSI(4);
+							}
+							$thisfile_ac3_raw_bsi['flags']['extpgmlsscl']  = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							if ($thisfile_ac3_raw_bsi['flags']['extpgmlsscl']) {
+								$thisfile_ac3_raw_bsi['extpgmlsscl']   =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
+							}
+							$thisfile_ac3_raw_bsi['flags']['extpgmrsscl']  = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							if ($thisfile_ac3_raw_bsi['flags']['extpgmrsscl']) {
+								$thisfile_ac3_raw_bsi['extpgmrsscl']   =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
+							}
+							$thisfile_ac3_raw_bsi['flags']['extpgmlfescl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							if ($thisfile_ac3_raw_bsi['flags']['extpgmlfescl']) {
+								$thisfile_ac3_raw_bsi['extpgmlfescl']  =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
+							}
+							$thisfile_ac3_raw_bsi['flags']['dmixscl']      = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							if ($thisfile_ac3_raw_bsi['flags']['dmixscl']) {
+								$thisfile_ac3_raw_bsi['dmixscl']       =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
+							}
+							$thisfile_ac3_raw_bsi['flags']['addch']        = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							if ($thisfile_ac3_raw_bsi['flags']['addch']) {
+								$thisfile_ac3_raw_bsi['flags']['extpgmaux1scl']   = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+								if ($thisfile_ac3_raw_bsi['flags']['extpgmaux1scl']) {
+									$thisfile_ac3_raw_bsi['extpgmaux1scl']    =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
+								}
+								$thisfile_ac3_raw_bsi['flags']['extpgmaux2scl']   = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+								if ($thisfile_ac3_raw_bsi['flags']['extpgmaux2scl']) {
+									$thisfile_ac3_raw_bsi['extpgmaux2scl']    =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
+								}
+							}
+						}
+						$thisfile_ac3_raw_bsi['flags']['mixdata3'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+						if ($thisfile_ac3_raw_bsi['flags']['mixdata3']) {
+							$thisfile_ac3_raw_bsi['spchdat']   =        $this->readHeaderBSI(5); $mixdefbitsread += 5;
+							$thisfile_ac3_raw_bsi['flags']['addspchdat'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+							if ($thisfile_ac3_raw_bsi['flags']['addspchdat']) {
+								$thisfile_ac3_raw_bsi['spchdat1']   =         $this->readHeaderBSI(5); $mixdefbitsread += 5;
+								$thisfile_ac3_raw_bsi['spchan1att'] =         $this->readHeaderBSI(2); $mixdefbitsread += 2;
+								$thisfile_ac3_raw_bsi['flags']['addspchdat1'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
+								if ($thisfile_ac3_raw_bsi['flags']['addspchdat1']) {
+									$thisfile_ac3_raw_bsi['spchdat2']   =         $this->readHeaderBSI(5); $mixdefbitsread += 5;
+									$thisfile_ac3_raw_bsi['spchan2att'] =         $this->readHeaderBSI(3); $mixdefbitsread += 3;
+								}
+							}
+						}
+						$mixdata_bits = (8 * ($thisfile_ac3_raw_bsi['mixdeflen'] + 2)) - $mixdefbitsread;
+						$mixdata_fill = (($mixdata_bits % 8) ? 8 - ($mixdata_bits % 8) : 0);
+						$thisfile_ac3_raw_bsi['mixdata']     =        $this->readHeaderBSI($mixdata_bits);
+						$thisfile_ac3_raw_bsi['mixdatafill'] =        $this->readHeaderBSI($mixdata_fill);
+						unset($mixdefbitsread, $mixdata_bits, $mixdata_fill);
+					}
+					if ($thisfile_ac3_raw_bsi['acmod'] < 2) { // if mono or dual mono source
+						$thisfile_ac3_raw_bsi['flags']['paninfo'] = (bool) $this->readHeaderBSI(1);
+						if ($thisfile_ac3_raw_bsi['flags']['paninfo']) {
+							$thisfile_ac3_raw_bsi['panmean']   =        $this->readHeaderBSI(8);
+							$thisfile_ac3_raw_bsi['paninfo']   =        $this->readHeaderBSI(6);
+						}
+						if ($thisfile_ac3_raw_bsi['acmod'] == 0) { // if 1+1 mode (dual mono, so some items need a second value)
+							$thisfile_ac3_raw_bsi['flags']['paninfo2'] = (bool) $this->readHeaderBSI(1);
+							if ($thisfile_ac3_raw_bsi['flags']['paninfo2']) {
+								$thisfile_ac3_raw_bsi['panmean2']   =        $this->readHeaderBSI(8);
+								$thisfile_ac3_raw_bsi['paninfo2']   =        $this->readHeaderBSI(6);
+							}
+						}
+					}
+					$thisfile_ac3_raw_bsi['flags']['frmmixcfginfo'] = (bool) $this->readHeaderBSI(1);
+					if ($thisfile_ac3_raw_bsi['flags']['frmmixcfginfo']) { // mixing configuration information
+						if ($thisfile_ac3_raw_bsi['numblkscod'] == 0) {
+							$thisfile_ac3_raw_bsi['blkmixcfginfo'][0]  =        $this->readHeaderBSI(5);
+						} else {
+							for ($blk = 0; $blk < $thisfile_ac3_raw_bsi['numblkscod']; $blk++) {
+								$thisfile_ac3_raw_bsi['flags']['blkmixcfginfo'.$blk] = (bool) $this->readHeaderBSI(1);
+								if ($thisfile_ac3_raw_bsi['flags']['blkmixcfginfo'.$blk]) { // mixing configuration information
+									$thisfile_ac3_raw_bsi['blkmixcfginfo'][$blk]  =        $this->readHeaderBSI(5);
+								}
+							}
+						}
+					}
+				}
+			}
+			$thisfile_ac3_raw_bsi['flags']['infomdat']          = (bool) $this->readHeaderBSI(1);
+			if ($thisfile_ac3_raw_bsi['flags']['infomdat']) { // Informational metadata
+				$thisfile_ac3_raw_bsi['bsmod']                  =        $this->readHeaderBSI(3);
+				$thisfile_ac3_raw_bsi['flags']['copyrightb']    = (bool) $this->readHeaderBSI(1);
+				$thisfile_ac3_raw_bsi['flags']['origbs']        = (bool) $this->readHeaderBSI(1);
+				if ($thisfile_ac3_raw_bsi['acmod'] == 2) { //  if in 2/0 mode
+					$thisfile_ac3_raw_bsi['dsurmod']            =        $this->readHeaderBSI(2);
+					$thisfile_ac3_raw_bsi['dheadphonmod']       =        $this->readHeaderBSI(2);
+				}
+				if ($thisfile_ac3_raw_bsi['acmod'] >= 6) { //  if both surround channels exist
+					$thisfile_ac3_raw_bsi['dsurexmod']          =        $this->readHeaderBSI(2);
+				}
+				$thisfile_ac3_raw_bsi['flags']['audprodi']      = (bool) $this->readHeaderBSI(1);
+				if ($thisfile_ac3_raw_bsi['flags']['audprodi']) {
+					$thisfile_ac3_raw_bsi['mixlevel']           =        $this->readHeaderBSI(5);
+					$thisfile_ac3_raw_bsi['roomtyp']            =        $this->readHeaderBSI(2);
+					$thisfile_ac3_raw_bsi['flags']['adconvtyp'] = (bool) $this->readHeaderBSI(1);
+				}
+				if ($thisfile_ac3_raw_bsi['acmod'] == 0) { //  if 1+1 mode (dual mono, so some items need a second value)
+					$thisfile_ac3_raw_bsi['flags']['audprodi2']      = (bool) $this->readHeaderBSI(1);
+					if ($thisfile_ac3_raw_bsi['flags']['audprodi2']) {
+						$thisfile_ac3_raw_bsi['mixlevel2']           =        $this->readHeaderBSI(5);
+						$thisfile_ac3_raw_bsi['roomtyp2']            =        $this->readHeaderBSI(2);
+						$thisfile_ac3_raw_bsi['flags']['adconvtyp2'] = (bool) $this->readHeaderBSI(1);
+					}
+				}
+				if ($thisfile_ac3_raw_bsi['fscod'] < 3) { // if not half sample rate
+					$thisfile_ac3_raw_bsi['flags']['sourcefscod'] = (bool) $this->readHeaderBSI(1);
+				}
+			}
+			if (($thisfile_ac3_raw_bsi['strmtyp'] == 0) && ($thisfile_ac3_raw_bsi['numblkscod'] != 3)) { //  if both surround channels exist
+				$thisfile_ac3_raw_bsi['flags']['convsync'] = (bool) $this->readHeaderBSI(1);
+			}
+			if ($thisfile_ac3_raw_bsi['strmtyp'] == 2) { //  if bit stream converted from AC-3
+				if ($thisfile_ac3_raw_bsi['numblkscod'] != 3) { // 6 blocks per syncframe
+					$thisfile_ac3_raw_bsi['flags']['blkid']  = 1;
+				} else {
+					$thisfile_ac3_raw_bsi['flags']['blkid']  = (bool) $this->readHeaderBSI(1);
+				}
+				if ($thisfile_ac3_raw_bsi['flags']['blkid']) {
+					$thisfile_ac3_raw_bsi['frmsizecod']  =        $this->readHeaderBSI(6);
+				}
+			}
+			$thisfile_ac3_raw_bsi['flags']['addbsi']  = (bool) $this->readHeaderBSI(1);
+			if ($thisfile_ac3_raw_bsi['flags']['addbsi']) {
+				$thisfile_ac3_raw_bsi['addbsil']  =        $this->readHeaderBSI(6);
+				$thisfile_ac3_raw_bsi['addbsi']   =        $this->readHeaderBSI(($thisfile_ac3_raw_bsi['addbsil'] + 1) * 8);
+			}
+
+		} else {
+
+			$this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 16. Please submit a support ticket with a sample file.');
+		    unset($info['ac3']);
+			return false;
 
-		$thisfile_ac3_raw_bsi['timecode2_flag'] = (bool) $this->readHeaderBSI(1);
-		if ($thisfile_ac3_raw_bsi['timecode2_flag']) {
-			$thisfile_ac3_raw_bsi['timecode2'] = $this->readHeaderBSI(14);
 		}
 
-		$thisfile_ac3_raw_bsi['addbsi_flag'] = (bool) $this->readHeaderBSI(1);
-		if ($thisfile_ac3_raw_bsi['addbsi_flag']) {
-			$thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6);
+		if (isset($thisfile_ac3_raw_bsi['fscod2'])) {
+			$thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup2($thisfile_ac3_raw_bsi['fscod2']);
+		} else {
+			$thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup($thisfile_ac3_raw_bsi['fscod']);
+		}
+		if ($thisfile_ac3_raw_bsi['fscod'] <= 3) {
+			$info['audio']['sample_rate'] = $thisfile_ac3['sample_rate'];
+		} else {
+			$this->warning('Unexpected ac3.bsi.fscod value: '.$thisfile_ac3_raw_bsi['fscod']);
+		}
+		if (isset($thisfile_ac3_raw_bsi['frmsizecod'])) {
+			$thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw_bsi['frmsizecod'], $thisfile_ac3_raw_bsi['fscod']);
+			$thisfile_ac3['bitrate']      = self::bitrateLookup($thisfile_ac3_raw_bsi['frmsizecod']);
+		} elseif (!empty($thisfile_ac3_raw_bsi['frmsiz'])) {
+// this isn't right, but it's (usually) close, roughly 5% less than it should be.
+// but WHERE is the actual bitrate value stored in EAC3?? email info@getid3.org if you know!
+			$thisfile_ac3['bitrate']      = ($thisfile_ac3_raw_bsi['frmsiz'] + 1) * 16 * 30; // The frmsiz field shall contain a value one less than the overall size of the coded syncframe in 16-bit words. That is, this field may assume a value ranging from 0 to 2047, and these values correspond to syncframe sizes ranging from 1 to 2048.
+// kludge-fix to make it approximately the expected value, still not "right":
+$thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16000;
+		}
+		$info['audio']['bitrate'] = $thisfile_ac3['bitrate'];
 
-			$this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin($this->fread($thisfile_ac3_raw_bsi['addbsi_length']));
+		$thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']);
+		$ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']);
+		foreach($ac3_coding_mode as $key => $value) {
+			$thisfile_ac3[$key] = $value;
+		}
+		switch ($thisfile_ac3_raw_bsi['acmod']) {
+			case 0:
+			case 1:
+				$info['audio']['channelmode'] = 'mono';
+				break;
+			case 3:
+			case 4:
+				$info['audio']['channelmode'] = 'stereo';
+				break;
+			default:
+				$info['audio']['channelmode'] = 'surround';
+				break;
+		}
+		$info['audio']['channels'] = $thisfile_ac3['num_channels'];
 
-			$thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8);
-			$this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8;
+		$thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['flags']['lfeon'];
+		if ($thisfile_ac3_raw_bsi['flags']['lfeon']) {
+			$info['audio']['channels'] .= '.1';
 		}
 
+		$thisfile_ac3['channels_enabled'] = self::channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['flags']['lfeon']);
+		$thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB';
+
 		return true;
 	}
 
@@ -251,6 +487,16 @@ class getid3_ac3 extends getid3_handler
 		return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false);
 	}
 
+	public static function sampleRateCodeLookup2($fscod2) {
+		static $sampleRateCodeLookup2 = array(
+			0 => 24000,
+			1 => 22050,
+			2 => 16000,
+			3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute.
+		);
+		return (isset($sampleRateCodeLookup2[$fscod2]) ? $sampleRateCodeLookup2[$fscod2] : false);
+	}
+
 	public static function serviceTypeLookup($bsmod, $acmod) {
 		static $serviceTypeLookup = array();
 		if (empty($serviceTypeLookup)) {
@@ -409,31 +655,32 @@ class getid3_ac3 extends getid3_handler
 	}
 
 	public static function frameSizeLookup($frmsizecod, $fscod) {
-		$padding     = (bool) ($frmsizecod % 2);
-		$framesizeid =   floor($frmsizecod / 2);
+		// LSB is whether padding is used or not
+		$padding     = (bool) ($frmsizecod & 0x01);
+		$framesizeid =        ($frmsizecod & 0x3E) >> 1;
 
 		static $frameSizeLookup = array();
 		if (empty($frameSizeLookup)) {
 			$frameSizeLookup = array (
-				0  => array(128, 138, 192),
-				1  => array(40, 160, 174, 240),
-				2  => array(48, 192, 208, 288),
-				3  => array(56, 224, 242, 336),
-				4  => array(64, 256, 278, 384),
-				5  => array(80, 320, 348, 480),
-				6  => array(96, 384, 416, 576),
-				7  => array(112, 448, 486, 672),
-				8  => array(128, 512, 556, 768),
-				9  => array(160, 640, 696, 960),
-				10 => array(192, 768, 834, 1152),
-				11 => array(224, 896, 974, 1344),
-				12 => array(256, 1024, 1114, 1536),
-				13 => array(320, 1280, 1392, 1920),
-				14 => array(384, 1536, 1670, 2304),
-				15 => array(448, 1792, 1950, 2688),
-				16 => array(512, 2048, 2228, 3072),
-				17 => array(576, 2304, 2506, 3456),
-				18 => array(640, 2560, 2786, 3840)
+				0  => array( 128,  138,  192),  //  32 kbps
+				1  => array( 160,  174,  240),  //  40 kbps
+				2  => array( 192,  208,  288),  //  48 kbps
+				3  => array( 224,  242,  336),  //  56 kbps
+				4  => array( 256,  278,  384),  //  64 kbps
+				5  => array( 320,  348,  480),  //  80 kbps
+				6  => array( 384,  416,  576),  //  96 kbps
+				7  => array( 448,  486,  672),  // 112 kbps
+				8  => array( 512,  556,  768),  // 128 kbps
+				9  => array( 640,  696,  960),  // 160 kbps
+				10 => array( 768,  834, 1152),  // 192 kbps
+				11 => array( 896,  974, 1344),  // 224 kbps
+				12 => array(1024, 1114, 1536),  // 256 kbps
+				13 => array(1280, 1392, 1920),  // 320 kbps
+				14 => array(1536, 1670, 2304),  // 384 kbps
+				15 => array(1792, 1950, 2688),  // 448 kbps
+				16 => array(2048, 2228, 3072),  // 512 kbps
+				17 => array(2304, 2506, 3456),  // 576 kbps
+				18 => array(2560, 2786, 3840)   // 640 kbps
 			);
 		}
 		if (($fscod == 1) && $padding) {
@@ -444,19 +691,21 @@ class getid3_ac3 extends getid3_handler
 	}
 
 	public static function bitrateLookup($frmsizecod) {
-		$framesizeid =   floor($frmsizecod / 2);
+		// LSB is whether padding is used or not
+		$padding     = (bool) ($frmsizecod & 0x01);
+		$framesizeid =        ($frmsizecod & 0x3E) >> 1;
 
 		static $bitrateLookup = array(
-			0  => 32000,
-			1  => 40000,
-			2  => 48000,
-			3  => 56000,
-			4  => 64000,
-			5  => 80000,
-			6  => 96000,
-			7  => 112000,
-			8  => 128000,
-			9  => 160000,
+			 0 =>  32000,
+			 1 =>  40000,
+			 2 =>  48000,
+			 3 =>  56000,
+			 4 =>  64000,
+			 5 =>  80000,
+			 6 =>  96000,
+			 7 => 112000,
+			 8 => 128000,
+			 9 => 160000,
 			10 => 192000,
 			11 => 224000,
 			12 => 256000,
@@ -465,10 +714,20 @@ class getid3_ac3 extends getid3_handler
 			15 => 448000,
 			16 => 512000,
 			17 => 576000,
-			18 => 640000
+			18 => 640000,
 		);
 		return (isset($bitrateLookup[$framesizeid]) ? $bitrateLookup[$framesizeid] : false);
 	}
 
+	public static function blocksPerSyncFrame($numblkscod) {
+		static $blocksPerSyncFrameLookup = array(
+			0 => 1,
+			1 => 2,
+			2 => 3,
+			3 => 6,
+		);
+		return (isset($blocksPerSyncFrameLookup[$numblkscod]) ? $blocksPerSyncFrameLookup[$numblkscod] : false);
+	}
+
 
 }
diff --git a/src/wp-includes/ID3/module.audio.mp3.php b/src/wp-includes/ID3/module.audio.mp3.php
index 329f7a6..ed2d844 100644
--- a/src/wp-includes/ID3/module.audio.mp3.php
+++ b/src/wp-includes/ID3/module.audio.mp3.php
@@ -34,7 +34,7 @@ class getid3_mp3 extends getid3_handler
 
 		if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) {
 			if ($this->allow_bruteforce) {
-				$info['error'][] = 'Rescanning file in BruteForce mode';
+				$this->error('Rescanning file in BruteForce mode');
 				$this->getOnlyMPEGaudioInfoBruteForce($this->getid3->fp, $info);
 			}
 		}
@@ -72,7 +72,7 @@ class getid3_mp3 extends getid3_handler
 				}
 
 			}
-			$info['warning'][] = $synchoffsetwarning;
+			$this->warning($synchoffsetwarning);
 
 		}
 
@@ -134,7 +134,7 @@ class getid3_mp3 extends getid3_handler
 					break;
 
 				default:
-					$info['warning'][] = 'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"';
+					$this->warning('Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"');
 					break;
 			}
 		}
@@ -424,7 +424,7 @@ class getid3_mp3 extends getid3_handler
 		}
 
 		if ($this->fseek($offset) != 0) {
-			$info['error'][] = 'decodeMPEGaudioHeader() failed to seek to next offset at '.$offset;
+			$this->error('decodeMPEGaudioHeader() failed to seek to next offset at '.$offset);
 			return false;
 		}
 		//$headerstring = $this->fread(1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame
@@ -437,19 +437,19 @@ class getid3_mp3 extends getid3_handler
 		// and $cc... is the audio data
 
 		$head4 = substr($headerstring, 0, 4);
-
+		$head4_key = getid3_lib::PrintHexBytes($head4, true, false, false);
 		static $MPEGaudioHeaderDecodeCache = array();
-		if (isset($MPEGaudioHeaderDecodeCache[$head4])) {
-			$MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4];
+		if (isset($MPEGaudioHeaderDecodeCache[$head4_key])) {
+			$MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4_key];
 		} else {
 			$MPEGheaderRawArray = self::MPEGaudioHeaderDecode($head4);
-			$MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray;
+			$MPEGaudioHeaderDecodeCache[$head4_key] = $MPEGheaderRawArray;
 		}
 
 		static $MPEGaudioHeaderValidCache = array();
-		if (!isset($MPEGaudioHeaderValidCache[$head4])) { // Not in cache
-			//$MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true);  // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1)
-			$MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false);
+		if (!isset($MPEGaudioHeaderValidCache[$head4_key])) { // Not in cache
+			//$MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true);  // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1)
+			$MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false);
 		}
 
 		// shortcut
@@ -459,10 +459,10 @@ class getid3_mp3 extends getid3_handler
 		$thisfile_mpeg_audio = &$info['mpeg']['audio'];
 
 
-		if ($MPEGaudioHeaderValidCache[$head4]) {
+		if ($MPEGaudioHeaderValidCache[$head4_key]) {
 			$thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray;
 		} else {
-			$info['error'][] = 'Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset;
+			$this->error('Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset);
 			return false;
 		}
 
@@ -490,7 +490,7 @@ class getid3_mp3 extends getid3_handler
 
 		if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) {
 			// http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0
-			$info['warning'][] = 'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1';
+			$this->warning('Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1');
 			$thisfile_mpeg_audio['raw']['bitrate'] = 0;
 		}
 		$thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding'];
@@ -512,7 +512,7 @@ class getid3_mp3 extends getid3_handler
 					if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) {
 						// these are ok
 					} else {
-						$info['error'][] = $thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.';
+						$this->error($thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.');
 						return false;
 					}
 					break;
@@ -523,7 +523,7 @@ class getid3_mp3 extends getid3_handler
 					if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) {
 						// these are ok
 					} else {
-						$info['error'][] = intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.';
+						$this->error(intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.');
 						return false;
 					}
 					break;
@@ -545,7 +545,7 @@ class getid3_mp3 extends getid3_handler
 			if (isset($thisfile_mpeg_audio['framelength'])) {
 				$nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength'];
 			} else {
-				$info['error'][] = 'Frame at offset('.$offset.') is has an invalid frame length.';
+				$this->error('Frame at offset('.$offset.') is has an invalid frame length.');
 				return false;
 			}
 
@@ -648,9 +648,20 @@ class getid3_mp3 extends getid3_handler
 				}
 
 				//if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
-				if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
+				//if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
+				if (!empty($thisfile_mpeg_audio['VBR_frames'])) {
+					$used_filesize  = 0;
+					if (!empty($thisfile_mpeg_audio['VBR_bytes'])) {
+						$used_filesize = $thisfile_mpeg_audio['VBR_bytes'];
+					} elseif (!empty($info['filesize'])) {
+						$used_filesize  = $info['filesize'];
+						$used_filesize -= intval(@$info['id3v2']['headerlength']);
+						$used_filesize -= (isset($info['id3v1']) ? 128 : 0);
+						$used_filesize -= (isset($info['tag_offset_end']) ? $info['tag_offset_end'] - $info['tag_offset_start'] : 0);
+						$this->warning('MP3.Xing header missing VBR_bytes, assuming MPEG audio portion of file is '.number_format($used_filesize).' bytes');
+					}
 
-					$framelengthfloat = $thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames'];
+					$framelengthfloat = $used_filesize / $thisfile_mpeg_audio['VBR_frames'];
 
 					if ($thisfile_mpeg_audio['layer'] == '1') {
 						// BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
@@ -837,7 +848,7 @@ class getid3_mp3 extends getid3_handler
 						$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'])) {
-							$info['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org';
+							$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
@@ -881,7 +892,7 @@ class getid3_mp3 extends getid3_handler
 						$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
 					}
 					if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') {
-						$info['warning'][] = 'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.';
+						$this->warning('VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.');
 					}
 				}
 
@@ -908,12 +919,12 @@ class getid3_mp3 extends getid3_handler
 				//	$this->fseek($prenullbytefileoffset);
 				//	if ($PossibleNullByte === "\x00") {
 						$info['avdataend']--;
-				//		$info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored';
+				//		$this->warning('Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored');
 				//	} else {
-				//		$info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)';
+				//		$this->warning('Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)');
 				//	}
 				} else {
-					$info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)';
+					$this->warning('Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)');
 				}
 			}
 		}
@@ -931,7 +942,7 @@ class getid3_mp3 extends getid3_handler
 						$info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
 					}
 				} else {
-					$info['error'][] = 'Error calculating frame length of free-format MP3 without Xing/LAME header';
+					$this->error('Error calculating frame length of free-format MP3 without Xing/LAME header');
 				}
 			}
 		}
@@ -948,7 +959,7 @@ class getid3_mp3 extends getid3_handler
 					}
 					$thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0);
 					if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) {
-						$info['audio']['bitrate']         = $thisfile_mpeg_audio['VBR_bitrate'];
+						$info['audio']['bitrate']       = $thisfile_mpeg_audio['VBR_bitrate'];
 						$thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion
 					}
 					break;
@@ -1074,7 +1085,7 @@ class getid3_mp3 extends getid3_handler
 
 	public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) {
 		$info = &$this->getid3->info;
-		$firstframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
+		$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++) {
@@ -1084,7 +1095,7 @@ class getid3_mp3 extends getid3_handler
 				return true;
 			}
 
-			$nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
+			$nextframetestarray = array('error' => array(), 'warning' => array(), 'avdataend' => $info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
 			if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) {
 				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
@@ -1098,7 +1109,7 @@ class getid3_mp3 extends getid3_handler
 				if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) {
 					$nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength'];
 				} else {
-					$info['error'][] = 'Frame at offset ('.$offset.') is has an invalid frame length.';
+					$this->error('Frame at offset ('.$offset.') is has an invalid frame length.');
 					return false;
 				}
 
@@ -1110,7 +1121,7 @@ class getid3_mp3 extends getid3_handler
 			} else {
 
 				// next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence
-				$info['warning'][] = 'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.';
+				$this->warning('Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.');
 
 				return false;
 			}
@@ -1153,10 +1164,10 @@ class getid3_mp3 extends getid3_handler
 				$framelength = $framelength2;
 			}
 			if (!$framelength) {
-				$info['error'][] = 'Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset;
+				$this->error('Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset);
 				return false;
 			} else {
-				$info['warning'][] = 'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)';
+				$this->warning('ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)');
 				$info['audio']['codec']   = 'LAME';
 				$info['audio']['encoder'] = 'LAME3.88';
 				$SyncPattern1 = substr($SyncPattern1, 0, 3);
@@ -1183,7 +1194,7 @@ class getid3_mp3 extends getid3_handler
 					$ActualFrameLengthValues[] = ($framelength + 1);
 					$nextoffset++;
 				} else {
-					$info['error'][] = 'Did not find expected free-format sync pattern at offset '.$nextoffset;
+					$this->error('Did not find expected free-format sync pattern at offset '.$nextoffset);
 					return false;
 				}
 				$nextoffset += $framelength;
@@ -1281,7 +1292,7 @@ class getid3_mp3 extends getid3_handler
 							getid3_lib::safe_inc($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]);
 							if ($max_frames_scan && (++$frames_scanned >= $max_frames_scan)) {
 								$pct_data_scanned = ($this->ftell() - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']);
-								$info['warning'][] = 'too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.';
+								$this->warning('too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.');
 								foreach ($Distribution as $key1 => $value1) {
 									foreach ($value1 as $key2 => $value2) {
 										$Distribution[$key1][$key2] = round($value2 / $pct_data_scanned);
@@ -1308,13 +1319,13 @@ class getid3_mp3 extends getid3_handler
 		$info['mpeg']['audio']['version_distribution']   = $Distribution['version'];
 		$info['mpeg']['audio']['padding_distribution']   = $Distribution['padding'];
 		if (count($Distribution['version']) > 1) {
-			$info['error'][] = 'Corrupt file - more than one MPEG version detected';
+			$this->error('Corrupt file - more than one MPEG version detected');
 		}
 		if (count($Distribution['layer']) > 1) {
-			$info['error'][] = 'Corrupt file - more than one MPEG layer detected';
+			$this->error('Corrupt file - more than one MPEG layer detected');
 		}
 		if (count($Distribution['frequency']) > 1) {
-			$info['error'][] = 'Corrupt file - more than one MPEG sample rate detected';
+			$this->error('Corrupt file - more than one MPEG sample rate detected');
 		}
 
 
@@ -1326,7 +1337,7 @@ class getid3_mp3 extends getid3_handler
 		}
 		$info['mpeg']['audio']['frame_count']  = array_sum($Distribution['bitrate']);
 		if ($info['mpeg']['audio']['frame_count'] == 0) {
-			$info['error'][] = 'no MPEG audio frames found';
+			$this->error('no MPEG audio frames found');
 			return false;
 		}
 		$info['mpeg']['audio']['bitrate']      = ($bittotal / $info['mpeg']['audio']['frame_count']);
@@ -1361,7 +1372,7 @@ class getid3_mp3 extends getid3_handler
 		$this->fseek($avdataoffset);
 		$sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset);
 		if ($sync_seek_buffer_size <= 0) {
-			$info['error'][] = 'Invalid $sync_seek_buffer_size at offset '.$avdataoffset;
+			$this->error('Invalid $sync_seek_buffer_size at offset '.$avdataoffset);
 			return false;
 		}
 		$header = $this->fread($sync_seek_buffer_size);
@@ -1372,7 +1383,7 @@ class getid3_mp3 extends getid3_handler
 
 				if ($SynchSeekOffset > $sync_seek_buffer_size) {
 					// if a synch's not found within the first 128k bytes, then give up
-					$info['error'][] = 'Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB';
+					$this->error('Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB');
 					if (isset($info['audio']['bitrate'])) {
 						unset($info['audio']['bitrate']);
 					}
@@ -1386,7 +1397,7 @@ class getid3_mp3 extends getid3_handler
 
 				} elseif (feof($this->getid3->fp)) {
 
-					$info['error'][] = 'Could not find valid MPEG audio synch before end of file';
+					$this->error('Could not find valid MPEG audio synch before end of file');
 					if (isset($info['audio']['bitrate'])) {
 						unset($info['audio']['bitrate']);
 					}
@@ -1401,7 +1412,7 @@ class getid3_mp3 extends getid3_handler
 			}
 
 			if (($SynchSeekOffset + 1) >= strlen($header)) {
-				$info['error'][] = 'Could not find valid MPEG synch before end of file';
+				$this->error('Could not find valid MPEG synch before end of file');
 				return false;
 			}
 
@@ -1444,9 +1455,9 @@ class getid3_mp3 extends getid3_handler
 							if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) {
 								$info = $dummy;
 								$info['avdataoffset'] = $GarbageOffsetEnd;
-								$info['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 '.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);
 							} else {
-								$info['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 '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')');
 							}
 						}
 					}
@@ -1539,7 +1550,7 @@ class getid3_mp3 extends getid3_handler
 							}
 						}
 						if ($pct_data_scanned > 0) {
-							$info['warning'][] = 'too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.';
+							$this->warning('too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.');
 							foreach ($info['mpeg']['audio'] as $key1 => $value1) {
 								if (!preg_match('#_distribution$#i', $key1)) {
 									continue;
@@ -1551,7 +1562,7 @@ class getid3_mp3 extends getid3_handler
 						}
 
 						if ($SynchErrorsFound > 0) {
-							$info['warning'][] = 'Found '.$SynchErrorsFound.' synch errors in histogram analysis';
+							$this->warning('Found '.$SynchErrorsFound.' synch errors in histogram analysis');
 							//return false;
 						}
 
@@ -1564,7 +1575,7 @@ class getid3_mp3 extends getid3_handler
 							}
 						}
 						if ($framecounter == 0) {
-							$info['error'][] = 'Corrupt MP3 file: framecounter == zero';
+							$this->error('Corrupt MP3 file: framecounter == zero');
 							return false;
 						}
 						$info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter);
@@ -1599,7 +1610,7 @@ class getid3_mp3 extends getid3_handler
 
 				if (empty($info['mpeg']['audio'])) {
 
-					$info['error'][] = 'could not find valid MPEG synch before end of file';
+					$this->error('could not find valid MPEG synch before end of file');
 					if (isset($info['audio']['bitrate'])) {
 						unset($info['audio']['bitrate']);
 					}
diff --git a/src/wp-includes/ID3/module.audio.ogg.php b/src/wp-includes/ID3/module.audio.ogg.php
index 2a77768..e41c96c 100644
--- a/src/wp-includes/ID3/module.audio.ogg.php
+++ b/src/wp-includes/ID3/module.audio.ogg.php
@@ -26,13 +26,13 @@ class getid3_ogg extends getid3_handler
 
 		// Warn about illegal tags - only vorbiscomments are allowed
 		if (isset($info['id3v2'])) {
-			$info['warning'][] = 'Illegal ID3v2 tag present.';
+			$this->warning('Illegal ID3v2 tag present.');
 		}
 		if (isset($info['id3v1'])) {
-			$info['warning'][] = 'Illegal ID3v1 tag present.';
+			$this->warning('Illegal ID3v1 tag present.');
 		}
 		if (isset($info['ape'])) {
-			$info['warning'][] = 'Illegal APE tag present.';
+			$this->warning('Illegal APE tag present.');
 		}
 
 
@@ -44,7 +44,7 @@ class getid3_ogg extends getid3_handler
 		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 
 		if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
-			$info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)';
+			$this->error('Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)');
 			unset($info['fileformat']);
 			unset($info['ogg']);
 			return false;
@@ -179,7 +179,7 @@ class getid3_ogg extends getid3_handler
 			if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) {
 				$info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'];
 			}
-$info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable';
+$this->warning('Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable');
 
 
 		} elseif (substr($filedata, 0, 8) == "fishead\x00") {
@@ -240,7 +240,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 				} elseif (substr($filedata, 1, 6) == 'theora') {
 
 					$info['video']['dataformat'] = 'theora1';
-					$info['error'][] = 'Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']';
+					$this->error('Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']');
 					//break;
 
 				} elseif (substr($filedata, 1, 6) == 'vorbis') {
@@ -248,7 +248,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 					$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
 
 				} else {
-					$info['error'][] = 'unexpected';
+					$this->error('unexpected');
 					//break;
 				}
 			//} while ($oggpageinfo['page_seqno'] == 0);
@@ -256,12 +256,12 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 
 			$this->fseek($oggpageinfo['page_start_offset']);
 
-			$info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
+			$this->error('Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']');
 			//return false;
 
 		} else {
 
-			$info['error'][] = 'Expecting either "Speex   ", "OpusHead" or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
+			$this->error('Expecting either "Speex   ", "OpusHead" or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"');
 			unset($info['ogg']);
 			unset($info['mime_type']);
 			return false;
@@ -284,7 +284,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 			case 'flac':
 				$flac = new getid3_flac($this->getid3);
 				if (!$flac->parseMETAdata()) {
-					$info['error'][] = 'Failed to parse FLAC headers';
+					$this->error('Failed to parse FLAC headers');
 					return false;
 				}
 				unset($flac);
@@ -299,7 +299,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 				$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
 				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 0, 8); // hard-coded to 'OpusTags'
 				if(substr($filedata, 0, 8)  != 'OpusTags') {
-					$info['error'][] = 'Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"';
+					$this->error('Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"');
 					return false;
 				}
 
@@ -311,7 +311,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 		// Last Page - Number of Samples
 		if (!getid3_lib::intValueSupported($info['avdataend'])) {
 
-			$info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
+			$this->warning('Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)');
 
 		} else {
 
@@ -323,7 +323,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 				$info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
 				$info['ogg']['samples']   = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
 				if ($info['ogg']['samples'] == 0) {
-					$info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
+					$this->error('Corrupt Ogg file: eos.number of samples == zero');
 					return false;
 				}
 				if (!empty($info['audio']['sample_rate'])) {
@@ -342,7 +342,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 		}
 		if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
 			if ($info['audio']['bitrate'] == 0) {
-				$info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
+				$this->error('Corrupt Ogg file: bitrate_audio == zero');
 				return false;
 			}
 			$info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
@@ -395,7 +395,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 		$info['ogg']['samplerate']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 		$filedataoffset += 4;
 		if ($info['ogg']['samplerate'] == 0) {
-			$info['error'][] = 'Corrupt Ogg file: sample rate == zero';
+			$this->error('Corrupt Ogg file: sample rate == zero');
 			return false;
 		}
 		$info['audio']['sample_rate']    = $info['ogg']['samplerate'];
@@ -443,7 +443,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 		$filedataoffset += 1;
 
 		if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) {
-			$info['error'][] = 'Unknown opus version number (only accepting 1-15)';
+			$this->error('Unknown opus version number (only accepting 1-15)');
 			return false;
 		}
 
@@ -451,7 +451,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 		$filedataoffset += 1;
 
 		if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) {
-			$info['error'][] = 'Invalid channel count in opus header (must not be zero)';
+			$this->error('Invalid channel count in opus header (must not be zero)');
 			return false;
 		}
 
@@ -562,6 +562,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 
 			default:
 				return false;
+				break;
 		}
 
 		$VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
@@ -580,7 +581,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 
 			if ($i >= 10000) {
 				// https://github.com/owncloud/music/issues/212#issuecomment-43082336
-				$info['warning'][] = 'Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments';
+				$this->warning('Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments');
 				break;
 			}
 
@@ -618,7 +619,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 			$commentdataoffset += 4;
 			while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
 				if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
-					$info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
+					$this->warning('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments');
 					break 2;
 				}
 
@@ -642,12 +643,12 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 
 				//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
 				if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
-					$info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
+					$this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
 					break;
 				}
 				$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
 				if ($readlength <= 0) {
-					$info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
+					$this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
 					break;
 				}
 				$commentdata .= $this->fread($readlength);
@@ -661,7 +662,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 			if (!$commentstring) {
 
 				// no comment?
-				$info['warning'][] = 'Blank Ogg comment ['.$i.']';
+				$this->warning('Blank Ogg comment ['.$i.']');
 
 			} elseif (strstr($commentstring, '=')) {
 
@@ -711,7 +712,7 @@ $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of get
 
 			} else {
 
-				$info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
+				$this->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring);
 
 			}
 			unset($ThisFileInfo_ogg_comments_raw[$i]);
diff --git a/src/wp-includes/ID3/module.tag.apetag.php b/src/wp-includes/ID3/module.tag.apetag.php
index 724b8b0..eb081b4 100644
--- a/src/wp-includes/ID3/module.tag.apetag.php
+++ b/src/wp-includes/ID3/module.tag.apetag.php
@@ -23,7 +23,7 @@ class getid3_apetag extends getid3_handler
 		$info = &$this->getid3->info;
 
 		if (!getid3_lib::intValueSupported($info['filesize'])) {
-			$info['warning'][] = 'Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
+			$this->warning('Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
 			return false;
 		}
 
@@ -72,7 +72,7 @@ class getid3_apetag extends getid3_handler
 		$this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize);
 		$APEfooterData = $this->fread(32);
 		if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
-			$info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end'];
+			$this->error('Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']);
 			return false;
 		}
 
@@ -88,7 +88,7 @@ class getid3_apetag extends getid3_handler
 		$info['avdataend'] = $thisfile_ape['tag_offset_start'];
 
 		if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
-			$info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
+			$this->warning('ID3v1 tag information ignored since it appears to be a false synch in APEtag data');
 			unset($info['id3v1']);
 			foreach ($info['warning'] as $key => $value) {
 				if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
@@ -104,7 +104,7 @@ class getid3_apetag extends getid3_handler
 			if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
 				$offset += $apetagheadersize;
 			} else {
-				$info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start'];
+				$this->error('Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']);
 				return false;
 			}
 		}
@@ -119,7 +119,7 @@ class getid3_apetag extends getid3_handler
 			$item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
 			$offset += 4;
 			if (strstr(substr($APEtagData, $offset), "\x00") === false) {
-				$info['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset);
+				$this->error('Cannot find null-byte (0x00) separator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset));
 				return false;
 			}
 			$ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
@@ -154,7 +154,7 @@ class getid3_apetag extends getid3_handler
 						$thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 						$thisfile_replaygain['track']['originator'] = 'unspecified';
 					} else {
-						$info['warning'][] = 'MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"';
+						$this->warning('MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
 					}
 					break;
 
@@ -163,10 +163,10 @@ class getid3_apetag extends getid3_handler
 						$thisfile_replaygain['track']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 						$thisfile_replaygain['track']['originator'] = 'unspecified';
 						if ($thisfile_replaygain['track']['peak'] <= 0) {
-							$info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
+							$this->warning('ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
 						}
 					} else {
-						$info['warning'][] = 'MP3gainTrackPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"';
+						$this->warning('MP3gainTrackPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
 					}
 					break;
 
@@ -175,7 +175,7 @@ class getid3_apetag extends getid3_handler
 						$thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 						$thisfile_replaygain['album']['originator'] = 'unspecified';
 					} else {
-						$info['warning'][] = 'MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"';
+						$this->warning('MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
 					}
 					break;
 
@@ -184,10 +184,10 @@ class getid3_apetag extends getid3_handler
 						$thisfile_replaygain['album']['peak']       = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
 						$thisfile_replaygain['album']['originator'] = 'unspecified';
 						if ($thisfile_replaygain['album']['peak'] <= 0) {
-							$info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
+							$this->warning('ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
 						}
 					} else {
-						$info['warning'][] = 'MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"';
+						$this->warning('MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
 					}
 					break;
 
@@ -198,7 +198,7 @@ class getid3_apetag extends getid3_handler
 						$thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
 						$thisfile_replaygain['mp3gain']['undo_wrap']  = (($mp3gain_undo_wrap == 'Y') ? true : false);
 					} else {
-						$info['warning'][] = 'MP3gainUndo value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"';
+						$this->warning('MP3gainUndo value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
 					}
 					break;
 
@@ -208,7 +208,7 @@ class getid3_apetag extends getid3_handler
 						$thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
 						$thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
 					} else {
-						$info['warning'][] = 'MP3gainMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"';
+						$this->warning('MP3gainMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
 					}
 					break;
 
@@ -218,7 +218,7 @@ class getid3_apetag extends getid3_handler
 						$thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
 						$thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
 					} else {
-						$info['warning'][] = 'MP3gainAlbumMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"';
+						$this->warning('MP3gainAlbumMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
 					}
 					break;
 
@@ -253,19 +253,23 @@ class getid3_apetag extends getid3_handler
 				case 'cover art (studio)':
 					// list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
 					if (is_array($thisfile_ape_items_current['data'])) {
-						$info['warning'][] = 'APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8';
+						$this->warning('APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8');
 						$thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']);
 					}
 					list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
 					$thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
 					$thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
 
-					$thisfile_ape_items_current['image_mime'] = '';
-					$imageinfo = array();
-					$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
-					$thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
-
 					do {
+						$thisfile_ape_items_current['image_mime'] = '';
+						$imageinfo = array();
+						$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
+						if (($imagechunkcheck === false) || !isset($imagechunkcheck[2])) {
+							$this->warning('APEtag "'.$item_key.'" contains invalid image data');
+							break;
+						}
+						$thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
+
 						if ($this->inline_attachments === false) {
 							// skip entirely
 							unset($thisfile_ape_items_current['data']);
@@ -276,15 +280,15 @@ class getid3_apetag extends getid3_handler
 						} elseif (is_int($this->inline_attachments)) {
 							if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
 								// too big, skip
-								$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)';
+								$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)');
 								unset($thisfile_ape_items_current['data']);
 								break;
 							}
 						} elseif (is_string($this->inline_attachments)) {
 							$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
-							if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
+							if (!is_dir($this->inline_attachments) || !getID3::is_writable($this->inline_attachments)) {
 								// cannot write, skip
-								$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
+								$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)');
 								unset($thisfile_ape_items_current['data']);
 								break;
 							}
@@ -292,10 +296,10 @@ class getid3_apetag extends getid3_handler
 						// if we get this far, must be OK
 						if (is_string($this->inline_attachments)) {
 							$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
-							if (!file_exists($destination_filename) || is_writable($destination_filename)) {
+							if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
 								file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
 							} else {
-								$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
+								$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)');
 							}
 							$thisfile_ape_items_current['data_filename'] = $destination_filename;
 							unset($thisfile_ape_items_current['data']);
diff --git a/src/wp-includes/ID3/module.tag.id3v1.php b/src/wp-includes/ID3/module.tag.id3v1.php
index 3b4edfd..d160e9b 100644
--- a/src/wp-includes/ID3/module.tag.id3v1.php
+++ b/src/wp-includes/ID3/module.tag.id3v1.php
@@ -22,7 +22,7 @@ class getid3_id3v1 extends getid3_handler
 		$info = &$this->getid3->info;
 
 		if (!getid3_lib::intValueSupported($info['filesize'])) {
-			$info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
+			$this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
 			return false;
 		}
 
@@ -60,6 +60,26 @@ class getid3_id3v1 extends getid3_handler
 			foreach ($ParsedID3v1 as $key => $value) {
 				$ParsedID3v1['comments'][$key][0] = $value;
 			}
+			// ID3v1 encoding detection hack START
+			// ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets
+			// Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess
+			$ID3v1encoding = 'ISO-8859-1';
+			foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) {
+				foreach ($valuearray as $key => $value) {
+					if (preg_match('#^[\\x00-\\x40\\xA8\\B8\\x80-\\xFF]+$#', $value)) {
+						foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
+							if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) {
+								$ID3v1encoding = $id3v1_bad_encoding;
+								break 3;
+							} elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) {
+								$ID3v1encoding = $id3v1_bad_encoding;
+								break 3;
+							}
+						}
+					}
+				}
+			}
+			// ID3v1 encoding detection hack END
 
 			// ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
 			$GoodFormatID3v1tag = $this->GenerateID3v1Tag(
@@ -73,13 +93,14 @@ class getid3_id3v1 extends getid3_handler
 			$ParsedID3v1['padding_valid'] = true;
 			if ($id3v1tag !== $GoodFormatID3v1tag) {
 				$ParsedID3v1['padding_valid'] = false;
-				$info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding';
+				$this->warning('Some ID3v1 fields do not use NULL characters for padding');
 			}
 
 			$ParsedID3v1['tag_offset_end']   = $info['filesize'];
 			$ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
 
 			$info['id3v1'] = $ParsedID3v1;
+			$info['id3v1']['encoding'] = $ID3v1encoding;
 		}
 
 		if (substr($preid3v1, 0, 3) == 'TAG') {
@@ -95,7 +116,7 @@ class getid3_id3v1 extends getid3_handler
 				// a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
 			} else {
 				// APE and Lyrics3 footers not found - assume double ID3v1
-				$info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes';
+				$this->warning('Duplicate ID3v1 tag detected - this has been known to happen with iTunes');
 				$info['avdataend'] -= 128;
 			}
 		}
diff --git a/src/wp-includes/ID3/module.tag.id3v2.php b/src/wp-includes/ID3/module.tag.id3v2.php
index 7092504..fc586e8 100644
--- a/src/wp-includes/ID3/module.tag.id3v2.php
+++ b/src/wp-includes/ID3/module.tag.id3v2.php
@@ -71,7 +71,7 @@ class getid3_id3v2 extends getid3_handler
 
 		if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
 
-			$info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
+			$this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
 			return false;
 
 		}
@@ -241,7 +241,7 @@ class getid3_id3v2 extends getid3_handler
 					}
 
 					if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
-						$info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
+						$this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
 					}
 				}
 
@@ -260,7 +260,7 @@ class getid3_id3v2 extends getid3_handler
 						if ($framedata{$i} != "\x00") {
 							$thisfile_id3v2['padding']['valid'] = false;
 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
-							$info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
+							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
 							break;
 						}
 					}
@@ -300,7 +300,7 @@ class getid3_id3v2 extends getid3_handler
 						} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
 							// MP3ext known broken frames - "ok" for the purposes of this test
 						} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
-							$info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
+							$this->warning('ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3');
 							$id3v2_majorversion = 3;
 							$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
 						}
@@ -322,16 +322,16 @@ class getid3_id3v2 extends getid3_handler
 						if ($framedata{$i} != "\x00") {
 							$thisfile_id3v2['padding']['valid'] = false;
 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
-							$info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
+							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
 							break;
 						}
 					}
 					break; // skip rest of ID3v2 header
 				}
 
-				if ($frame_name == 'COM ') {
-					$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
-					$frame_name = 'COMM';
+				if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
+					$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
+					$frame_name = $iTunesBrokenFrameNameFixed;
 				}
 				if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
 
@@ -355,28 +355,28 @@ class getid3_id3v2 extends getid3_handler
 
 							// next frame is valid, just skip the current frame
 							$framedata = substr($framedata, $frame_size);
-							$info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
+							$this->warning('Next ID3v2 frame is valid, skipping current frame.');
 
 						} else {
 
 							// next frame is invalid too, abort processing
 							//unset($framedata);
 							$framedata = null;
-							$info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
+							$this->error('Next ID3v2 frame is also invalid, aborting processing.');
 
 						}
 
 					} elseif ($frame_size == strlen($framedata)) {
 
 						// this is the last frame, just skip
-						$info['warning'][] = 'This was the last ID3v2 frame.';
+						$this->warning('This was the last ID3v2 frame.');
 
 					} else {
 
 						// next frame is invalid too, abort processing
 						//unset($framedata);
 						$framedata = null;
-						$info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
+						$this->warning('Invalid ID3v2 frame size, aborting.');
 
 					}
 					if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
@@ -389,21 +389,21 @@ class getid3_id3v2 extends getid3_handler
 							case "\x00".'MP':
 							case ' MP':
 							case 'MP3':
-								$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
+								$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]');
 								break;
 
 							default:
-								$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
+								$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
 								break;
 						}
 
 					} elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
 
-						$info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).';
+						$this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).');
 
 					} else {
 
-						$info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
+						$this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');
 
 					}
 
@@ -442,10 +442,14 @@ class getid3_id3v2 extends getid3_handler
 		} // end footer
 
 		if (isset($thisfile_id3v2['comments']['genre'])) {
+			$genres = array();
 			foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
-				unset($thisfile_id3v2['comments']['genre'][$key]);
-				$thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
+				foreach ($this->ParseID3v2GenreString($value) as $genre) {
+					$genres[] = $genre;
+				}
 			}
+			$thisfile_id3v2['comments']['genre'] = array_unique($genres);
+			unset($key, $value, $genres, $genre);
 		}
 
 		if (isset($thisfile_id3v2['comments']['track'])) {
@@ -500,9 +504,28 @@ class getid3_id3v2 extends getid3_handler
 		// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
 		// ID3v2.4.x: '21' $00 'Eurodisco' $00
 		$clean_genres = array();
+
+		// hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
+		if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
+			// note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
+			// replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
+			if (preg_match('#/#', $genrestring)) {
+				$genrestring = str_replace('/', "\x00", $genrestring);
+				$genrestring = str_replace('Pop'."\x00".'Funk', 'Pop/Funk', $genrestring);
+				$genrestring = str_replace('Rock'."\x00".'Rock', 'Folk/Rock', $genrestring);
+			}
+
+			// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
+			if (preg_match('#;#', $genrestring)) {
+				$genrestring = str_replace(';', "\x00", $genrestring);
+			}
+		}
+
+
 		if (strpos($genrestring, "\x00") === false) {
 			$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
 		}
+
 		$genre_elements = explode("\x00", $genrestring);
 		foreach ($genre_elements as $element) {
 			$element = trim($element);
@@ -571,14 +594,14 @@ class getid3_id3v2 extends getid3_handler
 			if ($parsedFrame['flags']['compression']) {
 				$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
 				if (!function_exists('gzuncompress')) {
-					$info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
+					$this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
 				} else {
 					if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
 					//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
 						$parsedFrame['data'] = $decompresseddata;
 						unset($decompresseddata);
 					} else {
-						$info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
+						$this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
 					}
 				}
 			}
@@ -586,7 +609,7 @@ class getid3_id3v2 extends getid3_handler
 
 		if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
 			if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
-				$info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
+				$this->warning('ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data');
 			}
 		}
 
@@ -601,7 +624,7 @@ class getid3_id3v2 extends getid3_handler
 				default:
 					break;
 			}
-			$info['warning'][] = $warning;
+			$this->warning($warning);
 
 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
 			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
@@ -627,7 +650,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 				$frame_textencoding_terminator = "\x00";
 			}
 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
@@ -635,7 +658,8 @@ class getid3_id3v2 extends getid3_handler
 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 			}
 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
-			if (ord($frame_description) === 0) {
+			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+				// if description only contains a BOM or terminator then make it blank
 				$frame_description = '';
 			}
 			$parsedFrame['encodingid']  = $frame_textencoding;
@@ -664,7 +688,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_offset = 0;
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 			}
 
 			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
@@ -720,7 +744,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 				$frame_textencoding_terminator = "\x00";
 			}
 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
@@ -728,8 +752,8 @@ class getid3_id3v2 extends getid3_handler
 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 			}
 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
-
-			if (ord($frame_description) === 0) {
+			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+				// if description only contains a BOM or terminator then make it blank
 				$frame_description = '';
 			}
 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
@@ -783,7 +807,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_offset = 0;
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 			}
 			$parsedFrame['encodingid'] = $frame_textencoding;
 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
@@ -961,7 +985,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$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);
@@ -971,7 +995,8 @@ class getid3_id3v2 extends getid3_handler
 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 			}
 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
-			if (ord($frame_description) === 0) {
+			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+				// if description only contains a BOM or terminator then make it blank
 				$frame_description = '';
 			}
 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
@@ -979,7 +1004,6 @@ class getid3_id3v2 extends getid3_handler
 			$parsedFrame['encodingid']   = $frame_textencoding;
 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
 
-			$parsedFrame['data']         = $parsedFrame['data'];
 			$parsedFrame['language']     = $frame_language;
 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
 			$parsedFrame['description']  = $frame_description;
@@ -1009,7 +1033,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$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);
@@ -1061,7 +1085,7 @@ class getid3_id3v2 extends getid3_handler
 
 			if (strlen($parsedFrame['data']) < 5) {
 
-				$info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
+				$this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
 
 			} else {
 
@@ -1069,7 +1093,7 @@ class getid3_id3v2 extends getid3_handler
 				$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 				$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 				if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-					$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+					$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);
@@ -1079,7 +1103,8 @@ class getid3_id3v2 extends getid3_handler
 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 				}
 				$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
-				if (ord($frame_description) === 0) {
+				if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+					// if description only contains a BOM or terminator then make it blank
 					$frame_description = '';
 				}
 				$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
@@ -1132,7 +1157,7 @@ class getid3_id3v2 extends getid3_handler
 				$frame_offset += 2;
 				$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
 				if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
-					$info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
+					$this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
 					break;
 				}
 				$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
@@ -1341,7 +1366,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 				$frame_textencoding_terminator = "\x00";
 			}
 
@@ -1376,14 +1401,15 @@ class getid3_id3v2 extends getid3_handler
 			$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 
 			if ($frame_offset >= $parsedFrame['datalength']) {
-				$info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
+				$this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
 			} else {
 				$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
 				}
 				$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
-				if (ord($frame_description) === 0) {
+				if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+					// if description only contains a BOM or terminator then make it blank
 					$frame_description = '';
 				}
 				$parsedFrame['encodingid']       = $frame_textencoding;
@@ -1402,14 +1428,15 @@ class getid3_id3v2 extends getid3_handler
 
 				$parsedFrame['image_mime'] = '';
 				$imageinfo = array();
-				$imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
-				if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
-					$parsedFrame['image_mime']       = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
-					if ($imagechunkcheck[0]) {
-						$parsedFrame['image_width']  = $imagechunkcheck[0];
-					}
-					if ($imagechunkcheck[1]) {
-						$parsedFrame['image_height'] = $imagechunkcheck[1];
+				if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
+					if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
+						$parsedFrame['image_mime']       = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
+						if ($imagechunkcheck[0]) {
+							$parsedFrame['image_width']  = $imagechunkcheck[0];
+						}
+						if ($imagechunkcheck[1]) {
+							$parsedFrame['image_height'] = $imagechunkcheck[1];
+						}
 					}
 				}
 
@@ -1425,16 +1452,16 @@ class getid3_id3v2 extends getid3_handler
 					} elseif (is_int($this->getid3->option_save_attachments)) {
 						if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
 							// too big, skip
-							$info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
+							$this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
 							unset($parsedFrame['data']);
 							break;
 						}
 */
 					} elseif (is_string($this->getid3->option_save_attachments)) {
 						$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
-						if (!is_dir($dir) || !is_writable($dir)) {
+						if (!is_dir($dir) || !getID3::is_writable($dir)) {
 							// cannot write, skip
-							$info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)';
+							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
 							unset($parsedFrame['data']);
 							break;
 						}
@@ -1442,10 +1469,10 @@ class getid3_id3v2 extends getid3_handler
 					// if we get this far, must be OK
 					if (is_string($this->getid3->option_save_attachments)) {
 						$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
-						if (!file_exists($destination_filename) || is_writable($destination_filename)) {
+						if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
 							file_put_contents($destination_filename, $parsedFrame['data']);
 						} else {
-							$info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
+							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
 						}
 						$parsedFrame['data_filename'] = $destination_filename;
 						unset($parsedFrame['data']);
@@ -1482,7 +1509,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 				$frame_textencoding_terminator = "\x00";
 			}
 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
@@ -1507,7 +1534,8 @@ class getid3_id3v2 extends getid3_handler
 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 			}
 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
-			if (ord($frame_description) === 0) {
+			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+				// if description only contains a BOM or terminator then make it blank
 				$frame_description = '';
 			}
 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
@@ -1589,7 +1617,8 @@ class getid3_id3v2 extends getid3_handler
 
 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
-			if (ord($frame_description) === 0) {
+			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+				// if description only contains a BOM or terminator then make it blank
 				$frame_description = '';
 			}
 			$frame_offset = $frame_terminatorpos + strlen("\x00");
@@ -1614,7 +1643,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
 			if (ord($frame_ownerid) === 0) {
-				$frame_ownerid == '';
+				$frame_ownerid = '';
 			}
 			$frame_offset = $frame_terminatorpos + strlen("\x00");
 			$parsedFrame['ownerid'] = $frame_ownerid;
@@ -1683,7 +1712,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_offset = 0;
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 			}
 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
 			$frame_offset += 3;
@@ -1710,7 +1739,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_offset = 0;
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 			}
 			$parsedFrame['encodingid'] = $frame_textencoding;
 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
@@ -1724,7 +1753,7 @@ class getid3_id3v2 extends getid3_handler
 			$parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);
 
 			$parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
-			if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) {
+			if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
 				$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
 			}
 			$frame_offset += 8;
@@ -1751,7 +1780,7 @@ class getid3_id3v2 extends getid3_handler
 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
-				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
 				$frame_textencoding_terminator = "\x00";
 			}
 
@@ -1789,7 +1818,8 @@ class getid3_id3v2 extends getid3_handler
 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 			}
 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
-			if (ord($frame_description) === 0) {
+			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+				// if description only contains a BOM or terminator then make it blank
 				$frame_description = '';
 			}
 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
@@ -2006,7 +2036,7 @@ class getid3_id3v2 extends getid3_handler
 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
 					$frame_offset += 2;
 					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
-						$info['warning'][] = 'CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)';
+						$this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
 						break;
 					}
 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
@@ -2043,7 +2073,7 @@ class getid3_id3v2 extends getid3_handler
 						}
 						$parsedFrame['subframes'][] = $subframe;
 					} else {
-						$info['warning'][] = 'ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)';
+						$this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
 					}
 				}
 				unset($subframe_rawdata, $subframe, $encoding_converted_text);
@@ -2103,7 +2133,7 @@ class getid3_id3v2 extends getid3_handler
 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
 					$frame_offset += 2;
 					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
-						$info['warning'][] = 'CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)';
+						$this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
 						break;
 					}
 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
@@ -2140,7 +2170,7 @@ class getid3_id3v2 extends getid3_handler
 						}
 						$parsedFrame['subframes'][] = $subframe;
 					} else {
-						$info['warning'][] = 'ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)';
+						$this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
 					}
 				}
 				unset($subframe_rawdata, $subframe, $encoding_converted_text);
@@ -3623,5 +3653,89 @@ class getid3_id3v2 extends getid3_handler
 		return (($majorversion == 2) ? 6 : 10);
 	}
 
-}
+	public static function ID3v22iTunesBrokenFrameName($frame_name) {
+		// iTunes (multiple versions) has been known to write ID3v2.3 style frames
+		// but use ID3v2.2 frame names, right-padded using either [space] or [null]
+		// to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
+		// This function will detect and translate the corrupt frame name into ID3v2.3 standard.
+		static $ID3v22_iTunes_BrokenFrames = array(
+			'BUF' => 'RBUF', // Recommended buffer size
+			'CNT' => 'PCNT', // Play counter
+			'COM' => 'COMM', // Comments
+			'CRA' => 'AENC', // Audio encryption
+			'EQU' => 'EQUA', // Equalisation
+			'ETC' => 'ETCO', // Event timing codes
+			'GEO' => 'GEOB', // General encapsulated object
+			'IPL' => 'IPLS', // Involved people list
+			'LNK' => 'LINK', // Linked information
+			'MCI' => 'MCDI', // Music CD identifier
+			'MLL' => 'MLLT', // MPEG location lookup table
+			'PIC' => 'APIC', // Attached picture
+			'POP' => 'POPM', // Popularimeter
+			'REV' => 'RVRB', // Reverb
+			'RVA' => 'RVAD', // Relative volume adjustment
+			'SLT' => 'SYLT', // Synchronised lyric/text
+			'STC' => 'SYTC', // Synchronised tempo codes
+			'TAL' => 'TALB', // Album/Movie/Show title
+			'TBP' => 'TBPM', // BPM (beats per minute)
+			'TCM' => 'TCOM', // Composer
+			'TCO' => 'TCON', // Content type
+			'TCP' => 'TCMP', // Part of a compilation
+			'TCR' => 'TCOP', // Copyright message
+			'TDA' => 'TDAT', // Date
+			'TDY' => 'TDLY', // Playlist delay
+			'TEN' => 'TENC', // Encoded by
+			'TFT' => 'TFLT', // File type
+			'TIM' => 'TIME', // Time
+			'TKE' => 'TKEY', // Initial key
+			'TLA' => 'TLAN', // Language(s)
+			'TLE' => 'TLEN', // Length
+			'TMT' => 'TMED', // Media type
+			'TOA' => 'TOPE', // Original artist(s)/performer(s)
+			'TOF' => 'TOFN', // Original filename
+			'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
+			'TOR' => 'TORY', // Original release year
+			'TOT' => 'TOAL', // Original album/movie/show title
+			'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
+			'TP2' => 'TPE2', // Band/orchestra/accompaniment
+			'TP3' => 'TPE3', // Conductor/performer refinement
+			'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
+			'TPA' => 'TPOS', // Part of a set
+			'TPB' => 'TPUB', // Publisher
+			'TRC' => 'TSRC', // ISRC (international standard recording code)
+			'TRD' => 'TRDA', // Recording dates
+			'TRK' => 'TRCK', // Track number/Position in set
+			'TS2' => 'TSO2', // Album-Artist sort order
+			'TSA' => 'TSOA', // Album sort order
+			'TSC' => 'TSOC', // Composer sort order
+			'TSI' => 'TSIZ', // Size
+			'TSP' => 'TSOP', // Performer sort order
+			'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
+			'TST' => 'TSOT', // Title sort order
+			'TT1' => 'TIT1', // Content group description
+			'TT2' => 'TIT2', // Title/songname/content description
+			'TT3' => 'TIT3', // Subtitle/Description refinement
+			'TXT' => 'TEXT', // Lyricist/Text writer
+			'TXX' => 'TXXX', // User defined text information frame
+			'TYE' => 'TYER', // Year
+			'UFI' => 'UFID', // Unique file identifier
+			'ULT' => 'USLT', // Unsynchronised lyric/text transcription
+			'WAF' => 'WOAF', // Official audio file webpage
+			'WAR' => 'WOAR', // Official artist/performer webpage
+			'WAS' => 'WOAS', // Official audio source webpage
+			'WCM' => 'WCOM', // Commercial information
+			'WCP' => 'WCOP', // Copyright/Legal information
+			'WPB' => 'WPUB', // Publishers official webpage
+			'WXX' => 'WXXX', // User defined URL link frame
+		);
+		if (strlen($frame_name) == 4) {
+			if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) {
+				if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) {
+					return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)];
+				}
+			}
+		}
+		return false;
+	}
 
+}
diff --git a/src/wp-includes/ID3/module.tag.lyrics3.php b/src/wp-includes/ID3/module.tag.lyrics3.php
index 419888b..1645396 100644
--- a/src/wp-includes/ID3/module.tag.lyrics3.php
+++ b/src/wp-includes/ID3/module.tag.lyrics3.php
@@ -24,7 +24,7 @@ class getid3_lyrics3 extends getid3_handler
 		// http://www.volweb.cz/str/tags.htm
 
 		if (!getid3_lib::intValueSupported($info['filesize'])) {
-			$info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
+			$this->warning('Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
 			return false;
 		}
 
@@ -80,7 +80,7 @@ class getid3_lyrics3 extends getid3_handler
 					$lyrics3offset  = $info['ape']['tag_offset_start'] - $lyrics3size;
 					$info['avdataend'] = $lyrics3offset;
 					$lyrics3version = 1;
-					$info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability';
+					$this->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability');
 
 				} elseif ($lyrics3end == 'LYRICS200') {
 					// Lyrics3v2, APE, maybe ID3v1
@@ -88,7 +88,7 @@ class getid3_lyrics3 extends getid3_handler
 					$lyrics3size    = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
 					$lyrics3offset  = $info['ape']['tag_offset_start'] - $lyrics3size;
 					$lyrics3version = 2;
-					$info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability';
+					$this->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability');
 
 				}
 
@@ -117,7 +117,7 @@ class getid3_lyrics3 extends getid3_handler
 					}
 					unset($getid3_temp, $getid3_apetag);
 				} else {
-					$info['warning'][] = 'Lyrics3 and APE tags appear to have become entangled (most likely due to updating the APE tags with a non-Lyrics3-aware tagger)';
+					$this->warning('Lyrics3 and APE tags appear to have become entangled (most likely due to updating the APE tags with a non-Lyrics3-aware tagger)');
 				}
 			}
 
@@ -132,7 +132,7 @@ class getid3_lyrics3 extends getid3_handler
 		$info = &$this->getid3->info;
 
 		if (!getid3_lib::intValueSupported($endoffset)) {
-			$info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
+			$this->warning('Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
 			return false;
 		}
 
@@ -150,7 +150,7 @@ class getid3_lyrics3 extends getid3_handler
 		if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') {
 			if (strpos($rawdata, 'LYRICSBEGIN') !== false) {
 
-				$info['warning'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version;
+				$this->warning('"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version);
 				$info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN');
 				$rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN'));
 				$length = strlen($rawdata);
@@ -159,7 +159,7 @@ class getid3_lyrics3 extends getid3_handler
 
 			} else {
 
-				$info['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead';
+				$this->error('"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead');
 				return false;
 
 			}
@@ -173,7 +173,7 @@ class getid3_lyrics3 extends getid3_handler
 					$ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9));
 					$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
 				} else {
-					$info['error'][] = '"LYRICSEND" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead';
+					$this->error('"LYRICSEND" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead');
 					return false;
 				}
 				break;
@@ -221,20 +221,20 @@ class getid3_lyrics3 extends getid3_handler
 						$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
 					}
 				} else {
-					$info['error'][] = '"LYRICS200" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead';
+					$this->error('"LYRICS200" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead');
 					return false;
 				}
 				break;
 
 			default:
-				$info['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)';
+				$this->error('Cannot process Lyrics3 version '.$version.' (only v1 and v2)');
 				return false;
 				break;
 		}
 
 
 		if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) {
-			$info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data';
+			$this->warning('ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data');
 			unset($info['id3v1']);
 			foreach ($info['warning'] as $key => $value) {
 				if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
