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