WordPress 4.1
[autoinstalls/wordpress.git] / wp-includes / ID3 / module.tag.id3v2.php
1 <?php
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 /////////////////////////////////////////////////////////////////
10 ///                                                            //
11 // module.tag.id3v2.php                                        //
12 // module for analyzing ID3v2 tags                             //
13 // dependencies: module.tag.id3v1.php                          //
14 //                                                            ///
15 /////////////////////////////////////////////////////////////////
16
17 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
18
19 class getid3_id3v2 extends getid3_handler
20 {
21         public $StartingOffset = 0;
22
23         public function Analyze() {
24                 $info = &$this->getid3->info;
25
26                 //    Overall tag structure:
27                 //        +-----------------------------+
28                 //        |      Header (10 bytes)      |
29                 //        +-----------------------------+
30                 //        |       Extended Header       |
31                 //        | (variable length, OPTIONAL) |
32                 //        +-----------------------------+
33                 //        |   Frames (variable length)  |
34                 //        +-----------------------------+
35                 //        |           Padding           |
36                 //        | (variable length, OPTIONAL) |
37                 //        +-----------------------------+
38                 //        | Footer (10 bytes, OPTIONAL) |
39                 //        +-----------------------------+
40
41                 //    Header
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
46
47
48                 // shortcuts
49                 $info['id3v2']['header'] = true;
50                 $thisfile_id3v2                  = &$info['id3v2'];
51                 $thisfile_id3v2['flags']         =  array();
52                 $thisfile_id3v2_flags            = &$thisfile_id3v2['flags'];
53
54
55                 $this->fseek($this->StartingOffset);
56                 $header = $this->fread(10);
57                 if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
58
59                         $thisfile_id3v2['majorversion'] = ord($header{3});
60                         $thisfile_id3v2['minorversion'] = ord($header{4});
61
62                         // shortcut
63                         $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
64
65                 } else {
66
67                         unset($info['id3v2']);
68                         return false;
69
70                 }
71
72                 if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
73
74                         $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
75                         return false;
76
77                 }
78
79                 $id3_flags = ord($header{5});
80                 switch ($id3v2_majorversion) {
81                         case 2:
82                                 // %ab000000 in v2.2
83                                 $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
84                                 $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
85                                 break;
86
87                         case 3:
88                                 // %abc00000 in v2.3
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
92                                 break;
93
94                         case 4:
95                                 // %abcd0000 in v2.4
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
100                                 break;
101                 }
102
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
104
105                 $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
106                 $thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
107
108
109
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';
114
115
116         //    Frames
117
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:
121         //
122         //        Frame ID      $xx xx xx xx  (four characters)
123         //        Size      4 * %0xxxxxxx
124         //        Flags         $xx xx
125
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);
129                 }
130                 if (!empty($thisfile_id3v2_flags['isfooter'])) {
131                         $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
132                 }
133                 if ($sizeofframes > 0) {
134
135                         $framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
136
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);
140                         }
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.
146
147
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
150
151
152                         //    Extended Header
153                         if (!empty($thisfile_id3v2_flags['exthead'])) {
154                                 $extended_header_offset = 0;
155
156                                 if ($id3v2_majorversion == 3) {
157
158                                         // v2.3 definition:
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
164
165                                         $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
166                                         $extended_header_offset += 4;
167
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'];
171
172                                         $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
173
174                                         $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
175                                         $extended_header_offset += 4;
176
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;
180                                         }
181                                         $extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
182
183                                 } elseif ($id3v2_majorversion == 4) {
184
185                                         // v2.4 definition:
186                                         //Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
187                                         //Number of flag bytes       $01
188                                         //Extended Flags             $xx
189                                         //     %0bcd0000 // v2.4
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
197
198                                         $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
199                                         $extended_header_offset += 4;
200
201                                         $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
202                                         $extended_header_offset += 1;
203
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'];
206
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);
210
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;
214                                         }
215
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;
221                                         }
222
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;
226
227                                                 // %ppqrrstt
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
235
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']);
241                                         }
242
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).')';
245                                         }
246                                 }
247
248                                 $framedataoffset += $extended_header_offset;
249                                 $framedata = substr($framedata, $extended_header_offset);
250                         } // end extended header
251
252
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)';
264                                                         break;
265                                                 }
266                                         }
267                                         break; // skip rest of ID3v2 header
268                                 }
269                                 if ($id3v2_majorversion == 2) {
270                                         // Frame ID  $xx xx xx (three characters)
271                                         // Size      $xx xx xx (24-bit integer)
272                                         // Flags     $xx xx
273
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
279
280                                 } elseif ($id3v2_majorversion > 2) {
281
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+)
284                                         // Flags     $xx xx
285
286                                         $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
287                                         $framedata    = substr($framedata, 10);    // and leave the rest in $framedata
288
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
292                                         } else { // ID3v2.4+
293                                                 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
294                                         }
295
296                                         if ($frame_size < (strlen($framedata) + 4)) {
297                                                 $nextFrameID = substr($framedata, $frame_size, 4);
298                                                 if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
299                                                         // next frame is OK
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
306                                                 }
307                                         }
308
309
310                                         $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
311                                 }
312
313                                 if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
314                                         // padding encountered
315
316                                         $thisfile_id3v2['padding']['start']  = $framedataoffset;
317                                         $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
318                                         $thisfile_id3v2['padding']['valid']  = true;
319
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)';
326                                                         break;
327                                                 }
328                                         }
329                                         break; // skip rest of ID3v2 header
330                                 }
331
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';
335                                 }
336                                 if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
337
338                                         unset($parsedFrame);
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;
344
345                                         $this->ParseID3v2Frame($parsedFrame);
346                                         $thisfile_id3v2[$frame_name][] = $parsedFrame;
347
348                                         $framedata = substr($framedata, $frame_size);
349
350                                 } else { // invalid frame length or FrameID
351
352                                         if ($frame_size <= strlen($framedata)) {
353
354                                                 if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
355
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.';
359
360                                                 } else {
361
362                                                         // next frame is invalid too, abort processing
363                                                         //unset($framedata);
364                                                         $framedata = null;
365                                                         $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
366
367                                                 }
368
369                                         } elseif ($frame_size == strlen($framedata)) {
370
371                                                 // this is the last frame, just skip
372                                                 $info['warning'][] = 'This was the last ID3v2 frame.';
373
374                                         } else {
375
376                                                 // next frame is invalid too, abort processing
377                                                 //unset($framedata);
378                                                 $framedata = null;
379                                                 $info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
380
381                                         }
382                                         if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
383
384                                                 switch ($frame_name) {
385                                                         case "\x00\x00".'MP':
386                                                         case "\x00".'MP3':
387                                                         case ' MP3':
388                                                         case 'MP3e':
389                                                         case "\x00".'MP':
390                                                         case ' MP':
391                                                         case 'MP3':
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/)"]';
393                                                                 break;
394
395                                                         default:
396                                                                 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
397                                                                 break;
398                                                 }
399
400                                         } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
401
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').')).';
403
404                                         } else {
405
406                                                 $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
407
408                                         }
409
410                                 }
411                                 $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
412
413                         }
414
415                 }
416
417
418         //    Footer
419
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
425
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});
432                         }
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);
439
440                                 $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
441                         }
442                 } // end footer
443
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)));
448                         }
449                 }
450
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]);
455                                 }
456                         }
457                 }
458
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]);
461                 }
462
463
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'])));
471                                                 }
472                                                 break;
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']);
476                                                 }
477                                                 break;
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'])));
481                                                 }
482                                                 break;
483                                 }
484                         }
485                 }
486
487
488                 // Set avdataoffset
489                 $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
490                 if (isset($thisfile_id3v2['footer'])) {
491                         $info['avdataoffset'] += 10;
492                 }
493
494                 return true;
495         }
496
497
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);
505                 }
506                 $genre_elements = explode("\x00", $genrestring);
507                 foreach ($genre_elements as $element) {
508                         $element = trim($element);
509                         if ($element) {
510                                 if (preg_match('#^[0-9]{1,3}#', $element)) {
511                                         $clean_genres[] = getid3_id3v1::LookupGenreName($element);
512                                 } else {
513                                         $clean_genres[] = str_replace('((', '(', $element);
514                                 }
515                         }
516                 }
517                 return $clean_genres;
518         }
519
520
521         public function ParseID3v2Frame(&$parsedFrame) {
522
523                 // shortcuts
524                 $info = &$this->getid3->info;
525                 $id3v2_majorversion = $info['id3v2']['majorversion'];
526
527                 $parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
528                 if (empty($parsedFrame['framenamelong'])) {
529                         unset($parsedFrame['framenamelong']);
530                 }
531                 $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
532                 if (empty($parsedFrame['framenameshort'])) {
533                         unset($parsedFrame['framenameshort']);
534                 }
535
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
546
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
558
559                                 // Frame-level de-unsynchronisation - ID3v2.4
560                                 if ($parsedFrame['flags']['Unsynchronisation']) {
561                                         $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
562                                 }
563
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);
567                                 }
568                         }
569
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'].'"';
575                                 } else {
576                                         if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
577                                         //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
578                                                 $parsedFrame['data'] = $decompresseddata;
579                                                 unset($decompresseddata);
580                                         } else {
581                                                 $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
582                                         }
583                                 }
584                         }
585                 }
586
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';
590                         }
591                 }
592
593                 if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
594
595                         $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
596                         switch ($parsedFrame['frame_name']) {
597                                 case 'WCOM':
598                                         $warning .= ' (this is known to happen with files tagged by RioPort)';
599                                         break;
600
601                                 default:
602                                         break;
603                         }
604                         $info['warning'][] = $warning;
605
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] : '');
616
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'>
622                         // Text encoding     $xx
623                         // Description       <text string according to encoding> $00 (00)
624                         // Value             <text string according to encoding>
625
626                         $frame_offset = 0;
627                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
628
629                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
630                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
631                         }
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
635                         }
636                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
637                         if (ord($frame_description) === 0) {
638                                 $frame_description = '';
639                         }
640                         $parsedFrame['encodingid']  = $frame_textencoding;
641                         $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
642
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']));
649                                 } else {
650                                         $info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
651                                 }
652                         }
653                         //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
654
655
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.>
660                         // Text encoding                $xx
661                         // Information                  <text string(s) according to encoding>
662
663                         $frame_offset = 0;
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';
667                         }
668
669                         $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
670
671                         $parsedFrame['encodingid'] = $frame_textencoding;
672                         $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
673
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']) {
680                                         case 'UTF-16':
681                                         case 'UTF-16BE':
682                                         case 'UTF-16LE':
683                                                 $wordsize = 2;
684                                                 break;
685                                         case 'ISO-8859-1':
686                                         case 'UTF-8':
687                                         default:
688                                                 $wordsize = 1;
689                                                 break;
690                                 }
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;
697                                         }
698                                 }
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;
704                                         }
705                                 }
706                                 unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
707                         }
708
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'>
714                         // Text encoding     $xx
715                         // Description       <text string according to encoding> $00 (00)
716                         // URL               <text string>
717
718                         $frame_offset = 0;
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';
722                         }
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
726                         }
727                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
728
729                         if (ord($frame_description) === 0) {
730                                 $frame_description = '';
731                         }
732                         $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
733
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
737                         }
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);
742                         } else {
743                                 // no null bytes following data, just use all data
744                                 $frame_urldata = (string) $parsedFrame['data'];
745                         }
746
747                         $parsedFrame['encodingid']  = $frame_textencoding;
748                         $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
749
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']);
754                         }
755                         unset($parsedFrame['data']);
756
757
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.>
763                         // URL              <text string>
764
765                         $parsedFrame['url'] = trim($parsedFrame['data']);
766                         if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
767                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
768                         }
769                         unset($parsedFrame['data']);
770
771
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'>
777                         // Text encoding     $xx
778                         // People list strings    <textstrings>
779
780                         $frame_offset = 0;
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';
784                         }
785                         $parsedFrame['encodingid'] = $frame_textencoding;
786                         $parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
787                         $parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
788
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
797                                         $thisILPS  = '';
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);
802                                                         $thisILPS  = '';
803                                                 } else {
804                                                         $thisILPS .= $twobytes;
805                                                 }
806                                         }
807                                         if (strlen($thisILPS) > 2) { // 2-byte BOM
808                                                 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
809                                         }
810                                 } else {
811                                         // ISO-8859-1 or UTF-8 or other single-byte-null character set
812                                         $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
813                                 }
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);
818                                                 $position = '';
819                                                 foreach ($IPLS_parts_sorted as $person) {
820                                                         $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
821                                                 }
822                                         }
823                                 } elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
824                                         $position = '';
825                                         $person   = '';
826                                         foreach ($IPLS_parts_unsorted as $key => $value) {
827                                                 if (($key % 2) == 0) {
828                                                         $position = $value;
829                                                 } else {
830                                                         $person   = $value;
831                                                         $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
832                                                         $position = '';
833                                                         $person   = '';
834                                                 }
835                                         }
836                                 } else {
837                                         foreach ($IPLS_parts_unsorted as $key => $value) {
838                                                 $IPLS_parts[] = array($value);
839                                         }
840                                 }
841
842                         } else {
843                                 $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
844                         }
845                         $parsedFrame['data'] = $IPLS_parts;
846
847                         if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
848                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
849                         }
850
851
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>
857
858                         if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
859                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
860                         }
861
862
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:
872                         // Type of event   $xx
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.
876
877                         $frame_offset = 0;
878                         $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
879
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));
884                                 $frame_offset += 4;
885                         }
886                         unset($parsedFrame['data']);
887
888
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....
901
902                         $frame_offset = 0;
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));
911                         }
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++;
918                         }
919                         unset($parsedFrame['data']);
920
921
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
931
932                         $frame_offset = 0;
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));
939                                 }
940                                 $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
941                                 $frame_offset += 4;
942                                 $timestamp_counter++;
943                         }
944                         unset($parsedFrame['data']);
945
946
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'>
952                         // Text encoding        $xx
953                         // Language             $xx xx xx
954                         // Content descriptor   <text string according to encoding> $00 (00)
955                         // Lyrics/text          <full text string according to encoding>
956
957                         $frame_offset = 0;
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';
961                         }
962                         $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
963                         $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
967                         }
968                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
969                         if (ord($frame_description) === 0) {
970                                 $frame_description = '';
971                         }
972                         $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
973
974                         $parsedFrame['encodingid']   = $frame_textencoding;
975                         $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
976
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']);
983                         }
984                         unset($parsedFrame['data']);
985
986
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'>
992                         // Text encoding        $xx
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
997                         // Content type         $xx
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 ...)
1002
1003                         $frame_offset = 0;
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';
1007                         }
1008                         $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1009                         $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);
1015
1016                         $parsedFrame['language']        = $frame_language;
1017                         $parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);
1018
1019                         $timestampindex = 0;
1020                         $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1021                         while (strlen($frame_remainingdata)) {
1022                                 $frame_offset = 0;
1023                                 $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding));
1024                                 if ($frame_terminatorpos === false) {
1025                                         $frame_remainingdata = '';
1026                                 } else {
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
1029                                         }
1030                                         $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1031
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
1035                                         } else {
1036                                                 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1037                                                 $frame_remainingdata = substr($frame_remainingdata, 4);
1038                                         }
1039                                         $timestampindex++;
1040                                 }
1041                         }
1042                         unset($parsedFrame['data']);
1043
1044
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>
1054
1055                         if (strlen($parsedFrame['data']) < 5) {
1056
1057                                 $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
1058
1059                         } else {
1060
1061                                 $frame_offset = 0;
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';
1065                                 }
1066                                 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1067                                 $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
1071                                 }
1072                                 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1073                                 if (ord($frame_description) === 0) {
1074                                         $frame_description = '';
1075                                 }
1076                                 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1077
1078                                 $parsedFrame['encodingid']   = $frame_textencoding;
1079                                 $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1080
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']);
1089                                         } else {
1090                                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1091                                         }
1092                                 }
1093
1094                         }
1095
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 ...)
1108
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 = '';
1113                         }
1114                         $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1115                         $parsedFrame['description'] = $frame_idstring;
1116                         $RVA2channelcounter = 0;
1117                         while (strlen($frame_remainingdata) >= 5) {
1118                                 $frame_offset = 0;
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
1123                                 $frame_offset += 2;
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';
1127                                         break;
1128                                 }
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++;
1133                         }
1134                         unset($parsedFrame['data']);
1135
1136
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 ...)
1159
1160                         $frame_offset = 0;
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;
1169                         }
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;
1174                         }
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;
1188                                         }
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;
1193                                         }
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;
1199                                 }
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;
1206                                         }
1207                                         $frame_offset += $frame_bytesvolume;
1208                                         $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1209                                         $frame_offset += $frame_bytesvolume;
1210                                 }
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;
1217                                         }
1218                                         $frame_offset += $frame_bytesvolume;
1219                                         $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1220                                         $frame_offset += $frame_bytesvolume;
1221                                 }
1222                         }
1223                         unset($parsedFrame['data']);
1224
1225
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
1231                         //   $00  Band
1232                         //   $01  Linear
1233                         // Identification        <text string> $00
1234                         //   The following is then repeated for every adjustment point
1235                         // Frequency          $xx xx
1236                         // Volume adjustment  $xx xx
1237
1238                         $frame_offset = 0;
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 = '';
1244                         }
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);
1251                         }
1252                         $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1253                         unset($parsedFrame['data']);
1254
1255
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 ...)
1267
1268                         $frame_offset = 0;
1269                         $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1270                         $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1271
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;
1281                                 }
1282                                 $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1283                         }
1284                         unset($parsedFrame['data']);
1285
1286
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
1301
1302                         $frame_offset = 0;
1303                         $parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1304                         $frame_offset += 2;
1305                         $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1306                         $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']);
1316
1317
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
1327                         // Picture type       $xx
1328                         // Description        <text string according to encoding> $00 (00)
1329                         // Picture data       <binary data>
1330
1331                         $frame_offset = 0;
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';
1335                         }
1336
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 = '';
1346                                         }
1347                                         $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1348                                         if ($frame_imagetype == 'JPEG') {
1349                                                 $frame_imagetype = 'JPG';
1350                                         }
1351                                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1352                                 } else {
1353                                         $frame_offset += 3;
1354                                 }
1355                         }
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 = '';
1361                                 }
1362                                 $frame_offset = $frame_terminatorpos + strlen("\x00");
1363                         }
1364
1365                         $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1366
1367                         if ($frame_offset >= $parsedFrame['datalength']) {
1368                                 $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
1369                         } else {
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
1373                                 }
1374                                 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1375                                 if (ord($frame_description) === 0) {
1376                                         $frame_description = '';
1377                                 }
1378                                 $parsedFrame['encodingid']       = $frame_textencoding;
1379                                 $parsedFrame['encoding']         = $this->TextEncodingNameLookup($frame_textencoding);
1380
1381                                 if ($id3v2_majorversion == 2) {
1382                                         $parsedFrame['imagetype']    = $frame_imagetype;
1383                                 } else {
1384                                         $parsedFrame['mime']         = $frame_mimetype;
1385                                 }
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']);
1391
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];
1399                                         }
1400                                         if ($imagechunkcheck[1]) {
1401                                                 $parsedFrame['image_height'] = $imagechunkcheck[1];
1402                                         }
1403                                 }
1404
1405                                 do {
1406                                         if ($this->getid3->option_save_attachments === false) {
1407                                                 // skip entirely
1408                                                 unset($parsedFrame['data']);
1409                                                 break;
1410                                         }
1411                                         if ($this->getid3->option_save_attachments === true) {
1412                                                 // great
1413 /*
1414                                         } elseif (is_int($this->getid3->option_save_attachments)) {
1415                                                 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1416                                                         // too big, skip
1417                                                         $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
1418                                                         unset($parsedFrame['data']);
1419                                                         break;
1420                                                 }
1421 */
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']);
1428                                                         break;
1429                                                 }
1430                                         }
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']);
1436                                                 } else {
1437                                                         $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
1438                                                 }
1439                                                 $parsedFrame['data_filename'] = $destination_filename;
1440                                                 unset($parsedFrame['data']);
1441                                         } else {
1442                                                 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1443                                                         if (!isset($info['id3v2']['comments']['picture'])) {
1444                                                                 $info['id3v2']['comments']['picture'] = array();
1445                                                         }
1446                                                         $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']);
1447                                                 }
1448                                         }
1449                                 } while (false);
1450                         }
1451
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>
1462
1463                         $frame_offset = 0;
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';
1467                         }
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 = '';
1472                         }
1473                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1474
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
1478                         }
1479                         $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1480                         if (ord($frame_filename) === 0) {
1481                                 $frame_filename = '';
1482                         }
1483                         $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1484
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
1488                         }
1489                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1490                         if (ord($frame_description) === 0) {
1491                                 $frame_description = '';
1492                         }
1493                         $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1494
1495                         $parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
1496                         $parsedFrame['encodingid']  = $frame_textencoding;
1497                         $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
1498
1499                         $parsedFrame['mime']        = $frame_mimetype;
1500                         $parsedFrame['filename']    = $frame_filename;
1501                         $parsedFrame['description'] = $frame_description;
1502                         unset($parsedFrame['data']);
1503
1504
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 ...)
1512
1513                         $parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);
1514
1515
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
1522                         // Rating          $xx
1523                         // Counter         $xx xx xx xx (xx ...)
1524
1525                         $frame_offset = 0;
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 = '';
1530                         }
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']);
1537
1538
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
1546
1547                         $frame_offset = 0;
1548                         $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1549                         $frame_offset += 3;
1550
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']);
1555
1556
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>
1564
1565                         $frame_offset = 0;
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");
1569
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 = '';
1574                         }
1575                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1576
1577                         $parsedFrame['ownerid']     = $frame_ownerid;
1578                         $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1579                         $parsedFrame['description'] = $frame_description;
1580                         unset($parsedFrame['data']);
1581
1582
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>
1592
1593                         $frame_offset = 0;
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 == '';
1598                         }
1599                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1600                         $parsedFrame['ownerid'] = $frame_ownerid;
1601                         $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1602                         $frame_offset += 2;
1603                         $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1604                         $frame_offset += 2;
1605                         $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1606                         unset($parsedFrame['data']);
1607
1608
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)>
1618
1619                         $frame_offset = 0;
1620                         if ($id3v2_majorversion == 2) {
1621                                 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1622                                 $frame_offset += 3;
1623                         } else {
1624                                 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1625                                 $frame_offset += 4;
1626                         }
1627
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) {
1631                                 $frame_url = '';
1632                         }
1633                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1634                         $parsedFrame['url'] = $frame_url;
1635
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']);
1639                         }
1640                         unset($parsedFrame['data']);
1641
1642
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 ...)
1648
1649                         $frame_offset = 0;
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']);
1653
1654
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>
1662
1663                         $frame_offset = 0;
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';
1667                         }
1668                         $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1669                         $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);
1674
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']);
1678                         }
1679                         unset($parsedFrame['data']);
1680
1681
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>
1689
1690                         $frame_offset = 0;
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';
1694                         }
1695                         $parsedFrame['encodingid'] = $frame_textencoding;
1696                         $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
1697
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");
1701
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);
1705
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));
1709                         }
1710                         $frame_offset += 8;
1711
1712                         $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1713                         unset($parsedFrame['data']);
1714
1715
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
1724                         // Received as        $xx
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>
1729
1730                         $frame_offset = 0;
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';
1734                         }
1735
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);
1744                         }
1745
1746                         $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1747                         $frame_offset += 8;
1748
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");
1752
1753                         $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1754
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
1758                         }
1759                         $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1760                         if (ord($frame_sellername) === 0) {
1761                                 $frame_sellername = '';
1762                         }
1763                         $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1764
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
1768                         }
1769                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1770                         if (ord($frame_description) === 0) {
1771                                 $frame_description = '';
1772                         }
1773                         $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1774
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");
1778
1779                         $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1780
1781                         $parsedFrame['encodingid']        = $frame_textencoding;
1782                         $parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);
1783
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']);
1793
1794
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>
1803
1804                         $frame_offset = 0;
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 = '';
1809                         }
1810                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1811
1812                         $parsedFrame['ownerid']      = $frame_ownerid;
1813                         $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1814                         $parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
1815
1816
1817                 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)
1818
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
1824                         // Group symbol          $xx
1825                         // Group dependent data  <binary data>
1826
1827                         $frame_offset = 0;
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 = '';
1832                         }
1833                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1834
1835                         $parsedFrame['ownerid']       = $frame_ownerid;
1836                         $parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1837                         $parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);
1838
1839
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>
1846
1847                         $frame_offset = 0;
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 = '';
1852                         }
1853                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1854
1855                         $parsedFrame['ownerid'] = $frame_ownerid;
1856                         $parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);
1857
1858
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'>
1863                         // Group symbol      $xx
1864                         // Signature         <binary data>
1865
1866                         $frame_offset = 0;
1867                         $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1868                         $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1869
1870
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
1875
1876                         $frame_offset = 0;
1877                         $parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1878
1879
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)
1889
1890                         $frame_offset = 0;
1891                         $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1892                         $frame_offset += 4;
1893                         $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1894                         $frame_offset += 4;
1895                         $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1896                         $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;
1902                         }
1903                         unset($parsedFrame['data']);
1904
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
1912                         //   a - name code
1913                         //   b - originator code
1914                         //   c - sign bit
1915                         //   d - replay gain adjustment
1916
1917                         $frame_offset = 0;
1918                         $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1919                         $frame_offset += 4;
1920                         $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1921                         $frame_offset += 2;
1922                         $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1923                         $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']);
1938
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'];
1944
1945                         unset($parsedFrame['data']);
1946
1947                 }
1948
1949                 return true;
1950         }
1951
1952
1953         public function DeUnsynchronise($data) {
1954                 return str_replace("\xFF\x00", "\xFF", $data);
1955         }
1956
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',
1963                 );
1964                 return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
1965         }
1966
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',
1971                 );
1972                 return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
1973         }
1974
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',
1981                 );
1982                 return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
1983         }
1984
1985         public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
1986                 static $LookupExtendedHeaderRestrictionsImageEncoding = array(
1987                         0x00 => 'No restrictions',
1988                         0x01 => 'Images are encoded only with PNG or JPEG',
1989                 );
1990                 return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
1991         }
1992
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',
1999                 );
2000                 return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
2001         }
2002
2003         public function LookupCurrencyUnits($currencyid) {
2004
2005                 $begin = __LINE__;
2006
2007                 /** This is not a comment!
2008
2009
2010                         AED     Dirhams
2011                         AFA     Afghanis
2012                         ALL     Leke
2013                         AMD     Drams
2014                         ANG     Guilders
2015                         AOA     Kwanza
2016                         ARS     Pesos
2017                         ATS     Schillings
2018                         AUD     Dollars
2019                         AWG     Guilders
2020                         AZM     Manats
2021                         BAM     Convertible Marka
2022                         BBD     Dollars
2023                         BDT     Taka
2024                         BEF     Francs
2025                         BGL     Leva
2026                         BHD     Dinars
2027                         BIF     Francs
2028                         BMD     Dollars
2029                         BND     Dollars
2030                         BOB     Bolivianos
2031                         BRL     Brazil Real
2032                         BSD     Dollars
2033                         BTN     Ngultrum
2034                         BWP     Pulas
2035                         BYR     Rubles
2036                         BZD     Dollars
2037                         CAD     Dollars
2038                         CDF     Congolese Francs
2039                         CHF     Francs
2040                         CLP     Pesos
2041                         CNY     Yuan Renminbi
2042                         COP     Pesos
2043                         CRC     Colones
2044                         CUP     Pesos
2045                         CVE     Escudos
2046                         CYP     Pounds
2047                         CZK     Koruny
2048                         DEM     Deutsche Marks
2049                         DJF     Francs
2050                         DKK     Kroner
2051                         DOP     Pesos
2052                         DZD     Algeria Dinars
2053                         EEK     Krooni
2054                         EGP     Pounds
2055                         ERN     Nakfa
2056                         ESP     Pesetas
2057                         ETB     Birr
2058                         EUR     Euro
2059                         FIM     Markkaa
2060                         FJD     Dollars
2061                         FKP     Pounds
2062                         FRF     Francs
2063                         GBP     Pounds
2064                         GEL     Lari
2065                         GGP     Pounds
2066                         GHC     Cedis
2067                         GIP     Pounds
2068                         GMD     Dalasi
2069                         GNF     Francs
2070                         GRD     Drachmae
2071                         GTQ     Quetzales
2072                         GYD     Dollars
2073                         HKD     Dollars
2074                         HNL     Lempiras
2075                         HRK     Kuna
2076                         HTG     Gourdes
2077                         HUF     Forints
2078                         IDR     Rupiahs
2079                         IEP     Pounds
2080                         ILS     New Shekels
2081                         IMP     Pounds
2082                         INR     Rupees
2083                         IQD     Dinars
2084                         IRR     Rials
2085                         ISK     Kronur
2086                         ITL     Lire
2087                         JEP     Pounds
2088                         JMD     Dollars
2089                         JOD     Dinars
2090                         JPY     Yen
2091                         KES     Shillings
2092                         KGS     Soms
2093                         KHR     Riels
2094                         KMF     Francs
2095                         KPW     Won
2096                         KWD     Dinars
2097                         KYD     Dollars
2098                         KZT     Tenge
2099                         LAK     Kips
2100                         LBP     Pounds
2101                         LKR     Rupees
2102                         LRD     Dollars
2103                         LSL     Maloti
2104                         LTL     Litai
2105                         LUF     Francs
2106                         LVL     Lati
2107                         LYD     Dinars
2108                         MAD     Dirhams
2109                         MDL     Lei
2110                         MGF     Malagasy Francs
2111                         MKD     Denars
2112                         MMK     Kyats
2113                         MNT     Tugriks
2114                         MOP     Patacas
2115                         MRO     Ouguiyas
2116                         MTL     Liri
2117                         MUR     Rupees
2118                         MVR     Rufiyaa
2119                         MWK     Kwachas
2120                         MXN     Pesos
2121                         MYR     Ringgits
2122                         MZM     Meticais
2123                         NAD     Dollars
2124                         NGN     Nairas
2125                         NIO     Gold Cordobas
2126                         NLG     Guilders
2127                         NOK     Krone
2128                         NPR     Nepal Rupees
2129                         NZD     Dollars
2130                         OMR     Rials
2131                         PAB     Balboa
2132                         PEN     Nuevos Soles
2133                         PGK     Kina
2134                         PHP     Pesos
2135                         PKR     Rupees
2136                         PLN     Zlotych
2137                         PTE     Escudos
2138                         PYG     Guarani
2139                         QAR     Rials
2140                         ROL     Lei
2141                         RUR     Rubles
2142                         RWF     Rwanda Francs
2143                         SAR     Riyals
2144                         SBD     Dollars
2145                         SCR     Rupees
2146                         SDD     Dinars
2147                         SEK     Kronor
2148                         SGD     Dollars
2149                         SHP     Pounds
2150                         SIT     Tolars
2151                         SKK     Koruny
2152                         SLL     Leones
2153                         SOS     Shillings
2154                         SPL     Luigini
2155                         SRG     Guilders
2156                         STD     Dobras
2157                         SVC     Colones
2158                         SYP     Pounds
2159                         SZL     Emalangeni
2160                         THB     Baht
2161                         TJR     Rubles
2162                         TMM     Manats
2163                         TND     Dinars
2164                         TOP     Pa'anga
2165                         TRL     Liras
2166                         TTD     Dollars
2167                         TVD     Tuvalu Dollars
2168                         TWD     New Dollars
2169                         TZS     Shillings
2170                         UAH     Hryvnia
2171                         UGX     Shillings
2172                         USD     Dollars
2173                         UYU     Pesos
2174                         UZS     Sums
2175                         VAL     Lire
2176                         VEB     Bolivares
2177                         VND     Dong
2178                         VUV     Vatu
2179                         WST     Tala
2180                         XAF     Francs
2181                         XAG     Ounces
2182                         XAU     Ounces
2183                         XCD     Dollars
2184                         XDR     Special Drawing Rights
2185                         XPD     Ounces
2186                         XPF     Francs
2187                         XPT     Ounces
2188                         YER     Rials
2189                         YUM     New Dinars
2190                         ZAR     Rand
2191                         ZMK     Kwacha
2192                         ZWD     Zimbabwe Dollars
2193
2194                 */
2195
2196                 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2197         }
2198
2199
2200         public function LookupCurrencyCountry($currencyid) {
2201
2202                 $begin = __LINE__;
2203
2204                 /** This is not a comment!
2205
2206                         AED     United Arab Emirates
2207                         AFA     Afghanistan
2208                         ALL     Albania
2209                         AMD     Armenia
2210                         ANG     Netherlands Antilles
2211                         AOA     Angola
2212                         ARS     Argentina
2213                         ATS     Austria
2214                         AUD     Australia
2215                         AWG     Aruba
2216                         AZM     Azerbaijan
2217                         BAM     Bosnia and Herzegovina
2218                         BBD     Barbados
2219                         BDT     Bangladesh
2220                         BEF     Belgium
2221                         BGL     Bulgaria
2222                         BHD     Bahrain
2223                         BIF     Burundi
2224                         BMD     Bermuda
2225                         BND     Brunei Darussalam
2226                         BOB     Bolivia
2227                         BRL     Brazil
2228                         BSD     Bahamas
2229                         BTN     Bhutan
2230                         BWP     Botswana
2231                         BYR     Belarus
2232                         BZD     Belize
2233                         CAD     Canada
2234                         CDF     Congo/Kinshasa
2235                         CHF     Switzerland
2236                         CLP     Chile
2237                         CNY     China
2238                         COP     Colombia
2239                         CRC     Costa Rica
2240                         CUP     Cuba
2241                         CVE     Cape Verde
2242                         CYP     Cyprus
2243                         CZK     Czech Republic
2244                         DEM     Germany
2245                         DJF     Djibouti
2246                         DKK     Denmark
2247                         DOP     Dominican Republic
2248                         DZD     Algeria
2249                         EEK     Estonia
2250                         EGP     Egypt
2251                         ERN     Eritrea
2252                         ESP     Spain
2253                         ETB     Ethiopia
2254                         EUR     Euro Member Countries
2255                         FIM     Finland
2256                         FJD     Fiji
2257                         FKP     Falkland Islands (Malvinas)
2258                         FRF     France
2259                         GBP     United Kingdom
2260                         GEL     Georgia
2261                         GGP     Guernsey
2262                         GHC     Ghana
2263                         GIP     Gibraltar
2264                         GMD     Gambia
2265                         GNF     Guinea
2266                         GRD     Greece
2267                         GTQ     Guatemala
2268                         GYD     Guyana
2269                         HKD     Hong Kong
2270                         HNL     Honduras
2271                         HRK     Croatia
2272                         HTG     Haiti
2273                         HUF     Hungary
2274                         IDR     Indonesia
2275                         IEP     Ireland (Eire)
2276                         ILS     Israel
2277                         IMP     Isle of Man
2278                         INR     India
2279                         IQD     Iraq
2280                         IRR     Iran
2281                         ISK     Iceland
2282                         ITL     Italy
2283                         JEP     Jersey
2284                         JMD     Jamaica
2285                         JOD     Jordan
2286                         JPY     Japan
2287                         KES     Kenya
2288                         KGS     Kyrgyzstan
2289                         KHR     Cambodia
2290                         KMF     Comoros
2291                         KPW     Korea
2292                         KWD     Kuwait
2293                         KYD     Cayman Islands
2294                         KZT     Kazakstan
2295                         LAK     Laos
2296                         LBP     Lebanon
2297                         LKR     Sri Lanka
2298                         LRD     Liberia
2299                         LSL     Lesotho
2300                         LTL     Lithuania
2301                         LUF     Luxembourg
2302                         LVL     Latvia
2303                         LYD     Libya
2304                         MAD     Morocco
2305                         MDL     Moldova
2306                         MGF     Madagascar
2307                         MKD     Macedonia
2308                         MMK     Myanmar (Burma)
2309                         MNT     Mongolia
2310                         MOP     Macau
2311                         MRO     Mauritania
2312                         MTL     Malta
2313                         MUR     Mauritius
2314                         MVR     Maldives (Maldive Islands)
2315                         MWK     Malawi
2316                         MXN     Mexico
2317                         MYR     Malaysia
2318                         MZM     Mozambique
2319                         NAD     Namibia
2320                         NGN     Nigeria
2321                         NIO     Nicaragua
2322                         NLG     Netherlands (Holland)
2323                         NOK     Norway
2324                         NPR     Nepal
2325                         NZD     New Zealand
2326                         OMR     Oman
2327                         PAB     Panama
2328                         PEN     Peru
2329                         PGK     Papua New Guinea
2330                         PHP     Philippines
2331                         PKR     Pakistan
2332                         PLN     Poland
2333                         PTE     Portugal
2334                         PYG     Paraguay
2335                         QAR     Qatar
2336                         ROL     Romania
2337                         RUR     Russia
2338                         RWF     Rwanda
2339                         SAR     Saudi Arabia
2340                         SBD     Solomon Islands
2341                         SCR     Seychelles
2342                         SDD     Sudan
2343                         SEK     Sweden
2344                         SGD     Singapore
2345                         SHP     Saint Helena
2346                         SIT     Slovenia
2347                         SKK     Slovakia
2348                         SLL     Sierra Leone
2349                         SOS     Somalia
2350                         SPL     Seborga
2351                         SRG     Suriname
2352                         STD     São Tome and Principe
2353                         SVC     El Salvador
2354                         SYP     Syria
2355                         SZL     Swaziland
2356                         THB     Thailand
2357                         TJR     Tajikistan
2358                         TMM     Turkmenistan
2359                         TND     Tunisia
2360                         TOP     Tonga
2361                         TRL     Turkey
2362                         TTD     Trinidad and Tobago
2363                         TVD     Tuvalu
2364                         TWD     Taiwan
2365                         TZS     Tanzania
2366                         UAH     Ukraine
2367                         UGX     Uganda
2368                         USD     United States of America
2369                         UYU     Uruguay
2370                         UZS     Uzbekistan
2371                         VAL     Vatican City
2372                         VEB     Venezuela
2373                         VND     Viet Nam
2374                         VUV     Vanuatu
2375                         WST     Samoa
2376                         XAF     Communauté Financière Africaine
2377                         XAG     Silver
2378                         XAU     Gold
2379                         XCD     East Caribbean
2380                         XDR     International Monetary Fund
2381                         XPD     Palladium
2382                         XPF     Comptoirs Français du Pacifique
2383                         XPT     Platinum
2384                         YER     Yemen
2385                         YUM     Yugoslavia
2386                         ZAR     South Africa
2387                         ZMK     Zambia
2388                         ZWD     Zimbabwe
2389
2390                 */
2391
2392                 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2393         }
2394
2395
2396
2397         public static function LanguageLookup($languagecode, $casesensitive=false) {
2398
2399                 if (!$casesensitive) {
2400                         $languagecode = strtolower($languagecode);
2401                 }
2402
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.
2409
2410
2411                 // ISO 639-2 - http://www.id3.org/iso639-2.html
2412
2413                 $begin = __LINE__;
2414
2415                 /** This is not a comment!
2416
2417                         XXX     unknown
2418                         xxx     unknown
2419                         aar     Afar
2420                         abk     Abkhazian
2421                         ace     Achinese
2422                         ach     Acoli
2423                         ada     Adangme
2424                         afa     Afro-Asiatic (Other)
2425                         afh     Afrihili
2426                         afr     Afrikaans
2427                         aka     Akan
2428                         akk     Akkadian
2429                         alb     Albanian
2430                         ale     Aleut
2431                         alg     Algonquian Languages
2432                         amh     Amharic
2433                         ang     English, Old (ca. 450-1100)
2434                         apa     Apache Languages
2435                         ara     Arabic
2436                         arc     Aramaic
2437                         arm     Armenian
2438                         arn     Araucanian
2439                         arp     Arapaho
2440                         art     Artificial (Other)
2441                         arw     Arawak
2442                         asm     Assamese
2443                         ath     Athapascan Languages
2444                         ava     Avaric
2445                         ave     Avestan
2446                         awa     Awadhi
2447                         aym     Aymara
2448                         aze     Azerbaijani
2449                         bad     Banda
2450                         bai     Bamileke Languages
2451                         bak     Bashkir
2452                         bal     Baluchi
2453                         bam     Bambara
2454                         ban     Balinese
2455                         baq     Basque
2456                         bas     Basa
2457                         bat     Baltic (Other)
2458                         bej     Beja
2459                         bel     Byelorussian
2460                         bem     Bemba
2461                         ben     Bengali
2462                         ber     Berber (Other)
2463                         bho     Bhojpuri
2464                         bih     Bihari
2465                         bik     Bikol
2466                         bin     Bini
2467                         bis     Bislama
2468                         bla     Siksika
2469                         bnt     Bantu (Other)
2470                         bod     Tibetan
2471                         bra     Braj
2472                         bre     Breton
2473                         bua     Buriat
2474                         bug     Buginese
2475                         bul     Bulgarian
2476                         bur     Burmese
2477                         cad     Caddo
2478                         cai     Central American Indian (Other)
2479                         car     Carib
2480                         cat     Catalan
2481                         cau     Caucasian (Other)
2482                         ceb     Cebuano
2483                         cel     Celtic (Other)
2484                         ces     Czech
2485                         cha     Chamorro
2486                         chb     Chibcha
2487                         che     Chechen
2488                         chg     Chagatai
2489                         chi     Chinese
2490                         chm     Mari
2491                         chn     Chinook jargon
2492                         cho     Choctaw
2493                         chr     Cherokee
2494                         chu     Church Slavic
2495                         chv     Chuvash
2496                         chy     Cheyenne
2497                         cop     Coptic
2498                         cor     Cornish
2499                         cos     Corsican
2500                         cpe     Creoles and Pidgins, English-based (Other)
2501                         cpf     Creoles and Pidgins, French-based (Other)
2502                         cpp     Creoles and Pidgins, Portuguese-based (Other)
2503                         cre     Cree
2504                         crp     Creoles and Pidgins (Other)
2505                         cus     Cushitic (Other)
2506                         cym     Welsh
2507                         cze     Czech
2508                         dak     Dakota
2509                         dan     Danish
2510                         del     Delaware
2511                         deu     German
2512                         din     Dinka
2513                         div     Divehi
2514                         doi     Dogri
2515                         dra     Dravidian (Other)
2516                         dua     Duala
2517                         dum     Dutch, Middle (ca. 1050-1350)
2518                         dut     Dutch
2519                         dyu     Dyula
2520                         dzo     Dzongkha
2521                         efi     Efik
2522                         egy     Egyptian (Ancient)
2523                         eka     Ekajuk
2524                         ell     Greek, Modern (1453-)
2525                         elx     Elamite
2526                         eng     English
2527                         enm     English, Middle (ca. 1100-1500)
2528                         epo     Esperanto
2529                         esk     Eskimo (Other)
2530                         esl     Spanish
2531                         est     Estonian
2532                         eus     Basque
2533                         ewe     Ewe
2534                         ewo     Ewondo
2535                         fan     Fang
2536                         fao     Faroese
2537                         fas     Persian
2538                         fat     Fanti
2539                         fij     Fijian
2540                         fin     Finnish
2541                         fiu     Finno-Ugrian (Other)
2542                         fon     Fon
2543                         fra     French
2544                         fre     French
2545                         frm     French, Middle (ca. 1400-1600)
2546                         fro     French, Old (842- ca. 1400)
2547                         fry     Frisian
2548                         ful     Fulah
2549                         gaa     Ga
2550                         gae     Gaelic (Scots)
2551                         gai     Irish
2552                         gay     Gayo
2553                         gdh     Gaelic (Scots)
2554                         gem     Germanic (Other)
2555                         geo     Georgian
2556                         ger     German
2557                         gez     Geez
2558                         gil     Gilbertese
2559                         glg     Gallegan