2 /////////////////////////////////////////////////////////////////
3 /// getID3() by James Heinrich <info@getid3.org> //
4 // available at http://getid3.sourceforge.net //
5 // or http://www.getid3.org //
6 /////////////////////////////////////////////////////////////////
7 // See readme.txt for more details //
8 /////////////////////////////////////////////////////////////////
10 // module.tag.id3v2.php //
11 // module for analyzing ID3v2 tags //
12 // dependencies: module.tag.id3v1.php //
14 /////////////////////////////////////////////////////////////////
16 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
18 class getid3_id3v2 extends getid3_handler
20 public $StartingOffset = 0;
22 public function Analyze() {
23 $info = &$this->getid3->info;
25 // Overall tag structure:
26 // +-----------------------------+
27 // | Header (10 bytes) |
28 // +-----------------------------+
29 // | Extended Header |
30 // | (variable length, OPTIONAL) |
31 // +-----------------------------+
32 // | Frames (variable length) |
33 // +-----------------------------+
35 // | (variable length, OPTIONAL) |
36 // +-----------------------------+
37 // | Footer (10 bytes, OPTIONAL) |
38 // +-----------------------------+
41 // ID3v2/file identifier "ID3"
42 // ID3v2 version $04 00
43 // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
44 // ID3v2 size 4 * %0xxxxxxx
48 $info['id3v2']['header'] = true;
49 $thisfile_id3v2 = &$info['id3v2'];
50 $thisfile_id3v2['flags'] = array();
51 $thisfile_id3v2_flags = &$thisfile_id3v2['flags'];
54 fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET);
55 $header = fread($this->getid3->fp, 10);
56 if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) {
58 $thisfile_id3v2['majorversion'] = ord($header{3});
59 $thisfile_id3v2['minorversion'] = ord($header{4});
62 $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
66 unset($info['id3v2']);
71 if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
73 $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
78 $id3_flags = ord($header{5});
79 switch ($id3v2_majorversion) {
82 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
83 $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
88 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
89 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
90 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
95 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
96 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
97 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
98 $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present
102 $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
104 $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
105 $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
109 // create 'encoding' key - used by getid3::HandleAllTags()
110 // in ID3v2 every field can have it's own encoding type
111 // so force everything to UTF-8 so it can be handled consistantly
112 $thisfile_id3v2['encoding'] = 'UTF-8';
117 // All ID3v2 frames consists of one frame header followed by one or more
118 // fields containing the actual information. The header is always 10
119 // bytes and laid out as follows:
121 // Frame ID $xx xx xx xx (four characters)
122 // Size 4 * %0xxxxxxx
125 $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
126 if (!empty($thisfile_id3v2['exthead']['length'])) {
127 $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
129 if (!empty($thisfile_id3v2_flags['isfooter'])) {
130 $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
132 if ($sizeofframes > 0) {
134 $framedata = fread($this->getid3->fp, $sizeofframes); // read all frames from file into $framedata variable
136 // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
137 if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
138 $framedata = $this->DeUnsynchronise($framedata);
140 // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
141 // of on tag level, making it easier to skip frames, increasing the streamability
142 // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
143 // there exists an unsynchronised frame, while the new unsynchronisation flag in
144 // the frame header [S:4.1.2] indicates unsynchronisation.
147 //$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)
148 $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
152 if (!empty($thisfile_id3v2_flags['exthead'])) {
153 $extended_header_offset = 0;
155 if ($id3v2_majorversion == 3) {
158 //Extended header size $xx xx xx xx // 32-bit integer
159 //Extended Flags $xx xx
160 // %x0000000 %00000000 // v2.3
161 // x - CRC data present
162 //Size of padding $xx xx xx xx
164 $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
165 $extended_header_offset += 4;
167 $thisfile_id3v2['exthead']['flag_bytes'] = 2;
168 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
169 $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
171 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
173 $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
174 $extended_header_offset += 4;
176 if ($thisfile_id3v2['exthead']['flags']['crc']) {
177 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
178 $extended_header_offset += 4;
180 $extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
182 } elseif ($id3v2_majorversion == 4) {
185 //Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer
186 //Number of flag bytes $01
189 // b - Tag is an update
190 // Flag data length $00
191 // c - CRC data present
192 // Flag data length $05
193 // Total frame CRC 5 * %0xxxxxxx
194 // d - Tag restrictions
195 // Flag data length $01
197 $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
198 $extended_header_offset += 4;
200 $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
201 $extended_header_offset += 1;
203 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
204 $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
206 $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
207 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
208 $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
210 if ($thisfile_id3v2['exthead']['flags']['update']) {
211 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
212 $extended_header_offset += 1;
215 if ($thisfile_id3v2['exthead']['flags']['crc']) {
216 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
217 $extended_header_offset += 1;
218 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
219 $extended_header_offset += $ext_header_chunk_length;
222 if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
223 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
224 $extended_header_offset += 1;
227 $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
228 $extended_header_offset += 1;
229 $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
230 $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
231 $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
232 $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
233 $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
235 $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
236 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
237 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
238 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
239 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
242 if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
243 $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
247 $framedataoffset += $extended_header_offset;
248 $framedata = substr($framedata, $extended_header_offset);
249 } // end extended header
252 while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
253 if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
254 // insufficient room left in ID3v2 header for actual data - must be padding
255 $thisfile_id3v2['padding']['start'] = $framedataoffset;
256 $thisfile_id3v2['padding']['length'] = strlen($framedata);
257 $thisfile_id3v2['padding']['valid'] = true;
258 for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
259 if ($framedata{$i} != "\x00") {
260 $thisfile_id3v2['padding']['valid'] = false;
261 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
262 $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
266 break; // skip rest of ID3v2 header
268 if ($id3v2_majorversion == 2) {
269 // Frame ID $xx xx xx (three characters)
270 // Size $xx xx xx (24-bit integer)
273 $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
274 $framedata = substr($framedata, 6); // and leave the rest in $framedata
275 $frame_name = substr($frame_header, 0, 3);
276 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
277 $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
279 } elseif ($id3v2_majorversion > 2) {
281 // Frame ID $xx xx xx xx (four characters)
282 // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
285 $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
286 $framedata = substr($framedata, 10); // and leave the rest in $framedata
288 $frame_name = substr($frame_header, 0, 4);
289 if ($id3v2_majorversion == 3) {
290 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
292 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
295 if ($frame_size < (strlen($framedata) + 4)) {
296 $nextFrameID = substr($framedata, $frame_size, 4);
297 if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
299 } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
300 // MP3ext known broken frames - "ok" for the purposes of this test
301 } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
302 $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
303 $id3v2_majorversion = 3;
304 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
309 $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
312 if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
313 // padding encountered
315 $thisfile_id3v2['padding']['start'] = $framedataoffset;
316 $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
317 $thisfile_id3v2['padding']['valid'] = true;
319 $len = strlen($framedata);
320 for ($i = 0; $i < $len; $i++) {
321 if ($framedata{$i} != "\x00") {
322 $thisfile_id3v2['padding']['valid'] = false;
323 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
324 $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
328 break; // skip rest of ID3v2 header
331 if ($frame_name == 'COM ') {
332 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
333 $frame_name = 'COMM';
335 if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
338 $parsedFrame['frame_name'] = $frame_name;
339 $parsedFrame['frame_flags_raw'] = $frame_flags;
340 $parsedFrame['data'] = substr($framedata, 0, $frame_size);
341 $parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size);
342 $parsedFrame['dataoffset'] = $framedataoffset;
344 $this->ParseID3v2Frame($parsedFrame);
345 $thisfile_id3v2[$frame_name][] = $parsedFrame;
347 $framedata = substr($framedata, $frame_size);
349 } else { // invalid frame length or FrameID
351 if ($frame_size <= strlen($framedata)) {
353 if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
355 // next frame is valid, just skip the current frame
356 $framedata = substr($framedata, $frame_size);
357 $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
361 // next frame is invalid too, abort processing
364 $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
368 } elseif ($frame_size == strlen($framedata)) {
370 // this is the last frame, just skip
371 $info['warning'][] = 'This was the last ID3v2 frame.';
375 // next frame is invalid too, abort processing
378 $info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
381 if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
383 switch ($frame_name) {
384 case "\x00\x00".'MP':
391 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
395 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
399 } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
401 $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).';
405 $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
410 $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
419 // The footer is a copy of the header, but with a different identifier.
420 // ID3v2 identifier "3DI"
421 // ID3v2 version $04 00
422 // ID3v2 flags %abcd0000
423 // ID3v2 size 4 * %0xxxxxxx
425 if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
426 $footer = fread($this->getid3->fp, 10);
427 if (substr($footer, 0, 3) == '3DI') {
428 $thisfile_id3v2['footer'] = true;
429 $thisfile_id3v2['majorversion_footer'] = ord($footer{3});
430 $thisfile_id3v2['minorversion_footer'] = ord($footer{4});
432 if ($thisfile_id3v2['majorversion_footer'] <= 4) {
433 $id3_flags = ord(substr($footer{5}));
434 $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80);
435 $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40);
436 $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20);
437 $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
439 $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
443 if (isset($thisfile_id3v2['comments']['genre'])) {
444 foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
445 unset($thisfile_id3v2['comments']['genre'][$key]);
446 $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
450 if (isset($thisfile_id3v2['comments']['track'])) {
451 foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
452 if (strstr($value, '/')) {
453 list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
458 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)) {
459 $thisfile_id3v2['comments']['year'] = array($matches[1]);
463 if (!empty($thisfile_id3v2['TXXX'])) {
464 // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
465 foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
466 switch ($txxx_array['description']) {
467 case 'replaygain_track_gain':
468 if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
469 $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
472 case 'replaygain_track_peak':
473 if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
474 $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
477 case 'replaygain_album_gain':
478 if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
479 $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
488 $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
489 if (isset($thisfile_id3v2['footer'])) {
490 $info['avdataoffset'] += 10;
497 public function ParseID3v2GenreString($genrestring) {
498 // Parse genres into arrays of genreName and genreID
499 // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
500 // ID3v2.4.x: '21' $00 'Eurodisco' $00
501 $clean_genres = array();
502 if (strpos($genrestring, "\x00") === false) {
503 $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
505 $genre_elements = explode("\x00", $genrestring);
506 foreach ($genre_elements as $element) {
507 $element = trim($element);
509 if (preg_match('#^[0-9]{1,3}#', $element)) {
510 $clean_genres[] = getid3_id3v1::LookupGenreName($element);
512 $clean_genres[] = str_replace('((', '(', $element);
516 return $clean_genres;
520 public function ParseID3v2Frame(&$parsedFrame) {
523 $info = &$this->getid3->info;
524 $id3v2_majorversion = $info['id3v2']['majorversion'];
526 $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']);
527 if (empty($parsedFrame['framenamelong'])) {
528 unset($parsedFrame['framenamelong']);
530 $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
531 if (empty($parsedFrame['framenameshort'])) {
532 unset($parsedFrame['framenameshort']);
535 if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
536 if ($id3v2_majorversion == 3) {
537 // Frame Header Flags
538 // %abc00000 %ijk00000
539 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
540 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
541 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
542 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
543 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
544 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
546 } elseif ($id3v2_majorversion == 4) {
547 // Frame Header Flags
548 // %0abc0000 %0h00kmnp
549 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
550 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
551 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
552 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
553 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
554 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
555 $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
556 $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
558 // Frame-level de-unsynchronisation - ID3v2.4
559 if ($parsedFrame['flags']['Unsynchronisation']) {
560 $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
563 if ($parsedFrame['flags']['DataLengthIndicator']) {
564 $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
565 $parsedFrame['data'] = substr($parsedFrame['data'], 4);
569 // Frame-level de-compression
570 if ($parsedFrame['flags']['compression']) {
571 $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
572 if (!function_exists('gzuncompress')) {
573 $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
575 if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
576 //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
577 $parsedFrame['data'] = $decompresseddata;
578 unset($decompresseddata);
580 $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
586 if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
587 if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
588 $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
592 if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
594 $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
595 switch ($parsedFrame['frame_name']) {
597 $warning .= ' (this is known to happen with files tagged by RioPort)';
603 $info['warning'][] = $warning;
605 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier
606 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier
607 // There may be more than one 'UFID' frame in a tag,
608 // but only one with the same 'Owner identifier'.
609 // <Header for 'Unique file identifier', ID: 'UFID'>
610 // Owner identifier <text string> $00
611 // Identifier <up to 64 bytes binary data>
612 $exploded = explode("\x00", $parsedFrame['data'], 2);
613 $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
614 $parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : '');
616 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
617 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame
618 // There may be more than one 'TXXX' frame in each tag,
619 // but only one with the same description.
620 // <Header for 'User defined text information frame', ID: 'TXXX'>
622 // Description <text string according to encoding> $00 (00)
623 // Value <text string according to encoding>
626 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
628 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
629 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
631 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
632 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
633 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
635 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
636 if (ord($frame_description) === 0) {
637 $frame_description = '';
639 $parsedFrame['encodingid'] = $frame_textencoding;
640 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
642 $parsedFrame['description'] = $frame_description;
643 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
644 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
645 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
647 //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
650 } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
651 // There may only be one text information frame of its kind in an tag.
652 // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
653 // excluding 'TXXX' described in 4.2.6.>
655 // Information <text string(s) according to encoding>
658 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
659 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
660 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
663 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
665 $parsedFrame['encodingid'] = $frame_textencoding;
666 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
668 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
669 // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
670 // This of course breaks when an artist name contains slash character, e.g. "AC/DC"
671 // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
672 // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
673 switch ($parsedFrame['encoding']) {
685 $Txxx_elements = array();
686 $Txxx_elements_start_offset = 0;
687 for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
688 if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
689 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
690 $Txxx_elements_start_offset = $i + $wordsize;
693 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
694 foreach ($Txxx_elements as $Txxx_element) {
695 $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
696 if (!empty($string)) {
697 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
700 unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
703 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
704 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame
705 // There may be more than one 'WXXX' frame in each tag,
706 // but only one with the same description
707 // <Header for 'User defined URL link frame', ID: 'WXXX'>
709 // Description <text string according to encoding> $00 (00)
713 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
714 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
715 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
717 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
718 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
719 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
721 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
723 if (ord($frame_description) === 0) {
724 $frame_description = '';
726 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
728 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
729 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
730 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
732 if ($frame_terminatorpos) {
733 // there are null bytes after the data - this is not according to spec
734 // only use data up to first null byte
735 $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
737 // no null bytes following data, just use all data
738 $frame_urldata = (string) $parsedFrame['data'];
741 $parsedFrame['encodingid'] = $frame_textencoding;
742 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
744 $parsedFrame['url'] = $frame_urldata;
745 $parsedFrame['description'] = $frame_description;
746 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
747 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
749 unset($parsedFrame['data']);
752 } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
753 // There may only be one URL link frame of its kind in a tag,
754 // except when stated otherwise in the frame description
755 // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
756 // described in 4.3.2.>
759 $parsedFrame['url'] = trim($parsedFrame['data']);
760 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
761 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
763 unset($parsedFrame['data']);
766 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
767 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
768 // http://id3.org/id3v2.3.0#sec4.4
769 // There may only be one 'IPL' frame in each tag
770 // <Header for 'User defined URL link frame', ID: 'IPL'>
772 // People list strings <textstrings>
775 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
776 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
777 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
779 $parsedFrame['encodingid'] = $frame_textencoding;
780 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
781 $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset);
783 // http://www.getid3.org/phpBB3/viewtopic.php?t=1369
784 // "this tag typically contains null terminated strings, which are associated in pairs"
785 // "there are users that use the tag incorrectly"
786 $IPLS_parts = array();
787 if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
788 $IPLS_parts_unsorted = array();
789 if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
790 // 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
792 for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
793 $twobytes = substr($parsedFrame['data_raw'], $i, 2);
794 if ($twobytes === "\x00\x00") {
795 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
798 $thisILPS .= $twobytes;
801 if (strlen($thisILPS) > 2) { // 2-byte BOM
802 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
805 // ISO-8859-1 or UTF-8 or other single-byte-null character set
806 $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
808 if (count($IPLS_parts_unsorted) == 1) {
809 // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
810 foreach ($IPLS_parts_unsorted as $key => $value) {
811 $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
813 foreach ($IPLS_parts_sorted as $person) {
814 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
817 } elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
820 foreach ($IPLS_parts_unsorted as $key => $value) {
821 if (($key % 2) == 0) {
825 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
831 foreach ($IPLS_parts_unsorted as $key => $value) {
832 $IPLS_parts[] = array($value);
837 $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
839 $parsedFrame['data'] = $IPLS_parts;
841 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
842 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
846 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier
847 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
848 // There may only be one 'MCDI' frame in each tag
849 // <Header for 'Music CD identifier', ID: 'MCDI'>
850 // CD TOC <binary data>
852 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
853 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
857 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes
858 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes
859 // There may only be one 'ETCO' frame in each tag
860 // <Header for 'Event timing codes', ID: 'ETCO'>
861 // Time stamp format $xx
862 // Where time stamp format is:
863 // $01 (32-bit value) MPEG frames from beginning of file
864 // $02 (32-bit value) milliseconds from beginning of file
865 // Followed by a list of key events in the following format:
867 // Time stamp $xx (xx ...)
868 // The 'Time stamp' is set to zero if directly at the beginning of the sound
869 // or after the previous event. All events MUST be sorted in chronological order.
872 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
874 while ($frame_offset < strlen($parsedFrame['data'])) {
875 $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1);
876 $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']);
877 $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
880 unset($parsedFrame['data']);
883 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table
884 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table
885 // There may only be one 'MLLT' frame in each tag
886 // <Header for 'Location lookup table', ID: 'MLLT'>
887 // MPEG frames between reference $xx xx
888 // Bytes between reference $xx xx xx
889 // Milliseconds between reference $xx xx xx
890 // Bits for bytes deviation $xx
891 // Bits for milliseconds dev. $xx
892 // Then for every reference the following data is included;
893 // Deviation in bytes %xxx....
894 // Deviation in milliseconds %xxx....
897 $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
898 $parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
899 $parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
900 $parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
901 $parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
902 $parsedFrame['data'] = substr($parsedFrame['data'], 10);
903 while ($frame_offset < strlen($parsedFrame['data'])) {
904 $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
906 $reference_counter = 0;
907 while (strlen($deviationbitstream) > 0) {
908 $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
909 $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
910 $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
911 $reference_counter++;
913 unset($parsedFrame['data']);
916 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes
917 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes
918 // There may only be one 'SYTC' frame in each tag
919 // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
920 // Time stamp format $xx
921 // Tempo data <binary data>
922 // Where time stamp format is:
923 // $01 (32-bit value) MPEG frames from beginning of file
924 // $02 (32-bit value) milliseconds from beginning of file
927 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
928 $timestamp_counter = 0;
929 while ($frame_offset < strlen($parsedFrame['data'])) {
930 $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
931 if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
932 $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
934 $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
936 $timestamp_counter++;
938 unset($parsedFrame['data']);
941 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription
942 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription
943 // There may be more than one 'Unsynchronised lyrics/text transcription' frame
944 // in each tag, but only one with the same language and content descriptor.
945 // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
947 // Language $xx xx xx
948 // Content descriptor <text string according to encoding> $00 (00)
949 // Lyrics/text <full text string according to encoding>
952 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
953 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
954 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
956 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
958 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
959 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
960 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
962 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
963 if (ord($frame_description) === 0) {
964 $frame_description = '';
966 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
968 $parsedFrame['encodingid'] = $frame_textencoding;
969 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
971 $parsedFrame['data'] = $parsedFrame['data'];
972 $parsedFrame['language'] = $frame_language;
973 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
974 $parsedFrame['description'] = $frame_description;
975 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
976 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
978 unset($parsedFrame['data']);
981 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text
982 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text
983 // There may be more than one 'SYLT' frame in each tag,
984 // but only one with the same language and content descriptor.
985 // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
987 // Language $xx xx xx
988 // Time stamp format $xx
989 // $01 (32-bit value) MPEG frames from beginning of file
990 // $02 (32-bit value) milliseconds from beginning of file
992 // Content descriptor <text string according to encoding> $00 (00)
993 // Terminated text to be synced (typically a syllable)
994 // Sync identifier (terminator to above string) $00 (00)
995 // Time stamp $xx (xx ...)
998 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
999 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1000 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1002 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1004 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1005 $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1006 $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1007 $parsedFrame['encodingid'] = $frame_textencoding;
1008 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1010 $parsedFrame['language'] = $frame_language;
1011 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1013 $timestampindex = 0;
1014 $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1015 while (strlen($frame_remainingdata)) {
1017 $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding));
1018 if ($frame_terminatorpos === false) {
1019 $frame_remainingdata = '';
1021 if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1022 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1024 $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1026 $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1027 if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
1028 // timestamp probably omitted for first data item
1030 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1031 $frame_remainingdata = substr($frame_remainingdata, 4);
1036 unset($parsedFrame['data']);
1039 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments
1040 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments
1041 // There may be more than one comment frame in each tag,
1042 // but only one with the same language and content descriptor.
1043 // <Header for 'Comment', ID: 'COMM'>
1044 // Text encoding $xx
1045 // Language $xx xx xx
1046 // Short content descrip. <text string according to encoding> $00 (00)
1047 // The actual text <full text string according to encoding>
1049 if (strlen($parsedFrame['data']) < 5) {
1051 $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
1056 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1057 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1058 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1060 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1062 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1063 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1064 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1066 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1067 if (ord($frame_description) === 0) {
1068 $frame_description = '';
1070 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1072 $parsedFrame['encodingid'] = $frame_textencoding;
1073 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1075 $parsedFrame['language'] = $frame_language;
1076 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1077 $parsedFrame['description'] = $frame_description;
1078 $parsedFrame['data'] = $frame_text;
1079 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1080 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1085 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1086 // There may be more than one 'RVA2' frame in each tag,
1087 // but only one with the same identification string
1088 // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1089 // Identification <text string> $00
1090 // The 'identification' string is used to identify the situation and/or
1091 // device where this adjustment should apply. The following is then
1092 // repeated for every channel:
1093 // Type of channel $xx
1094 // Volume adjustment $xx xx
1095 // Bits representing peak $xx
1096 // Peak volume $xx (xx ...)
1098 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1099 $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1100 if (ord($frame_idstring) === 0) {
1101 $frame_idstring = '';
1103 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1104 $parsedFrame['description'] = $frame_idstring;
1105 $RVA2channelcounter = 0;
1106 while (strlen($frame_remainingdata) >= 5) {
1108 $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1109 $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid;
1110 $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1111 $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1113 $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1114 if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1115 $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
1118 $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1119 $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1120 $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1121 $RVA2channelcounter++;
1123 unset($parsedFrame['data']);
1126 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
1127 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only)
1128 // There may only be one 'RVA' frame in each tag
1129 // <Header for 'Relative volume adjustment', ID: 'RVA'>
1130 // ID3v2.2 => Increment/decrement %000000ba
1131 // ID3v2.3 => Increment/decrement %00fedcba
1132 // Bits used for volume descr. $xx
1133 // Relative volume change, right $xx xx (xx ...) // a
1134 // Relative volume change, left $xx xx (xx ...) // b
1135 // Peak volume right $xx xx (xx ...)
1136 // Peak volume left $xx xx (xx ...)
1137 // ID3v2.3 only, optional (not present in ID3v2.2):
1138 // Relative volume change, right back $xx xx (xx ...) // c
1139 // Relative volume change, left back $xx xx (xx ...) // d
1140 // Peak volume right back $xx xx (xx ...)
1141 // Peak volume left back $xx xx (xx ...)
1142 // ID3v2.3 only, optional (not present in ID3v2.2):
1143 // Relative volume change, center $xx xx (xx ...) // e
1144 // Peak volume center $xx xx (xx ...)
1145 // ID3v2.3 only, optional (not present in ID3v2.2):
1146 // Relative volume change, bass $xx xx (xx ...) // f
1147 // Peak volume bass $xx xx (xx ...)
1150 $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1151 $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1152 $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1);
1153 $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1154 $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1155 $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1156 if ($parsedFrame['incdec']['right'] === false) {
1157 $parsedFrame['volumechange']['right'] *= -1;
1159 $frame_offset += $frame_bytesvolume;
1160 $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1161 if ($parsedFrame['incdec']['left'] === false) {
1162 $parsedFrame['volumechange']['left'] *= -1;
1164 $frame_offset += $frame_bytesvolume;
1165 $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1166 $frame_offset += $frame_bytesvolume;
1167 $parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1168 $frame_offset += $frame_bytesvolume;
1169 if ($id3v2_majorversion == 3) {
1170 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1171 if (strlen($parsedFrame['data']) > 0) {
1172 $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1173 $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1);
1174 $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1175 if ($parsedFrame['incdec']['rightrear'] === false) {
1176 $parsedFrame['volumechange']['rightrear'] *= -1;
1178 $frame_offset += $frame_bytesvolume;
1179 $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1180 if ($parsedFrame['incdec']['leftrear'] === false) {
1181 $parsedFrame['volumechange']['leftrear'] *= -1;
1183 $frame_offset += $frame_bytesvolume;
1184 $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1185 $frame_offset += $frame_bytesvolume;
1186 $parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1187 $frame_offset += $frame_bytesvolume;
1189 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1190 if (strlen($parsedFrame['data']) > 0) {
1191 $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1192 $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1193 if ($parsedFrame['incdec']['center'] === false) {
1194 $parsedFrame['volumechange']['center'] *= -1;
1196 $frame_offset += $frame_bytesvolume;
1197 $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1198 $frame_offset += $frame_bytesvolume;
1200 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1201 if (strlen($parsedFrame['data']) > 0) {
1202 $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1203 $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1204 if ($parsedFrame['incdec']['bass'] === false) {
1205 $parsedFrame['volumechange']['bass'] *= -1;
1207 $frame_offset += $frame_bytesvolume;
1208 $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1209 $frame_offset += $frame_bytesvolume;
1212 unset($parsedFrame['data']);
1215 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
1216 // There may be more than one 'EQU2' frame in each tag,
1217 // but only one with the same identification string
1218 // <Header of 'Equalisation (2)', ID: 'EQU2'>
1219 // Interpolation method $xx
1222 // Identification <text string> $00
1223 // The following is then repeated for every adjustment point
1225 // Volume adjustment $xx xx
1228 $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1229 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1230 $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1231 if (ord($frame_idstring) === 0) {
1232 $frame_idstring = '';
1234 $parsedFrame['description'] = $frame_idstring;
1235 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1236 while (strlen($frame_remainingdata)) {
1237 $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1238 $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1239 $frame_remainingdata = substr($frame_remainingdata, 4);
1241 $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1242 unset($parsedFrame['data']);
1245 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only)
1246 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only)
1247 // There may only be one 'EQUA' frame in each tag
1248 // <Header for 'Relative volume adjustment', ID: 'EQU'>
1249 // Adjustment bits $xx
1250 // This is followed by 2 bytes + ('adjustment bits' rounded up to the
1251 // nearest byte) for every equalisation band in the following format,
1252 // giving a frequency range of 0 - 32767Hz:
1253 // Increment/decrement %x (MSB of the Frequency)
1254 // Frequency (lower 15 bits)
1255 // Adjustment $xx (xx ...)
1258 $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1259 $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1261 $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1262 while (strlen($frame_remainingdata) > 0) {
1263 $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1264 $frame_incdec = (bool) substr($frame_frequencystr, 0, 1);
1265 $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1266 $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1267 $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1268 if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1269 $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1271 $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1273 unset($parsedFrame['data']);
1276 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb
1277 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb
1278 // There may only be one 'RVRB' frame in each tag.
1279 // <Header for 'Reverb', ID: 'RVRB'>
1280 // Reverb left (ms) $xx xx
1281 // Reverb right (ms) $xx xx
1282 // Reverb bounces, left $xx
1283 // Reverb bounces, right $xx
1284 // Reverb feedback, left to left $xx
1285 // Reverb feedback, left to right $xx
1286 // Reverb feedback, right to right $xx
1287 // Reverb feedback, right to left $xx
1288 // Premix left to right $xx
1289 // Premix right to left $xx
1292 $parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1294 $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1296 $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1297 $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1298 $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1299 $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1300 $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1301 $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1302 $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1303 $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1304 unset($parsedFrame['data']);
1307 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture
1308 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture
1309 // There may be several pictures attached to one file,
1310 // each in their individual 'APIC' frame, but only one
1311 // with the same content descriptor
1312 // <Header for 'Attached picture', ID: 'APIC'>
1313 // Text encoding $xx
1314 // ID3v2.3+ => MIME type <text string> $00
1315 // ID3v2.2 => Image format $xx xx xx
1317 // Description <text string according to encoding> $00 (00)
1318 // Picture data <binary data>
1321 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1322 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1323 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1326 if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1327 $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1328 if (strtolower($frame_imagetype) == 'ima') {
1329 // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1330 // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net)
1331 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1332 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1333 if (ord($frame_mimetype) === 0) {
1334 $frame_mimetype = '';
1336 $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1337 if ($frame_imagetype == 'JPEG') {
1338 $frame_imagetype = 'JPG';
1340 $frame_offset = $frame_terminatorpos + strlen("\x00");
1345 if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1346 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1347 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1348 if (ord($frame_mimetype) === 0) {
1349 $frame_mimetype = '';
1351 $frame_offset = $frame_terminatorpos + strlen("\x00");
1354 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1356 if ($frame_offset >= $parsedFrame['datalength']) {
1357 $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
1359 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1360 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1361 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1363 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1364 if (ord($frame_description) === 0) {
1365 $frame_description = '';
1367 $parsedFrame['encodingid'] = $frame_textencoding;
1368 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1370 if ($id3v2_majorversion == 2) {
1371 $parsedFrame['imagetype'] = $frame_imagetype;
1373 $parsedFrame['mime'] = $frame_mimetype;
1375 $parsedFrame['picturetypeid'] = $frame_picturetype;
1376 $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype);
1377 $parsedFrame['description'] = $frame_description;
1378 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1379 $parsedFrame['datalength'] = strlen($parsedFrame['data']);
1381 $parsedFrame['image_mime'] = '';
1382 $imageinfo = array();
1383 $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
1384 if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1385 $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
1386 if ($imagechunkcheck[0]) {
1387 $parsedFrame['image_width'] = $imagechunkcheck[0];
1389 if ($imagechunkcheck[1]) {
1390 $parsedFrame['image_height'] = $imagechunkcheck[1];
1395 if ($this->getid3->option_save_attachments === false) {
1397 unset($parsedFrame['data']);
1400 if ($this->getid3->option_save_attachments === true) {
1403 } elseif (is_int($this->getid3->option_save_attachments)) {
1404 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1406 $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
1407 unset($parsedFrame['data']);
1411 } elseif (is_string($this->getid3->option_save_attachments)) {
1412 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1413 if (!is_dir($dir) || !is_writable($dir)) {
1414 // cannot write, skip
1415 $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)';
1416 unset($parsedFrame['data']);
1420 // if we get this far, must be OK
1421 if (is_string($this->getid3->option_save_attachments)) {
1422 $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1423 if (!file_exists($destination_filename) || is_writable($destination_filename)) {
1424 file_put_contents($destination_filename, $parsedFrame['data']);
1426 $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
1428 $parsedFrame['data_filename'] = $destination_filename;
1429 unset($parsedFrame['data']);
1431 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1432 if (!isset($info['id3v2']['comments']['picture'])) {
1433 $info['id3v2']['comments']['picture'] = array();
1435 $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']);
1441 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object
1442 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object
1443 // There may be more than one 'GEOB' frame in each tag,
1444 // but only one with the same content descriptor
1445 // <Header for 'General encapsulated object', ID: 'GEOB'>
1446 // Text encoding $xx
1447 // MIME type <text string> $00
1448 // Filename <text string according to encoding> $00 (00)
1449 // Content description <text string according to encoding> $00 (00)
1450 // Encapsulated object <binary data>
1453 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1454 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1455 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1457 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1458 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1459 if (ord($frame_mimetype) === 0) {
1460 $frame_mimetype = '';
1462 $frame_offset = $frame_terminatorpos + strlen("\x00");
1464 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1465 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1466 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1468 $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1469 if (ord($frame_filename) === 0) {
1470 $frame_filename = '';
1472 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1474 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1475 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1476 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1478 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1479 if (ord($frame_description) === 0) {
1480 $frame_description = '';
1482 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1484 $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset);
1485 $parsedFrame['encodingid'] = $frame_textencoding;
1486 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1488 $parsedFrame['mime'] = $frame_mimetype;
1489 $parsedFrame['filename'] = $frame_filename;
1490 $parsedFrame['description'] = $frame_description;
1491 unset($parsedFrame['data']);
1494 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter
1495 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter
1496 // There may only be one 'PCNT' frame in each tag.
1497 // When the counter reaches all one's, one byte is inserted in
1498 // front of the counter thus making the counter eight bits bigger
1499 // <Header for 'Play counter', ID: 'PCNT'>
1500 // Counter $xx xx xx xx (xx ...)
1502 $parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']);
1505 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter
1506 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter
1507 // There may be more than one 'POPM' frame in each tag,
1508 // but only one with the same email address
1509 // <Header for 'Popularimeter', ID: 'POPM'>
1510 // Email to user <text string> $00
1512 // Counter $xx xx xx xx (xx ...)
1515 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1516 $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1517 if (ord($frame_emailaddress) === 0) {
1518 $frame_emailaddress = '';
1520 $frame_offset = $frame_terminatorpos + strlen("\x00");
1521 $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1522 $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1523 $parsedFrame['email'] = $frame_emailaddress;
1524 $parsedFrame['rating'] = $frame_rating;
1525 unset($parsedFrame['data']);
1528 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size
1529 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size
1530 // There may only be one 'RBUF' frame in each tag
1531 // <Header for 'Recommended buffer size', ID: 'RBUF'>
1532 // Buffer size $xx xx xx
1533 // Embedded info flag %0000000x
1534 // Offset to next tag $xx xx xx xx
1537 $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1540 $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1541 $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1542 $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1543 unset($parsedFrame['data']);
1546 } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only)
1547 // There may be more than one 'CRM' frame in a tag,
1548 // but only one with the same 'owner identifier'
1549 // <Header for 'Encrypted meta frame', ID: 'CRM'>
1550 // Owner identifier <textstring> $00 (00)
1551 // Content/explanation <textstring> $00 (00)
1552 // Encrypted datablock <binary data>
1555 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1556 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1557 $frame_offset = $frame_terminatorpos + strlen("\x00");
1559 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1560 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1561 if (ord($frame_description) === 0) {
1562 $frame_description = '';
1564 $frame_offset = $frame_terminatorpos + strlen("\x00");
1566 $parsedFrame['ownerid'] = $frame_ownerid;
1567 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1568 $parsedFrame['description'] = $frame_description;
1569 unset($parsedFrame['data']);
1572 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption
1573 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption
1574 // There may be more than one 'AENC' frames in a tag,
1575 // but only one with the same 'Owner identifier'
1576 // <Header for 'Audio encryption', ID: 'AENC'>
1577 // Owner identifier <text string> $00
1578 // Preview start $xx xx
1579 // Preview length $xx xx
1580 // Encryption info <binary data>
1583 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1584 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1585 if (ord($frame_ownerid) === 0) {
1586 $frame_ownerid == '';
1588 $frame_offset = $frame_terminatorpos + strlen("\x00");
1589 $parsedFrame['ownerid'] = $frame_ownerid;
1590 $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1592 $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1594 $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1595 unset($parsedFrame['data']);
1598 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information
1599 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information
1600 // There may be more than one 'LINK' frame in a tag,
1601 // but only one with the same contents
1602 // <Header for 'Linked information', ID: 'LINK'>
1603 // ID3v2.3+ => Frame identifier $xx xx xx xx
1604 // ID3v2.2 => Frame identifier $xx xx xx
1605 // URL <text string> $00
1606 // ID and additional data <text string(s)>
1609 if ($id3v2_majorversion == 2) {
1610 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1613 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1617 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1618 $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1619 if (ord($frame_url) === 0) {
1622 $frame_offset = $frame_terminatorpos + strlen("\x00");
1623 $parsedFrame['url'] = $frame_url;
1625 $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1626 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1627 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']);
1629 unset($parsedFrame['data']);
1632 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
1633 // There may only be one 'POSS' frame in each tag
1634 // <Head for 'Position synchronisation', ID: 'POSS'>
1635 // Time stamp format $xx
1636 // Position $xx (xx ...)
1639 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1640 $parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1641 unset($parsedFrame['data']);
1644 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only)
1645 // There may be more than one 'Terms of use' frame in a tag,
1646 // but only one with the same 'Language'
1647 // <Header for 'Terms of use frame', ID: 'USER'>
1648 // Text encoding $xx
1649 // Language $xx xx xx
1650 // The actual text <text string according to encoding>
1653 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1654 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1655 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1657 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1659 $parsedFrame['language'] = $frame_language;
1660 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1661 $parsedFrame['encodingid'] = $frame_textencoding;
1662 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1664 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1665 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1666 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1668 unset($parsedFrame['data']);
1671 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only)
1672 // There may only be one 'OWNE' frame in a tag
1673 // <Header for 'Ownership frame', ID: 'OWNE'>
1674 // Text encoding $xx
1675 // Price paid <text string> $00
1676 // Date of purch. <text string>
1677 // Seller <text string according to encoding>
1680 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1681 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1682 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1684 $parsedFrame['encodingid'] = $frame_textencoding;
1685 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1687 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1688 $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1689 $frame_offset = $frame_terminatorpos + strlen("\x00");
1691 $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1692 $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1693 $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3);
1695 $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1696 if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1697 $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1701 $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1702 unset($parsedFrame['data']);
1705 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only)
1706 // There may be more than one 'commercial frame' in a tag,
1707 // but no two may be identical
1708 // <Header for 'Commercial frame', ID: 'COMR'>
1709 // Text encoding $xx
1710 // Price string <text string> $00
1711 // Valid until <text string>
1712 // Contact URL <text string> $00
1714 // Name of seller <text string according to encoding> $00 (00)
1715 // Description <text string according to encoding> $00 (00)
1716 // Picture MIME type <string> $00
1717 // Seller logo <binary data>
1720 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1721 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1722 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1725 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1726 $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1727 $frame_offset = $frame_terminatorpos + strlen("\x00");
1728 $frame_rawpricearray = explode('/', $frame_pricestring);
1729 foreach ($frame_rawpricearray as $key => $val) {
1730 $frame_currencyid = substr($val, 0, 3);
1731 $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1732 $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3);
1735 $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1738 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1739 $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1740 $frame_offset = $frame_terminatorpos + strlen("\x00");
1742 $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1744 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1745 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1746 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1748 $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1749 if (ord($frame_sellername) === 0) {
1750 $frame_sellername = '';
1752 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1754 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1755 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1756 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1758 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1759 if (ord($frame_description) === 0) {
1760 $frame_description = '';
1762 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1764 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1765 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1766 $frame_offset = $frame_terminatorpos + strlen("\x00");
1768 $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1770 $parsedFrame['encodingid'] = $frame_textencoding;
1771 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1773 $parsedFrame['pricevaliduntil'] = $frame_datestring;
1774 $parsedFrame['contacturl'] = $frame_contacturl;
1775 $parsedFrame['receivedasid'] = $frame_receivedasid;
1776 $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid);
1777 $parsedFrame['sellername'] = $frame_sellername;
1778 $parsedFrame['description'] = $frame_description;
1779 $parsedFrame['mime'] = $frame_mimetype;
1780 $parsedFrame['logo'] = $frame_sellerlogo;
1781 unset($parsedFrame['data']);
1784 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
1785 // There may be several 'ENCR' frames in a tag,
1786 // but only one containing the same symbol
1787 // and only one containing the same owner identifier
1788 // <Header for 'Encryption method registration', ID: 'ENCR'>
1789 // Owner identifier <text string> $00
1790 // Method symbol $xx
1791 // Encryption data <binary data>
1794 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1795 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1796 if (ord($frame_ownerid) === 0) {
1797 $frame_ownerid = '';
1799 $frame_offset = $frame_terminatorpos + strlen("\x00");
1801 $parsedFrame['ownerid'] = $frame_ownerid;
1802 $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1803 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1806 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only)
1808 // There may be several 'GRID' frames in a tag,
1809 // but only one containing the same symbol
1810 // and only one containing the same owner identifier
1811 // <Header for 'Group ID registration', ID: 'GRID'>
1812 // Owner identifier <text string> $00
1814 // Group dependent data <binary data>
1817 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1818 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1819 if (ord($frame_ownerid) === 0) {
1820 $frame_ownerid = '';
1822 $frame_offset = $frame_terminatorpos + strlen("\x00");
1824 $parsedFrame['ownerid'] = $frame_ownerid;
1825 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1826 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1829 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only)
1830 // The tag may contain more than one 'PRIV' frame
1831 // but only with different contents
1832 // <Header for 'Private frame', ID: 'PRIV'>
1833 // Owner identifier <text string> $00
1834 // The private data <binary data>
1837 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1838 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1839 if (ord($frame_ownerid) === 0) {
1840 $frame_ownerid = '';
1842 $frame_offset = $frame_terminatorpos + strlen("\x00");
1844 $parsedFrame['ownerid'] = $frame_ownerid;
1845 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1848 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only)
1849 // There may be more than one 'signature frame' in a tag,
1850 // but no two may be identical
1851 // <Header for 'Signature frame', ID: 'SIGN'>
1853 // Signature <binary data>
1856 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1857 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1860 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only)
1861 // There may only be one 'seek frame' in a tag
1862 // <Header for 'Seek frame', ID: 'SEEK'>
1863 // Minimum offset to next tag $xx xx xx xx
1866 $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1869 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
1870 // There may only be one 'audio seek point index' frame in a tag
1871 // <Header for 'Seek Point Index', ID: 'ASPI'>
1872 // Indexed data start (S) $xx xx xx xx
1873 // Indexed data length (L) $xx xx xx xx
1874 // Number of index points (N) $xx xx
1875 // Bits per index point (b) $xx
1876 // Then for every index point the following data is included:
1877 // Fraction at index (Fi) $xx (xx)
1880 $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1882 $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1884 $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1886 $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1887 $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1888 for ($i = 0; $i < $frame_indexpoints; $i++) {
1889 $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1890 $frame_offset += $frame_bytesperpoint;
1892 unset($parsedFrame['data']);
1894 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1895 // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1896 // There may only be one 'RGAD' frame in a tag
1897 // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1898 // Peak Amplitude $xx $xx $xx $xx
1899 // Radio Replay Gain Adjustment %aaabbbcd %dddddddd
1900 // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
1902 // b - originator code
1904 // d - replay gain adjustment
1907 $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1909 $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1911 $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1913 $parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1914 $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1915 $parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1916 $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1917 $parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1918 $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1919 $parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
1920 $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
1921 $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1922 $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1923 $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1924 $parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1925 $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1926 $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1928 $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude'];
1929 $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1930 $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1931 $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1932 $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1934 unset($parsedFrame['data']);
1942 public function DeUnsynchronise($data) {
1943 return str_replace("\xFF\x00", "\xFF", $data);
1946 public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
1947 static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
1948 0x00 => 'No more than 128 frames and 1 MB total tag size',
1949 0x01 => 'No more than 64 frames and 128 KB total tag size',
1950 0x02 => 'No more than 32 frames and 40 KB total tag size',
1951 0x03 => 'No more than 32 frames and 4 KB total tag size',
1953 return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
1956 public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
1957 static $LookupExtendedHeaderRestrictionsTextEncodings = array(
1958 0x00 => 'No restrictions',
1959 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
1961 return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
1964 public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
1965 static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
1966 0x00 => 'No restrictions',
1967 0x01 => 'No string is longer than 1024 characters',
1968 0x02 => 'No string is longer than 128 characters',
1969 0x03 => 'No string is longer than 30 characters',
1971 return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
1974 public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
1975 static $LookupExtendedHeaderRestrictionsImageEncoding = array(
1976 0x00 => 'No restrictions',
1977 0x01 => 'Images are encoded only with PNG or JPEG',
1979 return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
1982 public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
1983 static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
1984 0x00 => 'No restrictions',
1985 0x01 => 'All images are 256x256 pixels or smaller',
1986 0x02 => 'All images are 64x64 pixels or smaller',
1987 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
1989 return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
1992 public function LookupCurrencyUnits($currencyid) {
1996 /** This is not a comment!
2010 BAM Convertible Marka
2027 CDF Congolese Francs
2173 XDR Special Drawing Rights
2181 ZWD Zimbabwe Dollars
2185 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2189 public function LookupCurrencyCountry($currencyid) {
2193 /** This is not a comment!
2195 AED United Arab Emirates
2199 ANG Netherlands Antilles
2206 BAM Bosnia and Herzegovina
2214 BND Brunei Darussalam
2236 DOP Dominican Republic
2243 EUR Euro Member Countries
2246 FKP Falkland Islands (Malvinas)
2303 MVR Maldives (Maldive Islands)
2311 NLG Netherlands (Holland)
2318 PGK Papua New Guinea
2341 STD São Tome and Principe
2351 TTD Trinidad and Tobago
2357 USD United States of America
2365 XAF Communauté Financière Africaine
2369 XDR International Monetary Fund
2371 XPF Comptoirs Français du Pacifique
2381 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2386 public static function LanguageLookup($languagecode, $casesensitive=false) {
2388 if (!$casesensitive) {
2389 $languagecode = strtolower($languagecode);
2392 // http://www.id3.org/id3v2.4.0-structure.txt
2393 // [4. ID3v2 frame overview]
2394 // The three byte language field, present in several frames, is used to
2395 // describe the language of the frame's content, according to ISO-639-2
2396 // [ISO-639-2]. The language should be represented in lower case. If the
2397 // language is not known the string "XXX" should be used.
2400 // ISO 639-2 - http://www.id3.org/iso639-2.html
2404 /** This is not a comment!
2413 afa Afro-Asiatic (Other)
2420 alg Algonquian Languages
2422 ang English, Old (ca. 450-1100)
2423 apa Apache Languages
2429 art Artificial (Other)
2432 ath Athapascan Languages
2439 bai Bamileke Languages
2467 cai Central American Indian (Other)
2470 cau Caucasian (Other)
2489 cpe Creoles and Pidgins, English-based (Other)
2490 cpf Creoles and Pidgins, French-based (Other)
2491 cpp Creoles and Pidgins, Portuguese-based (Other)
2493 crp Creoles and Pidgins (Other)
2494 cus Cushitic (Other)
2504 dra Dravidian (Other)
2506 dum Dutch, Middle (ca. 1050-1350)
2511 egy Egyptian (Ancient)
2513 ell Greek, Modern (1453-)
2516 enm English, Middle (ca. 1100-1500)
2530 fiu Finno-Ugrian (Other)
2534 frm French, Middle (ca. 1400-1600)
2535 fro French, Old (842- ca. 1400)
2543 gem Germanic (Other)
2549 gmh German, Middle High (ca. 1050-1500)
2550 goh German, Old High (ca. 750-1050)
2554 grc Greek, Ancient (to 1453)
2555 gre Greek, Modern (1453-)