X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/wordpress.git/blobdiff_plain/baca9ce86a38dc54c4574890ee2d352fd81f78b2..61343b82c4f0da4c68e4c6373daafff4a81efdd1:/wp-includes/ID3/module.tag.id3v2.php diff --git a/wp-includes/ID3/module.tag.id3v2.php b/wp-includes/ID3/module.tag.id3v2.php new file mode 100644 index 00000000..b08f9f9a --- /dev/null +++ b/wp-includes/ID3/module.tag.id3v2.php @@ -0,0 +1,3414 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +/// // +// module.tag.id3v2.php // +// module for analyzing ID3v2 tags // +// dependencies: module.tag.id3v1.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); + +class getid3_id3v2 extends getid3_handler +{ + public $StartingOffset = 0; + + 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']; + + + fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET); + $header = fread($this->getid3->fp, 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) + + $info['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 = fread($this->getid3->fp, $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) { + $info['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; + $info['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 ($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))) { + $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3'; + $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; + $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; + break; + } + } + break; // skip rest of ID3v2 header + } + + if ($frame_name == 'COM ') { + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]'; + $frame_name = 'COMM'; + } + if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { + + unset($parsedFrame); + $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); + $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.'; + + } else { + + // next frame is invalid too, abort processing + //unset($framedata); + $framedata = null; + $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.'; + + } + + } elseif ($frame_size == strlen($framedata)) { + + // this is the last frame, just skip + $info['warning'][] = 'This was the last ID3v2 frame.'; + + } else { + + // next frame is invalid too, abort processing + //unset($framedata); + $framedata = null; + $info['warning'][] = 'Invalid ID3v2 frame size, aborting.'; + + } + 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': + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]'; + break; + + default: + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).'; + break; + } + + } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) { + + $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).'; + + } else { + + $info['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 = fread($this->getid3->fp, 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(substr($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'])) { + foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) { + unset($thisfile_id3v2['comments']['genre'][$key]); + $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value))); + } + } + + if (isset($thisfile_id3v2['comments']['track'])) { + foreach ($thisfile_id3v2['comments']['track'] as $key => $value) { + if (strstr($value, '/')) { + list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$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; + } + + + 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(); + 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; + } + + + 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')) { + $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'; + } else { + if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { + //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) { + $parsedFrame['data'] = $decompresseddata; + unset($decompresseddata); + } else { + $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'; + } + } + } + } + + if (!empty($parsedFrame['flags']['DataLengthIndicator'])) { + if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) { + $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data'; + } + } + + 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; + } + $info['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'. + //
+ // Owner identifier $00 + // Identifier + $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. + //
+ // Text encoding $xx + // Description $00 (00) + // Value + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $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. + //
+ // Text encoding $xx + // Information + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + $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 + //
+ // Text encoding $xx + // Description $00 (00) + // URL + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + if ($frame_terminatorpos) { + // there are null bytes after the data - this is not according to spec + // only use data up to first null byte + $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos); + } else { + // no null bytes following data, just use all data + $frame_urldata = (string) $parsedFrame['data']; + } + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['url'] = $frame_urldata; + $parsedFrame['description'] = $frame_description; + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $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 + //
+ // URL + + $parsedFrame['url'] = trim($parsedFrame['data']); + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $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 + //
+ // Text encoding $xx + // People list strings + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); + $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset); + + // http://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 + //
+ // CD TOC + + 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 + //
+ // 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 + //
+ // 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); + 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 + //
+ // Time stamp format $xx + // Tempo 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. + //
+ // Text encoding $xx + // Language $xx xx xx + // Content descriptor $00 (00) + // Lyrics/text + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['data'] = $parsedFrame['data']; + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + $parsedFrame['description'] = $frame_description; + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text + // There may be more than one 'SYLT' frame in each tag, + // but only one with the same language and content descriptor. + //
+ // 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 $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)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $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, $this->TextEncodingTerminatorLookup($frame_textencoding)); + if ($frame_terminatorpos === false) { + $frame_remainingdata = ''; + } else { + if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); + + $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + 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. + //
+ // Text encoding $xx + // Language $xx xx xx + // Short content descrip. $00 (00) + // The actual text + + if (strlen($parsedFrame['data']) < 5) { + + $info['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)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = $frame_text; + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $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 + //
+ // Identification $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)) { + $info['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 + //
+ // 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 + //
+ // Interpolation method $xx + // $00 Band + // $01 Linear + // Identification $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 + //
+ // 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. + //
+ // 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 + //
+ // Text encoding $xx + // ID3v2.3+ => MIME type $00 + // ID3v2.2 => Image format $xx xx xx + // Picture type $xx + // Description $00 (00) + // Picture data + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + + 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']) { + $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset); + } else { + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + if ($id3v2_majorversion == 2) { + $parsedFrame['imagetype'] = $frame_imagetype; + } else { + $parsedFrame['mime'] = $frame_mimetype; + } + $parsedFrame['picturetypeid'] = $frame_picturetype; + $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['datalength'] = strlen($parsedFrame['data']); + + $parsedFrame['image_mime'] = ''; + $imageinfo = array(); + $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo); + if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { + $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); + if ($imagechunkcheck[0]) { + $parsedFrame['image_width'] = $imagechunkcheck[0]; + } + if ($imagechunkcheck[1]) { + $parsedFrame['image_height'] = $imagechunkcheck[1]; + } + } + + do { + if ($this->getid3->option_save_attachments === false) { + // skip entirely + unset($parsedFrame['data']); + break; + } + 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 + $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)'; + unset($parsedFrame['data']); + break; + } +*/ + } elseif (is_string($this->getid3->option_save_attachments)) { + $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($dir) || !is_writable($dir)) { + // cannot write, skip + $info['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) || is_writable($destination_filename)) { + file_put_contents($destination_filename, $parsedFrame['data']); + } else { + $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)'; + } + $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(); + } + $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']); + } + } + } while (false); + } + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object + // There may be more than one 'GEOB' frame in each tag, + // but only one with the same content descriptor + //
+ // Text encoding $xx + // MIME type $00 + // Filename $00 (00) + // Content description $00 (00) + // Encapsulated object + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $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'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_filename) === 0) { + $frame_filename = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $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; + $parsedFrame['description'] = $frame_description; + 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 + //
+ // 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 + //
+ // Email to user $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 + //
+ // 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' + //
+ // Owner identifier $00 (00) + // Content/explanation $00 (00) + // Encrypted datablock + + $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); + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['description'] = $frame_description; + 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' + //
+ // Owner identifier $00 + // Preview start $xx xx + // Preview length $xx xx + // Encryption info + + $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 + //
+ // ID3v2.3+ => Frame identifier $xx xx xx xx + // ID3v2.2 => Frame identifier $xx xx xx + // URL $00 + // ID and additional data + + $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']][] = utf8_encode($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 + // + // 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' + //
+ // Text encoding $xx + // Language $xx xx xx + // The actual text + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $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); + 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 + //
+ // Text encoding $xx + // Price paid $00 + // Date of purch. + // Seller + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $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); + 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 + //
+ // Text encoding $xx + // Price string $00 + // Valid until + // Contact URL $00 + // Received as $xx + // Name of seller $00 (00) + // Description $00 (00) + // Picture MIME type $00 + // Seller logo + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + + $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'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_sellername) === 0) { + $frame_sellername = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $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['description'] = $frame_description; + $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 + //
+ // Owner identifier $00 + // Method symbol $xx + // Encryption 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 + //
+ // Owner identifier $00 + // Group symbol $xx + // Group dependent 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 + //
+ // Owner identifier $00 + // The private 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 + //
+ // Group symbol $xx + // Signature + + $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 + //
+ // 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 + //
+ // 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 < $frame_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 + //
+ // Peak Amplitude $xx $xx $xx $xx + // Radio Replay Gain Adjustment %aaabbbcd %dddddddd + // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd + // a - name code + // b - originator code + // c - sign bit + // d - replay gain adjustment + + $frame_offset = 0; + $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3)); + $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3)); + $parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1)); + $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9)); + $parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3)); + $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3)); + $parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1)); + $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9)); + $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']); + $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']); + $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']); + $parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']); + $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']); + $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']); + + $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude']; + $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; + $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; + $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; + $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; + + unset($parsedFrame['data']); + + } + + return true; + } + + + public function DeUnsynchronise($data) { + return str_replace("\xFF\x00", "\xFF", $data); + } + + 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] : ''); + } + + 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] : ''); + } + + 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] : ''); + } + + 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] : ''); + } + + 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] : ''); + } + + 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 + 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'); + } + + + 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 + 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'); + } + + + + 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'); + } + + + 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] : ''); + } + + 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] : ''); + } + + 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] : ''); + } + + 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] : ''); + } + + 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] : ''); + } + + 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 Unsychronised 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 + } + + + 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 unsychronised_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'); + } + + 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] : ''); + } + + 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'); + } + + public static function IsValidID3v2FrameName($framename, $id3v2majorversion) { + switch ($id3v2majorversion) { + case 2: + return preg_match('#[A-Z][A-Z0-9]{2}#', $framename); + break; + + case 3: + case 4: + return preg_match('#[A-Z][A-Z0-9]{3}#', $framename); + break; + } + return false; + } + + public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { + for ($i = 0; $i < strlen($numberstring); $i++) { + if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) { + if (($numberstring{$i} == '.') && $allowdecimal) { + // allowed + } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) { + // allowed + } else { + return false; + } + } + } + return true; + } + + public static function IsValidDateStampString($datestamp) { + if (strlen($datestamp) != 8) { + return false; + } + if (!self::IsANumber($datestamp, false)) { + 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; + } + + public static function ID3v2HeaderLength($majorversion) { + return (($majorversion == 2) ? 6 : 10); + } + +} +