WordPress как на ладони
Недорогой хостинг для сайтов на WordPress: wordpress.jino.ru
функция не описана

getid3_id3v2::ParseID3v2Frame() public WP 1.0

Это метод класса: getid3_id3v2{}

Хуков нет.

Возвращает

true/false.

Использование

$getid3_id3v2 = new getid3_id3v2();
$getid3_id3v2->ParseID3v2Frame( $parsedFrame );
$parsedFrame(массив) (обязательный) (передается по ссылке — &)

Код getid3_id3v2::ParseID3v2Frame() WP 5.5.1

wp-includes/ID3/module.tag.id3v2.php
<?php
public function ParseID3v2Frame(&$parsedFrame) {

	// shortcuts
	$info = &$this->getid3->info;
	$id3v2_majorversion = $info['id3v2']['majorversion'];

	$parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
	if (empty($parsedFrame['framenamelong'])) {
		unset($parsedFrame['framenamelong']);
	}
	$parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
	if (empty($parsedFrame['framenameshort'])) {
		unset($parsedFrame['framenameshort']);
	}

	if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
		if ($id3v2_majorversion == 3) {
			//    Frame Header Flags
			//    %abc00000 %ijk00000
			$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
			$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
			$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
			$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
			$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
			$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity

		} elseif ($id3v2_majorversion == 4) {
			//    Frame Header Flags
			//    %0abc0000 %0h00kmnp
			$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
			$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
			$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
			$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
			$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
			$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
			$parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
			$parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator

			// Frame-level de-unsynchronisation - ID3v2.4
			if ($parsedFrame['flags']['Unsynchronisation']) {
				$parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
			}

			if ($parsedFrame['flags']['DataLengthIndicator']) {
				$parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
				$parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
			}
		}

		//    Frame-level de-compression
		if ($parsedFrame['flags']['compression']) {
			$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
			if (!function_exists('gzuncompress')) {
				$this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
			} else {
				if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
				//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
					$parsedFrame['data'] = $decompresseddata;
					unset($decompresseddata);
				} else {
					$this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
				}
			}
		}
	}

	if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
		if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
			$this->warning('ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data');
		}
	}

	if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {

		$warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
		switch ($parsedFrame['frame_name']) {
			case 'WCOM':
				$warning .= ' (this is known to happen with files tagged by RioPort)';
				break;

			default:
				break;
		}
		$this->warning($warning);

	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
		(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
		//   There may be more than one 'UFID' frame in a tag,
		//   but only one with the same 'Owner identifier'.
		// <Header for 'Unique file identifier', ID: 'UFID'>
		// Owner identifier        <text string> $00
		// Identifier              <up to 64 bytes binary data>
		$exploded = explode("\x00", $parsedFrame['data'], 2);
		$parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
		$parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : '');

	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
		//   There may be more than one 'TXXX' frame in each tag,
		//   but only one with the same description.
		// <Header for 'User defined text information frame', ID: 'TXXX'>
		// Text encoding     $xx
		// Description       <text string according to encoding> $00 (00)
		// Value             <text string according to encoding>

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			$frame_textencoding_terminator = "\x00";
		}
		$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
		if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
			$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
		}
		$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
		$parsedFrame['encodingid']  = $frame_textencoding;
		$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);

		$parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description']));
		$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
		$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
		if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
			$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
			if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
				$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
			} else {
				$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
			}
		}
		//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain


	} elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame
		//   There may only be one text information frame of its kind in an tag.
		// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
		// excluding 'TXXX' described in 4.2.6.>
		// Text encoding                $xx
		// Information                  <text string(s) according to encoding>

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
		}

		$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
		$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));

		$parsedFrame['encodingid'] = $frame_textencoding;
		$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
		if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
			// ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
			// This of course breaks when an artist name contains slash character, e.g. "AC/DC"
			// MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
			// getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
			switch ($parsedFrame['encoding']) {
				case 'UTF-16':
				case 'UTF-16BE':
				case 'UTF-16LE':
					$wordsize = 2;
					break;
				case 'ISO-8859-1':
				case 'UTF-8':
				default:
					$wordsize = 1;
					break;
			}
			$Txxx_elements = array();
			$Txxx_elements_start_offset = 0;
			for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
				if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
					$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
					$Txxx_elements_start_offset = $i + $wordsize;
				}
			}
			$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
			foreach ($Txxx_elements as $Txxx_element) {
				$string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
				if (!empty($string)) {
					$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
				}
			}
			unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
		}

	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
		//   There may be more than one 'WXXX' frame in each tag,
		//   but only one with the same description
		// <Header for 'User defined URL link frame', ID: 'WXXX'>
		// Text encoding     $xx
		// Description       <text string according to encoding> $00 (00)
		// URL               <text string>

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			$frame_textencoding_terminator = "\x00";
		}
		$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
		if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
			$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
		}
		$parsedFrame['encodingid']  = $frame_textencoding;
		$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
		$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);           // according to the frame text encoding
		$parsedFrame['url']         = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1
		$parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator);
		$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);

		if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
			$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
		}
		unset($parsedFrame['data']);


	} elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames
		//   There may only be one URL link frame of its kind in a tag,
		//   except when stated otherwise in the frame description
		// <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
		// described in 4.3.2.>
		// URL              <text string>

		$parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1
		if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
			$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
		}
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
		// http://id3.org/id3v2.3.0#sec4.4
		//   There may only be one 'IPL' frame in each tag
		// <Header for 'User defined URL link frame', ID: 'IPL'>
		// Text encoding     $xx
		// People list strings    <textstrings>

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
		}
		$parsedFrame['encodingid'] = $frame_textencoding;
		$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
		$parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);

		// https://www.getid3.org/phpBB3/viewtopic.php?t=1369
		// "this tag typically contains null terminated strings, which are associated in pairs"
		// "there are users that use the tag incorrectly"
		$IPLS_parts = array();
		if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
			$IPLS_parts_unsorted = array();
			if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
				// UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
				$thisILPS  = '';
				for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
					$twobytes = substr($parsedFrame['data_raw'], $i, 2);
					if ($twobytes === "\x00\x00") {
						$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
						$thisILPS  = '';
					} else {
						$thisILPS .= $twobytes;
					}
				}
				if (strlen($thisILPS) > 2) { // 2-byte BOM
					$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
				}
			} else {
				// ISO-8859-1 or UTF-8 or other single-byte-null character set
				$IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
			}
			if (count($IPLS_parts_unsorted) == 1) {
				// just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
				foreach ($IPLS_parts_unsorted as $key => $value) {
					$IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
					$position = '';
					foreach ($IPLS_parts_sorted as $person) {
						$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
					}
				}
			} elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
				$position = '';
				$person   = '';
				foreach ($IPLS_parts_unsorted as $key => $value) {
					if (($key % 2) == 0) {
						$position = $value;
					} else {
						$person   = $value;
						$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
						$position = '';
						$person   = '';
					}
				}
			} else {
				foreach ($IPLS_parts_unsorted as $key => $value) {
					$IPLS_parts[] = array($value);
				}
			}

		} else {
			$IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
		}
		$parsedFrame['data'] = $IPLS_parts;

		if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
			$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
		}


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
		//   There may only be one 'MCDI' frame in each tag
		// <Header for 'Music CD identifier', ID: 'MCDI'>
		// CD TOC                <binary data>

		if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
			$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
		}


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
		//   There may only be one 'ETCO' frame in each tag
		// <Header for 'Event timing codes', ID: 'ETCO'>
		// Time stamp format    $xx
		//   Where time stamp format is:
		// $01  (32-bit value) MPEG frames from beginning of file
		// $02  (32-bit value) milliseconds from beginning of file
		//   Followed by a list of key events in the following format:
		// Type of event   $xx
		// Time stamp      $xx (xx ...)
		//   The 'Time stamp' is set to zero if directly at the beginning of the sound
		//   or after the previous event. All events MUST be sorted in chronological order.

		$frame_offset = 0;
		$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));

		while ($frame_offset < strlen($parsedFrame['data'])) {
			$parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1);
			$parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']);
			$parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
			$frame_offset += 4;
		}
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
		//   There may only be one 'MLLT' frame in each tag
		// <Header for 'Location lookup table', ID: 'MLLT'>
		// MPEG frames between reference  $xx xx
		// Bytes between reference        $xx xx xx
		// Milliseconds between reference $xx xx xx
		// Bits for bytes deviation       $xx
		// Bits for milliseconds dev.     $xx
		//   Then for every reference the following data is included;
		// Deviation in bytes         %xxx....
		// Deviation in milliseconds  %xxx....

		$frame_offset = 0;
		$parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
		$parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
		$parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
		$parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
		$parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
		$parsedFrame['data'] = substr($parsedFrame['data'], 10);
		$deviationbitstream = '';
		while ($frame_offset < strlen($parsedFrame['data'])) {
			$deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
		}
		$reference_counter = 0;
		while (strlen($deviationbitstream) > 0) {
			$parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
			$parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
			$deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
			$reference_counter++;
		}
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
			  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
		//   There may only be one 'SYTC' frame in each tag
		// <Header for 'Synchronised tempo codes', ID: 'SYTC'>
		// Time stamp format   $xx
		// Tempo data          <binary data>
		//   Where time stamp format is:
		// $01  (32-bit value) MPEG frames from beginning of file
		// $02  (32-bit value) milliseconds from beginning of file

		$frame_offset = 0;
		$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$timestamp_counter = 0;
		while ($frame_offset < strlen($parsedFrame['data'])) {
			$parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
				$parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
			}
			$parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
			$frame_offset += 4;
			$timestamp_counter++;
		}
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
		//   There may be more than one 'Unsynchronised lyrics/text transcription' frame
		//   in each tag, but only one with the same language and content descriptor.
		// <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
		// Text encoding        $xx
		// Language             $xx xx xx
		// Content descriptor   <text string according to encoding> $00 (00)
		// Lyrics/text          <full text string according to encoding>

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			$frame_textencoding_terminator = "\x00";
		}
		$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
		$frame_offset += 3;
		$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
		if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
			$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
		}
		$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
		$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
		$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);

		$parsedFrame['encodingid']   = $frame_textencoding;
		$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);

		$parsedFrame['language']     = $frame_language;
		$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
		if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
			$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
		}
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
		//   There may be more than one 'SYLT' frame in each tag,
		//   but only one with the same language and content descriptor.
		// <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
		// Text encoding        $xx
		// Language             $xx xx xx
		// Time stamp format    $xx
		//   $01  (32-bit value) MPEG frames from beginning of file
		//   $02  (32-bit value) milliseconds from beginning of file
		// Content type         $xx
		// Content descriptor   <text string according to encoding> $00 (00)
		//   Terminated text to be synced (typically a syllable)
		//   Sync identifier (terminator to above string)   $00 (00)
		//   Time stamp                                     $xx (xx ...)

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			$frame_textencoding_terminator = "\x00";
		}
		$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
		$frame_offset += 3;
		$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
		$parsedFrame['encodingid']      = $frame_textencoding;
		$parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding);

		$parsedFrame['language']        = $frame_language;
		$parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);

		$timestampindex = 0;
		$frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
		while (strlen($frame_remainingdata)) {
			$frame_offset = 0;
			$frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
			if ($frame_terminatorpos === false) {
				$frame_remainingdata = '';
			} else {
				if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
				}
				$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);

				$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
				if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) {
					// timestamp probably omitted for first data item
				} else {
					$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
					$frame_remainingdata = substr($frame_remainingdata, 4);
				}
				$timestampindex++;
			}
		}
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
		//   There may be more than one comment frame in each tag,
		//   but only one with the same language and content descriptor.
		// <Header for 'Comment', ID: 'COMM'>
		// Text encoding          $xx
		// Language               $xx xx xx
		// Short content descrip. <text string according to encoding> $00 (00)
		// The actual text        <full text string according to encoding>

		if (strlen($parsedFrame['data']) < 5) {

			$this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);

		} else {

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
				$frame_textencoding_terminator = "\x00";
			}
			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
			$frame_offset += 3;
			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
			}
			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
			$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
			$frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator);

			$parsedFrame['encodingid']   = $frame_textencoding;
			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);

			$parsedFrame['language']     = $frame_language;
			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
			$parsedFrame['data']         = $frame_text;
			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
				$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
				if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
					$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
				} else {
					$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
				}
			}

		}

	} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
		//   There may be more than one 'RVA2' frame in each tag,
		//   but only one with the same identification string
		// <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
		// Identification          <text string> $00
		//   The 'identification' string is used to identify the situation and/or
		//   device where this adjustment should apply. The following is then
		//   repeated for every channel:
		// Type of channel         $xx
		// Volume adjustment       $xx xx
		// Bits representing peak  $xx
		// Peak volume             $xx (xx ...)

		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
		$frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
		if (ord($frame_idstring) === 0) {
			$frame_idstring = '';
		}
		$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
		$parsedFrame['description'] = $frame_idstring;
		$RVA2channelcounter = 0;
		while (strlen($frame_remainingdata) >= 5) {
			$frame_offset = 0;
			$frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
			$parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid;
			$parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
			$parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
			$frame_offset += 2;
			$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
			if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
				$this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
				break;
			}
			$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
			$parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
			$frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
			$RVA2channelcounter++;
		}
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
			  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
		//   There may only be one 'RVA' frame in each tag
		// <Header for 'Relative volume adjustment', ID: 'RVA'>
		// ID3v2.2 => Increment/decrement     %000000ba
		// ID3v2.3 => Increment/decrement     %00fedcba
		// Bits used for volume descr.        $xx
		// Relative volume change, right      $xx xx (xx ...) // a
		// Relative volume change, left       $xx xx (xx ...) // b
		// Peak volume right                  $xx xx (xx ...)
		// Peak volume left                   $xx xx (xx ...)
		//   ID3v2.3 only, optional (not present in ID3v2.2):
		// Relative volume change, right back $xx xx (xx ...) // c
		// Relative volume change, left back  $xx xx (xx ...) // d
		// Peak volume right back             $xx xx (xx ...)
		// Peak volume left back              $xx xx (xx ...)
		//   ID3v2.3 only, optional (not present in ID3v2.2):
		// Relative volume change, center     $xx xx (xx ...) // e
		// Peak volume center                 $xx xx (xx ...)
		//   ID3v2.3 only, optional (not present in ID3v2.2):
		// Relative volume change, bass       $xx xx (xx ...) // f
		// Peak volume bass                   $xx xx (xx ...)

		$frame_offset = 0;
		$frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
		$parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1);
		$parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
		$parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
		if ($parsedFrame['incdec']['right'] === false) {
			$parsedFrame['volumechange']['right'] *= -1;
		}
		$frame_offset += $frame_bytesvolume;
		$parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
		if ($parsedFrame['incdec']['left'] === false) {
			$parsedFrame['volumechange']['left'] *= -1;
		}
		$frame_offset += $frame_bytesvolume;
		$parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
		$frame_offset += $frame_bytesvolume;
		$parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
		$frame_offset += $frame_bytesvolume;
		if ($id3v2_majorversion == 3) {
			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
			if (strlen($parsedFrame['data']) > 0) {
				$parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
				$parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1);
				$parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
				if ($parsedFrame['incdec']['rightrear'] === false) {
					$parsedFrame['volumechange']['rightrear'] *= -1;
				}
				$frame_offset += $frame_bytesvolume;
				$parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
				if ($parsedFrame['incdec']['leftrear'] === false) {
					$parsedFrame['volumechange']['leftrear'] *= -1;
				}
				$frame_offset += $frame_bytesvolume;
				$parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
				$frame_offset += $frame_bytesvolume;
				$parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
				$frame_offset += $frame_bytesvolume;
			}
			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
			if (strlen($parsedFrame['data']) > 0) {
				$parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
				$parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
				if ($parsedFrame['incdec']['center'] === false) {
					$parsedFrame['volumechange']['center'] *= -1;
				}
				$frame_offset += $frame_bytesvolume;
				$parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
				$frame_offset += $frame_bytesvolume;
			}
			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
			if (strlen($parsedFrame['data']) > 0) {
				$parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
				$parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
				if ($parsedFrame['incdec']['bass'] === false) {
					$parsedFrame['volumechange']['bass'] *= -1;
				}
				$frame_offset += $frame_bytesvolume;
				$parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
				$frame_offset += $frame_bytesvolume;
			}
		}
		unset($parsedFrame['data']);


	} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
		//   There may be more than one 'EQU2' frame in each tag,
		//   but only one with the same identification string
		// <Header of 'Equalisation (2)', ID: 'EQU2'>
		// Interpolation method  $xx
		//   $00  Band
		//   $01  Linear
		// Identification        <text string> $00
		//   The following is then repeated for every adjustment point
		// Frequency          $xx xx
		// Volume adjustment  $xx xx

		$frame_offset = 0;
		$frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		if (ord($frame_idstring) === 0) {
			$frame_idstring = '';
		}
		$parsedFrame['description'] = $frame_idstring;
		$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
		while (strlen($frame_remainingdata)) {
			$frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
			$parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
			$frame_remainingdata = substr($frame_remainingdata, 4);
		}
		$parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
		//   There may only be one 'EQUA' frame in each tag
		// <Header for 'Relative volume adjustment', ID: 'EQU'>
		// Adjustment bits    $xx
		//   This is followed by 2 bytes + ('adjustment bits' rounded up to the
		//   nearest byte) for every equalisation band in the following format,
		//   giving a frequency range of 0 - 32767Hz:
		// Increment/decrement   %x (MSB of the Frequency)
		// Frequency             (lower 15 bits)
		// Adjustment            $xx (xx ...)

		$frame_offset = 0;
		$parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
		$frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);

		$frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
		while (strlen($frame_remainingdata) > 0) {
			$frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
			$frame_incdec    = (bool) substr($frame_frequencystr, 0, 1);
			$frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
			$parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
			$parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
			if ($parsedFrame[$frame_frequency]['incdec'] === false) {
				$parsedFrame[$frame_frequency]['adjustment'] *= -1;
			}
			$frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
		}
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
		//   There may only be one 'RVRB' frame in each tag.
		// <Header for 'Reverb', ID: 'RVRB'>
		// Reverb left (ms)                 $xx xx
		// Reverb right (ms)                $xx xx
		// Reverb bounces, left             $xx
		// Reverb bounces, right            $xx
		// Reverb feedback, left to left    $xx
		// Reverb feedback, left to right   $xx
		// Reverb feedback, right to right  $xx
		// Reverb feedback, right to left   $xx
		// Premix left to right             $xx
		// Premix right to left             $xx

		$frame_offset = 0;
		$parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
		$frame_offset += 2;
		$parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
		$frame_offset += 2;
		$parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
		//   There may be several pictures attached to one file,
		//   each in their individual 'APIC' frame, but only one
		//   with the same content descriptor
		// <Header for 'Attached picture', ID: 'APIC'>
		// Text encoding      $xx
		// ID3v2.3+ => MIME type          <text string> $00
		// ID3v2.2  => Image format       $xx xx xx
		// Picture type       $xx
		// Description        <text string according to encoding> $00 (00)
		// Picture data       <binary data>

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			$frame_textencoding_terminator = "\x00";
		}

		if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
			$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
			if (strtolower($frame_imagetype) == 'ima') {
				// complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
				// MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
				$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
				$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
				if (ord($frame_mimetype) === 0) {
					$frame_mimetype = '';
				}
				$frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
				if ($frame_imagetype == 'JPEG') {
					$frame_imagetype = 'JPG';
				}
				$frame_offset = $frame_terminatorpos + strlen("\x00");
			} else {
				$frame_offset += 3;
			}
		}
		if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_mimetype) === 0) {
				$frame_mimetype = '';
			}
			$frame_offset = $frame_terminatorpos + strlen("\x00");
		}

		$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));

		if ($frame_offset >= $parsedFrame['datalength']) {
			$this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
		} else {
			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
			}
			$parsedFrame['description']   = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$parsedFrame['description']   = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
			$parsedFrame['encodingid']    = $frame_textencoding;
			$parsedFrame['encoding']      = $this->TextEncodingNameLookup($frame_textencoding);

			if ($id3v2_majorversion == 2) {
				$parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null;
			} else {
				$parsedFrame['mime']      = isset($frame_mimetype) ? $frame_mimetype : null;
			}
			$parsedFrame['picturetypeid'] = $frame_picturetype;
			$parsedFrame['picturetype']   = $this->APICPictureTypeLookup($frame_picturetype);
			$parsedFrame['data']          = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
			$parsedFrame['datalength']    = strlen($parsedFrame['data']);

			$parsedFrame['image_mime']    = '';
			$imageinfo = array();
			if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
				if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
					$parsedFrame['image_mime']       = image_type_to_mime_type($imagechunkcheck[2]);
					if ($imagechunkcheck[0]) {
						$parsedFrame['image_width']  = $imagechunkcheck[0];
					}
					if ($imagechunkcheck[1]) {
						$parsedFrame['image_height'] = $imagechunkcheck[1];
					}
				}
			}

			do {
				if ($this->getid3->option_save_attachments === false) {
					// skip entirely
					unset($parsedFrame['data']);
					break;
				}
				$dir = '';
				if ($this->getid3->option_save_attachments === true) {
					// great
/*
				} elseif (is_int($this->getid3->option_save_attachments)) {
					if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
						// too big, skip
						$this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
						unset($parsedFrame['data']);
						break;
					}
*/
				} elseif (is_string($this->getid3->option_save_attachments)) {
					$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
					if (!is_dir($dir) || !getID3::is_writable($dir)) {
						// cannot write, skip
						$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
						unset($parsedFrame['data']);
						break;
					}
				}
				// if we get this far, must be OK
				if (is_string($this->getid3->option_save_attachments)) {
					$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
					if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
						file_put_contents($destination_filename, $parsedFrame['data']);
					} else {
						$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
					}
					$parsedFrame['data_filename'] = $destination_filename;
					unset($parsedFrame['data']);
				} else {
					if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
						if (!isset($info['id3v2']['comments']['picture'])) {
							$info['id3v2']['comments']['picture'] = array();
						}
						$comments_picture_data = array();
						foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
							if (isset($parsedFrame[$picture_key])) {
								$comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
							}
						}
						$info['id3v2']['comments']['picture'][] = $comments_picture_data;
						unset($comments_picture_data);
					}
				}
			} while (false);
		}

	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
		//   There may be more than one 'GEOB' frame in each tag,
		//   but only one with the same content descriptor
		// <Header for 'General encapsulated object', ID: 'GEOB'>
		// Text encoding          $xx
		// MIME type              <text string> $00
		// Filename               <text string according to encoding> $00 (00)
		// Content description    <text string according to encoding> $00 (00)
		// Encapsulated object    <binary data>

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			$frame_textencoding_terminator = "\x00";
		}
		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		if (ord($frame_mimetype) === 0) {
			$frame_mimetype = '';
		}
		$frame_offset = $frame_terminatorpos + strlen("\x00");

		$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
		if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
			$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
		}
		$frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		if (ord($frame_filename) === 0) {
			$frame_filename = '';
		}
		$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

		$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
		if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
			$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
		}
		$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
		$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

		$parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
		$parsedFrame['encodingid']  = $frame_textencoding;
		$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);

		$parsedFrame['mime']        = $frame_mimetype;
		$parsedFrame['filename']    = $frame_filename;
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
		//   There may only be one 'PCNT' frame in each tag.
		//   When the counter reaches all one's, one byte is inserted in
		//   front of the counter thus making the counter eight bits bigger
		// <Header for 'Play counter', ID: 'PCNT'>
		// Counter        $xx xx xx xx (xx ...)

		$parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
		//   There may be more than one 'POPM' frame in each tag,
		//   but only one with the same email address
		// <Header for 'Popularimeter', ID: 'POPM'>
		// Email to user   <text string> $00
		// Rating          $xx
		// Counter         $xx xx xx xx (xx ...)

		$frame_offset = 0;
		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		if (ord($frame_emailaddress) === 0) {
			$frame_emailaddress = '';
		}
		$frame_offset = $frame_terminatorpos + strlen("\x00");
		$frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
		$parsedFrame['email']   = $frame_emailaddress;
		$parsedFrame['rating']  = $frame_rating;
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
		//   There may only be one 'RBUF' frame in each tag
		// <Header for 'Recommended buffer size', ID: 'RBUF'>
		// Buffer size               $xx xx xx
		// Embedded info flag        %0000000x
		// Offset to next tag        $xx xx xx xx

		$frame_offset = 0;
		$parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
		$frame_offset += 3;

		$frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
		$parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
		unset($parsedFrame['data']);


	} elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
		//   There may be more than one 'CRM' frame in a tag,
		//   but only one with the same 'owner identifier'
		// <Header for 'Encrypted meta frame', ID: 'CRM'>
		// Owner identifier      <textstring> $00 (00)
		// Content/explanation   <textstring> $00 (00)
		// Encrypted datablock   <binary data>

		$frame_offset = 0;
		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		$frame_offset = $frame_terminatorpos + strlen("\x00");

		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
		$frame_offset = $frame_terminatorpos + strlen("\x00");

		$parsedFrame['ownerid']     = $frame_ownerid;
		$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
		//   There may be more than one 'AENC' frames in a tag,
		//   but only one with the same 'Owner identifier'
		// <Header for 'Audio encryption', ID: 'AENC'>
		// Owner identifier   <text string> $00
		// Preview start      $xx xx
		// Preview length     $xx xx
		// Encryption info    <binary data>

		$frame_offset = 0;
		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		if (ord($frame_ownerid) === 0) {
			$frame_ownerid = '';
		}
		$frame_offset = $frame_terminatorpos + strlen("\x00");
		$parsedFrame['ownerid'] = $frame_ownerid;
		$parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
		$frame_offset += 2;
		$parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
		$frame_offset += 2;
		$parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
		unset($parsedFrame['data']);


	} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {    // 4.22  LNK  Linked information
		//   There may be more than one 'LINK' frame in a tag,
		//   but only one with the same contents
		// <Header for 'Linked information', ID: 'LINK'>
		// ID3v2.3+ => Frame identifier   $xx xx xx xx
		// ID3v2.2  => Frame identifier   $xx xx xx
		// URL                            <text string> $00
		// ID and additional data         <text string(s)>

		$frame_offset = 0;
		if ($id3v2_majorversion == 2) {
			$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
			$frame_offset += 3;
		} else {
			$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
			$frame_offset += 4;
		}

		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		if (ord($frame_url) === 0) {
			$frame_url = '';
		}
		$frame_offset = $frame_terminatorpos + strlen("\x00");
		$parsedFrame['url'] = $frame_url;

		$parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
		if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
			$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
		}
		unset($parsedFrame['data']);


	} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
		//   There may only be one 'POSS' frame in each tag
		// <Head for 'Position synchronisation', ID: 'POSS'>
		// Time stamp format         $xx
		// Position                  $xx (xx ...)

		$frame_offset = 0;
		$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
		unset($parsedFrame['data']);


	} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
		//   There may be more than one 'Terms of use' frame in a tag,
		//   but only one with the same 'Language'
		// <Header for 'Terms of use frame', ID: 'USER'>
		// Text encoding        $xx
		// Language             $xx xx xx
		// The actual text      <text string according to encoding>

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
		}
		$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
		$frame_offset += 3;
		$parsedFrame['language']     = $frame_language;
		$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
		$parsedFrame['encodingid']   = $frame_textencoding;
		$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);

		$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
		$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
		if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
			$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
		}
		unset($parsedFrame['data']);


	} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
		//   There may only be one 'OWNE' frame in a tag
		// <Header for 'Ownership frame', ID: 'OWNE'>
		// Text encoding     $xx
		// Price paid        <text string> $00
		// Date of purch.    <text string>
		// Seller            <text string according to encoding>

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
		}
		$parsedFrame['encodingid'] = $frame_textencoding;
		$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);

		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		$frame_offset = $frame_terminatorpos + strlen("\x00");

		$parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
		$parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
		$parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);

		$parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
		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;

		$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
		$parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding));
		unset($parsedFrame['data']);


	} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
		//   There may be more than one 'commercial frame' in a tag,
		//   but no two may be identical
		// <Header for 'Commercial frame', ID: 'COMR'>
		// Text encoding      $xx
		// Price string       <text string> $00
		// Valid until        <text string>
		// Contact URL        <text string> $00
		// Received as        $xx
		// Name of seller     <text string according to encoding> $00 (00)
		// Description        <text string according to encoding> $00 (00)
		// Picture MIME type  <string> $00
		// Seller logo        <binary data>

		$frame_offset = 0;
		$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
		if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
			$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			$frame_textencoding_terminator = "\x00";
		}

		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		$frame_offset = $frame_terminatorpos + strlen("\x00");
		$frame_rawpricearray = explode('/', $frame_pricestring);
		foreach ($frame_rawpricearray as $key => $val) {
			$frame_currencyid = substr($val, 0, 3);
			$parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
			$parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3);
		}

		$frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
		$frame_offset += 8;

		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		$frame_offset = $frame_terminatorpos + strlen("\x00");

		$frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));

		$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
		if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
			$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
		}
		$frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		if (ord($frame_sellername) === 0) {
			$frame_sellername = '';
		}
		$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

		$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
		if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
			$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
		}
		$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
		$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		$frame_offset = $frame_terminatorpos + strlen("\x00");

		$frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);

		$parsedFrame['encodingid']        = $frame_textencoding;
		$parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);

		$parsedFrame['pricevaliduntil']   = $frame_datestring;
		$parsedFrame['contacturl']        = $frame_contacturl;
		$parsedFrame['receivedasid']      = $frame_receivedasid;
		$parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
		$parsedFrame['sellername']        = $frame_sellername;
		$parsedFrame['mime']              = $frame_mimetype;
		$parsedFrame['logo']              = $frame_sellerlogo;
		unset($parsedFrame['data']);


	} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
		//   There may be several 'ENCR' frames in a tag,
		//   but only one containing the same symbol
		//   and only one containing the same owner identifier
		// <Header for 'Encryption method registration', ID: 'ENCR'>
		// Owner identifier    <text string> $00
		// Method symbol       $xx
		// Encryption data     <binary data>

		$frame_offset = 0;
		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		if (ord($frame_ownerid) === 0) {
			$frame_ownerid = '';
		}
		$frame_offset = $frame_terminatorpos + strlen("\x00");

		$parsedFrame['ownerid']      = $frame_ownerid;
		$parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);


	} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)

		//   There may be several 'GRID' frames in a tag,
		//   but only one containing the same symbol
		//   and only one containing the same owner identifier
		// <Header for 'Group ID registration', ID: 'GRID'>
		// Owner identifier      <text string> $00
		// Group symbol          $xx
		// Group dependent data  <binary data>

		$frame_offset = 0;
		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		if (ord($frame_ownerid) === 0) {
			$frame_ownerid = '';
		}
		$frame_offset = $frame_terminatorpos + strlen("\x00");

		$parsedFrame['ownerid']       = $frame_ownerid;
		$parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);


	} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
		//   The tag may contain more than one 'PRIV' frame
		//   but only with different contents
		// <Header for 'Private frame', ID: 'PRIV'>
		// Owner identifier      <text string> $00
		// The private data      <binary data>

		$frame_offset = 0;
		$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
		$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
		if (ord($frame_ownerid) === 0) {
			$frame_ownerid = '';
		}
		$frame_offset = $frame_terminatorpos + strlen("\x00");

		$parsedFrame['ownerid'] = $frame_ownerid;
		$parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);


	} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
		//   There may be more than one 'signature frame' in a tag,
		//   but no two may be identical
		// <Header for 'Signature frame', ID: 'SIGN'>
		// Group symbol      $xx
		// Signature         <binary data>

		$frame_offset = 0;
		$parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);


	} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
		//   There may only be one 'seek frame' in a tag
		// <Header for 'Seek frame', ID: 'SEEK'>
		// Minimum offset to next tag       $xx xx xx xx

		$frame_offset = 0;
		$parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));


	} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
		//   There may only be one 'audio seek point index' frame in a tag
		// <Header for 'Seek Point Index', ID: 'ASPI'>
		// Indexed data start (S)         $xx xx xx xx
		// Indexed data length (L)        $xx xx xx xx
		// Number of index points (N)     $xx xx
		// Bits per index point (b)       $xx
		//   Then for every index point the following data is included:
		// Fraction at index (Fi)          $xx (xx)

		$frame_offset = 0;
		$parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
		$frame_offset += 4;
		$parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
		$frame_offset += 4;
		$parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
		$frame_offset += 2;
		$parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
		$frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
		for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
			$parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
			$frame_offset += $frame_bytesperpoint;
		}
		unset($parsedFrame['data']);

	} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
		// http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
		//   There may only be one 'RGAD' frame in a tag
		// <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
		// Peak Amplitude                      $xx $xx $xx $xx
		// Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
		// Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
		//   a - name code
		//   b - originator code
		//   c - sign bit
		//   d - replay gain adjustment

		$frame_offset = 0;
		$parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
		$frame_offset += 4;
		$rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
		$frame_offset += 2;
		$rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
		$frame_offset += 2;
		$parsedFrame['raw']['track']['name']       = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
		$parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
		$parsedFrame['raw']['track']['signbit']    = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
		$parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
		$parsedFrame['raw']['album']['name']       = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
		$parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
		$parsedFrame['raw']['album']['signbit']    = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
		$parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
		$parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
		$parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
		$parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
		$parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
		$parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
		$parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);

		$info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude'];
		$info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
		$info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
		$info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
		$info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];

		unset($parsedFrame['data']);

	} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
		// http://id3.org/id3v2-chapters-1.0
		// <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
		// Element ID      <text string> $00
		// Start time      $xx xx xx xx
		// End time        $xx xx xx xx
		// Start offset    $xx xx xx xx
		// End offset      $xx xx xx xx
		// <Optional embedded sub-frames>

		$frame_offset = 0;
		@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
		$frame_offset += strlen($parsedFrame['element_id']."\x00");
		$parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
		$frame_offset += 4;
		$parsedFrame['time_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
		$frame_offset += 4;
		if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
			// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
			$parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
		}
		$frame_offset += 4;
		if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
			// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
			$parsedFrame['offset_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
		}
		$frame_offset += 4;

		if ($frame_offset < strlen($parsedFrame['data'])) {
			$parsedFrame['subframes'] = array();
			while ($frame_offset < strlen($parsedFrame['data'])) {
				// <Optional embedded sub-frames>
				$subframe = array();
				$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
				$frame_offset += 4;
				$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
				$frame_offset += 4;
				$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
				$frame_offset += 2;
				if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
					$this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
					break;
				}
				$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
				$frame_offset += $subframe['size'];

				$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
				$subframe['text']       =     substr($subframe_rawdata, 1);
				$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
				$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));
				switch (substr($encoding_converted_text, 0, 2)) {
					case "\xFF\xFE":
					case "\xFE\xFF":
						switch (strtoupper($info['id3v2']['encoding'])) {
							case 'ISO-8859-1':
							case 'UTF-8':
								$encoding_converted_text = substr($encoding_converted_text, 2);
								// remove unwanted byte-order-marks
								break;
							default:
								// ignore
								break;
						}
						break;
					default:
						// do not remove BOM
						break;
				}

				switch ($subframe['name']) {
					case 'TIT2':
						$parsedFrame['chapter_name']        = $encoding_converted_text;
						$parsedFrame['subframes'][] = $subframe;
						break;
					case 'TIT3':
						$parsedFrame['chapter_description'] = $encoding_converted_text;
						$parsedFrame['subframes'][] = $subframe;
						break;
					case 'WXXX':
						list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
						$parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
						$parsedFrame['subframes'][] = $subframe;
						break;
					case 'APIC':
						if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) {
							list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches;
							$subframe['image_mime']   = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime));
							$subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype);
							$subframe['description']  = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description));
							if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) {
								// the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16)
								// the above regex assumes one byte, if it's actually two then strip the second one here
								$subframe_apic_picturedata = substr($subframe_apic_picturedata, 1);
							}
							$subframe['data'] = $subframe_apic_picturedata;
							unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata);
							unset($subframe['text'], $parsedFrame['text']);
							$parsedFrame['subframes'][] = $subframe;
							$parsedFrame['picture_present'] = true;
						} else {
							$this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format');
						}
						break;
					default:
						$this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)');
						break;
				}
			}
			unset($subframe_rawdata, $subframe, $encoding_converted_text);
			unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC
		}

		$id3v2_chapter_entry = array();
		foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) {
			if (isset($parsedFrame[$id3v2_chapter_key])) {
				$id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
			}
		}
		if (!isset($info['id3v2']['chapters'])) {
			$info['id3v2']['chapters'] = array();
		}
		$info['id3v2']['chapters'][] = $id3v2_chapter_entry;
		unset($id3v2_chapter_entry, $id3v2_chapter_key);


	} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
		// http://id3.org/id3v2-chapters-1.0
		// <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC">           (10 bytes)
		// Element ID      <text string> $00
		// CTOC flags        %xx
		// Entry count       $xx
		// Child Element ID  <string>$00   /* zero or more child CHAP or CTOC entries */
		// <Optional embedded sub-frames>

		$frame_offset = 0;
		@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
		$frame_offset += strlen($parsedFrame['element_id']."\x00");
		$ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
		$frame_offset += 1;
		$parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
		$frame_offset += 1;

		$terminator_position = null;
		for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
			$terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
			$frame_offset = $terminator_position + 1;
		}

		$parsedFrame['ctoc_flags']['ordered']   = (bool) ($ctoc_flags_raw & 0x01);
		$parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);

		unset($ctoc_flags_raw, $terminator_position);

		if ($frame_offset < strlen($parsedFrame['data'])) {
			$parsedFrame['subframes'] = array();
			while ($frame_offset < strlen($parsedFrame['data'])) {
				// <Optional embedded sub-frames>
				$subframe = array();
				$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
				$frame_offset += 4;
				$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
				$frame_offset += 4;
				$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
				$frame_offset += 2;
				if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
					$this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
					break;
				}
				$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
				$frame_offset += $subframe['size'];

				$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
				$subframe['text']       =     substr($subframe_rawdata, 1);
				$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
				$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
				switch (substr($encoding_converted_text, 0, 2)) {
					case "\xFF\xFE":
					case "\xFE\xFF":
						switch (strtoupper($info['id3v2']['encoding'])) {
							case 'ISO-8859-1':
							case 'UTF-8':
								$encoding_converted_text = substr($encoding_converted_text, 2);
								// remove unwanted byte-order-marks
								break;
							default:
								// ignore
								break;
						}
						break;
					default:
						// do not remove BOM
						break;
				}

				if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
					if ($subframe['name'] == 'TIT2') {
						$parsedFrame['toc_name']        = $encoding_converted_text;
					} elseif ($subframe['name'] == 'TIT3') {
						$parsedFrame['toc_description'] = $encoding_converted_text;
					}
					$parsedFrame['subframes'][] = $subframe;
				} else {
					$this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
				}
			}
			unset($subframe_rawdata, $subframe, $encoding_converted_text);
		}

	}

	return true;
}