3 /////////////////////////////////////////////////////////////////
4 /// getID3() by James Heinrich <info@getid3.org> //
5 // available at https://github.com/JamesHeinrich/getID3 //
6 // or https://www.getid3.org //
7 // or http://getid3.sourceforge.net //
8 // see readme.txt for more details //
9 /////////////////////////////////////////////////////////////////
11 // module.tag.id3v2.php //
12 // module for analyzing ID3v2 tags //
13 // dependencies: module.tag.id3v1.php //
15 /////////////////////////////////////////////////////////////////
17 getid3_lib
::IncludeDependency(GETID3_INCLUDEPATH
.'module.tag.id3v1.php', __FILE__
, true);
19 class getid3_id3v2
extends getid3_handler
21 public $StartingOffset = 0;
26 public function Analyze() {
27 $info = &$this->getid3
->info
;
29 // Overall tag structure:
30 // +-----------------------------+
31 // | Header (10 bytes) |
32 // +-----------------------------+
33 // | Extended Header |
34 // | (variable length, OPTIONAL) |
35 // +-----------------------------+
36 // | Frames (variable length) |
37 // +-----------------------------+
39 // | (variable length, OPTIONAL) |
40 // +-----------------------------+
41 // | Footer (10 bytes, OPTIONAL) |
42 // +-----------------------------+
45 // ID3v2/file identifier "ID3"
46 // ID3v2 version $04 00
47 // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
48 // ID3v2 size 4 * %0xxxxxxx
52 $info['id3v2']['header'] = true;
53 $thisfile_id3v2 = &$info['id3v2'];
54 $thisfile_id3v2['flags'] = array();
55 $thisfile_id3v2_flags = &$thisfile_id3v2['flags'];
58 $this->fseek($this->StartingOffset
);
59 $header = $this->fread(10);
60 if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) {
62 $thisfile_id3v2['majorversion'] = ord($header{3});
63 $thisfile_id3v2['minorversion'] = ord($header{4});
66 $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
70 unset($info['id3v2']);
75 if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
77 $this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
82 $id3_flags = ord($header{5});
83 switch ($id3v2_majorversion) {
86 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
87 $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
92 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
93 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
94 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
99 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
100 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
101 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
102 $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present
106 $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
108 $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset
;
109 $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] +
$thisfile_id3v2['headerlength'];
113 // create 'encoding' key - used by getid3::HandleAllTags()
114 // in ID3v2 every field can have it's own encoding type
115 // so force everything to UTF-8 so it can be handled consistantly
116 $thisfile_id3v2['encoding'] = 'UTF-8';
121 // All ID3v2 frames consists of one frame header followed by one or more
122 // fields containing the actual information. The header is always 10
123 // bytes and laid out as follows:
125 // Frame ID $xx xx xx xx (four characters)
126 // Size 4 * %0xxxxxxx
129 $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
130 if (!empty($thisfile_id3v2['exthead']['length'])) {
131 $sizeofframes -= ($thisfile_id3v2['exthead']['length'] +
4);
133 if (!empty($thisfile_id3v2_flags['isfooter'])) {
134 $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
136 if ($sizeofframes > 0) {
138 $framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
140 // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
141 if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
142 $framedata = $this->DeUnsynchronise($framedata);
144 // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
145 // of on tag level, making it easier to skip frames, increasing the streamability
146 // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
147 // there exists an unsynchronised frame, while the new unsynchronisation flag in
148 // the frame header [S:4.1.2] indicates unsynchronisation.
151 //$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)
152 $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
156 if (!empty($thisfile_id3v2_flags['exthead'])) {
157 $extended_header_offset = 0;
159 if ($id3v2_majorversion == 3) {
162 //Extended header size $xx xx xx xx // 32-bit integer
163 //Extended Flags $xx xx
164 // %x0000000 %00000000 // v2.3
165 // x - CRC data present
166 //Size of padding $xx xx xx xx
168 $thisfile_id3v2['exthead']['length'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
169 $extended_header_offset +
= 4;
171 $thisfile_id3v2['exthead']['flag_bytes'] = 2;
172 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
173 $extended_header_offset +
= $thisfile_id3v2['exthead']['flag_bytes'];
175 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
177 $thisfile_id3v2['exthead']['padding_size'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
178 $extended_header_offset +
= 4;
180 if ($thisfile_id3v2['exthead']['flags']['crc']) {
181 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
182 $extended_header_offset +
= 4;
184 $extended_header_offset +
= $thisfile_id3v2['exthead']['padding_size'];
186 } elseif ($id3v2_majorversion == 4) {
189 //Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer
190 //Number of flag bytes $01
193 // b - Tag is an update
194 // Flag data length $00
195 // c - CRC data present
196 // Flag data length $05
197 // Total frame CRC 5 * %0xxxxxxx
198 // d - Tag restrictions
199 // Flag data length $01
201 $thisfile_id3v2['exthead']['length'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
202 $extended_header_offset +
= 4;
204 $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
205 $extended_header_offset +
= 1;
207 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
208 $extended_header_offset +
= $thisfile_id3v2['exthead']['flag_bytes'];
210 $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
211 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
212 $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
214 if ($thisfile_id3v2['exthead']['flags']['update']) {
215 $ext_header_chunk_length = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
216 $extended_header_offset +
= 1;
219 if ($thisfile_id3v2['exthead']['flags']['crc']) {
220 $ext_header_chunk_length = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
221 $extended_header_offset +
= 1;
222 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
223 $extended_header_offset +
= $ext_header_chunk_length;
226 if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
227 $ext_header_chunk_length = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
228 $extended_header_offset +
= 1;
231 $restrictions_raw = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
232 $extended_header_offset +
= 1;
233 $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
234 $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
235 $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
236 $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
237 $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
239 $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
240 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
241 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
242 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
243 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
246 if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
247 $this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
251 $framedataoffset +
= $extended_header_offset;
252 $framedata = substr($framedata, $extended_header_offset);
253 } // end extended header
256 while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
257 if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
258 // insufficient room left in ID3v2 header for actual data - must be padding
259 $thisfile_id3v2['padding']['start'] = $framedataoffset;
260 $thisfile_id3v2['padding']['length'] = strlen($framedata);
261 $thisfile_id3v2['padding']['valid'] = true;
262 for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++
) {
263 if ($framedata{$i} != "\x00") {
264 $thisfile_id3v2['padding']['valid'] = false;
265 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] +
$i;
266 $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
270 break; // skip rest of ID3v2 header
272 $frame_header = null;
276 if ($id3v2_majorversion == 2) {
277 // Frame ID $xx xx xx (three characters)
278 // Size $xx xx xx (24-bit integer)
281 $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
282 $framedata = substr($framedata, 6); // and leave the rest in $framedata
283 $frame_name = substr($frame_header, 0, 3);
284 $frame_size = getid3_lib
::BigEndian2Int(substr($frame_header, 3, 3), 0);
285 $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
287 } elseif ($id3v2_majorversion > 2) {
289 // Frame ID $xx xx xx xx (four characters)
290 // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
293 $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
294 $framedata = substr($framedata, 10); // and leave the rest in $framedata
296 $frame_name = substr($frame_header, 0, 4);
297 if ($id3v2_majorversion == 3) {
298 $frame_size = getid3_lib
::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
300 $frame_size = getid3_lib
::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
303 if ($frame_size < (strlen($framedata) +
4)) {
304 $nextFrameID = substr($framedata, $frame_size, 4);
305 if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
307 } elseif (($frame_name == "\x00".'MP3') ||
($frame_name == "\x00\x00".'MP') ||
($frame_name == ' MP3') ||
($frame_name == 'MP3e')) {
308 // MP3ext known broken frames - "ok" for the purposes of this test
309 } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib
::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
310 $this->warning('ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3');
311 $id3v2_majorversion = 3;
312 $frame_size = getid3_lib
::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
317 $frame_flags = getid3_lib
::BigEndian2Int(substr($frame_header, 8, 2));
320 if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) ||
($frame_name == "\x00\x00\x00\x00")) {
321 // padding encountered
323 $thisfile_id3v2['padding']['start'] = $framedataoffset;
324 $thisfile_id3v2['padding']['length'] = strlen($frame_header) +
strlen($framedata);
325 $thisfile_id3v2['padding']['valid'] = true;
327 $len = strlen($framedata);
328 for ($i = 0; $i < $len; $i++
) {
329 if ($framedata{$i} != "\x00") {
330 $thisfile_id3v2['padding']['valid'] = false;
331 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] +
$i;
332 $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
336 break; // skip rest of ID3v2 header
339 if ($iTunesBrokenFrameNameFixed = self
::ID3v22iTunesBrokenFrameName($frame_name)) {
340 $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
341 $frame_name = $iTunesBrokenFrameNameFixed;
343 if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
346 $parsedFrame['frame_name'] = $frame_name;
347 $parsedFrame['frame_flags_raw'] = $frame_flags;
348 $parsedFrame['data'] = substr($framedata, 0, $frame_size);
349 $parsedFrame['datalength'] = getid3_lib
::CastAsInt($frame_size);
350 $parsedFrame['dataoffset'] = $framedataoffset;
352 $this->ParseID3v2Frame($parsedFrame);
353 $thisfile_id3v2[$frame_name][] = $parsedFrame;
355 $framedata = substr($framedata, $frame_size);
357 } else { // invalid frame length or FrameID
359 if ($frame_size <= strlen($framedata)) {
361 if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
363 // next frame is valid, just skip the current frame
364 $framedata = substr($framedata, $frame_size);
365 $this->warning('Next ID3v2 frame is valid, skipping current frame.');
369 // next frame is invalid too, abort processing
372 $this->error('Next ID3v2 frame is also invalid, aborting processing.');
376 } elseif ($frame_size == strlen($framedata)) {
378 // this is the last frame, just skip
379 $this->warning('This was the last ID3v2 frame.');
383 // next frame is invalid too, abort processing
386 $this->warning('Invalid ID3v2 frame size, aborting.');
389 if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
391 switch ($frame_name) {
392 case "\x00\x00".'MP':
399 $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]');
403 $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
407 } elseif (!isset($framedata) ||
($frame_size > strlen($framedata))) {
409 $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ?
strlen($framedata) : 'null').')).');
413 $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');
418 $framedataoffset +
= ($frame_size +
$this->ID3v2HeaderLength($id3v2_majorversion));
427 // The footer is a copy of the header, but with a different identifier.
428 // ID3v2 identifier "3DI"
429 // ID3v2 version $04 00
430 // ID3v2 flags %abcd0000
431 // ID3v2 size 4 * %0xxxxxxx
433 if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
434 $footer = $this->fread(10);
435 if (substr($footer, 0, 3) == '3DI') {
436 $thisfile_id3v2['footer'] = true;
437 $thisfile_id3v2['majorversion_footer'] = ord($footer{3});
438 $thisfile_id3v2['minorversion_footer'] = ord($footer{4});
440 if ($thisfile_id3v2['majorversion_footer'] <= 4) {
441 $id3_flags = ord($footer{5});
442 $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80);
443 $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40);
444 $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20);
445 $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
447 $thisfile_id3v2['footerlength'] = getid3_lib
::BigEndian2Int(substr($footer, 6, 4), 1);
451 if (isset($thisfile_id3v2['comments']['genre'])) {
453 foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
454 foreach ($this->ParseID3v2GenreString($value) as $genre) {
458 $thisfile_id3v2['comments']['genre'] = array_unique($genres);
459 unset($key, $value, $genres, $genre);
462 if (isset($thisfile_id3v2['comments']['track'])) {
463 foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
464 if (strstr($value, '/')) {
465 list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
470 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)) {
471 $thisfile_id3v2['comments']['year'] = array($matches[1]);
475 if (!empty($thisfile_id3v2['TXXX'])) {
476 // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
477 foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
478 switch ($txxx_array['description']) {
479 case 'replaygain_track_gain':
480 if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
481 $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
484 case 'replaygain_track_peak':
485 if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
486 $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
489 case 'replaygain_album_gain':
490 if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
491 $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
500 $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
501 if (isset($thisfile_id3v2['footer'])) {
502 $info['avdataoffset'] +
= 10;
509 * @param string $genrestring
513 public function ParseID3v2GenreString($genrestring) {
514 // Parse genres into arrays of genreName and genreID
515 // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
516 // ID3v2.4.x: '21' $00 'Eurodisco' $00
517 $clean_genres = array();
519 // hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
520 if (($this->getid3
->info
['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
521 // note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
522 // replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
523 if (preg_match('#/#', $genrestring)) {
524 $genrestring = str_replace('/', "\x00", $genrestring);
525 $genrestring = str_replace('Pop'."\x00".'Funk', 'Pop/Funk', $genrestring);
526 $genrestring = str_replace('Rock'."\x00".'Rock', 'Folk/Rock', $genrestring);
529 // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
530 if (preg_match('#;#', $genrestring)) {
531 $genrestring = str_replace(';', "\x00", $genrestring);
536 if (strpos($genrestring, "\x00") === false) {
537 $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
540 $genre_elements = explode("\x00", $genrestring);
541 foreach ($genre_elements as $element) {
542 $element = trim($element);
544 if (preg_match('#^[0-9]{1,3}$#', $element)) {
545 $clean_genres[] = getid3_id3v1
::LookupGenreName($element);
547 $clean_genres[] = str_replace('((', '(', $element);
551 return $clean_genres;
555 * @param array $parsedFrame
559 public function ParseID3v2Frame(&$parsedFrame) {
562 $info = &$this->getid3
->info
;
563 $id3v2_majorversion = $info['id3v2']['majorversion'];
565 $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']);
566 if (empty($parsedFrame['framenamelong'])) {
567 unset($parsedFrame['framenamelong']);
569 $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
570 if (empty($parsedFrame['framenameshort'])) {
571 unset($parsedFrame['framenameshort']);
574 if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
575 if ($id3v2_majorversion == 3) {
576 // Frame Header Flags
577 // %abc00000 %ijk00000
578 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
579 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
580 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
581 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
582 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
583 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
585 } elseif ($id3v2_majorversion == 4) {
586 // Frame Header Flags
587 // %0abc0000 %0h00kmnp
588 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
589 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
590 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
591 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
592 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
593 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
594 $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
595 $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
597 // Frame-level de-unsynchronisation - ID3v2.4
598 if ($parsedFrame['flags']['Unsynchronisation']) {
599 $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
602 if ($parsedFrame['flags']['DataLengthIndicator']) {
603 $parsedFrame['data_length_indicator'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
604 $parsedFrame['data'] = substr($parsedFrame['data'], 4);
608 // Frame-level de-compression
609 if ($parsedFrame['flags']['compression']) {
610 $parsedFrame['decompressed_size'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
611 if (!function_exists('gzuncompress')) {
612 $this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
614 if ($decompresseddata = @gzuncompress
(substr($parsedFrame['data'], 4))) {
615 //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
616 $parsedFrame['data'] = $decompresseddata;
617 unset($decompresseddata);
619 $this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
625 if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
626 if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
627 $this->warning('ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data');
631 if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
633 $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
634 switch ($parsedFrame['frame_name']) {
636 $warning .= ' (this is known to happen with files tagged by RioPort)';
642 $this->warning($warning);
644 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) ||
// 4.1 UFID Unique file identifier
645 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier
646 // There may be more than one 'UFID' frame in a tag,
647 // but only one with the same 'Owner identifier'.
648 // <Header for 'Unique file identifier', ID: 'UFID'>
649 // Owner identifier <text string> $00
650 // Identifier <up to 64 bytes binary data>
651 $exploded = explode("\x00", $parsedFrame['data'], 2);
652 $parsedFrame['ownerid'] = (isset($exploded[0]) ?
$exploded[0] : '');
653 $parsedFrame['data'] = (isset($exploded[1]) ?
$exploded[1] : '');
655 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) ||
// 4.2.2 TXXX User defined text information frame
656 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame
657 // There may be more than one 'TXXX' frame in each tag,
658 // but only one with the same description.
659 // <Header for 'User defined text information frame', ID: 'TXXX'>
661 // Description <text string according to encoding> $00 (00)
662 // Value <text string according to encoding>
665 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
666 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
667 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
668 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
669 $frame_textencoding_terminator = "\x00";
671 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
672 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
673 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
675 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
676 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
677 // if description only contains a BOM or terminator then make it blank
678 $frame_description = '';
680 $parsedFrame['encodingid'] = $frame_textencoding;
681 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
683 $parsedFrame['description'] = trim(getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description));
684 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator));
685 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
686 $commentkey = ($parsedFrame['description'] ?
$parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ?
count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
687 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ||
!array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
688 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
690 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
693 //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
696 } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
697 // There may only be one text information frame of its kind in an tag.
698 // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
699 // excluding 'TXXX' described in 4.2.6.>
701 // Information <text string(s) according to encoding>
704 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
705 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
706 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
709 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
711 $parsedFrame['encodingid'] = $frame_textencoding;
712 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
714 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
715 // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
716 // This of course breaks when an artist name contains slash character, e.g. "AC/DC"
717 // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
718 // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
719 switch ($parsedFrame['encoding']) {
731 $Txxx_elements = array();
732 $Txxx_elements_start_offset = 0;
733 for ($i = 0; $i < strlen($parsedFrame['data']); $i +
= $wordsize) {
734 if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
735 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
736 $Txxx_elements_start_offset = $i +
$wordsize;
739 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
740 foreach ($Txxx_elements as $Txxx_element) {
741 $string = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
742 if (!empty($string)) {
743 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
746 unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
749 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) ||
// 4.3.2 WXXX User defined URL link frame
750 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame
751 // There may be more than one 'WXXX' frame in each tag,
752 // but only one with the same description
753 // <Header for 'User defined URL link frame', ID: 'WXXX'>
755 // Description <text string according to encoding> $00 (00)
759 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
760 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
761 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
762 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
763 $frame_textencoding_terminator = "\x00";
765 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
766 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
767 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
769 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
770 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
771 // if description only contains a BOM or terminator then make it blank
772 $frame_description = '';
774 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator));
776 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator);
777 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
778 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
780 if ($frame_terminatorpos) {
781 // there are null bytes after the data - this is not according to spec
782 // only use data up to first null byte
783 $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
785 // no null bytes following data, just use all data
786 $frame_urldata = (string) $parsedFrame['data'];
789 $parsedFrame['encodingid'] = $frame_textencoding;
790 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
792 $parsedFrame['url'] = $frame_urldata; // always ISO-8859-1
793 $parsedFrame['description'] = $frame_description; // according to the frame text encoding
794 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
795 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
797 unset($parsedFrame['data']);
800 } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
801 // There may only be one URL link frame of its kind in a tag,
802 // except when stated otherwise in the frame description
803 // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
804 // described in 4.3.2.>
807 $parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1
808 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
809 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
811 unset($parsedFrame['data']);
814 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) ||
// 4.4 IPLS Involved people list (ID3v2.3 only)
815 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
816 // http://id3.org/id3v2.3.0#sec4.4
817 // There may only be one 'IPL' frame in each tag
818 // <Header for 'User defined URL link frame', ID: 'IPL'>
820 // People list strings <textstrings>
823 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
824 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
825 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
827 $parsedFrame['encodingid'] = $frame_textencoding;
828 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
829 $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset);
831 // https://www.getid3.org/phpBB3/viewtopic.php?t=1369
832 // "this tag typically contains null terminated strings, which are associated in pairs"
833 // "there are users that use the tag incorrectly"
834 $IPLS_parts = array();
835 if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
836 $IPLS_parts_unsorted = array();
837 if (((strlen($parsedFrame['data_raw']) %
2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") ||
(substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
838 // 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
840 for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i +
= 2) {
841 $twobytes = substr($parsedFrame['data_raw'], $i, 2);
842 if ($twobytes === "\x00\x00") {
843 $IPLS_parts_unsorted[] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
846 $thisILPS .= $twobytes;
849 if (strlen($thisILPS) > 2) { // 2-byte BOM
850 $IPLS_parts_unsorted[] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
853 // ISO-8859-1 or UTF-8 or other single-byte-null character set
854 $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
856 if (count($IPLS_parts_unsorted) == 1) {
857 // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
858 foreach ($IPLS_parts_unsorted as $key => $value) {
859 $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
861 foreach ($IPLS_parts_sorted as $person) {
862 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
865 } elseif ((count($IPLS_parts_unsorted) %
2) == 0) {
868 foreach ($IPLS_parts_unsorted as $key => $value) {
869 if (($key %
2) == 0) {
873 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
879 foreach ($IPLS_parts_unsorted as $key => $value) {
880 $IPLS_parts[] = array($value);
885 $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
887 $parsedFrame['data'] = $IPLS_parts;
889 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
890 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
894 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) ||
// 4.4 MCDI Music CD identifier
895 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
896 // There may only be one 'MCDI' frame in each tag
897 // <Header for 'Music CD identifier', ID: 'MCDI'>
898 // CD TOC <binary data>
900 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
901 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
905 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) ||
// 4.5 ETCO Event timing codes
906 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes
907 // There may only be one 'ETCO' frame in each tag
908 // <Header for 'Event timing codes', ID: 'ETCO'>
909 // Time stamp format $xx
910 // Where time stamp format is:
911 // $01 (32-bit value) MPEG frames from beginning of file
912 // $02 (32-bit value) milliseconds from beginning of file
913 // Followed by a list of key events in the following format:
915 // Time stamp $xx (xx ...)
916 // The 'Time stamp' is set to zero if directly at the beginning of the sound
917 // or after the previous event. All events MUST be sorted in chronological order.
920 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
922 while ($frame_offset < strlen($parsedFrame['data'])) {
923 $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++
, 1);
924 $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']);
925 $parsedFrame['timestamp'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
928 unset($parsedFrame['data']);
931 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) ||
// 4.6 MLLT MPEG location lookup table
932 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table
933 // There may only be one 'MLLT' frame in each tag
934 // <Header for 'Location lookup table', ID: 'MLLT'>
935 // MPEG frames between reference $xx xx
936 // Bytes between reference $xx xx xx
937 // Milliseconds between reference $xx xx xx
938 // Bits for bytes deviation $xx
939 // Bits for milliseconds dev. $xx
940 // Then for every reference the following data is included;
941 // Deviation in bytes %xxx....
942 // Deviation in milliseconds %xxx....
945 $parsedFrame['framesbetweenreferences'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
946 $parsedFrame['bytesbetweenreferences'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
947 $parsedFrame['msbetweenreferences'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
948 $parsedFrame['bitsforbytesdeviation'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
949 $parsedFrame['bitsformsdeviation'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
950 $parsedFrame['data'] = substr($parsedFrame['data'], 10);
951 $deviationbitstream = '';
952 while ($frame_offset < strlen($parsedFrame['data'])) {
953 $deviationbitstream .= getid3_lib
::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++
, 1));
955 $reference_counter = 0;
956 while (strlen($deviationbitstream) > 0) {
957 $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
958 $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
959 $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] +
$parsedFrame['bitsformsdeviation']);
960 $reference_counter++
;
962 unset($parsedFrame['data']);
965 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) ||
// 4.7 SYTC Synchronised tempo codes
966 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes
967 // There may only be one 'SYTC' frame in each tag
968 // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
969 // Time stamp format $xx
970 // Tempo data <binary data>
971 // Where time stamp format is:
972 // $01 (32-bit value) MPEG frames from beginning of file
973 // $02 (32-bit value) milliseconds from beginning of file
976 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
977 $timestamp_counter = 0;
978 while ($frame_offset < strlen($parsedFrame['data'])) {
979 $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
980 if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
981 $parsedFrame[$timestamp_counter]['tempo'] +
= ord(substr($parsedFrame['data'], $frame_offset++
, 1));
983 $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
985 $timestamp_counter++
;
987 unset($parsedFrame['data']);
990 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) ||
// 4.8 USLT Unsynchronised lyric/text transcription
991 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription
992 // There may be more than one 'Unsynchronised lyrics/text transcription' frame
993 // in each tag, but only one with the same language and content descriptor.
994 // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
996 // Language $xx xx xx
997 // Content descriptor <text string according to encoding> $00 (00)
998 // Lyrics/text <full text string according to encoding>
1001 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1002 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1003 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1004 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1005 $frame_textencoding_terminator = "\x00";
1007 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1009 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1010 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1011 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1013 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1014 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1015 // if description only contains a BOM or terminator then make it blank
1016 $frame_description = '';
1018 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator));
1020 $parsedFrame['encodingid'] = $frame_textencoding;
1021 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1023 $parsedFrame['language'] = $frame_language;
1024 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1025 $parsedFrame['description'] = $frame_description;
1026 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1027 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1029 unset($parsedFrame['data']);
1032 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) ||
// 4.9 SYLT Synchronised lyric/text
1033 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text
1034 // There may be more than one 'SYLT' frame in each tag,
1035 // but only one with the same language and content descriptor.
1036 // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
1037 // Text encoding $xx
1038 // Language $xx xx xx
1039 // Time stamp format $xx
1040 // $01 (32-bit value) MPEG frames from beginning of file
1041 // $02 (32-bit value) milliseconds from beginning of file
1043 // Content descriptor <text string according to encoding> $00 (00)
1044 // Terminated text to be synced (typically a syllable)
1045 // Sync identifier (terminator to above string) $00 (00)
1046 // Time stamp $xx (xx ...)
1049 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1050 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1051 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1052 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1053 $frame_textencoding_terminator = "\x00";
1055 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1057 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1058 $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1059 $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1060 $parsedFrame['encodingid'] = $frame_textencoding;
1061 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1063 $parsedFrame['language'] = $frame_language;
1064 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1066 $timestampindex = 0;
1067 $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1068 while (strlen($frame_remainingdata)) {
1070 $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
1071 if ($frame_terminatorpos === false) {
1072 $frame_remainingdata = '';
1074 if (ord(substr($frame_remainingdata, $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1075 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1077 $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1079 $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos +
strlen($frame_textencoding_terminator));
1080 if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
1081 // timestamp probably omitted for first data item
1083 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1084 $frame_remainingdata = substr($frame_remainingdata, 4);
1089 unset($parsedFrame['data']);
1092 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) ||
// 4.10 COMM Comments
1093 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments
1094 // There may be more than one comment frame in each tag,
1095 // but only one with the same language and content descriptor.
1096 // <Header for 'Comment', ID: 'COMM'>
1097 // Text encoding $xx
1098 // Language $xx xx xx
1099 // Short content descrip. <text string according to encoding> $00 (00)
1100 // The actual text <full text string according to encoding>
1102 if (strlen($parsedFrame['data']) < 5) {
1104 $this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
1109 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1110 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1111 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1112 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1113 $frame_textencoding_terminator = "\x00";
1115 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1117 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1118 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1119 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1121 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1122 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1123 // if description only contains a BOM or terminator then make it blank
1124 $frame_description = '';
1126 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator));
1128 $parsedFrame['encodingid'] = $frame_textencoding;
1129 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1131 $parsedFrame['language'] = $frame_language;
1132 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1133 $parsedFrame['description'] = $frame_description;
1134 $parsedFrame['data'] = $frame_text;
1135 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1136 $commentkey = ($parsedFrame['description'] ?
$parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ?
count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
1137 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ||
!array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
1138 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1140 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1146 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1147 // There may be more than one 'RVA2' frame in each tag,
1148 // but only one with the same identification string
1149 // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1150 // Identification <text string> $00
1151 // The 'identification' string is used to identify the situation and/or
1152 // device where this adjustment should apply. The following is then
1153 // repeated for every channel:
1154 // Type of channel $xx
1155 // Volume adjustment $xx xx
1156 // Bits representing peak $xx
1157 // Peak volume $xx (xx ...)
1159 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1160 $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1161 if (ord($frame_idstring) === 0) {
1162 $frame_idstring = '';
1164 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos +
strlen("\x00"));
1165 $parsedFrame['description'] = $frame_idstring;
1166 $RVA2channelcounter = 0;
1167 while (strlen($frame_remainingdata) >= 5) {
1169 $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++
, 1));
1170 $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid;
1171 $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1172 $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1174 $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++
, 1));
1175 if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) ||
($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1176 $this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
1179 $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1180 $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1181 $frame_remainingdata = substr($frame_remainingdata, $frame_offset +
$frame_bytespeakvolume);
1182 $RVA2channelcounter++
;
1184 unset($parsedFrame['data']);
1187 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) ||
// 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
1188 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only)
1189 // There may only be one 'RVA' frame in each tag
1190 // <Header for 'Relative volume adjustment', ID: 'RVA'>
1191 // ID3v2.2 => Increment/decrement %000000ba
1192 // ID3v2.3 => Increment/decrement %00fedcba
1193 // Bits used for volume descr. $xx
1194 // Relative volume change, right $xx xx (xx ...) // a
1195 // Relative volume change, left $xx xx (xx ...) // b
1196 // Peak volume right $xx xx (xx ...)
1197 // Peak volume left $xx xx (xx ...)
1198 // ID3v2.3 only, optional (not present in ID3v2.2):
1199 // Relative volume change, right back $xx xx (xx ...) // c
1200 // Relative volume change, left back $xx xx (xx ...) // d
1201 // Peak volume right back $xx xx (xx ...)
1202 // Peak volume left back $xx xx (xx ...)
1203 // ID3v2.3 only, optional (not present in ID3v2.2):
1204 // Relative volume change, center $xx xx (xx ...) // e
1205 // Peak volume center $xx xx (xx ...)
1206 // ID3v2.3 only, optional (not present in ID3v2.2):
1207 // Relative volume change, bass $xx xx (xx ...) // f
1208 // Peak volume bass $xx xx (xx ...)
1211 $frame_incrdecrflags = getid3_lib
::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++
, 1));
1212 $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1213 $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1);
1214 $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1215 $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1216 $parsedFrame['volumechange']['right'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1217 if ($parsedFrame['incdec']['right'] === false) {
1218 $parsedFrame['volumechange']['right'] *= -1;
1220 $frame_offset +
= $frame_bytesvolume;
1221 $parsedFrame['volumechange']['left'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1222 if ($parsedFrame['incdec']['left'] === false) {
1223 $parsedFrame['volumechange']['left'] *= -1;
1225 $frame_offset +
= $frame_bytesvolume;
1226 $parsedFrame['peakvolume']['right'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1227 $frame_offset +
= $frame_bytesvolume;
1228 $parsedFrame['peakvolume']['left'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1229 $frame_offset +
= $frame_bytesvolume;
1230 if ($id3v2_majorversion == 3) {
1231 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1232 if (strlen($parsedFrame['data']) > 0) {
1233 $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1234 $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1);
1235 $parsedFrame['volumechange']['rightrear'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1236 if ($parsedFrame['incdec']['rightrear'] === false) {
1237 $parsedFrame['volumechange']['rightrear'] *= -1;
1239 $frame_offset +
= $frame_bytesvolume;
1240 $parsedFrame['volumechange']['leftrear'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1241 if ($parsedFrame['incdec']['leftrear'] === false) {
1242 $parsedFrame['volumechange']['leftrear'] *= -1;
1244 $frame_offset +
= $frame_bytesvolume;
1245 $parsedFrame['peakvolume']['rightrear'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1246 $frame_offset +
= $frame_bytesvolume;
1247 $parsedFrame['peakvolume']['leftrear'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1248 $frame_offset +
= $frame_bytesvolume;
1250 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1251 if (strlen($parsedFrame['data']) > 0) {
1252 $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1253 $parsedFrame['volumechange']['center'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1254 if ($parsedFrame['incdec']['center'] === false) {
1255 $parsedFrame['volumechange']['center'] *= -1;
1257 $frame_offset +
= $frame_bytesvolume;
1258 $parsedFrame['peakvolume']['center'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1259 $frame_offset +
= $frame_bytesvolume;
1261 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1262 if (strlen($parsedFrame['data']) > 0) {
1263 $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1264 $parsedFrame['volumechange']['bass'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1265 if ($parsedFrame['incdec']['bass'] === false) {
1266 $parsedFrame['volumechange']['bass'] *= -1;
1268 $frame_offset +
= $frame_bytesvolume;
1269 $parsedFrame['peakvolume']['bass'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1270 $frame_offset +
= $frame_bytesvolume;
1273 unset($parsedFrame['data']);
1276 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
1277 // There may be more than one 'EQU2' frame in each tag,
1278 // but only one with the same identification string
1279 // <Header of 'Equalisation (2)', ID: 'EQU2'>
1280 // Interpolation method $xx
1283 // Identification <text string> $00
1284 // The following is then repeated for every adjustment point
1286 // Volume adjustment $xx xx
1289 $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1290 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1291 $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1292 if (ord($frame_idstring) === 0) {
1293 $frame_idstring = '';
1295 $parsedFrame['description'] = $frame_idstring;
1296 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos +
strlen("\x00"));
1297 while (strlen($frame_remainingdata)) {
1298 $frame_frequency = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1299 $parsedFrame['data'][$frame_frequency] = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1300 $frame_remainingdata = substr($frame_remainingdata, 4);
1302 $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1303 unset($parsedFrame['data']);
1306 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) ||
// 4.12 EQUA Equalisation (ID3v2.3 only)
1307 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only)
1308 // There may only be one 'EQUA' frame in each tag
1309 // <Header for 'Relative volume adjustment', ID: 'EQU'>
1310 // Adjustment bits $xx
1311 // This is followed by 2 bytes + ('adjustment bits' rounded up to the
1312 // nearest byte) for every equalisation band in the following format,
1313 // giving a frequency range of 0 - 32767Hz:
1314 // Increment/decrement %x (MSB of the Frequency)
1315 // Frequency (lower 15 bits)
1316 // Adjustment $xx (xx ...)
1319 $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++
, 1);
1320 $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1322 $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1323 while (strlen($frame_remainingdata) > 0) {
1324 $frame_frequencystr = getid3_lib
::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1325 $frame_incdec = (bool) substr($frame_frequencystr, 0, 1);
1326 $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1327 $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1328 $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1329 if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1330 $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1332 $frame_remainingdata = substr($frame_remainingdata, 2 +
$frame_adjustmentbytes);
1334 unset($parsedFrame['data']);
1337 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) ||
// 4.13 RVRB Reverb
1338 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb
1339 // There may only be one 'RVRB' frame in each tag.
1340 // <Header for 'Reverb', ID: 'RVRB'>
1341 // Reverb left (ms) $xx xx
1342 // Reverb right (ms) $xx xx
1343 // Reverb bounces, left $xx
1344 // Reverb bounces, right $xx
1345 // Reverb feedback, left to left $xx
1346 // Reverb feedback, left to right $xx
1347 // Reverb feedback, right to right $xx
1348 // Reverb feedback, right to left $xx
1349 // Premix left to right $xx
1350 // Premix right to left $xx
1353 $parsedFrame['left'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1355 $parsedFrame['right'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1357 $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1358 $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1359 $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1360 $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1361 $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1362 $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1363 $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1364 $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1365 unset($parsedFrame['data']);
1368 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) ||
// 4.14 APIC Attached picture
1369 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture
1370 // There may be several pictures attached to one file,
1371 // each in their individual 'APIC' frame, but only one
1372 // with the same content descriptor
1373 // <Header for 'Attached picture', ID: 'APIC'>
1374 // Text encoding $xx
1375 // ID3v2.3+ => MIME type <text string> $00
1376 // ID3v2.2 => Image format $xx xx xx
1378 // Description <text string according to encoding> $00 (00)
1379 // Picture data <binary data>
1382 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1383 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1384 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1385 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1386 $frame_textencoding_terminator = "\x00";
1389 if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1390 $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1391 if (strtolower($frame_imagetype) == 'ima') {
1392 // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1393 // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net)
1394 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1395 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1396 if (ord($frame_mimetype) === 0) {
1397 $frame_mimetype = '';
1399 $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1400 if ($frame_imagetype == 'JPEG') {
1401 $frame_imagetype = 'JPG';
1403 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1408 if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1409 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1410 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1411 if (ord($frame_mimetype) === 0) {
1412 $frame_mimetype = '';
1414 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1417 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1419 if ($frame_offset >= $parsedFrame['datalength']) {
1420 $this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] +
8 +
$frame_offset));
1422 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1423 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1424 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1426 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1427 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1428 // if description only contains a BOM or terminator then make it blank
1429 $frame_description = '';
1431 $parsedFrame['encodingid'] = $frame_textencoding;
1432 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1434 if ($id3v2_majorversion == 2) {
1435 $parsedFrame['imagetype'] = isset($frame_imagetype) ?
$frame_imagetype : null;
1437 $parsedFrame['mime'] = isset($frame_mimetype) ?
$frame_mimetype : null;
1439 $parsedFrame['picturetypeid'] = $frame_picturetype;
1440 $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype);
1441 $parsedFrame['description'] = $frame_description;
1442 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator));
1443 $parsedFrame['datalength'] = strlen($parsedFrame['data']);
1445 $parsedFrame['image_mime'] = '';
1446 $imageinfo = array();
1447 if ($imagechunkcheck = getid3_lib
::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
1448 if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1449 $parsedFrame['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
1450 if ($imagechunkcheck[0]) {
1451 $parsedFrame['image_width'] = $imagechunkcheck[0];
1453 if ($imagechunkcheck[1]) {
1454 $parsedFrame['image_height'] = $imagechunkcheck[1];
1460 if ($this->getid3
->option_save_attachments
=== false) {
1462 unset($parsedFrame['data']);
1466 if ($this->getid3
->option_save_attachments
=== true) {
1469 } elseif (is_int($this->getid3->option_save_attachments)) {
1470 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1472 $this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
1473 unset($parsedFrame['data']);
1477 } elseif (is_string($this->getid3
->option_save_attachments
)) {
1478 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR
, $this->getid3
->option_save_attachments
), DIRECTORY_SEPARATOR
);
1479 if (!is_dir($dir) ||
!getID3
::is_writable($dir)) {
1480 // cannot write, skip
1481 $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
1482 unset($parsedFrame['data']);
1486 // if we get this far, must be OK
1487 if (is_string($this->getid3
->option_save_attachments
)) {
1488 $destination_filename = $dir.DIRECTORY_SEPARATOR
.md5($info['filenamepath']).'_'.$frame_offset;
1489 if (!file_exists($destination_filename) || getID3
::is_writable($destination_filename)) {
1490 file_put_contents($destination_filename, $parsedFrame['data']);
1492 $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
1494 $parsedFrame['data_filename'] = $destination_filename;
1495 unset($parsedFrame['data']);
1497 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1498 if (!isset($info['id3v2']['comments']['picture'])) {
1499 $info['id3v2']['comments']['picture'] = array();
1501 $comments_picture_data = array();
1502 foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
1503 if (isset($parsedFrame[$picture_key])) {
1504 $comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
1507 $info['id3v2']['comments']['picture'][] = $comments_picture_data;
1508 unset($comments_picture_data);
1514 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) ||
// 4.15 GEOB General encapsulated object
1515 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object
1516 // There may be more than one 'GEOB' frame in each tag,
1517 // but only one with the same content descriptor
1518 // <Header for 'General encapsulated object', ID: 'GEOB'>
1519 // Text encoding $xx
1520 // MIME type <text string> $00
1521 // Filename <text string according to encoding> $00 (00)
1522 // Content description <text string according to encoding> $00 (00)
1523 // Encapsulated object <binary data>
1526 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1527 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1528 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1529 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1530 $frame_textencoding_terminator = "\x00";
1532 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1533 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1534 if (ord($frame_mimetype) === 0) {
1535 $frame_mimetype = '';
1537 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1539 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1540 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1541 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1543 $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1544 if (ord($frame_filename) === 0) {
1545 $frame_filename = '';
1547 $frame_offset = $frame_terminatorpos +
strlen($frame_textencoding_terminator);
1549 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1550 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1551 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1553 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1554 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1555 // if description only contains a BOM or terminator then make it blank
1556 $frame_description = '';
1558 $frame_offset = $frame_terminatorpos +
strlen($frame_textencoding_terminator);
1560 $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset);
1561 $parsedFrame['encodingid'] = $frame_textencoding;
1562 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1564 $parsedFrame['mime'] = $frame_mimetype;
1565 $parsedFrame['filename'] = $frame_filename;
1566 $parsedFrame['description'] = $frame_description;
1567 unset($parsedFrame['data']);
1570 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) ||
// 4.16 PCNT Play counter
1571 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter
1572 // There may only be one 'PCNT' frame in each tag.
1573 // When the counter reaches all one's, one byte is inserted in
1574 // front of the counter thus making the counter eight bits bigger
1575 // <Header for 'Play counter', ID: 'PCNT'>
1576 // Counter $xx xx xx xx (xx ...)
1578 $parsedFrame['data'] = getid3_lib
::BigEndian2Int($parsedFrame['data']);
1581 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) ||
// 4.17 POPM Popularimeter
1582 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter
1583 // There may be more than one 'POPM' frame in each tag,
1584 // but only one with the same email address
1585 // <Header for 'Popularimeter', ID: 'POPM'>
1586 // Email to user <text string> $00
1588 // Counter $xx xx xx xx (xx ...)
1591 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1592 $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1593 if (ord($frame_emailaddress) === 0) {
1594 $frame_emailaddress = '';
1596 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1597 $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1598 $parsedFrame['counter'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1599 $parsedFrame['email'] = $frame_emailaddress;
1600 $parsedFrame['rating'] = $frame_rating;
1601 unset($parsedFrame['data']);
1604 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) ||
// 4.18 RBUF Recommended buffer size
1605 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size
1606 // There may only be one 'RBUF' frame in each tag
1607 // <Header for 'Recommended buffer size', ID: 'RBUF'>
1608 // Buffer size $xx xx xx
1609 // Embedded info flag %0000000x
1610 // Offset to next tag $xx xx xx xx
1613 $parsedFrame['buffersize'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1616 $frame_embeddedinfoflags = getid3_lib
::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++
, 1));
1617 $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1618 $parsedFrame['nexttagoffset'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1619 unset($parsedFrame['data']);
1622 } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only)
1623 // There may be more than one 'CRM' frame in a tag,
1624 // but only one with the same 'owner identifier'
1625 // <Header for 'Encrypted meta frame', ID: 'CRM'>
1626 // Owner identifier <textstring> $00 (00)
1627 // Content/explanation <textstring> $00 (00)
1628 // Encrypted datablock <binary data>
1631 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1632 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1633 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1635 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1636 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1637 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1638 // if description only contains a BOM or terminator then make it blank
1639 $frame_description = '';
1641 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1643 $parsedFrame['ownerid'] = $frame_ownerid;
1644 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1645 $parsedFrame['description'] = $frame_description;
1646 unset($parsedFrame['data']);
1649 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) ||
// 4.19 AENC Audio encryption
1650 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption
1651 // There may be more than one 'AENC' frames in a tag,
1652 // but only one with the same 'Owner identifier'
1653 // <Header for 'Audio encryption', ID: 'AENC'>
1654 // Owner identifier <text string> $00
1655 // Preview start $xx xx
1656 // Preview length $xx xx
1657 // Encryption info <binary data>
1660 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1661 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1662 if (ord($frame_ownerid) === 0) {
1663 $frame_ownerid = '';
1665 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1666 $parsedFrame['ownerid'] = $frame_ownerid;
1667 $parsedFrame['previewstart'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1669 $parsedFrame['previewlength'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1671 $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1672 unset($parsedFrame['data']);
1675 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) ||
// 4.20 LINK Linked information
1676 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information
1677 // There may be more than one 'LINK' frame in a tag,
1678 // but only one with the same contents
1679 // <Header for 'Linked information', ID: 'LINK'>
1680 // ID3v2.3+ => Frame identifier $xx xx xx xx
1681 // ID3v2.2 => Frame identifier $xx xx xx
1682 // URL <text string> $00
1683 // ID and additional data <text string(s)>
1686 if ($id3v2_majorversion == 2) {
1687 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1690 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1694 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1695 $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1696 if (ord($frame_url) === 0) {
1699 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1700 $parsedFrame['url'] = $frame_url;
1702 $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1703 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1704 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback_iso88591_utf8($parsedFrame['url']);
1706 unset($parsedFrame['data']);
1709 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
1710 // There may only be one 'POSS' frame in each tag
1711 // <Head for 'Position synchronisation', ID: 'POSS'>
1712 // Time stamp format $xx
1713 // Position $xx (xx ...)
1716 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1717 $parsedFrame['position'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1718 unset($parsedFrame['data']);
1721 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only)
1722 // There may be more than one 'Terms of use' frame in a tag,
1723 // but only one with the same 'Language'
1724 // <Header for 'Terms of use frame', ID: 'USER'>
1725 // Text encoding $xx
1726 // Language $xx xx xx
1727 // The actual text <text string according to encoding>
1730 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1731 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1732 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1734 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1736 $parsedFrame['language'] = $frame_language;
1737 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1738 $parsedFrame['encodingid'] = $frame_textencoding;
1739 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1741 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1742 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1743 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1745 unset($parsedFrame['data']);
1748 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only)
1749 // There may only be one 'OWNE' frame in a tag
1750 // <Header for 'Ownership frame', ID: 'OWNE'>
1751 // Text encoding $xx
1752 // Price paid <text string> $00
1753 // Date of purch. <text string>
1754 // Seller <text string according to encoding>
1757 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1758 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1759 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1761 $parsedFrame['encodingid'] = $frame_textencoding;
1762 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1764 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1765 $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1766 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1768 $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1769 $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1770 $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3);
1772 $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1773 if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1774 $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1778 $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1779 unset($parsedFrame['data']);
1782 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only)
1783 // There may be more than one 'commercial frame' in a tag,
1784 // but no two may be identical
1785 // <Header for 'Commercial frame', ID: 'COMR'>
1786 // Text encoding $xx
1787 // Price string <text string> $00
1788 // Valid until <text string>
1789 // Contact URL <text string> $00
1791 // Name of seller <text string according to encoding> $00 (00)
1792 // Description <text string according to encoding> $00 (00)
1793 // Picture MIME type <string> $00
1794 // Seller logo <binary data>
1797 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1798 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1799 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1800 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1801 $frame_textencoding_terminator = "\x00";
1804 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1805 $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1806 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1807 $frame_rawpricearray = explode('/', $frame_pricestring);
1808 foreach ($frame_rawpricearray as $key => $val) {
1809 $frame_currencyid = substr($val, 0, 3);
1810 $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1811 $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3);
1814 $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1817 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1818 $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1819 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1821 $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1823 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1824 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1825 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1827 $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1828 if (ord($frame_sellername) === 0) {
1829 $frame_sellername = '';
1831 $frame_offset = $frame_terminatorpos +
strlen($frame_textencoding_terminator);
1833 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1834 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1835 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1837 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1838 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1839 // if description only contains a BOM or terminator then make it blank
1840 $frame_description = '';
1842 $frame_offset = $frame_terminatorpos +
strlen($frame_textencoding_terminator);
1844 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1845 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1846 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1848 $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1850 $parsedFrame['encodingid'] = $frame_textencoding;
1851 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1853 $parsedFrame['pricevaliduntil'] = $frame_datestring;
1854 $parsedFrame['contacturl'] = $frame_contacturl;
1855 $parsedFrame['receivedasid'] = $frame_receivedasid;
1856 $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid);
1857 $parsedFrame['sellername'] = $frame_sellername;
1858 $parsedFrame['description'] = $frame_description;
1859 $parsedFrame['mime'] = $frame_mimetype;
1860 $parsedFrame['logo'] = $frame_sellerlogo;
1861 unset($parsedFrame['data']);
1864 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
1865 // There may be several 'ENCR' frames in a tag,
1866 // but only one containing the same symbol
1867 // and only one containing the same owner identifier
1868 // <Header for 'Encryption method registration', ID: 'ENCR'>
1869 // Owner identifier <text string> $00
1870 // Method symbol $xx
1871 // Encryption data <binary data>
1874 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1875 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1876 if (ord($frame_ownerid) === 0) {
1877 $frame_ownerid = '';
1879 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1881 $parsedFrame['ownerid'] = $frame_ownerid;
1882 $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1883 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1886 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only)
1888 // There may be several 'GRID' frames in a tag,
1889 // but only one containing the same symbol
1890 // and only one containing the same owner identifier
1891 // <Header for 'Group ID registration', ID: 'GRID'>
1892 // Owner identifier <text string> $00
1894 // Group dependent data <binary data>
1897 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1898 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1899 if (ord($frame_ownerid) === 0) {
1900 $frame_ownerid = '';
1902 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1904 $parsedFrame['ownerid'] = $frame_ownerid;
1905 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1906 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1909 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only)
1910 // The tag may contain more than one 'PRIV' frame
1911 // but only with different contents
1912 // <Header for 'Private frame', ID: 'PRIV'>
1913 // Owner identifier <text string> $00
1914 // The private data <binary data>
1917 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1918 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1919 if (ord($frame_ownerid) === 0) {
1920 $frame_ownerid = '';
1922 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1924 $parsedFrame['ownerid'] = $frame_ownerid;
1925 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1928 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only)
1929 // There may be more than one 'signature frame' in a tag,
1930 // but no two may be identical
1931 // <Header for 'Signature frame', ID: 'SIGN'>
1933 // Signature <binary data>
1936 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1937 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1940 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only)
1941 // There may only be one 'seek frame' in a tag
1942 // <Header for 'Seek frame', ID: 'SEEK'>
1943 // Minimum offset to next tag $xx xx xx xx
1946 $parsedFrame['data'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1949 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
1950 // There may only be one 'audio seek point index' frame in a tag
1951 // <Header for 'Seek Point Index', ID: 'ASPI'>
1952 // Indexed data start (S) $xx xx xx xx
1953 // Indexed data length (L) $xx xx xx xx
1954 // Number of index points (N) $xx xx
1955 // Bits per index point (b) $xx
1956 // Then for every index point the following data is included:
1957 // Fraction at index (Fi) $xx (xx)
1960 $parsedFrame['datastart'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1962 $parsedFrame['indexeddatalength'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1964 $parsedFrame['indexpoints'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1966 $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1967 $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1968 for ($i = 0; $i < $parsedFrame['indexpoints']; $i++
) {
1969 $parsedFrame['indexes'][$i] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1970 $frame_offset +
= $frame_bytesperpoint;
1972 unset($parsedFrame['data']);
1974 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1975 // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1976 // There may only be one 'RGAD' frame in a tag
1977 // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1978 // Peak Amplitude $xx $xx $xx $xx
1979 // Radio Replay Gain Adjustment %aaabbbcd %dddddddd
1980 // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
1982 // b - originator code
1984 // d - replay gain adjustment
1987 $parsedFrame['peakamplitude'] = getid3_lib
::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1989 $rg_track_adjustment = getid3_lib
::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1991 $rg_album_adjustment = getid3_lib
::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1993 $parsedFrame['raw']['track']['name'] = getid3_lib
::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1994 $parsedFrame['raw']['track']['originator'] = getid3_lib
::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1995 $parsedFrame['raw']['track']['signbit'] = getid3_lib
::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1996 $parsedFrame['raw']['track']['adjustment'] = getid3_lib
::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1997 $parsedFrame['raw']['album']['name'] = getid3_lib
::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1998 $parsedFrame['raw']['album']['originator'] = getid3_lib
::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1999 $parsedFrame['raw']['album']['signbit'] = getid3_lib
::Bin2Dec(substr($rg_album_adjustment, 6, 1));
2000 $parsedFrame['raw']['album']['adjustment'] = getid3_lib
::Bin2Dec(substr($rg_album_adjustment, 7, 9));
2001 $parsedFrame['track']['name'] = getid3_lib
::RGADnameLookup($parsedFrame['raw']['track']['name']);
2002 $parsedFrame['track']['originator'] = getid3_lib
::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
2003 $parsedFrame['track']['adjustment'] = getid3_lib
::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
2004 $parsedFrame['album']['name'] = getid3_lib
::RGADnameLookup($parsedFrame['raw']['album']['name']);
2005 $parsedFrame['album']['originator'] = getid3_lib
::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
2006 $parsedFrame['album']['adjustment'] = getid3_lib
::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
2008 $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude'];
2009 $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
2010 $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
2011 $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
2012 $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
2014 unset($parsedFrame['data']);
2016 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
2017 // http://id3.org/id3v2-chapters-1.0
2018 // <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP"> (10 bytes)
2019 // Element ID <text string> $00
2020 // Start time $xx xx xx xx
2021 // End time $xx xx xx xx
2022 // Start offset $xx xx xx xx
2023 // End offset $xx xx xx xx
2024 // <Optional embedded sub-frames>
2027 @list
($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2028 $frame_offset +
= strlen($parsedFrame['element_id']."\x00");
2029 $parsedFrame['time_begin'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2031 $parsedFrame['time_end'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2033 if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
2034 // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2035 $parsedFrame['offset_begin'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2038 if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
2039 // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2040 $parsedFrame['offset_end'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2044 if ($frame_offset < strlen($parsedFrame['data'])) {
2045 $parsedFrame['subframes'] = array();
2046 while ($frame_offset < strlen($parsedFrame['data'])) {
2047 // <Optional embedded sub-frames>
2048 $subframe = array();
2049 $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
2051 $subframe['size'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2053 $subframe['flags_raw'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2055 if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2056 $this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
2059 $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2060 $frame_offset +
= $subframe['size'];
2062 $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2063 $subframe['text'] = substr($subframe_rawdata, 1);
2064 $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
2065 $encoding_converted_text = trim(getid3_lib
::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
2066 switch (substr($encoding_converted_text, 0, 2)) {
2069 switch (strtoupper($info['id3v2']['encoding'])) {
2072 $encoding_converted_text = substr($encoding_converted_text, 2);
2073 // remove unwanted byte-order-marks
2081 // do not remove BOM
2085 if (($subframe['name'] == 'TIT2') ||
($subframe['name'] == 'TIT3')) {
2086 if ($subframe['name'] == 'TIT2') {
2087 $parsedFrame['chapter_name'] = $encoding_converted_text;
2088 } elseif ($subframe['name'] == 'TIT3') {
2089 $parsedFrame['chapter_description'] = $encoding_converted_text;
2091 $parsedFrame['subframes'][] = $subframe;
2093 $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
2096 unset($subframe_rawdata, $subframe, $encoding_converted_text);
2099 $id3v2_chapter_entry = array();
2100 foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') as $id3v2_chapter_key) {
2101 if (isset($parsedFrame[$id3v2_chapter_key])) {
2102 $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
2105 if (!isset($info['id3v2']['chapters'])) {
2106 $info['id3v2']['chapters'] = array();
2108 $info['id3v2']['chapters'][] = $id3v2_chapter_entry;
2109 unset($id3v2_chapter_entry, $id3v2_chapter_key);
2112 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
2113 // http://id3.org/id3v2-chapters-1.0
2114 // <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC"> (10 bytes)
2115 // Element ID <text string> $00
2118 // Child Element ID <string>$00 /* zero or more child CHAP or CTOC entries */
2119 // <Optional embedded sub-frames>
2122 @list
($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2123 $frame_offset +
= strlen($parsedFrame['element_id']."\x00");
2124 $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
2126 $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
2129 $terminator_position = null;
2130 for ($i = 0; $i < $parsedFrame['entry_count']; $i++
) {
2131 $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
2132 $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
2133 $frame_offset = $terminator_position +
1;
2136 $parsedFrame['ctoc_flags']['ordered'] = (bool) ($ctoc_flags_raw & 0x01);
2137 $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
2139 unset($ctoc_flags_raw, $terminator_position);
2141 if ($frame_offset < strlen($parsedFrame['data'])) {
2142 $parsedFrame['subframes'] = array();
2143 while ($frame_offset < strlen($parsedFrame['data'])) {
2144 // <Optional embedded sub-frames>
2145 $subframe = array();
2146 $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
2148 $subframe['size'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2150 $subframe['flags_raw'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2152 if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2153 $this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
2156 $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2157 $frame_offset +
= $subframe['size'];
2159 $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2160 $subframe['text'] = substr($subframe_rawdata, 1);
2161 $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
2162 $encoding_converted_text = trim(getid3_lib
::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
2163 switch (substr($encoding_converted_text, 0, 2)) {
2166 switch (strtoupper($info['id3v2']['encoding'])) {
2169 $encoding_converted_text = substr($encoding_converted_text, 2);
2170 // remove unwanted byte-order-marks
2178 // do not remove BOM
2182 if (($subframe['name'] == 'TIT2') ||
($subframe['name'] == 'TIT3')) {
2183 if ($subframe['name'] == 'TIT2') {
2184 $parsedFrame['toc_name'] = $encoding_converted_text;
2185 } elseif ($subframe['name'] == 'TIT3') {
2186 $parsedFrame['toc_description'] = $encoding_converted_text;
2188 $parsedFrame['subframes'][] = $subframe;
2190 $this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
2193 unset($subframe_rawdata, $subframe, $encoding_converted_text);
2202 * @param string $data
2206 public function DeUnsynchronise($data) {
2207 return str_replace("\xFF\x00", "\xFF", $data);
2215 public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
2216 static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
2217 0x00 => 'No more than 128 frames and 1 MB total tag size',
2218 0x01 => 'No more than 64 frames and 128 KB total tag size',
2219 0x02 => 'No more than 32 frames and 40 KB total tag size',
2220 0x03 => 'No more than 32 frames and 4 KB total tag size',
2222 return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ?
$LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
2230 public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
2231 static $LookupExtendedHeaderRestrictionsTextEncodings = array(
2232 0x00 => 'No restrictions',
2233 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
2235 return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ?
$LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
2243 public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
2244 static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
2245 0x00 => 'No restrictions',
2246 0x01 => 'No string is longer than 1024 characters',
2247 0x02 => 'No string is longer than 128 characters',
2248 0x03 => 'No string is longer than 30 characters',
2250 return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ?
$LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
2258 public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
2259 static $LookupExtendedHeaderRestrictionsImageEncoding = array(
2260 0x00 => 'No restrictions',
2261 0x01 => 'Images are encoded only with PNG or JPEG',
2263 return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ?
$LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
2271 public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
2272 static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
2273 0x00 => 'No restrictions',
2274 0x01 => 'All images are 256x256 pixels or smaller',
2275 0x02 => 'All images are 64x64 pixels or smaller',
2276 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
2278 return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ?
$LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
2282 * @param string $currencyid
2286 public function LookupCurrencyUnits($currencyid) {
2290 /** This is not a comment!
2304 BAM Convertible Marka
2321 CDF Congolese Francs
2467 XDR Special Drawing Rights
2475 ZWD Zimbabwe Dollars
2479 return getid3_lib
::EmbeddedLookup($currencyid, $begin, __LINE__
, __FILE__
, 'id3v2-currency-units');
2483 * @param string $currencyid
2487 public function LookupCurrencyCountry($currencyid) {
2491 /** This is not a comment!
2493 AED United Arab Emirates
2497 ANG Netherlands Antilles
2504 BAM Bosnia and Herzegovina
2512 BND Brunei Darussalam
2534 DOP Dominican Republic
2541 EUR Euro Member Countries
2544 FKP Falkland Islands (Malvinas)
2601 MVR Maldives (Maldive Islands)
2609 NLG Netherlands (Holland)
2616 PGK Papua New Guinea
2639 STD São Tome and Principe
2649 TTD Trinidad and Tobago
2655 USD United States of America
2663 XAF Communauté Financière Africaine
2667 XDR International Monetary Fund
2669 XPF Comptoirs Français du Pacifique
2679 return getid3_lib
::EmbeddedLookup($currencyid, $begin, __LINE__
, __FILE__
, 'id3v2-currency-country');
2683 * @param string $languagecode
2684 * @param bool $casesensitive
2688 public static function LanguageLookup($languagecode, $casesensitive=false) {
2690 if (!$casesensitive) {
2691 $languagecode = strtolower($languagecode);
2694 // http://www.id3.org/id3v2.4.0-structure.txt
2695 // [4. ID3v2 frame overview]
2696 // The three byte language field, present in several frames, is used to
2697 // describe the language of the frame's content, according to ISO-639-2
2698 // [ISO-639-2]. The language should be represented in lower case. If the
2699 // language is not known the string "XXX" should be used.
2702 // ISO 639-2 - http://www.id3.org/iso639-2.html
2706 /** This is not a comment!
2715 afa Afro-Asiatic (Other)
2722 alg Algonquian Languages
2724 ang English, Old (ca. 450-1100)
2725 apa Apache Languages
2731 art Artificial (Other)
2734 ath Athapascan Languages
2741 bai Bamileke Languages
2769 cai Central American Indian (Other)
2772 cau Caucasian (Other)
2791 cpe Creoles and Pidgins, English-based (Other)
2792 cpf Creoles and Pidgins, French-based (Other)
2793 cpp Creoles and Pidgins, Portuguese-based (Other)
2795 crp Creoles and Pidgins (Other)
2796 cus Cushitic (Other)
2806 dra Dravidian (Other)
2808 dum Dutch, Middle (ca. 1050-1350)
2813 egy Egyptian (Ancient)
2815 ell Greek, Modern (1453-)
2818 enm English, Middle (ca. 1100-1500)
2832 fiu Finno-Ugrian (Other)
2836 frm French, Middle (ca. 1400-1600)
2837 fro French, Old (842- ca. 1400)
2845 gem Germanic (Other)
2851 gmh German, Middle High (ca. 1050-1500)
2852 goh German, Old High (ca. 750-1050)
2856 grc Greek, Ancient (to 1453)
2857 gre Greek, Modern (1453-)
2878 ina Interlingua (International Auxiliary language Association)
2881 ine Indo-European (Other)
2941 luo Luo (Kenya and Tanzania)
2952 map Austronesian (Other)
2958 mga Irish, Middle (900 - 1200)
2961 mis Miscellaneous (Other)
2962 mkh Mon-Kmer (Other)
2966 mno Manobo Languages
2973 mul Multiple Languages
2980 nai North American Indian (Other)
2988 nic Niger-Kordofanian (Other)
2991 nno Norwegian (Nynorsk)
2995 nub Nubian Languages
3001 oci Langue d'Oc (post 1500)
3007 ota Turkish, Ottoman (1500 - 1928)
3008 oto Otomian Languages
3009 paa Papuan-Australian (Other)
3016 peo Persian, Old (ca 600 - 400 B.C.)
3024 pro Provencal, Old (to 1500)
3039 sai South American Indian (Other)
3040 sal Salishan Languages
3041 sam Samaritan Aramaic
3047 sga Irish, Old (to 900)
3051 sio Siouan Languages
3052 sit Sino-Tibetan (Other)
3069 ssa Nilo-Saharan (Other)
3096 ton Tonga (Tonga Islands)
3119 wak Wakashan Languages
3124 wen Sorbian Languages
3140 return getid3_lib
::EmbeddedLookup($languagecode, $begin, __LINE__
, __FILE__
, 'id3v2-languagecode');
3148 public static function ETCOEventLookup($index) {
3149 if (($index >= 0x17) && ($index <= 0xDF)) {
3150 return 'reserved for future use';
3152 if (($index >= 0xE0) && ($index <= 0xEF)) {
3153 return 'not predefined synch 0-F';
3155 if (($index >= 0xF0) && ($index <= 0xFC)) {
3156 return 'reserved for future use';
3159 static $EventLookup = array(
3160 0x00 => 'padding (has no meaning)',
3161 0x01 => 'end of initial silence',
3162 0x02 => 'intro start',
3163 0x03 => 'main part start',
3164 0x04 => 'outro start',
3165 0x05 => 'outro end',
3166 0x06 => 'verse start',
3167 0x07 => 'refrain start',
3168 0x08 => 'interlude start',
3169 0x09 => 'theme start',
3170 0x0A => 'variation start',
3171 0x0B => 'key change',
3172 0x0C => 'time change',
3173 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
3174 0x0E => 'sustained noise',
3175 0x0F => 'sustained noise end',
3176 0x10 => 'intro end',
3177 0x11 => 'main part end',
3178 0x12 => 'verse end',
3179 0x13 => 'refrain end',
3180 0x14 => 'theme end',
3181 0x15 => 'profanity',
3182 0x16 => 'profanity end',
3183 0xFD => 'audio end (start of silence)',
3184 0xFE => 'audio file ends',
3185 0xFF => 'one more byte of events follows'
3188 return (isset($EventLookup[$index]) ?
$EventLookup[$index] : '');
3196 public static function SYTLContentTypeLookup($index) {
3197 static $SYTLContentTypeLookup = array(
3200 0x02 => 'text transcription',
3201 0x03 => 'movement/part name', // (e.g. 'Adagio')
3202 0x04 => 'events', // (e.g. 'Don Quijote enters the stage')
3203 0x05 => 'chord', // (e.g. 'Bb F Fsus')
3204 0x06 => 'trivia/\'pop up\' information',
3205 0x07 => 'URLs to webpages',
3206 0x08 => 'URLs to images'
3209 return (isset($SYTLContentTypeLookup[$index]) ?
$SYTLContentTypeLookup[$index] : '');
3214 * @param bool $returnarray
3216 * @return array|string
3218 public static function APICPictureTypeLookup($index, $returnarray=false) {
3219 static $APICPictureTypeLookup = array(
3221 0x01 => '32x32 pixels \'file icon\' (PNG only)',
3222 0x02 => 'Other file icon',
3223 0x03 => 'Cover (front)',
3224 0x04 => 'Cover (back)',
3225 0x05 => 'Leaflet page',
3226 0x06 => 'Media (e.g. label side of CD)',
3227 0x07 => 'Lead artist/lead performer/soloist',
3228 0x08 => 'Artist/performer',
3229 0x09 => 'Conductor',
3230 0x0A => 'Band/Orchestra',
3232 0x0C => 'Lyricist/text writer',
3233 0x0D => 'Recording Location',
3234 0x0E => 'During recording',
3235 0x0F => 'During performance',
3236 0x10 => 'Movie/video screen capture',
3237 0x11 => 'A bright coloured fish',
3238 0x12 => 'Illustration',
3239 0x13 => 'Band/artist logotype',
3240 0x14 => 'Publisher/Studio logotype'
3243 return $APICPictureTypeLookup;
3245 return (isset($APICPictureTypeLookup[$index]) ?
$APICPictureTypeLookup[$index] : '');
3253 public static function COMRReceivedAsLookup($index) {
3254 static $COMRReceivedAsLookup = array(
3256 0x01 => 'Standard CD album with other songs',
3257 0x02 => 'Compressed audio on CD',
3258 0x03 => 'File over the Internet',
3259 0x04 => 'Stream over the Internet',
3260 0x05 => 'As note sheets',
3261 0x06 => 'As note sheets in a book with other sheets',
3262 0x07 => 'Music on other media',
3263 0x08 => 'Non-musical merchandise'
3266 return (isset($COMRReceivedAsLookup[$index]) ?
$COMRReceivedAsLookup[$index] : '');
3274 public static function RVA2ChannelTypeLookup($index) {
3275 static $RVA2ChannelTypeLookup = array(
3277 0x01 => 'Master volume',
3278 0x02 => 'Front right',
3279 0x03 => 'Front left',
3280 0x04 => 'Back right',
3281 0x05 => 'Back left',
3282 0x06 => 'Front centre',
3283 0x07 => 'Back centre',
3287 return (isset($RVA2ChannelTypeLookup[$index]) ?
$RVA2ChannelTypeLookup[$index] : '');
3291 * @param string $framename
3295 public static function FrameNameLongLookup($framename) {
3299 /** This is not a comment!
3301 AENC Audio encryption
3302 APIC Attached picture
3303 ASPI Audio seek point index
3304 BUF Recommended buffer size
3308 COMR Commercial frame
3309 CRA Audio encryption
3310 CRM Encrypted meta frame
3311 ENCR Encryption method registration
3313 EQU2 Equalisation (2)
3315 ETC Event timing codes
3316 ETCO Event timing codes
3317 GEO General encapsulated object
3318 GEOB General encapsulated object
3319 GRID Group identification registration
3320 IPL Involved people list
3321 IPLS Involved people list
3322 LINK Linked information
3323 LNK Linked information
3324 MCDI Music CD identifier
3325 MCI Music CD Identifier
3326 MLL MPEG location lookup table
3327 MLLT MPEG location lookup table
3328 OWNE Ownership frame
3330 PIC Attached picture
3333 POSS Position synchronisation frame
3335 RBUF Recommended buffer size
3337 RVA Relative volume adjustment
3338 RVA2 Relative volume adjustment (2)
3339 RVAD Relative volume adjustment
3342 SIGN Signature frame
3343 SLT Synchronised lyric/text
3344 STC Synced tempo codes
3345 SYLT Synchronised lyric/text
3346 SYTC Synchronised tempo codes
3347 TAL Album/Movie/Show title
3348 TALB Album/Movie/Show title
3349 TBP BPM (Beats Per Minute)
3350 TBPM BPM (beats per minute)
3352 TCMP Part of a compilation
3356 TCOP Copyright message
3357 TCP Part of a compilation
3358 TCR Copyright message
3363 TDOR Original release time
3370 TEXT Lyricist/Text writer
3375 TIPL Involved people list
3376 TIT1 Content group description
3377 TIT2 Title/songname/content description
3378 TIT3 Subtitle/Description refinement
3385 TMCL Musician credits list
3389 TOA Original artist(s)/performer(s)
3390 TOAL Original album/movie/show title
3391 TOF Original filename
3392 TOFN Original filename
3393 TOL Original Lyricist(s)/text writer(s)
3394 TOLY Original lyricist(s)/text writer(s)
3395 TOPE Original artist(s)/performer(s)
3396 TOR Original release year
3397 TORY Original release year
3398 TOT Original album/Movie/Show title
3399 TOWN File owner/licensee
3400 TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3401 TP2 Band/Orchestra/Accompaniment
3402 TP3 Conductor/Performer refinement
3403 TP4 Interpreted, remixed, or otherwise modified by
3406 TPE1 Lead performer(s)/Soloist(s)
3407 TPE2 Band/orchestra/accompaniment
3408 TPE3 Conductor/performer refinement
3409 TPE4 Interpreted, remixed, or otherwise modified by
3411 TPRO Produced notice
3413 TRC ISRC (International Standard Recording Code)
3414 TRCK Track number/Position in set
3416 TRDA Recording dates
3417 TRK Track number/Position in set
3418 TRSN Internet radio station name
3419 TRSO Internet radio station owner
3420 TS2 Album-Artist sort order
3421 TSA Album sort order
3422 TSC Composer sort order
3425 TSO2 Album-Artist sort order
3426 TSOA Album sort order
3427 TSOC Composer sort order
3428 TSOP Performer sort order
3429 TSOT Title sort order
3430 TSP Performer sort order
3431 TSRC ISRC (international standard recording code)
3432 TSS Software/hardware and settings used for encoding
3433 TSSE Software/Hardware and settings used for encoding
3435 TST Title sort order
3436 TT1 Content group description
3437 TT2 Title/Songname/Content description
3438 TT3 Subtitle/Description refinement
3439 TXT Lyricist/text writer
3440 TXX User defined text information frame
3441 TXXX User defined text information frame
3444 UFI Unique file identifier
3445 UFID Unique file identifier
3446 ULT Unsynchronised lyric/text transcription
3448 USLT Unsynchronised lyric/text transcription
3449 WAF Official audio file webpage
3450 WAR Official artist/performer webpage
3451 WAS Official audio source webpage
3452 WCM Commercial information
3453 WCOM Commercial information
3454 WCOP Copyright/Legal information
3455 WCP Copyright/Legal information
3456 WOAF Official audio file webpage
3457 WOAR Official artist/performer webpage
3458 WOAS Official audio source webpage
3459 WORS Official Internet radio station homepage
3461 WPB Publishers official webpage
3462 WPUB Publishers official webpage
3463 WXX User defined URL link frame
3464 WXXX User defined URL link frame
3465 TFEA Featured Artist
3466 TSTU Recording Studio
3467 rgad Replay Gain Adjustment
3471 return getid3_lib
::EmbeddedLookup($framename, $begin, __LINE__
, __FILE__
, 'id3v2-framename_long');
3474 // from Helium2 [www.helium2.com]
3475 // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3479 * @param string $framename
3483 public static function FrameNameShortLookup($framename) {
3487 /** This is not a comment!
3489 AENC audio_encryption
3490 APIC attached_picture
3491 ASPI audio_seek_point_index
3492 BUF recommended_buffer_size
3496 COMR commercial_frame
3497 CRA audio_encryption
3498 CRM encrypted_meta_frame
3499 ENCR encryption_method_registration
3503 ETC event_timing_codes
3504 ETCO event_timing_codes
3505 GEO general_encapsulated_object
3506 GEOB general_encapsulated_object
3507 GRID group_identification_registration
3508 IPL involved_people_list
3509 IPLS involved_people_list
3510 LINK linked_information
3511 LNK linked_information
3512 MCDI music_cd_identifier
3513 MCI music_cd_identifier
3514 MLL mpeg_location_lookup_table
3515 MLLT mpeg_location_lookup_table
3516 OWNE ownership_frame
3518 PIC attached_picture
3521 POSS position_synchronisation_frame
3523 RBUF recommended_buffer_size
3525 RVA relative_volume_adjustment
3526 RVA2 relative_volume_adjustment
3527 RVAD relative_volume_adjustment
3530 SIGN signature_frame
3531 SLT synchronised_lyric
3532 STC synced_tempo_codes
3533 SYLT synchronised_lyric
3534 SYTC synchronised_tempo_codes
3540 TCMP part_of_a_compilation
3544 TCOP copyright_message
3545 TCP part_of_a_compilation
3546 TCR copyright_message
3551 TDOR original_release_time
3563 TIPL involved_people_list
3564 TIT1 content_group_description
3573 TMCL musician_credits_list
3579 TOF original_filename
3580 TOFN original_filename
3581 TOL original_lyricist
3582 TOLY original_lyricist
3583 TOPE original_artist
3599 TPRO produced_notice
3604 TRDA recording_dates
3606 TRSN internet_radio_station_name
3607 TRSO internet_radio_station_owner
3608 TS2 album_artist_sort_order
3609 TSA album_sort_order
3610 TSC composer_sort_order
3613 TSO2 album_artist_sort_order
3614 TSOA album_sort_order
3615 TSOC composer_sort_order
3616 TSOP performer_sort_order
3617 TSOT title_sort_order
3618 TSP performer_sort_order
3620 TSS encoder_settings
3621 TSSE encoder_settings
3623 TST title_sort_order
3624 TT1 content_group_description
3632 UFI unique_file_identifier
3633 UFID unique_file_identifier
3634 ULT unsynchronised_lyric
3636 USLT unsynchronised_lyric
3640 WCM commercial_information
3641 WCOM commercial_information
3653 TFEA featured_artist
3654 TSTU recording_studio
3655 rgad replay_gain_adjustment
3659 return getid3_lib
::EmbeddedLookup($framename, $begin, __LINE__
, __FILE__
, 'id3v2-framename_short');
3663 * @param string $encoding
3667 public static function TextEncodingTerminatorLookup($encoding) {
3668 // http://www.id3.org/id3v2.4.0-structure.txt
3669 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3670 static $TextEncodingTerminatorLookup = array(
3671 0 => "\x00", // $00 ISO-8859-1. Terminated with $00.
3672 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.
3673 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3674 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
3677 return (isset($TextEncodingTerminatorLookup[$encoding]) ?
$TextEncodingTerminatorLookup[$encoding] : "\x00");
3681 * @param int $encoding
3685 public static function TextEncodingNameLookup($encoding) {
3686 // http://www.id3.org/id3v2.4.0-structure.txt
3687 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3688 static $TextEncodingNameLookup = array(
3689 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00.
3690 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.
3691 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3692 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00.
3695 return (isset($TextEncodingNameLookup[$encoding]) ?
$TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3699 * @param string $framename
3700 * @param int $id3v2majorversion
3704 public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3705 switch ($id3v2majorversion) {
3707 return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3712 return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3719 * @param string $numberstring
3720 * @param bool $allowdecimal
3721 * @param bool $allownegative
3725 public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3726 for ($i = 0; $i < strlen($numberstring); $i++
) {
3727 if ((chr($numberstring{$i}) < chr('0')) ||
(chr($numberstring{$i}) > chr('9'))) {
3728 if (($numberstring{$i} == '.') && $allowdecimal) {
3730 } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) {
3741 * @param string $datestamp
3745 public static function IsValidDateStampString($datestamp) {
3746 if (strlen($datestamp) != 8) {
3749 if (!self
::IsANumber($datestamp, false)) {
3752 $year = substr($datestamp, 0, 4);
3753 $month = substr($datestamp, 4, 2);
3754 $day = substr($datestamp, 6, 2);
3755 if (($year == 0) ||
($month == 0) ||
($day == 0)) {
3764 if (($day > 30) && (($month == 4) ||
($month == 6) ||
($month == 9) ||
($month == 11))) {
3767 if (($day > 29) && ($month == 2)) {
3774 * @param int $majorversion
3778 public static function ID3v2HeaderLength($majorversion) {
3779 return (($majorversion == 2) ?
6 : 10);
3783 * @param string $frame_name
3785 * @return string|false
3787 public static function ID3v22iTunesBrokenFrameName($frame_name) {
3788 // iTunes (multiple versions) has been known to write ID3v2.3 style frames
3789 // but use ID3v2.2 frame names, right-padded using either [space] or [null]
3790 // to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
3791 // This function will detect and translate the corrupt frame name into ID3v2.3 standard.
3792 static $ID3v22_iTunes_BrokenFrames = array(
3793 'BUF' => 'RBUF', // Recommended buffer size
3794 'CNT' => 'PCNT', // Play counter
3795 'COM' => 'COMM', // Comments
3796 'CRA' => 'AENC', // Audio encryption
3797 'EQU' => 'EQUA', // Equalisation
3798 'ETC' => 'ETCO', // Event timing codes
3799 'GEO' => 'GEOB', // General encapsulated object
3800 'IPL' => 'IPLS', // Involved people list
3801 'LNK' => 'LINK', // Linked information
3802 'MCI' => 'MCDI', // Music CD identifier
3803 'MLL' => 'MLLT', // MPEG location lookup table
3804 'PIC' => 'APIC', // Attached picture
3805 'POP' => 'POPM', // Popularimeter
3806 'REV' => 'RVRB', // Reverb
3807 'RVA' => 'RVAD', // Relative volume adjustment
3808 'SLT' => 'SYLT', // Synchronised lyric/text
3809 'STC' => 'SYTC', // Synchronised tempo codes
3810 'TAL' => 'TALB', // Album/Movie/Show title
3811 'TBP' => 'TBPM', // BPM (beats per minute)
3812 'TCM' => 'TCOM', // Composer
3813 'TCO' => 'TCON', // Content type
3814 'TCP' => 'TCMP', // Part of a compilation
3815 'TCR' => 'TCOP', // Copyright message
3816 'TDA' => 'TDAT', // Date
3817 'TDY' => 'TDLY', // Playlist delay
3818 'TEN' => 'TENC', // Encoded by
3819 'TFT' => 'TFLT', // File type
3820 'TIM' => 'TIME', // Time
3821 'TKE' => 'TKEY', // Initial key
3822 'TLA' => 'TLAN', // Language(s)
3823 'TLE' => 'TLEN', // Length
3824 'TMT' => 'TMED', // Media type
3825 'TOA' => 'TOPE', // Original artist(s)/performer(s)
3826 'TOF' => 'TOFN', // Original filename
3827 'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
3828 'TOR' => 'TORY', // Original release year
3829 'TOT' => 'TOAL', // Original album/movie/show title
3830 'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
3831 'TP2' => 'TPE2', // Band/orchestra/accompaniment
3832 'TP3' => 'TPE3', // Conductor/performer refinement
3833 'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
3834 'TPA' => 'TPOS', // Part of a set
3835 'TPB' => 'TPUB', // Publisher
3836 'TRC' => 'TSRC', // ISRC (international standard recording code)
3837 'TRD' => 'TRDA', // Recording dates
3838 'TRK' => 'TRCK', // Track number/Position in set
3839 'TS2' => 'TSO2', // Album-Artist sort order
3840 'TSA' => 'TSOA', // Album sort order
3841 'TSC' => 'TSOC', // Composer sort order
3842 'TSI' => 'TSIZ', // Size
3843 'TSP' => 'TSOP', // Performer sort order
3844 'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
3845 'TST' => 'TSOT', // Title sort order
3846 'TT1' => 'TIT1', // Content group description
3847 'TT2' => 'TIT2', // Title/songname/content description
3848 'TT3' => 'TIT3', // Subtitle/Description refinement
3849 'TXT' => 'TEXT', // Lyricist/Text writer
3850 'TXX' => 'TXXX', // User defined text information frame
3851 'TYE' => 'TYER', // Year
3852 'UFI' => 'UFID', // Unique file identifier
3853 'ULT' => 'USLT', // Unsynchronised lyric/text transcription
3854 'WAF' => 'WOAF', // Official audio file webpage
3855 'WAR' => 'WOAR', // Official artist/performer webpage
3856 'WAS' => 'WOAS', // Official audio source webpage
3857 'WCM' => 'WCOM', // Commercial information
3858 'WCP' => 'WCOP', // Copyright/Legal information
3859 'WPB' => 'WPUB', // Publishers official webpage
3860 'WXX' => 'WXXX', // User defined URL link frame
3862 if (strlen($frame_name) == 4) {
3863 if ((substr($frame_name, 3, 1) == ' ') ||
(substr($frame_name, 3, 1) == "\x00")) {
3864 if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) {
3865 return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)];