<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
/// //
// module.tag.id3v2.php //
// module for analyzing ID3v2 tags //
// dependencies: module.tag.id3v1.php //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
class getid3_id3v2 extends getid3_handler
{
public $StartingOffset = 0;
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
// Overall tag structure:
// +-----------------------------+
// | Header (10 bytes) |
// +-----------------------------+
// | Extended Header |
// | (variable length, OPTIONAL) |
// +-----------------------------+
// | Frames (variable length) |
// +-----------------------------+
// | Padding |
// | (variable length, OPTIONAL) |
// +-----------------------------+
// | Footer (10 bytes, OPTIONAL) |
// +-----------------------------+
// Header
// ID3v2/file identifier "ID3"
// ID3v2 version $04 00
// ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
// ID3v2 size 4 * %0xxxxxxx
// shortcuts
$info['id3v2']['header'] = true;
$thisfile_id3v2 = &$info['id3v2'];
$thisfile_id3v2['flags'] = array();
$thisfile_id3v2_flags = &$thisfile_id3v2['flags'];
$this->fseek($this->StartingOffset);
$header = $this->fread(10);
if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) {
$thisfile_id3v2['majorversion'] = ord($header[3]);
$thisfile_id3v2['minorversion'] = ord($header[4]);
// shortcut
$id3v2_majorversion = &$thisfile_id3v2['majorversion'];
} else {
unset($info['id3v2']);
return false;
}
if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
$this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
return false;
}
$id3_flags = ord($header[5]);
switch ($id3v2_majorversion) {
case 2:
// %ab000000 in v2.2
$thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
$thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
break;
case 3:
// %abc00000 in v2.3
$thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
$thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
$thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
break;
case 4:
// %abcd0000 in v2.4
$thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
$thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
$thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
$thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present
break;
}
$thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
$thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
$thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
// create 'encoding' key - used by getid3::HandleAllTags()
// in ID3v2 every field can have it's own encoding type
// so force everything to UTF-8 so it can be handled consistantly
$thisfile_id3v2['encoding'] = 'UTF-8';
// Frames
// All ID3v2 frames consists of one frame header followed by one or more
// fields containing the actual information. The header is always 10
// bytes and laid out as follows:
//
// Frame ID $xx xx xx xx (four characters)
// Size 4 * %0xxxxxxx
// Flags $xx xx
$sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
if (!empty($thisfile_id3v2['exthead']['length'])) {
$sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
}
if (!empty($thisfile_id3v2_flags['isfooter'])) {
$sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
}
if ($sizeofframes > 0) {
$framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
// if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
$framedata = $this->DeUnsynchronise($framedata);
}
// [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
// of on tag level, making it easier to skip frames, increasing the streamability
// of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
// there exists an unsynchronised frame, while the new unsynchronisation flag in
// the frame header [S:4.1.2] indicates unsynchronisation.
//$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
$framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
// Extended Header
if (!empty($thisfile_id3v2_flags['exthead'])) {
$extended_header_offset = 0;
if ($id3v2_majorversion == 3) {
// v2.3 definition:
//Extended header size $xx xx xx xx // 32-bit integer
//Extended Flags $xx xx
// %x0000000 %00000000 // v2.3
// x - CRC data present
//Size of padding $xx xx xx xx
$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
$extended_header_offset += 4;
$thisfile_id3v2['exthead']['flag_bytes'] = 2;
$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
$thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
$extended_header_offset += 4;
if ($thisfile_id3v2['exthead']['flags']['crc']) {
$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
$extended_header_offset += 4;
}
$extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
} elseif ($id3v2_majorversion == 4) {
// v2.4 definition:
//Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer
//Number of flag bytes $01
//Extended Flags $xx
// %0bcd0000 // v2.4
// b - Tag is an update
// Flag data length $00
// c - CRC data present
// Flag data length $05
// Total frame CRC 5 * %0xxxxxxx
// d - Tag restrictions
// Flag data length $01
$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
$extended_header_offset += 4;
$thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
$extended_header_offset += 1;
$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
$thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
if ($thisfile_id3v2['exthead']['flags']['update']) {
$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
$extended_header_offset += 1;
}
if ($thisfile_id3v2['exthead']['flags']['crc']) {
$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
$extended_header_offset += 1;
$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
$extended_header_offset += $ext_header_chunk_length;
}
if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
$extended_header_offset += 1;
// %ppqrrstt
$restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
$extended_header_offset += 1;
$thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
$thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
$thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
$thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
$thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
$thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
$thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
$thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
}
if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
$this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
}
}
$framedataoffset += $extended_header_offset;
$framedata = substr($framedata, $extended_header_offset);
} // end extended header
while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
// insufficient room left in ID3v2 header for actual data - must be padding
$thisfile_id3v2['padding']['start'] = $framedataoffset;
$thisfile_id3v2['padding']['length'] = strlen($framedata);
$thisfile_id3v2['padding']['valid'] = true;
for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
if ($framedata[$i] != "\x00") {
$thisfile_id3v2['padding']['valid'] = false;
$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
$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
}
$frame_header = null;
$frame_name = null;
$frame_size = null;
$frame_flags = null;
if ($id3v2_majorversion == 2) {
// Frame ID $xx xx xx (three characters)
// Size $xx xx xx (24-bit integer)
// Flags $xx xx
$frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
$framedata = substr($framedata, 6); // and leave the rest in $framedata
$frame_name = substr($frame_header, 0, 3);
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
$frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
} elseif ($id3v2_majorversion > 2) {
// Frame ID $xx xx xx xx (four characters)
// Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
// Flags $xx xx
$frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
$framedata = substr($framedata, 10); // and leave the rest in $framedata
$frame_name = substr($frame_header, 0, 4);
if ($id3v2_majorversion == 3) {
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
} else { // ID3v2.4+
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
}
if ($frame_size < (strlen($framedata) + 4)) {
$nextFrameID = substr($framedata, $frame_size, 4);
if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
// next frame is OK
} 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))) {
$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
}
}
$frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
}
if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
// padding encountered
$thisfile_id3v2['padding']['start'] = $framedataoffset;
$thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
$thisfile_id3v2['padding']['valid'] = true;
$len = strlen($framedata);
for ($i = 0; $i < $len; $i++) {
if ($framedata[$i] != "\x00") {
$thisfile_id3v2['padding']['valid'] = false;
$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
$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 ($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))) {
$parsedFrame = array();
$parsedFrame['frame_name'] = $frame_name;
$parsedFrame['frame_flags_raw'] = $frame_flags;
$parsedFrame['data'] = substr($framedata, 0, $frame_size);
$parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size);
$parsedFrame['dataoffset'] = $framedataoffset;
$this->ParseID3v2Frame($parsedFrame);
$thisfile_id3v2[$frame_name][] = $parsedFrame;
$framedata = substr($framedata, $frame_size);
} else { // invalid frame length or FrameID
if ($frame_size <= strlen($framedata)) {
if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
// next frame is valid, just skip the current frame
$framedata = substr($framedata, $frame_size);
$this->warning('Next ID3v2 frame is valid, skipping current frame.');
} else {
// next frame is invalid too, abort processing
//unset($framedata);
$framedata = null;
$this->error('Next ID3v2 frame is also invalid, aborting processing.');
}
} elseif ($frame_size == strlen($framedata)) {
// this is the last frame, just skip
$this->warning('This was the last ID3v2 frame.');
} else {
// next frame is invalid too, abort processing
//unset($framedata);
$framedata = null;
$this->warning('Invalid ID3v2 frame size, aborting.');
}
if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
switch ($frame_name) {
case "\x00\x00".'MP':
case "\x00".'MP3':
case ' MP3':
case 'MP3e':
case "\x00".'MP':
case ' MP':
case 'MP3':
$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:
$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))) {
$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 {
$this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');
}
}
$framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
}
}
// Footer
// The footer is a copy of the header, but with a different identifier.
// ID3v2 identifier "3DI"
// ID3v2 version $04 00
// ID3v2 flags %abcd0000
// ID3v2 size 4 * %0xxxxxxx
if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
$footer = $this->fread(10);
if (substr($footer, 0, 3) == '3DI') {
$thisfile_id3v2['footer'] = true;
$thisfile_id3v2['majorversion_footer'] = ord($footer[3]);
$thisfile_id3v2['minorversion_footer'] = ord($footer[4]);
}
if ($thisfile_id3v2['majorversion_footer'] <= 4) {
$id3_flags = ord($footer[5]);
$thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80);
$thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40);
$thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20);
$thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
$thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
}
} // end footer
if (isset($thisfile_id3v2['comments']['genre'])) {
$genres = array();
foreach ($thisfile_id3v2['comments']['genre'] as $key => $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_number'])) {
foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) {
if (strstr($value, '/')) {
list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]);
}
}
}
if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
$thisfile_id3v2['comments']['year'] = array($matches[1]);
}
if (!empty($thisfile_id3v2['TXXX'])) {
// MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
switch ($txxx_array['description']) {
case 'replaygain_track_gain':
if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
$info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
}
break;
case 'replaygain_track_peak':
if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
$info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
}
break;
case 'replaygain_album_gain':
if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
$info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
}
break;
}
}
}
// Set avdataoffset
$info['avdataoffset'] = $thisfile_id3v2['headerlength'];
if (isset($thisfile_id3v2['footer'])) {
$info['avdataoffset'] += 10;
}
return true;
}
/**
* @param string $genrestring
*
* @return array
*/
public function ParseID3v2GenreString($genrestring) {
// Parse genres into arrays of genreName and genreID
// 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 (strpos($genrestring, '/') !== false) {
$LegitimateSlashedGenreList = array( // https://github.com/JamesHeinrich/getID3/issues/223
'Pop/Funk', // ID3v1 genre #62 - https://en.wikipedia.org/wiki/ID3#standard
'Cut-up/DJ', // Discogs - https://www.discogs.com/style/cut-up/dj
'RnB/Swing', // Discogs - https://www.discogs.com/style/rnb/swing
'Funk / Soul', // Discogs (note spaces) - https://www.discogs.com/genre/funk+%2F+soul
);
$genrestring = str_replace('/', "\x00", $genrestring);
foreach ($LegitimateSlashedGenreList as $SlashedGenre) {
$genrestring = str_ireplace(str_replace('/', "\x00", $SlashedGenre), $SlashedGenre, $genrestring);
}
}
// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
if (strpos($genrestring, ';') !== false) {
$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);
if ($element) {
if (preg_match('#^[0-9]{1,3}$#', $element)) {
$clean_genres[] = getid3_id3v1::LookupGenreName($element);
} else {
$clean_genres[] = str_replace('((', '(', $element);
}
}
}
return $clean_genres;
}
/**
* @param array $parsedFrame
*
* @return bool
*/
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";
}
if (strlen($parsedFrame['data']) >= (4 + strlen($frame_textencoding_terminator))) { // shouldn't be an issue but badly-written files have been spotted in the wild with not only no contents but also missing the required language field, see https://github.com/JamesHeinrich/getID3/issues/315
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3;
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
$parsedFrame['language'] = $frame_language;
$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
}
} else {
$this->warning('Invalid data in frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset']);
}
unset($parsedFrame['data']);
} 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";
}
$frame_imagetype = null;
$frame_mimetype = null;
if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
if (strtolower($frame_imagetype) == 'ima') {
// 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); // @phpstan-ignore-line
}
} 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;
foreach (array('track','album') as $rgad_entry_type) {
$rg_adjustment_word = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2;
$parsedFrame['raw'][$rgad_entry_type]['name'] = ($rg_adjustment_word & 0xE000) >> 13;
$parsedFrame['raw'][$rgad_entry_type]['originator'] = ($rg_adjustment_word & 0x1C00) >> 10;
$parsedFrame['raw'][$rgad_entry_type]['signbit'] = ($rg_adjustment_word & 0x0200) >> 9;
$parsedFrame['raw'][$rgad_entry_type]['adjustment'] = ($rg_adjustment_word & 0x0100);
}
$parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
$parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
$parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
$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;
}
/**
* @param string $data
*
* @return string
*/
public function DeUnsynchronise($data) {
return str_replace("\xFF\x00", "\xFF", $data);
}
/**
* @param int $index
*
* @return string
*/
public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
0x00 => 'No more than 128 frames and 1 MB total tag size',
0x01 => 'No more than 64 frames and 128 KB total tag size',
0x02 => 'No more than 32 frames and 40 KB total tag size',
0x03 => 'No more than 32 frames and 4 KB total tag size',
);
return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
static $LookupExtendedHeaderRestrictionsTextEncodings = array(
0x00 => 'No restrictions',
0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
);
return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
0x00 => 'No restrictions',
0x01 => 'No string is longer than 1024 characters',
0x02 => 'No string is longer than 128 characters',
0x03 => 'No string is longer than 30 characters',
);
return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
static $LookupExtendedHeaderRestrictionsImageEncoding = array(
0x00 => 'No restrictions',
0x01 => 'Images are encoded only with PNG or JPEG',
);
return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
0x00 => 'No restrictions',
0x01 => 'All images are 256x256 pixels or smaller',
0x02 => 'All images are 64x64 pixels or smaller',
0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
);
return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
}
/**
* @param string $currencyid
*
* @return string
*/
public function LookupCurrencyUnits($currencyid) {
$begin = __LINE__;
/** This is not a comment!
AED Dirhams
AFA Afghanis
ALL Leke
AMD Drams
ANG Guilders
AOA Kwanza
ARS Pesos
ATS Schillings
AUD Dollars
AWG Guilders
AZM Manats
BAM Convertible Marka
BBD Dollars
BDT Taka
BEF Francs
BGL Leva
BHD Dinars
BIF Francs
BMD Dollars
BND Dollars
BOB Bolivianos
BRL Brazil Real
BSD Dollars
BTN Ngultrum
BWP Pulas
BYR Rubles
BZD Dollars
CAD Dollars
CDF Congolese Francs
CHF Francs
CLP Pesos
CNY Yuan Renminbi
COP Pesos
CRC Colones
CUP Pesos
CVE Escudos
CYP Pounds
CZK Koruny
DEM Deutsche Marks
DJF Francs
DKK Kroner
DOP Pesos
DZD Algeria Dinars
EEK Krooni
EGP Pounds
ERN Nakfa
ESP Pesetas
ETB Birr
EUR Euro
FIM Markkaa
FJD Dollars
FKP Pounds
FRF Francs
GBP Pounds
GEL Lari
GGP Pounds
GHC Cedis
GIP Pounds
GMD Dalasi
GNF Francs
GRD Drachmae
GTQ Quetzales
GYD Dollars
HKD Dollars
HNL Lempiras
HRK Kuna
HTG Gourdes
HUF Forints
IDR Rupiahs
IEP Pounds
ILS New Shekels
IMP Pounds
INR Rupees
IQD Dinars
IRR Rials
ISK Kronur
ITL Lire
JEP Pounds
JMD Dollars
JOD Dinars
JPY Yen
KES Shillings
KGS Soms
KHR Riels
KMF Francs
KPW Won
KWD Dinars
KYD Dollars
KZT Tenge
LAK Kips
LBP Pounds
LKR Rupees
LRD Dollars
LSL Maloti
LTL Litai
LUF Francs
LVL Lati
LYD Dinars
MAD Dirhams
MDL Lei
MGF Malagasy Francs
MKD Denars
MMK Kyats
MNT Tugriks
MOP Patacas
MRO Ouguiyas
MTL Liri
MUR Rupees
MVR Rufiyaa
MWK Kwachas
MXN Pesos
MYR Ringgits
MZM Meticais
NAD Dollars
NGN Nairas
NIO Gold Cordobas
NLG Guilders
NOK Krone
NPR Nepal Rupees
NZD Dollars
OMR Rials
PAB Balboa
PEN Nuevos Soles
PGK Kina
PHP Pesos
PKR Rupees
PLN Zlotych
PTE Escudos
PYG Guarani
QAR Rials
ROL Lei
RUR Rubles
RWF Rwanda Francs
SAR Riyals
SBD Dollars
SCR Rupees
SDD Dinars
SEK Kronor
SGD Dollars
SHP Pounds
SIT Tolars
SKK Koruny
SLL Leones
SOS Shillings
SPL Luigini
SRG Guilders
STD Dobras
SVC Colones
SYP Pounds
SZL Emalangeni
THB Baht
TJR Rubles
TMM Manats
TND Dinars
TOP Pa'anga
TRL Liras (old)
TRY Liras
TTD Dollars
TVD Tuvalu Dollars
TWD New Dollars
TZS Shillings
UAH Hryvnia
UGX Shillings
USD Dollars
UYU Pesos
UZS Sums
VAL Lire
VEB Bolivares
VND Dong
VUV Vatu
WST Tala
XAF Francs
XAG Ounces
XAU Ounces
XCD Dollars
XDR Special Drawing Rights
XPD Ounces
XPF Francs
XPT Ounces
YER Rials
YUM New Dinars
ZAR Rand
ZMK Kwacha
ZWD Zimbabwe Dollars
*/
return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
}
/**
* @param string $currencyid
*
* @return string
*/
public function LookupCurrencyCountry($currencyid) {
$begin = __LINE__;
/** This is not a comment!
AED United Arab Emirates
AFA Afghanistan
ALL Albania
AMD Armenia
ANG Netherlands Antilles
AOA Angola
ARS Argentina
ATS Austria
AUD Australia
AWG Aruba
AZM Azerbaijan
BAM Bosnia and Herzegovina
BBD Barbados
BDT Bangladesh
BEF Belgium
BGL Bulgaria
BHD Bahrain
BIF Burundi
BMD Bermuda
BND Brunei Darussalam
BOB Bolivia
BRL Brazil
BSD Bahamas
BTN Bhutan
BWP Botswana
BYR Belarus
BZD Belize
CAD Canada
CDF Congo/Kinshasa
CHF Switzerland
CLP Chile
CNY China
COP Colombia
CRC Costa Rica
CUP Cuba
CVE Cape Verde
CYP Cyprus
CZK Czech Republic
DEM Germany
DJF Djibouti
DKK Denmark
DOP Dominican Republic
DZD Algeria
EEK Estonia
EGP Egypt
ERN Eritrea
ESP Spain
ETB Ethiopia
EUR Euro Member Countries
FIM Finland
FJD Fiji
FKP Falkland Islands (Malvinas)
FRF France
GBP United Kingdom
GEL Georgia
GGP Guernsey
GHC Ghana
GIP Gibraltar
GMD Gambia
GNF Guinea
GRD Greece
GTQ Guatemala
GYD Guyana
HKD Hong Kong
HNL Honduras
HRK Croatia
HTG Haiti
HUF Hungary
IDR Indonesia
IEP Ireland (Eire)
ILS Israel
IMP Isle of Man
INR India
IQD Iraq
IRR Iran
ISK Iceland
ITL Italy
JEP Jersey
JMD Jamaica
JOD Jordan
JPY Japan
KES Kenya
KGS Kyrgyzstan
KHR Cambodia
KMF Comoros
KPW Korea
KWD Kuwait
KYD Cayman Islands
KZT Kazakstan
LAK Laos
LBP Lebanon
LKR Sri Lanka
LRD Liberia
LSL Lesotho
LTL Lithuania
LUF Luxembourg
LVL Latvia
LYD Libya
MAD Morocco
MDL Moldova
MGF Madagascar
MKD Macedonia
MMK Myanmar (Burma)
MNT Mongolia
MOP Macau
MRO Mauritania
MTL Malta
MUR Mauritius
MVR Maldives (Maldive Islands)
MWK Malawi
MXN Mexico
MYR Malaysia
MZM Mozambique
NAD Namibia
NGN Nigeria
NIO Nicaragua
NLG Netherlands (Holland)
NOK Norway
NPR Nepal
NZD New Zealand
OMR Oman
PAB Panama
PEN Peru
PGK Papua New Guinea
PHP Philippines
PKR Pakistan
PLN Poland
PTE Portugal
PYG Paraguay
QAR Qatar
ROL Romania
RUR Russia
RWF Rwanda
SAR Saudi Arabia
SBD Solomon Islands
SCR Seychelles
SDD Sudan
SEK Sweden
SGD Singapore
SHP Saint Helena
SIT Slovenia
SKK Slovakia
SLL Sierra Leone
SOS Somalia
SPL Seborga
SRG Suriname
STD São Tome and Principe
SVC El Salvador
SYP Syria
SZL Swaziland
THB Thailand
TJR Tajikistan
TMM Turkmenistan
TND Tunisia
TOP Tonga
TRL Turkey
TRY Turkey
TTD Trinidad and Tobago
TVD Tuvalu
TWD Taiwan
TZS Tanzania
UAH Ukraine
UGX Uganda
USD United States of America
UYU Uruguay
UZS Uzbekistan
VAL Vatican City
VEB Venezuela
VND Viet Nam
VUV Vanuatu
WST Samoa
XAF Communauté Financière Africaine
XAG Silver
XAU Gold
XCD East Caribbean
XDR International Monetary Fund
XPD Palladium
XPF Comptoirs Français du Pacifique
XPT Platinum
YER Yemen
YUM Yugoslavia
ZAR South Africa
ZMK Zambia
ZWD Zimbabwe
*/
return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
}
/**
* @param string $languagecode
* @param bool $casesensitive
*
* @return string
*/
public static function LanguageLookup($languagecode, $casesensitive=false) {
if (!$casesensitive) {
$languagecode = strtolower($languagecode);
}
// http://www.id3.org/id3v2.4.0-structure.txt
// [4. ID3v2 frame overview]
// The three byte language field, present in several frames, is used to
// describe the language of the frame's content, according to ISO-639-2
// [ISO-639-2]. The language should be represented in lower case. If the
// language is not known the string "XXX" should be used.
// ISO 639-2 - http://www.id3.org/iso639-2.html
$begin = __LINE__;
/** This is not a comment!
XXX unknown
xxx unknown
aar Afar
abk Abkhazian
ace Achinese
ach Acoli
ada Adangme
afa Afro-Asiatic (Other)
afh Afrihili
afr Afrikaans
aka Akan
akk Akkadian
alb Albanian
ale Aleut
alg Algonquian Languages
amh Amharic
ang English, Old (ca. 450-1100)
apa Apache Languages
ara Arabic
arc Aramaic
arm Armenian
arn Araucanian
arp Arapaho
art Artificial (Other)
arw Arawak
asm Assamese
ath Athapascan Languages
ava Avaric
ave Avestan
awa Awadhi
aym Aymara
aze Azerbaijani
bad Banda
bai Bamileke Languages
bak Bashkir
bal Baluchi
bam Bambara
ban Balinese
baq Basque
bas Basa
bat Baltic (Other)
bej Beja
bel Byelorussian
bem Bemba
ben Bengali
ber Berber (Other)
bho Bhojpuri
bih Bihari
bik Bikol
bin Bini
bis Bislama
bla Siksika
bnt Bantu (Other)
bod Tibetan
bra Braj
bre Breton
bua Buriat
bug Buginese
bul Bulgarian
bur Burmese
cad Caddo
cai Central American Indian (Other)
car Carib
cat Catalan
cau Caucasian (Other)
ceb Cebuano
cel Celtic (Other)
ces Czech
cha Chamorro
chb Chibcha
che Chechen
chg Chagatai
chi Chinese
chm Mari
chn Chinook jargon
cho Choctaw
chr Cherokee
chu Church Slavic
chv Chuvash
chy Cheyenne
cop Coptic
cor Cornish
cos Corsican
cpe Creoles and Pidgins, English-based (Other)
cpf Creoles and Pidgins, French-based (Other)
cpp Creoles and Pidgins, Portuguese-based (Other)
cre Cree
crp Creoles and Pidgins (Other)
cus Cushitic (Other)
cym Welsh
cze Czech
dak Dakota
dan Danish
del Delaware
deu German
din Dinka
div Divehi
doi Dogri
dra Dravidian (Other)
dua Duala
dum Dutch, Middle (ca. 1050-1350)
dut Dutch
dyu Dyula
dzo Dzongkha
efi Efik
egy Egyptian (Ancient)
eka Ekajuk
ell Greek, Modern (1453-)
elx Elamite
eng English
enm English, Middle (ca. 1100-1500)
epo Esperanto
esk Eskimo (Other)
esl Spanish
est Estonian
eus Basque
ewe Ewe
ewo Ewondo
fan Fang
fao Faroese
fas Persian
fat Fanti
fij Fijian
fin Finnish
fiu Finno-Ugrian (Other)
fon Fon
fra French
fre French
frm French, Middle (ca. 1400-1600)
fro French, Old (842- ca. 1400)
fry Frisian
ful Fulah
gaa Ga
gae Gaelic (Scots)
gai Irish
gay Gayo
gdh Gaelic (Scots)
gem Germanic (Other)
geo Georgian
ger German
gez Geez
gil Gilbertese
glg Gallegan
gmh German, Middle High (ca. 1050-1500)
goh German, Old High (ca. 750-1050)
gon Gondi
got Gothic
grb Grebo
grc Greek, Ancient (to 1453)
gre Greek, Modern (1453-)
grn Guarani
guj Gujarati
hai Haida
hau Hausa
haw Hawaiian
heb Hebrew
her Herero
hil Hiligaynon
him Himachali
hin Hindi
hmo Hiri Motu
hun Hungarian
hup Hupa
hye Armenian
iba Iban
ibo Igbo
ice Icelandic
ijo Ijo
iku Inuktitut
ilo Iloko
ina Interlingua (International Auxiliary language Association)
inc Indic (Other)
ind Indonesian
ine Indo-European (Other)
ine Interlingue
ipk Inupiak
ira Iranian (Other)
iri Irish
iro Iroquoian uages
isl Icelandic
ita Italian
jav Javanese
jaw Javanese
jpn Japanese
jpr Judeo-Persian
jrb Judeo-Arabic
kaa Kara-Kalpak
kab Kabyle
kac Kachin
kal Greenlandic
kam Kamba
kan Kannada
kar Karen
kas Kashmiri
kat Georgian
kau Kanuri
kaw Kawi
kaz Kazakh
kha Khasi
khi Khoisan (Other)
khm Khmer
kho Khotanese
kik Kikuyu
kin Kinyarwanda
kir Kirghiz
kok Konkani
kom Komi
kon Kongo
kor Korean
kpe Kpelle
kro Kru
kru Kurukh
kua Kuanyama
kum Kumyk
kur Kurdish
kus Kusaie
kut Kutenai
lad Ladino
lah Lahnda
lam Lamba
lao Lao
lat Latin
lav Latvian
lez Lezghian
lin Lingala
lit Lithuanian
lol Mongo
loz Lozi
ltz Letzeburgesch
lub Luba-Katanga
lug Ganda
lui Luiseno
lun Lunda
luo Luo (Kenya and Tanzania)
mac Macedonian
mad Madurese
mag Magahi
mah Marshall
mai Maithili
mak Macedonian
mak Makasar
mal Malayalam
man Mandingo
mao Maori
map Austronesian (Other)
mar Marathi
mas Masai
max Manx
may Malay
men Mende
mga Irish, Middle (900 - 1200)
mic Micmac
min Minangkabau
mis Miscellaneous (Other)
mkh Mon-Kmer (Other)
mlg Malagasy
mlt Maltese
mni Manipuri
mno Manobo Languages
moh Mohawk
mol Moldavian
mon Mongolian
mos Mossi
mri Maori
msa Malay
mul Multiple Languages
mun Munda Languages
mus Creek
mwr Marwari
mya Burmese
myn Mayan Languages
nah Aztec
nai North American Indian (Other)
nau Nauru
nav Navajo
nbl Ndebele, South
nde Ndebele, North
ndo Ndongo
nep Nepali
new Newari
nic Niger-Kordofanian (Other)
niu Niuean
nla Dutch
nno Norwegian (Nynorsk)
non Norse, Old
nor Norwegian
nso Sotho, Northern
nub Nubian Languages
nya Nyanja
nym Nyamwezi
nyn Nyankole
nyo Nyoro
nzi Nzima
oci Langue d'Oc (post 1500)
oji Ojibwa
ori Oriya
orm Oromo
osa Osage
oss Ossetic
ota Turkish, Ottoman (1500 - 1928)
oto Otomian Languages
paa Papuan-Australian (Other)
pag Pangasinan
pal Pahlavi
pam Pampanga
pan Panjabi
pap Papiamento
pau Palauan
peo Persian, Old (ca 600 - 400 B.C.)
per Persian
phn Phoenician
pli Pali
pol Polish
pon Ponape
por Portuguese
pra Prakrit uages
pro Provencal, Old (to 1500)
pus Pushto
que Quechua
raj Rajasthani
rar Rarotongan
roa Romance (Other)
roh Rhaeto-Romance
rom Romany
ron Romanian
rum Romanian
run Rundi
rus Russian
sad Sandawe
sag Sango
sah Yakut
sai South American Indian (Other)
sal Salishan Languages
sam Samaritan Aramaic
san Sanskrit
sco Scots
scr Serbo-Croatian
sel Selkup
sem Semitic (Other)
sga Irish, Old (to 900)
shn Shan
sid Sidamo
sin Singhalese
sio Siouan Languages
sit Sino-Tibetan (Other)
sla Slavic (Other)
slk Slovak
slo Slovak
slv Slovenian
smi Sami Languages
smo Samoan
sna Shona
snd Sindhi
sog Sogdian
som Somali
son Songhai
sot Sotho, Southern
spa Spanish
sqi Albanian
srd Sardinian
srr Serer
ssa Nilo-Saharan (Other)
ssw Siswant
ssw Swazi
suk Sukuma
sun Sudanese
sus Susu
sux Sumerian
sve Swedish
swa Swahili
swe Swedish
syr Syriac
tah Tahitian
tam Tamil
tat Tatar
tel Telugu
tem Timne
ter Tereno
tgk Tajik
tgl Tagalog
tha Thai
tib Tibetan
tig Tigre
tir Tigrinya
tiv Tivi
tli Tlingit
tmh Tamashek
tog Tonga (Nyasa)
ton Tonga (Tonga Islands)
tru Truk
tsi Tsimshian
tsn Tswana
tso Tsonga
tuk Turkmen
tum Tumbuka
tur Turkish
tut Altaic (Other)
twi Twi
tyv Tuvinian
uga Ugaritic
uig Uighur
ukr Ukrainian
umb Umbundu
und Undetermined
urd Urdu
uzb Uzbek
vai Vai
ven Venda
vie Vietnamese
vol Volapük
vot Votic
wak Wakashan Languages
wal Walamo
war Waray
was Washo
wel Welsh
wen Sorbian Languages
wol Wolof
xho Xhosa
yao Yao
yap Yap
yid Yiddish
yor Yoruba
zap Zapotec
zen Zenaga
zha Zhuang
zho Chinese
zul Zulu
zun Zuni
*/
return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
}
/**
* @param int $index
*
* @return string
*/
public static function ETCOEventLookup($index) {
if (($index >= 0x17) && ($index <= 0xDF)) {
return 'reserved for future use';
}
if (($index >= 0xE0) && ($index <= 0xEF)) {
return 'not predefined synch 0-F';
}
if (($index >= 0xF0) && ($index <= 0xFC)) {
return 'reserved for future use';
}
static $EventLookup = array(
0x00 => 'padding (has no meaning)',
0x01 => 'end of initial silence',
0x02 => 'intro start',
0x03 => 'main part start',
0x04 => 'outro start',
0x05 => 'outro end',
0x06 => 'verse start',
0x07 => 'refrain start',
0x08 => 'interlude start',
0x09 => 'theme start',
0x0A => 'variation start',
0x0B => 'key change',
0x0C => 'time change',
0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
0x0E => 'sustained noise',
0x0F => 'sustained noise end',
0x10 => 'intro end',
0x11 => 'main part end',
0x12 => 'verse end',
0x13 => 'refrain end',
0x14 => 'theme end',
0x15 => 'profanity',
0x16 => 'profanity end',
0xFD => 'audio end (start of silence)',
0xFE => 'audio file ends',
0xFF => 'one more byte of events follows'
);
return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public static function SYTLContentTypeLookup($index) {
static $SYTLContentTypeLookup = array(
0x00 => 'other',
0x01 => 'lyrics',
0x02 => 'text transcription',
0x03 => 'movement/part name', // (e.g. 'Adagio')
0x04 => 'events', // (e.g. 'Don Quijote enters the stage')
0x05 => 'chord', // (e.g. 'Bb F Fsus')
0x06 => 'trivia/\'pop up\' information',
0x07 => 'URLs to webpages',
0x08 => 'URLs to images'
);
return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
}
/**
* @param int $index
* @param bool $returnarray
*
* @return array|string
*/
public static function APICPictureTypeLookup($index, $returnarray=false) {
static $APICPictureTypeLookup = array(
0x00 => 'Other',
0x01 => '32x32 pixels \'file icon\' (PNG only)',
0x02 => 'Other file icon',
0x03 => 'Cover (front)',
0x04 => 'Cover (back)',
0x05 => 'Leaflet page',
0x06 => 'Media (e.g. label side of CD)',
0x07 => 'Lead artist/lead performer/soloist',
0x08 => 'Artist/performer',
0x09 => 'Conductor',
0x0A => 'Band/Orchestra',
0x0B => 'Composer',
0x0C => 'Lyricist/text writer',
0x0D => 'Recording Location',
0x0E => 'During recording',
0x0F => 'During performance',
0x10 => 'Movie/video screen capture',
0x11 => 'A bright coloured fish',
0x12 => 'Illustration',
0x13 => 'Band/artist logotype',
0x14 => 'Publisher/Studio logotype'
);
if ($returnarray) {
return $APICPictureTypeLookup;
}
return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public static function COMRReceivedAsLookup($index) {
static $COMRReceivedAsLookup = array(
0x00 => 'Other',
0x01 => 'Standard CD album with other songs',
0x02 => 'Compressed audio on CD',
0x03 => 'File over the Internet',
0x04 => 'Stream over the Internet',
0x05 => 'As note sheets',
0x06 => 'As note sheets in a book with other sheets',
0x07 => 'Music on other media',
0x08 => 'Non-musical merchandise'
);
return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public static function RVA2ChannelTypeLookup($index) {
static $RVA2ChannelTypeLookup = array(
0x00 => 'Other',
0x01 => 'Master volume',
0x02 => 'Front right',
0x03 => 'Front left',
0x04 => 'Back right',
0x05 => 'Back left',
0x06 => 'Front centre',
0x07 => 'Back centre',
0x08 => 'Subwoofer'
);
return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
}
/**
* @param string $framename
*
* @return string
*/
public static function FrameNameLongLookup($framename) {
$begin = __LINE__;
/** This is not a comment!
AENC Audio encryption
APIC Attached picture
ASPI Audio seek point index
BUF Recommended buffer size
CNT Play counter
COM Comments
COMM Comments
COMR Commercial frame
CRA Audio encryption
CRM Encrypted meta frame
ENCR Encryption method registration
EQU Equalisation
EQU2 Equalisation (2)
EQUA Equalisation
ETC Event timing codes
ETCO Event timing codes
GEO General encapsulated object
GEOB General encapsulated object
GRID Group identification registration
IPL Involved people list
IPLS Involved people list
LINK Linked information
LNK Linked information
MCDI Music CD identifier
MCI Music CD Identifier
MLL MPEG location lookup table
MLLT MPEG location lookup table
OWNE Ownership frame
PCNT Play counter
PIC Attached picture
POP Popularimeter
POPM Popularimeter
POSS Position synchronisation frame
PRIV Private frame
RBUF Recommended buffer size
REV Reverb
RVA Relative volume adjustment
RVA2 Relative volume adjustment (2)
RVAD Relative volume adjustment
RVRB Reverb
SEEK Seek frame
SIGN Signature frame
SLT Synchronised lyric/text
STC Synced tempo codes
SYLT Synchronised lyric/text
SYTC Synchronised tempo codes
TAL Album/Movie/Show title
TALB Album/Movie/Show title
TBP BPM (Beats Per Minute)
TBPM BPM (beats per minute)
TCM Composer
TCMP Part of a compilation
TCO Content type
TCOM Composer
TCON Content type
TCOP Copyright message
TCP Part of a compilation
TCR Copyright message
TDA Date
TDAT Date
TDEN Encoding time
TDLY Playlist delay
TDOR Original release time
TDRC Recording time
TDRL Release time
TDTG Tagging time
TDY Playlist delay
TEN Encoded by
TENC Encoded by
TEXT Lyricist/Text writer
TFLT File type
TFT File type
TIM Time
TIME Time
TIPL Involved people list
TIT1 Content group description
TIT2 Title/songname/content description
TIT3 Subtitle/Description refinement
TKE Initial key
TKEY Initial key
TLA Language(s)
TLAN Language(s)
TLE Length
TLEN Length
TMCL Musician credits list
TMED Media type
TMOO Mood
TMT Media type
TOA Original artist(s)/performer(s)
TOAL Original album/movie/show title
TOF Original filename
TOFN Original filename
TOL Original Lyricist(s)/text writer(s)
TOLY Original lyricist(s)/text writer(s)
TOPE Original artist(s)/performer(s)
TOR Original release year
TORY Original release year
TOT Original album/Movie/Show title
TOWN File owner/licensee
TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
TP2 Band/Orchestra/Accompaniment
TP3 Conductor/Performer refinement
TP4 Interpreted, remixed, or otherwise modified by
TPA Part of a set
TPB Publisher
TPE1 Lead performer(s)/Soloist(s)
TPE2 Band/orchestra/accompaniment
TPE3 Conductor/performer refinement
TPE4 Interpreted, remixed, or otherwise modified by
TPOS Part of a set
TPRO Produced notice
TPUB Publisher
TRC ISRC (International Standard Recording Code)
TRCK Track number/Position in set
TRD Recording dates
TRDA Recording dates
TRK Track number/Position in set
TRSN Internet radio station name
TRSO Internet radio station owner
TS2 Album-Artist sort order
TSA Album sort order
TSC Composer sort order
TSI Size
TSIZ Size
TSO2 Album-Artist sort order
TSOA Album sort order
TSOC Composer sort order
TSOP Performer sort order
TSOT Title sort order
TSP Performer sort order
TSRC ISRC (international standard recording code)
TSS Software/hardware and settings used for encoding
TSSE Software/Hardware and settings used for encoding
TSST Set subtitle
TST Title sort order
TT1 Content group description
TT2 Title/Songname/Content description
TT3 Subtitle/Description refinement
TXT Lyricist/text writer
TXX User defined text information frame
TXXX User defined text information frame
TYE Year
TYER Year
UFI Unique file identifier
UFID Unique file identifier
ULT Unsynchronised lyric/text transcription
USER Terms of use
USLT Unsynchronised lyric/text transcription
WAF Official audio file webpage
WAR Official artist/performer webpage
WAS Official audio source webpage
WCM Commercial information
WCOM Commercial information
WCOP Copyright/Legal information
WCP Copyright/Legal information
WOAF Official audio file webpage
WOAR Official artist/performer webpage
WOAS Official audio source webpage
WORS Official Internet radio station homepage
WPAY Payment
WPB Publishers official webpage
WPUB Publishers official webpage
WXX User defined URL link frame
WXXX User defined URL link frame
TFEA Featured Artist
TSTU Recording Studio
rgad Replay Gain Adjustment
*/
return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
// Last three:
// from Helium2 [www.helium2.com]
// from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
}
/**
* @param string $framename
*
* @return string
*/
public static function FrameNameShortLookup($framename) {
$begin = __LINE__;
/** This is not a comment!
AENC audio_encryption
APIC attached_picture
ASPI audio_seek_point_index
BUF recommended_buffer_size
CNT play_counter
COM comment
COMM comment
COMR commercial_frame
CRA audio_encryption
CRM encrypted_meta_frame
ENCR encryption_method_registration
EQU equalisation
EQU2 equalisation
EQUA equalisation
ETC event_timing_codes
ETCO event_timing_codes
GEO general_encapsulated_object
GEOB general_encapsulated_object
GRID group_identification_registration
IPL involved_people_list
IPLS involved_people_list
LINK linked_information
LNK linked_information
MCDI music_cd_identifier
MCI music_cd_identifier
MLL mpeg_location_lookup_table
MLLT mpeg_location_lookup_table
OWNE ownership_frame
PCNT play_counter
PIC attached_picture
POP popularimeter
POPM popularimeter
POSS position_synchronisation_frame
PRIV private_frame
RBUF recommended_buffer_size
REV reverb
RVA relative_volume_adjustment
RVA2 relative_volume_adjustment
RVAD relative_volume_adjustment
RVRB reverb
SEEK seek_frame
SIGN signature_frame
SLT synchronised_lyric
STC synced_tempo_codes
SYLT synchronised_lyric
SYTC synchronised_tempo_codes
TAL album
TALB album
TBP bpm
TBPM bpm
TCM composer
TCMP part_of_a_compilation
TCO genre
TCOM composer
TCON genre
TCOP copyright_message
TCP part_of_a_compilation
TCR copyright_message
TDA date
TDAT date
TDEN encoding_time
TDLY playlist_delay
TDOR original_release_time
TDRC recording_time
TDRL release_time
TDTG tagging_time
TDY playlist_delay
TEN encoded_by
TENC encoded_by
TEXT lyricist
TFLT file_type
TFT file_type
TIM time
TIME time
TIPL involved_people_list
TIT1 content_group_description
TIT2 title
TIT3 subtitle
TKE initial_key
TKEY initial_key
TLA language
TLAN language
TLE length
TLEN length
TMCL musician_credits_list
TMED media_type
TMOO mood
TMT media_type
TOA original_artist
TOAL original_album
TOF original_filename
TOFN original_filename
TOL original_lyricist
TOLY original_lyricist
TOPE original_artist
TOR original_year
TORY original_year
TOT original_album
TOWN file_owner
TP1 artist
TP2 band
TP3 conductor
TP4 remixer
TPA part_of_a_set
TPB publisher
TPE1 artist
TPE2 band
TPE3 conductor
TPE4 remixer
TPOS part_of_a_set
TPRO produced_notice
TPUB publisher
TRC isrc
TRCK track_number
TRD recording_dates
TRDA recording_dates
TRK track_number
TRSN internet_radio_station_name
TRSO internet_radio_station_owner
TS2 album_artist_sort_order
TSA album_sort_order
TSC composer_sort_order
TSI size
TSIZ size
TSO2 album_artist_sort_order
TSOA album_sort_order
TSOC composer_sort_order
TSOP performer_sort_order
TSOT title_sort_order
TSP performer_sort_order
TSRC isrc
TSS encoder_settings
TSSE encoder_settings
TSST set_subtitle
TST title_sort_order
TT1 content_group_description
TT2 title
TT3 subtitle
TXT lyricist
TXX text
TXXX text
TYE year
TYER year
UFI unique_file_identifier
UFID unique_file_identifier
ULT unsynchronised_lyric
USER terms_of_use
USLT unsynchronised_lyric
WAF url_file
WAR url_artist
WAS url_source
WCM commercial_information
WCOM commercial_information
WCOP copyright
WCP copyright
WOAF url_file
WOAR url_artist
WOAS url_source
WORS url_station
WPAY url_payment
WPB url_publisher
WPUB url_publisher
WXX url_user
WXXX url_user
TFEA featured_artist
TSTU recording_studio
rgad replay_gain_adjustment
*/
return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
}
/**
* @param string $encoding
*
* @return string
*/
public static function TextEncodingTerminatorLookup($encoding) {
// http://www.id3.org/id3v2.4.0-structure.txt
// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
static $TextEncodingTerminatorLookup = array(
0 => "\x00", // $00 ISO-8859-1. Terminated with $00.
1 => "\x00\x00", // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
255 => "\x00\x00"
);
return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
}
/**
* @param int $encoding
*
* @return string
*/
public static function TextEncodingNameLookup($encoding) {
// http://www.id3.org/id3v2.4.0-structure.txt
// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
static $TextEncodingNameLookup = array(
0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00.
1 => 'UTF-16', // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00.
255 => 'UTF-16BE'
);
return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
}
/**
* @param string $string
* @param string $terminator
*
* @return string
*/
public static function RemoveStringTerminator($string, $terminator) {
// Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present.
// https://github.com/JamesHeinrich/getID3/issues/121
// https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227
if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) {
$string = substr($string, 0, -strlen($terminator));
}
return $string;
}
/**
* @param string $string
*
* @return string
*/
public static function MakeUTF16emptyStringEmpty($string) {
if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
// if string only contains a BOM or terminator then make it actually an empty string
$string = '';
}
return $string;
}
/**
* @param string $framename
* @param int $id3v2majorversion
*
* @return bool|int
*/
public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
switch ($id3v2majorversion) {
case 2:
return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
case 3:
case 4:
return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
}
return false;
}
/**
* @param string $numberstring
* @param bool $allowdecimal
* @param bool $allownegative
*
* @return bool
*/
public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
$pattern = '#^';
$pattern .= ($allownegative ? '\\-?' : '');
$pattern .= '[0-9]+';
$pattern .= ($allowdecimal ? '(\\.[0-9]+)?' : '');
$pattern .= '$#';
return preg_match($pattern, $numberstring);
}
/**
* @param string $datestamp
*
* @return bool
*/
public static function IsValidDateStampString($datestamp) {
if (!preg_match('#^[12][0-9]{3}[01][0-9][0123][0-9]$#', $datestamp)) {
return false;
}
$year = substr($datestamp, 0, 4);
$month = substr($datestamp, 4, 2);
$day = substr($datestamp, 6, 2);
if (($year == 0) || ($month == 0) || ($day == 0)) {
return false;
}
if ($month > 12) {
return false;
}
if ($day > 31) {
return false;
}
if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
return false;
}
if (($day > 29) && ($month == 2)) {
return false;
}
return true;
}
/**
* @param int $majorversion
*
* @return int
*/
public static function ID3v2HeaderLength($majorversion) {
return (($majorversion == 2) ? 6 : 10);
}
/**
* @param string $frame_name
*
* @return string|false
*/
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;
}
}