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-)