829f5ee2d5534f35e22f23e861ac4517944419ef
2 /////////////////////////////////////////////////////////////////
3 /// getID3() by James Heinrich <info@getid3.org> //
4 // available at http://getid3.sourceforge.net //
5 // or http://www.getid3.org //
6 // also https://github.com/JamesHeinrich/getID3 //
7 /////////////////////////////////////////////////////////////////
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;
23 public function Analyze() {
24 $info = &$this->getid3
->info
;
26 // Overall tag structure:
27 // +-----------------------------+
28 // | Header (10 bytes) |
29 // +-----------------------------+
30 // | Extended Header |
31 // | (variable length, OPTIONAL) |
32 // +-----------------------------+
33 // | Frames (variable length) |
34 // +-----------------------------+
36 // | (variable length, OPTIONAL) |
37 // +-----------------------------+
38 // | Footer (10 bytes, OPTIONAL) |
39 // +-----------------------------+
42 // ID3v2/file identifier "ID3"
43 // ID3v2 version $04 00
44 // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
45 // ID3v2 size 4 * %0xxxxxxx
49 $info['id3v2']['header'] = true;
50 $thisfile_id3v2 = &$info['id3v2'];
51 $thisfile_id3v2['flags'] = array();
52 $thisfile_id3v2_flags = &$thisfile_id3v2['flags'];
55 $this->fseek($this->StartingOffset
);
56 $header = $this->fread(10);
57 if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) {
59 $thisfile_id3v2['majorversion'] = ord($header{3});
60 $thisfile_id3v2['minorversion'] = ord($header{4});
63 $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
67 unset($info['id3v2']);
72 if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
74 $this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
79 $id3_flags = ord($header{5});
80 switch ($id3v2_majorversion) {
83 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
84 $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
89 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
90 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
91 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
96 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
97 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
98 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
99 $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present
103 $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
105 $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset
;
106 $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] +
$thisfile_id3v2['headerlength'];
110 // create 'encoding' key - used by getid3::HandleAllTags()
111 // in ID3v2 every field can have it's own encoding type
112 // so force everything to UTF-8 so it can be handled consistantly
113 $thisfile_id3v2['encoding'] = 'UTF-8';
118 // All ID3v2 frames consists of one frame header followed by one or more
119 // fields containing the actual information. The header is always 10
120 // bytes and laid out as follows:
122 // Frame ID $xx xx xx xx (four characters)
123 // Size 4 * %0xxxxxxx
126 $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
127 if (!empty($thisfile_id3v2['exthead']['length'])) {
128 $sizeofframes -= ($thisfile_id3v2['exthead']['length'] +
4);
130 if (!empty($thisfile_id3v2_flags['isfooter'])) {
131 $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
133 if ($sizeofframes > 0) {
135 $framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
137 // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
138 if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
139 $framedata = $this->DeUnsynchronise($framedata);
141 // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
142 // of on tag level, making it easier to skip frames, increasing the streamability
143 // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
144 // there exists an unsynchronised frame, while the new unsynchronisation flag in
145 // the frame header [S:4.1.2] indicates unsynchronisation.
148 //$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)
149 $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
153 if (!empty($thisfile_id3v2_flags['exthead'])) {
154 $extended_header_offset = 0;
156 if ($id3v2_majorversion == 3) {
159 //Extended header size $xx xx xx xx // 32-bit integer
160 //Extended Flags $xx xx
161 // %x0000000 %00000000 // v2.3
162 // x - CRC data present
163 //Size of padding $xx xx xx xx
165 $thisfile_id3v2['exthead']['length'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
166 $extended_header_offset +
= 4;
168 $thisfile_id3v2['exthead']['flag_bytes'] = 2;
169 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
170 $extended_header_offset +
= $thisfile_id3v2['exthead']['flag_bytes'];
172 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
174 $thisfile_id3v2['exthead']['padding_size'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
175 $extended_header_offset +
= 4;
177 if ($thisfile_id3v2['exthead']['flags']['crc']) {
178 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
179 $extended_header_offset +
= 4;
181 $extended_header_offset +
= $thisfile_id3v2['exthead']['padding_size'];
183 } elseif ($id3v2_majorversion == 4) {
186 //Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer
187 //Number of flag bytes $01
190 // b - Tag is an update
191 // Flag data length $00
192 // c - CRC data present
193 // Flag data length $05
194 // Total frame CRC 5 * %0xxxxxxx
195 // d - Tag restrictions
196 // Flag data length $01
198 $thisfile_id3v2['exthead']['length'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
199 $extended_header_offset +
= 4;
201 $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
202 $extended_header_offset +
= 1;
204 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
205 $extended_header_offset +
= $thisfile_id3v2['exthead']['flag_bytes'];
207 $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
208 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
209 $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
211 if ($thisfile_id3v2['exthead']['flags']['update']) {
212 $ext_header_chunk_length = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
213 $extended_header_offset +
= 1;
216 if ($thisfile_id3v2['exthead']['flags']['crc']) {
217 $ext_header_chunk_length = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
218 $extended_header_offset +
= 1;
219 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
220 $extended_header_offset +
= $ext_header_chunk_length;
223 if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
224 $ext_header_chunk_length = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
225 $extended_header_offset +
= 1;
228 $restrictions_raw = getid3_lib
::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
229 $extended_header_offset +
= 1;
230 $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
231 $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
232 $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
233 $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
234 $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
236 $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
237 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
238 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
239 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
240 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
243 if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
244 $this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
248 $framedataoffset +
= $extended_header_offset;
249 $framedata = substr($framedata, $extended_header_offset);
250 } // end extended header
253 while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
254 if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
255 // insufficient room left in ID3v2 header for actual data - must be padding
256 $thisfile_id3v2['padding']['start'] = $framedataoffset;
257 $thisfile_id3v2['padding']['length'] = strlen($framedata);
258 $thisfile_id3v2['padding']['valid'] = true;
259 for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++
) {
260 if ($framedata{$i} != "\x00") {
261 $thisfile_id3v2['padding']['valid'] = false;
262 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] +
$i;
263 $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
267 break; // skip rest of ID3v2 header
269 if ($id3v2_majorversion == 2) {
270 // Frame ID $xx xx xx (three characters)
271 // Size $xx xx xx (24-bit integer)
274 $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
275 $framedata = substr($framedata, 6); // and leave the rest in $framedata
276 $frame_name = substr($frame_header, 0, 3);
277 $frame_size = getid3_lib
::BigEndian2Int(substr($frame_header, 3, 3), 0);
278 $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
280 } elseif ($id3v2_majorversion > 2) {
282 // Frame ID $xx xx xx xx (four characters)
283 // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
286 $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
287 $framedata = substr($framedata, 10); // and leave the rest in $framedata
289 $frame_name = substr($frame_header, 0, 4);
290 if ($id3v2_majorversion == 3) {
291 $frame_size = getid3_lib
::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
293 $frame_size = getid3_lib
::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
296 if ($frame_size < (strlen($framedata) +
4)) {
297 $nextFrameID = substr($framedata, $frame_size, 4);
298 if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
300 } elseif (($frame_name == "\x00".'MP3') ||
($frame_name == "\x00\x00".'MP') ||
($frame_name == ' MP3') ||
($frame_name == 'MP3e')) {
301 // MP3ext known broken frames - "ok" for the purposes of this test
302 } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib
::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
303 $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');
304 $id3v2_majorversion = 3;
305 $frame_size = getid3_lib
::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
310 $frame_flags = getid3_lib
::BigEndian2Int(substr($frame_header, 8, 2));
313 if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) ||
($frame_name == "\x00\x00\x00\x00")) {
314 // padding encountered
316 $thisfile_id3v2['padding']['start'] = $framedataoffset;
317 $thisfile_id3v2['padding']['length'] = strlen($frame_header) +
strlen($framedata);
318 $thisfile_id3v2['padding']['valid'] = true;
320 $len = strlen($framedata);
321 for ($i = 0; $i < $len; $i++
) {
322 if ($framedata{$i} != "\x00") {
323 $thisfile_id3v2['padding']['valid'] = false;
324 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] +
$i;
325 $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
329 break; // skip rest of ID3v2 header
332 if ($iTunesBrokenFrameNameFixed = self
::ID3v22iTunesBrokenFrameName($frame_name)) {
333 $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.');
334 $frame_name = $iTunesBrokenFrameNameFixed;
336 if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
339 $parsedFrame['frame_name'] = $frame_name;
340 $parsedFrame['frame_flags_raw'] = $frame_flags;
341 $parsedFrame['data'] = substr($framedata, 0, $frame_size);
342 $parsedFrame['datalength'] = getid3_lib
::CastAsInt($frame_size);
343 $parsedFrame['dataoffset'] = $framedataoffset;
345 $this->ParseID3v2Frame($parsedFrame);
346 $thisfile_id3v2[$frame_name][] = $parsedFrame;
348 $framedata = substr($framedata, $frame_size);
350 } else { // invalid frame length or FrameID
352 if ($frame_size <= strlen($framedata)) {
354 if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
356 // next frame is valid, just skip the current frame
357 $framedata = substr($framedata, $frame_size);
358 $this->warning('Next ID3v2 frame is valid, skipping current frame.');
362 // next frame is invalid too, abort processing
365 $this->error('Next ID3v2 frame is also invalid, aborting processing.');
369 } elseif ($frame_size == strlen($framedata)) {
371 // this is the last frame, just skip
372 $this->warning('This was the last ID3v2 frame.');
376 // next frame is invalid too, abort processing
379 $this->warning('Invalid ID3v2 frame size, aborting.');
382 if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
384 switch ($frame_name) {
385 case "\x00\x00".'MP':
392 $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/)"]');
396 $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
400 } elseif (!isset($framedata) ||
($frame_size > strlen($framedata))) {
402 $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').')).');
406 $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');
411 $framedataoffset +
= ($frame_size +
$this->ID3v2HeaderLength($id3v2_majorversion));
420 // The footer is a copy of the header, but with a different identifier.
421 // ID3v2 identifier "3DI"
422 // ID3v2 version $04 00
423 // ID3v2 flags %abcd0000
424 // ID3v2 size 4 * %0xxxxxxx
426 if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
427 $footer = $this->fread(10);
428 if (substr($footer, 0, 3) == '3DI') {
429 $thisfile_id3v2['footer'] = true;
430 $thisfile_id3v2['majorversion_footer'] = ord($footer{3});
431 $thisfile_id3v2['minorversion_footer'] = ord($footer{4});
433 if ($thisfile_id3v2['majorversion_footer'] <= 4) {
434 $id3_flags = ord(substr($footer{5}));
435 $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80);
436 $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40);
437 $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20);
438 $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
440 $thisfile_id3v2['footerlength'] = getid3_lib
::BigEndian2Int(substr($footer, 6, 4), 1);
444 if (isset($thisfile_id3v2['comments']['genre'])) {
446 foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
447 foreach ($this->ParseID3v2GenreString($value) as $genre) {
451 $thisfile_id3v2['comments']['genre'] = array_unique($genres);
452 unset($key, $value, $genres, $genre);
455 if (isset($thisfile_id3v2['comments']['track'])) {
456 foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
457 if (strstr($value, '/')) {
458 list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
463 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)) {
464 $thisfile_id3v2['comments']['year'] = array($matches[1]);
468 if (!empty($thisfile_id3v2['TXXX'])) {
469 // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
470 foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
471 switch ($txxx_array['description']) {
472 case 'replaygain_track_gain':
473 if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
474 $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
477 case 'replaygain_track_peak':
478 if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
479 $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
482 case 'replaygain_album_gain':
483 if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
484 $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
493 $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
494 if (isset($thisfile_id3v2['footer'])) {
495 $info['avdataoffset'] +
= 10;
502 public function ParseID3v2GenreString($genrestring) {
503 // Parse genres into arrays of genreName and genreID
504 // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
505 // ID3v2.4.x: '21' $00 'Eurodisco' $00
506 $clean_genres = array();
508 // hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
509 if (($this->getid3
->info
['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
510 // note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
511 // replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
512 if (preg_match('#/#', $genrestring)) {
513 $genrestring = str_replace('/', "\x00", $genrestring);
514 $genrestring = str_replace('Pop'."\x00".'Funk', 'Pop/Funk', $genrestring);
515 $genrestring = str_replace('Rock'."\x00".'Rock', 'Folk/Rock', $genrestring);
518 // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
519 if (preg_match('#;#', $genrestring)) {
520 $genrestring = str_replace(';', "\x00", $genrestring);
525 if (strpos($genrestring, "\x00") === false) {
526 $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
529 $genre_elements = explode("\x00", $genrestring);
530 foreach ($genre_elements as $element) {
531 $element = trim($element);
533 if (preg_match('#^[0-9]{1,3}#', $element)) {
534 $clean_genres[] = getid3_id3v1
::LookupGenreName($element);
536 $clean_genres[] = str_replace('((', '(', $element);
540 return $clean_genres;
544 public function ParseID3v2Frame(&$parsedFrame) {
547 $info = &$this->getid3
->info
;
548 $id3v2_majorversion = $info['id3v2']['majorversion'];
550 $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']);
551 if (empty($parsedFrame['framenamelong'])) {
552 unset($parsedFrame['framenamelong']);
554 $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
555 if (empty($parsedFrame['framenameshort'])) {
556 unset($parsedFrame['framenameshort']);
559 if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
560 if ($id3v2_majorversion == 3) {
561 // Frame Header Flags
562 // %abc00000 %ijk00000
563 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
564 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
565 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
566 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
567 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
568 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
570 } elseif ($id3v2_majorversion == 4) {
571 // Frame Header Flags
572 // %0abc0000 %0h00kmnp
573 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
574 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
575 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
576 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
577 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
578 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
579 $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
580 $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
582 // Frame-level de-unsynchronisation - ID3v2.4
583 if ($parsedFrame['flags']['Unsynchronisation']) {
584 $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
587 if ($parsedFrame['flags']['DataLengthIndicator']) {
588 $parsedFrame['data_length_indicator'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
589 $parsedFrame['data'] = substr($parsedFrame['data'], 4);
593 // Frame-level de-compression
594 if ($parsedFrame['flags']['compression']) {
595 $parsedFrame['decompressed_size'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
596 if (!function_exists('gzuncompress')) {
597 $this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
599 if ($decompresseddata = @gzuncompress
(substr($parsedFrame['data'], 4))) {
600 //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
601 $parsedFrame['data'] = $decompresseddata;
602 unset($decompresseddata);
604 $this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
610 if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
611 if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
612 $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');
616 if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
618 $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
619 switch ($parsedFrame['frame_name']) {
621 $warning .= ' (this is known to happen with files tagged by RioPort)';
627 $this->warning($warning);
629 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) ||
// 4.1 UFID Unique file identifier
630 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier
631 // There may be more than one 'UFID' frame in a tag,
632 // but only one with the same 'Owner identifier'.
633 // <Header for 'Unique file identifier', ID: 'UFID'>
634 // Owner identifier <text string> $00
635 // Identifier <up to 64 bytes binary data>
636 $exploded = explode("\x00", $parsedFrame['data'], 2);
637 $parsedFrame['ownerid'] = (isset($exploded[0]) ?
$exploded[0] : '');
638 $parsedFrame['data'] = (isset($exploded[1]) ?
$exploded[1] : '');
640 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) ||
// 4.2.2 TXXX User defined text information frame
641 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame
642 // There may be more than one 'TXXX' frame in each tag,
643 // but only one with the same description.
644 // <Header for 'User defined text information frame', ID: 'TXXX'>
646 // Description <text string according to encoding> $00 (00)
647 // Value <text string according to encoding>
650 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
651 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
652 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
653 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
654 $frame_textencoding_terminator = "\x00";
656 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
657 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
658 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
660 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
661 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
662 // if description only contains a BOM or terminator then make it blank
663 $frame_description = '';
665 $parsedFrame['encodingid'] = $frame_textencoding;
666 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
668 $parsedFrame['description'] = trim(getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description));
669 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator));
670 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
671 $commentkey = ($parsedFrame['description'] ?
$parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ?
count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
672 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ||
!array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
673 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
675 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
678 //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
681 } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
682 // There may only be one text information frame of its kind in an tag.
683 // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
684 // excluding 'TXXX' described in 4.2.6.>
686 // Information <text string(s) according to encoding>
689 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
690 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
691 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
694 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
696 $parsedFrame['encodingid'] = $frame_textencoding;
697 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
699 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
700 // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
701 // This of course breaks when an artist name contains slash character, e.g. "AC/DC"
702 // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
703 // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
704 switch ($parsedFrame['encoding']) {
716 $Txxx_elements = array();
717 $Txxx_elements_start_offset = 0;
718 for ($i = 0; $i < strlen($parsedFrame['data']); $i +
= $wordsize) {
719 if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
720 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
721 $Txxx_elements_start_offset = $i +
$wordsize;
724 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
725 foreach ($Txxx_elements as $Txxx_element) {
726 $string = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
727 if (!empty($string)) {
728 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
731 unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
734 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) ||
// 4.3.2 WXXX User defined URL link frame
735 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame
736 // There may be more than one 'WXXX' frame in each tag,
737 // but only one with the same description
738 // <Header for 'User defined URL link frame', ID: 'WXXX'>
740 // Description <text string according to encoding> $00 (00)
744 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
745 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
746 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
747 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
748 $frame_textencoding_terminator = "\x00";
750 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
751 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
752 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
754 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
755 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
756 // if description only contains a BOM or terminator then make it blank
757 $frame_description = '';
759 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator));
761 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator);
762 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
763 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
765 if ($frame_terminatorpos) {
766 // there are null bytes after the data - this is not according to spec
767 // only use data up to first null byte
768 $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
770 // no null bytes following data, just use all data
771 $frame_urldata = (string) $parsedFrame['data'];
774 $parsedFrame['encodingid'] = $frame_textencoding;
775 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
777 $parsedFrame['url'] = $frame_urldata;
778 $parsedFrame['description'] = $frame_description;
779 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
780 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
782 unset($parsedFrame['data']);
785 } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
786 // There may only be one URL link frame of its kind in a tag,
787 // except when stated otherwise in the frame description
788 // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
789 // described in 4.3.2.>
792 $parsedFrame['url'] = trim($parsedFrame['data']);
793 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
794 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
796 unset($parsedFrame['data']);
799 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) ||
// 4.4 IPLS Involved people list (ID3v2.3 only)
800 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
801 // http://id3.org/id3v2.3.0#sec4.4
802 // There may only be one 'IPL' frame in each tag
803 // <Header for 'User defined URL link frame', ID: 'IPL'>
805 // People list strings <textstrings>
808 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
809 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
810 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
812 $parsedFrame['encodingid'] = $frame_textencoding;
813 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
814 $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset);
816 // http://www.getid3.org/phpBB3/viewtopic.php?t=1369
817 // "this tag typically contains null terminated strings, which are associated in pairs"
818 // "there are users that use the tag incorrectly"
819 $IPLS_parts = array();
820 if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
821 $IPLS_parts_unsorted = array();
822 if (((strlen($parsedFrame['data_raw']) %
2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") ||
(substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
823 // 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
825 for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i +
= 2) {
826 $twobytes = substr($parsedFrame['data_raw'], $i, 2);
827 if ($twobytes === "\x00\x00") {
828 $IPLS_parts_unsorted[] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
831 $thisILPS .= $twobytes;
834 if (strlen($thisILPS) > 2) { // 2-byte BOM
835 $IPLS_parts_unsorted[] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
838 // ISO-8859-1 or UTF-8 or other single-byte-null character set
839 $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
841 if (count($IPLS_parts_unsorted) == 1) {
842 // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
843 foreach ($IPLS_parts_unsorted as $key => $value) {
844 $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
846 foreach ($IPLS_parts_sorted as $person) {
847 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
850 } elseif ((count($IPLS_parts_unsorted) %
2) == 0) {
853 foreach ($IPLS_parts_unsorted as $key => $value) {
854 if (($key %
2) == 0) {
858 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
864 foreach ($IPLS_parts_unsorted as $key => $value) {
865 $IPLS_parts[] = array($value);
870 $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
872 $parsedFrame['data'] = $IPLS_parts;
874 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
875 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
879 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) ||
// 4.4 MCDI Music CD identifier
880 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
881 // There may only be one 'MCDI' frame in each tag
882 // <Header for 'Music CD identifier', ID: 'MCDI'>
883 // CD TOC <binary data>
885 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
886 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
890 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) ||
// 4.5 ETCO Event timing codes
891 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes
892 // There may only be one 'ETCO' frame in each tag
893 // <Header for 'Event timing codes', ID: 'ETCO'>
894 // Time stamp format $xx
895 // Where time stamp format is:
896 // $01 (32-bit value) MPEG frames from beginning of file
897 // $02 (32-bit value) milliseconds from beginning of file
898 // Followed by a list of key events in the following format:
900 // Time stamp $xx (xx ...)
901 // The 'Time stamp' is set to zero if directly at the beginning of the sound
902 // or after the previous event. All events MUST be sorted in chronological order.
905 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
907 while ($frame_offset < strlen($parsedFrame['data'])) {
908 $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++
, 1);
909 $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']);
910 $parsedFrame['timestamp'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
913 unset($parsedFrame['data']);
916 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) ||
// 4.6 MLLT MPEG location lookup table
917 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table
918 // There may only be one 'MLLT' frame in each tag
919 // <Header for 'Location lookup table', ID: 'MLLT'>
920 // MPEG frames between reference $xx xx
921 // Bytes between reference $xx xx xx
922 // Milliseconds between reference $xx xx xx
923 // Bits for bytes deviation $xx
924 // Bits for milliseconds dev. $xx
925 // Then for every reference the following data is included;
926 // Deviation in bytes %xxx....
927 // Deviation in milliseconds %xxx....
930 $parsedFrame['framesbetweenreferences'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
931 $parsedFrame['bytesbetweenreferences'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
932 $parsedFrame['msbetweenreferences'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
933 $parsedFrame['bitsforbytesdeviation'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
934 $parsedFrame['bitsformsdeviation'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
935 $parsedFrame['data'] = substr($parsedFrame['data'], 10);
936 while ($frame_offset < strlen($parsedFrame['data'])) {
937 $deviationbitstream .= getid3_lib
::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++
, 1));
939 $reference_counter = 0;
940 while (strlen($deviationbitstream) > 0) {
941 $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
942 $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
943 $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] +
$parsedFrame['bitsformsdeviation']);
944 $reference_counter++
;
946 unset($parsedFrame['data']);
949 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) ||
// 4.7 SYTC Synchronised tempo codes
950 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes
951 // There may only be one 'SYTC' frame in each tag
952 // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
953 // Time stamp format $xx
954 // Tempo data <binary data>
955 // Where time stamp format is:
956 // $01 (32-bit value) MPEG frames from beginning of file
957 // $02 (32-bit value) milliseconds from beginning of file
960 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
961 $timestamp_counter = 0;
962 while ($frame_offset < strlen($parsedFrame['data'])) {
963 $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
964 if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
965 $parsedFrame[$timestamp_counter]['tempo'] +
= ord(substr($parsedFrame['data'], $frame_offset++
, 1));
967 $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
969 $timestamp_counter++
;
971 unset($parsedFrame['data']);
974 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) ||
// 4.8 USLT Unsynchronised lyric/text transcription
975 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription
976 // There may be more than one 'Unsynchronised lyrics/text transcription' frame
977 // in each tag, but only one with the same language and content descriptor.
978 // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
980 // Language $xx xx xx
981 // Content descriptor <text string according to encoding> $00 (00)
982 // Lyrics/text <full text string according to encoding>
985 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
986 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
987 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
988 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
989 $frame_textencoding_terminator = "\x00";
991 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
993 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
994 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
995 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
997 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
998 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
999 // if description only contains a BOM or terminator then make it blank
1000 $frame_description = '';
1002 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator));
1004 $parsedFrame['encodingid'] = $frame_textencoding;
1005 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1007 $parsedFrame['language'] = $frame_language;
1008 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1009 $parsedFrame['description'] = $frame_description;
1010 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1011 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1013 unset($parsedFrame['data']);
1016 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) ||
// 4.9 SYLT Synchronised lyric/text
1017 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text
1018 // There may be more than one 'SYLT' frame in each tag,
1019 // but only one with the same language and content descriptor.
1020 // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
1021 // Text encoding $xx
1022 // Language $xx xx xx
1023 // Time stamp format $xx
1024 // $01 (32-bit value) MPEG frames from beginning of file
1025 // $02 (32-bit value) milliseconds from beginning of file
1027 // Content descriptor <text string according to encoding> $00 (00)
1028 // Terminated text to be synced (typically a syllable)
1029 // Sync identifier (terminator to above string) $00 (00)
1030 // Time stamp $xx (xx ...)
1033 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1034 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1035 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1036 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1037 $frame_textencoding_terminator = "\x00";
1039 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1041 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1042 $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1043 $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1044 $parsedFrame['encodingid'] = $frame_textencoding;
1045 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1047 $parsedFrame['language'] = $frame_language;
1048 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1050 $timestampindex = 0;
1051 $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1052 while (strlen($frame_remainingdata)) {
1054 $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
1055 if ($frame_terminatorpos === false) {
1056 $frame_remainingdata = '';
1058 if (ord(substr($frame_remainingdata, $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1059 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1061 $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1063 $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos +
strlen($frame_textencoding_terminator));
1064 if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
1065 // timestamp probably omitted for first data item
1067 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1068 $frame_remainingdata = substr($frame_remainingdata, 4);
1073 unset($parsedFrame['data']);
1076 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) ||
// 4.10 COMM Comments
1077 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments
1078 // There may be more than one comment frame in each tag,
1079 // but only one with the same language and content descriptor.
1080 // <Header for 'Comment', ID: 'COMM'>
1081 // Text encoding $xx
1082 // Language $xx xx xx
1083 // Short content descrip. <text string according to encoding> $00 (00)
1084 // The actual text <full text string according to encoding>
1086 if (strlen($parsedFrame['data']) < 5) {
1088 $this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
1093 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1094 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1095 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1096 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1097 $frame_textencoding_terminator = "\x00";
1099 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1101 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1102 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1103 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1105 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1106 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1107 // if description only contains a BOM or terminator then make it blank
1108 $frame_description = '';
1110 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator));
1112 $parsedFrame['encodingid'] = $frame_textencoding;
1113 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1115 $parsedFrame['language'] = $frame_language;
1116 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1117 $parsedFrame['description'] = $frame_description;
1118 $parsedFrame['data'] = $frame_text;
1119 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1120 $commentkey = ($parsedFrame['description'] ?
$parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ?
count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
1121 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ||
!array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
1122 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1124 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1130 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1131 // There may be more than one 'RVA2' frame in each tag,
1132 // but only one with the same identification string
1133 // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1134 // Identification <text string> $00
1135 // The 'identification' string is used to identify the situation and/or
1136 // device where this adjustment should apply. The following is then
1137 // repeated for every channel:
1138 // Type of channel $xx
1139 // Volume adjustment $xx xx
1140 // Bits representing peak $xx
1141 // Peak volume $xx (xx ...)
1143 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1144 $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1145 if (ord($frame_idstring) === 0) {
1146 $frame_idstring = '';
1148 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos +
strlen("\x00"));
1149 $parsedFrame['description'] = $frame_idstring;
1150 $RVA2channelcounter = 0;
1151 while (strlen($frame_remainingdata) >= 5) {
1153 $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++
, 1));
1154 $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid;
1155 $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1156 $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1158 $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++
, 1));
1159 if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) ||
($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1160 $this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
1163 $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1164 $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1165 $frame_remainingdata = substr($frame_remainingdata, $frame_offset +
$frame_bytespeakvolume);
1166 $RVA2channelcounter++
;
1168 unset($parsedFrame['data']);
1171 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) ||
// 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
1172 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only)
1173 // There may only be one 'RVA' frame in each tag
1174 // <Header for 'Relative volume adjustment', ID: 'RVA'>
1175 // ID3v2.2 => Increment/decrement %000000ba
1176 // ID3v2.3 => Increment/decrement %00fedcba
1177 // Bits used for volume descr. $xx
1178 // Relative volume change, right $xx xx (xx ...) // a
1179 // Relative volume change, left $xx xx (xx ...) // b
1180 // Peak volume right $xx xx (xx ...)
1181 // Peak volume left $xx xx (xx ...)
1182 // ID3v2.3 only, optional (not present in ID3v2.2):
1183 // Relative volume change, right back $xx xx (xx ...) // c
1184 // Relative volume change, left back $xx xx (xx ...) // d
1185 // Peak volume right back $xx xx (xx ...)
1186 // Peak volume left back $xx xx (xx ...)
1187 // ID3v2.3 only, optional (not present in ID3v2.2):
1188 // Relative volume change, center $xx xx (xx ...) // e
1189 // Peak volume center $xx xx (xx ...)
1190 // ID3v2.3 only, optional (not present in ID3v2.2):
1191 // Relative volume change, bass $xx xx (xx ...) // f
1192 // Peak volume bass $xx xx (xx ...)
1195 $frame_incrdecrflags = getid3_lib
::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++
, 1));
1196 $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1197 $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1);
1198 $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1199 $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1200 $parsedFrame['volumechange']['right'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1201 if ($parsedFrame['incdec']['right'] === false) {
1202 $parsedFrame['volumechange']['right'] *= -1;
1204 $frame_offset +
= $frame_bytesvolume;
1205 $parsedFrame['volumechange']['left'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1206 if ($parsedFrame['incdec']['left'] === false) {
1207 $parsedFrame['volumechange']['left'] *= -1;
1209 $frame_offset +
= $frame_bytesvolume;
1210 $parsedFrame['peakvolume']['right'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1211 $frame_offset +
= $frame_bytesvolume;
1212 $parsedFrame['peakvolume']['left'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1213 $frame_offset +
= $frame_bytesvolume;
1214 if ($id3v2_majorversion == 3) {
1215 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1216 if (strlen($parsedFrame['data']) > 0) {
1217 $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1218 $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1);
1219 $parsedFrame['volumechange']['rightrear'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1220 if ($parsedFrame['incdec']['rightrear'] === false) {
1221 $parsedFrame['volumechange']['rightrear'] *= -1;
1223 $frame_offset +
= $frame_bytesvolume;
1224 $parsedFrame['volumechange']['leftrear'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1225 if ($parsedFrame['incdec']['leftrear'] === false) {
1226 $parsedFrame['volumechange']['leftrear'] *= -1;
1228 $frame_offset +
= $frame_bytesvolume;
1229 $parsedFrame['peakvolume']['rightrear'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1230 $frame_offset +
= $frame_bytesvolume;
1231 $parsedFrame['peakvolume']['leftrear'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1232 $frame_offset +
= $frame_bytesvolume;
1234 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1235 if (strlen($parsedFrame['data']) > 0) {
1236 $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1237 $parsedFrame['volumechange']['center'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1238 if ($parsedFrame['incdec']['center'] === false) {
1239 $parsedFrame['volumechange']['center'] *= -1;
1241 $frame_offset +
= $frame_bytesvolume;
1242 $parsedFrame['peakvolume']['center'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1243 $frame_offset +
= $frame_bytesvolume;
1245 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1246 if (strlen($parsedFrame['data']) > 0) {
1247 $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1248 $parsedFrame['volumechange']['bass'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1249 if ($parsedFrame['incdec']['bass'] === false) {
1250 $parsedFrame['volumechange']['bass'] *= -1;
1252 $frame_offset +
= $frame_bytesvolume;
1253 $parsedFrame['peakvolume']['bass'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1254 $frame_offset +
= $frame_bytesvolume;
1257 unset($parsedFrame['data']);
1260 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
1261 // There may be more than one 'EQU2' frame in each tag,
1262 // but only one with the same identification string
1263 // <Header of 'Equalisation (2)', ID: 'EQU2'>
1264 // Interpolation method $xx
1267 // Identification <text string> $00
1268 // The following is then repeated for every adjustment point
1270 // Volume adjustment $xx xx
1273 $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1274 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1275 $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1276 if (ord($frame_idstring) === 0) {
1277 $frame_idstring = '';
1279 $parsedFrame['description'] = $frame_idstring;
1280 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos +
strlen("\x00"));
1281 while (strlen($frame_remainingdata)) {
1282 $frame_frequency = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1283 $parsedFrame['data'][$frame_frequency] = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1284 $frame_remainingdata = substr($frame_remainingdata, 4);
1286 $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1287 unset($parsedFrame['data']);
1290 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) ||
// 4.12 EQUA Equalisation (ID3v2.3 only)
1291 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only)
1292 // There may only be one 'EQUA' frame in each tag
1293 // <Header for 'Relative volume adjustment', ID: 'EQU'>
1294 // Adjustment bits $xx
1295 // This is followed by 2 bytes + ('adjustment bits' rounded up to the
1296 // nearest byte) for every equalisation band in the following format,
1297 // giving a frequency range of 0 - 32767Hz:
1298 // Increment/decrement %x (MSB of the Frequency)
1299 // Frequency (lower 15 bits)
1300 // Adjustment $xx (xx ...)
1303 $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++
, 1);
1304 $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1306 $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1307 while (strlen($frame_remainingdata) > 0) {
1308 $frame_frequencystr = getid3_lib
::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1309 $frame_incdec = (bool) substr($frame_frequencystr, 0, 1);
1310 $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1311 $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1312 $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib
::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1313 if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1314 $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1316 $frame_remainingdata = substr($frame_remainingdata, 2 +
$frame_adjustmentbytes);
1318 unset($parsedFrame['data']);
1321 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) ||
// 4.13 RVRB Reverb
1322 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb
1323 // There may only be one 'RVRB' frame in each tag.
1324 // <Header for 'Reverb', ID: 'RVRB'>
1325 // Reverb left (ms) $xx xx
1326 // Reverb right (ms) $xx xx
1327 // Reverb bounces, left $xx
1328 // Reverb bounces, right $xx
1329 // Reverb feedback, left to left $xx
1330 // Reverb feedback, left to right $xx
1331 // Reverb feedback, right to right $xx
1332 // Reverb feedback, right to left $xx
1333 // Premix left to right $xx
1334 // Premix right to left $xx
1337 $parsedFrame['left'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1339 $parsedFrame['right'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1341 $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1342 $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1343 $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1344 $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1345 $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1346 $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1347 $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1348 $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1349 unset($parsedFrame['data']);
1352 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) ||
// 4.14 APIC Attached picture
1353 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture
1354 // There may be several pictures attached to one file,
1355 // each in their individual 'APIC' frame, but only one
1356 // with the same content descriptor
1357 // <Header for 'Attached picture', ID: 'APIC'>
1358 // Text encoding $xx
1359 // ID3v2.3+ => MIME type <text string> $00
1360 // ID3v2.2 => Image format $xx xx xx
1362 // Description <text string according to encoding> $00 (00)
1363 // Picture data <binary data>
1366 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1367 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1368 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1369 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1370 $frame_textencoding_terminator = "\x00";
1373 if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1374 $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1375 if (strtolower($frame_imagetype) == 'ima') {
1376 // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1377 // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net)
1378 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1379 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1380 if (ord($frame_mimetype) === 0) {
1381 $frame_mimetype = '';
1383 $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1384 if ($frame_imagetype == 'JPEG') {
1385 $frame_imagetype = 'JPG';
1387 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1392 if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1393 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1394 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1395 if (ord($frame_mimetype) === 0) {
1396 $frame_mimetype = '';
1398 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1401 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1403 if ($frame_offset >= $parsedFrame['datalength']) {
1404 $this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] +
8 +
$frame_offset));
1406 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1407 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1408 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1410 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1411 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1412 // if description only contains a BOM or terminator then make it blank
1413 $frame_description = '';
1415 $parsedFrame['encodingid'] = $frame_textencoding;
1416 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1418 if ($id3v2_majorversion == 2) {
1419 $parsedFrame['imagetype'] = $frame_imagetype;
1421 $parsedFrame['mime'] = $frame_mimetype;
1423 $parsedFrame['picturetypeid'] = $frame_picturetype;
1424 $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype);
1425 $parsedFrame['description'] = $frame_description;
1426 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator));
1427 $parsedFrame['datalength'] = strlen($parsedFrame['data']);
1429 $parsedFrame['image_mime'] = '';
1430 $imageinfo = array();
1431 if ($imagechunkcheck = getid3_lib
::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
1432 if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1433 $parsedFrame['image_mime'] = 'image/'.getid3_lib
::ImageTypesLookup($imagechunkcheck[2]);
1434 if ($imagechunkcheck[0]) {
1435 $parsedFrame['image_width'] = $imagechunkcheck[0];
1437 if ($imagechunkcheck[1]) {
1438 $parsedFrame['image_height'] = $imagechunkcheck[1];
1444 if ($this->getid3
->option_save_attachments
=== false) {
1446 unset($parsedFrame['data']);
1449 if ($this->getid3
->option_save_attachments
=== true) {
1452 } elseif (is_int($this->getid3->option_save_attachments)) {
1453 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1455 $this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
1456 unset($parsedFrame['data']);
1460 } elseif (is_string($this->getid3
->option_save_attachments
)) {
1461 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR
, $this->getid3
->option_save_attachments
), DIRECTORY_SEPARATOR
);
1462 if (!is_dir($dir) ||
!is_writable($dir)) {
1463 // cannot write, skip
1464 $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
1465 unset($parsedFrame['data']);
1469 // if we get this far, must be OK
1470 if (is_string($this->getid3
->option_save_attachments
)) {
1471 $destination_filename = $dir.DIRECTORY_SEPARATOR
.md5($info['filenamepath']).'_'.$frame_offset;
1472 if (!file_exists($destination_filename) ||
is_writable($destination_filename)) {
1473 file_put_contents($destination_filename, $parsedFrame['data']);
1475 $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
1477 $parsedFrame['data_filename'] = $destination_filename;
1478 unset($parsedFrame['data']);
1480 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1481 if (!isset($info['id3v2']['comments']['picture'])) {
1482 $info['id3v2']['comments']['picture'] = array();
1484 $comments_picture_data = array();
1485 foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
1486 if (isset($parsedFrame[$picture_key])) {
1487 $comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
1490 $info['id3v2']['comments']['picture'][] = $comments_picture_data;
1491 unset($comments_picture_data);
1497 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) ||
// 4.15 GEOB General encapsulated object
1498 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object
1499 // There may be more than one 'GEOB' frame in each tag,
1500 // but only one with the same content descriptor
1501 // <Header for 'General encapsulated object', ID: 'GEOB'>
1502 // Text encoding $xx
1503 // MIME type <text string> $00
1504 // Filename <text string according to encoding> $00 (00)
1505 // Content description <text string according to encoding> $00 (00)
1506 // Encapsulated object <binary data>
1509 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1510 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1511 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1512 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1513 $frame_textencoding_terminator = "\x00";
1515 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1516 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1517 if (ord($frame_mimetype) === 0) {
1518 $frame_mimetype = '';
1520 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1522 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1523 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1524 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1526 $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1527 if (ord($frame_filename) === 0) {
1528 $frame_filename = '';
1530 $frame_offset = $frame_terminatorpos +
strlen($frame_textencoding_terminator);
1532 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1533 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1534 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1536 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1537 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1538 // if description only contains a BOM or terminator then make it blank
1539 $frame_description = '';
1541 $frame_offset = $frame_terminatorpos +
strlen($frame_textencoding_terminator);
1543 $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset);
1544 $parsedFrame['encodingid'] = $frame_textencoding;
1545 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1547 $parsedFrame['mime'] = $frame_mimetype;
1548 $parsedFrame['filename'] = $frame_filename;
1549 $parsedFrame['description'] = $frame_description;
1550 unset($parsedFrame['data']);
1553 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) ||
// 4.16 PCNT Play counter
1554 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter
1555 // There may only be one 'PCNT' frame in each tag.
1556 // When the counter reaches all one's, one byte is inserted in
1557 // front of the counter thus making the counter eight bits bigger
1558 // <Header for 'Play counter', ID: 'PCNT'>
1559 // Counter $xx xx xx xx (xx ...)
1561 $parsedFrame['data'] = getid3_lib
::BigEndian2Int($parsedFrame['data']);
1564 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) ||
// 4.17 POPM Popularimeter
1565 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter
1566 // There may be more than one 'POPM' frame in each tag,
1567 // but only one with the same email address
1568 // <Header for 'Popularimeter', ID: 'POPM'>
1569 // Email to user <text string> $00
1571 // Counter $xx xx xx xx (xx ...)
1574 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1575 $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1576 if (ord($frame_emailaddress) === 0) {
1577 $frame_emailaddress = '';
1579 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1580 $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1581 $parsedFrame['counter'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1582 $parsedFrame['email'] = $frame_emailaddress;
1583 $parsedFrame['rating'] = $frame_rating;
1584 unset($parsedFrame['data']);
1587 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) ||
// 4.18 RBUF Recommended buffer size
1588 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size
1589 // There may only be one 'RBUF' frame in each tag
1590 // <Header for 'Recommended buffer size', ID: 'RBUF'>
1591 // Buffer size $xx xx xx
1592 // Embedded info flag %0000000x
1593 // Offset to next tag $xx xx xx xx
1596 $parsedFrame['buffersize'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1599 $frame_embeddedinfoflags = getid3_lib
::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++
, 1));
1600 $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1601 $parsedFrame['nexttagoffset'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1602 unset($parsedFrame['data']);
1605 } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only)
1606 // There may be more than one 'CRM' frame in a tag,
1607 // but only one with the same 'owner identifier'
1608 // <Header for 'Encrypted meta frame', ID: 'CRM'>
1609 // Owner identifier <textstring> $00 (00)
1610 // Content/explanation <textstring> $00 (00)
1611 // Encrypted datablock <binary data>
1614 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1615 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1616 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1618 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1619 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1620 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1621 // if description only contains a BOM or terminator then make it blank
1622 $frame_description = '';
1624 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1626 $parsedFrame['ownerid'] = $frame_ownerid;
1627 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1628 $parsedFrame['description'] = $frame_description;
1629 unset($parsedFrame['data']);
1632 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) ||
// 4.19 AENC Audio encryption
1633 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption
1634 // There may be more than one 'AENC' frames in a tag,
1635 // but only one with the same 'Owner identifier'
1636 // <Header for 'Audio encryption', ID: 'AENC'>
1637 // Owner identifier <text string> $00
1638 // Preview start $xx xx
1639 // Preview length $xx xx
1640 // Encryption info <binary data>
1643 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1644 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1645 if (ord($frame_ownerid) === 0) {
1646 $frame_ownerid = '';
1648 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1649 $parsedFrame['ownerid'] = $frame_ownerid;
1650 $parsedFrame['previewstart'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1652 $parsedFrame['previewlength'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1654 $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1655 unset($parsedFrame['data']);
1658 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) ||
// 4.20 LINK Linked information
1659 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information
1660 // There may be more than one 'LINK' frame in a tag,
1661 // but only one with the same contents
1662 // <Header for 'Linked information', ID: 'LINK'>
1663 // ID3v2.3+ => Frame identifier $xx xx xx xx
1664 // ID3v2.2 => Frame identifier $xx xx xx
1665 // URL <text string> $00
1666 // ID and additional data <text string(s)>
1669 if ($id3v2_majorversion == 2) {
1670 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1673 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1677 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1678 $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1679 if (ord($frame_url) === 0) {
1682 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1683 $parsedFrame['url'] = $frame_url;
1685 $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1686 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1687 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback_iso88591_utf8($parsedFrame['url']);
1689 unset($parsedFrame['data']);
1692 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
1693 // There may only be one 'POSS' frame in each tag
1694 // <Head for 'Position synchronisation', ID: 'POSS'>
1695 // Time stamp format $xx
1696 // Position $xx (xx ...)
1699 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1700 $parsedFrame['position'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1701 unset($parsedFrame['data']);
1704 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only)
1705 // There may be more than one 'Terms of use' frame in a tag,
1706 // but only one with the same 'Language'
1707 // <Header for 'Terms of use frame', ID: 'USER'>
1708 // Text encoding $xx
1709 // Language $xx xx xx
1710 // The actual text <text string according to encoding>
1713 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1714 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1715 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1717 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1719 $parsedFrame['language'] = $frame_language;
1720 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1721 $parsedFrame['encodingid'] = $frame_textencoding;
1722 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1724 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1725 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1726 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib
::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1728 unset($parsedFrame['data']);
1731 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only)
1732 // There may only be one 'OWNE' frame in a tag
1733 // <Header for 'Ownership frame', ID: 'OWNE'>
1734 // Text encoding $xx
1735 // Price paid <text string> $00
1736 // Date of purch. <text string>
1737 // Seller <text string according to encoding>
1740 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1741 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1742 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1744 $parsedFrame['encodingid'] = $frame_textencoding;
1745 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1747 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1748 $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1749 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1751 $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1752 $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1753 $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3);
1755 $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1756 if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1757 $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1761 $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1762 unset($parsedFrame['data']);
1765 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only)
1766 // There may be more than one 'commercial frame' in a tag,
1767 // but no two may be identical
1768 // <Header for 'Commercial frame', ID: 'COMR'>
1769 // Text encoding $xx
1770 // Price string <text string> $00
1771 // Valid until <text string>
1772 // Contact URL <text string> $00
1774 // Name of seller <text string according to encoding> $00 (00)
1775 // Description <text string according to encoding> $00 (00)
1776 // Picture MIME type <string> $00
1777 // Seller logo <binary data>
1780 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1781 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1782 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) ||
(($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1783 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1784 $frame_textencoding_terminator = "\x00";
1787 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1788 $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1789 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1790 $frame_rawpricearray = explode('/', $frame_pricestring);
1791 foreach ($frame_rawpricearray as $key => $val) {
1792 $frame_currencyid = substr($val, 0, 3);
1793 $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1794 $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3);
1797 $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1800 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1801 $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1802 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1804 $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1806 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1807 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1808 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1810 $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1811 if (ord($frame_sellername) === 0) {
1812 $frame_sellername = '';
1814 $frame_offset = $frame_terminatorpos +
strlen($frame_textencoding_terminator);
1816 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1817 if (ord(substr($parsedFrame['data'], $frame_terminatorpos +
strlen($frame_textencoding_terminator), 1)) === 0) {
1818 $frame_terminatorpos++
; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1820 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1821 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
1822 // if description only contains a BOM or terminator then make it blank
1823 $frame_description = '';
1825 $frame_offset = $frame_terminatorpos +
strlen($frame_textencoding_terminator);
1827 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1828 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1829 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1831 $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1833 $parsedFrame['encodingid'] = $frame_textencoding;
1834 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1836 $parsedFrame['pricevaliduntil'] = $frame_datestring;
1837 $parsedFrame['contacturl'] = $frame_contacturl;
1838 $parsedFrame['receivedasid'] = $frame_receivedasid;
1839 $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid);
1840 $parsedFrame['sellername'] = $frame_sellername;
1841 $parsedFrame['description'] = $frame_description;
1842 $parsedFrame['mime'] = $frame_mimetype;
1843 $parsedFrame['logo'] = $frame_sellerlogo;
1844 unset($parsedFrame['data']);
1847 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
1848 // There may be several 'ENCR' frames in a tag,
1849 // but only one containing the same symbol
1850 // and only one containing the same owner identifier
1851 // <Header for 'Encryption method registration', ID: 'ENCR'>
1852 // Owner identifier <text string> $00
1853 // Method symbol $xx
1854 // Encryption data <binary data>
1857 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1858 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1859 if (ord($frame_ownerid) === 0) {
1860 $frame_ownerid = '';
1862 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1864 $parsedFrame['ownerid'] = $frame_ownerid;
1865 $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1866 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1869 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only)
1871 // There may be several 'GRID' frames in a tag,
1872 // but only one containing the same symbol
1873 // and only one containing the same owner identifier
1874 // <Header for 'Group ID registration', ID: 'GRID'>
1875 // Owner identifier <text string> $00
1877 // Group dependent data <binary data>
1880 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1881 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1882 if (ord($frame_ownerid) === 0) {
1883 $frame_ownerid = '';
1885 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1887 $parsedFrame['ownerid'] = $frame_ownerid;
1888 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1889 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1892 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only)
1893 // The tag may contain more than one 'PRIV' frame
1894 // but only with different contents
1895 // <Header for 'Private frame', ID: 'PRIV'>
1896 // Owner identifier <text string> $00
1897 // The private data <binary data>
1900 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1901 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1902 if (ord($frame_ownerid) === 0) {
1903 $frame_ownerid = '';
1905 $frame_offset = $frame_terminatorpos +
strlen("\x00");
1907 $parsedFrame['ownerid'] = $frame_ownerid;
1908 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1911 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only)
1912 // There may be more than one 'signature frame' in a tag,
1913 // but no two may be identical
1914 // <Header for 'Signature frame', ID: 'SIGN'>
1916 // Signature <binary data>
1919 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1920 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1923 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only)
1924 // There may only be one 'seek frame' in a tag
1925 // <Header for 'Seek frame', ID: 'SEEK'>
1926 // Minimum offset to next tag $xx xx xx xx
1929 $parsedFrame['data'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1932 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
1933 // There may only be one 'audio seek point index' frame in a tag
1934 // <Header for 'Seek Point Index', ID: 'ASPI'>
1935 // Indexed data start (S) $xx xx xx xx
1936 // Indexed data length (L) $xx xx xx xx
1937 // Number of index points (N) $xx xx
1938 // Bits per index point (b) $xx
1939 // Then for every index point the following data is included:
1940 // Fraction at index (Fi) $xx (xx)
1943 $parsedFrame['datastart'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1945 $parsedFrame['indexeddatalength'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1947 $parsedFrame['indexpoints'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1949 $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++
, 1));
1950 $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1951 for ($i = 0; $i < $parsedFrame['indexpoints']; $i++
) {
1952 $parsedFrame['indexes'][$i] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1953 $frame_offset +
= $frame_bytesperpoint;
1955 unset($parsedFrame['data']);
1957 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1958 // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1959 // There may only be one 'RGAD' frame in a tag
1960 // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1961 // Peak Amplitude $xx $xx $xx $xx
1962 // Radio Replay Gain Adjustment %aaabbbcd %dddddddd
1963 // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
1965 // b - originator code
1967 // d - replay gain adjustment
1970 $parsedFrame['peakamplitude'] = getid3_lib
::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1972 $rg_track_adjustment = getid3_lib
::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1974 $rg_album_adjustment = getid3_lib
::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1976 $parsedFrame['raw']['track']['name'] = getid3_lib
::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1977 $parsedFrame['raw']['track']['originator'] = getid3_lib
::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1978 $parsedFrame['raw']['track']['signbit'] = getid3_lib
::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1979 $parsedFrame['raw']['track']['adjustment'] = getid3_lib
::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1980 $parsedFrame['raw']['album']['name'] = getid3_lib
::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1981 $parsedFrame['raw']['album']['originator'] = getid3_lib
::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1982 $parsedFrame['raw']['album']['signbit'] = getid3_lib
::Bin2Dec(substr($rg_album_adjustment, 6, 1));
1983 $parsedFrame['raw']['album']['adjustment'] = getid3_lib
::Bin2Dec(substr($rg_album_adjustment, 7, 9));
1984 $parsedFrame['track']['name'] = getid3_lib
::RGADnameLookup($parsedFrame['raw']['track']['name']);
1985 $parsedFrame['track']['originator'] = getid3_lib
::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1986 $parsedFrame['track']['adjustment'] = getid3_lib
::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1987 $parsedFrame['album']['name'] = getid3_lib
::RGADnameLookup($parsedFrame['raw']['album']['name']);
1988 $parsedFrame['album']['originator'] = getid3_lib
::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1989 $parsedFrame['album']['adjustment'] = getid3_lib
::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1991 $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude'];
1992 $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1993 $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1994 $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1995 $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1997 unset($parsedFrame['data']);
1999 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
2000 // http://id3.org/id3v2-chapters-1.0
2001 // <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP"> (10 bytes)
2002 // Element ID <text string> $00
2003 // Start time $xx xx xx xx
2004 // End time $xx xx xx xx
2005 // Start offset $xx xx xx xx
2006 // End offset $xx xx xx xx
2007 // <Optional embedded sub-frames>
2010 @list
($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2011 $frame_offset +
= strlen($parsedFrame['element_id']."\x00");
2012 $parsedFrame['time_begin'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2014 $parsedFrame['time_end'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2016 if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
2017 // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2018 $parsedFrame['offset_begin'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2021 if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
2022 // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2023 $parsedFrame['offset_end'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2027 if ($frame_offset < strlen($parsedFrame['data'])) {
2028 $parsedFrame['subframes'] = array();
2029 while ($frame_offset < strlen($parsedFrame['data'])) {
2030 // <Optional embedded sub-frames>
2031 $subframe = array();
2032 $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
2034 $subframe['size'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2036 $subframe['flags_raw'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2038 if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2039 $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)');
2042 $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2043 $frame_offset +
= $subframe['size'];
2045 $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2046 $subframe['text'] = substr($subframe_rawdata, 1);
2047 $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
2048 $encoding_converted_text = trim(getid3_lib
::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
2049 switch (substr($encoding_converted_text, 0, 2)) {
2052 switch (strtoupper($info['id3v2']['encoding'])) {
2055 $encoding_converted_text = substr($encoding_converted_text, 2);
2056 // remove unwanted byte-order-marks
2064 // do not remove BOM
2068 if (($subframe['name'] == 'TIT2') ||
($subframe['name'] == 'TIT3')) {
2069 if ($subframe['name'] == 'TIT2') {
2070 $parsedFrame['chapter_name'] = $encoding_converted_text;
2071 } elseif ($subframe['name'] == 'TIT3') {
2072 $parsedFrame['chapter_description'] = $encoding_converted_text;
2074 $parsedFrame['subframes'][] = $subframe;
2076 $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
2079 unset($subframe_rawdata, $subframe, $encoding_converted_text);
2082 $id3v2_chapter_entry = array();
2083 foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') as $id3v2_chapter_key) {
2084 if (isset($parsedFrame[$id3v2_chapter_key])) {
2085 $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
2088 if (!isset($info['id3v2']['chapters'])) {
2089 $info['id3v2']['chapters'] = array();
2091 $info['id3v2']['chapters'][] = $id3v2_chapter_entry;
2092 unset($id3v2_chapter_entry, $id3v2_chapter_key);
2095 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
2096 // http://id3.org/id3v2-chapters-1.0
2097 // <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC"> (10 bytes)
2098 // Element ID <text string> $00
2101 // Child Element ID <string>$00 /* zero or more child CHAP or CTOC entries */
2102 // <Optional embedded sub-frames>
2105 @list
($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2106 $frame_offset +
= strlen($parsedFrame['element_id']."\x00");
2107 $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
2109 $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
2112 $terminator_position = null;
2113 for ($i = 0; $i < $parsedFrame['entry_count']; $i++
) {
2114 $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
2115 $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
2116 $frame_offset = $terminator_position +
1;
2119 $parsedFrame['ctoc_flags']['ordered'] = (bool) ($ctoc_flags_raw & 0x01);
2120 $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
2122 unset($ctoc_flags_raw, $terminator_position);
2124 if ($frame_offset < strlen($parsedFrame['data'])) {
2125 $parsedFrame['subframes'] = array();
2126 while ($frame_offset < strlen($parsedFrame['data'])) {
2127 // <Optional embedded sub-frames>
2128 $subframe = array();
2129 $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
2131 $subframe['size'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2133 $subframe['flags_raw'] = getid3_lib
::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2135 if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2136 $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)');
2139 $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2140 $frame_offset +
= $subframe['size'];
2142 $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2143 $subframe['text'] = substr($subframe_rawdata, 1);
2144 $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
2145 $encoding_converted_text = trim(getid3_lib
::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
2146 switch (substr($encoding_converted_text, 0, 2)) {
2149 switch (strtoupper($info['id3v2']['encoding'])) {
2152 $encoding_converted_text = substr($encoding_converted_text, 2);
2153 // remove unwanted byte-order-marks
2161 // do not remove BOM
2165 if (($subframe['name'] == 'TIT2') ||
($subframe['name'] == 'TIT3')) {
2166 if ($subframe['name'] == 'TIT2') {
2167 $parsedFrame['toc_name'] = $encoding_converted_text;
2168 } elseif ($subframe['name'] == 'TIT3') {
2169 $parsedFrame['toc_description'] = $encoding_converted_text;
2171 $parsedFrame['subframes'][] = $subframe;
2173 $this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
2176 unset($subframe_rawdata, $subframe, $encoding_converted_text);
2185 public function DeUnsynchronise($data) {
2186 return str_replace("\xFF\x00", "\xFF", $data);
2189 public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
2190 static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
2191 0x00 => 'No more than 128 frames and 1 MB total tag size',
2192 0x01 => 'No more than 64 frames and 128 KB total tag size',
2193 0x02 => 'No more than 32 frames and 40 KB total tag size',
2194 0x03 => 'No more than 32 frames and 4 KB total tag size',
2196 return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ?
$LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
2199 public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
2200 static $LookupExtendedHeaderRestrictionsTextEncodings = array(
2201 0x00 => 'No restrictions',
2202 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
2204 return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ?
$LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
2207 public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
2208 static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
2209 0x00 => 'No restrictions',
2210 0x01 => 'No string is longer than 1024 characters',
2211 0x02 => 'No string is longer than 128 characters',
2212 0x03 => 'No string is longer than 30 characters',
2214 return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ?
$LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
2217 public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
2218 static $LookupExtendedHeaderRestrictionsImageEncoding = array(
2219 0x00 => 'No restrictions',
2220 0x01 => 'Images are encoded only with PNG or JPEG',
2222 return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ?
$LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
2225 public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
2226 static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
2227 0x00 => 'No restrictions',
2228 0x01 => 'All images are 256x256 pixels or smaller',
2229 0x02 => 'All images are 64x64 pixels or smaller',
2230 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
2232 return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ?
$LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
2235 public function LookupCurrencyUnits($currencyid) {
2239 /** This is not a comment!
2253 BAM Convertible Marka
2270 CDF Congolese Francs
2416 XDR Special Drawing Rights
2424 ZWD Zimbabwe Dollars
2428 return getid3_lib
::EmbeddedLookup($currencyid, $begin, __LINE__
, __FILE__
, 'id3v2-currency-units');
2432 public function LookupCurrencyCountry($currencyid) {
2436 /** This is not a comment!
2438 AED United Arab Emirates
2442 ANG Netherlands Antilles
2449 BAM Bosnia and Herzegovina
2457 BND Brunei Darussalam
2479 DOP Dominican Republic
2486 EUR Euro Member Countries
2489 FKP Falkland Islands (Malvinas)
2546 MVR Maldives (Maldive Islands)
2554 NLG Netherlands (Holland)
2561 PGK Papua New Guinea
2584 STD São Tome and Principe
2594 TTD Trinidad and Tobago
2600 USD United States of America
2608 XAF Communauté Financière Africaine
2612 XDR International Monetary Fund
2614 XPF Comptoirs Français du Pacifique
2624 return getid3_lib
::EmbeddedLookup($currencyid, $begin, __LINE__
, __FILE__
, 'id3v2-currency-country');
2629 public static function LanguageLookup($languagecode, $casesensitive=false) {
2631 if (!$casesensitive) {
2632 $languagecode = strtolower($languagecode);
2635 // http://www.id3.org/id3v2.4.0-structure.txt
2636 // [4. ID3v2 frame overview]
2637 // The three byte language field, present in several frames, is used to
2638 // describe the language of the frame's content, according to ISO-639-2
2639 // [ISO-639-2]. The language should be represented in lower case. If the
2640 // language is not known the string "XXX" should be used.
2643 // ISO 639-2 - http://www.id3.org/iso639-2.html
2647 /** This is not a comment!
2656 afa Afro-Asiatic (Other)
2663 alg Algonquian Languages
2665 ang English, Old (ca. 450-1100)
2666 apa Apache Languages
2672 art Artificial (Other)
2675 ath Athapascan Languages
2682 bai Bamileke Languages
2710 cai Central American Indian (Other)
2713 cau Caucasian (Other)
2732 cpe Creoles and Pidgins, English-based (Other)
2733 cpf Creoles and Pidgins, French-based (Other)
2734 cpp Creoles and Pidgins, Portuguese-based (Other)
2736 crp Creoles and Pidgins (Other)
2737 cus Cushitic (Other)
2747 dra Dravidian (Other)
2749 dum Dutch, Middle (ca. 1050-1350)
2754 egy Egyptian (Ancient)
2756 ell Greek, Modern (1453-)
2759 enm English, Middle (ca. 1100-1500)
2773 fiu Finno-Ugrian (Other)
2777 frm French, Middle (ca. 1400-1600)
2778 fro French, Old (842- ca. 1400)
2786 gem Germanic (Other)
2792 gmh German, Middle High (ca. 1050-1500)
2793 goh German, Old High (ca. 750-1050)
2797 grc Greek, Ancient (to 1453)
2798 gre Greek, Modern (1453-)
2819 ina Interlingua (International Auxiliary language Association)
2822 ine Indo-European (Other)
2882 luo Luo (Kenya and Tanzania)
2893 map Austronesian (Other)
2899 mga Irish, Middle (900 - 1200)
2902 mis Miscellaneous (Other)
2903 mkh Mon-Kmer (Other)
2907 mno Manobo Languages
2914 mul Multiple Languages
2921 nai North American Indian (Other)
2929 nic Niger-Kordofanian (Other)
2932 nno Norwegian (Nynorsk)
2936 nub Nubian Languages
2942 oci Langue d'Oc (post 1500)
2948 ota Turkish, Ottoman (1500 - 1928)
2949 oto Otomian Languages
2950 paa Papuan-Australian (Other)
2957 peo Persian, Old (ca 600 - 400 B.C.)
2965 pro Provencal, Old (to 1500)
2980 sai South American Indian (Other)
2981 sal Salishan Languages
2982 sam Samaritan Aramaic
2988 sga Irish, Old (to 900)
2992 sio Siouan Languages
2993 sit Sino-Tibetan (Other)
3010 ssa Nilo-Saharan (Other)
3037 ton Tonga (Tonga Islands)
3060 wak Wakashan Languages
3065 wen Sorbian Languages
3081 return getid3_lib
::EmbeddedLookup($languagecode, $begin, __LINE__
, __FILE__
, 'id3v2-languagecode');
3085 public static function ETCOEventLookup($index) {
3086 if (($index >= 0x17) && ($index <= 0xDF)) {
3087 return 'reserved for future use';
3089 if (($index >= 0xE0) && ($index <= 0xEF)) {
3090 return 'not predefined synch 0-F';
3092 if (($index >= 0xF0) && ($index <= 0xFC)) {
3093 return 'reserved for future use';
3096 static $EventLookup = array(
3097 0x00 => 'padding (has no meaning)',
3098 0x01 => 'end of initial silence',
3099 0x02 => 'intro start',
3100 0x03 => 'main part start',
3101 0x04 => 'outro start',
3102 0x05 => 'outro end',
3103 0x06 => 'verse start',
3104 0x07 => 'refrain start',
3105 0x08 => 'interlude start',
3106 0x09 => 'theme start',
3107 0x0A => 'variation start',
3108 0x0B => 'key change',
3109 0x0C => 'time change',
3110 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
3111 0x0E => 'sustained noise',
3112 0x0F => 'sustained noise end',
3113 0x10 => 'intro end',
3114 0x11 => 'main part end',
3115 0x12 => 'verse end',
3116 0x13 => 'refrain end',
3117 0x14 => 'theme end',
3118 0x15 => 'profanity',
3119 0x16 => 'profanity end',
3120 0xFD => 'audio end (start of silence)',
3121 0xFE => 'audio file ends',
3122 0xFF => 'one more byte of events follows'
3125 return (isset($EventLookup[$index]) ?
$EventLookup[$index] : '');
3128 public static function SYTLContentTypeLookup($index) {
3129 static $SYTLContentTypeLookup = array(
3132 0x02 => 'text transcription',
3133 0x03 => 'movement/part name', // (e.g. 'Adagio')
3134 0x04 => 'events', // (e.g. 'Don Quijote enters the stage')
3135 0x05 => 'chord', // (e.g. 'Bb F Fsus')
3136 0x06 => 'trivia/\'pop up\' information',
3137 0x07 => 'URLs to webpages',
3138 0x08 => 'URLs to images'
3141 return (isset($SYTLContentTypeLookup[$index]) ?
$SYTLContentTypeLookup[$index] : '');
3144 public static function APICPictureTypeLookup($index, $returnarray=false) {
3145 static $APICPictureTypeLookup = array(
3147 0x01 => '32x32 pixels \'file icon\' (PNG only)',
3148 0x02 => 'Other file icon',
3149 0x03 => 'Cover (front)',
3150 0x04 => 'Cover (back)',
3151 0x05 => 'Leaflet page',
3152 0x06 => 'Media (e.g. label side of CD)',
3153 0x07 => 'Lead artist/lead performer/soloist',
3154 0x08 => 'Artist/performer',
3155 0x09 => 'Conductor',
3156 0x0A => 'Band/Orchestra',
3158 0x0C => 'Lyricist/text writer',
3159 0x0D => 'Recording Location',
3160 0x0E => 'During recording',
3161 0x0F => 'During performance',
3162 0x10 => 'Movie/video screen capture',
3163 0x11 => 'A bright coloured fish',
3164 0x12 => 'Illustration',
3165 0x13 => 'Band/artist logotype',
3166 0x14 => 'Publisher/Studio logotype'
3169 return $APICPictureTypeLookup;
3171 return (isset($APICPictureTypeLookup[$index]) ?
$APICPictureTypeLookup[$index] : '');
3174 public static function COMRReceivedAsLookup($index) {
3175 static $COMRReceivedAsLookup = array(
3177 0x01 => 'Standard CD album with other songs',
3178 0x02 => 'Compressed audio on CD',
3179 0x03 => 'File over the Internet',
3180 0x04 => 'Stream over the Internet',
3181 0x05 => 'As note sheets',
3182 0x06 => 'As note sheets in a book with other sheets',
3183 0x07 => 'Music on other media',
3184 0x08 => 'Non-musical merchandise'
3187 return (isset($COMRReceivedAsLookup[$index]) ?
$COMRReceivedAsLookup[$index] : '');
3190 public static function RVA2ChannelTypeLookup($index) {
3191 static $RVA2ChannelTypeLookup = array(
3193 0x01 => 'Master volume',
3194 0x02 => 'Front right',
3195 0x03 => 'Front left',
3196 0x04 => 'Back right',
3197 0x05 => 'Back left',
3198 0x06 => 'Front centre',
3199 0x07 => 'Back centre',
3203 return (isset($RVA2ChannelTypeLookup[$index]) ?
$RVA2ChannelTypeLookup[$index] : '');
3206 public static function FrameNameLongLookup($framename) {
3210 /** This is not a comment!
3212 AENC Audio encryption
3213 APIC Attached picture
3214 ASPI Audio seek point index
3215 BUF Recommended buffer size
3219 COMR Commercial frame
3220 CRA Audio encryption
3221 CRM Encrypted meta frame
3222 ENCR Encryption method registration
3224 EQU2 Equalisation (2)
3226 ETC Event timing codes
3227 ETCO Event timing codes
3228 GEO General encapsulated object
3229 GEOB General encapsulated object
3230 GRID Group identification registration
3231 IPL Involved people list
3232 IPLS Involved people list
3233 LINK Linked information
3234 LNK Linked information
3235 MCDI Music CD identifier
3236 MCI Music CD Identifier
3237 MLL MPEG location lookup table
3238 MLLT MPEG location lookup table
3239 OWNE Ownership frame
3241 PIC Attached picture
3244 POSS Position synchronisation frame
3246 RBUF Recommended buffer size
3248 RVA Relative volume adjustment
3249 RVA2 Relative volume adjustment (2)
3250 RVAD Relative volume adjustment
3253 SIGN Signature frame
3254 SLT Synchronised lyric/text
3255 STC Synced tempo codes
3256 SYLT Synchronised lyric/text
3257 SYTC Synchronised tempo codes
3258 TAL Album/Movie/Show title
3259 TALB Album/Movie/Show title
3260 TBP BPM (Beats Per Minute)
3261 TBPM BPM (beats per minute)
3263 TCMP Part of a compilation
3267 TCOP Copyright message
3268 TCP Part of a compilation
3269 TCR Copyright message
3274 TDOR Original release time
3281 TEXT Lyricist/Text writer
3286 TIPL Involved people list
3287 TIT1 Content group description
3288 TIT2 Title/songname/content description
3289 TIT3 Subtitle/Description refinement
3296 TMCL Musician credits list
3300 TOA Original artist(s)/performer(s)
3301 TOAL Original album/movie/show title
3302 TOF Original filename
3303 TOFN Original filename
3304 TOL Original Lyricist(s)/text writer(s)
3305 TOLY Original lyricist(s)/text writer(s)
3306 TOPE Original artist(s)/performer(s)
3307 TOR Original release year
3308 TORY Original release year
3309 TOT Original album/Movie/Show title
3310 TOWN File owner/licensee
3311 TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3312 TP2 Band/Orchestra/Accompaniment
3313 TP3 Conductor/Performer refinement
3314 TP4 Interpreted, remixed, or otherwise modified by
3317 TPE1 Lead performer(s)/Soloist(s)
3318 TPE2 Band/orchestra/accompaniment
3319 TPE3 Conductor/performer refinement
3320 TPE4 Interpreted, remixed, or otherwise modified by
3322 TPRO Produced notice
3324 TRC ISRC (International Standard Recording Code)
3325 TRCK Track number/Position in set
3327 TRDA Recording dates
3328 TRK Track number/Position in set
3329 TRSN Internet radio station name
3330 TRSO Internet radio station owner
3331 TS2 Album-Artist sort order
3332 TSA Album sort order
3333 TSC Composer sort order
3336 TSO2 Album-Artist sort order
3337 TSOA Album sort order
3338 TSOC Composer sort order
3339 TSOP Performer sort order
3340 TSOT Title sort order
3341 TSP Performer sort order
3342 TSRC ISRC (international standard recording code)
3343 TSS Software/hardware and settings used for encoding
3344 TSSE Software/Hardware and settings used for encoding
3346 TST Title sort order
3347 TT1 Content group description
3348 TT2 Title/Songname/Content description
3349 TT3 Subtitle/Description refinement
3350 TXT Lyricist/text writer
3351 TXX User defined text information frame
3352 TXXX User defined text information frame
3355 UFI Unique file identifier
3356 UFID Unique file identifier
3357 ULT Unsychronised lyric/text transcription
3359 USLT Unsynchronised lyric/text transcription
3360 WAF Official audio file webpage
3361 WAR Official artist/performer webpage
3362 WAS Official audio source webpage
3363 WCM Commercial information
3364 WCOM Commercial information
3365 WCOP Copyright/Legal information
3366 WCP Copyright/Legal information
3367 WOAF Official audio file webpage
3368 WOAR Official artist/performer webpage
3369 WOAS Official audio source webpage
3370 WORS Official Internet radio station homepage
3372 WPB Publishers official webpage
3373 WPUB Publishers official webpage
3374 WXX User defined URL link frame
3375 WXXX User defined URL link frame
3376 TFEA Featured Artist
3377 TSTU Recording Studio
3378 rgad Replay Gain Adjustment
3382 return getid3_lib
::EmbeddedLookup($framename, $begin, __LINE__
, __FILE__
, 'id3v2-framename_long');
3385 // from Helium2 [www.helium2.com]
3386 // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3390 public static function FrameNameShortLookup($framename) {
3394 /** This is not a comment!
3396 AENC audio_encryption
3397 APIC attached_picture
3398 ASPI audio_seek_point_index
3399 BUF recommended_buffer_size
3403 COMR commercial_frame
3404 CRA audio_encryption
3405 CRM encrypted_meta_frame
3406 ENCR encryption_method_registration
3410 ETC event_timing_codes
3411 ETCO event_timing_codes
3412 GEO general_encapsulated_object
3413 GEOB general_encapsulated_object
3414 GRID group_identification_registration
3415 IPL involved_people_list
3416 IPLS involved_people_list
3417 LINK linked_information
3418 LNK linked_information
3419 MCDI music_cd_identifier
3420 MCI music_cd_identifier
3421 MLL mpeg_location_lookup_table
3422 MLLT mpeg_location_lookup_table
3423 OWNE ownership_frame
3425 PIC attached_picture
3428 POSS position_synchronisation_frame
3430 RBUF recommended_buffer_size
3432 RVA relative_volume_adjustment
3433 RVA2 relative_volume_adjustment
3434 RVAD relative_volume_adjustment
3437 SIGN signature_frame
3438 SLT synchronised_lyric
3439 STC synced_tempo_codes
3440 SYLT synchronised_lyric
3441 SYTC synchronised_tempo_codes
3447 TCMP part_of_a_compilation
3451 TCOP copyright_message
3452 TCP part_of_a_compilation
3453 TCR copyright_message
3458 TDOR original_release_time
3470 TIPL involved_people_list
3471 TIT1 content_group_description
3480 TMCL musician_credits_list
3486 TOF original_filename
3487 TOFN original_filename
3488 TOL original_lyricist
3489 TOLY original_lyricist
3490 TOPE original_artist
3506 TPRO produced_notice
3511 TRDA recording_dates
3513 TRSN internet_radio_station_name
3514 TRSO internet_radio_station_owner
3515 TS2 album_artist_sort_order
3516 TSA album_sort_order
3517 TSC composer_sort_order
3520 TSO2 album_artist_sort_order
3521 TSOA album_sort_order
3522 TSOC composer_sort_order
3523 TSOP performer_sort_order
3524 TSOT title_sort_order
3525 TSP performer_sort_order
3527 TSS encoder_settings
3528 TSSE encoder_settings
3530 TST title_sort_order
3531 TT1 content_group_description
3539 UFI unique_file_identifier
3540 UFID unique_file_identifier
3541 ULT unsychronised_lyric
3543 USLT unsynchronised_lyric
3547 WCM commercial_information
3548 WCOM commercial_information
3560 TFEA featured_artist
3561 TSTU recording_studio
3562 rgad replay_gain_adjustment
3566 return getid3_lib
::EmbeddedLookup($framename, $begin, __LINE__
, __FILE__
, 'id3v2-framename_short');
3569 public static function TextEncodingTerminatorLookup($encoding) {
3570 // http://www.id3.org/id3v2.4.0-structure.txt
3571 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3572 static $TextEncodingTerminatorLookup = array(
3573 0 => "\x00", // $00 ISO-8859-1. Terminated with $00.
3574 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.
3575 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3576 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
3579 return (isset($TextEncodingTerminatorLookup[$encoding]) ?
$TextEncodingTerminatorLookup[$encoding] : "\x00");
3582 public static function TextEncodingNameLookup($encoding) {
3583 // http://www.id3.org/id3v2.4.0-structure.txt
3584 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3585 static $TextEncodingNameLookup = array(
3586 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00.
3587 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.
3588 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3589 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00.
3592 return (isset($TextEncodingNameLookup[$encoding]) ?
$TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3595 public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3596 switch ($id3v2majorversion) {
3598 return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3603 return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3609 public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3610 for ($i = 0; $i < strlen($numberstring); $i++
) {
3611 if ((chr($numberstring{$i}) < chr('0')) ||
(chr($numberstring{$i}) > chr('9'))) {
3612 if (($numberstring{$i} == '.') && $allowdecimal) {
3614 } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) {
3624 public static function IsValidDateStampString($datestamp) {
3625 if (strlen($datestamp) != 8) {
3628 if (!self
::IsANumber($datestamp, false)) {
3631 $year = substr($datestamp, 0, 4);
3632 $month = substr($datestamp, 4, 2);
3633 $day = substr($datestamp, 6, 2);
3634 if (($year == 0) ||
($month == 0) ||
($day == 0)) {
3643 if (($day > 30) && (($month == 4) ||
($month == 6) ||
($month == 9) ||
($month == 11))) {
3646 if (($day > 29) && ($month == 2)) {
3652 public static function ID3v2HeaderLength($majorversion) {
3653 return (($majorversion == 2) ?
6 : 10);
3656 public static function ID3v22iTunesBrokenFrameName($frame_name) {
3657 // iTunes (multiple versions) has been known to write ID3v2.3 style frames
3658 // but use ID3v2.2 frame names, right-padded using either [space] or [null]
3659 // to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
3660 // This function will detect and translate the corrupt frame name into ID3v2.3 standard.
3661 static $ID3v22_iTunes_BrokenFrames = array(
3662 'BUF' => 'RBUF', // Recommended buffer size
3663 'CNT' => 'PCNT', // Play counter
3664 'COM' => 'COMM', // Comments
3665 'CRA' => 'AENC', // Audio encryption
3666 'EQU' => 'EQUA', // Equalisation
3667 'ETC' => 'ETCO', // Event timing codes
3668 'GEO' => 'GEOB', // General encapsulated object
3669 'IPL' => 'IPLS', // Involved people list
3670 'LNK' => 'LINK', // Linked information
3671 'MCI' => 'MCDI', // Music CD identifier
3672 'MLL' => 'MLLT', // MPEG location lookup table
3673 'PIC' => 'APIC', // Attached picture
3674 'POP' => 'POPM', // Popularimeter
3675 'REV' => 'RVRB', // Reverb
3676 'RVA' => 'RVAD', // Relative volume adjustment
3677 'SLT' => 'SYLT', // Synchronised lyric/text
3678 'STC' => 'SYTC', // Synchronised tempo codes
3679 'TAL' => 'TALB', // Album/Movie/Show title
3680 'TBP' => 'TBPM', // BPM (beats per minute)
3681 'TCM' => 'TCOM', // Composer
3682 'TCO' => 'TCON', // Content type
3683 'TCP' => 'TCMP', // Part of a compilation
3684 'TCR' => 'TCOP', // Copyright message
3685 'TDA' => 'TDAT', // Date
3686 'TDY' => 'TDLY', // Playlist delay
3687 'TEN' => 'TENC', // Encoded by
3688 'TFT' => 'TFLT', // File type
3689 'TIM' => 'TIME', // Time
3690 'TKE' => 'TKEY', // Initial key
3691 'TLA' => 'TLAN', // Language(s)
3692 'TLE' => 'TLEN', // Length
3693 'TMT' => 'TMED', // Media type
3694 'TOA' => 'TOPE', // Original artist(s)/performer(s)
3695 'TOF' => 'TOFN', // Original filename
3696 'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
3697 'TOR' => 'TORY', // Original release year
3698 'TOT' => 'TOAL', // Original album/movie/show title
3699 'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
3700 'TP2' => 'TPE2', // Band/orchestra/accompaniment
3701 'TP3' => 'TPE3', // Conductor/performer refinement
3702 'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
3703 'TPA' => 'TPOS', // Part of a set
3704 'TPB' => 'TPUB', // Publisher
3705 'TRC' => 'TSRC', // ISRC (international standard recording code)
3706 'TRD' => 'TRDA', // Recording dates
3707 'TRK' => 'TRCK', // Track number/Position in set
3708 'TS2' => 'TSO2', // Album-Artist sort order
3709 'TSA' => 'TSOA', // Album sort order
3710 'TSC' => 'TSOC', // Composer sort order
3711 'TSI' => 'TSIZ', // Size
3712 'TSP' => 'TSOP', // Performer sort order
3713 'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
3714 'TST' => 'TSOT', // Title sort order
3715 'TT1' => 'TIT1', // Content group description
3716 'TT2' => 'TIT2', // Title/songname/content description
3717 'TT3' => 'TIT3', // Subtitle/Description refinement
3718 'TXT' => 'TEXT', // Lyricist/Text writer
3719 'TXX' => 'TXXX', // User defined text information frame
3720 'TYE' => 'TYER', // Year
3721 'UFI' => 'UFID', // Unique file identifier
3722 'ULT' => 'USLT', // Unsynchronised lyric/text transcription
3723 'WAF' => 'WOAF', // Official audio file webpage
3724 'WAR' => 'WOAR', // Official artist/performer webpage
3725 'WAS' => 'WOAS', // Official audio source webpage
3726 'WCM' => 'WCOM', // Commercial information
3727 'WCP' => 'WCOP', // Copyright/Legal information
3728 'WPB' => 'WPUB', // Publishers official webpage
3729 'WXX' => 'WXXX', // User defined URL link frame
3731 if (strlen($frame_name) == 4) {
3732 if ((substr($frame_name, 3, 1) == ' ') ||
(substr($frame_name, 3, 1) == "\x00")) {
3733 if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) {
3734 return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)];