From d47785d3d6ce98b1e98f942f77c92ac8a7833f33 Mon Sep 17 00:00:00 2001
From: "kent1@arscenic.info" <>
Date: Sat, 17 Dec 2016 09:52:58 +0000
Subject: [PATCH] =?UTF-8?q?Mise=20=C3=A0=20jour=20de=20librairie=20getid3?=
 =?UTF-8?q?=20en=201.9.13?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

https://github.com/JamesHeinrich/getID3/releases/tag/v1.9.13

bugfix #89: ID3v2.4 custom genres with slashes
bugfix #88: large QuickTime files exceed PHP memory limit
bugfix #87: ID3v2 write GRID data not working properly
bugfix #86: Increase autoloading definitions
bugfix #84: ID3v2 available writable frames list
bugfix #82: ID3v2 datetime logic
bugfix #80: attempt to autodetect ID3v1 encoding
bugfix #77: add partial support of DSSv6
bugfix #76: add mysqli version of caching extension
bugfix #75: mysql cache max key length
bugfix #71: custom error handler to catch exif_read_data() errors
bugfix #71: add support for mb_convert_encoding
bugfix #70: ID3v2 POPM / UFID
bugfix #68: workaround broken iTunes ID3v2
bugfix #48: Quicktime set MIME to video/mp4 where applicable
bugfix #1930 fread on pipes
bugfix #1926 relax ID3v2.IsValidURL check
---
 .gitattributes                              |   1 +
 lib/getid3/extension.cache.mysql.php        |   4 +-
 lib/getid3/extension.cache.mysqli.php       | 183 ++++++++++
 lib/getid3/getid3.lib.php                   |  74 ++--
 lib/getid3/getid3.php                       | 163 ++++-----
 lib/getid3/module.audio-video.asf.php       |   2 +-
 lib/getid3/module.audio-video.matroska.php  |   7 +-
 lib/getid3/module.audio-video.quicktime.php |  12 +-
 lib/getid3/module.audio.dss.php             |  42 ++-
 lib/getid3/module.graphic.jpg.php           |  11 +
 lib/getid3/module.tag.apetag.php            |   2 +-
 lib/getid3/module.tag.id3v1.php             |  21 ++
 lib/getid3/module.tag.id3v2.php             | 120 ++++++-
 lib/getid3/write.id3v2.php                  | 358 +++++++++++---------
 lib/getid3/write.php                        |  34 ++
 15 files changed, 736 insertions(+), 298 deletions(-)
 create mode 100644 lib/getid3/extension.cache.mysqli.php

diff --git a/.gitattributes b/.gitattributes
index 5e54f707..e5de9d81 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -124,6 +124,7 @@ lang/paquet-medias_sk.php -text
 lang/paquet-medias_uk.php -text
 lib/getid3/extension.cache.dbm.php -text
 lib/getid3/extension.cache.mysql.php -text
+lib/getid3/extension.cache.mysqli.php -text
 lib/getid3/extension.cache.sqlite3.php -text
 lib/getid3/getid3.lib.php -text
 lib/getid3/getid3.php -text
diff --git a/lib/getid3/extension.cache.mysql.php b/lib/getid3/extension.cache.mysql.php
index f4358211..b572676a 100644
--- a/lib/getid3/extension.cache.mysql.php
+++ b/lib/getid3/extension.cache.mysql.php
@@ -178,12 +178,12 @@ class getID3_cached_mysql extends getID3
 	private function create_table($drop=false) {
 
 		$SQLquery  = 'CREATE TABLE IF NOT EXISTS `'.mysql_real_escape_string($this->table).'` (';
-		$SQLquery .=   '`filename` VARCHAR(500) NOT NULL DEFAULT \'\'';
+		$SQLquery .=   '`filename` VARCHAR(990) NOT NULL DEFAULT \'\'';
 		$SQLquery .= ', `filesize` INT(11) NOT NULL DEFAULT \'0\'';
 		$SQLquery .= ', `filetime` INT(11) NOT NULL DEFAULT \'0\'';
 		$SQLquery .= ', `analyzetime` INT(11) NOT NULL DEFAULT \'0\'';
 		$SQLquery .= ', `value` LONGTEXT NOT NULL';
-		$SQLquery .= ', PRIMARY KEY (`filename`, `filesize`, `filetime`)) ENGINE=MyISAM';
+		$SQLquery .= ', PRIMARY KEY (`filename`, `filesize`, `filetime`)) ENGINE=MyISAM CHARACTER SET=latin1 COLLATE=latin1_general_ci';
 		$this->cursor = mysql_query($SQLquery, $this->connection);
 		echo mysql_error($this->connection);
 	}
diff --git a/lib/getid3/extension.cache.mysqli.php b/lib/getid3/extension.cache.mysqli.php
new file mode 100644
index 00000000..3299caae
--- /dev/null
+++ b/lib/getid3/extension.cache.mysqli.php
@@ -0,0 +1,183 @@
+<?php
+/////////////////////////////////////////////////////////////////
+/// getID3() by James Heinrich <info@getid3.org>               //
+//  available at http://getid3.sourceforge.net                 //
+//            or http://www.getid3.org                         //
+//          also https://github.com/JamesHeinrich/getID3       //
+/////////////////////////////////////////////////////////////////
+//                                                             //
+// extension.cache.mysqli.php - part of getID3()                //
+// Please see readme.txt for more information                  //
+//                                                            ///
+/////////////////////////////////////////////////////////////////
+//                                                             //
+// This extension written by Allan Hansen <ahØartemis*dk>      //
+// Table name mod by Carlo Capocasa <calroØcarlocapocasa*com>  //
+//                                                            ///
+/////////////////////////////////////////////////////////////////
+
+
+/**
+* This is a caching extension for getID3(). It works the exact same
+* way as the getID3 class, but return cached information very fast
+*
+* Example:  (see also demo.cache.mysql.php in /demo/)
+*
+*    Normal getID3 usage (example):
+*
+*       require_once 'getid3/getid3.php';
+*       $getID3 = new getID3;
+*       $getID3->encoding = 'UTF-8';
+*       $info1 = $getID3->analyze('file1.flac');
+*       $info2 = $getID3->analyze('file2.wv');
+*
+*    getID3_cached usage:
+*
+*       require_once 'getid3/getid3.php';
+*       require_once 'getid3/getid3/extension.cache.mysqli.php';
+*       // 5th parameter (tablename) is optional, default is 'getid3_cache'
+*       $getID3 = new getID3_cached_mysqli('localhost', 'database', 'username', 'password', 'tablename');
+*       $getID3->encoding = 'UTF-8';
+*       $info1 = $getID3->analyze('file1.flac');
+*       $info2 = $getID3->analyze('file2.wv');
+*
+*
+* Supported Cache Types    (this extension)
+*
+*   SQL Databases:
+*
+*   cache_type          cache_options
+*   -------------------------------------------------------------------
+*   mysqli              host, database, username, password
+*
+*
+*   DBM-Style Databases:    (use extension.cache.dbm)
+*
+*   cache_type          cache_options
+*   -------------------------------------------------------------------
+*   gdbm                dbm_filename, lock_filename
+*   ndbm                dbm_filename, lock_filename
+*   db2                 dbm_filename, lock_filename
+*   db3                 dbm_filename, lock_filename
+*   db4                 dbm_filename, lock_filename  (PHP5 required)
+*
+*   PHP must have write access to both dbm_filename and lock_filename.
+*
+*
+* Recommended Cache Types
+*
+*   Infrequent updates, many reads      any DBM
+*   Frequent updates                    mysqli
+*/
+
+class getID3_cached_mysqli extends getID3
+{
+	// private vars
+	private $mysqli;
+	private $cursor;
+
+
+	// public: constructor - see top of this file for cache type and cache_options
+	public function __construct($host, $database, $username, $password, $table='getid3_cache') {
+
+		// Check for mysqli support
+		if (!function_exists('mysqli_connect')) {
+			throw new Exception('PHP not compiled with mysqli support.');
+		}
+
+		// Connect to database
+		$this->mysqli = new mysqli($host, $username, $password);
+		if (!$this->mysqli) {
+			throw new Exception('mysqli_connect() failed - check permissions and spelling.');
+		}
+
+		// Select database
+		if (!$this->mysqli->select_db($database)) {
+			throw new Exception('Cannot use database '.$database);
+		}
+
+		// Set table
+		$this->table = $table;
+
+		// Create cache table if not exists
+		$this->create_table();
+
+		// Check version number and clear cache if changed
+		$version = '';
+		$SQLquery  = 'SELECT `value`';
+		$SQLquery .= ' FROM `'.$this->mysqli->real_escape_string($this->table).'`';
+		$SQLquery .= ' WHERE (`filename` = \''.$this->mysqli->real_escape_string(getID3::VERSION).'\')';
+		$SQLquery .= ' AND (`filesize` = -1)';
+		$SQLquery .= ' AND (`filetime` = -1)';
+		$SQLquery .= ' AND (`analyzetime` = -1)';
+		if ($this->cursor = $this->mysqli->query($SQLquery)) {
+			list($version) = $this->cursor->fetch_array();
+		}
+		if ($version != getID3::VERSION) {
+			$this->clear_cache();
+		}
+
+		parent::__construct();
+	}
+
+
+	// public: clear cache
+	public function clear_cache() {
+		$this->mysqli->query('DELETE FROM `'.$this->mysqli->real_escape_string($this->table).'`');
+		$this->mysqli->query('INSERT INTO `'.$this->mysqli->real_escape_string($this->table).'` (`filename`, `filesize`, `filetime`, `analyzetime`, `value`) VALUES (\''.getID3::VERSION.'\', -1, -1, -1, \''.getID3::VERSION.'\')');
+	}
+
+
+	// public: analyze file
+	public function analyze($filename, $filesize=null, $original_filename='') {
+
+		if (file_exists($filename)) {
+
+			// Short-hands
+			$filetime = filemtime($filename);
+			$filesize =  filesize($filename);
+
+			// Lookup file
+			$SQLquery  = 'SELECT `value`';
+			$SQLquery .= ' FROM `'.$this->mysqli->real_escape_string($this->table).'`';
+			$SQLquery .= ' WHERE (`filename` = \''.$this->mysqli->real_escape_string($filename).'\')';
+			$SQLquery .= '   AND (`filesize` = \''.$this->mysqli->real_escape_string($filesize).'\')';
+			$SQLquery .= '   AND (`filetime` = \''.$this->mysqli->real_escape_string($filetime).'\')';
+			$this->cursor = $this->mysqli->query($SQLquery);
+			if ($this->cursor->num_rows > 0) {
+				// Hit
+				list($result) = $this->cursor->fetch_array();
+				return unserialize(base64_decode($result));
+			}
+		}
+
+		// Miss
+		$analysis = parent::analyze($filename, $filesize, $original_filename);
+
+		// Save result
+		if (file_exists($filename)) {
+			$SQLquery  = 'INSERT INTO `'.$this->mysqli->real_escape_string($this->table).'` (`filename`, `filesize`, `filetime`, `analyzetime`, `value`) VALUES (';
+			$SQLquery .=   '\''.$this->mysqli->real_escape_string($filename).'\'';
+			$SQLquery .= ', \''.$this->mysqli->real_escape_string($filesize).'\'';
+			$SQLquery .= ', \''.$this->mysqli->real_escape_string($filetime).'\'';
+			$SQLquery .= ', \''.$this->mysqli->real_escape_string(time()   ).'\'';
+			$SQLquery .= ', \''.$this->mysqli->real_escape_string(base64_encode(serialize($analysis))).'\')';
+			$this->cursor = $this->mysqli->query($SQLquery);
+		}
+		return $analysis;
+	}
+
+
+	// private: (re)create mysqli table
+	private function create_table($drop=false) {
+		$SQLquery  = 'CREATE TABLE IF NOT EXISTS `'.$this->mysqli->real_escape_string($this->table).'` (';
+		$SQLquery .=   '`filename` VARCHAR(990) NOT NULL DEFAULT \'\'';
+		$SQLquery .= ', `filesize` INT(11) NOT NULL DEFAULT \'0\'';
+		$SQLquery .= ', `filetime` INT(11) NOT NULL DEFAULT \'0\'';
+		$SQLquery .= ', `analyzetime` INT(11) NOT NULL DEFAULT \'0\'';
+		$SQLquery .= ', `value` LONGTEXT NOT NULL';
+		$SQLquery .= ', PRIMARY KEY (`filename`, `filesize`, `filetime`)) ENGINE=MyISAM CHARACTER SET=latin1 COLLATE=latin1_general_ci';
+		$this->cursor = $this->mysqli->query($SQLquery);
+		echo $this->mysqli->error;
+	}
+}
\ No newline at end of file
diff --git a/lib/getid3/getid3.lib.php b/lib/getid3/getid3.lib.php
index 1dc97a62..808f5921 100644
--- a/lib/getid3/getid3.lib.php
+++ b/lib/getid3/getid3.lib.php
@@ -960,8 +960,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':
@@ -977,7 +989,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';
@@ -999,7 +1011,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') {
@@ -1020,38 +1032,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});
@@ -1079,7 +1091,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)) {
@@ -1090,7 +1102,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)) {
@@ -1262,10 +1274,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;
+									}
 								}
 							}
 						}
@@ -1273,6 +1289,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/lib/getid3/getid3.php b/lib/getid3/getid3.php
index a57d7417..79f562aa 100644
--- a/lib/getid3/getid3.php
+++ b/lib/getid3/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.12-201602240818';
+	const VERSION           = '1.9.13-201612051806';
 	const FREAD_BUFFER_SIZE = 32768;
 
 	const ATTACHMENTS_NONE   = false;
@@ -121,7 +124,7 @@ class getID3
 		// Check for PHP version
 		$required_php_version = '5.3.0';
 		if (version_compare(PHP_VERSION, $required_php_version, '<')) {
-			$this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION;
+			$this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION."\n";
 			return false;
 		}
 
@@ -137,9 +140,9 @@ class getID3
 		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
@@ -147,27 +150,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) {
@@ -186,7 +192,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();
@@ -206,7 +212,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;
@@ -216,6 +222,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;
 	}
 
@@ -249,7 +260,9 @@ class getID3
 				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
@@ -259,7 +272,7 @@ 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');
 			}
 
@@ -426,14 +439,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);
 			}
@@ -568,7 +581,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',
@@ -586,7 +599,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',
@@ -594,7 +607,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',
@@ -604,7 +617,7 @@ class getID3
 
 				// AU   - audio       - NeXT/Sun AUdio (AU)
 				'au'   => array(
-							'pattern'   => '^\.snd',
+							'pattern'   => '^\\.snd',
 							'group'     => 'audio',
 							'module'    => 'au',
 							'mime_type' => 'audio/basic',
@@ -612,7 +625,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',
@@ -628,7 +641,7 @@ 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',
@@ -644,7 +657,7 @@ class getID3
 
 				// 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',
@@ -652,7 +665,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',
@@ -737,7 +750,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',
@@ -745,7 +758,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',
@@ -753,7 +766,7 @@ class getID3
 
 				// OFR  - audio       - OptimFROG
 				'ofr'  => array(
-							'pattern'   => '^(\*RIFF|OFR)',
+							'pattern'   => '^(\\*RIFF|OFR)',
 							'group'     => 'audio',
 							'module'    => 'optimfrog',
 							'mime_type' => 'application/octet-stream',
@@ -779,7 +792,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',
@@ -814,7 +827,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',
@@ -831,7 +844,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',
@@ -839,7 +852,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
@@ -847,7 +860,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',
@@ -890,7 +903,7 @@ class getID3
 
 				// Real - audio/video - RealAudio, RealVideo
 				'real' => array(
-							'pattern'   => '^(\\.RMF|\\.ra)',
+							'pattern'   => '^\\.(RMF|ra)',
 							'group'     => 'audio-video',
 							'module'    => 'real',
 							'mime_type' => 'audio/x-realaudio',
@@ -906,7 +919,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',
@@ -937,7 +950,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',
@@ -947,7 +960,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',
@@ -958,7 +971,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',
@@ -969,7 +982,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',
@@ -980,7 +993,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',
@@ -991,7 +1004,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',
@@ -1015,7 +1028,7 @@ class getID3
 
 				// RAR  - data        - RAR compressed data
 				'rar'  => array(
-							'pattern'   => '^Rar\!',
+							'pattern'   => '^Rar\\!',
 							'group'     => 'archive',
 							'module'    => 'rar',
 							'mime_type' => 'application/octet-stream',
@@ -1025,7 +1038,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',
@@ -1035,7 +1048,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',
@@ -1045,7 +1058,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',
@@ -1055,7 +1068,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',
@@ -1068,7 +1081,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',
@@ -1078,7 +1091,7 @@ class getID3
 
 				// PDF  - data        - Portable Document Format
 				'pdf'  => array(
-							'pattern'   => '^\x25PDF',
+							'pattern'   => '^\\x25PDF',
 							'group'     => 'misc',
 							'module'    => 'pdf',
 							'mime_type' => 'application/pdf',
@@ -1088,7 +1101,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',
@@ -1129,14 +1142,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
@@ -1237,36 +1250,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']);
 					}
 				}
 
-				// 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
-				if ($comment_name == 'id3v1') {
-					if ($encoding == 'ISO-8859-1') {
-						if (function_exists('iconv')) {
-							foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
-								foreach ($valuearray as $key => $value) {
-									if (preg_match('#^[\\x80-\\xFF]+$#', $value)) {
-										foreach (array('windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
-											if (@iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) {
-												$encoding = $id3v1_bad_encoding;
-												break 3;
-											}
-										}
-									}
-								}
-							}
-						}
-					}
-				}
-				// ID3v1 encoding detection hack end
-
-				$this->CharConvert($this->info['tags'][$tag_name], $encoding);           // only copy gets converted!
 			}
 
 		}
@@ -1699,7 +1690,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) {
diff --git a/lib/getid3/module.audio-video.asf.php b/lib/getid3/module.audio-video.asf.php
index ee61d7dc..b22798f8 100644
--- a/lib/getid3/module.audio-video.asf.php
+++ b/lib/getid3/module.audio-video.asf.php
@@ -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'].'"';
+								$info['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']));
diff --git a/lib/getid3/module.audio-video.matroska.php b/lib/getid3/module.audio-video.matroska.php
index 09793602..57e2746b 100644
--- a/lib/getid3/module.audio-video.matroska.php
+++ b/lib/getid3/module.audio-video.matroska.php
@@ -566,8 +566,11 @@ class getid3_matroska extends getid3_handler
 														$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;
diff --git a/lib/getid3/module.audio-video.quicktime.php b/lib/getid3/module.audio-video.quicktime.php
index d46cc6ea..eecd96a3 100644
--- a/lib/getid3/module.audio-video.quicktime.php
+++ b/lib/getid3/module.audio-video.quicktime.php
@@ -35,7 +35,7 @@ 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';
@@ -173,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) {
diff --git a/lib/getid3/module.audio.dss.php b/lib/getid3/module.audio.dss.php
index 2a5b1a73..188b4709 100644
--- a/lib/getid3/module.audio.dss.php
+++ b/lib/getid3/module.audio.dss.php
@@ -24,8 +24,8 @@ class getid3_dss extends getid3_handler
 		$this->fseek($info['avdataoffset']);
 		$DSSheader  = $this->fread(1540);
 
-		if (!preg_match('#^(\x02|\x03)ds[s2]#', $DSSheader)) {
-			$info['error'][] = 'Expecting "[02-03] 64 73 [73|32]" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSSheader, 0, 4)).'"';
+		if (!preg_match('#^[\\x02-\\x06]ds[s2]#', $DSSheader)) {
+			$info['error'][] = 'Expecting "[02-06] 64 73 [73|32]" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSSheader, 0, 4)).'"';
 			return false;
 		}
 
@@ -38,27 +38,33 @@ class getid3_dss extends getid3_handler
 		$info['audio']['dataformat']   =            substr($DSSheader, 1, 3); //         "dss" or         "ds2"
 		$info['audio']['bitrate_mode'] = 'cbr';
 
-		$info['dss']['version']           =                            ord(substr($DSSheader,    0,   1));
-		$info['dss']['hardware']          =                           trim(substr($DSSheader,   12,  16)); // identification string for hardware used to create the file, e.g. "DPM 9600", "DS2400"
-		$info['dss']['unknown1']          =   getid3_lib::LittleEndian2Int(substr($DSSheader,   28,   4));
+		$info['dss']['version']            =                            ord(substr($DSSheader,    0,   1));
+		$info['dss']['hardware']           =                           trim(substr($DSSheader,   12,  16)); // identification string for hardware used to create the file, e.g. "DPM 9600", "DS2400"
+		$info['dss']['unknown1']           =   getid3_lib::LittleEndian2Int(substr($DSSheader,   28,   4));
 		// 32-37 = "FE FF FE FF F7 FF" in all the sample files I've seen
-		$info['dss']['date_create']       = $this->DSSdateStringToUnixDate(substr($DSSheader,   38,  12));
-		$info['dss']['date_complete']     = $this->DSSdateStringToUnixDate(substr($DSSheader,   50,  12));
-		$info['dss']['playtime_sec']      = intval((substr($DSSheader,  62, 2) * 3600) + (substr($DSSheader,  64, 2) * 60) + substr($DSSheader,  66, 2)); // approximate file playtime in HHMMSS
-		$info['dss']['playtime_ms']       =   getid3_lib::LittleEndian2Int(substr($DSSheader,  512,   4)); // exact file playtime in milliseconds. Has also been observed at offset 530 in one sample file, with something else (unknown) at offset 512
-		$info['dss']['priority']          =                            ord(substr($DSSheader,  793,   1));
-		$info['dss']['comments']          =                           trim(substr($DSSheader,  798, 100));
-		$info['dss']['sample_rate_index'] =                            ord(substr($DSSheader, 1538,   1));  // this isn't certain, this may or may not be where the sample rate info is stored, but it seems consistent on my small selection of sample files
+		$info['dss']['date_create_unix']   = $this->DSSdateStringToUnixDate(substr($DSSheader,   38,  12));
+		$info['dss']['date_complete_unix'] = $this->DSSdateStringToUnixDate(substr($DSSheader,   50,  12));
+		$info['dss']['playtime_sec']       = intval((substr($DSSheader,  62, 2) * 3600) + (substr($DSSheader,  64, 2) * 60) + substr($DSSheader,  66, 2)); // approximate file playtime in HHMMSS
+		if ($info['dss']['version'] <= 3) {
+			$info['dss']['playtime_ms']        =   getid3_lib::LittleEndian2Int(substr($DSSheader,  512,   4)); // exact file playtime in milliseconds. Has also been observed at offset 530 in one sample file, with something else (unknown) at offset 512
+			$info['dss']['priority']           =                            ord(substr($DSSheader,  793,   1));
+			$info['dss']['comments']           =                           trim(substr($DSSheader,  798, 100));
+			$info['dss']['sample_rate_index']  =                            ord(substr($DSSheader, 1538,   1));  // this isn't certain, this may or may not be where the sample rate info is stored, but it seems consistent on my small selection of sample files
+			$info['audio']['sample_rate']      = $this->DSSsampleRateLookup($info['dss']['sample_rate_index']);
+		} else {
+			$this->getid3->warning('DSS above version 3 not fully supported in this version of getID3. Any additional documentation or format specifications would be welcome. This file is version '.$info['dss']['version']);
+		}
 
 		$info['audio']['bits_per_sample']  = 16; // maybe, maybe not -- most compressed audio formats don't have a fixed bits-per-sample value, but this is a reasonable approximation
-		$info['audio']['sample_rate']      = $this->DSSsampleRateLookup($info['dss']['sample_rate_index']);
 		$info['audio']['channels']         = 1;
 
-		$info['playtime_seconds'] = $info['dss']['playtime_ms'] / 1000;
-		if (floor($info['dss']['playtime_ms'] / 1000) != $info['dss']['playtime_sec']) {
-			// *should* just be playtime_ms / 1000 but at least one sample file has playtime_ms at offset 530 instead of offset 512, so safety check
+		if (!empty($info['dss']['playtime_ms']) && (floor($info['dss']['playtime_ms'] / 1000) == $info['dss']['playtime_sec'])) { // *should* just be playtime_ms / 1000 but at least one sample file has playtime_ms at offset 530 instead of offset 512, so safety check
+			$info['playtime_seconds'] = $info['dss']['playtime_ms'] / 1000;
+		} else {
 			$info['playtime_seconds'] = $info['dss']['playtime_sec'];
-			$this->getid3->warning('playtime_ms ('.number_format($info['dss']['playtime_ms'] / 1000, 3).') does not match playtime_sec ('.number_format($info['dss']['playtime_sec']).') - using playtime_sec value');
+			if (!empty($info['dss']['playtime_ms'])) {
+				$this->getid3->warning('playtime_ms ('.number_format($info['dss']['playtime_ms'] / 1000, 3).') does not match playtime_sec ('.number_format($info['dss']['playtime_sec']).') - using playtime_sec value');
+			}
 		}
 		$info['audio']['bitrate'] = ($info['filesize'] * 8) / $info['playtime_seconds'];
 
@@ -84,7 +90,7 @@ class getid3_dss extends getid3_handler
 			0x15 =>  8000,
 		);
 		if (!array_key_exists($sample_rate_index, $dssSampleRateLookup)) {
-			$this->getid3->warning('unknown sample_rate_index: '.$sample_rate_index);
+			$this->getid3->warning('unknown sample_rate_index: 0x'.strtoupper(dechex($sample_rate_index)));
 			return false;
 		}
 		return $dssSampleRateLookup[$sample_rate_index];
diff --git a/lib/getid3/module.graphic.jpg.php b/lib/getid3/module.graphic.jpg.php
index 64a019c3..9613c692 100644
--- a/lib/getid3/module.graphic.jpg.php
+++ b/lib/getid3/module.graphic.jpg.php
@@ -68,7 +68,18 @@ class getid3_jpg extends getid3_handler
 						if (substr($imageinfo['APP1'], 0, 4) == 'Exif') {
 //$info['warning'][] = 'known issue: https://bugs.php.net/bug.php?id=62523';
 //return false;
+							set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) {
+								if (!(error_reporting() & $errno)) {
+									// error is not specified in the error_reporting setting, so we ignore it
+									return false;
+								}
+
+								$errcontext['info']['warning'][] = 'Error parsing EXIF data ('.$errstr.')';
+							});
+
 							$info['jpg']['exif'] = exif_read_data($info['filenamepath'], null, true, false);
+
+							restore_error_handler();
 						} else {
 							$info['warning'][] = 'exif_read_data() cannot parse non-EXIF data in APP1 (expected "Exif", found "'.substr($imageinfo['APP1'], 0, 4).'")';
 						}
diff --git a/lib/getid3/module.tag.apetag.php b/lib/getid3/module.tag.apetag.php
index 819e5e33..6626c7d6 100644
--- a/lib/getid3/module.tag.apetag.php
+++ b/lib/getid3/module.tag.apetag.php
@@ -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);
+				$info['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;
diff --git a/lib/getid3/module.tag.id3v1.php b/lib/getid3/module.tag.id3v1.php
index 3b4edfd2..088e645a 100644
--- a/lib/getid3/module.tag.id3v1.php
+++ b/lib/getid3/module.tag.id3v1.php
@@ -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(
@@ -80,6 +100,7 @@ class getid3_id3v1 extends getid3_handler
 			$ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
 
 			$info['id3v1'] = $ParsedID3v1;
+			$info['id3v1']['encoding'] = $ID3v1encoding;
 		}
 
 		if (substr($preid3v1, 0, 3) == 'TAG') {
diff --git a/lib/getid3/module.tag.id3v2.php b/lib/getid3/module.tag.id3v2.php
index de334135..14b1ff59 100644
--- a/lib/getid3/module.tag.id3v2.php
+++ b/lib/getid3/module.tag.id3v2.php
@@ -329,9 +329,9 @@ class getid3_id3v2 extends getid3_handler
 					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)) {
+					$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", "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))) {
 
@@ -504,18 +504,27 @@ 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();
-		if (strpos($genrestring, "\x00") === false) {
-			$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
+
+		// 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);
+			}
 		}
 
-		// 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
-		$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"
-		$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) {
@@ -1744,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;
@@ -3644,5 +3653,90 @@ 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/lib/getid3/write.id3v2.php b/lib/getid3/write.id3v2.php
index d3fa84c4..17138db8 100644
--- a/lib/getid3/write.id3v2.php
+++ b/lib/getid3/write.id3v2.php
@@ -815,7 +815,7 @@ class getid3_write_id3v2
 					// Counter         $xx xx xx xx (xx ...)
 					if (!$this->IsWithinBitRange($source_data_array['rating'], 8, false)) {
 						$this->errors[] = 'Invalid Rating byte in '.$frame_name.' ('.$source_data_array['rating'].') (range = 0 to 255)';
-					} elseif (!IsValidEmail($source_data_array['email'])) {
+					} elseif (!$this->IsValidEmail($source_data_array['email'])) {
 						$this->errors[] = 'Invalid Email in '.$frame_name.' ('.$source_data_array['email'].')';
 					} else {
 						$framedata .= str_replace("\x00", '', $source_data_array['email'])."\x00";
@@ -1183,7 +1183,6 @@ class getid3_write_id3v2
 			$PreviousFrames = array();
 			return true;
 		}
-
 		if ($this->majorversion == 4) {
 			switch ($frame_name) {
 				case 'UFID':
@@ -1744,14 +1743,14 @@ class getid3_write_id3v2
 		return false;
 	}
 
-	public function ID3v2IsValidRGADname($RGADname) {
+	public static function ID3v2IsValidRGADname($RGADname) {
 		if (($RGADname >= 0) && ($RGADname <= 2)) {
 			return true;
 		}
 		return false;
 	}
 
-	public function ID3v2IsValidRGADoriginator($RGADoriginator) {
+	public static function ID3v2IsValidRGADoriginator($RGADoriginator) {
 		if (($RGADoriginator >= 0) && ($RGADoriginator <= 3)) {
 			return true;
 		}
@@ -1771,7 +1770,7 @@ class getid3_write_id3v2
 		return isset($ID3v2IsValidTextEncoding_cache[$this->majorversion][$textencodingbyte]);
 	}
 
-	public function Unsynchronise($data) {
+	public static function Unsynchronise($data) {
 		// Whenever a false synchronisation is found within the tag, one zeroed
 		// byte is inserted after the first false synchronisation byte. The
 		// format of a correct sync that should be altered by ID3 encoders is as
@@ -1840,14 +1839,11 @@ class getid3_write_id3v2
 		}
 	}
 
-	public function IsValidMIMEstring($mimestring) {
-		if ((strlen($mimestring) >= 3) && (strpos($mimestring, '/') > 0) && (strpos($mimestring, '/') < (strlen($mimestring) - 1))) {
-			return true;
-		}
-		return false;
+	public static function IsValidMIMEstring($mimestring) {
+		return preg_match('#^.+/.+$#', $mimestring);
 	}
 
-	public function IsWithinBitRange($number, $maxbits, $signed=false) {
+	public static function IsWithinBitRange($number, $maxbits, $signed=false) {
 		if ($signed) {
 			if (($number > (0 - pow(2, $maxbits - 1))) && ($number <= pow(2, $maxbits - 1))) {
 				return true;
@@ -1860,18 +1856,15 @@ class getid3_write_id3v2
 		return false;
 	}
 
-	public function safe_parse_url($url) {
-		$parts = @parse_url($url);
-		$parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : '');
-		$parts['host']   = (isset($parts['host'])   ? $parts['host']   : '');
-		$parts['user']   = (isset($parts['user'])   ? $parts['user']   : '');
-		$parts['pass']   = (isset($parts['pass'])   ? $parts['pass']   : '');
-		$parts['path']   = (isset($parts['path'])   ? $parts['path']   : '');
-		$parts['query']  = (isset($parts['query'])  ? $parts['query']  : '');
-		return $parts;
+	public static function IsValidEmail($email) {
+		if (function_exists('filter_var')) {
+			return filter_var($email, FILTER_VALIDATE_EMAIL);
+		}
+		// VERY crude email validation
+		return preg_match('#^[^ ]+@[a-z\\-\\.]+\\.[a-z]{2,}$#', $email);
 	}
 
-	public function IsValidURL($url, $allowUserPass=false) {
+	public static function IsValidURL($url, $allowUserPass=false) {
 		if ($url == '') {
 			return false;
 		}
@@ -1882,6 +1875,10 @@ class getid3_write_id3v2
 				return false;
 			}
 		}
+		// 2016-06-08: relax URL checking to avoid falsely rejecting valid URLs, leave URL validation to the user
+		// http://www.getid3.org/phpBB3/viewtopic.php?t=1926
+		return true;
+		/*
 		if ($parts = $this->safe_parse_url($url)) {
 			if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher')) {
 				return false;
@@ -1900,6 +1897,18 @@ class getid3_write_id3v2
 			}
 		}
 		return false;
+		*/
+	}
+
+	public static function safe_parse_url($url) {
+		$parts = @parse_url($url);
+		$parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : '');
+		$parts['host']   = (isset($parts['host'])   ? $parts['host']   : '');
+		$parts['user']   = (isset($parts['user'])   ? $parts['user']   : '');
+		$parts['pass']   = (isset($parts['pass'])   ? $parts['pass']   : '');
+		$parts['path']   = (isset($parts['path'])   ? $parts['path']   : '');
+		$parts['query']  = (isset($parts['query'])  ? $parts['query']  : '');
+		return $parts;
 	}
 
 	public static function ID3v2ShortFrameNameLookup($majorversion, $long_description) {
@@ -1908,152 +1917,189 @@ class getid3_write_id3v2
 		if (empty($ID3v2ShortFrameNameLookup)) {
 
 			// The following are unique to ID3v2.2
-			$ID3v2ShortFrameNameLookup[2]['comment']                                          = 'COM';
-			$ID3v2ShortFrameNameLookup[2]['album']                                            = 'TAL';
-			$ID3v2ShortFrameNameLookup[2]['beats_per_minute']                                 = 'TBP';
-			$ID3v2ShortFrameNameLookup[2]['bpm']                                              = 'TBP';
-			$ID3v2ShortFrameNameLookup[2]['composer']                                         = 'TCM';
-			$ID3v2ShortFrameNameLookup[2]['genre']                                            = 'TCO';
-			$ID3v2ShortFrameNameLookup[2]['itunescompilation']                                = 'TCP';
-			$ID3v2ShortFrameNameLookup[2]['copyright']                                        = 'TCR';
-			$ID3v2ShortFrameNameLookup[2]['encoded_by']                                       = 'TEN';
-			$ID3v2ShortFrameNameLookup[2]['language']                                         = 'TLA';
-			$ID3v2ShortFrameNameLookup[2]['length']                                           = 'TLE';
-			$ID3v2ShortFrameNameLookup[2]['original_artist']                                  = 'TOA';
-			$ID3v2ShortFrameNameLookup[2]['original_filename']                                = 'TOF';
-			$ID3v2ShortFrameNameLookup[2]['original_lyricist']                                = 'TOL';
-			$ID3v2ShortFrameNameLookup[2]['original_album_title']                             = 'TOT';
-			$ID3v2ShortFrameNameLookup[2]['artist']                                           = 'TP1';
-			$ID3v2ShortFrameNameLookup[2]['band']                                             = 'TP2';
-			$ID3v2ShortFrameNameLookup[2]['conductor']                                        = 'TP3';
-			$ID3v2ShortFrameNameLookup[2]['remixer']                                          = 'TP4';
-			$ID3v2ShortFrameNameLookup[2]['publisher']                                        = 'TPB';
-			$ID3v2ShortFrameNameLookup[2]['isrc']                                             = 'TRC';
-			$ID3v2ShortFrameNameLookup[2]['tracknumber']                                      = 'TRK';
-			$ID3v2ShortFrameNameLookup[2]['track_number']                                     = 'TRK';
-			$ID3v2ShortFrameNameLookup[2]['size']                                             = 'TSI';
-			$ID3v2ShortFrameNameLookup[2]['encoder_settings']                                 = 'TSS';
-			$ID3v2ShortFrameNameLookup[2]['description']                                      = 'TT1';
-			$ID3v2ShortFrameNameLookup[2]['title']                                            = 'TT2';
-			$ID3v2ShortFrameNameLookup[2]['subtitle']                                         = 'TT3';
-			$ID3v2ShortFrameNameLookup[2]['lyricist']                                         = 'TXT';
-			$ID3v2ShortFrameNameLookup[2]['user_text']                                        = 'TXX';
-			$ID3v2ShortFrameNameLookup[2]['year']                                             = 'TYE';
-			$ID3v2ShortFrameNameLookup[2]['unique_file_identifier']                           = 'UFI';
-			$ID3v2ShortFrameNameLookup[2]['unsynchronised_lyrics']                            = 'ULT';
-			$ID3v2ShortFrameNameLookup[2]['url_file']                                         = 'WAF';
-			$ID3v2ShortFrameNameLookup[2]['url_artist']                                       = 'WAR';
-			$ID3v2ShortFrameNameLookup[2]['url_source']                                       = 'WAS';
-			$ID3v2ShortFrameNameLookup[2]['copyright_information']                            = 'WCP';
-			$ID3v2ShortFrameNameLookup[2]['url_publisher']                                    = 'WPB';
-			$ID3v2ShortFrameNameLookup[2]['url_user']                                         = 'WXX';
+			$ID3v2ShortFrameNameLookup[2]['recommended_buffer_size']           = 'BUF';
+			$ID3v2ShortFrameNameLookup[2]['comment']                           = 'COM';
+			$ID3v2ShortFrameNameLookup[2]['audio_encryption']                  = 'CRA';
+			$ID3v2ShortFrameNameLookup[2]['encrypted_meta_frame']              = 'CRM';
+			$ID3v2ShortFrameNameLookup[2]['equalisation']                      = 'EQU';
+			$ID3v2ShortFrameNameLookup[2]['event_timing_codes']                = 'ETC';
+			$ID3v2ShortFrameNameLookup[2]['general_encapsulated_object']       = 'GEO';
+			$ID3v2ShortFrameNameLookup[2]['involved_people_list']              = 'IPL';
+			$ID3v2ShortFrameNameLookup[2]['linked_information']                = 'LNK';
+			$ID3v2ShortFrameNameLookup[2]['music_cd_identifier']               = 'MCI';
+			$ID3v2ShortFrameNameLookup[2]['mpeg_location_lookup_table']        = 'MLL';
+			$ID3v2ShortFrameNameLookup[2]['attached_picture']                  = 'PIC';
+			$ID3v2ShortFrameNameLookup[2]['popularimeter']                     = 'POP';
+			$ID3v2ShortFrameNameLookup[2]['reverb']                            = 'REV';
+			$ID3v2ShortFrameNameLookup[2]['relative_volume_adjustment']        = 'RVA';
+			$ID3v2ShortFrameNameLookup[2]['synchronised_lyric']                = 'SLT';
+			$ID3v2ShortFrameNameLookup[2]['synchronised_tempo_codes']          = 'STC';
+			$ID3v2ShortFrameNameLookup[2]['album']                             = 'TAL';
+			$ID3v2ShortFrameNameLookup[2]['beats_per_minute']                  = 'TBP';
+			$ID3v2ShortFrameNameLookup[2]['bpm']                               = 'TBP';
+			$ID3v2ShortFrameNameLookup[2]['composer']                          = 'TCM';
+			$ID3v2ShortFrameNameLookup[2]['genre']                             = 'TCO';
+			$ID3v2ShortFrameNameLookup[2]['part_of_a_compilation']             = 'TCP';
+			$ID3v2ShortFrameNameLookup[2]['copyright_message']                 = 'TCR';
+			$ID3v2ShortFrameNameLookup[2]['date']                              = 'TDA';
+			$ID3v2ShortFrameNameLookup[2]['playlist_delay']                    = 'TDY';
+			$ID3v2ShortFrameNameLookup[2]['encoded_by']                        = 'TEN';
+			$ID3v2ShortFrameNameLookup[2]['file_type']                         = 'TFT';
+			$ID3v2ShortFrameNameLookup[2]['time']                              = 'TIM';
+			$ID3v2ShortFrameNameLookup[2]['initial_key']                       = 'TKE';
+			$ID3v2ShortFrameNameLookup[2]['language']                          = 'TLA';
+			$ID3v2ShortFrameNameLookup[2]['length']                            = 'TLE';
+			$ID3v2ShortFrameNameLookup[2]['media_type']                        = 'TMT';
+			$ID3v2ShortFrameNameLookup[2]['original_artist']                   = 'TOA';
+			$ID3v2ShortFrameNameLookup[2]['original_filename']                 = 'TOF';
+			$ID3v2ShortFrameNameLookup[2]['original_lyricist']                 = 'TOL';
+			$ID3v2ShortFrameNameLookup[2]['original_year']                     = 'TOR';
+			$ID3v2ShortFrameNameLookup[2]['original_album']                    = 'TOT';
+			$ID3v2ShortFrameNameLookup[2]['artist']                            = 'TP1';
+			$ID3v2ShortFrameNameLookup[2]['band']                              = 'TP2';
+			$ID3v2ShortFrameNameLookup[2]['conductor']                         = 'TP3';
+			$ID3v2ShortFrameNameLookup[2]['remixer']                           = 'TP4';
+			$ID3v2ShortFrameNameLookup[2]['part_of_a_set']                     = 'TPA';
+			$ID3v2ShortFrameNameLookup[2]['publisher']                         = 'TPB';
+			$ID3v2ShortFrameNameLookup[2]['isrc']                              = 'TRC';
+			$ID3v2ShortFrameNameLookup[2]['recording_dates']                   = 'TRD';
+			$ID3v2ShortFrameNameLookup[2]['tracknumber']                       = 'TRK';
+			$ID3v2ShortFrameNameLookup[2]['track_number']                      = 'TRK';
+			$ID3v2ShortFrameNameLookup[2]['album_artist_sort_order']           = 'TS2';
+			$ID3v2ShortFrameNameLookup[2]['album_sort_order']                  = 'TSA';
+			$ID3v2ShortFrameNameLookup[2]['composer_sort_order']               = 'TSC';
+			$ID3v2ShortFrameNameLookup[2]['size']                              = 'TSI';
+			$ID3v2ShortFrameNameLookup[2]['performer_sort_order']              = 'TSP';
+			$ID3v2ShortFrameNameLookup[2]['encoder_settings']                  = 'TSS';
+			$ID3v2ShortFrameNameLookup[2]['title_sort_order']                  = 'TST';
+			$ID3v2ShortFrameNameLookup[2]['content_group_description']         = 'TT1';
+			$ID3v2ShortFrameNameLookup[2]['title']                             = 'TT2';
+			$ID3v2ShortFrameNameLookup[2]['subtitle']                          = 'TT3';
+			$ID3v2ShortFrameNameLookup[2]['lyricist']                          = 'TXT';
+			$ID3v2ShortFrameNameLookup[2]['text']                              = 'TXX';
+			$ID3v2ShortFrameNameLookup[2]['year']                              = 'TYE';
+			$ID3v2ShortFrameNameLookup[2]['unique_file_identifier']            = 'UFI';
+			$ID3v2ShortFrameNameLookup[2]['unsychronised_lyric']               = 'ULT';
+			$ID3v2ShortFrameNameLookup[2]['url_file']                          = 'WAF';
+			$ID3v2ShortFrameNameLookup[2]['url_artist']                        = 'WAR';
+			$ID3v2ShortFrameNameLookup[2]['url_source']                        = 'WAS';
+			$ID3v2ShortFrameNameLookup[2]['commercial_information']            = 'WCM';
+			$ID3v2ShortFrameNameLookup[2]['copyright']                         = 'WCP';
+			$ID3v2ShortFrameNameLookup[2]['url_publisher']                     = 'WPB';
+			$ID3v2ShortFrameNameLookup[2]['url_user']                          = 'WXX';
 
 			// The following are common to ID3v2.3 and ID3v2.4
-			$ID3v2ShortFrameNameLookup[3]['audio_encryption']                                 = 'AENC';
-			$ID3v2ShortFrameNameLookup[3]['attached_picture']                                 = 'APIC';
-			$ID3v2ShortFrameNameLookup[3]['picture']                                          = 'APIC';
-			$ID3v2ShortFrameNameLookup[3]['comment']                                          = 'COMM';
-			$ID3v2ShortFrameNameLookup[3]['commercial']                                       = 'COMR';
-			$ID3v2ShortFrameNameLookup[3]['encryption_method_registration']                   = 'ENCR';
-			$ID3v2ShortFrameNameLookup[3]['event_timing_codes']                               = 'ETCO';
-			$ID3v2ShortFrameNameLookup[3]['general_encapsulated_object']                      = 'GEOB';
-			$ID3v2ShortFrameNameLookup[3]['group_identification_registration']                = 'GRID';
-			$ID3v2ShortFrameNameLookup[3]['linked_information']                               = 'LINK';
-			$ID3v2ShortFrameNameLookup[3]['music_cd_identifier']                              = 'MCDI';
-			$ID3v2ShortFrameNameLookup[3]['mpeg_location_lookup_table']                       = 'MLLT';
-			$ID3v2ShortFrameNameLookup[3]['ownership']                                        = 'OWNE';
-			$ID3v2ShortFrameNameLookup[3]['play_counter']                                     = 'PCNT';
-			$ID3v2ShortFrameNameLookup[3]['popularimeter']                                    = 'POPM';
-			$ID3v2ShortFrameNameLookup[3]['position_synchronisation']                         = 'POSS';
-			$ID3v2ShortFrameNameLookup[3]['private']                                          = 'PRIV';
-			$ID3v2ShortFrameNameLookup[3]['recommended_buffer_size']                          = 'RBUF';
-			$ID3v2ShortFrameNameLookup[3]['reverb']                                           = 'RVRB';
-			$ID3v2ShortFrameNameLookup[3]['synchronised_lyrics']                              = 'SYLT';
-			$ID3v2ShortFrameNameLookup[3]['synchronised_tempo_codes']                         = 'SYTC';
-			$ID3v2ShortFrameNameLookup[3]['album']                                            = 'TALB';
-			$ID3v2ShortFrameNameLookup[3]['beats_per_minute']                                 = 'TBPM';
-			$ID3v2ShortFrameNameLookup[3]['bpm']                                              = 'TBPM';
-			$ID3v2ShortFrameNameLookup[3]['itunescompilation']                                = 'TCMP';
-			$ID3v2ShortFrameNameLookup[3]['composer']                                         = 'TCOM';
-			$ID3v2ShortFrameNameLookup[3]['genre']                                            = 'TCON';
-			$ID3v2ShortFrameNameLookup[3]['copyright']                                        = 'TCOP';
-			$ID3v2ShortFrameNameLookup[3]['playlist_delay']                                   = 'TDLY';
-			$ID3v2ShortFrameNameLookup[3]['encoded_by']                                       = 'TENC';
-			$ID3v2ShortFrameNameLookup[3]['lyricist']                                         = 'TEXT';
-			$ID3v2ShortFrameNameLookup[3]['file_type']                                        = 'TFLT';
-			$ID3v2ShortFrameNameLookup[3]['content_group_description']                        = 'TIT1';
-			$ID3v2ShortFrameNameLookup[3]['title']                                            = 'TIT2';
-			$ID3v2ShortFrameNameLookup[3]['subtitle']                                         = 'TIT3';
-			$ID3v2ShortFrameNameLookup[3]['initial_key']                                      = 'TKEY';
-			$ID3v2ShortFrameNameLookup[3]['language']                                         = 'TLAN';
-			$ID3v2ShortFrameNameLookup[3]['length']                                           = 'TLEN';
-			$ID3v2ShortFrameNameLookup[3]['media_type']                                       = 'TMED';
-			$ID3v2ShortFrameNameLookup[3]['original_album_title']                             = 'TOAL';
-			$ID3v2ShortFrameNameLookup[3]['original_filename']                                = 'TOFN';
-			$ID3v2ShortFrameNameLookup[3]['original_lyricist']                                = 'TOLY';
-			$ID3v2ShortFrameNameLookup[3]['original_artist']                                  = 'TOPE';
-			$ID3v2ShortFrameNameLookup[3]['file_owner']                                       = 'TOWN';
-			$ID3v2ShortFrameNameLookup[3]['artist']                                           = 'TPE1';
-			$ID3v2ShortFrameNameLookup[3]['band']                                             = 'TPE2';
-			$ID3v2ShortFrameNameLookup[3]['conductor']                                        = 'TPE3';
-			$ID3v2ShortFrameNameLookup[3]['remixer']                                          = 'TPE4';
-			$ID3v2ShortFrameNameLookup[3]['part_of_a_set']                                    = 'TPOS';
-			$ID3v2ShortFrameNameLookup[3]['publisher']                                        = 'TPUB';
-			$ID3v2ShortFrameNameLookup[3]['tracknumber']                                      = 'TRCK';
-			$ID3v2ShortFrameNameLookup[3]['track_number']                                     = 'TRCK';
-			$ID3v2ShortFrameNameLookup[3]['internet_radio_station_name']                      = 'TRSN';
-			$ID3v2ShortFrameNameLookup[3]['internet_radio_station_owner']                     = 'TRSO';
-			$ID3v2ShortFrameNameLookup[3]['isrc']                                             = 'TSRC';
-			$ID3v2ShortFrameNameLookup[3]['encoder_settings']                                 = 'TSSE';
-			$ID3v2ShortFrameNameLookup[3]['user_text']                                        = 'TXXX';
-			$ID3v2ShortFrameNameLookup[3]['unique_file_identifier']                           = 'UFID';
-			$ID3v2ShortFrameNameLookup[3]['terms_of_use']                                     = 'USER';
-			$ID3v2ShortFrameNameLookup[3]['unsynchronised_lyrics']                            = 'USLT';
-			$ID3v2ShortFrameNameLookup[3]['commercial']                                       = 'WCOM';
-			$ID3v2ShortFrameNameLookup[3]['copyright_information']                            = 'WCOP';
-			$ID3v2ShortFrameNameLookup[3]['url_file']                                         = 'WOAF';
-			$ID3v2ShortFrameNameLookup[3]['url_artist']                                       = 'WOAR';
-			$ID3v2ShortFrameNameLookup[3]['url_source']                                       = 'WOAS';
-			$ID3v2ShortFrameNameLookup[3]['url_station']                                      = 'WORS';
-			$ID3v2ShortFrameNameLookup[3]['payment']                                          = 'WPAY';
-			$ID3v2ShortFrameNameLookup[3]['url_publisher']                                    = 'WPUB';
-			$ID3v2ShortFrameNameLookup[3]['url_user']                                         = 'WXXX';
+			$ID3v2ShortFrameNameLookup[3]['audio_encryption']                  = 'AENC';
+			$ID3v2ShortFrameNameLookup[3]['attached_picture']                  = 'APIC';
+			$ID3v2ShortFrameNameLookup[3]['picture']                           = 'APIC';
+			$ID3v2ShortFrameNameLookup[3]['comment']                           = 'COMM';
+			$ID3v2ShortFrameNameLookup[3]['commercial_frame']                  = 'COMR';
+			$ID3v2ShortFrameNameLookup[3]['encryption_method_registration']    = 'ENCR';
+			$ID3v2ShortFrameNameLookup[3]['event_timing_codes']                = 'ETCO';
+			$ID3v2ShortFrameNameLookup[3]['general_encapsulated_object']       = 'GEOB';
+			$ID3v2ShortFrameNameLookup[3]['group_identification_registration'] = 'GRID';
+			$ID3v2ShortFrameNameLookup[3]['linked_information']                = 'LINK';
+			$ID3v2ShortFrameNameLookup[3]['music_cd_identifier']               = 'MCDI';
+			$ID3v2ShortFrameNameLookup[3]['mpeg_location_lookup_table']        = 'MLLT';
+			$ID3v2ShortFrameNameLookup[3]['ownership_frame']                   = 'OWNE';
+			$ID3v2ShortFrameNameLookup[3]['play_counter']                      = 'PCNT';
+			$ID3v2ShortFrameNameLookup[3]['popularimeter']                     = 'POPM';
+			$ID3v2ShortFrameNameLookup[3]['position_synchronisation_frame']    = 'POSS';
+			$ID3v2ShortFrameNameLookup[3]['private_frame']                     = 'PRIV';
+			$ID3v2ShortFrameNameLookup[3]['recommended_buffer_size']           = 'RBUF';
+			$ID3v2ShortFrameNameLookup[3]['replay_gain_adjustment']            = 'RGAD';
+			$ID3v2ShortFrameNameLookup[3]['reverb']                            = 'RVRB';
+			$ID3v2ShortFrameNameLookup[3]['synchronised_lyric']                = 'SYLT';
+			$ID3v2ShortFrameNameLookup[3]['synchronised_tempo_codes']          = 'SYTC';
+			$ID3v2ShortFrameNameLookup[3]['album']                             = 'TALB';
+			$ID3v2ShortFrameNameLookup[3]['beats_per_minute']                  = 'TBPM';
+			$ID3v2ShortFrameNameLookup[3]['bpm']                               = 'TBPM';
+			$ID3v2ShortFrameNameLookup[3]['part_of_a_compilation']             = 'TCMP';
+			$ID3v2ShortFrameNameLookup[3]['composer']                          = 'TCOM';
+			$ID3v2ShortFrameNameLookup[3]['genre']                             = 'TCON';
+			$ID3v2ShortFrameNameLookup[3]['copyright_message']                 = 'TCOP';
+			$ID3v2ShortFrameNameLookup[3]['playlist_delay']                    = 'TDLY';
+			$ID3v2ShortFrameNameLookup[3]['encoded_by']                        = 'TENC';
+			$ID3v2ShortFrameNameLookup[3]['lyricist']                          = 'TEXT';
+			$ID3v2ShortFrameNameLookup[3]['file_type']                         = 'TFLT';
+			$ID3v2ShortFrameNameLookup[3]['content_group_description']         = 'TIT1';
+			$ID3v2ShortFrameNameLookup[3]['title']                             = 'TIT2';
+			$ID3v2ShortFrameNameLookup[3]['subtitle']                          = 'TIT3';
+			$ID3v2ShortFrameNameLookup[3]['initial_key']                       = 'TKEY';
+			$ID3v2ShortFrameNameLookup[3]['language']                          = 'TLAN';
+			$ID3v2ShortFrameNameLookup[3]['length']                            = 'TLEN';
+			$ID3v2ShortFrameNameLookup[3]['media_type']                        = 'TMED';
+			$ID3v2ShortFrameNameLookup[3]['original_album']                    = 'TOAL';
+			$ID3v2ShortFrameNameLookup[3]['original_filename']                 = 'TOFN';
+			$ID3v2ShortFrameNameLookup[3]['original_lyricist']                 = 'TOLY';
+			$ID3v2ShortFrameNameLookup[3]['original_artist']                   = 'TOPE';
+			$ID3v2ShortFrameNameLookup[3]['file_owner']                        = 'TOWN';
+			$ID3v2ShortFrameNameLookup[3]['artist']                            = 'TPE1';
+			$ID3v2ShortFrameNameLookup[3]['band']                              = 'TPE2';
+			$ID3v2ShortFrameNameLookup[3]['conductor']                         = 'TPE3';
+			$ID3v2ShortFrameNameLookup[3]['remixer']                           = 'TPE4';
+			$ID3v2ShortFrameNameLookup[3]['part_of_a_set']                     = 'TPOS';
+			$ID3v2ShortFrameNameLookup[3]['publisher']                         = 'TPUB';
+			$ID3v2ShortFrameNameLookup[3]['tracknumber']                       = 'TRCK';
+			$ID3v2ShortFrameNameLookup[3]['track_number']                      = 'TRCK';
+			$ID3v2ShortFrameNameLookup[3]['internet_radio_station_name']       = 'TRSN';
+			$ID3v2ShortFrameNameLookup[3]['internet_radio_station_owner']      = 'TRSO';
+			$ID3v2ShortFrameNameLookup[3]['album_artist_sort_order']           = 'TSO2';
+			$ID3v2ShortFrameNameLookup[3]['album_sort_order']                  = 'TSOA';
+			$ID3v2ShortFrameNameLookup[3]['composer_sort_order']               = 'TSOC';
+			$ID3v2ShortFrameNameLookup[3]['performer_sort_order']              = 'TSOP';
+			$ID3v2ShortFrameNameLookup[3]['title_sort_order']                  = 'TSOT';
+			$ID3v2ShortFrameNameLookup[3]['isrc']                              = 'TSRC';
+			$ID3v2ShortFrameNameLookup[3]['encoder_settings']                  = 'TSSE';
+			$ID3v2ShortFrameNameLookup[3]['text']                              = 'TXXX';
+			$ID3v2ShortFrameNameLookup[3]['unique_file_identifier']            = 'UFID';
+			$ID3v2ShortFrameNameLookup[3]['terms_of_use']                      = 'USER';
+			$ID3v2ShortFrameNameLookup[3]['unsychronised_lyric']               = 'USLT';
+			$ID3v2ShortFrameNameLookup[3]['commercial_information']            = 'WCOM';
+			$ID3v2ShortFrameNameLookup[3]['copyright']                         = 'WCOP';
+			$ID3v2ShortFrameNameLookup[3]['url_file']                          = 'WOAF';
+			$ID3v2ShortFrameNameLookup[3]['url_artist']                        = 'WOAR';
+			$ID3v2ShortFrameNameLookup[3]['url_source']                        = 'WOAS';
+			$ID3v2ShortFrameNameLookup[3]['url_station']                       = 'WORS';
+			$ID3v2ShortFrameNameLookup[3]['url_payment']                       = 'WPAY';
+			$ID3v2ShortFrameNameLookup[3]['url_publisher']                     = 'WPUB';
+			$ID3v2ShortFrameNameLookup[3]['url_user']                          = 'WXXX';
 
 			// The above are common to ID3v2.3 and ID3v2.4
 			// so copy them to ID3v2.4 before adding specifics for 2.3 and 2.4
 			$ID3v2ShortFrameNameLookup[4] = $ID3v2ShortFrameNameLookup[3];
 
 			// The following are unique to ID3v2.3
-			$ID3v2ShortFrameNameLookup[3]['equalisation']                                     = 'EQUA';
-			$ID3v2ShortFrameNameLookup[3]['involved_people_list']                             = 'IPLS';
-			$ID3v2ShortFrameNameLookup[3]['relative_volume_adjustment']                       = 'RVAD';
-			$ID3v2ShortFrameNameLookup[3]['date']                                             = 'TDAT';
-			$ID3v2ShortFrameNameLookup[3]['time']                                             = 'TIME';
-			$ID3v2ShortFrameNameLookup[3]['original_release_year']                            = 'TORY';
-			$ID3v2ShortFrameNameLookup[3]['recording_dates']                                  = 'TRDA';
-			$ID3v2ShortFrameNameLookup[3]['size']                                             = 'TSIZ';
-			$ID3v2ShortFrameNameLookup[3]['year']                                             = 'TYER';
+			$ID3v2ShortFrameNameLookup[3]['equalisation']                      = 'EQUA';
+			$ID3v2ShortFrameNameLookup[3]['involved_people_list']              = 'IPLS';
+			$ID3v2ShortFrameNameLookup[3]['relative_volume_adjustment']        = 'RVAD';
+			$ID3v2ShortFrameNameLookup[3]['date']                              = 'TDAT';
+			$ID3v2ShortFrameNameLookup[3]['time']                              = 'TIME';
+			$ID3v2ShortFrameNameLookup[3]['original_year']                     = 'TORY';
+			$ID3v2ShortFrameNameLookup[3]['recording_dates']                   = 'TRDA';
+			$ID3v2ShortFrameNameLookup[3]['size']                              = 'TSIZ';
+			$ID3v2ShortFrameNameLookup[3]['year']                              = 'TYER';
 
 
 			// The following are unique to ID3v2.4
-			$ID3v2ShortFrameNameLookup[4]['audio_seek_point_index']                           = 'ASPI';
-			$ID3v2ShortFrameNameLookup[4]['equalisation']                                     = 'EQU2';
-			$ID3v2ShortFrameNameLookup[4]['relative_volume_adjustment']                       = 'RVA2';
-			$ID3v2ShortFrameNameLookup[4]['seek']                                             = 'SEEK';
-			$ID3v2ShortFrameNameLookup[4]['signature']                                        = 'SIGN';
-			$ID3v2ShortFrameNameLookup[4]['encoding_time']                                    = 'TDEN';
-			$ID3v2ShortFrameNameLookup[4]['original_release_time']                            = 'TDOR';
-			$ID3v2ShortFrameNameLookup[4]['recording_time']                                   = 'TDRC';
-			$ID3v2ShortFrameNameLookup[4]['release_time']                                     = 'TDRL';
-			$ID3v2ShortFrameNameLookup[4]['tagging_time']                                     = 'TDTG';
-			$ID3v2ShortFrameNameLookup[4]['involved_people_list']                             = 'TIPL';
-			$ID3v2ShortFrameNameLookup[4]['musician_credits_list']                            = 'TMCL';
-			$ID3v2ShortFrameNameLookup[4]['mood']                                             = 'TMOO';
-			$ID3v2ShortFrameNameLookup[4]['produced_notice']                                  = 'TPRO';
-			$ID3v2ShortFrameNameLookup[4]['album_sort_order']                                 = 'TSOA';
-			$ID3v2ShortFrameNameLookup[4]['performer_sort_order']                             = 'TSOP';
-			$ID3v2ShortFrameNameLookup[4]['title_sort_order']                                 = 'TSOT';
-			$ID3v2ShortFrameNameLookup[4]['set_subtitle']                                     = 'TSST';
+			$ID3v2ShortFrameNameLookup[4]['audio_seek_point_index']            = 'ASPI';
+			$ID3v2ShortFrameNameLookup[4]['equalisation']                      = 'EQU2';
+			$ID3v2ShortFrameNameLookup[4]['relative_volume_adjustment']        = 'RVA2';
+			$ID3v2ShortFrameNameLookup[4]['seek_frame']                        = 'SEEK';
+			$ID3v2ShortFrameNameLookup[4]['signature_frame']                   = 'SIGN';
+			$ID3v2ShortFrameNameLookup[4]['encoding_time']                     = 'TDEN';
+			$ID3v2ShortFrameNameLookup[4]['original_release_time']             = 'TDOR';
+			$ID3v2ShortFrameNameLookup[4]['recording_time']                    = 'TDRC';
+			$ID3v2ShortFrameNameLookup[4]['release_time']                      = 'TDRL';
+			$ID3v2ShortFrameNameLookup[4]['tagging_time']                      = 'TDTG';
+			$ID3v2ShortFrameNameLookup[4]['involved_people_list']              = 'TIPL';
+			$ID3v2ShortFrameNameLookup[4]['musician_credits_list']             = 'TMCL';
+			$ID3v2ShortFrameNameLookup[4]['mood']                              = 'TMOO';
+			$ID3v2ShortFrameNameLookup[4]['produced_notice']                   = 'TPRO';
+			$ID3v2ShortFrameNameLookup[4]['album_sort_order']                  = 'TSOA';
+			$ID3v2ShortFrameNameLookup[4]['performer_sort_order']              = 'TSOP';
+			$ID3v2ShortFrameNameLookup[4]['title_sort_order']                  = 'TSOT';
+			$ID3v2ShortFrameNameLookup[4]['set_subtitle']                      = 'TSST';
 		}
 		return (isset($ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)]) ? $ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)] : '');
 
diff --git a/lib/getid3/write.php b/lib/getid3/write.php
index ea136934..8b2b67df 100644
--- a/lib/getid3/write.php
+++ b/lib/getid3/write.php
@@ -497,6 +497,40 @@ throw new Exception('$this->overwrite_tags=false is known to be buggy in this ve
 					}
 					break;
 
+				case 'POPM':
+					if (isset($valuearray['email']) &&
+						isset($valuearray['rating']) &&
+						isset($valuearray['data'])) {
+							$tag_data_id3v2['POPM'][] = $valuearray;
+					} else {
+						$this->errors[] = 'ID3v2 POPM data is not properly structured';
+						return false;
+					}
+					break;
+
+				case 'GRID':
+					if (
+						isset($valuearray['groupsymbol']) &&
+						isset($valuearray['ownerid']) &&
+						isset($valuearray['data'])
+					) {
+							$tag_data_id3v2['GRID'][] = $valuearray;
+					} else {
+						$this->errors[] = 'ID3v2 GRID data is not properly structured';
+						return false;
+					}
+					break;
+
+				case 'UFID':
+					if (isset($valuearray['ownerid']) &&
+						isset($valuearray['data'])) {
+							$tag_data_id3v2['UFID'][] = $valuearray;
+					} else {
+						$this->errors[] = 'ID3v2 UFID data is not properly structured';
+						return false;
+					}
+					break;
+
 				case '':
 					$this->errors[] = 'ID3v2: Skipping "'.$tag_key.'" because cannot match it to a known ID3v2 frame type';
 					// some other data type, don't know how to handle it, ignore it
-- 
GitLab