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-)
2576 ina Interlingua (International Auxiliary language Association)
2579 ine Indo-European (Other)
2639 luo Luo (Kenya and Tanzania)
2650 map Austronesian (Other)
2656 mga Irish, Middle (900 - 1200)
2659 mis Miscellaneous (Other)
2660 mkh Mon-Kmer (Other)
2664 mno Manobo Languages
2671 mul Multiple Languages
2678 nai North American Indian (Other)
2686 nic Niger-Kordofanian (Other)
2689 nno Norwegian (Nynorsk)
2693 nub Nubian Languages
2699 oci Langue d'Oc (post 1500)
2705 ota Turkish, Ottoman (1500 - 1928)
2706 oto Otomian Languages
2707 paa Papuan-Australian (Other)
2714 peo Persian, Old (ca 600 - 400 B.C.)
2722 pro Provencal, Old (to 1500)
2737 sai South American Indian (Other)
2738 sal Salishan Languages
2739 sam Samaritan Aramaic
2745 sga Irish, Old (to 900)
2749 sio Siouan Languages
2750 sit Sino-Tibetan (Other)
2767 ssa Nilo-Saharan (Other)
2794 ton Tonga (Tonga Islands)
2817 wak Wakashan Languages
2822 wen Sorbian Languages
2838 return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
2842 public static function ETCOEventLookup($index) {
2843 if (($index >= 0x17) && ($index <= 0xDF)) {
2844 return 'reserved for future use';
2846 if (($index >= 0xE0) && ($index <= 0xEF)) {
2847 return 'not predefined synch 0-F';
2849 if (($index >= 0xF0) && ($index <= 0xFC)) {
2850 return 'reserved for future use';
2853 static $EventLookup = array(
2854 0x00 => 'padding (has no meaning)',
2855 0x01 => 'end of initial silence',
2856 0x02 => 'intro start',
2857 0x03 => 'main part start',
2858 0x04 => 'outro start',
2859 0x05 => 'outro end',
2860 0x06 => 'verse start',
2861 0x07 => 'refrain start',
2862 0x08 => 'interlude start',
2863 0x09 => 'theme start',
2864 0x0A => 'variation start',
2865 0x0B => 'key change',
2866 0x0C => 'time change',
2867 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
2868 0x0E => 'sustained noise',
2869 0x0F => 'sustained noise end',
2870 0x10 => 'intro end',
2871 0x11 => 'main part end',
2872 0x12 => 'verse end',
2873 0x13 => 'refrain end',
2874 0x14 => 'theme end',
2875 0x15 => 'profanity',
2876 0x16 => 'profanity end',
2877 0xFD => 'audio end (start of silence)',
2878 0xFE => 'audio file ends',
2879 0xFF => 'one more byte of events follows'
2882 return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
2885 public static function SYTLContentTypeLookup($index) {
2886 static $SYTLContentTypeLookup = array(
2889 0x02 => 'text transcription',
2890 0x03 => 'movement/part name', // (e.g. 'Adagio')
2891 0x04 => 'events', // (e.g. 'Don Quijote enters the stage')
2892 0x05 => 'chord', // (e.g. 'Bb F Fsus')
2893 0x06 => 'trivia/\'pop up\' information',
2894 0x07 => 'URLs to webpages',
2895 0x08 => 'URLs to images'
2898 return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
2901 public static function APICPictureTypeLookup($index, $returnarray=false) {
2902 static $APICPictureTypeLookup = array(
2904 0x01 => '32x32 pixels \'file icon\' (PNG only)',
2905 0x02 => 'Other file icon',
2906 0x03 => 'Cover (front)',
2907 0x04 => 'Cover (back)',
2908 0x05 => 'Leaflet page',
2909 0x06 => 'Media (e.g. label side of CD)',
2910 0x07 => 'Lead artist/lead performer/soloist',
2911 0x08 => 'Artist/performer',
2912 0x09 => 'Conductor',
2913 0x0A => 'Band/Orchestra',
2915 0x0C => 'Lyricist/text writer',
2916 0x0D => 'Recording Location',
2917 0x0E => 'During recording',
2918 0x0F => 'During performance',
2919 0x10 => 'Movie/video screen capture',
2920 0x11 => 'A bright coloured fish',
2921 0x12 => 'Illustration',
2922 0x13 => 'Band/artist logotype',
2923 0x14 => 'Publisher/Studio logotype'
2926 return $APICPictureTypeLookup;
2928 return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
2931 public static function COMRReceivedAsLookup($index) {
2932 static $COMRReceivedAsLookup = array(
2934 0x01 => 'Standard CD album with other songs',
2935 0x02 => 'Compressed audio on CD',
2936 0x03 => 'File over the Internet',
2937 0x04 => 'Stream over the Internet',
2938 0x05 => 'As note sheets',
2939 0x06 => 'As note sheets in a book with other sheets',
2940 0x07 => 'Music on other media',
2941 0x08 => 'Non-musical merchandise'
2944 return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
2947 public static function RVA2ChannelTypeLookup($index) {
2948 static $RVA2ChannelTypeLookup = array(
2950 0x01 => 'Master volume',
2951 0x02 => 'Front right',
2952 0x03 => 'Front left',
2953 0x04 => 'Back right',
2954 0x05 => 'Back left',
2955 0x06 => 'Front centre',
2956 0x07 => 'Back centre',
2960 return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
2963 public static function FrameNameLongLookup($framename) {
2967 /** This is not a comment!
2969 AENC Audio encryption
2970 APIC Attached picture
2971 ASPI Audio seek point index
2972 BUF Recommended buffer size
2976 COMR Commercial frame
2977 CRA Audio encryption
2978 CRM Encrypted meta frame
2979 ENCR Encryption method registration
2981 EQU2 Equalisation (2)
2983 ETC Event timing codes
2984 ETCO Event timing codes
2985 GEO General encapsulated object
2986 GEOB General encapsulated object
2987 GRID Group identification registration
2988 IPL Involved people list
2989 IPLS Involved people list
2990 LINK Linked information
2991 LNK Linked information
2992 MCDI Music CD identifier
2993 MCI Music CD Identifier
2994 MLL MPEG location lookup table
2995 MLLT MPEG location lookup table
2996 OWNE Ownership frame
2998 PIC Attached picture
3001 POSS Position synchronisation frame
3003 RBUF Recommended buffer size
3005 RVA Relative volume adjustment
3006 RVA2 Relative volume adjustment (2)
3007 RVAD Relative volume adjustment
3010 SIGN Signature frame
3011 SLT Synchronised lyric/text
3012 STC Synced tempo codes
3013 SYLT Synchronised lyric/text
3014 SYTC Synchronised tempo codes
3015 TAL Album/Movie/Show title
3016 TALB Album/Movie/Show title
3017 TBP BPM (Beats Per Minute)
3018 TBPM BPM (beats per minute)
3020 TCMP Part of a compilation
3024 TCOP Copyright message
3025 TCP Part of a compilation
3026 TCR Copyright message
3031 TDOR Original release time
3038 TEXT Lyricist/Text writer
3043 TIPL Involved people list
3044 TIT1 Content group description
3045 TIT2 Title/songname/content description
3046 TIT3 Subtitle/Description refinement
3053 TMCL Musician credits list
3057 TOA Original artist(s)/performer(s)
3058 TOAL Original album/movie/show title
3059 TOF Original filename
3060 TOFN Original filename
3061 TOL Original Lyricist(s)/text writer(s)
3062 TOLY Original lyricist(s)/text writer(s)
3063 TOPE Original artist(s)/performer(s)
3064 TOR Original release year
3065 TORY Original release year
3066 TOT Original album/Movie/Show title
3067 TOWN File owner/licensee
3068 TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3069 TP2 Band/Orchestra/Accompaniment
3070 TP3 Conductor/Performer refinement
3071 TP4 Interpreted, remixed, or otherwise modified by
3074 TPE1 Lead performer(s)/Soloist(s)
3075 TPE2 Band/orchestra/accompaniment
3076 TPE3 Conductor/performer refinement
3077 TPE4 Interpreted, remixed, or otherwise modified by
3079 TPRO Produced notice
3081 TRC ISRC (International Standard Recording Code)
3082 TRCK Track number/Position in set
3084 TRDA Recording dates
3085 TRK Track number/Position in set
3086 TRSN Internet radio station name
3087 TRSO Internet radio station owner
3088 TS2 Album-Artist sort order
3089 TSA Album sort order
3090 TSC Composer sort order
3093 TSO2 Album-Artist sort order
3094 TSOA Album sort order
3095 TSOC Composer sort order
3096 TSOP Performer sort order
3097 TSOT Title sort order
3098 TSP Performer sort order
3099 TSRC ISRC (international standard recording code)
3100 TSS Software/hardware and settings used for encoding
3101 TSSE Software/Hardware and settings used for encoding
3103 TST Title sort order
3104 TT1 Content group description
3105 TT2 Title/Songname/Content description
3106 TT3 Subtitle/Description refinement
3107 TXT Lyricist/text writer
3108 TXX User defined text information frame
3109 TXXX User defined text information frame
3112 UFI Unique file identifier
3113 UFID Unique file identifier
3114 ULT Unsychronised lyric/text transcription
3116 USLT Unsynchronised lyric/text transcription
3117 WAF Official audio file webpage
3118 WAR Official artist/performer webpage
3119 WAS Official audio source webpage
3120 WCM Commercial information
3121 WCOM Commercial information
3122 WCOP Copyright/Legal information
3123 WCP Copyright/Legal information
3124 WOAF Official audio file webpage
3125 WOAR Official artist/performer webpage
3126 WOAS Official audio source webpage
3127 WORS Official Internet radio station homepage
3129 WPB Publishers official webpage
3130 WPUB Publishers official webpage
3131 WXX User defined URL link frame
3132 WXXX User defined URL link frame
3133 TFEA Featured Artist
3134 TSTU Recording Studio
3135 rgad Replay Gain Adjustment
3139 return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
3142 // from Helium2 [www.helium2.com]
3143 // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3147 public static function FrameNameShortLookup($framename) {
3151 /** This is not a comment!
3153 AENC audio_encryption
3154 APIC attached_picture
3155 ASPI audio_seek_point_index
3156 BUF recommended_buffer_size
3160 COMR commercial_frame
3161 CRA audio_encryption
3162 CRM encrypted_meta_frame
3163 ENCR encryption_method_registration
3167 ETC event_timing_codes
3168 ETCO event_timing_codes
3169 GEO general_encapsulated_object
3170 GEOB general_encapsulated_object
3171 GRID group_identification_registration
3172 IPL involved_people_list
3173 IPLS involved_people_list
3174 LINK linked_information
3175 LNK linked_information
3176 MCDI music_cd_identifier
3177 MCI music_cd_identifier
3178 MLL mpeg_location_lookup_table
3179 MLLT mpeg_location_lookup_table
3180 OWNE ownership_frame
3182 PIC attached_picture
3185 POSS position_synchronisation_frame
3187 RBUF recommended_buffer_size
3189 RVA relative_volume_adjustment
3190 RVA2 relative_volume_adjustment
3191 RVAD relative_volume_adjustment
3194 SIGN signature_frame
3195 SLT synchronised_lyric
3196 STC synced_tempo_codes
3197 SYLT synchronised_lyric
3198 SYTC synchronised_tempo_codes
3204 TCMP part_of_a_compilation
3208 TCOP copyright_message
3209 TCP part_of_a_compilation
3210 TCR copyright_message
3215 TDOR original_release_time
3227 TIPL involved_people_list
3228 TIT1 content_group_description
3237 TMCL musician_credits_list
3243 TOF original_filename
3244 TOFN original_filename
3245 TOL original_lyricist
3246 TOLY original_lyricist
3247 TOPE original_artist
3263 TPRO produced_notice
3268 TRDA recording_dates
3270 TRSN internet_radio_station_name
3271 TRSO internet_radio_station_owner
3272 TS2 album_artist_sort_order
3273 TSA album_sort_order
3274 TSC composer_sort_order
3277 TSO2 album_artist_sort_order
3278 TSOA album_sort_order
3279 TSOC composer_sort_order
3280 TSOP performer_sort_order
3281 TSOT title_sort_order
3282 TSP performer_sort_order
3284 TSS encoder_settings
3285 TSSE encoder_settings
3287 TST title_sort_order
3288 TT1 content_group_description
3296 UFI unique_file_identifier
3297 UFID unique_file_identifier
3298 ULT unsychronised_lyric
3300 USLT unsynchronised_lyric
3304 WCM commercial_information
3305 WCOM commercial_information
3317 TFEA featured_artist
3318 TSTU recording_studio
3319 rgad replay_gain_adjustment
3323 return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
3326 public static function TextEncodingTerminatorLookup($encoding) {
3327 // http://www.id3.org/id3v2.4.0-structure.txt
3328 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3329 static $TextEncodingTerminatorLookup = array(
3330 0 => "\x00", // $00 ISO-8859-1. Terminated with $00.
3331 1 => "\x00\x00", // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3332 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3333 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
3336 return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : '');
3339 public static function TextEncodingNameLookup($encoding) {
3340 // http://www.id3.org/id3v2.4.0-structure.txt
3341 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3342 static $TextEncodingNameLookup = array(
3343 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00.
3344 1 => 'UTF-16', // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3345 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3346 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00.
3349 return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3352 public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3353 switch ($id3v2majorversion) {
3355 return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3360 return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3366 public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3367 for ($i = 0; $i < strlen($numberstring); $i++) {
3368 if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) {
3369 if (($numberstring{$i} == '.') && $allowdecimal) {
3371 } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) {
3381 public static function IsValidDateStampString($datestamp) {
3382 if (strlen($datestamp) != 8) {
3385 if (!self::IsANumber($datestamp, false)) {
3388 $year = substr($datestamp, 0, 4);
3389 $month = substr($datestamp, 4, 2);
3390 $day = substr($datestamp, 6, 2);
3391 if (($year == 0) || ($month == 0) || ($day == 0)) {
3400 if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3403 if (($day > 29) && ($month == 2)) {
3409 public static function ID3v2HeaderLength($majorversion) {
3410 return (($majorversion == 2) ? 6 : 10);