--- a/wp/wp-includes/ID3/module.tag.id3v2.php Tue Jun 09 11:14:17 2015 +0000
+++ b/wp/wp-includes/ID3/module.tag.id3v2.php Mon Oct 14 17:39:30 2019 +0200
@@ -71,7 +71,7 @@
if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
- $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
+ $this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
return false;
}
@@ -241,7 +241,7 @@
}
if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
- $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
+ $this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
}
}
@@ -260,7 +260,7 @@
if ($framedata{$i} != "\x00") {
$thisfile_id3v2['padding']['valid'] = false;
$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
- $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
+ $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
break;
}
}
@@ -300,7 +300,7 @@
} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
// MP3ext known broken frames - "ok" for the purposes of this test
} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
- $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
+ $this->warning('ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3');
$id3v2_majorversion = 3;
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
}
@@ -322,16 +322,16 @@
if ($framedata{$i} != "\x00") {
$thisfile_id3v2['padding']['valid'] = false;
$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
- $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
+ $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
break;
}
}
break; // skip rest of ID3v2 header
}
- if ($frame_name == 'COM ') {
- $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
- $frame_name = 'COMM';
+ if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
+ $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
+ $frame_name = $iTunesBrokenFrameNameFixed;
}
if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
@@ -355,28 +355,28 @@
// next frame is valid, just skip the current frame
$framedata = substr($framedata, $frame_size);
- $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
+ $this->warning('Next ID3v2 frame is valid, skipping current frame.');
} else {
// next frame is invalid too, abort processing
//unset($framedata);
$framedata = null;
- $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
+ $this->error('Next ID3v2 frame is also invalid, aborting processing.');
}
} elseif ($frame_size == strlen($framedata)) {
// this is the last frame, just skip
- $info['warning'][] = 'This was the last ID3v2 frame.';
+ $this->warning('This was the last ID3v2 frame.');
} else {
// next frame is invalid too, abort processing
//unset($framedata);
$framedata = null;
- $info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
+ $this->warning('Invalid ID3v2 frame size, aborting.');
}
if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
@@ -389,21 +389,21 @@
case "\x00".'MP':
case ' MP':
case 'MP3':
- $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
+ $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]');
break;
default:
- $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
+ $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
break;
}
} elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
- $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).';
+ $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).');
} else {
- $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
+ $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');
}
@@ -442,10 +442,14 @@
} // end footer
if (isset($thisfile_id3v2['comments']['genre'])) {
+ $genres = array();
foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
- unset($thisfile_id3v2['comments']['genre'][$key]);
- $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
+ foreach ($this->ParseID3v2GenreString($value) as $genre) {
+ $genres[] = $genre;
+ }
}
+ $thisfile_id3v2['comments']['genre'] = array_unique($genres);
+ unset($key, $value, $genres, $genre);
}
if (isset($thisfile_id3v2['comments']['track'])) {
@@ -500,9 +504,28 @@
// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
// ID3v2.4.x: '21' $00 'Eurodisco' $00
$clean_genres = array();
+
+ // hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
+ if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
+ // note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
+ // replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
+ if (preg_match('#/#', $genrestring)) {
+ $genrestring = str_replace('/', "\x00", $genrestring);
+ $genrestring = str_replace('Pop'."\x00".'Funk', 'Pop/Funk', $genrestring);
+ $genrestring = str_replace('Rock'."\x00".'Rock', 'Folk/Rock', $genrestring);
+ }
+
+ // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
+ if (preg_match('#;#', $genrestring)) {
+ $genrestring = str_replace(';', "\x00", $genrestring);
+ }
+ }
+
+
if (strpos($genrestring, "\x00") === false) {
$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
}
+
$genre_elements = explode("\x00", $genrestring);
foreach ($genre_elements as $element) {
$element = trim($element);
@@ -571,14 +594,14 @@
if ($parsedFrame['flags']['compression']) {
$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
if (!function_exists('gzuncompress')) {
- $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
+ $this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
} else {
if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
$parsedFrame['data'] = $decompresseddata;
unset($decompresseddata);
} else {
- $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
+ $this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
}
}
}
@@ -586,7 +609,7 @@
if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
- $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
+ $this->warning('ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data');
}
}
@@ -601,7 +624,7 @@
default:
break;
}
- $info['warning'][] = $warning;
+ $this->warning($warning);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier
@@ -625,23 +648,25 @@
$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))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $frame_textencoding_terminator = "\x00";
}
- $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
- if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
- if (ord($frame_description) === 0) {
+ if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+ // if description only contains a BOM or terminator then make it blank
$frame_description = '';
}
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
- $parsedFrame['description'] = $frame_description;
- $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
+ $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description));
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($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']])) {
@@ -663,7 +688,7 @@
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
}
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
@@ -717,22 +742,24 @@
$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))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $frame_textencoding_terminator = "\x00";
}
- $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
- if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
-
- if (ord($frame_description) === 0) {
+ if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+ // if description only contains a BOM or terminator then make it blank
$frame_description = '';
}
- $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
-
- $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
- if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
+
+ $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator);
+ 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
}
if ($frame_terminatorpos) {
@@ -780,7 +807,7 @@
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
}
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
@@ -956,25 +983,27 @@
$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))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $frame_textencoding_terminator = "\x00";
}
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3;
- $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
- if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
- if (ord($frame_description) === 0) {
+ if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+ // if description only contains a BOM or terminator then make it blank
$frame_description = '';
}
- $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
- $parsedFrame['data'] = $parsedFrame['data'];
$parsedFrame['language'] = $frame_language;
$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
$parsedFrame['description'] = $frame_description;
@@ -1002,8 +1031,10 @@
$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))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $frame_textencoding_terminator = "\x00";
}
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3;
@@ -1020,16 +1051,16 @@
$frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
while (strlen($frame_remainingdata)) {
$frame_offset = 0;
- $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding));
+ $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
if ($frame_terminatorpos === false) {
$frame_remainingdata = '';
} else {
- if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ 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($this->TextEncodingTerminatorLookup($frame_textencoding)));
+ $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 {
@@ -1054,26 +1085,29 @@
if (strlen($parsedFrame['data']) < 5) {
- $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
+ $this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
} else {
$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))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $frame_textencoding_terminator = "\x00";
}
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3;
- $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
- if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
- if (ord($frame_description) === 0) {
+ if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+ // if description only contains a BOM or terminator then make it blank
$frame_description = '';
}
- $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
+ $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
@@ -1123,7 +1157,7 @@
$frame_offset += 2;
$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
- $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
+ $this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
break;
}
$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
@@ -1330,8 +1364,10 @@
$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))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $frame_textencoding_terminator = "\x00";
}
if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
@@ -1365,14 +1401,15 @@
$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ($frame_offset >= $parsedFrame['datalength']) {
- $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
+ $this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
} else {
- $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
- if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
- if (ord($frame_description) === 0) {
+ if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+ // if description only contains a BOM or terminator then make it blank
$frame_description = '';
}
$parsedFrame['encodingid'] = $frame_textencoding;
@@ -1386,19 +1423,20 @@
$parsedFrame['picturetypeid'] = $frame_picturetype;
$parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype);
$parsedFrame['description'] = $frame_description;
- $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
$parsedFrame['datalength'] = strlen($parsedFrame['data']);
$parsedFrame['image_mime'] = '';
$imageinfo = array();
- $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
- if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
- $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
- if ($imagechunkcheck[0]) {
- $parsedFrame['image_width'] = $imagechunkcheck[0];
- }
- if ($imagechunkcheck[1]) {
- $parsedFrame['image_height'] = $imagechunkcheck[1];
+ if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
+ if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
+ $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
+ if ($imagechunkcheck[0]) {
+ $parsedFrame['image_width'] = $imagechunkcheck[0];
+ }
+ if ($imagechunkcheck[1]) {
+ $parsedFrame['image_height'] = $imagechunkcheck[1];
+ }
}
}
@@ -1414,16 +1452,16 @@
} elseif (is_int($this->getid3->option_save_attachments)) {
if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
// too big, skip
- $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
+ $this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
unset($parsedFrame['data']);
break;
}
*/
} elseif (is_string($this->getid3->option_save_attachments)) {
$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
- if (!is_dir($dir) || !is_writable($dir)) {
+ if (!is_dir($dir) || !getID3::is_writable($dir)) {
// cannot write, skip
- $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)';
+ $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
unset($parsedFrame['data']);
break;
}
@@ -1431,10 +1469,10 @@
// if we get this far, must be OK
if (is_string($this->getid3->option_save_attachments)) {
$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
- if (!file_exists($destination_filename) || is_writable($destination_filename)) {
+ if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
file_put_contents($destination_filename, $parsedFrame['data']);
} else {
- $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
+ $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
}
$parsedFrame['data_filename'] = $destination_filename;
unset($parsedFrame['data']);
@@ -1443,7 +1481,14 @@
if (!isset($info['id3v2']['comments']['picture'])) {
$info['id3v2']['comments']['picture'] = array();
}
- $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']);
+ $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);
@@ -1462,8 +1507,10 @@
$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))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $frame_textencoding_terminator = "\x00";
}
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
@@ -1472,25 +1519,26 @@
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
- $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
- if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ $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($this->TextEncodingTerminatorLookup($frame_textencoding));
-
- $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
- if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ $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
}
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
- if (ord($frame_description) === 0) {
+ if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+ // if description only contains a BOM or terminator then make it blank
$frame_description = '';
}
- $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
+ $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
$parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset);
$parsedFrame['encodingid'] = $frame_textencoding;
@@ -1569,7 +1617,8 @@
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
- if (ord($frame_description) === 0) {
+ if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+ // if description only contains a BOM or terminator then make it blank
$frame_description = '';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
@@ -1594,7 +1643,7 @@
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_ownerid) === 0) {
- $frame_ownerid == '';
+ $frame_ownerid = '';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
$parsedFrame['ownerid'] = $frame_ownerid;
@@ -1607,7 +1656,7 @@
} 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
+ (($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'>
@@ -1635,7 +1684,7 @@
$parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
- $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']);
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
}
unset($parsedFrame['data']);
@@ -1663,7 +1712,7 @@
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
}
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3;
@@ -1690,7 +1739,7 @@
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
}
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
@@ -1704,7 +1753,7 @@
$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;
@@ -1729,8 +1778,10 @@
$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))) {
- $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
+ $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
+ $frame_textencoding_terminator = "\x00";
}
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
@@ -1752,25 +1803,26 @@
$frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
- $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
- if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ $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($this->TextEncodingTerminatorLookup($frame_textencoding));
-
- $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
- if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
+ $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
}
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
- if (ord($frame_description) === 0) {
+ if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
+ // if description only contains a BOM or terminator then make it blank
$frame_description = '';
}
- $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
+ $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);
@@ -1944,6 +1996,186 @@
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;
+ }
+
+ if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
+ if ($subframe['name'] == 'TIT2') {
+ $parsedFrame['chapter_name'] = $encoding_converted_text;
+ } elseif ($subframe['name'] == 'TIT3') {
+ $parsedFrame['chapter_description'] = $encoding_converted_text;
+ }
+ $parsedFrame['subframes'][] = $subframe;
+ } else {
+ $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
+ }
+ }
+ unset($subframe_rawdata, $subframe, $encoding_converted_text);
+ }
+
+ $id3v2_chapter_entry = array();
+ foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') 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;
@@ -3344,7 +3576,7 @@
3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
255 => "\x00\x00"
);
- return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : '');
+ return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
}
public static function TextEncodingNameLookup($encoding) {
@@ -3421,4 +3653,89 @@
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;
+ }
+
}