Wordpress 3.6
[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 /////////////////////////////////////////////////////////////////
7 // See readme.txt for more details                             //
8 /////////////////////////////////////////////////////////////////
9 ///                                                            //
10 // module.tag.id3v2.php                                        //
11 // module for analyzing ID3v2 tags                             //
12 // dependencies: module.tag.id3v1.php                          //
13 //                                                            ///
14 /////////////////////////////////////////////////////////////////
15
16 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
17
18 class getid3_id3v2 extends getid3_handler
19 {
20         public $StartingOffset = 0;
21
22         public function Analyze() {
23                 $info = &$this->getid3->info;
24
25                 //    Overall tag structure:
26                 //        +-----------------------------+
27                 //        |      Header (10 bytes)      |
28                 //        +-----------------------------+
29                 //        |       Extended Header       |
30                 //        | (variable length, OPTIONAL) |
31                 //        +-----------------------------+
32                 //        |   Frames (variable length)  |
33                 //        +-----------------------------+
34                 //        |           Padding           |
35                 //        | (variable length, OPTIONAL) |
36                 //        +-----------------------------+
37                 //        | Footer (10 bytes, OPTIONAL) |
38                 //        +-----------------------------+
39
40                 //    Header
41                 //        ID3v2/file identifier      "ID3"
42                 //        ID3v2 version              $04 00
43                 //        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
44                 //        ID3v2 size             4 * %0xxxxxxx
45
46
47                 // shortcuts
48                 $info['id3v2']['header'] = true;
49                 $thisfile_id3v2                  = &$info['id3v2'];
50                 $thisfile_id3v2['flags']         =  array();
51                 $thisfile_id3v2_flags            = &$thisfile_id3v2['flags'];
52
53
54                 fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET);
55                 $header = fread($this->getid3->fp, 10);
56                 if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
57
58                         $thisfile_id3v2['majorversion'] = ord($header{3});
59                         $thisfile_id3v2['minorversion'] = ord($header{4});
60
61                         // shortcut
62                         $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
63
64                 } else {
65
66                         unset($info['id3v2']);
67                         return false;
68
69                 }
70
71                 if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
72
73                         $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
74                         return false;
75
76                 }
77
78                 $id3_flags = ord($header{5});
79                 switch ($id3v2_majorversion) {
80                         case 2:
81                                 // %ab000000 in v2.2
82                                 $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
83                                 $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
84                                 break;
85
86                         case 3:
87                                 // %abc00000 in v2.3
88                                 $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
89                                 $thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
90                                 $thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
91                                 break;
92
93                         case 4:
94                                 // %abcd0000 in v2.4
95                                 $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
96                                 $thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
97                                 $thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
98                                 $thisfile_id3v2_flags['isfooter']    = (bool) ($id3_flags & 0x10); // d - Footer present
99                                 break;
100                 }
101
102                 $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
103
104                 $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
105                 $thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
106
107
108
109                 // create 'encoding' key - used by getid3::HandleAllTags()
110                 // in ID3v2 every field can have it's own encoding type
111                 // so force everything to UTF-8 so it can be handled consistantly
112                 $thisfile_id3v2['encoding'] = 'UTF-8';
113
114
115         //    Frames
116
117         //        All ID3v2 frames consists of one frame header followed by one or more
118         //        fields containing the actual information. The header is always 10
119         //        bytes and laid out as follows:
120         //
121         //        Frame ID      $xx xx xx xx  (four characters)
122         //        Size      4 * %0xxxxxxx
123         //        Flags         $xx xx
124
125                 $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
126                 if (!empty($thisfile_id3v2['exthead']['length'])) {
127                         $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
128                 }
129                 if (!empty($thisfile_id3v2_flags['isfooter'])) {
130                         $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
131                 }
132                 if ($sizeofframes > 0) {
133
134                         $framedata = fread($this->getid3->fp, $sizeofframes); // read all frames from file into $framedata variable
135
136                         //    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
137                         if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
138                                 $framedata = $this->DeUnsynchronise($framedata);
139                         }
140                         //        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
141                         //        of on tag level, making it easier to skip frames, increasing the streamability
142                         //        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
143                         //        there exists an unsynchronised frame, while the new unsynchronisation flag in
144                         //        the frame header [S:4.1.2] indicates unsynchronisation.
145
146
147                         //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
148                         $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
149
150
151                         //    Extended Header
152                         if (!empty($thisfile_id3v2_flags['exthead'])) {
153                                 $extended_header_offset = 0;
154
155                                 if ($id3v2_majorversion == 3) {
156
157                                         // v2.3 definition:
158                                         //Extended header size  $xx xx xx xx   // 32-bit integer
159                                         //Extended Flags        $xx xx
160                                         //     %x0000000 %00000000 // v2.3
161                                         //     x - CRC data present
162                                         //Size of padding       $xx xx xx xx
163
164                                         $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
165                                         $extended_header_offset += 4;
166
167                                         $thisfile_id3v2['exthead']['flag_bytes'] = 2;
168                                         $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
169                                         $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
170
171                                         $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
172
173                                         $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
174                                         $extended_header_offset += 4;
175
176                                         if ($thisfile_id3v2['exthead']['flags']['crc']) {
177                                                 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
178                                                 $extended_header_offset += 4;
179                                         }
180                                         $extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
181
182                                 } elseif ($id3v2_majorversion == 4) {
183
184                                         // v2.4 definition:
185                                         //Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
186                                         //Number of flag bytes       $01
187                                         //Extended Flags             $xx
188                                         //     %0bcd0000 // v2.4
189                                         //     b - Tag is an update
190                                         //         Flag data length       $00
191                                         //     c - CRC data present
192                                         //         Flag data length       $05
193                                         //         Total frame CRC    5 * %0xxxxxxx
194                                         //     d - Tag restrictions
195                                         //         Flag data length       $01
196
197                                         $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
198                                         $extended_header_offset += 4;
199
200                                         $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
201                                         $extended_header_offset += 1;
202
203                                         $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
204                                         $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
205
206                                         $thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
207                                         $thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
208                                         $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
209
210                                         if ($thisfile_id3v2['exthead']['flags']['update']) {
211                                                 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
212                                                 $extended_header_offset += 1;
213                                         }
214
215                                         if ($thisfile_id3v2['exthead']['flags']['crc']) {
216                                                 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
217                                                 $extended_header_offset += 1;
218                                                 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
219                                                 $extended_header_offset += $ext_header_chunk_length;
220                                         }
221
222                                         if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
223                                                 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
224                                                 $extended_header_offset += 1;
225
226                                                 // %ppqrrstt
227                                                 $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
228                                                 $extended_header_offset += 1;
229                                                 $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
230                                                 $thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
231                                                 $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
232                                                 $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
233                                                 $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
234
235                                                 $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize']  = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
236                                                 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc']  = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
237                                                 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
238                                                 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
239                                                 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
240                                         }
241
242                                         if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
243                                                 $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
244                                         }
245                                 }
246
247                                 $framedataoffset += $extended_header_offset;
248                                 $framedata = substr($framedata, $extended_header_offset);
249                         } // end extended header
250
251
252                         while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
253                                 if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
254                                         // insufficient room left in ID3v2 header for actual data - must be padding
255                                         $thisfile_id3v2['padding']['start']  = $framedataoffset;
256                                         $thisfile_id3v2['padding']['length'] = strlen($framedata);
257                                         $thisfile_id3v2['padding']['valid']  = true;
258                                         for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
259                                                 if ($framedata{$i} != "\x00") {
260                                                         $thisfile_id3v2['padding']['valid'] = false;
261                                                         $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
262                                                         $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
263                                                         break;
264                                                 }
265                                         }
266                                         break; // skip rest of ID3v2 header
267                                 }
268                                 if ($id3v2_majorversion == 2) {
269                                         // Frame ID  $xx xx xx (three characters)
270                                         // Size      $xx xx xx (24-bit integer)
271                                         // Flags     $xx xx
272
273                                         $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
274                                         $framedata    = substr($framedata, 6);    // and leave the rest in $framedata
275                                         $frame_name   = substr($frame_header, 0, 3);
276                                         $frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
277                                         $frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
278
279                                 } elseif ($id3v2_majorversion > 2) {
280
281                                         // Frame ID  $xx xx xx xx (four characters)
282                                         // Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
283                                         // Flags     $xx xx
284
285                                         $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
286                                         $framedata    = substr($framedata, 10);    // and leave the rest in $framedata
287
288                                         $frame_name = substr($frame_header, 0, 4);
289                                         if ($id3v2_majorversion == 3) {
290                                                 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
291                                         } else { // ID3v2.4+
292                                                 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
293                                         }
294
295                                         if ($frame_size < (strlen($framedata) + 4)) {
296                                                 $nextFrameID = substr($framedata, $frame_size, 4);
297                                                 if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
298                                                         // next frame is OK
299                                                 } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
300                                                         // MP3ext known broken frames - "ok" for the purposes of this test
301                                                 } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
302                                                         $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
303                                                         $id3v2_majorversion = 3;
304                                                         $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
305                                                 }
306                                         }
307
308
309                                         $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
310                                 }
311
312                                 if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
313                                         // padding encountered
314
315                                         $thisfile_id3v2['padding']['start']  = $framedataoffset;
316                                         $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
317                                         $thisfile_id3v2['padding']['valid']  = true;
318
319                                         $len = strlen($framedata);
320                                         for ($i = 0; $i < $len; $i++) {
321                                                 if ($framedata{$i} != "\x00") {
322                                                         $thisfile_id3v2['padding']['valid'] = false;
323                                                         $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
324                                                         $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
325                                                         break;
326                                                 }
327                                         }
328                                         break; // skip rest of ID3v2 header
329                                 }
330
331                                 if ($frame_name == 'COM ') {
332                                         $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
333                                         $frame_name = 'COMM';
334                                 }
335                                 if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
336
337                                         unset($parsedFrame);
338                                         $parsedFrame['frame_name']      = $frame_name;
339                                         $parsedFrame['frame_flags_raw'] = $frame_flags;
340                                         $parsedFrame['data']            = substr($framedata, 0, $frame_size);
341                                         $parsedFrame['datalength']      = getid3_lib::CastAsInt($frame_size);
342                                         $parsedFrame['dataoffset']      = $framedataoffset;
343
344                                         $this->ParseID3v2Frame($parsedFrame);
345                                         $thisfile_id3v2[$frame_name][] = $parsedFrame;
346
347                                         $framedata = substr($framedata, $frame_size);
348
349                                 } else { // invalid frame length or FrameID
350
351                                         if ($frame_size <= strlen($framedata)) {
352
353                                                 if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
354
355                                                         // next frame is valid, just skip the current frame
356                                                         $framedata = substr($framedata, $frame_size);
357                                                         $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
358
359                                                 } else {
360
361                                                         // next frame is invalid too, abort processing
362                                                         //unset($framedata);
363                                                         $framedata = null;
364                                                         $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
365
366                                                 }
367
368                                         } elseif ($frame_size == strlen($framedata)) {
369
370                                                 // this is the last frame, just skip
371                                                 $info['warning'][] = 'This was the last ID3v2 frame.';
372
373                                         } else {
374
375                                                 // next frame is invalid too, abort processing
376                                                 //unset($framedata);
377                                                 $framedata = null;
378                                                 $info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
379
380                                         }
381                                         if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
382
383                                                 switch ($frame_name) {
384                                                         case "\x00\x00".'MP':
385                                                         case "\x00".'MP3':
386                                                         case ' MP3':
387                                                         case 'MP3e':
388                                                         case "\x00".'MP':
389                                                         case ' MP':
390                                                         case 'MP3':
391                                                                 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
392                                                                 break;
393
394                                                         default:
395                                                                 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
396                                                                 break;
397                                                 }
398
399                                         } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
400
401                                                 $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).';
402
403                                         } else {
404
405                                                 $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
406
407                                         }
408
409                                 }
410                                 $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
411
412                         }
413
414                 }
415
416
417         //    Footer
418
419         //    The footer is a copy of the header, but with a different identifier.
420         //        ID3v2 identifier           "3DI"
421         //        ID3v2 version              $04 00
422         //        ID3v2 flags                %abcd0000
423         //        ID3v2 size             4 * %0xxxxxxx
424
425                 if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
426                         $footer = fread($this->getid3->fp, 10);
427                         if (substr($footer, 0, 3) == '3DI') {
428                                 $thisfile_id3v2['footer'] = true;
429                                 $thisfile_id3v2['majorversion_footer'] = ord($footer{3});
430                                 $thisfile_id3v2['minorversion_footer'] = ord($footer{4});
431                         }
432                         if ($thisfile_id3v2['majorversion_footer'] <= 4) {
433                                 $id3_flags = ord(substr($footer{5}));
434                                 $thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
435                                 $thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
436                                 $thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
437                                 $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
438
439                                 $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
440                         }
441                 } // end footer
442
443                 if (isset($thisfile_id3v2['comments']['genre'])) {
444                         foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
445                                 unset($thisfile_id3v2['comments']['genre'][$key]);
446                                 $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
447                         }
448                 }
449
450                 if (isset($thisfile_id3v2['comments']['track'])) {
451                         foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
452                                 if (strstr($value, '/')) {
453                                         list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
454                                 }
455                         }
456                 }
457
458                 if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
459                         $thisfile_id3v2['comments']['year'] = array($matches[1]);
460                 }
461
462
463                 if (!empty($thisfile_id3v2['TXXX'])) {
464                         // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
465                         foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
466                                 switch ($txxx_array['description']) {
467                                         case 'replaygain_track_gain':
468                                                 if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
469                                                         $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
470                                                 }
471                                                 break;
472                                         case 'replaygain_track_peak':
473                                                 if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
474                                                         $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
475                                                 }
476                                                 break;
477                                         case 'replaygain_album_gain':
478                                                 if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
479                                                         $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
480                                                 }
481                                                 break;
482                                 }
483                         }
484                 }
485
486
487                 // Set avdataoffset
488                 $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
489                 if (isset($thisfile_id3v2['footer'])) {
490                         $info['avdataoffset'] += 10;
491                 }
492
493                 return true;
494         }
495
496
497         public function ParseID3v2GenreString($genrestring) {
498                 // Parse genres into arrays of genreName and genreID
499                 // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
500                 // ID3v2.4.x: '21' $00 'Eurodisco' $00
501                 $clean_genres = array();
502                 if (strpos($genrestring, "\x00") === false) {
503                         $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
504                 }
505                 $genre_elements = explode("\x00", $genrestring);
506                 foreach ($genre_elements as $element) {
507                         $element = trim($element);
508                         if ($element) {
509                                 if (preg_match('#^[0-9]{1,3}#', $element)) {
510                                         $clean_genres[] = getid3_id3v1::LookupGenreName($element);
511                                 } else {
512                                         $clean_genres[] = str_replace('((', '(', $element);
513                                 }
514                         }
515                 }
516                 return $clean_genres;
517         }
518
519
520         public function ParseID3v2Frame(&$parsedFrame) {
521
522                 // shortcuts
523                 $info = &$this->getid3->info;
524                 $id3v2_majorversion = $info['id3v2']['majorversion'];
525
526                 $parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
527                 if (empty($parsedFrame['framenamelong'])) {
528                         unset($parsedFrame['framenamelong']);
529                 }
530                 $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
531                 if (empty($parsedFrame['framenameshort'])) {
532                         unset($parsedFrame['framenameshort']);
533                 }
534
535                 if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
536                         if ($id3v2_majorversion == 3) {
537                                 //    Frame Header Flags
538                                 //    %abc00000 %ijk00000
539                                 $parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
540                                 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
541                                 $parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
542                                 $parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
543                                 $parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
544                                 $parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
545
546                         } elseif ($id3v2_majorversion == 4) {
547                                 //    Frame Header Flags
548                                 //    %0abc0000 %0h00kmnp
549                                 $parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
550                                 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
551                                 $parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
552                                 $parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
553                                 $parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
554                                 $parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
555                                 $parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
556                                 $parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
557
558                                 // Frame-level de-unsynchronisation - ID3v2.4
559                                 if ($parsedFrame['flags']['Unsynchronisation']) {
560                                         $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
561                                 }
562
563                                 if ($parsedFrame['flags']['DataLengthIndicator']) {
564                                         $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
565                                         $parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
566                                 }
567                         }
568
569                         //    Frame-level de-compression
570                         if ($parsedFrame['flags']['compression']) {
571                                 $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
572                                 if (!function_exists('gzuncompress')) {
573                                         $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
574                                 } else {
575                                         if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
576                                         //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
577                                                 $parsedFrame['data'] = $decompresseddata;
578                                                 unset($decompresseddata);
579                                         } else {
580                                                 $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
581                                         }
582                                 }
583                         }
584                 }
585
586                 if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
587                         if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
588                                 $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
589                         }
590                 }
591
592                 if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
593
594                         $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
595                         switch ($parsedFrame['frame_name']) {
596                                 case 'WCOM':
597                                         $warning .= ' (this is known to happen with files tagged by RioPort)';
598                                         break;
599
600                                 default:
601                                         break;
602                         }
603                         $info['warning'][] = $warning;
604
605                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
606                         (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
607                         //   There may be more than one 'UFID' frame in a tag,
608                         //   but only one with the same 'Owner identifier'.
609                         // <Header for 'Unique file identifier', ID: 'UFID'>
610                         // Owner identifier        <text string> $00
611                         // Identifier              <up to 64 bytes binary data>
612                         $exploded = explode("\x00", $parsedFrame['data'], 2);
613                         $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
614                         $parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : '');
615
616                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
617                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
618                         //   There may be more than one 'TXXX' frame in each tag,
619                         //   but only one with the same description.
620                         // <Header for 'User defined text information frame', ID: 'TXXX'>
621                         // Text encoding     $xx
622                         // Description       <text string according to encoding> $00 (00)
623                         // Value             <text string according to encoding>
624
625                         $frame_offset = 0;
626                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
627
628                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
629                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
630                         }
631                         $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
632                         if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
633                                 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
634                         }
635                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
636                         if (ord($frame_description) === 0) {
637                                 $frame_description = '';
638                         }
639                         $parsedFrame['encodingid']  = $frame_textencoding;
640                         $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
641
642                         $parsedFrame['description'] = $frame_description;
643                         $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
644                         if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
645                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
646                         }
647                         //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
648
649
650                 } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
651                         //   There may only be one text information frame of its kind in an tag.
652                         // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
653                         // excluding 'TXXX' described in 4.2.6.>
654                         // Text encoding                $xx
655                         // Information                  <text string(s) according to encoding>
656
657                         $frame_offset = 0;
658                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
659                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
660                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
661                         }
662
663                         $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
664
665                         $parsedFrame['encodingid'] = $frame_textencoding;
666                         $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
667
668                         if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
669                                 // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
670                                 // This of course breaks when an artist name contains slash character, e.g. "AC/DC"
671                                 // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
672                                 // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
673                                 switch ($parsedFrame['encoding']) {
674                                         case 'UTF-16':
675                                         case 'UTF-16BE':
676                                         case 'UTF-16LE':
677                                                 $wordsize = 2;
678                                                 break;
679                                         case 'ISO-8859-1':
680                                         case 'UTF-8':
681                                         default:
682                                                 $wordsize = 1;
683                                                 break;
684                                 }
685                                 $Txxx_elements = array();
686                                 $Txxx_elements_start_offset = 0;
687                                 for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
688                                         if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
689                                                 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
690                                                 $Txxx_elements_start_offset = $i + $wordsize;
691                                         }
692                                 }
693                                 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
694                                 foreach ($Txxx_elements as $Txxx_element) {
695                                         $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
696                                         if (!empty($string)) {
697                                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
698                                         }
699                                 }
700                                 unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
701                         }
702
703                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
704                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
705                         //   There may be more than one 'WXXX' frame in each tag,
706                         //   but only one with the same description
707                         // <Header for 'User defined URL link frame', ID: 'WXXX'>
708                         // Text encoding     $xx
709                         // Description       <text string according to encoding> $00 (00)
710                         // URL               <text string>
711
712                         $frame_offset = 0;
713                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
714                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
715                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
716                         }
717                         $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
718                         if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
719                                 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
720                         }
721                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
722
723                         if (ord($frame_description) === 0) {
724                                 $frame_description = '';
725                         }
726                         $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
727
728                         $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
729                         if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
730                                 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
731                         }
732                         if ($frame_terminatorpos) {
733                                 // there are null bytes after the data - this is not according to spec
734                                 // only use data up to first null byte
735                                 $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
736                         } else {
737                                 // no null bytes following data, just use all data
738                                 $frame_urldata = (string) $parsedFrame['data'];
739                         }
740
741                         $parsedFrame['encodingid']  = $frame_textencoding;
742                         $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
743
744                         $parsedFrame['url']         = $frame_urldata;
745                         $parsedFrame['description'] = $frame_description;
746                         if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
747                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
748                         }
749                         unset($parsedFrame['data']);
750
751
752                 } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
753                         //   There may only be one URL link frame of its kind in a tag,
754                         //   except when stated otherwise in the frame description
755                         // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
756                         // described in 4.3.2.>
757                         // URL              <text string>
758
759                         $parsedFrame['url'] = trim($parsedFrame['data']);
760                         if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
761                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
762                         }
763                         unset($parsedFrame['data']);
764
765
766                 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
767                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
768                         // http://id3.org/id3v2.3.0#sec4.4
769                         //   There may only be one 'IPL' frame in each tag
770                         // <Header for 'User defined URL link frame', ID: 'IPL'>
771                         // Text encoding     $xx
772                         // People list strings    <textstrings>
773
774                         $frame_offset = 0;
775                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
776                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
777                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
778                         }
779                         $parsedFrame['encodingid'] = $frame_textencoding;
780                         $parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
781                         $parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
782
783                         // http://www.getid3.org/phpBB3/viewtopic.php?t=1369
784                         // "this tag typically contains null terminated strings, which are associated in pairs"
785                         // "there are users that use the tag incorrectly"
786                         $IPLS_parts = array();
787                         if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
788                                 $IPLS_parts_unsorted = array();
789                                 if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
790                                         // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
791                                         $thisILPS  = '';
792                                         for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
793                                                 $twobytes = substr($parsedFrame['data_raw'], $i, 2);
794                                                 if ($twobytes === "\x00\x00") {
795                                                         $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
796                                                         $thisILPS  = '';
797                                                 } else {
798                                                         $thisILPS .= $twobytes;
799                                                 }
800                                         }
801                                         if (strlen($thisILPS) > 2) { // 2-byte BOM
802                                                 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
803                                         }
804                                 } else {
805                                         // ISO-8859-1 or UTF-8 or other single-byte-null character set
806                                         $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
807                                 }
808                                 if (count($IPLS_parts_unsorted) == 1) {
809                                         // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
810                                         foreach ($IPLS_parts_unsorted as $key => $value) {
811                                                 $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
812                                                 $position = '';
813                                                 foreach ($IPLS_parts_sorted as $person) {
814                                                         $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
815                                                 }
816                                         }
817                                 } elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
818                                         $position = '';
819                                         $person   = '';
820                                         foreach ($IPLS_parts_unsorted as $key => $value) {
821                                                 if (($key % 2) == 0) {
822                                                         $position = $value;
823                                                 } else {
824                                                         $person   = $value;
825                                                         $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
826                                                         $position = '';
827                                                         $person   = '';
828                                                 }
829                                         }
830                                 } else {
831                                         foreach ($IPLS_parts_unsorted as $key => $value) {
832                                                 $IPLS_parts[] = array($value);
833                                         }
834                                 }
835
836                         } else {
837                                 $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
838                         }
839                         $parsedFrame['data'] = $IPLS_parts;
840
841                         if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
842                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
843                         }
844
845
846                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
847                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
848                         //   There may only be one 'MCDI' frame in each tag
849                         // <Header for 'Music CD identifier', ID: 'MCDI'>
850                         // CD TOC                <binary data>
851
852                         if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
853                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
854                         }
855
856
857                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
858                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
859                         //   There may only be one 'ETCO' frame in each tag
860                         // <Header for 'Event timing codes', ID: 'ETCO'>
861                         // Time stamp format    $xx
862                         //   Where time stamp format is:
863                         // $01  (32-bit value) MPEG frames from beginning of file
864                         // $02  (32-bit value) milliseconds from beginning of file
865                         //   Followed by a list of key events in the following format:
866                         // Type of event   $xx
867                         // Time stamp      $xx (xx ...)
868                         //   The 'Time stamp' is set to zero if directly at the beginning of the sound
869                         //   or after the previous event. All events MUST be sorted in chronological order.
870
871                         $frame_offset = 0;
872                         $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
873
874                         while ($frame_offset < strlen($parsedFrame['data'])) {
875                                 $parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1);
876                                 $parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']);
877                                 $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
878                                 $frame_offset += 4;
879                         }
880                         unset($parsedFrame['data']);
881
882
883                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
884                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
885                         //   There may only be one 'MLLT' frame in each tag
886                         // <Header for 'Location lookup table', ID: 'MLLT'>
887                         // MPEG frames between reference  $xx xx
888                         // Bytes between reference        $xx xx xx
889                         // Milliseconds between reference $xx xx xx
890                         // Bits for bytes deviation       $xx
891                         // Bits for milliseconds dev.     $xx
892                         //   Then for every reference the following data is included;
893                         // Deviation in bytes         %xxx....
894                         // Deviation in milliseconds  %xxx....
895
896                         $frame_offset = 0;
897                         $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
898                         $parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
899                         $parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
900                         $parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
901                         $parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
902                         $parsedFrame['data'] = substr($parsedFrame['data'], 10);
903                         while ($frame_offset < strlen($parsedFrame['data'])) {
904                                 $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
905                         }
906                         $reference_counter = 0;
907                         while (strlen($deviationbitstream) > 0) {
908                                 $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
909                                 $parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
910                                 $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
911                                 $reference_counter++;
912                         }
913                         unset($parsedFrame['data']);
914
915
916                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
917                                   (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
918                         //   There may only be one 'SYTC' frame in each tag
919                         // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
920                         // Time stamp format   $xx
921                         // Tempo data          <binary data>
922                         //   Where time stamp format is:
923                         // $01  (32-bit value) MPEG frames from beginning of file
924                         // $02  (32-bit value) milliseconds from beginning of file
925
926                         $frame_offset = 0;
927                         $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
928                         $timestamp_counter = 0;
929                         while ($frame_offset < strlen($parsedFrame['data'])) {
930                                 $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
931                                 if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
932                                         $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
933                                 }
934                                 $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
935                                 $frame_offset += 4;
936                                 $timestamp_counter++;
937                         }
938                         unset($parsedFrame['data']);
939
940
941                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
942                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
943                         //   There may be more than one 'Unsynchronised lyrics/text transcription' frame
944                         //   in each tag, but only one with the same language and content descriptor.
945                         // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
946                         // Text encoding        $xx
947                         // Language             $xx xx xx
948                         // Content descriptor   <text string according to encoding> $00 (00)
949                         // Lyrics/text          <full text string according to encoding>
950
951                         $frame_offset = 0;
952                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
953                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
954                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
955                         }
956                         $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
957                         $frame_offset += 3;
958                         $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
959                         if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
960                                 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
961                         }
962                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
963                         if (ord($frame_description) === 0) {
964                                 $frame_description = '';
965                         }
966                         $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
967
968                         $parsedFrame['encodingid']   = $frame_textencoding;
969                         $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
970
971                         $parsedFrame['data']         = $parsedFrame['data'];
972                         $parsedFrame['language']     = $frame_language;
973                         $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
974                         $parsedFrame['description']  = $frame_description;
975                         if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
976                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
977                         }
978                         unset($parsedFrame['data']);
979
980
981                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
982                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
983                         //   There may be more than one 'SYLT' frame in each tag,
984                         //   but only one with the same language and content descriptor.
985                         // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
986                         // Text encoding        $xx
987                         // Language             $xx xx xx
988                         // Time stamp format    $xx
989                         //   $01  (32-bit value) MPEG frames from beginning of file
990                         //   $02  (32-bit value) milliseconds from beginning of file
991                         // Content type         $xx
992                         // Content descriptor   <text string according to encoding> $00 (00)
993                         //   Terminated text to be synced (typically a syllable)
994                         //   Sync identifier (terminator to above string)   $00 (00)
995                         //   Time stamp                                     $xx (xx ...)
996
997                         $frame_offset = 0;
998                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
999                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1000                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1001                         }
1002                         $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1003                         $frame_offset += 3;
1004                         $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1005                         $parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1006                         $parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1007                         $parsedFrame['encodingid']      = $frame_textencoding;
1008                         $parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding);
1009
1010                         $parsedFrame['language']        = $frame_language;
1011                         $parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);
1012
1013                         $timestampindex = 0;
1014                         $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1015                         while (strlen($frame_remainingdata)) {
1016                                 $frame_offset = 0;
1017                                 $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding));
1018                                 if ($frame_terminatorpos === false) {
1019                                         $frame_remainingdata = '';
1020                                 } else {
1021                                         if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1022                                                 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1023                                         }
1024                                         $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1025
1026                                         $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1027                                         if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
1028                                                 // timestamp probably omitted for first data item
1029                                         } else {
1030                                                 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1031                                                 $frame_remainingdata = substr($frame_remainingdata, 4);
1032                                         }
1033                                         $timestampindex++;
1034                                 }
1035                         }
1036                         unset($parsedFrame['data']);
1037
1038
1039                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
1040                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
1041                         //   There may be more than one comment frame in each tag,
1042                         //   but only one with the same language and content descriptor.
1043                         // <Header for 'Comment', ID: 'COMM'>
1044                         // Text encoding          $xx
1045                         // Language               $xx xx xx
1046                         // Short content descrip. <text string according to encoding> $00 (00)
1047                         // The actual text        <full text string according to encoding>
1048
1049                         if (strlen($parsedFrame['data']) < 5) {
1050
1051                                 $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
1052
1053                         } else {
1054
1055                                 $frame_offset = 0;
1056                                 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1057                                 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1058                                         $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1059                                 }
1060                                 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1061                                 $frame_offset += 3;
1062                                 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1063                                 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1064                                         $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1065                                 }
1066                                 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1067                                 if (ord($frame_description) === 0) {
1068                                         $frame_description = '';
1069                                 }
1070                                 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1071
1072                                 $parsedFrame['encodingid']   = $frame_textencoding;
1073                                 $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1074
1075                                 $parsedFrame['language']     = $frame_language;
1076                                 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1077                                 $parsedFrame['description']  = $frame_description;
1078                                 $parsedFrame['data']         = $frame_text;
1079                                 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1080                                         $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1081                                 }
1082
1083                         }
1084
1085                 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1086                         //   There may be more than one 'RVA2' frame in each tag,
1087                         //   but only one with the same identification string
1088                         // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1089                         // Identification          <text string> $00
1090                         //   The 'identification' string is used to identify the situation and/or
1091                         //   device where this adjustment should apply. The following is then
1092                         //   repeated for every channel:
1093                         // Type of channel         $xx
1094                         // Volume adjustment       $xx xx
1095                         // Bits representing peak  $xx
1096                         // Peak volume             $xx (xx ...)
1097
1098                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1099                         $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1100                         if (ord($frame_idstring) === 0) {
1101                                 $frame_idstring = '';
1102                         }
1103                         $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1104                         $parsedFrame['description'] = $frame_idstring;
1105                         $RVA2channelcounter = 0;
1106                         while (strlen($frame_remainingdata) >= 5) {
1107                                 $frame_offset = 0;
1108                                 $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1109                                 $parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid;
1110                                 $parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1111                                 $parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1112                                 $frame_offset += 2;
1113                                 $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1114                                 if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1115                                         $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
1116                                         break;
1117                                 }
1118                                 $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1119                                 $parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1120                                 $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1121                                 $RVA2channelcounter++;
1122                         }
1123                         unset($parsedFrame['data']);
1124
1125
1126                 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
1127                                   (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
1128                         //   There may only be one 'RVA' frame in each tag
1129                         // <Header for 'Relative volume adjustment', ID: 'RVA'>
1130                         // ID3v2.2 => Increment/decrement     %000000ba
1131                         // ID3v2.3 => Increment/decrement     %00fedcba
1132                         // Bits used for volume descr.        $xx
1133                         // Relative volume change, right      $xx xx (xx ...) // a
1134                         // Relative volume change, left       $xx xx (xx ...) // b
1135                         // Peak volume right                  $xx xx (xx ...)
1136                         // Peak volume left                   $xx xx (xx ...)
1137                         //   ID3v2.3 only, optional (not present in ID3v2.2):
1138                         // Relative volume change, right back $xx xx (xx ...) // c
1139                         // Relative volume change, left back  $xx xx (xx ...) // d
1140                         // Peak volume right back             $xx xx (xx ...)
1141                         // Peak volume left back              $xx xx (xx ...)
1142                         //   ID3v2.3 only, optional (not present in ID3v2.2):
1143                         // Relative volume change, center     $xx xx (xx ...) // e
1144                         // Peak volume center                 $xx xx (xx ...)
1145                         //   ID3v2.3 only, optional (not present in ID3v2.2):
1146                         // Relative volume change, bass       $xx xx (xx ...) // f
1147                         // Peak volume bass                   $xx xx (xx ...)
1148
1149                         $frame_offset = 0;
1150                         $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1151                         $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1152                         $parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1);
1153                         $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1154                         $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1155                         $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1156                         if ($parsedFrame['incdec']['right'] === false) {
1157                                 $parsedFrame['volumechange']['right'] *= -1;
1158                         }
1159                         $frame_offset += $frame_bytesvolume;
1160                         $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1161                         if ($parsedFrame['incdec']['left'] === false) {
1162                                 $parsedFrame['volumechange']['left'] *= -1;
1163                         }
1164                         $frame_offset += $frame_bytesvolume;
1165                         $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1166                         $frame_offset += $frame_bytesvolume;
1167                         $parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1168                         $frame_offset += $frame_bytesvolume;
1169                         if ($id3v2_majorversion == 3) {
1170                                 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1171                                 if (strlen($parsedFrame['data']) > 0) {
1172                                         $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1173                                         $parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1);
1174                                         $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1175                                         if ($parsedFrame['incdec']['rightrear'] === false) {
1176                                                 $parsedFrame['volumechange']['rightrear'] *= -1;
1177                                         }
1178                                         $frame_offset += $frame_bytesvolume;
1179                                         $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1180                                         if ($parsedFrame['incdec']['leftrear'] === false) {
1181                                                 $parsedFrame['volumechange']['leftrear'] *= -1;
1182                                         }
1183                                         $frame_offset += $frame_bytesvolume;
1184                                         $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1185                                         $frame_offset += $frame_bytesvolume;
1186                                         $parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1187                                         $frame_offset += $frame_bytesvolume;
1188                                 }
1189                                 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1190                                 if (strlen($parsedFrame['data']) > 0) {
1191                                         $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1192                                         $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1193                                         if ($parsedFrame['incdec']['center'] === false) {
1194                                                 $parsedFrame['volumechange']['center'] *= -1;
1195                                         }
1196                                         $frame_offset += $frame_bytesvolume;
1197                                         $parsedFrame['peakvolume']['center'] = 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']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1203                                         $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1204                                         if ($parsedFrame['incdec']['bass'] === false) {
1205                                                 $parsedFrame['volumechange']['bass'] *= -1;
1206                                         }
1207                                         $frame_offset += $frame_bytesvolume;
1208                                         $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1209                                         $frame_offset += $frame_bytesvolume;
1210                                 }
1211                         }
1212                         unset($parsedFrame['data']);
1213
1214
1215                 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
1216                         //   There may be more than one 'EQU2' frame in each tag,
1217                         //   but only one with the same identification string
1218                         // <Header of 'Equalisation (2)', ID: 'EQU2'>
1219                         // Interpolation method  $xx
1220                         //   $00  Band
1221                         //   $01  Linear
1222                         // Identification        <text string> $00
1223                         //   The following is then repeated for every adjustment point
1224                         // Frequency          $xx xx
1225                         // Volume adjustment  $xx xx
1226
1227                         $frame_offset = 0;
1228                         $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1229                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1230                         $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1231                         if (ord($frame_idstring) === 0) {
1232                                 $frame_idstring = '';
1233                         }
1234                         $parsedFrame['description'] = $frame_idstring;
1235                         $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1236                         while (strlen($frame_remainingdata)) {
1237                                 $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1238                                 $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1239                                 $frame_remainingdata = substr($frame_remainingdata, 4);
1240                         }
1241                         $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1242                         unset($parsedFrame['data']);
1243
1244
1245                 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
1246                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
1247                         //   There may only be one 'EQUA' frame in each tag
1248                         // <Header for 'Relative volume adjustment', ID: 'EQU'>
1249                         // Adjustment bits    $xx
1250                         //   This is followed by 2 bytes + ('adjustment bits' rounded up to the
1251                         //   nearest byte) for every equalisation band in the following format,
1252                         //   giving a frequency range of 0 - 32767Hz:
1253                         // Increment/decrement   %x (MSB of the Frequency)
1254                         // Frequency             (lower 15 bits)
1255                         // Adjustment            $xx (xx ...)
1256
1257                         $frame_offset = 0;
1258                         $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1259                         $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1260
1261                         $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1262                         while (strlen($frame_remainingdata) > 0) {
1263                                 $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1264                                 $frame_incdec    = (bool) substr($frame_frequencystr, 0, 1);
1265                                 $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1266                                 $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1267                                 $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1268                                 if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1269                                         $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1270                                 }
1271                                 $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1272                         }
1273                         unset($parsedFrame['data']);
1274
1275
1276                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
1277                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
1278                         //   There may only be one 'RVRB' frame in each tag.
1279                         // <Header for 'Reverb', ID: 'RVRB'>
1280                         // Reverb left (ms)                 $xx xx
1281                         // Reverb right (ms)                $xx xx
1282                         // Reverb bounces, left             $xx
1283                         // Reverb bounces, right            $xx
1284                         // Reverb feedback, left to left    $xx
1285                         // Reverb feedback, left to right   $xx
1286                         // Reverb feedback, right to right  $xx
1287                         // Reverb feedback, right to left   $xx
1288                         // Premix left to right             $xx
1289                         // Premix right to left             $xx
1290
1291                         $frame_offset = 0;
1292                         $parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1293                         $frame_offset += 2;
1294                         $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1295                         $frame_offset += 2;
1296                         $parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1297                         $parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1298                         $parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1299                         $parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1300                         $parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1301                         $parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1302                         $parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1303                         $parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1304                         unset($parsedFrame['data']);
1305
1306
1307                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
1308                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
1309                         //   There may be several pictures attached to one file,
1310                         //   each in their individual 'APIC' frame, but only one
1311                         //   with the same content descriptor
1312                         // <Header for 'Attached picture', ID: 'APIC'>
1313                         // Text encoding      $xx
1314                         // ID3v2.3+ => MIME type          <text string> $00
1315                         // ID3v2.2  => Image format       $xx xx xx
1316                         // Picture type       $xx
1317                         // Description        <text string according to encoding> $00 (00)
1318                         // Picture data       <binary data>
1319
1320                         $frame_offset = 0;
1321                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1322                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1323                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1324                         }
1325
1326                         if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1327                                 $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1328                                 if (strtolower($frame_imagetype) == 'ima') {
1329                                         // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1330                                         // MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
1331                                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1332                                         $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1333                                         if (ord($frame_mimetype) === 0) {
1334                                                 $frame_mimetype = '';
1335                                         }
1336                                         $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1337                                         if ($frame_imagetype == 'JPEG') {
1338                                                 $frame_imagetype = 'JPG';
1339                                         }
1340                                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1341                                 } else {
1342                                         $frame_offset += 3;
1343                                 }
1344                         }
1345                         if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1346                                 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1347                                 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1348                                 if (ord($frame_mimetype) === 0) {
1349                                         $frame_mimetype = '';
1350                                 }
1351                                 $frame_offset = $frame_terminatorpos + strlen("\x00");
1352                         }
1353
1354                         $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1355
1356                         if ($frame_offset >= $parsedFrame['datalength']) {
1357                                 $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
1358                         } else {
1359                                 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1360                                 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1361                                         $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1362                                 }
1363                                 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1364                                 if (ord($frame_description) === 0) {
1365                                         $frame_description = '';
1366                                 }
1367                                 $parsedFrame['encodingid']       = $frame_textencoding;
1368                                 $parsedFrame['encoding']         = $this->TextEncodingNameLookup($frame_textencoding);
1369
1370                                 if ($id3v2_majorversion == 2) {
1371                                         $parsedFrame['imagetype']    = $frame_imagetype;
1372                                 } else {
1373                                         $parsedFrame['mime']         = $frame_mimetype;
1374                                 }
1375                                 $parsedFrame['picturetypeid']    = $frame_picturetype;
1376                                 $parsedFrame['picturetype']      = $this->APICPictureTypeLookup($frame_picturetype);
1377                                 $parsedFrame['description']      = $frame_description;
1378                                 $parsedFrame['data']             = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1379                                 $parsedFrame['datalength']       = strlen($parsedFrame['data']);
1380
1381                                 $parsedFrame['image_mime'] = '';
1382                                 $imageinfo = array();
1383                                 $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
1384                                 if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1385                                         $parsedFrame['image_mime']       = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
1386                                         if ($imagechunkcheck[0]) {
1387                                                 $parsedFrame['image_width']  = $imagechunkcheck[0];
1388                                         }
1389                                         if ($imagechunkcheck[1]) {
1390                                                 $parsedFrame['image_height'] = $imagechunkcheck[1];
1391                                         }
1392                                 }
1393
1394                                 do {
1395                                         if ($this->getid3->option_save_attachments === false) {
1396                                                 // skip entirely
1397                                                 unset($parsedFrame['data']);
1398                                                 break;
1399                                         }
1400                                         if ($this->getid3->option_save_attachments === true) {
1401                                                 // great
1402 /*
1403                                         } elseif (is_int($this->getid3->option_save_attachments)) {
1404                                                 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1405                                                         // too big, skip
1406                                                         $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
1407                                                         unset($parsedFrame['data']);
1408                                                         break;
1409                                                 }
1410 */
1411                                         } elseif (is_string($this->getid3->option_save_attachments)) {
1412                                                 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1413                                                 if (!is_dir($dir) || !is_writable($dir)) {
1414                                                         // cannot write, skip
1415                                                         $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)';
1416                                                         unset($parsedFrame['data']);
1417                                                         break;
1418                                                 }
1419                                         }
1420                                         // if we get this far, must be OK
1421                                         if (is_string($this->getid3->option_save_attachments)) {
1422                                                 $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1423                                                 if (!file_exists($destination_filename) || is_writable($destination_filename)) {
1424                                                         file_put_contents($destination_filename, $parsedFrame['data']);
1425                                                 } else {
1426                                                         $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
1427                                                 }
1428                                                 $parsedFrame['data_filename'] = $destination_filename;
1429                                                 unset($parsedFrame['data']);
1430                                         } else {
1431                                                 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1432                                                         if (!isset($info['id3v2']['comments']['picture'])) {
1433                                                                 $info['id3v2']['comments']['picture'] = array();
1434                                                         }
1435                                                         $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']);
1436                                                 }
1437                                         }
1438                                 } while (false);
1439                         }
1440
1441                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
1442                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
1443                         //   There may be more than one 'GEOB' frame in each tag,
1444                         //   but only one with the same content descriptor
1445                         // <Header for 'General encapsulated object', ID: 'GEOB'>
1446                         // Text encoding          $xx
1447                         // MIME type              <text string> $00
1448                         // Filename               <text string according to encoding> $00 (00)
1449                         // Content description    <text string according to encoding> $00 (00)
1450                         // Encapsulated object    <binary data>
1451
1452                         $frame_offset = 0;
1453                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1454                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1455                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1456                         }
1457                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1458                         $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1459                         if (ord($frame_mimetype) === 0) {
1460                                 $frame_mimetype = '';
1461                         }
1462                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1463
1464                         $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1465                         if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1466                                 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1467                         }
1468                         $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1469                         if (ord($frame_filename) === 0) {
1470                                 $frame_filename = '';
1471                         }
1472                         $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1473
1474                         $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1475                         if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1476                                 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1477                         }
1478                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1479                         if (ord($frame_description) === 0) {
1480                                 $frame_description = '';
1481                         }
1482                         $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1483
1484                         $parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
1485                         $parsedFrame['encodingid']  = $frame_textencoding;
1486                         $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
1487
1488                         $parsedFrame['mime']        = $frame_mimetype;
1489                         $parsedFrame['filename']    = $frame_filename;
1490                         $parsedFrame['description'] = $frame_description;
1491                         unset($parsedFrame['data']);
1492
1493
1494                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
1495                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
1496                         //   There may only be one 'PCNT' frame in each tag.
1497                         //   When the counter reaches all one's, one byte is inserted in
1498                         //   front of the counter thus making the counter eight bits bigger
1499                         // <Header for 'Play counter', ID: 'PCNT'>
1500                         // Counter        $xx xx xx xx (xx ...)
1501
1502                         $parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);
1503
1504
1505                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
1506                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
1507                         //   There may be more than one 'POPM' frame in each tag,
1508                         //   but only one with the same email address
1509                         // <Header for 'Popularimeter', ID: 'POPM'>
1510                         // Email to user   <text string> $00
1511                         // Rating          $xx
1512                         // Counter         $xx xx xx xx (xx ...)
1513
1514                         $frame_offset = 0;
1515                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1516                         $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1517                         if (ord($frame_emailaddress) === 0) {
1518                                 $frame_emailaddress = '';
1519                         }
1520                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1521                         $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1522                         $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1523                         $parsedFrame['email']   = $frame_emailaddress;
1524                         $parsedFrame['rating']  = $frame_rating;
1525                         unset($parsedFrame['data']);
1526
1527
1528                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
1529                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
1530                         //   There may only be one 'RBUF' frame in each tag
1531                         // <Header for 'Recommended buffer size', ID: 'RBUF'>
1532                         // Buffer size               $xx xx xx
1533                         // Embedded info flag        %0000000x
1534                         // Offset to next tag        $xx xx xx xx
1535
1536                         $frame_offset = 0;
1537                         $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1538                         $frame_offset += 3;
1539
1540                         $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1541                         $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1542                         $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1543                         unset($parsedFrame['data']);
1544
1545
1546                 } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
1547                         //   There may be more than one 'CRM' frame in a tag,
1548                         //   but only one with the same 'owner identifier'
1549                         // <Header for 'Encrypted meta frame', ID: 'CRM'>
1550                         // Owner identifier      <textstring> $00 (00)
1551                         // Content/explanation   <textstring> $00 (00)
1552                         // Encrypted datablock   <binary data>
1553
1554                         $frame_offset = 0;
1555                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1556                         $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1557                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1558
1559                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1560                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1561                         if (ord($frame_description) === 0) {
1562                                 $frame_description = '';
1563                         }
1564                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1565
1566                         $parsedFrame['ownerid']     = $frame_ownerid;
1567                         $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1568                         $parsedFrame['description'] = $frame_description;
1569                         unset($parsedFrame['data']);
1570
1571
1572                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
1573                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
1574                         //   There may be more than one 'AENC' frames in a tag,
1575                         //   but only one with the same 'Owner identifier'
1576                         // <Header for 'Audio encryption', ID: 'AENC'>
1577                         // Owner identifier   <text string> $00
1578                         // Preview start      $xx xx
1579                         // Preview length     $xx xx
1580                         // Encryption info    <binary data>
1581
1582                         $frame_offset = 0;
1583                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1584                         $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1585                         if (ord($frame_ownerid) === 0) {
1586                                 $frame_ownerid == '';
1587                         }
1588                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1589                         $parsedFrame['ownerid'] = $frame_ownerid;
1590                         $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1591                         $frame_offset += 2;
1592                         $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1593                         $frame_offset += 2;
1594                         $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1595                         unset($parsedFrame['data']);
1596
1597
1598                 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
1599                                 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {     // 4.22  LNK  Linked information
1600                         //   There may be more than one 'LINK' frame in a tag,
1601                         //   but only one with the same contents
1602                         // <Header for 'Linked information', ID: 'LINK'>
1603                         // ID3v2.3+ => Frame identifier   $xx xx xx xx
1604                         // ID3v2.2  => Frame identifier   $xx xx xx
1605                         // URL                            <text string> $00
1606                         // ID and additional data         <text string(s)>
1607
1608                         $frame_offset = 0;
1609                         if ($id3v2_majorversion == 2) {
1610                                 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1611                                 $frame_offset += 3;
1612                         } else {
1613                                 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1614                                 $frame_offset += 4;
1615                         }
1616
1617                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1618                         $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1619                         if (ord($frame_url) === 0) {
1620                                 $frame_url = '';
1621                         }
1622                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1623                         $parsedFrame['url'] = $frame_url;
1624
1625                         $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1626                         if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1627                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']);
1628                         }
1629                         unset($parsedFrame['data']);
1630
1631
1632                 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
1633                         //   There may only be one 'POSS' frame in each tag
1634                         // <Head for 'Position synchronisation', ID: 'POSS'>
1635                         // Time stamp format         $xx
1636                         // Position                  $xx (xx ...)
1637
1638                         $frame_offset = 0;
1639                         $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1640                         $parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1641                         unset($parsedFrame['data']);
1642
1643
1644                 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
1645                         //   There may be more than one 'Terms of use' frame in a tag,
1646                         //   but only one with the same 'Language'
1647                         // <Header for 'Terms of use frame', ID: 'USER'>
1648                         // Text encoding        $xx
1649                         // Language             $xx xx xx
1650                         // The actual text      <text string according to encoding>
1651
1652                         $frame_offset = 0;
1653                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1654                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1655                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1656                         }
1657                         $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1658                         $frame_offset += 3;
1659                         $parsedFrame['language']     = $frame_language;
1660                         $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1661                         $parsedFrame['encodingid']   = $frame_textencoding;
1662                         $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1663
1664                         $parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
1665                         if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1666                                 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1667                         }
1668                         unset($parsedFrame['data']);
1669
1670
1671                 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
1672                         //   There may only be one 'OWNE' frame in a tag
1673                         // <Header for 'Ownership frame', ID: 'OWNE'>
1674                         // Text encoding     $xx
1675                         // Price paid        <text string> $00
1676                         // Date of purch.    <text string>
1677                         // Seller            <text string according to encoding>
1678
1679                         $frame_offset = 0;
1680                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1681                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1682                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1683                         }
1684                         $parsedFrame['encodingid'] = $frame_textencoding;
1685                         $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
1686
1687                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1688                         $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1689                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1690
1691                         $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1692                         $parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1693                         $parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);
1694
1695                         $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1696                         if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1697                                 $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1698                         }
1699                         $frame_offset += 8;
1700
1701                         $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1702                         unset($parsedFrame['data']);
1703
1704
1705                 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
1706                         //   There may be more than one 'commercial frame' in a tag,
1707                         //   but no two may be identical
1708                         // <Header for 'Commercial frame', ID: 'COMR'>
1709                         // Text encoding      $xx
1710                         // Price string       <text string> $00
1711                         // Valid until        <text string>
1712                         // Contact URL        <text string> $00
1713                         // Received as        $xx
1714                         // Name of seller     <text string according to encoding> $00 (00)
1715                         // Description        <text string according to encoding> $00 (00)
1716                         // Picture MIME type  <string> $00
1717                         // Seller logo        <binary data>
1718
1719                         $frame_offset = 0;
1720                         $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1721                         if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1722                                 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1723                         }
1724
1725                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1726                         $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1727                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1728                         $frame_rawpricearray = explode('/', $frame_pricestring);
1729                         foreach ($frame_rawpricearray as $key => $val) {
1730                                 $frame_currencyid = substr($val, 0, 3);
1731                                 $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1732                                 $parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3);
1733                         }
1734
1735                         $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1736                         $frame_offset += 8;
1737
1738                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1739                         $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1740                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1741
1742                         $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1743
1744                         $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1745                         if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1746                                 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1747                         }
1748                         $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1749                         if (ord($frame_sellername) === 0) {
1750                                 $frame_sellername = '';
1751                         }
1752                         $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1753
1754                         $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1755                         if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1756                                 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1757                         }
1758                         $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1759                         if (ord($frame_description) === 0) {
1760                                 $frame_description = '';
1761                         }
1762                         $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1763
1764                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1765                         $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1766                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1767
1768                         $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1769
1770                         $parsedFrame['encodingid']        = $frame_textencoding;
1771                         $parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);
1772
1773                         $parsedFrame['pricevaliduntil']   = $frame_datestring;
1774                         $parsedFrame['contacturl']        = $frame_contacturl;
1775                         $parsedFrame['receivedasid']      = $frame_receivedasid;
1776                         $parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
1777                         $parsedFrame['sellername']        = $frame_sellername;
1778                         $parsedFrame['description']       = $frame_description;
1779                         $parsedFrame['mime']              = $frame_mimetype;
1780                         $parsedFrame['logo']              = $frame_sellerlogo;
1781                         unset($parsedFrame['data']);
1782
1783
1784                 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
1785                         //   There may be several 'ENCR' frames in a tag,
1786                         //   but only one containing the same symbol
1787                         //   and only one containing the same owner identifier
1788                         // <Header for 'Encryption method registration', ID: 'ENCR'>
1789                         // Owner identifier    <text string> $00
1790                         // Method symbol       $xx
1791                         // Encryption data     <binary data>
1792
1793                         $frame_offset = 0;
1794                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1795                         $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1796                         if (ord($frame_ownerid) === 0) {
1797                                 $frame_ownerid = '';
1798                         }
1799                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1800
1801                         $parsedFrame['ownerid']      = $frame_ownerid;
1802                         $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1803                         $parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
1804
1805
1806                 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)
1807
1808                         //   There may be several 'GRID' frames in a tag,
1809                         //   but only one containing the same symbol
1810                         //   and only one containing the same owner identifier
1811                         // <Header for 'Group ID registration', ID: 'GRID'>
1812                         // Owner identifier      <text string> $00
1813                         // Group symbol          $xx
1814                         // Group dependent data  <binary data>
1815
1816                         $frame_offset = 0;
1817                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1818                         $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1819                         if (ord($frame_ownerid) === 0) {
1820                                 $frame_ownerid = '';
1821                         }
1822                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1823
1824                         $parsedFrame['ownerid']       = $frame_ownerid;
1825                         $parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1826                         $parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);
1827
1828
1829                 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
1830                         //   The tag may contain more than one 'PRIV' frame
1831                         //   but only with different contents
1832                         // <Header for 'Private frame', ID: 'PRIV'>
1833                         // Owner identifier      <text string> $00
1834                         // The private data      <binary data>
1835
1836                         $frame_offset = 0;
1837                         $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1838                         $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1839                         if (ord($frame_ownerid) === 0) {
1840                                 $frame_ownerid = '';
1841                         }
1842                         $frame_offset = $frame_terminatorpos + strlen("\x00");
1843
1844                         $parsedFrame['ownerid'] = $frame_ownerid;
1845                         $parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);
1846
1847
1848                 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
1849                         //   There may be more than one 'signature frame' in a tag,
1850                         //   but no two may be identical
1851                         // <Header for 'Signature frame', ID: 'SIGN'>
1852                         // Group symbol      $xx
1853                         // Signature         <binary data>
1854
1855                         $frame_offset = 0;
1856                         $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1857                         $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1858
1859
1860                 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
1861                         //   There may only be one 'seek frame' in a tag
1862                         // <Header for 'Seek frame', ID: 'SEEK'>
1863                         // Minimum offset to next tag       $xx xx xx xx
1864
1865                         $frame_offset = 0;
1866                         $parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1867
1868
1869                 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
1870                         //   There may only be one 'audio seek point index' frame in a tag
1871                         // <Header for 'Seek Point Index', ID: 'ASPI'>
1872                         // Indexed data start (S)         $xx xx xx xx
1873                         // Indexed data length (L)        $xx xx xx xx
1874                         // Number of index points (N)     $xx xx
1875                         // Bits per index point (b)       $xx
1876                         //   Then for every index point the following data is included:
1877                         // Fraction at index (Fi)          $xx (xx)
1878
1879                         $frame_offset = 0;
1880                         $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1881                         $frame_offset += 4;
1882                         $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1883                         $frame_offset += 4;
1884                         $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1885                         $frame_offset += 2;
1886                         $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1887                         $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1888                         for ($i = 0; $i < $frame_indexpoints; $i++) {
1889                                 $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1890                                 $frame_offset += $frame_bytesperpoint;
1891                         }
1892                         unset($parsedFrame['data']);
1893
1894                 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1895                         // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1896                         //   There may only be one 'RGAD' frame in a tag
1897                         // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1898                         // Peak Amplitude                      $xx $xx $xx $xx
1899                         // Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
1900                         // Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
1901                         //   a - name code
1902                         //   b - originator code
1903                         //   c - sign bit
1904                         //   d - replay gain adjustment
1905
1906                         $frame_offset = 0;
1907                         $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1908                         $frame_offset += 4;
1909                         $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1910                         $frame_offset += 2;
1911                         $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1912                         $frame_offset += 2;
1913                         $parsedFrame['raw']['track']['name']       = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1914                         $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1915                         $parsedFrame['raw']['track']['signbit']    = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1916                         $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1917                         $parsedFrame['raw']['album']['name']       = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1918                         $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1919                         $parsedFrame['raw']['album']['signbit']    = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
1920                         $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
1921                         $parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1922                         $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1923                         $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1924                         $parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1925                         $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1926                         $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1927
1928                         $info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude'];
1929                         $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1930                         $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1931                         $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1932                         $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1933
1934                         unset($parsedFrame['data']);
1935
1936                 }
1937
1938                 return true;
1939         }
1940
1941
1942         public function DeUnsynchronise($data) {
1943                 return str_replace("\xFF\x00", "\xFF", $data);
1944         }
1945
1946         public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
1947                 static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
1948                         0x00 => 'No more than 128 frames and 1 MB total tag size',
1949                         0x01 => 'No more than 64 frames and 128 KB total tag size',
1950                         0x02 => 'No more than 32 frames and 40 KB total tag size',
1951                         0x03 => 'No more than 32 frames and 4 KB total tag size',
1952                 );
1953                 return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
1954         }
1955
1956         public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
1957                 static $LookupExtendedHeaderRestrictionsTextEncodings = array(
1958                         0x00 => 'No restrictions',
1959                         0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
1960                 );
1961                 return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
1962         }
1963
1964         public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
1965                 static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
1966                         0x00 => 'No restrictions',
1967                         0x01 => 'No string is longer than 1024 characters',
1968                         0x02 => 'No string is longer than 128 characters',
1969                         0x03 => 'No string is longer than 30 characters',
1970                 );
1971                 return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
1972         }
1973
1974         public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
1975                 static $LookupExtendedHeaderRestrictionsImageEncoding = array(
1976                         0x00 => 'No restrictions',
1977                         0x01 => 'Images are encoded only with PNG or JPEG',
1978                 );
1979                 return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
1980         }
1981
1982         public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
1983                 static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
1984                         0x00 => 'No restrictions',
1985                         0x01 => 'All images are 256x256 pixels or smaller',
1986                         0x02 => 'All images are 64x64 pixels or smaller',
1987                         0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
1988                 );
1989                 return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
1990         }
1991
1992         public function LookupCurrencyUnits($currencyid) {
1993
1994                 $begin = __LINE__;
1995
1996                 /** This is not a comment!
1997
1998
1999                         AED     Dirhams
2000                         AFA     Afghanis
2001                         ALL     Leke
2002                         AMD     Drams
2003                         ANG     Guilders
2004                         AOA     Kwanza
2005                         ARS     Pesos
2006                         ATS     Schillings
2007                         AUD     Dollars
2008                         AWG     Guilders
2009                         AZM     Manats
2010                         BAM     Convertible Marka
2011                         BBD     Dollars
2012                         BDT     Taka
2013                         BEF     Francs
2014                         BGL     Leva
2015                         BHD     Dinars
2016                         BIF     Francs
2017                         BMD     Dollars
2018                         BND     Dollars
2019                         BOB     Bolivianos
2020                         BRL     Brazil Real
2021                         BSD     Dollars
2022                         BTN     Ngultrum
2023                         BWP     Pulas
2024                         BYR     Rubles
2025                         BZD     Dollars
2026                         CAD     Dollars
2027                         CDF     Congolese Francs
2028                         CHF     Francs
2029                         CLP     Pesos
2030                         CNY     Yuan Renminbi
2031                         COP     Pesos
2032                         CRC     Colones
2033                         CUP     Pesos
2034                         CVE     Escudos
2035                         CYP     Pounds
2036                         CZK     Koruny
2037                         DEM     Deutsche Marks
2038                         DJF     Francs
2039                         DKK     Kroner
2040                         DOP     Pesos
2041                         DZD     Algeria Dinars
2042                         EEK     Krooni
2043                         EGP     Pounds
2044                         ERN     Nakfa
2045                         ESP     Pesetas
2046                         ETB     Birr
2047                         EUR     Euro
2048                         FIM     Markkaa
2049                         FJD     Dollars
2050                         FKP     Pounds
2051                         FRF     Francs
2052                         GBP     Pounds
2053                         GEL     Lari
2054                         GGP     Pounds
2055                         GHC     Cedis
2056                         GIP     Pounds
2057                         GMD     Dalasi
2058                         GNF     Francs
2059                         GRD     Drachmae
2060                         GTQ     Quetzales
2061                         GYD     Dollars
2062                         HKD     Dollars
2063                         HNL     Lempiras
2064                         HRK     Kuna
2065                         HTG     Gourdes
2066                         HUF     Forints
2067                         IDR     Rupiahs
2068                         IEP     Pounds
2069                         ILS     New Shekels
2070                         IMP     Pounds
2071                         INR     Rupees
2072                         IQD     Dinars
2073                         IRR     Rials
2074                         ISK     Kronur
2075                         ITL     Lire
2076                         JEP     Pounds
2077                         JMD     Dollars
2078                         JOD     Dinars
2079                         JPY     Yen
2080                         KES     Shillings
2081                         KGS     Soms
2082                         KHR     Riels
2083                         KMF     Francs
2084                         KPW     Won
2085                         KWD     Dinars
2086                         KYD     Dollars
2087                         KZT     Tenge
2088                         LAK     Kips
2089                         LBP     Pounds
2090                         LKR     Rupees
2091                         LRD     Dollars
2092                         LSL     Maloti
2093                         LTL     Litai
2094                         LUF     Francs
2095                         LVL     Lati
2096                         LYD     Dinars
2097                         MAD     Dirhams
2098                         MDL     Lei
2099                         MGF     Malagasy Francs
2100                         MKD     Denars
2101                         MMK     Kyats
2102                         MNT     Tugriks
2103                         MOP     Patacas
2104                         MRO     Ouguiyas
2105                         MTL     Liri
2106                         MUR     Rupees
2107                         MVR     Rufiyaa
2108                         MWK     Kwachas
2109                         MXN     Pesos
2110                         MYR     Ringgits
2111                         MZM     Meticais
2112                         NAD     Dollars
2113                         NGN     Nairas
2114                         NIO     Gold Cordobas
2115                         NLG     Guilders
2116                         NOK     Krone
2117                         NPR     Nepal Rupees
2118                         NZD     Dollars
2119                         OMR     Rials
2120                         PAB     Balboa
2121                         PEN     Nuevos Soles
2122                         PGK     Kina
2123                         PHP     Pesos
2124                         PKR     Rupees
2125                         PLN     Zlotych
2126                         PTE     Escudos
2127                         PYG     Guarani
2128                         QAR     Rials
2129                         ROL     Lei
2130                         RUR     Rubles
2131                         RWF     Rwanda Francs
2132                         SAR     Riyals
2133                         SBD     Dollars
2134                         SCR     Rupees
2135                         SDD     Dinars
2136                         SEK     Kronor
2137                         SGD     Dollars
2138                         SHP     Pounds
2139                         SIT     Tolars
2140                         SKK     Koruny
2141                         SLL     Leones
2142                         SOS     Shillings
2143                         SPL     Luigini
2144                         SRG     Guilders
2145                         STD     Dobras
2146                         SVC     Colones
2147                         SYP     Pounds
2148                         SZL     Emalangeni
2149                         THB     Baht
2150                         TJR     Rubles
2151                         TMM     Manats
2152                         TND     Dinars
2153                         TOP     Pa'anga
2154                         TRL     Liras
2155                         TTD     Dollars
2156                         TVD     Tuvalu Dollars
2157                         TWD     New Dollars
2158                         TZS     Shillings
2159                         UAH     Hryvnia
2160                         UGX     Shillings
2161                         USD     Dollars
2162                         UYU     Pesos
2163                         UZS     Sums
2164                         VAL     Lire
2165                         VEB     Bolivares
2166                         VND     Dong
2167                         VUV     Vatu
2168                         WST     Tala
2169                         XAF     Francs
2170                         XAG     Ounces
2171                         XAU     Ounces
2172                         XCD     Dollars
2173                         XDR     Special Drawing Rights
2174                         XPD     Ounces
2175                         XPF     Francs
2176                         XPT     Ounces
2177                         YER     Rials
2178                         YUM     New Dinars
2179                         ZAR     Rand
2180                         ZMK     Kwacha
2181                         ZWD     Zimbabwe Dollars
2182
2183                 */
2184
2185                 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2186         }
2187
2188
2189         public function LookupCurrencyCountry($currencyid) {
2190
2191                 $begin = __LINE__;
2192
2193                 /** This is not a comment!
2194
2195                         AED     United Arab Emirates
2196                         AFA     Afghanistan
2197                         ALL     Albania
2198                         AMD     Armenia
2199                         ANG     Netherlands Antilles
2200                         AOA     Angola
2201                         ARS     Argentina
2202                         ATS     Austria
2203                         AUD     Australia
2204                         AWG     Aruba
2205                         AZM     Azerbaijan
2206                         BAM     Bosnia and Herzegovina
2207                         BBD     Barbados
2208                         BDT     Bangladesh
2209                         BEF     Belgium
2210                         BGL     Bulgaria
2211                         BHD     Bahrain
2212                         BIF     Burundi
2213                         BMD     Bermuda
2214                         BND     Brunei Darussalam
2215                         BOB     Bolivia
2216                         BRL     Brazil
2217                         BSD     Bahamas
2218                         BTN     Bhutan
2219                         BWP     Botswana
2220                         BYR     Belarus
2221                         BZD     Belize
2222                         CAD     Canada
2223                         CDF     Congo/Kinshasa
2224                         CHF     Switzerland
2225                         CLP     Chile
2226                         CNY     China
2227                         COP     Colombia
2228                         CRC     Costa Rica
2229                         CUP     Cuba
2230                         CVE     Cape Verde
2231                         CYP     Cyprus
2232                         CZK     Czech Republic
2233                         DEM     Germany
2234                         DJF     Djibouti
2235                         DKK     Denmark
2236                         DOP     Dominican Republic
2237                         DZD     Algeria
2238                         EEK     Estonia
2239                         EGP     Egypt
2240                         ERN     Eritrea
2241                         ESP     Spain
2242                         ETB     Ethiopia
2243                         EUR     Euro Member Countries
2244                         FIM     Finland
2245                         FJD     Fiji
2246                         FKP     Falkland Islands (Malvinas)
2247                         FRF     France
2248                         GBP     United Kingdom
2249                         GEL     Georgia
2250                         GGP     Guernsey
2251                         GHC     Ghana
2252                         GIP     Gibraltar
2253                         GMD     Gambia
2254                         GNF     Guinea
2255                         GRD     Greece
2256                         GTQ     Guatemala
2257                         GYD     Guyana
2258                         HKD     Hong Kong
2259                         HNL     Honduras
2260                         HRK     Croatia
2261                         HTG     Haiti
2262                         HUF     Hungary
2263                         IDR     Indonesia
2264                         IEP     Ireland (Eire)
2265                         ILS     Israel
2266                         IMP     Isle of Man
2267                         INR     India
2268                         IQD     Iraq
2269                         IRR     Iran
2270                         ISK     Iceland
2271                         ITL     Italy
2272                         JEP     Jersey
2273                         JMD     Jamaica
2274                         JOD     Jordan
2275                         JPY     Japan
2276                         KES     Kenya
2277                         KGS     Kyrgyzstan
2278                         KHR     Cambodia
2279                         KMF     Comoros
2280                         KPW     Korea
2281                         KWD     Kuwait
2282                         KYD     Cayman Islands
2283                         KZT     Kazakstan
2284                         LAK     Laos
2285                         LBP     Lebanon
2286                         LKR     Sri Lanka
2287                         LRD     Liberia
2288                         LSL     Lesotho
2289                         LTL     Lithuania
2290                         LUF     Luxembourg
2291                         LVL     Latvia
2292                         LYD     Libya
2293                         MAD     Morocco
2294                         MDL     Moldova
2295                         MGF     Madagascar
2296                         MKD     Macedonia
2297                         MMK     Myanmar (Burma)
2298                         MNT     Mongolia
2299                         MOP     Macau
2300                         MRO     Mauritania
2301                         MTL     Malta
2302                         MUR     Mauritius
2303                         MVR     Maldives (Maldive Islands)
2304                         MWK     Malawi
2305                         MXN     Mexico
2306                         MYR     Malaysia
2307                         MZM     Mozambique
2308                         NAD     Namibia
2309                         NGN     Nigeria
2310                         NIO     Nicaragua
2311                         NLG     Netherlands (Holland)
2312                         NOK     Norway
2313                         NPR     Nepal
2314                         NZD     New Zealand
2315                         OMR     Oman
2316                         PAB     Panama
2317                         PEN     Peru
2318                         PGK     Papua New Guinea
2319                         PHP     Philippines
2320                         PKR     Pakistan
2321                         PLN     Poland
2322                         PTE     Portugal
2323                         PYG     Paraguay
2324                         QAR     Qatar
2325                         ROL     Romania
2326                         RUR     Russia
2327                         RWF     Rwanda
2328                         SAR     Saudi Arabia
2329                         SBD     Solomon Islands
2330                         SCR     Seychelles
2331                         SDD     Sudan
2332                         SEK     Sweden
2333                         SGD     Singapore
2334                         SHP     Saint Helena
2335                         SIT     Slovenia
2336                         SKK     Slovakia
2337                         SLL     Sierra Leone
2338                         SOS     Somalia
2339                         SPL     Seborga
2340                         SRG     Suriname
2341                         STD     São Tome and Principe
2342                         SVC     El Salvador
2343                         SYP     Syria
2344                         SZL     Swaziland
2345                         THB     Thailand
2346                         TJR     Tajikistan
2347                         TMM     Turkmenistan
2348                         TND     Tunisia
2349                         TOP     Tonga
2350                         TRL     Turkey
2351                         TTD     Trinidad and Tobago
2352                         TVD     Tuvalu
2353                         TWD     Taiwan
2354                         TZS     Tanzania
2355                         UAH     Ukraine
2356                         UGX     Uganda
2357                         USD     United States of America
2358                         UYU     Uruguay
2359                         UZS     Uzbekistan
2360                         VAL     Vatican City
2361                         VEB     Venezuela
2362                         VND     Viet Nam
2363                         VUV     Vanuatu
2364                         WST     Samoa
2365                         XAF     Communauté Financière Africaine
2366                         XAG     Silver
2367                         XAU     Gold
2368                         XCD     East Caribbean
2369                         XDR     International Monetary Fund
2370                         XPD     Palladium
2371                         XPF     Comptoirs Français du Pacifique
2372                         XPT     Platinum
2373                         YER     Yemen
2374                         YUM     Yugoslavia
2375                         ZAR     South Africa
2376                         ZMK     Zambia
2377                         ZWD     Zimbabwe
2378
2379                 */
2380
2381                 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2382         }
2383
2384
2385
2386         public static function LanguageLookup($languagecode, $casesensitive=false) {
2387
2388                 if (!$casesensitive) {
2389                         $languagecode = strtolower($languagecode);
2390                 }
2391
2392                 // http://www.id3.org/id3v2.4.0-structure.txt
2393                 // [4.   ID3v2 frame overview]
2394                 // The three byte language field, present in several frames, is used to
2395                 // describe the language of the frame's content, according to ISO-639-2
2396                 // [ISO-639-2]. The language should be represented in lower case. If the
2397                 // language is not known the string "XXX" should be used.
2398
2399
2400                 // ISO 639-2 - http://www.id3.org/iso639-2.html
2401
2402                 $begin = __LINE__;
2403
2404                 /** This is not a comment!
2405
2406                         XXX     unknown
2407                         xxx     unknown
2408                         aar     Afar
2409                         abk     Abkhazian
2410                         ace     Achinese
2411                         ach     Acoli
2412                         ada     Adangme
2413                         afa     Afro-Asiatic (Other)
2414                         afh     Afrihili
2415                         afr     Afrikaans
2416                         aka     Akan
2417                         akk     Akkadian
2418                         alb     Albanian
2419                         ale     Aleut
2420                         alg     Algonquian Languages
2421                         amh     Amharic
2422                         ang     English, Old (ca. 450-1100)
2423                         apa     Apache Languages
2424                         ara     Arabic
2425                         arc     Aramaic
2426                         arm     Armenian
2427                         arn     Araucanian
2428                         arp     Arapaho
2429                         art     Artificial (Other)
2430                         arw     Arawak
2431                         asm     Assamese
2432                         ath     Athapascan Languages
2433                         ava     Avaric
2434                         ave     Avestan
2435                         awa     Awadhi
2436                         aym     Aymara
2437                         aze     Azerbaijani
2438                         bad     Banda
2439                         bai     Bamileke Languages
2440                         bak     Bashkir
2441                         bal     Baluchi
2442                         bam     Bambara
2443                         ban     Balinese
2444                         baq     Basque
2445                         bas     Basa
2446                         bat     Baltic (Other)
2447                         bej     Beja
2448                         bel     Byelorussian
2449                         bem     Bemba
2450                         ben     Bengali
2451                         ber     Berber (Other)
2452                         bho     Bhojpuri
2453                         bih     Bihari
2454                         bik     Bikol
2455                         bin     Bini
2456                         bis     Bislama
2457                         bla     Siksika
2458                         bnt     Bantu (Other)
2459                         bod     Tibetan
2460                         bra     Braj
2461                         bre     Breton
2462                         bua     Buriat
2463                         bug     Buginese
2464                         bul     Bulgarian
2465                         bur     Burmese
2466                         cad     Caddo
2467                         cai     Central American Indian (Other)
2468                         car     Carib
2469                         cat     Catalan
2470                         cau     Caucasian (Other)
2471                         ceb     Cebuano
2472                         cel     Celtic (Other)
2473                         ces     Czech
2474                         cha     Chamorro
2475                         chb     Chibcha
2476                         che     Chechen
2477                         chg     Chagatai
2478                         chi     Chinese
2479                         chm     Mari
2480                         chn     Chinook jargon
2481                         cho     Choctaw
2482                         chr     Cherokee
2483                         chu     Church Slavic
2484                         chv     Chuvash
2485                         chy     Cheyenne
2486                         cop     Coptic
2487                         cor     Cornish
2488                         cos     Corsican
2489                         cpe     Creoles and Pidgins, English-based (Other)
2490                         cpf     Creoles and Pidgins, French-based (Other)
2491                         cpp     Creoles and Pidgins, Portuguese-based (Other)
2492                         cre     Cree
2493                         crp     Creoles and Pidgins (Other)
2494                         cus     Cushitic (Other)
2495                         cym     Welsh
2496                         cze     Czech
2497                         dak     Dakota
2498                         dan     Danish
2499                         del     Delaware
2500                         deu     German
2501                         din     Dinka
2502                         div     Divehi
2503                         doi     Dogri
2504                         dra     Dravidian (Other)
2505                         dua     Duala
2506                         dum     Dutch, Middle (ca. 1050-1350)
2507                         dut     Dutch
2508                         dyu     Dyula
2509                         dzo     Dzongkha
2510                         efi     Efik
2511                         egy     Egyptian (Ancient)
2512                         eka     Ekajuk
2513                         ell     Greek, Modern (1453-)
2514                         elx     Elamite
2515                         eng     English
2516                         enm     English, Middle (ca. 1100-1500)
2517                         epo     Esperanto
2518                         esk     Eskimo (Other)
2519                         esl     Spanish
2520                         est     Estonian
2521                         eus     Basque
2522                         ewe     Ewe
2523                         ewo     Ewondo
2524                         fan     Fang
2525                         fao     Faroese
2526                         fas     Persian
2527                         fat     Fanti
2528                         fij     Fijian
2529                         fin     Finnish
2530                         fiu     Finno-Ugrian (Other)
2531                         fon     Fon
2532                         fra     French
2533                         fre     French
2534                         frm     French, Middle (ca. 1400-1600)
2535                         fro     French, Old (842- ca. 1400)
2536                         fry     Frisian
2537                         ful     Fulah
2538                         gaa     Ga
2539                         gae     Gaelic (Scots)
2540                         gai     Irish
2541                         gay     Gayo
2542                         gdh     Gaelic (Scots)
2543                         gem     Germanic (Other)
2544                         geo     Georgian
2545                         ger     German
2546                         gez     Geez
2547                         gil     Gilbertese
2548                         glg     Gallegan
2549                         gmh     German, Middle High (ca. 1050-1500)
2550                         goh     German, Old High (ca. 750-1050)
2551                         gon     Gondi
2552                         got     Gothic
2553                         grb     Grebo
2554                         grc     Greek, Ancient (to 1453)
2555                         gre     Greek, Modern (1453-)
2556                         grn     Guarani
2557                         guj     Gujarati
2558                         hai     Haida
2559                         hau     Hausa
2560                         haw     Hawaiian
2561                         heb     Hebrew
2562                         her     Herero
2563                         hil     Hiligaynon
2564