2 /////////////////////////////////////////////////////////////////
3 /// getID3() by James Heinrich <info@getid3.org> //
4 // available at http://getid3.sourceforge.net //
5 // or http://www.getid3.org //
6 // also https://github.com/JamesHeinrich/getID3 //
7 /////////////////////////////////////////////////////////////////
8 // See readme.txt for more details //
9 /////////////////////////////////////////////////////////////////
11 // module.tag.id3v2.php //
12 // module for analyzing ID3v2 tags //
13 // dependencies: module.tag.id3v1.php //
15 /////////////////////////////////////////////////////////////////
17 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
19 class getid3_id3v2 extends getid3_handler
21 public $StartingOffset = 0;
23 public function Analyze() {
24 $info = &$this->getid3->info;
26 // Overall tag structure:
27 // +-----------------------------+
28 // | Header (10 bytes) |
29 // +-----------------------------+
30 // | Extended Header |
31 // | (variable length, OPTIONAL) |
32 // +-----------------------------+
33 // | Frames (variable length) |
34 // +-----------------------------+
36 // | (variable length, OPTIONAL) |
37 // +-----------------------------+
38 // | Footer (10 bytes, OPTIONAL) |
39 // +-----------------------------+
42 // ID3v2/file identifier "ID3"
43 // ID3v2 version $04 00
44 // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
45 // ID3v2 size 4 * %0xxxxxxx
49 $info['id3v2']['header'] = true;
50 $thisfile_id3v2 = &$info['id3v2'];
51 $thisfile_id3v2['flags'] = array();
52 $thisfile_id3v2_flags = &$thisfile_id3v2['flags'];
55 $this->fseek($this->StartingOffset);
56 $header = $this->fread(10);
57 if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) {
59 $thisfile_id3v2['majorversion'] = ord($header{3});
60 $thisfile_id3v2['minorversion'] = ord($header{4});
63 $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
67 unset($info['id3v2']);
72 if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
74 $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
79 $id3_flags = ord($header{5});
80 switch ($id3v2_majorversion) {
83 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
84 $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
89 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
90 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
91 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
96 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
97 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
98 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
99 $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present
103 $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
105 $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
106 $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
110 // create 'encoding' key - used by getid3::HandleAllTags()
111 // in ID3v2 every field can have it's own encoding type
112 // so force everything to UTF-8 so it can be handled consistantly
113 $thisfile_id3v2['encoding'] = 'UTF-8';
118 // All ID3v2 frames consists of one frame header followed by one or more
119 // fields containing the actual information. The header is always 10
120 // bytes and laid out as follows:
122 // Frame ID $xx xx xx xx (four characters)
123 // Size 4 * %0xxxxxxx
126 $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
127 if (!empty($thisfile_id3v2['exthead']['length'])) {
128 $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
130 if (!empty($thisfile_id3v2_flags['isfooter'])) {
131 $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
133 if ($sizeofframes > 0) {
135 $framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
137 // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
138 if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
139 $framedata = $this->DeUnsynchronise($framedata);
141 // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
142 // of on tag level, making it easier to skip frames, increasing the streamability
143 // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
144 // there exists an unsynchronised frame, while the new unsynchronisation flag in
145 // the frame header [S:4.1.2] indicates unsynchronisation.
148 //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
149 $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
153 if (!empty($thisfile_id3v2_flags['exthead'])) {
154 $extended_header_offset = 0;
156 if ($id3v2_majorversion == 3) {
159 //Extended header size $xx xx xx xx // 32-bit integer
160 //Extended Flags $xx xx
161 // %x0000000 %00000000 // v2.3
162 // x - CRC data present
163 //Size of padding $xx xx xx xx
165 $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
166 $extended_header_offset += 4;
168 $thisfile_id3v2['exthead']['flag_bytes'] = 2;
169 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
170 $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
172 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
174 $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
175 $extended_header_offset += 4;
177 if ($thisfile_id3v2['exthead']['flags']['crc']) {
178 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
179 $extended_header_offset += 4;
181 $extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
183 } elseif ($id3v2_majorversion == 4) {
186 //Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer
187 //Number of flag bytes $01
190 // b - Tag is an update
191 // Flag data length $00
192 // c - CRC data present
193 // Flag data length $05
194 // Total frame CRC 5 * %0xxxxxxx
195 // d - Tag restrictions
196 // Flag data length $01
198 $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
199 $extended_header_offset += 4;
201 $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
202 $extended_header_offset += 1;
204 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
205 $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
207 $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
208 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
209 $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
211 if ($thisfile_id3v2['exthead']['flags']['update']) {
212 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
213 $extended_header_offset += 1;
216 if ($thisfile_id3v2['exthead']['flags']['crc']) {
217 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
218 $extended_header_offset += 1;
219 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
220 $extended_header_offset += $ext_header_chunk_length;
223 if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
224 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
225 $extended_header_offset += 1;
228 $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
229 $extended_header_offset += 1;
230 $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
231 $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
232 $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
233 $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
234 $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
236 $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
237 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
238 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
239 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
240 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
243 if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
244 $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
248 $framedataoffset += $extended_header_offset;
249 $framedata = substr($framedata, $extended_header_offset);
250 } // end extended header
253 while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
254 if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
255 // insufficient room left in ID3v2 header for actual data - must be padding
256 $thisfile_id3v2['padding']['start'] = $framedataoffset;
257 $thisfile_id3v2['padding']['length'] = strlen($framedata);
258 $thisfile_id3v2['padding']['valid'] = true;
259 for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
260 if ($framedata{$i} != "\x00") {
261 $thisfile_id3v2['padding']['valid'] = false;
262 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
263 $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
267 break; // skip rest of ID3v2 header
269 if ($id3v2_majorversion == 2) {
270 // Frame ID $xx xx xx (three characters)
271 // Size $xx xx xx (24-bit integer)
274 $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
275 $framedata = substr($framedata, 6); // and leave the rest in $framedata
276 $frame_name = substr($frame_header, 0, 3);
277 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
278 $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
280 } elseif ($id3v2_majorversion > 2) {
282 // Frame ID $xx xx xx xx (four characters)
283 // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
286 $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
287 $framedata = substr($framedata, 10); // and leave the rest in $framedata
289 $frame_name = substr($frame_header, 0, 4);
290 if ($id3v2_majorversion == 3) {
291 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
293 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
296 if ($frame_size < (strlen($framedata) + 4)) {
297 $nextFrameID = substr($framedata, $frame_size, 4);
298 if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
300 } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
301 // MP3ext known broken frames - "ok" for the purposes of this test
302 } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
303 $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';
304 $id3v2_majorversion = 3;
305 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
310 $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
313 if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
314 // padding encountered
316 $thisfile_id3v2['padding']['start'] = $framedataoffset;
317 $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
318 $thisfile_id3v2['padding']['valid'] = true;
320 $len = strlen($framedata);
321 for ($i = 0; $i < $len; $i++) {
322 if ($framedata{$i} != "\x00") {
323 $thisfile_id3v2['padding']['valid'] = false;
324 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
325 $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
329 break; // skip rest of ID3v2 header
332 if ($frame_name == 'COM ') {
333 $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)]';
334 $frame_name = 'COMM';
336 if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
339 $parsedFrame['frame_name'] = $frame_name;
340 $parsedFrame['frame_flags_raw'] = $frame_flags;
341 $parsedFrame['data'] = substr($framedata, 0, $frame_size);
342 $parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size);
343 $parsedFrame['dataoffset'] = $framedataoffset;
345 $this->ParseID3v2Frame($parsedFrame);
346 $thisfile_id3v2[$frame_name][] = $parsedFrame;
348 $framedata = substr($framedata, $frame_size);
350 } else { // invalid frame length or FrameID
352 if ($frame_size <= strlen($framedata)) {
354 if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
356 // next frame is valid, just skip the current frame
357 $framedata = substr($framedata, $frame_size);
358 $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
362 // next frame is invalid too, abort processing
365 $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
369 } elseif ($frame_size == strlen($framedata)) {
371 // this is the last frame, just skip
372 $info['warning'][] = 'This was the last ID3v2 frame.';
376 // next frame is invalid too, abort processing
379 $info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
382 if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
384 switch ($frame_name) {
385 case "\x00\x00".'MP':
392 $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/)"]';
396 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
400 } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
402 $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').')).';
406 $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
411 $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
420 // The footer is a copy of the header, but with a different identifier.
421 // ID3v2 identifier "3DI"
422 // ID3v2 version $04 00
423 // ID3v2 flags %abcd0000
424 // ID3v2 size 4 * %0xxxxxxx
426 if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
427 $footer = $this->fread(10);
428 if (substr($footer, 0, 3) == '3DI') {
429 $thisfile_id3v2['footer'] = true;
430 $thisfile_id3v2['majorversion_footer'] = ord($footer{3});
431 $thisfile_id3v2['minorversion_footer'] = ord($footer{4});
433 if ($thisfile_id3v2['majorversion_footer'] <= 4) {
434 $id3_flags = ord(substr($footer{5}));
435 $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80);
436 $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40);
437 $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20);
438 $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
440 $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
444 if (isset($thisfile_id3v2['comments']['genre'])) {
445 foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
446 unset($thisfile_id3v2['comments']['genre'][$key]);
447 $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
451 if (isset($thisfile_id3v2['comments']['track'])) {
452 foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
453 if (strstr($value, '/')) {
454 list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
459 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)) {
460 $thisfile_id3v2['comments']['year'] = array($matches[1]);
464 if (!empty($thisfile_id3v2['TXXX'])) {
465 // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
466 foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
467 switch ($txxx_array['description']) {
468 case 'replaygain_track_gain':
469 if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
470 $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
473 case 'replaygain_track_peak':
474 if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
475 $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
478 case 'replaygain_album_gain':
479 if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
480 $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
489 $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
490 if (isset($thisfile_id3v2['footer'])) {
491 $info['avdataoffset'] += 10;
498 public function ParseID3v2GenreString($genrestring) {
499 // Parse genres into arrays of genreName and genreID
500 // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
501 // ID3v2.4.x: '21' $00 'Eurodisco' $00
502 $clean_genres = array();
503 if (strpos($genrestring, "\x00") === false) {
504 $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
506 $genre_elements = explode("\x00", $genrestring);
507 foreach ($genre_elements as $element) {
508 $element = trim($element);
510 if (preg_match('#^[0-9]{1,3}#', $element)) {
511 $clean_genres[] = getid3_id3v1::LookupGenreName($element);
513 $clean_genres[] = str_replace('((', '(', $element);
517 return $clean_genres;
521 public function ParseID3v2Frame(&$parsedFrame) {
524 $info = &$this->getid3->info;
525 $id3v2_majorversion = $info['id3v2']['majorversion'];
527 $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']);
528 if (empty($parsedFrame['framenamelong'])) {
529 unset($parsedFrame['framenamelong']);
531 $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
532 if (empty($parsedFrame['framenameshort'])) {
533 unset($parsedFrame['framenameshort']);
536 if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
537 if ($id3v2_majorversion == 3) {
538 // Frame Header Flags
539 // %abc00000 %ijk00000
540 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
541 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
542 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
543 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
544 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
545 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
547 } elseif ($id3v2_majorversion == 4) {
548 // Frame Header Flags
549 // %0abc0000 %0h00kmnp
550 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
551 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
552 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
553 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
554 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
555 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
556 $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
557 $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
559 // Frame-level de-unsynchronisation - ID3v2.4
560 if ($parsedFrame['flags']['Unsynchronisation']) {
561 $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
564 if ($parsedFrame['flags']['DataLengthIndicator']) {
565 $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
566 $parsedFrame['data'] = substr($parsedFrame['data'], 4);
570 // Frame-level de-compression
571 if ($parsedFrame['flags']['compression']) {
572 $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
573 if (!function_exists('gzuncompress')) {
574 $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
576 if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
577 //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
578 $parsedFrame['data'] = $decompresseddata;
579 unset($decompresseddata);
581 $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
587 if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
588 if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
589 $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';
593 if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
595 $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
596 switch ($parsedFrame['frame_name']) {
598 $warning .= ' (this is known to happen with files tagged by RioPort)';
604 $info['warning'][] = $warning;
606 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier
607 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier
608 // There may be more than one 'UFID' frame in a tag,
609 // but only one with the same 'Owner identifier'.
610 // <Header for 'Unique file identifier', ID: 'UFID'>
611 // Owner identifier <text string> $00
612 // Identifier <up to 64 bytes binary data>
613 $exploded = explode("\x00", $parsedFrame['data'], 2);
614 $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
615 $parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : '');
617 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
618 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame
619 // There may be more than one 'TXXX' frame in each tag,
620 // but only one with the same description.
621 // <Header for 'User defined text information frame', ID: 'TXXX'>
623 // Description <text string according to encoding> $00 (00)
624 // Value <text string according to encoding>
627 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
628 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
629 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
630 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
631 $frame_textencoding_terminator = "\x00";
633 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
634 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
635 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
637 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
638 if (ord($frame_description) === 0) {
639 $frame_description = '';
641 $parsedFrame['encodingid'] = $frame_textencoding;
642 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
644 $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description));
645 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
646 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
647 $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
648 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
649 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
651 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
654 //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
657 } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
658 // There may only be one text information frame of its kind in an tag.
659 // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
660 // excluding 'TXXX' described in 4.2.6.>
662 // Information <text string(s) according to encoding>
665 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
666 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
667 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
670 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
672 $parsedFrame['encodingid'] = $frame_textencoding;
673 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
675 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
676 // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
677 // This of course breaks when an artist name contains slash character, e.g. "AC/DC"
678 // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
679 // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
680 switch ($parsedFrame['encoding']) {
692 $Txxx_elements = array();
693 $Txxx_elements_start_offset = 0;
694 for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
695 if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
696 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
697 $Txxx_elements_start_offset = $i + $wordsize;
700 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
701 foreach ($Txxx_elements as $Txxx_element) {
702 $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
703 if (!empty($string)) {
704 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
707 unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
710 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
711 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame
712 // There may be more than one 'WXXX' frame in each tag,
713 // but only one with the same description
714 // <Header for 'User defined URL link frame', ID: 'WXXX'>
716 // Description <text string according to encoding> $00 (00)
720 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
721 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
722 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
723 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
724 $frame_textencoding_terminator = "\x00";
726 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
727 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
728 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
730 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
732 if (ord($frame_description) === 0) {
733 $frame_description = '';
735 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
737 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator);
738 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
739 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
741 if ($frame_terminatorpos) {
742 // there are null bytes after the data - this is not according to spec
743 // only use data up to first null byte
744 $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
746 // no null bytes following data, just use all data
747 $frame_urldata = (string) $parsedFrame['data'];
750 $parsedFrame['encodingid'] = $frame_textencoding;
751 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
753 $parsedFrame['url'] = $frame_urldata;
754 $parsedFrame['description'] = $frame_description;
755 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
756 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
758 unset($parsedFrame['data']);
761 } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
762 // There may only be one URL link frame of its kind in a tag,
763 // except when stated otherwise in the frame description
764 // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
765 // described in 4.3.2.>
768 $parsedFrame['url'] = trim($parsedFrame['data']);
769 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
770 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
772 unset($parsedFrame['data']);
775 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
776 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
777 // http://id3.org/id3v2.3.0#sec4.4
778 // There may only be one 'IPL' frame in each tag
779 // <Header for 'User defined URL link frame', ID: 'IPL'>
781 // People list strings <textstrings>
784 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
785 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
786 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
788 $parsedFrame['encodingid'] = $frame_textencoding;
789 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
790 $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset);
792 // http://www.getid3.org/phpBB3/viewtopic.php?t=1369
793 // "this tag typically contains null terminated strings, which are associated in pairs"
794 // "there are users that use the tag incorrectly"
795 $IPLS_parts = array();
796 if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
797 $IPLS_parts_unsorted = array();
798 if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
799 // 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
801 for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
802 $twobytes = substr($parsedFrame['data_raw'], $i, 2);
803 if ($twobytes === "\x00\x00") {
804 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
807 $thisILPS .= $twobytes;
810 if (strlen($thisILPS) > 2) { // 2-byte BOM
811 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
814 // ISO-8859-1 or UTF-8 or other single-byte-null character set
815 $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
817 if (count($IPLS_parts_unsorted) == 1) {
818 // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
819 foreach ($IPLS_parts_unsorted as $key => $value) {
820 $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
822 foreach ($IPLS_parts_sorted as $person) {
823 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
826 } elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
829 foreach ($IPLS_parts_unsorted as $key => $value) {
830 if (($key % 2) == 0) {
834 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
840 foreach ($IPLS_parts_unsorted as $key => $value) {
841 $IPLS_parts[] = array($value);
846 $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
848 $parsedFrame['data'] = $IPLS_parts;
850 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
851 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
855 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier
856 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
857 // There may only be one 'MCDI' frame in each tag
858 // <Header for 'Music CD identifier', ID: 'MCDI'>
859 // CD TOC <binary data>
861 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
862 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
866 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes
867 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes
868 // There may only be one 'ETCO' frame in each tag
869 // <Header for 'Event timing codes', ID: 'ETCO'>
870 // Time stamp format $xx
871 // Where time stamp format is:
872 // $01 (32-bit value) MPEG frames from beginning of file
873 // $02 (32-bit value) milliseconds from beginning of file
874 // Followed by a list of key events in the following format:
876 // Time stamp $xx (xx ...)
877 // The 'Time stamp' is set to zero if directly at the beginning of the sound
878 // or after the previous event. All events MUST be sorted in chronological order.
881 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
883 while ($frame_offset < strlen($parsedFrame['data'])) {
884 $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1);
885 $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']);
886 $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
889 unset($parsedFrame['data']);
892 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table
893 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table
894 // There may only be one 'MLLT' frame in each tag
895 // <Header for 'Location lookup table', ID: 'MLLT'>
896 // MPEG frames between reference $xx xx
897 // Bytes between reference $xx xx xx
898 // Milliseconds between reference $xx xx xx
899 // Bits for bytes deviation $xx
900 // Bits for milliseconds dev. $xx
901 // Then for every reference the following data is included;
902 // Deviation in bytes %xxx....
903 // Deviation in milliseconds %xxx....
906 $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
907 $parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
908 $parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
909 $parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
910 $parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
911 $parsedFrame['data'] = substr($parsedFrame['data'], 10);
912 while ($frame_offset < strlen($parsedFrame['data'])) {
913 $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
915 $reference_counter = 0;
916 while (strlen($deviationbitstream) > 0) {
917 $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
918 $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
919 $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
920 $reference_counter++;
922 unset($parsedFrame['data']);
925 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes
926 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes
927 // There may only be one 'SYTC' frame in each tag
928 // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
929 // Time stamp format $xx
930 // Tempo data <binary data>
931 // Where time stamp format is:
932 // $01 (32-bit value) MPEG frames from beginning of file
933 // $02 (32-bit value) milliseconds from beginning of file
936 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
937 $timestamp_counter = 0;
938 while ($frame_offset < strlen($parsedFrame['data'])) {
939 $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
940 if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
941 $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
943 $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
945 $timestamp_counter++;
947 unset($parsedFrame['data']);
950 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription
951 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription
952 // There may be more than one 'Unsynchronised lyrics/text transcription' frame
953 // in each tag, but only one with the same language and content descriptor.
954 // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
956 // Language $xx xx xx
957 // Content descriptor <text string according to encoding> $00 (00)
958 // Lyrics/text <full text string according to encoding>
961 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
962 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
963 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
964 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
965 $frame_textencoding_terminator = "\x00";
967 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
969 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
970 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
971 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
973 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
974 if (ord($frame_description) === 0) {
975 $frame_description = '';
977 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
979 $parsedFrame['encodingid'] = $frame_textencoding;
980 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
982 $parsedFrame['data'] = $parsedFrame['data'];
983 $parsedFrame['language'] = $frame_language;
984 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
985 $parsedFrame['description'] = $frame_description;
986 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
987 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
989 unset($parsedFrame['data']);
992 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text
993 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text
994 // There may be more than one 'SYLT' frame in each tag,
995 // but only one with the same language and content descriptor.
996 // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
998 // Language $xx xx xx
999 // Time stamp format $xx
1000 // $01 (32-bit value) MPEG frames from beginning of file
1001 // $02 (32-bit value) milliseconds from beginning of file
1003 // Content descriptor <text string according to encoding> $00 (00)
1004 // Terminated text to be synced (typically a syllable)
1005 // Sync identifier (terminator to above string) $00 (00)
1006 // Time stamp $xx (xx ...)
1009 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1010 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1011 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1012 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1013 $frame_textencoding_terminator = "\x00";
1015 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1017 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1018 $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1019 $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1020 $parsedFrame['encodingid'] = $frame_textencoding;
1021 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1023 $parsedFrame['language'] = $frame_language;
1024 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1026 $timestampindex = 0;
1027 $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1028 while (strlen($frame_remainingdata)) {
1030 $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
1031 if ($frame_terminatorpos === false) {
1032 $frame_remainingdata = '';
1034 if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1035 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1037 $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1039 $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
1040 if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
1041 // timestamp probably omitted for first data item
1043 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1044 $frame_remainingdata = substr($frame_remainingdata, 4);
1049 unset($parsedFrame['data']);
1052 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments
1053 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments
1054 // There may be more than one comment frame in each tag,
1055 // but only one with the same language and content descriptor.
1056 // <Header for 'Comment', ID: 'COMM'>
1057 // Text encoding $xx
1058 // Language $xx xx xx
1059 // Short content descrip. <text string according to encoding> $00 (00)
1060 // The actual text <full text string according to encoding>
1062 if (strlen($parsedFrame['data']) < 5) {
1064 $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
1069 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1070 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1071 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1072 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1073 $frame_textencoding_terminator = "\x00";
1075 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1077 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1078 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1079 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1081 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1082 if (ord($frame_description) === 0) {
1083 $frame_description = '';
1085 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1087 $parsedFrame['encodingid'] = $frame_textencoding;
1088 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1090 $parsedFrame['language'] = $frame_language;
1091 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1092 $parsedFrame['description'] = $frame_description;
1093 $parsedFrame['data'] = $frame_text;
1094 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1095 $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
1096 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
1097 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1099 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1105 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1106 // There may be more than one 'RVA2' frame in each tag,
1107 // but only one with the same identification string
1108 // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1109 // Identification <text string> $00
1110 // The 'identification' string is used to identify the situation and/or
1111 // device where this adjustment should apply. The following is then
1112 // repeated for every channel:
1113 // Type of channel $xx
1114 // Volume adjustment $xx xx
1115 // Bits representing peak $xx
1116 // Peak volume $xx (xx ...)
1118 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1119 $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1120 if (ord($frame_idstring) === 0) {
1121 $frame_idstring = '';
1123 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1124 $parsedFrame['description'] = $frame_idstring;
1125 $RVA2channelcounter = 0;
1126 while (strlen($frame_remainingdata) >= 5) {
1128 $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1129 $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid;
1130 $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1131 $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1133 $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1134 if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1135 $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
1138 $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1139 $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1140 $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1141 $RVA2channelcounter++;
1143 unset($parsedFrame['data']);
1146 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
1147 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only)
1148 // There may only be one 'RVA' frame in each tag
1149 // <Header for 'Relative volume adjustment', ID: 'RVA'>
1150 // ID3v2.2 => Increment/decrement %000000ba
1151 // ID3v2.3 => Increment/decrement %00fedcba
1152 // Bits used for volume descr. $xx
1153 // Relative volume change, right $xx xx (xx ...) // a
1154 // Relative volume change, left $xx xx (xx ...) // b
1155 // Peak volume right $xx xx (xx ...)
1156 // Peak volume left $xx xx (xx ...)
1157 // ID3v2.3 only, optional (not present in ID3v2.2):
1158 // Relative volume change, right back $xx xx (xx ...) // c
1159 // Relative volume change, left back $xx xx (xx ...) // d
1160 // Peak volume right back $xx xx (xx ...)
1161 // Peak volume left back $xx xx (xx ...)
1162 // ID3v2.3 only, optional (not present in ID3v2.2):
1163 // Relative volume change, center $xx xx (xx ...) // e
1164 // Peak volume center $xx xx (xx ...)
1165 // ID3v2.3 only, optional (not present in ID3v2.2):
1166 // Relative volume change, bass $xx xx (xx ...) // f
1167 // Peak volume bass $xx xx (xx ...)
1170 $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1171 $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1172 $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1);
1173 $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1174 $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1175 $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1176 if ($parsedFrame['incdec']['right'] === false) {
1177 $parsedFrame['volumechange']['right'] *= -1;
1179 $frame_offset += $frame_bytesvolume;
1180 $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1181 if ($parsedFrame['incdec']['left'] === false) {
1182 $parsedFrame['volumechange']['left'] *= -1;
1184 $frame_offset += $frame_bytesvolume;
1185 $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1186 $frame_offset += $frame_bytesvolume;
1187 $parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1188 $frame_offset += $frame_bytesvolume;
1189 if ($id3v2_majorversion == 3) {
1190 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1191 if (strlen($parsedFrame['data']) > 0) {
1192 $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1193 $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1);
1194 $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1195 if ($parsedFrame['incdec']['rightrear'] === false) {
1196 $parsedFrame['volumechange']['rightrear'] *= -1;
1198 $frame_offset += $frame_bytesvolume;
1199 $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1200 if ($parsedFrame['incdec']['leftrear'] === false) {
1201 $parsedFrame['volumechange']['leftrear'] *= -1;
1203 $frame_offset += $frame_bytesvolume;
1204 $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1205 $frame_offset += $frame_bytesvolume;
1206 $parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1207 $frame_offset += $frame_bytesvolume;
1209 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1210 if (strlen($parsedFrame['data']) > 0) {
1211 $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1212 $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1213 if ($parsedFrame['incdec']['center'] === false) {
1214 $parsedFrame['volumechange']['center'] *= -1;
1216 $frame_offset += $frame_bytesvolume;
1217 $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1218 $frame_offset += $frame_bytesvolume;
1220 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1221 if (strlen($parsedFrame['data']) > 0) {
1222 $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1223 $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1224 if ($parsedFrame['incdec']['bass'] === false) {
1225 $parsedFrame['volumechange']['bass'] *= -1;
1227 $frame_offset += $frame_bytesvolume;
1228 $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1229 $frame_offset += $frame_bytesvolume;
1232 unset($parsedFrame['data']);
1235 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
1236 // There may be more than one 'EQU2' frame in each tag,
1237 // but only one with the same identification string
1238 // <Header of 'Equalisation (2)', ID: 'EQU2'>
1239 // Interpolation method $xx
1242 // Identification <text string> $00
1243 // The following is then repeated for every adjustment point
1245 // Volume adjustment $xx xx
1248 $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1249 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1250 $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1251 if (ord($frame_idstring) === 0) {
1252 $frame_idstring = '';
1254 $parsedFrame['description'] = $frame_idstring;
1255 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1256 while (strlen($frame_remainingdata)) {
1257 $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1258 $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1259 $frame_remainingdata = substr($frame_remainingdata, 4);
1261 $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1262 unset($parsedFrame['data']);
1265 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only)
1266 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only)
1267 // There may only be one 'EQUA' frame in each tag
1268 // <Header for 'Relative volume adjustment', ID: 'EQU'>
1269 // Adjustment bits $xx
1270 // This is followed by 2 bytes + ('adjustment bits' rounded up to the
1271 // nearest byte) for every equalisation band in the following format,
1272 // giving a frequency range of 0 - 32767Hz:
1273 // Increment/decrement %x (MSB of the Frequency)
1274 // Frequency (lower 15 bits)
1275 // Adjustment $xx (xx ...)
1278 $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1279 $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1281 $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1282 while (strlen($frame_remainingdata) > 0) {
1283 $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1284 $frame_incdec = (bool) substr($frame_frequencystr, 0, 1);
1285 $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1286 $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1287 $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1288 if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1289 $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1291 $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1293 unset($parsedFrame['data']);
1296 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb
1297 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb
1298 // There may only be one 'RVRB' frame in each tag.
1299 // <Header for 'Reverb', ID: 'RVRB'>
1300 // Reverb left (ms) $xx xx
1301 // Reverb right (ms) $xx xx
1302 // Reverb bounces, left $xx
1303 // Reverb bounces, right $xx
1304 // Reverb feedback, left to left $xx
1305 // Reverb feedback, left to right $xx
1306 // Reverb feedback, right to right $xx
1307 // Reverb feedback, right to left $xx
1308 // Premix left to right $xx
1309 // Premix right to left $xx
1312 $parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1314 $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1316 $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1317 $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1318 $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1319 $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1320 $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1321 $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1322 $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1323 $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1324 unset($parsedFrame['data']);
1327 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture
1328 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture
1329 // There may be several pictures attached to one file,
1330 // each in their individual 'APIC' frame, but only one
1331 // with the same content descriptor
1332 // <Header for 'Attached picture', ID: 'APIC'>
1333 // Text encoding $xx
1334 // ID3v2.3+ => MIME type <text string> $00
1335 // ID3v2.2 => Image format $xx xx xx
1337 // Description <text string according to encoding> $00 (00)
1338 // Picture data <binary data>
1341 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1342 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1343 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1344 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1345 $frame_textencoding_terminator = "\x00";
1348 if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1349 $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1350 if (strtolower($frame_imagetype) == 'ima') {
1351 // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1352 // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net)
1353 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1354 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1355 if (ord($frame_mimetype) === 0) {
1356 $frame_mimetype = '';
1358 $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1359 if ($frame_imagetype == 'JPEG') {
1360 $frame_imagetype = 'JPG';
1362 $frame_offset = $frame_terminatorpos + strlen("\x00");
1367 if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1368 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1369 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1370 if (ord($frame_mimetype) === 0) {
1371 $frame_mimetype = '';
1373 $frame_offset = $frame_terminatorpos + strlen("\x00");
1376 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1378 if ($frame_offset >= $parsedFrame['datalength']) {
1379 $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
1381 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1382 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1383 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1385 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1386 if (ord($frame_description) === 0) {
1387 $frame_description = '';
1389 $parsedFrame['encodingid'] = $frame_textencoding;
1390 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1392 if ($id3v2_majorversion == 2) {
1393 $parsedFrame['imagetype'] = $frame_imagetype;
1395 $parsedFrame['mime'] = $frame_mimetype;
1397 $parsedFrame['picturetypeid'] = $frame_picturetype;
1398 $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype);
1399 $parsedFrame['description'] = $frame_description;
1400 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1401 $parsedFrame['datalength'] = strlen($parsedFrame['data']);
1403 $parsedFrame['image_mime'] = '';
1404 $imageinfo = array();
1405 $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
1406 if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1407 $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
1408 if ($imagechunkcheck[0]) {
1409 $parsedFrame['image_width'] = $imagechunkcheck[0];
1411 if ($imagechunkcheck[1]) {
1412 $parsedFrame['image_height'] = $imagechunkcheck[1];
1417 if ($this->getid3->option_save_attachments === false) {
1419 unset($parsedFrame['data']);
1422 if ($this->getid3->option_save_attachments === true) {
1425 } elseif (is_int($this->getid3->option_save_attachments)) {
1426 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1428 $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
1429 unset($parsedFrame['data']);
1433 } elseif (is_string($this->getid3->option_save_attachments)) {
1434 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1435 if (!is_dir($dir) || !is_writable($dir)) {
1436 // cannot write, skip
1437 $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)';
1438 unset($parsedFrame['data']);
1442 // if we get this far, must be OK
1443 if (is_string($this->getid3->option_save_attachments)) {
1444 $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1445 if (!file_exists($destination_filename) || is_writable($destination_filename)) {
1446 file_put_contents($destination_filename, $parsedFrame['data']);
1448 $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
1450 $parsedFrame['data_filename'] = $destination_filename;
1451 unset($parsedFrame['data']);
1453 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1454 if (!isset($info['id3v2']['comments']['picture'])) {
1455 $info['id3v2']['comments']['picture'] = array();
1457 $comments_picture_data = array();
1458 foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
1459 if (isset($parsedFrame[$picture_key])) {
1460 $comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
1463 $info['id3v2']['comments']['picture'][] = $comments_picture_data;
1464 unset($comments_picture_data);
1470 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object
1471 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object
1472 // There may be more than one 'GEOB' frame in each tag,
1473 // but only one with the same content descriptor
1474 // <Header for 'General encapsulated object', ID: 'GEOB'>
1475 // Text encoding $xx
1476 // MIME type <text string> $00
1477 // Filename <text string according to encoding> $00 (00)
1478 // Content description <text string according to encoding> $00 (00)
1479 // Encapsulated object <binary data>
1482 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1483 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1484 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1485 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1486 $frame_textencoding_terminator = "\x00";
1488 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1489 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1490 if (ord($frame_mimetype) === 0) {
1491 $frame_mimetype = '';
1493 $frame_offset = $frame_terminatorpos + strlen("\x00");
1495 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1496 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1497 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1499 $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1500 if (ord($frame_filename) === 0) {
1501 $frame_filename = '';
1503 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1505 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1506 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1507 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1509 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1510 if (ord($frame_description) === 0) {
1511 $frame_description = '';
1513 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1515 $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset);
1516 $parsedFrame['encodingid'] = $frame_textencoding;
1517 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1519 $parsedFrame['mime'] = $frame_mimetype;
1520 $parsedFrame['filename'] = $frame_filename;
1521 $parsedFrame['description'] = $frame_description;
1522 unset($parsedFrame['data']);
1525 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter
1526 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter
1527 // There may only be one 'PCNT' frame in each tag.
1528 // When the counter reaches all one's, one byte is inserted in
1529 // front of the counter thus making the counter eight bits bigger
1530 // <Header for 'Play counter', ID: 'PCNT'>
1531 // Counter $xx xx xx xx (xx ...)
1533 $parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']);
1536 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter
1537 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter
1538 // There may be more than one 'POPM' frame in each tag,
1539 // but only one with the same email address
1540 // <Header for 'Popularimeter', ID: 'POPM'>
1541 // Email to user <text string> $00
1543 // Counter $xx xx xx xx (xx ...)
1546 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1547 $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1548 if (ord($frame_emailaddress) === 0) {
1549 $frame_emailaddress = '';
1551 $frame_offset = $frame_terminatorpos + strlen("\x00");
1552 $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1553 $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1554 $parsedFrame['email'] = $frame_emailaddress;
1555 $parsedFrame['rating'] = $frame_rating;
1556 unset($parsedFrame['data']);
1559 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size
1560 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size
1561 // There may only be one 'RBUF' frame in each tag
1562 // <Header for 'Recommended buffer size', ID: 'RBUF'>
1563 // Buffer size $xx xx xx
1564 // Embedded info flag %0000000x
1565 // Offset to next tag $xx xx xx xx
1568 $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1571 $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1572 $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1573 $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1574 unset($parsedFrame['data']);
1577 } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only)
1578 // There may be more than one 'CRM' frame in a tag,
1579 // but only one with the same 'owner identifier'
1580 // <Header for 'Encrypted meta frame', ID: 'CRM'>
1581 // Owner identifier <textstring> $00 (00)
1582 // Content/explanation <textstring> $00 (00)
1583 // Encrypted datablock <binary data>
1586 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1587 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1588 $frame_offset = $frame_terminatorpos + strlen("\x00");
1590 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1591 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1592 if (ord($frame_description) === 0) {
1593 $frame_description = '';
1595 $frame_offset = $frame_terminatorpos + strlen("\x00");
1597 $parsedFrame['ownerid'] = $frame_ownerid;
1598 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1599 $parsedFrame['description'] = $frame_description;
1600 unset($parsedFrame['data']);
1603 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption
1604 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption
1605 // There may be more than one 'AENC' frames in a tag,
1606 // but only one with the same 'Owner identifier'
1607 // <Header for 'Audio encryption', ID: 'AENC'>
1608 // Owner identifier <text string> $00
1609 // Preview start $xx xx
1610 // Preview length $xx xx
1611 // Encryption info <binary data>
1614 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1615 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1616 if (ord($frame_ownerid) === 0) {
1617 $frame_ownerid == '';
1619 $frame_offset = $frame_terminatorpos + strlen("\x00");
1620 $parsedFrame['ownerid'] = $frame_ownerid;
1621 $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1623 $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1625 $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1626 unset($parsedFrame['data']);
1629 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information
1630 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information
1631 // There may be more than one 'LINK' frame in a tag,
1632 // but only one with the same contents
1633 // <Header for 'Linked information', ID: 'LINK'>
1634 // ID3v2.3+ => Frame identifier $xx xx xx xx
1635 // ID3v2.2 => Frame identifier $xx xx xx
1636 // URL <text string> $00
1637 // ID and additional data <text string(s)>
1640 if ($id3v2_majorversion == 2) {
1641 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1644 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1648 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1649 $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1650 if (ord($frame_url) === 0) {
1653 $frame_offset = $frame_terminatorpos + strlen("\x00");
1654 $parsedFrame['url'] = $frame_url;
1656 $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1657 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1658 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
1660 unset($parsedFrame['data']);
1663 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
1664 // There may only be one 'POSS' frame in each tag
1665 // <Head for 'Position synchronisation', ID: 'POSS'>
1666 // Time stamp format $xx
1667 // Position $xx (xx ...)
1670 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1671 $parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1672 unset($parsedFrame['data']);
1675 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only)
1676 // There may be more than one 'Terms of use' frame in a tag,
1677 // but only one with the same 'Language'
1678 // <Header for 'Terms of use frame', ID: 'USER'>
1679 // Text encoding $xx
1680 // Language $xx xx xx
1681 // The actual text <text string according to encoding>
1684 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1685 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1686 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1688 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1690 $parsedFrame['language'] = $frame_language;
1691 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1692 $parsedFrame['encodingid'] = $frame_textencoding;
1693 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1695 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1696 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1697 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1699 unset($parsedFrame['data']);
1702 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only)
1703 // There may only be one 'OWNE' frame in a tag
1704 // <Header for 'Ownership frame', ID: 'OWNE'>
1705 // Text encoding $xx
1706 // Price paid <text string> $00
1707 // Date of purch. <text string>
1708 // Seller <text string according to encoding>
1711 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1712 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1713 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1715 $parsedFrame['encodingid'] = $frame_textencoding;
1716 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1718 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1719 $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1720 $frame_offset = $frame_terminatorpos + strlen("\x00");
1722 $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1723 $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1724 $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3);
1726 $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1727 if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1728 $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1732 $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1733 unset($parsedFrame['data']);
1736 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only)
1737 // There may be more than one 'commercial frame' in a tag,
1738 // but no two may be identical
1739 // <Header for 'Commercial frame', ID: 'COMR'>
1740 // Text encoding $xx
1741 // Price string <text string> $00
1742 // Valid until <text string>
1743 // Contact URL <text string> $00
1745 // Name of seller <text string according to encoding> $00 (00)
1746 // Description <text string according to encoding> $00 (00)
1747 // Picture MIME type <string> $00
1748 // Seller logo <binary data>
1751 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1752 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1753 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1754 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1755 $frame_textencoding_terminator = "\x00";
1758 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1759 $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1760 $frame_offset = $frame_terminatorpos + strlen("\x00");
1761 $frame_rawpricearray = explode('/', $frame_pricestring);
1762 foreach ($frame_rawpricearray as $key => $val) {
1763 $frame_currencyid = substr($val, 0, 3);
1764 $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1765 $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3);
1768 $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1771 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1772 $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1773 $frame_offset = $frame_terminatorpos + strlen("\x00");
1775 $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1777 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1778 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1779 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1781 $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1782 if (ord($frame_sellername) === 0) {
1783 $frame_sellername = '';
1785 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1787 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1788 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1789 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1791 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1792 if (ord($frame_description) === 0) {
1793 $frame_description = '';
1795 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1797 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1798 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1799 $frame_offset = $frame_terminatorpos + strlen("\x00");
1801 $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1803 $parsedFrame['encodingid'] = $frame_textencoding;
1804 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1806 $parsedFrame['pricevaliduntil'] = $frame_datestring;
1807 $parsedFrame['contacturl'] = $frame_contacturl;
1808 $parsedFrame['receivedasid'] = $frame_receivedasid;
1809 $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid);
1810 $parsedFrame['sellername'] = $frame_sellername;
1811 $parsedFrame['description'] = $frame_description;
1812 $parsedFrame['mime'] = $frame_mimetype;
1813 $parsedFrame['logo'] = $frame_sellerlogo;
1814 unset($parsedFrame['data']);
1817 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
1818 // There may be several 'ENCR' frames in a tag,
1819 // but only one containing the same symbol
1820 // and only one containing the same owner identifier
1821 // <Header for 'Encryption method registration', ID: 'ENCR'>
1822 // Owner identifier <text string> $00
1823 // Method symbol $xx
1824 // Encryption data <binary data>
1827 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1828 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1829 if (ord($frame_ownerid) === 0) {
1830 $frame_ownerid = '';
1832 $frame_offset = $frame_terminatorpos + strlen("\x00");
1834 $parsedFrame['ownerid'] = $frame_ownerid;
1835 $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1836 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1839 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only)
1841 // There may be several 'GRID' frames in a tag,
1842 // but only one containing the same symbol
1843 // and only one containing the same owner identifier
1844 // <Header for 'Group ID registration', ID: 'GRID'>
1845 // Owner identifier <text string> $00
1847 // Group dependent data <binary data>
1850 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1851 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1852 if (ord($frame_ownerid) === 0) {
1853 $frame_ownerid = '';
1855 $frame_offset = $frame_terminatorpos + strlen("\x00");
1857 $parsedFrame['ownerid'] = $frame_ownerid;
1858 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1859 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1862 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only)
1863 // The tag may contain more than one 'PRIV' frame
1864 // but only with different contents
1865 // <Header for 'Private frame', ID: 'PRIV'>
1866 // Owner identifier <text string> $00
1867 // The private data <binary data>
1870 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1871 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1872 if (ord($frame_ownerid) === 0) {
1873 $frame_ownerid = '';
1875 $frame_offset = $frame_terminatorpos + strlen("\x00");
1877 $parsedFrame['ownerid'] = $frame_ownerid;
1878 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1881 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only)
1882 // There may be more than one 'signature frame' in a tag,
1883 // but no two may be identical
1884 // <Header for 'Signature frame', ID: 'SIGN'>
1886 // Signature <binary data>
1889 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1890 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1893 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only)
1894 // There may only be one 'seek frame' in a tag
1895 // <Header for 'Seek frame', ID: 'SEEK'>
1896 // Minimum offset to next tag $xx xx xx xx
1899 $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1902 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
1903 // There may only be one 'audio seek point index' frame in a tag
1904 // <Header for 'Seek Point Index', ID: 'ASPI'>
1905 // Indexed data start (S) $xx xx xx xx
1906 // Indexed data length (L) $xx xx xx xx
1907 // Number of index points (N) $xx xx
1908 // Bits per index point (b) $xx
1909 // Then for every index point the following data is included:
1910 // Fraction at index (Fi) $xx (xx)
1913 $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1915 $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1917 $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1919 $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1920 $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1921 for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
1922 $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1923 $frame_offset += $frame_bytesperpoint;
1925 unset($parsedFrame['data']);
1927 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1928 // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1929 // There may only be one 'RGAD' frame in a tag
1930 // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1931 // Peak Amplitude $xx $xx $xx $xx
1932 // Radio Replay Gain Adjustment %aaabbbcd %dddddddd
1933 // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
1935 // b - originator code
1937 // d - replay gain adjustment
1940 $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1942 $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1944 $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1946 $parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1947 $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1948 $parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1949 $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1950 $parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1951 $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1952 $parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
1953 $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
1954 $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1955 $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1956 $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1957 $parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1958 $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1959 $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1961 $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude'];
1962 $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1963 $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1964 $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1965 $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1967 unset($parsedFrame['data']);
1969 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
1970 // http://id3.org/id3v2-chapters-1.0
1971 // <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP"> (10 bytes)
1972 // Element ID <text string> $00
1973 // Start time $xx xx xx xx
1974 // End time $xx xx xx xx
1975 // Start offset $xx xx xx xx
1976 // End offset $xx xx xx xx
1977 // <Optional embedded sub-frames>
1980 @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
1981 $frame_offset += strlen($parsedFrame['element_id']."\x00");
1982 $parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1984 $parsedFrame['time_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1986 if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
1987 // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
1988 $parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1991 if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
1992 // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
1993 $parsedFrame['offset_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1997 if ($frame_offset < strlen($parsedFrame['data'])) {
1998 $parsedFrame['subframes'] = array();
1999 while ($frame_offset < strlen($parsedFrame['data'])) {
2000 // <Optional embedded sub-frames>
2001 $subframe = array();
2002 $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
2004 $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2006 $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2008 if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2009 $info['warning'][] = 'CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)';
2012 $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2013 $frame_offset += $subframe['size'];
2015 $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2016 $subframe['text'] = substr($subframe_rawdata, 1);
2017 $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
2018 $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
2019 switch (substr($encoding_converted_text, 0, 2)) {
2022 switch (strtoupper($info['id3v2']['encoding'])) {
2025 $encoding_converted_text = substr($encoding_converted_text, 2);
2026 // remove unwanted byte-order-marks
2034 // do not remove BOM
2038 if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
2039 if ($subframe['name'] == 'TIT2') {
2040 $parsedFrame['chapter_name'] = $encoding_converted_text;
2041 } elseif ($subframe['name'] == 'TIT3') {
2042 $parsedFrame['chapter_description'] = $encoding_converted_text;
2044 $parsedFrame['subframes'][] = $subframe;
2046 $info['warning'][] = 'ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)';
2049 unset($subframe_rawdata, $subframe, $encoding_converted_text);
2052 $id3v2_chapter_entry = array();
2053 foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') as $id3v2_chapter_key) {
2054 if (isset($parsedFrame[$id3v2_chapter_key])) {
2055 $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
2058 if (!isset($info['id3v2']['chapters'])) {
2059 $info['id3v2']['chapters'] = array();
2061 $info['id3v2']['chapters'][] = $id3v2_chapter_entry;
2062 unset($id3v2_chapter_entry, $id3v2_chapter_key);
2065 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
2066 // http://id3.org/id3v2-chapters-1.0
2067 // <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC"> (10 bytes)
2068 // Element ID <text string> $00
2071 // Child Element ID <string>$00 /* zero or more child CHAP or CTOC entries */
2072 // <Optional embedded sub-frames>
2075 @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2076 $frame_offset += strlen($parsedFrame['element_id']."\x00");
2077 $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
2079 $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
2082 $terminator_position = null;
2083 for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
2084 $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
2085 $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
2086 $frame_offset = $terminator_position + 1;
2089 $parsedFrame['ctoc_flags']['ordered'] = (bool) ($ctoc_flags_raw & 0x01);
2090 $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
2092 unset($ctoc_flags_raw, $terminator_position);
2094 if ($frame_offset < strlen($parsedFrame['data'])) {
2095 $parsedFrame['subframes'] = array();
2096 while ($frame_offset < strlen($parsedFrame['data'])) {
2097 // <Optional embedded sub-frames>
2098 $subframe = array();
2099 $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
2101 $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2103 $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2105 if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2106 $info['warning'][] = 'CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)';
2109 $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2110 $frame_offset += $subframe['size'];
2112 $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2113 $subframe['text'] = substr($subframe_rawdata, 1);
2114 $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
2115 $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
2116 switch (substr($encoding_converted_text, 0, 2)) {
2119 switch (strtoupper($info['id3v2']['encoding'])) {
2122 $encoding_converted_text = substr($encoding_converted_text, 2);
2123 // remove unwanted byte-order-marks
2131 // do not remove BOM
2135 if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
2136 if ($subframe['name'] == 'TIT2') {
2137 $parsedFrame['toc_name'] = $encoding_converted_text;
2138 } elseif ($subframe['name'] == 'TIT3') {
2139 $parsedFrame['toc_description'] = $encoding_converted_text;
2141 $parsedFrame['subframes'][] = $subframe;
2143 $info['warning'][] = 'ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)';
2146 unset($subframe_rawdata, $subframe, $encoding_converted_text);
2155 public function DeUnsynchronise($data) {
2156 return str_replace("\xFF\x00", "\xFF", $data);
2159 public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
2160 static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
2161 0x00 => 'No more than 128 frames and 1 MB total tag size',
2162 0x01 => 'No more than 64 frames and 128 KB total tag size',
2163 0x02 => 'No more than 32 frames and 40 KB total tag size',
2164 0x03 => 'No more than 32 frames and 4 KB total tag size',
2166 return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
2169 public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
2170 static $LookupExtendedHeaderRestrictionsTextEncodings = array(
2171 0x00 => 'No restrictions',
2172 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
2174 return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
2177 public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
2178 static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
2179 0x00 => 'No restrictions',
2180 0x01 => 'No string is longer than 1024 characters',
2181 0x02 => 'No string is longer than 128 characters',
2182 0x03 => 'No string is longer than 30 characters',
2184 return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
2187 public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
2188 static $LookupExtendedHeaderRestrictionsImageEncoding = array(
2189 0x00 => 'No restrictions',
2190 0x01 => 'Images are encoded only with PNG or JPEG',
2192 return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
2195 public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
2196 static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
2197 0x00 => 'No restrictions',
2198 0x01 => 'All images are 256x256 pixels or smaller',
2199 0x02 => 'All images are 64x64 pixels or smaller',
2200 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
2202 return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
2205 public function LookupCurrencyUnits($currencyid) {
2209 /** This is not a comment!
2223 BAM Convertible Marka
2240 CDF Congolese Francs
2386 XDR Special Drawing Rights
2394 ZWD Zimbabwe Dollars
2398 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2402 public function LookupCurrencyCountry($currencyid) {
2406 /** This is not a comment!
2408 AED United Arab Emirates
2412 ANG Netherlands Antilles
2419 BAM Bosnia and Herzegovina
2427 BND Brunei Darussalam
2449 DOP Dominican Republic
2456 EUR Euro Member Countries
2459 FKP Falkland Islands (Malvinas)
2516 MVR Maldives (Maldive Islands)
2524 NLG Netherlands (Holland)
2531 PGK Papua New Guinea
2554 STD São Tome and Principe
2564 TTD Trinidad and Tobago
2570 USD United States of America
2578 XAF Communauté Financière Africaine
2582 XDR International Monetary Fund
2584 XPF Comptoirs Français du Pacifique
2594 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2599 public static function LanguageLookup($languagecode, $casesensitive=false) {
2601 if (!$casesensitive) {
2602 $languagecode = strtolower($languagecode);
2605 // http://www.id3.org/id3v2.4.0-structure.txt
2606 // [4. ID3v2 frame overview]
2607 // The three byte language field, present in several frames, is used to
2608 // describe the language of the frame's content, according to ISO-639-2
2609 // [ISO-639-2]. The language should be represented in lower case. If the
2610 // language is not known the string "XXX" should be used.
2613 // ISO 639-2 - http://www.id3.org/iso639-2.html
2617 /** This is not a comment!
2626 afa Afro-Asiatic (Other)
2633 alg Algonquian Languages
2635 ang English, Old (ca. 450-1100)
2636 apa Apache Languages
2642 art Artificial (Other)
2645 ath Athapascan Languages
2652 bai Bamileke Languages
2680 cai Central American Indian (Other)
2683 cau Caucasian (Other)
2702 cpe Creoles and Pidgins, English-based (Other)
2703 cpf Creoles and Pidgins, French-based (Other)
2704 cpp Creoles and Pidgins, Portuguese-based (Other)
2706 crp Creoles and Pidgins (Other)
2707 cus Cushitic (Other)
2717 dra Dravidian (Other)
2719 dum Dutch, Middle (ca. 1050-1350)
2724 egy Egyptian (Ancient)
2726 ell Greek, Modern (1453-)
2729 enm English, Middle (ca. 1100-1500)
2743 fiu Finno-Ugrian (Other)
2747 frm French, Middle (ca. 1400-1600)
2748 fro French, Old (842- ca. 1400)
2756 gem Germanic (Other)
2762 gmh German, Middle High (ca. 1050-1500)
2763 goh German, Old High (ca. 750-1050)
2767 grc Greek, Ancient (to 1453)
2768 gre Greek, Modern (1453-)
2789 ina Interlingua (International Auxiliary language Association)
2792 ine Indo-European (Other)
2852 luo Luo (Kenya and Tanzania)
2863 map Austronesian (Other)
2869 mga Irish, Middle (900 - 1200)
2872 mis Miscellaneous (Other)
2873 mkh Mon-Kmer (Other)
2877 mno Manobo Languages
2884 mul Multiple Languages
2891 nai North American Indian (Other)
2899 nic Niger-Kordofanian (Other)
2902 nno Norwegian (Nynorsk)
2906 nub Nubian Languages
2912 oci Langue d'Oc (post 1500)
2918 ota Turkish, Ottoman (1500 - 1928)
2919 oto Otomian Languages
2920 paa Papuan-Australian (Other)
2927 peo Persian, Old (ca 600 - 400 B.C.)
2935 pro Provencal, Old (to 1500)
2950 sai South American Indian (Other)
2951 sal Salishan Languages
2952 sam Samaritan Aramaic
2958 sga Irish, Old (to 900)
2962 sio Siouan Languages
2963 sit Sino-Tibetan (Other)
2980 ssa Nilo-Saharan (Other)
3007 ton Tonga (Tonga Islands)
3030 wak Wakashan Languages
3035 wen Sorbian Languages
3051 return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
3055 public static function ETCOEventLookup($index) {
3056 if (($index >= 0x17) && ($index <= 0xDF)) {
3057 return 'reserved for future use';
3059 if (($index >= 0xE0) && ($index <= 0xEF)) {
3060 return 'not predefined synch 0-F';
3062 if (($index >= 0xF0) && ($index <= 0xFC)) {
3063 return 'reserved for future use';
3066 static $EventLookup = array(
3067 0x00 => 'padding (has no meaning)',
3068 0x01 => 'end of initial silence',
3069 0x02 => 'intro start',
3070 0x03 => 'main part start',
3071 0x04 => 'outro start',
3072 0x05 => 'outro end',
3073 0x06 => 'verse start',
3074 0x07 => 'refrain start',
3075 0x08 => 'interlude start',
3076 0x09 => 'theme start',
3077 0x0A => 'variation start',
3078 0x0B => 'key change',
3079 0x0C => 'time change',
3080 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
3081 0x0E => 'sustained noise',
3082 0x0F => 'sustained noise end',
3083 0x10 => 'intro end',
3084 0x11 => 'main part end',
3085 0x12 => 'verse end',
3086 0x13 => 'refrain end',
3087 0x14 => 'theme end',
3088 0x15 => 'profanity',
3089 0x16 => 'profanity end',
3090 0xFD => 'audio end (start of silence)',
3091 0xFE => 'audio file ends',
3092 0xFF => 'one more byte of events follows'
3095 return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
3098 public static function SYTLContentTypeLookup($index) {
3099 static $SYTLContentTypeLookup = array(
3102 0x02 => 'text transcription',
3103 0x03 => 'movement/part name', // (e.g. 'Adagio')
3104 0x04 => 'events', // (e.g. 'Don Quijote enters the stage')
3105 0x05 => 'chord', // (e.g. 'Bb F Fsus')
3106 0x06 => 'trivia/\'pop up\' information',
3107 0x07 => 'URLs to webpages',
3108 0x08 => 'URLs to images'
3111 return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
3114 public static function APICPictureTypeLookup($index, $returnarray=false) {
3115 static $APICPictureTypeLookup = array(
3117 0x01 => '32x32 pixels \'file icon\' (PNG only)',
3118 0x02 => 'Other file icon',
3119 0x03 => 'Cover (front)',
3120 0x04 => 'Cover (back)',
3121 0x05 => 'Leaflet page',
3122 0x06 => 'Media (e.g. label side of CD)',
3123 0x07 => 'Lead artist/lead performer/soloist',
3124 0x08 => 'Artist/performer',
3125 0x09 => 'Conductor',
3126 0x0A => 'Band/Orchestra',
3128 0x0C => 'Lyricist/text writer',
3129 0x0D => 'Recording Location',
3130 0x0E => 'During recording',
3131 0x0F => 'During performance',
3132 0x10 => 'Movie/video screen capture',
3133 0x11 => 'A bright coloured fish',
3134 0x12 => 'Illustration',
3135 0x13 => 'Band/artist logotype',
3136 0x14 => 'Publisher/Studio logotype'
3139 return $APICPictureTypeLookup;
3141 return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
3144 public static function COMRReceivedAsLookup($index) {
3145 static $COMRReceivedAsLookup = array(
3147 0x01 => 'Standard CD album with other songs',
3148 0x02 => 'Compressed audio on CD',
3149 0x03 => 'File over the Internet',
3150 0x04 => 'Stream over the Internet',
3151 0x05 => 'As note sheets',
3152 0x06 => 'As note sheets in a book with other sheets',
3153 0x07 => 'Music on other media',
3154 0x08 => 'Non-musical merchandise'
3157 return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
3160 public static function RVA2ChannelTypeLookup($index) {
3161 static $RVA2ChannelTypeLookup = array(
3163 0x01 => 'Master volume',
3164 0x02 => 'Front right',
3165 0x03 => 'Front left',
3166 0x04 => 'Back right',
3167 0x05 => 'Back left',
3168 0x06 => 'Front centre',
3169 0x07 => 'Back centre',
3173 return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
3176 public static function FrameNameLongLookup($framename) {
3180 /** This is not a comment!
3182 AENC Audio encryption
3183 APIC Attached picture
3184 ASPI Audio seek point index
3185 BUF Recommended buffer size
3189 COMR Commercial frame
3190 CRA Audio encryption
3191 CRM Encrypted meta frame
3192 ENCR Encryption method registration
3194 EQU2 Equalisation (2)
3196 ETC Event timing codes
3197 ETCO Event timing codes
3198 GEO General encapsulated object
3199 GEOB General encapsulated object
3200 GRID Group identification registration
3201 IPL Involved people list
3202 IPLS Involved people list
3203 LINK Linked information
3204 LNK Linked information
3205 MCDI Music CD identifier
3206 MCI Music CD Identifier
3207 MLL MPEG location lookup table
3208 MLLT MPEG location lookup table
3209 OWNE Ownership frame
3211 PIC Attached picture
3214 POSS Position synchronisation frame
3216 RBUF Recommended buffer size
3218 RVA Relative volume adjustment
3219 RVA2 Relative volume adjustment (2)
3220 RVAD Relative volume adjustment
3223 SIGN Signature frame
3224 SLT Synchronised lyric/text
3225 STC Synced tempo codes
3226 SYLT Synchronised lyric/text
3227 SYTC Synchronised tempo codes
3228 TAL Album/Movie/Show title
3229 TALB Album/Movie/Show title
3230 TBP BPM (Beats Per Minute)
3231 TBPM BPM (beats per minute)
3233 TCMP Part of a compilation
3237 TCOP Copyright message
3238 TCP Part of a compilation
3239 TCR Copyright message
3244 TDOR Original release time
3251 TEXT Lyricist/Text writer
3256 TIPL Involved people list
3257 TIT1 Content group description
3258 TIT2 Title/songname/content description
3259 TIT3 Subtitle/Description refinement
3266 TMCL Musician credits list
3270 TOA Original artist(s)/performer(s)
3271 TOAL Original album/movie/show title
3272 TOF Original filename
3273 TOFN Original filename
3274 TOL Original Lyricist(s)/text writer(s)
3275 TOLY Original lyricist(s)/text writer(s)
3276 TOPE Original artist(s)/performer(s)
3277 TOR Original release year
3278 TORY Original release year
3279 TOT Original album/Movie/Show title
3280 TOWN File owner/licensee
3281 TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3282 TP2 Band/Orchestra/Accompaniment
3283 TP3 Conductor/Performer refinement
3284 TP4 Interpreted, remixed, or otherwise modified by
3287 TPE1 Lead performer(s)/Soloist(s)
3288 TPE2 Band/orchestra/accompaniment
3289 TPE3 Conductor/performer refinement
3290 TPE4 Interpreted, remixed, or otherwise modified by
3292 TPRO Produced notice
3294 TRC ISRC (International Standard Recording Code)
3295 TRCK Track number/Position in set
3297 TRDA Recording dates
3298 TRK Track number/Position in set
3299 TRSN Internet radio station name
3300 TRSO Internet radio station owner
3301 TS2 Album-Artist sort order
3302 TSA Album sort order
3303 TSC Composer sort order
3306 TSO2 Album-Artist sort order
3307 TSOA Album sort order
3308 TSOC Composer sort order
3309 TSOP Performer sort order
3310 TSOT Title sort order
3311 TSP Performer sort order
3312 TSRC ISRC (international standard recording code)
3313 TSS Software/hardware and settings used for encoding
3314 TSSE Software/Hardware and settings used for encoding
3316 TST Title sort order
3317 TT1 Content group description
3318 TT2 Title/Songname/Content description
3319 TT3 Subtitle/Description refinement
3320 TXT Lyricist/text writer
3321 TXX User defined text information frame
3322 TXXX User defined text information frame
3325 UFI Unique file identifier
3326 UFID Unique file identifier
3327 ULT Unsychronised lyric/text transcription
3329 USLT Unsynchronised lyric/text transcription
3330 WAF Official audio file webpage
3331 WAR Official artist/performer webpage
3332 WAS Official audio source webpage
3333 WCM Commercial information
3334 WCOM Commercial information
3335 WCOP Copyright/Legal information
3336 WCP Copyright/Legal information
3337 WOAF Official audio file webpage
3338 WOAR Official artist/performer webpage
3339 WOAS Official audio source webpage
3340 WORS Official Internet radio station homepage
3342 WPB Publishers official webpage
3343 WPUB Publishers official webpage
3344 WXX User defined URL link frame
3345 WXXX User defined URL link frame
3346 TFEA Featured Artist
3347 TSTU Recording Studio
3348 rgad Replay Gain Adjustment
3352 return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
3355 // from Helium2 [www.helium2.com]
3356 // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3360 public static function FrameNameShortLookup($framename) {
3364 /** This is not a comment!
3366 AENC audio_encryption
3367 APIC attached_picture
3368 ASPI audio_seek_point_index
3369 BUF recommended_buffer_size
3373 COMR commercial_frame
3374 CRA audio_encryption
3375 CRM encrypted_meta_frame
3376 ENCR encryption_method_registration
3380 ETC event_timing_codes
3381 ETCO event_timing_codes
3382 GEO general_encapsulated_object
3383 GEOB general_encapsulated_object
3384 GRID group_identification_registration
3385 IPL involved_people_list
3386 IPLS involved_people_list
3387 LINK linked_information
3388 LNK linked_information
3389 MCDI music_cd_identifier
3390 MCI music_cd_identifier
3391 MLL mpeg_location_lookup_table
3392 MLLT mpeg_location_lookup_table
3393 OWNE ownership_frame
3395 PIC attached_picture
3398 POSS position_synchronisation_frame
3400 RBUF recommended_buffer_size
3402 RVA relative_volume_adjustment
3403 RVA2 relative_volume_adjustment
3404 RVAD relative_volume_adjustment
3407 SIGN signature_frame
3408 SLT synchronised_lyric
3409 STC synced_tempo_codes
3410 SYLT synchronised_lyric
3411 SYTC synchronised_tempo_codes
3417 TCMP part_of_a_compilation
3421 TCOP copyright_message
3422 TCP part_of_a_compilation
3423 TCR copyright_message
3428 TDOR original_release_time
3440 TIPL involved_people_list
3441 TIT1 content_group_description
3450 TMCL musician_credits_list
3456 TOF original_filename
3457 TOFN original_filename
3458 TOL original_lyricist
3459 TOLY original_lyricist
3460 TOPE original_artist
3476 TPRO produced_notice
3481 TRDA recording_dates
3483 TRSN internet_radio_station_name
3484 TRSO internet_radio_station_owner
3485 TS2 album_artist_sort_order
3486 TSA album_sort_order
3487 TSC composer_sort_order
3490 TSO2 album_artist_sort_order
3491 TSOA album_sort_order
3492 TSOC composer_sort_order
3493 TSOP performer_sort_order
3494 TSOT title_sort_order
3495 TSP performer_sort_order
3497 TSS encoder_settings
3498 TSSE encoder_settings
3500 TST title_sort_order
3501 TT1 content_group_description
3509 UFI unique_file_identifier
3510 UFID unique_file_identifier
3511 ULT unsychronised_lyric
3513 USLT unsynchronised_lyric
3517 WCM commercial_information
3518 WCOM commercial_information
3530 TFEA featured_artist
3531 TSTU recording_studio
3532 rgad replay_gain_adjustment
3536 return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
3539 public static function TextEncodingTerminatorLookup($encoding) {
3540 // http://www.id3.org/id3v2.4.0-structure.txt
3541 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3542 static $TextEncodingTerminatorLookup = array(
3543 0 => "\x00", // $00 ISO-8859-1. Terminated with $00.
3544 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.
3545 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3546 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
3549 return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
3552 public static function TextEncodingNameLookup($encoding) {
3553 // http://www.id3.org/id3v2.4.0-structure.txt
3554 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3555 static $TextEncodingNameLookup = array(
3556 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00.
3557 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.
3558 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3559 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00.
3562 return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3565 public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3566 switch ($id3v2majorversion) {
3568 return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3573 return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3579 public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3580 for ($i = 0; $i < strlen($numberstring); $i++) {
3581 if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) {
3582 if (($numberstring{$i} == '.') && $allowdecimal) {
3584 } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) {
3594 public static function IsValidDateStampString($datestamp) {
3595 if (strlen($datestamp) != 8) {
3598 if (!self::IsANumber($datestamp, false)) {
3601 $year = substr($datestamp, 0, 4);
3602 $month = substr($datestamp, 4, 2);
3603 $day = substr($datestamp, 6, 2);
3604 if (($year == 0) || ($month == 0) || ($day == 0)) {
3613 if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3616 if (($day > 29) && ($month == 2)) {
3622 public static function ID3v2HeaderLength($majorversion) {
3623 return (($majorversion == 2) ? 6 : 10);