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));
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';
632 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
633 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
634 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
636 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
637 if (ord($frame_description) === 0) {
638 $frame_description = '';
640 $parsedFrame['encodingid'] = $frame_textencoding;
641 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
643 $parsedFrame['description'] = $frame_description;
644 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
645 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
646 $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
647 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
648 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
650 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
653 //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
656 } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
657 // There may only be one text information frame of its kind in an tag.
658 // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
659 // excluding 'TXXX' described in 4.2.6.>
661 // Information <text string(s) according to encoding>
664 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
665 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
666 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
669 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
671 $parsedFrame['encodingid'] = $frame_textencoding;
672 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
674 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
675 // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
676 // This of course breaks when an artist name contains slash character, e.g. "AC/DC"
677 // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
678 // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
679 switch ($parsedFrame['encoding']) {
691 $Txxx_elements = array();
692 $Txxx_elements_start_offset = 0;
693 for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
694 if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
695 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
696 $Txxx_elements_start_offset = $i + $wordsize;
699 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
700 foreach ($Txxx_elements as $Txxx_element) {
701 $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
702 if (!empty($string)) {
703 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
706 unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
709 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
710 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame
711 // There may be more than one 'WXXX' frame in each tag,
712 // but only one with the same description
713 // <Header for 'User defined URL link frame', ID: 'WXXX'>
715 // Description <text string according to encoding> $00 (00)
719 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
720 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
721 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
723 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
724 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
725 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
727 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
729 if (ord($frame_description) === 0) {
730 $frame_description = '';
732 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
734 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
735 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
736 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
738 if ($frame_terminatorpos) {
739 // there are null bytes after the data - this is not according to spec
740 // only use data up to first null byte
741 $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
743 // no null bytes following data, just use all data
744 $frame_urldata = (string) $parsedFrame['data'];
747 $parsedFrame['encodingid'] = $frame_textencoding;
748 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
750 $parsedFrame['url'] = $frame_urldata;
751 $parsedFrame['description'] = $frame_description;
752 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
753 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
755 unset($parsedFrame['data']);
758 } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
759 // There may only be one URL link frame of its kind in a tag,
760 // except when stated otherwise in the frame description
761 // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
762 // described in 4.3.2.>
765 $parsedFrame['url'] = trim($parsedFrame['data']);
766 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
767 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
769 unset($parsedFrame['data']);
772 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
773 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
774 // http://id3.org/id3v2.3.0#sec4.4
775 // There may only be one 'IPL' frame in each tag
776 // <Header for 'User defined URL link frame', ID: 'IPL'>
778 // People list strings <textstrings>
781 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
782 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
783 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
785 $parsedFrame['encodingid'] = $frame_textencoding;
786 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
787 $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset);
789 // http://www.getid3.org/phpBB3/viewtopic.php?t=1369
790 // "this tag typically contains null terminated strings, which are associated in pairs"
791 // "there are users that use the tag incorrectly"
792 $IPLS_parts = array();
793 if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
794 $IPLS_parts_unsorted = array();
795 if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
796 // 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
798 for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
799 $twobytes = substr($parsedFrame['data_raw'], $i, 2);
800 if ($twobytes === "\x00\x00") {
801 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
804 $thisILPS .= $twobytes;
807 if (strlen($thisILPS) > 2) { // 2-byte BOM
808 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
811 // ISO-8859-1 or UTF-8 or other single-byte-null character set
812 $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
814 if (count($IPLS_parts_unsorted) == 1) {
815 // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
816 foreach ($IPLS_parts_unsorted as $key => $value) {
817 $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
819 foreach ($IPLS_parts_sorted as $person) {
820 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
823 } elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
826 foreach ($IPLS_parts_unsorted as $key => $value) {
827 if (($key % 2) == 0) {
831 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
837 foreach ($IPLS_parts_unsorted as $key => $value) {
838 $IPLS_parts[] = array($value);
843 $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
845 $parsedFrame['data'] = $IPLS_parts;
847 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
848 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
852 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier
853 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
854 // There may only be one 'MCDI' frame in each tag
855 // <Header for 'Music CD identifier', ID: 'MCDI'>
856 // CD TOC <binary data>
858 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
859 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
863 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes
864 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes
865 // There may only be one 'ETCO' frame in each tag
866 // <Header for 'Event timing codes', ID: 'ETCO'>
867 // Time stamp format $xx
868 // Where time stamp format is:
869 // $01 (32-bit value) MPEG frames from beginning of file
870 // $02 (32-bit value) milliseconds from beginning of file
871 // Followed by a list of key events in the following format:
873 // Time stamp $xx (xx ...)
874 // The 'Time stamp' is set to zero if directly at the beginning of the sound
875 // or after the previous event. All events MUST be sorted in chronological order.
878 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
880 while ($frame_offset < strlen($parsedFrame['data'])) {
881 $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1);
882 $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']);
883 $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
886 unset($parsedFrame['data']);
889 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table
890 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table
891 // There may only be one 'MLLT' frame in each tag
892 // <Header for 'Location lookup table', ID: 'MLLT'>
893 // MPEG frames between reference $xx xx
894 // Bytes between reference $xx xx xx
895 // Milliseconds between reference $xx xx xx
896 // Bits for bytes deviation $xx
897 // Bits for milliseconds dev. $xx
898 // Then for every reference the following data is included;
899 // Deviation in bytes %xxx....
900 // Deviation in milliseconds %xxx....
903 $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
904 $parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
905 $parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
906 $parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
907 $parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
908 $parsedFrame['data'] = substr($parsedFrame['data'], 10);
909 while ($frame_offset < strlen($parsedFrame['data'])) {
910 $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
912 $reference_counter = 0;
913 while (strlen($deviationbitstream) > 0) {
914 $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
915 $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
916 $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
917 $reference_counter++;
919 unset($parsedFrame['data']);
922 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes
923 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes
924 // There may only be one 'SYTC' frame in each tag
925 // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
926 // Time stamp format $xx
927 // Tempo data <binary data>
928 // Where time stamp format is:
929 // $01 (32-bit value) MPEG frames from beginning of file
930 // $02 (32-bit value) milliseconds from beginning of file
933 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
934 $timestamp_counter = 0;
935 while ($frame_offset < strlen($parsedFrame['data'])) {
936 $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
937 if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
938 $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
940 $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
942 $timestamp_counter++;
944 unset($parsedFrame['data']);
947 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription
948 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription
949 // There may be more than one 'Unsynchronised lyrics/text transcription' frame
950 // in each tag, but only one with the same language and content descriptor.
951 // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
953 // Language $xx xx xx
954 // Content descriptor <text string according to encoding> $00 (00)
955 // Lyrics/text <full text string according to encoding>
958 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
959 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
960 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
962 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
964 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
965 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
966 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
968 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
969 if (ord($frame_description) === 0) {
970 $frame_description = '';
972 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
974 $parsedFrame['encodingid'] = $frame_textencoding;
975 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
977 $parsedFrame['data'] = $parsedFrame['data'];
978 $parsedFrame['language'] = $frame_language;
979 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
980 $parsedFrame['description'] = $frame_description;
981 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
982 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
984 unset($parsedFrame['data']);
987 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text
988 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text
989 // There may be more than one 'SYLT' frame in each tag,
990 // but only one with the same language and content descriptor.
991 // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
993 // Language $xx xx xx
994 // Time stamp format $xx
995 // $01 (32-bit value) MPEG frames from beginning of file
996 // $02 (32-bit value) milliseconds from beginning of file
998 // Content descriptor <text string according to encoding> $00 (00)
999 // Terminated text to be synced (typically a syllable)
1000 // Sync identifier (terminator to above string) $00 (00)
1001 // Time stamp $xx (xx ...)
1004 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1005 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1006 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1008 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1010 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1011 $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1012 $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1013 $parsedFrame['encodingid'] = $frame_textencoding;
1014 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1016 $parsedFrame['language'] = $frame_language;
1017 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1019 $timestampindex = 0;
1020 $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1021 while (strlen($frame_remainingdata)) {
1023 $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding));
1024 if ($frame_terminatorpos === false) {
1025 $frame_remainingdata = '';
1027 if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1028 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1030 $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1032 $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1033 if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
1034 // timestamp probably omitted for first data item
1036 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1037 $frame_remainingdata = substr($frame_remainingdata, 4);
1042 unset($parsedFrame['data']);
1045 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments
1046 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments
1047 // There may be more than one comment frame in each tag,
1048 // but only one with the same language and content descriptor.
1049 // <Header for 'Comment', ID: 'COMM'>
1050 // Text encoding $xx
1051 // Language $xx xx xx
1052 // Short content descrip. <text string according to encoding> $00 (00)
1053 // The actual text <full text string according to encoding>
1055 if (strlen($parsedFrame['data']) < 5) {
1057 $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
1062 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1063 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1064 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1066 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1068 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1069 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1070 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1072 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1073 if (ord($frame_description) === 0) {
1074 $frame_description = '';
1076 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1078 $parsedFrame['encodingid'] = $frame_textencoding;
1079 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1081 $parsedFrame['language'] = $frame_language;
1082 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1083 $parsedFrame['description'] = $frame_description;
1084 $parsedFrame['data'] = $frame_text;
1085 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1086 $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
1087 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
1088 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1090 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1096 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1097 // There may be more than one 'RVA2' frame in each tag,
1098 // but only one with the same identification string
1099 // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1100 // Identification <text string> $00
1101 // The 'identification' string is used to identify the situation and/or
1102 // device where this adjustment should apply. The following is then
1103 // repeated for every channel:
1104 // Type of channel $xx
1105 // Volume adjustment $xx xx
1106 // Bits representing peak $xx
1107 // Peak volume $xx (xx ...)
1109 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1110 $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1111 if (ord($frame_idstring) === 0) {
1112 $frame_idstring = '';
1114 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1115 $parsedFrame['description'] = $frame_idstring;
1116 $RVA2channelcounter = 0;
1117 while (strlen($frame_remainingdata) >= 5) {
1119 $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1120 $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid;
1121 $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1122 $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1124 $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1125 if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1126 $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
1129 $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1130 $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1131 $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1132 $RVA2channelcounter++;
1134 unset($parsedFrame['data']);
1137 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
1138 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only)
1139 // There may only be one 'RVA' frame in each tag
1140 // <Header for 'Relative volume adjustment', ID: 'RVA'>
1141 // ID3v2.2 => Increment/decrement %000000ba
1142 // ID3v2.3 => Increment/decrement %00fedcba
1143 // Bits used for volume descr. $xx
1144 // Relative volume change, right $xx xx (xx ...) // a
1145 // Relative volume change, left $xx xx (xx ...) // b
1146 // Peak volume right $xx xx (xx ...)
1147 // Peak volume left $xx xx (xx ...)
1148 // ID3v2.3 only, optional (not present in ID3v2.2):
1149 // Relative volume change, right back $xx xx (xx ...) // c
1150 // Relative volume change, left back $xx xx (xx ...) // d
1151 // Peak volume right back $xx xx (xx ...)
1152 // Peak volume left back $xx xx (xx ...)
1153 // ID3v2.3 only, optional (not present in ID3v2.2):
1154 // Relative volume change, center $xx xx (xx ...) // e
1155 // Peak volume center $xx xx (xx ...)
1156 // ID3v2.3 only, optional (not present in ID3v2.2):
1157 // Relative volume change, bass $xx xx (xx ...) // f
1158 // Peak volume bass $xx xx (xx ...)
1161 $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1162 $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1163 $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1);
1164 $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1165 $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1166 $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1167 if ($parsedFrame['incdec']['right'] === false) {
1168 $parsedFrame['volumechange']['right'] *= -1;
1170 $frame_offset += $frame_bytesvolume;
1171 $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1172 if ($parsedFrame['incdec']['left'] === false) {
1173 $parsedFrame['volumechange']['left'] *= -1;
1175 $frame_offset += $frame_bytesvolume;
1176 $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1177 $frame_offset += $frame_bytesvolume;
1178 $parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1179 $frame_offset += $frame_bytesvolume;
1180 if ($id3v2_majorversion == 3) {
1181 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1182 if (strlen($parsedFrame['data']) > 0) {
1183 $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1184 $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1);
1185 $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1186 if ($parsedFrame['incdec']['rightrear'] === false) {
1187 $parsedFrame['volumechange']['rightrear'] *= -1;
1189 $frame_offset += $frame_bytesvolume;
1190 $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1191 if ($parsedFrame['incdec']['leftrear'] === false) {
1192 $parsedFrame['volumechange']['leftrear'] *= -1;
1194 $frame_offset += $frame_bytesvolume;
1195 $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1196 $frame_offset += $frame_bytesvolume;
1197 $parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1198 $frame_offset += $frame_bytesvolume;
1200 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1201 if (strlen($parsedFrame['data']) > 0) {
1202 $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1203 $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1204 if ($parsedFrame['incdec']['center'] === false) {
1205 $parsedFrame['volumechange']['center'] *= -1;
1207 $frame_offset += $frame_bytesvolume;
1208 $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1209 $frame_offset += $frame_bytesvolume;
1211 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1212 if (strlen($parsedFrame['data']) > 0) {
1213 $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1214 $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1215 if ($parsedFrame['incdec']['bass'] === false) {
1216 $parsedFrame['volumechange']['bass'] *= -1;
1218 $frame_offset += $frame_bytesvolume;
1219 $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1220 $frame_offset += $frame_bytesvolume;
1223 unset($parsedFrame['data']);
1226 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
1227 // There may be more than one 'EQU2' frame in each tag,
1228 // but only one with the same identification string
1229 // <Header of 'Equalisation (2)', ID: 'EQU2'>
1230 // Interpolation method $xx
1233 // Identification <text string> $00
1234 // The following is then repeated for every adjustment point
1236 // Volume adjustment $xx xx
1239 $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1240 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1241 $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1242 if (ord($frame_idstring) === 0) {
1243 $frame_idstring = '';
1245 $parsedFrame['description'] = $frame_idstring;
1246 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1247 while (strlen($frame_remainingdata)) {
1248 $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1249 $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1250 $frame_remainingdata = substr($frame_remainingdata, 4);
1252 $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1253 unset($parsedFrame['data']);
1256 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only)
1257 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only)
1258 // There may only be one 'EQUA' frame in each tag
1259 // <Header for 'Relative volume adjustment', ID: 'EQU'>
1260 // Adjustment bits $xx
1261 // This is followed by 2 bytes + ('adjustment bits' rounded up to the
1262 // nearest byte) for every equalisation band in the following format,
1263 // giving a frequency range of 0 - 32767Hz:
1264 // Increment/decrement %x (MSB of the Frequency)
1265 // Frequency (lower 15 bits)
1266 // Adjustment $xx (xx ...)
1269 $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1270 $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1272 $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1273 while (strlen($frame_remainingdata) > 0) {
1274 $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1275 $frame_incdec = (bool) substr($frame_frequencystr, 0, 1);
1276 $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1277 $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1278 $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1279 if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1280 $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1282 $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1284 unset($parsedFrame['data']);
1287 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb
1288 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb
1289 // There may only be one 'RVRB' frame in each tag.
1290 // <Header for 'Reverb', ID: 'RVRB'>
1291 // Reverb left (ms) $xx xx
1292 // Reverb right (ms) $xx xx
1293 // Reverb bounces, left $xx
1294 // Reverb bounces, right $xx
1295 // Reverb feedback, left to left $xx
1296 // Reverb feedback, left to right $xx
1297 // Reverb feedback, right to right $xx
1298 // Reverb feedback, right to left $xx
1299 // Premix left to right $xx
1300 // Premix right to left $xx
1303 $parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1305 $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1307 $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1308 $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1309 $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1310 $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1311 $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1312 $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1313 $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1314 $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1315 unset($parsedFrame['data']);
1318 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture
1319 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture
1320 // There may be several pictures attached to one file,
1321 // each in their individual 'APIC' frame, but only one
1322 // with the same content descriptor
1323 // <Header for 'Attached picture', ID: 'APIC'>
1324 // Text encoding $xx
1325 // ID3v2.3+ => MIME type <text string> $00
1326 // ID3v2.2 => Image format $xx xx xx
1328 // Description <text string according to encoding> $00 (00)
1329 // Picture data <binary data>
1332 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1333 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1334 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1337 if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1338 $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1339 if (strtolower($frame_imagetype) == 'ima') {
1340 // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1341 // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net)
1342 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1343 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1344 if (ord($frame_mimetype) === 0) {
1345 $frame_mimetype = '';
1347 $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1348 if ($frame_imagetype == 'JPEG') {
1349 $frame_imagetype = 'JPG';
1351 $frame_offset = $frame_terminatorpos + strlen("\x00");
1356 if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1357 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1358 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1359 if (ord($frame_mimetype) === 0) {
1360 $frame_mimetype = '';
1362 $frame_offset = $frame_terminatorpos + strlen("\x00");
1365 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1367 if ($frame_offset >= $parsedFrame['datalength']) {
1368 $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
1370 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1371 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1372 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1374 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1375 if (ord($frame_description) === 0) {
1376 $frame_description = '';
1378 $parsedFrame['encodingid'] = $frame_textencoding;
1379 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1381 if ($id3v2_majorversion == 2) {
1382 $parsedFrame['imagetype'] = $frame_imagetype;
1384 $parsedFrame['mime'] = $frame_mimetype;
1386 $parsedFrame['picturetypeid'] = $frame_picturetype;
1387 $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype);
1388 $parsedFrame['description'] = $frame_description;
1389 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1390 $parsedFrame['datalength'] = strlen($parsedFrame['data']);
1392 $parsedFrame['image_mime'] = '';
1393 $imageinfo = array();
1394 $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
1395 if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1396 $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
1397 if ($imagechunkcheck[0]) {
1398 $parsedFrame['image_width'] = $imagechunkcheck[0];
1400 if ($imagechunkcheck[1]) {
1401 $parsedFrame['image_height'] = $imagechunkcheck[1];
1406 if ($this->getid3->option_save_attachments === false) {
1408 unset($parsedFrame['data']);
1411 if ($this->getid3->option_save_attachments === true) {
1414 } elseif (is_int($this->getid3->option_save_attachments)) {
1415 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1417 $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
1418 unset($parsedFrame['data']);
1422 } elseif (is_string($this->getid3->option_save_attachments)) {
1423 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1424 if (!is_dir($dir) || !is_writable($dir)) {
1425 // cannot write, skip
1426 $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)';
1427 unset($parsedFrame['data']);
1431 // if we get this far, must be OK
1432 if (is_string($this->getid3->option_save_attachments)) {
1433 $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1434 if (!file_exists($destination_filename) || is_writable($destination_filename)) {
1435 file_put_contents($destination_filename, $parsedFrame['data']);
1437 $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
1439 $parsedFrame['data_filename'] = $destination_filename;
1440 unset($parsedFrame['data']);
1442 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1443 if (!isset($info['id3v2']['comments']['picture'])) {
1444 $info['id3v2']['comments']['picture'] = array();
1446 $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']);
1452 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object
1453 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object
1454 // There may be more than one 'GEOB' frame in each tag,
1455 // but only one with the same content descriptor
1456 // <Header for 'General encapsulated object', ID: 'GEOB'>
1457 // Text encoding $xx
1458 // MIME type <text string> $00
1459 // Filename <text string according to encoding> $00 (00)
1460 // Content description <text string according to encoding> $00 (00)
1461 // Encapsulated object <binary data>
1464 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1465 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1466 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1468 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1469 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1470 if (ord($frame_mimetype) === 0) {
1471 $frame_mimetype = '';
1473 $frame_offset = $frame_terminatorpos + strlen("\x00");
1475 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1476 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1477 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1479 $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1480 if (ord($frame_filename) === 0) {
1481 $frame_filename = '';
1483 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1485 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1486 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1487 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1489 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1490 if (ord($frame_description) === 0) {
1491 $frame_description = '';
1493 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1495 $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset);
1496 $parsedFrame['encodingid'] = $frame_textencoding;
1497 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1499 $parsedFrame['mime'] = $frame_mimetype;
1500 $parsedFrame['filename'] = $frame_filename;
1501 $parsedFrame['description'] = $frame_description;
1502 unset($parsedFrame['data']);
1505 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter
1506 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter
1507 // There may only be one 'PCNT' frame in each tag.
1508 // When the counter reaches all one's, one byte is inserted in
1509 // front of the counter thus making the counter eight bits bigger
1510 // <Header for 'Play counter', ID: 'PCNT'>
1511 // Counter $xx xx xx xx (xx ...)
1513 $parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']);
1516 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter
1517 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter
1518 // There may be more than one 'POPM' frame in each tag,
1519 // but only one with the same email address
1520 // <Header for 'Popularimeter', ID: 'POPM'>
1521 // Email to user <text string> $00
1523 // Counter $xx xx xx xx (xx ...)
1526 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1527 $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1528 if (ord($frame_emailaddress) === 0) {
1529 $frame_emailaddress = '';
1531 $frame_offset = $frame_terminatorpos + strlen("\x00");
1532 $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1533 $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1534 $parsedFrame['email'] = $frame_emailaddress;
1535 $parsedFrame['rating'] = $frame_rating;
1536 unset($parsedFrame['data']);
1539 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size
1540 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size
1541 // There may only be one 'RBUF' frame in each tag
1542 // <Header for 'Recommended buffer size', ID: 'RBUF'>
1543 // Buffer size $xx xx xx
1544 // Embedded info flag %0000000x
1545 // Offset to next tag $xx xx xx xx
1548 $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1551 $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1552 $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1553 $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1554 unset($parsedFrame['data']);
1557 } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only)
1558 // There may be more than one 'CRM' frame in a tag,
1559 // but only one with the same 'owner identifier'
1560 // <Header for 'Encrypted meta frame', ID: 'CRM'>
1561 // Owner identifier <textstring> $00 (00)
1562 // Content/explanation <textstring> $00 (00)
1563 // Encrypted datablock <binary data>
1566 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1567 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1568 $frame_offset = $frame_terminatorpos + strlen("\x00");
1570 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1571 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1572 if (ord($frame_description) === 0) {
1573 $frame_description = '';
1575 $frame_offset = $frame_terminatorpos + strlen("\x00");
1577 $parsedFrame['ownerid'] = $frame_ownerid;
1578 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1579 $parsedFrame['description'] = $frame_description;
1580 unset($parsedFrame['data']);
1583 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption
1584 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption
1585 // There may be more than one 'AENC' frames in a tag,
1586 // but only one with the same 'Owner identifier'
1587 // <Header for 'Audio encryption', ID: 'AENC'>
1588 // Owner identifier <text string> $00
1589 // Preview start $xx xx
1590 // Preview length $xx xx
1591 // Encryption info <binary data>
1594 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1595 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1596 if (ord($frame_ownerid) === 0) {
1597 $frame_ownerid == '';
1599 $frame_offset = $frame_terminatorpos + strlen("\x00");
1600 $parsedFrame['ownerid'] = $frame_ownerid;
1601 $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1603 $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1605 $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1606 unset($parsedFrame['data']);
1609 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information
1610 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information
1611 // There may be more than one 'LINK' frame in a tag,
1612 // but only one with the same contents
1613 // <Header for 'Linked information', ID: 'LINK'>
1614 // ID3v2.3+ => Frame identifier $xx xx xx xx
1615 // ID3v2.2 => Frame identifier $xx xx xx
1616 // URL <text string> $00
1617 // ID and additional data <text string(s)>
1620 if ($id3v2_majorversion == 2) {
1621 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1624 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1628 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1629 $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1630 if (ord($frame_url) === 0) {
1633 $frame_offset = $frame_terminatorpos + strlen("\x00");
1634 $parsedFrame['url'] = $frame_url;
1636 $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1637 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1638 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']);
1640 unset($parsedFrame['data']);
1643 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
1644 // There may only be one 'POSS' frame in each tag
1645 // <Head for 'Position synchronisation', ID: 'POSS'>
1646 // Time stamp format $xx
1647 // Position $xx (xx ...)
1650 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1651 $parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1652 unset($parsedFrame['data']);
1655 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only)
1656 // There may be more than one 'Terms of use' frame in a tag,
1657 // but only one with the same 'Language'
1658 // <Header for 'Terms of use frame', ID: 'USER'>
1659 // Text encoding $xx
1660 // Language $xx xx xx
1661 // The actual text <text string according to encoding>
1664 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1665 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1666 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1668 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1670 $parsedFrame['language'] = $frame_language;
1671 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1672 $parsedFrame['encodingid'] = $frame_textencoding;
1673 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1675 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1676 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1677 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1679 unset($parsedFrame['data']);
1682 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only)
1683 // There may only be one 'OWNE' frame in a tag
1684 // <Header for 'Ownership frame', ID: 'OWNE'>
1685 // Text encoding $xx
1686 // Price paid <text string> $00
1687 // Date of purch. <text string>
1688 // Seller <text string according to encoding>
1691 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1692 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1693 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1695 $parsedFrame['encodingid'] = $frame_textencoding;
1696 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1698 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1699 $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1700 $frame_offset = $frame_terminatorpos + strlen("\x00");
1702 $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1703 $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1704 $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3);
1706 $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1707 if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1708 $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1712 $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1713 unset($parsedFrame['data']);
1716 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only)
1717 // There may be more than one 'commercial frame' in a tag,
1718 // but no two may be identical
1719 // <Header for 'Commercial frame', ID: 'COMR'>
1720 // Text encoding $xx
1721 // Price string <text string> $00
1722 // Valid until <text string>
1723 // Contact URL <text string> $00
1725 // Name of seller <text string according to encoding> $00 (00)
1726 // Description <text string according to encoding> $00 (00)
1727 // Picture MIME type <string> $00
1728 // Seller logo <binary data>
1731 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1732 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1733 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1736 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1737 $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1738 $frame_offset = $frame_terminatorpos + strlen("\x00");
1739 $frame_rawpricearray = explode('/', $frame_pricestring);
1740 foreach ($frame_rawpricearray as $key => $val) {
1741 $frame_currencyid = substr($val, 0, 3);
1742 $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1743 $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3);
1746 $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1749 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1750 $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1751 $frame_offset = $frame_terminatorpos + strlen("\x00");
1753 $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1755 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1756 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1757 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1759 $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1760 if (ord($frame_sellername) === 0) {
1761 $frame_sellername = '';
1763 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1765 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1766 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1767 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1769 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1770 if (ord($frame_description) === 0) {
1771 $frame_description = '';
1773 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1775 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1776 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1777 $frame_offset = $frame_terminatorpos + strlen("\x00");
1779 $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1781 $parsedFrame['encodingid'] = $frame_textencoding;
1782 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1784 $parsedFrame['pricevaliduntil'] = $frame_datestring;
1785 $parsedFrame['contacturl'] = $frame_contacturl;
1786 $parsedFrame['receivedasid'] = $frame_receivedasid;
1787 $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid);
1788 $parsedFrame['sellername'] = $frame_sellername;
1789 $parsedFrame['description'] = $frame_description;
1790 $parsedFrame['mime'] = $frame_mimetype;
1791 $parsedFrame['logo'] = $frame_sellerlogo;
1792 unset($parsedFrame['data']);
1795 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
1796 // There may be several 'ENCR' frames in a tag,
1797 // but only one containing the same symbol
1798 // and only one containing the same owner identifier
1799 // <Header for 'Encryption method registration', ID: 'ENCR'>
1800 // Owner identifier <text string> $00
1801 // Method symbol $xx
1802 // Encryption data <binary data>
1805 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1806 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1807 if (ord($frame_ownerid) === 0) {
1808 $frame_ownerid = '';
1810 $frame_offset = $frame_terminatorpos + strlen("\x00");
1812 $parsedFrame['ownerid'] = $frame_ownerid;
1813 $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1814 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1817 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only)
1819 // There may be several 'GRID' frames in a tag,
1820 // but only one containing the same symbol
1821 // and only one containing the same owner identifier
1822 // <Header for 'Group ID registration', ID: 'GRID'>
1823 // Owner identifier <text string> $00
1825 // Group dependent data <binary data>
1828 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1829 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1830 if (ord($frame_ownerid) === 0) {
1831 $frame_ownerid = '';
1833 $frame_offset = $frame_terminatorpos + strlen("\x00");
1835 $parsedFrame['ownerid'] = $frame_ownerid;
1836 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1837 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1840 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only)
1841 // The tag may contain more than one 'PRIV' frame
1842 // but only with different contents
1843 // <Header for 'Private frame', ID: 'PRIV'>
1844 // Owner identifier <text string> $00
1845 // The private data <binary data>
1848 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1849 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1850 if (ord($frame_ownerid) === 0) {
1851 $frame_ownerid = '';
1853 $frame_offset = $frame_terminatorpos + strlen("\x00");
1855 $parsedFrame['ownerid'] = $frame_ownerid;
1856 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1859 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only)
1860 // There may be more than one 'signature frame' in a tag,
1861 // but no two may be identical
1862 // <Header for 'Signature frame', ID: 'SIGN'>
1864 // Signature <binary data>
1867 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1868 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1871 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only)
1872 // There may only be one 'seek frame' in a tag
1873 // <Header for 'Seek frame', ID: 'SEEK'>
1874 // Minimum offset to next tag $xx xx xx xx
1877 $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1880 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
1881 // There may only be one 'audio seek point index' frame in a tag
1882 // <Header for 'Seek Point Index', ID: 'ASPI'>
1883 // Indexed data start (S) $xx xx xx xx
1884 // Indexed data length (L) $xx xx xx xx
1885 // Number of index points (N) $xx xx
1886 // Bits per index point (b) $xx
1887 // Then for every index point the following data is included:
1888 // Fraction at index (Fi) $xx (xx)
1891 $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1893 $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1895 $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1897 $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1898 $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1899 for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
1900 $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1901 $frame_offset += $frame_bytesperpoint;
1903 unset($parsedFrame['data']);
1905 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1906 // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1907 // There may only be one 'RGAD' frame in a tag
1908 // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1909 // Peak Amplitude $xx $xx $xx $xx
1910 // Radio Replay Gain Adjustment %aaabbbcd %dddddddd
1911 // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
1913 // b - originator code
1915 // d - replay gain adjustment
1918 $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1920 $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1922 $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1924 $parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1925 $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1926 $parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1927 $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1928 $parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1929 $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1930 $parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
1931 $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
1932 $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1933 $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1934 $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1935 $parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1936 $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1937 $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1939 $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude'];
1940 $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1941 $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1942 $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1943 $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1945 unset($parsedFrame['data']);
1953 public function DeUnsynchronise($data) {
1954 return str_replace("\xFF\x00", "\xFF", $data);
1957 public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
1958 static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
1959 0x00 => 'No more than 128 frames and 1 MB total tag size',
1960 0x01 => 'No more than 64 frames and 128 KB total tag size',
1961 0x02 => 'No more than 32 frames and 40 KB total tag size',
1962 0x03 => 'No more than 32 frames and 4 KB total tag size',
1964 return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
1967 public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
1968 static $LookupExtendedHeaderRestrictionsTextEncodings = array(
1969 0x00 => 'No restrictions',
1970 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
1972 return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
1975 public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
1976 static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
1977 0x00 => 'No restrictions',
1978 0x01 => 'No string is longer than 1024 characters',
1979 0x02 => 'No string is longer than 128 characters',
1980 0x03 => 'No string is longer than 30 characters',
1982 return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
1985 public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
1986 static $LookupExtendedHeaderRestrictionsImageEncoding = array(
1987 0x00 => 'No restrictions',
1988 0x01 => 'Images are encoded only with PNG or JPEG',
1990 return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
1993 public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
1994 static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
1995 0x00 => 'No restrictions',
1996 0x01 => 'All images are 256x256 pixels or smaller',
1997 0x02 => 'All images are 64x64 pixels or smaller',
1998 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
2000 return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
2003 public function LookupCurrencyUnits($currencyid) {
2007 /** This is not a comment!
2021 BAM Convertible Marka
2038 CDF Congolese Francs
2184 XDR Special Drawing Rights
2192 ZWD Zimbabwe Dollars
2196 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2200 public function LookupCurrencyCountry($currencyid) {
2204 /** This is not a comment!
2206 AED United Arab Emirates
2210 ANG Netherlands Antilles
2217 BAM Bosnia and Herzegovina
2225 BND Brunei Darussalam
2247 DOP Dominican Republic
2254 EUR Euro Member Countries
2257 FKP Falkland Islands (Malvinas)
2314 MVR Maldives (Maldive Islands)
2322 NLG Netherlands (Holland)
2329 PGK Papua New Guinea
2352 STD São Tome and Principe
2362 TTD Trinidad and Tobago
2368 USD United States of America
2376 XAF Communauté Financière Africaine
2380 XDR International Monetary Fund
2382 XPF Comptoirs Français du Pacifique
2392 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2397 public static function LanguageLookup($languagecode, $casesensitive=false) {
2399 if (!$casesensitive) {
2400 $languagecode = strtolower($languagecode);
2403 // http://www.id3.org/id3v2.4.0-structure.txt
2404 // [4. ID3v2 frame overview]
2405 // The three byte language field, present in several frames, is used to
2406 // describe the language of the frame's content, according to ISO-639-2
2407 // [ISO-639-2]. The language should be represented in lower case. If the
2408 // language is not known the string "XXX" should be used.
2411 // ISO 639-2 - http://www.id3.org/iso639-2.html
2415 /** This is not a comment!
2424 afa Afro-Asiatic (Other)
2431 alg Algonquian Languages
2433 ang English, Old (ca. 450-1100)
2434 apa Apache Languages
2440 art Artificial (Other)
2443 ath Athapascan Languages
2450 bai Bamileke Languages
2478 cai Central American Indian (Other)
2481 cau Caucasian (Other)
2500 cpe Creoles and Pidgins, English-based (Other)
2501 cpf Creoles and Pidgins, French-based (Other)
2502 cpp Creoles and Pidgins, Portuguese-based (Other)
2504 crp Creoles and Pidgins (Other)
2505 cus Cushitic (Other)
2515 dra Dravidian (Other)
2517 dum Dutch, Middle (ca. 1050-1350)
2522 egy Egyptian (Ancient)
2524 ell Greek, Modern (1453-)
2527 enm English, Middle (ca. 1100-1500)
2541 fiu Finno-Ugrian (Other)
2545 frm French, Middle (ca. 1400-1600)
2546 fro French, Old (842- ca. 1400)
2554 gem Germanic (Other)
2560 gmh German, Middle High (ca. 1050-1500)
2561 goh German, Old High (ca. 750-1050)
2565 grc Greek, Ancient (to 1453)
2566 gre Greek, Modern (1453-)
2587 ina Interlingua (International Auxiliary language Association)
2590 ine Indo-European (Other)
2650 luo Luo (Kenya and Tanzania)
2661 map Austronesian (Other)
2667 mga Irish, Middle (900 - 1200)
2670 mis Miscellaneous (Other)
2671 mkh Mon-Kmer (Other)
2675 mno Manobo Languages
2682 mul Multiple Languages
2689 nai North American Indian (Other)
2697 nic Niger-Kordofanian (Other)
2700 nno Norwegian (Nynorsk)
2704 nub Nubian Languages
2710 oci Langue d'Oc (post 1500)
2716 ota Turkish, Ottoman (1500 - 1928)
2717 oto Otomian Languages
2718 paa Papuan-Australian (Other)
2725 peo Persian, Old (ca 600 - 400 B.C.)
2733 pro Provencal, Old (to 1500)
2748 sai South American Indian (Other)
2749 sal Salishan Languages
2750 sam Samaritan Aramaic
2756 sga Irish, Old (to 900)
2760 sio Siouan Languages
2761 sit Sino-Tibetan (Other)
2778 ssa Nilo-Saharan (Other)
2805 ton Tonga (Tonga Islands)
2828 wak Wakashan Languages
2833 wen Sorbian Languages
2849 return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
2853 public static function ETCOEventLookup($index) {
2854 if (($index >= 0x17) && ($index <= 0xDF)) {
2855 return 'reserved for future use';
2857 if (($index >= 0xE0) && ($index <= 0xEF)) {
2858 return 'not predefined synch 0-F';
2860 if (($index >= 0xF0) && ($index <= 0xFC)) {
2861 return 'reserved for future use';
2864 static $EventLookup = array(
2865 0x00 => 'padding (has no meaning)',
2866 0x01 => 'end of initial silence',
2867 0x02 => 'intro start',
2868 0x03 => 'main part start',
2869 0x04 => 'outro start',
2870 0x05 => 'outro end',
2871 0x06 => 'verse start',
2872 0x07 => 'refrain start',
2873 0x08 => 'interlude start',
2874 0x09 => 'theme start',
2875 0x0A => 'variation start',
2876 0x0B => 'key change',
2877 0x0C => 'time change',
2878 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
2879 0x0E => 'sustained noise',
2880 0x0F => 'sustained noise end',
2881 0x10 => 'intro end',
2882 0x11 => 'main part end',
2883 0x12 => 'verse end',
2884 0x13 => 'refrain end',
2885 0x14 => 'theme end',
2886 0x15 => 'profanity',
2887 0x16 => 'profanity end',
2888 0xFD => 'audio end (start of silence)',
2889 0xFE => 'audio file ends',
2890 0xFF => 'one more byte of events follows'
2893 return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
2896 public static function SYTLContentTypeLookup($index) {
2897 static $SYTLContentTypeLookup = array(
2900 0x02 => 'text transcription',
2901 0x03 => 'movement/part name', // (e.g. 'Adagio')
2902 0x04 => 'events', // (e.g. 'Don Quijote enters the stage')
2903 0x05 => 'chord', // (e.g. 'Bb F Fsus')
2904 0x06 => 'trivia/\'pop up\' information',
2905 0x07 => 'URLs to webpages',
2906 0x08 => 'URLs to images'
2909 return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
2912 public static function APICPictureTypeLookup($index, $returnarray=false) {
2913 static $APICPictureTypeLookup = array(
2915 0x01 => '32x32 pixels \'file icon\' (PNG only)',
2916 0x02 => 'Other file icon',
2917 0x03 => 'Cover (front)',
2918 0x04 => 'Cover (back)',
2919 0x05 => 'Leaflet page',
2920 0x06 => 'Media (e.g. label side of CD)',
2921 0x07 => 'Lead artist/lead performer/soloist',
2922 0x08 => 'Artist/performer',
2923 0x09 => 'Conductor',
2924 0x0A => 'Band/Orchestra',
2926 0x0C => 'Lyricist/text writer',
2927 0x0D => 'Recording Location',
2928 0x0E => 'During recording',
2929 0x0F => 'During performance',
2930 0x10 => 'Movie/video screen capture',
2931 0x11 => 'A bright coloured fish',
2932 0x12 => 'Illustration',
2933 0x13 => 'Band/artist logotype',
2934 0x14 => 'Publisher/Studio logotype'
2937 return $APICPictureTypeLookup;
2939 return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
2942 public static function COMRReceivedAsLookup($index) {
2943 static $COMRReceivedAsLookup = array(
2945 0x01 => 'Standard CD album with other songs',
2946 0x02 => 'Compressed audio on CD',
2947 0x03 => 'File over the Internet',
2948 0x04 => 'Stream over the Internet',
2949 0x05 => 'As note sheets',
2950 0x06 => 'As note sheets in a book with other sheets',
2951 0x07 => 'Music on other media',
2952 0x08 => 'Non-musical merchandise'
2955 return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
2958 public static function RVA2ChannelTypeLookup($index) {
2959 static $RVA2ChannelTypeLookup = array(
2961 0x01 => 'Master volume',
2962 0x02 => 'Front right',
2963 0x03 => 'Front left',
2964 0x04 => 'Back right',
2965 0x05 => 'Back left',
2966 0x06 => 'Front centre',
2967 0x07 => 'Back centre',
2971 return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
2974 public static function FrameNameLongLookup($framename) {
2978 /** This is not a comment!
2980 AENC Audio encryption
2981 APIC Attached picture
2982 ASPI Audio seek point index
2983 BUF Recommended buffer size
2987 COMR Commercial frame
2988 CRA Audio encryption
2989 CRM Encrypted meta frame
2990 ENCR Encryption method registration
2992 EQU2 Equalisation (2)
2994 ETC Event timing codes
2995 ETCO Event timing codes
2996 GEO General encapsulated object
2997 GEOB General encapsulated object
2998 GRID Group identification registration
2999 IPL Involved people list
3000 IPLS Involved people list
3001 LINK Linked information
3002 LNK Linked information
3003 MCDI Music CD identifier
3004 MCI Music CD Identifier
3005 MLL MPEG location lookup table
3006 MLLT MPEG location lookup table
3007 OWNE Ownership frame
3009 PIC Attached picture
3012 POSS Position synchronisation frame
3014 RBUF Recommended buffer size
3016 RVA Relative volume adjustment
3017 RVA2 Relative volume adjustment (2)
3018 RVAD Relative volume adjustment
3021 SIGN Signature frame
3022 SLT Synchronised lyric/text
3023 STC Synced tempo codes
3024 SYLT Synchronised lyric/text
3025 SYTC Synchronised tempo codes
3026 TAL Album/Movie/Show title
3027 TALB Album/Movie/Show title
3028 TBP BPM (Beats Per Minute)
3029 TBPM BPM (beats per minute)
3031 TCMP Part of a compilation
3035 TCOP Copyright message
3036 TCP Part of a compilation
3037 TCR Copyright message
3042 TDOR Original release time
3049 TEXT Lyricist/Text writer
3054 TIPL Involved people list
3055 TIT1 Content group description
3056 TIT2 Title/songname/content description
3057 TIT3 Subtitle/Description refinement
3064 TMCL Musician credits list
3068 TOA Original artist(s)/performer(s)
3069 TOAL Original album/movie/show title
3070 TOF Original filename
3071 TOFN Original filename
3072 TOL Original Lyricist(s)/text writer(s)
3073 TOLY Original lyricist(s)/text writer(s)
3074 TOPE Original artist(s)/performer(s)
3075 TOR Original release year
3076 TORY Original release year
3077 TOT Original album/Movie/Show title
3078 TOWN File owner/licensee
3079 TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3080 TP2 Band/Orchestra/Accompaniment
3081 TP3 Conductor/Performer refinement
3082 TP4 Interpreted, remixed, or otherwise modified by
3085 TPE1 Lead performer(s)/Soloist(s)
3086 TPE2 Band/orchestra/accompaniment
3087 TPE3 Conductor/performer refinement
3088 TPE4 Interpreted, remixed, or otherwise modified by
3090 TPRO Produced notice
3092 TRC ISRC (International Standard Recording Code)
3093 TRCK Track number/Position in set
3095 TRDA Recording dates
3096 TRK Track number/Position in set
3097 TRSN Internet radio station name
3098 TRSO Internet radio station owner
3099 TS2 Album-Artist sort order
3100 TSA Album sort order
3101 TSC Composer sort order
3104 TSO2 Album-Artist sort order
3105 TSOA Album sort order
3106 TSOC Composer sort order
3107 TSOP Performer sort order
3108 TSOT Title sort order
3109 TSP Performer sort order
3110 TSRC ISRC (international standard recording code)
3111 TSS Software/hardware and settings used for encoding
3112 TSSE Software/Hardware and settings used for encoding
3114 TST Title sort order
3115 TT1 Content group description
3116 TT2 Title/Songname/Content description
3117 TT3 Subtitle/Description refinement
3118 TXT Lyricist/text writer
3119 TXX User defined text information frame
3120 TXXX User defined text information frame
3123 UFI Unique file identifier
3124 UFID Unique file identifier
3125 ULT Unsychronised lyric/text transcription
3127 USLT Unsynchronised lyric/text transcription
3128 WAF Official audio file webpage
3129 WAR Official artist/performer webpage
3130 WAS Official audio source webpage
3131 WCM Commercial information
3132 WCOM Commercial information
3133 WCOP Copyright/Legal information
3134 WCP Copyright/Legal information
3135 WOAF Official audio file webpage
3136 WOAR Official artist/performer webpage
3137 WOAS Official audio source webpage
3138 WORS Official Internet radio station homepage
3140 WPB Publishers official webpage
3141 WPUB Publishers official webpage
3142 WXX User defined URL link frame
3143 WXXX User defined URL link frame
3144 TFEA Featured Artist
3145 TSTU Recording Studio
3146 rgad Replay Gain Adjustment
3150 return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
3153 // from Helium2 [www.helium2.com]
3154 // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3158 public static function FrameNameShortLookup($framename) {
3162 /** This is not a comment!
3164 AENC audio_encryption
3165 APIC attached_picture
3166 ASPI audio_seek_point_index
3167 BUF recommended_buffer_size
3171 COMR commercial_frame
3172 CRA audio_encryption
3173 CRM encrypted_meta_frame
3174 ENCR encryption_method_registration
3178 ETC event_timing_codes
3179 ETCO event_timing_codes
3180 GEO general_encapsulated_object
3181 GEOB general_encapsulated_object
3182 GRID group_identification_registration
3183 IPL involved_people_list
3184 IPLS involved_people_list
3185 LINK linked_information
3186 LNK linked_information
3187 MCDI music_cd_identifier
3188 MCI music_cd_identifier
3189 MLL mpeg_location_lookup_table
3190 MLLT mpeg_location_lookup_table
3191 OWNE ownership_frame
3193 PIC attached_picture
3196 POSS position_synchronisation_frame
3198 RBUF recommended_buffer_size
3200 RVA relative_volume_adjustment
3201 RVA2 relative_volume_adjustment
3202 RVAD relative_volume_adjustment
3205 SIGN signature_frame
3206 SLT synchronised_lyric
3207 STC synced_tempo_codes
3208 SYLT synchronised_lyric
3209 SYTC synchronised_tempo_codes
3215 TCMP part_of_a_compilation
3219 TCOP copyright_message
3220 TCP part_of_a_compilation
3221 TCR copyright_message
3226 TDOR original_release_time
3238 TIPL involved_people_list
3239 TIT1 content_group_description
3248 TMCL musician_credits_list
3254 TOF original_filename
3255 TOFN original_filename
3256 TOL original_lyricist
3257 TOLY original_lyricist
3258 TOPE original_artist
3274 TPRO produced_notice
3279 TRDA recording_dates
3281 TRSN internet_radio_station_name
3282 TRSO internet_radio_station_owner
3283 TS2 album_artist_sort_order
3284 TSA album_sort_order
3285 TSC composer_sort_order
3288 TSO2 album_artist_sort_order
3289 TSOA album_sort_order
3290 TSOC composer_sort_order
3291 TSOP performer_sort_order
3292 TSOT title_sort_order
3293 TSP performer_sort_order
3295 TSS encoder_settings
3296 TSSE encoder_settings
3298 TST title_sort_order
3299 TT1 content_group_description
3307 UFI unique_file_identifier
3308 UFID unique_file_identifier
3309 ULT unsychronised_lyric
3311 USLT unsynchronised_lyric
3315 WCM commercial_information
3316 WCOM commercial_information
3328 TFEA featured_artist
3329 TSTU recording_studio
3330 rgad replay_gain_adjustment
3334 return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
3337 public static function TextEncodingTerminatorLookup($encoding) {
3338 // http://www.id3.org/id3v2.4.0-structure.txt
3339 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3340 static $TextEncodingTerminatorLookup = array(
3341 0 => "\x00", // $00 ISO-8859-1. Terminated with $00.
3342 1 => "\x00\x00", // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3343 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3344 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
3347 return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : '');
3350 public static function TextEncodingNameLookup($encoding) {
3351 // http://www.id3.org/id3v2.4.0-structure.txt
3352 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3353 static $TextEncodingNameLookup = array(
3354 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00.
3355 1 => 'UTF-16', // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3356 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3357 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00.
3360 return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3363 public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3364 switch ($id3v2majorversion) {
3366 return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3371 return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3377 public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3378 for ($i = 0; $i < strlen($numberstring); $i++) {
3379 if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) {
3380 if (($numberstring{$i} == '.') && $allowdecimal) {
3382 } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) {
3392 public static function IsValidDateStampString($datestamp) {
3393 if (strlen($datestamp) != 8) {
3396 if (!self::IsANumber($datestamp, false)) {
3399 $year = substr($datestamp, 0, 4);
3400 $month = substr($datestamp, 4, 2);
3401 $day = substr($datestamp, 6, 2);
3402 if (($year == 0) || ($month == 0) || ($day == 0)) {
3411 if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3414 if (($day > 29) && ($month == 2)) {
3420 public static function ID3v2HeaderLength($majorversion) {
3421 return (($majorversion == 2) ? 6 : 10);