source: extensions/charlies_content/getid3/getid3/module.tag.id3v2.php @ 3318

Revision 3318, 153.1 KB checked in by vdigital, 10 years ago (diff)

+ Add Charlies' content to depository

  • Property svn:eol-style set to LF
  • Property svn:keywords set to Author Date Id Revision
Line 
1<?php
2// +----------------------------------------------------------------------+
3// | PHP version 5                                                        |
4// +----------------------------------------------------------------------+
5// | Copyright (c) 2002-2006 James Heinrich, Allan Hansen                 |
6// +----------------------------------------------------------------------+
7// | This source file is subject to version 2 of the GPL license,         |
8// | that is bundled with this package in the file license.txt and is     |
9// | available through the world-wide-web at the following url:           |
10// | http://www.gnu.org/copyleft/gpl.html                                 |
11// +----------------------------------------------------------------------+
12// | getID3() - http://getid3.sourceforge.net or http://www.getid3.org    |
13// +----------------------------------------------------------------------+
14// | Authors: James Heinrich <infoØgetid3*org>                            |
15// |          Allan Hansen <ahØartemis*dk>                                |
16// +----------------------------------------------------------------------+
17// | module.tag.id3v2.php                                                 |
18// | module for analyzing ID3v2 tags                                      |
19// | dependencies: module.tag.id3v1.php                                   |
20// |               module.lib.image_size.php (optional)                   |
21// |               zlib support in PHP (optional)                         |
22// +----------------------------------------------------------------------+
23//
24// $Id$
25
26
27       
28       
29class getid3_id3v2 extends getid3_handler
30{
31
32    public $option_starting_offset = 0;
33
34
35    public function Analyze() {
36
37        $getid3 = $this->getid3;
38       
39        // dependency
40        $getid3->include_module('tag.id3v1');
41       
42        if ($getid3->option_tags_images) {       
43            $getid3->include_module('lib.image_size');
44        }
45
46
47        //    Overall tag structure:
48        //        +-----------------------------+
49        //        |      Header (10 bytes)      |
50        //        +-----------------------------+
51        //        |       Extended Header       |
52        //        | (variable length, OPTIONAL) |
53        //        +-----------------------------+
54        //        |   Frames (variable length)  |
55        //        +-----------------------------+
56        //        |           Padding           |
57        //        | (variable length, OPTIONAL) |
58        //        +-----------------------------+
59        //        | Footer (10 bytes, OPTIONAL) |
60        //        +-----------------------------+
61        //
62        //    Header
63        //        ID3v2/file identifier      "ID3"
64        //        ID3v2 version              $04 00
65        //        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
66        //        ID3v2 size                 4 * %0xxxxxxx
67
68
69        // shortcuts
70        $getid3->info['id3v2']['header'] = true;
71        $info_id3v2          = &$getid3->info['id3v2'];
72        $info_id3v2['flags'] = array ();
73        $info_id3v2_flags    = &$info_id3v2['flags'];
74
75
76        $this->fseek($this->option_starting_offset, SEEK_SET);
77        $header = $this->fread(10);
78        if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
79
80            $info_id3v2['majorversion'] = ord($header{3});
81            $info_id3v2['minorversion'] = ord($header{4});
82
83            // shortcut
84            $id3v2_major_version = &$info_id3v2['majorversion'];
85
86        } else {
87            unset($getid3->info['id3v2']);
88            return false;
89
90        }
91
92        if ($id3v2_major_version > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
93            throw new getid3_exception('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_major_version.'.'.$info_id3v2['minorversion']);
94        }
95
96        $id3_flags = ord($header{5});
97        switch ($id3v2_major_version) {
98            case 2:
99                // %ab000000 in v2.2
100                $info_id3v2_flags['unsynch']     = (bool)($id3_flags & 0x80); // a - Unsynchronisation
101                $info_id3v2_flags['compression'] = (bool)($id3_flags & 0x40); // b - Compression
102                break;
103
104            case 3:
105                // %abc00000 in v2.3
106                $info_id3v2_flags['unsynch']     = (bool)($id3_flags & 0x80); // a - Unsynchronisation
107                $info_id3v2_flags['exthead']     = (bool)($id3_flags & 0x40); // b - Extended header
108                $info_id3v2_flags['experim']     = (bool)($id3_flags & 0x20); // c - Experimental indicator
109                break;
110
111            case 4:
112                // %abcd0000 in v2.4
113                $info_id3v2_flags['unsynch']     = (bool)($id3_flags & 0x80); // a - Unsynchronisation
114                $info_id3v2_flags['exthead']     = (bool)($id3_flags & 0x40); // b - Extended header
115                $info_id3v2_flags['experim']     = (bool)($id3_flags & 0x20); // c - Experimental indicator
116                $info_id3v2_flags['isfooter']    = (bool)($id3_flags & 0x10); // d - Footer present
117                break;
118        }
119
120        $info_id3v2['headerlength'] = getid3_lib::BigEndianSyncSafe2Int(substr($header, 6, 4)) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
121
122        $info_id3v2['tag_offset_start'] = $this->option_starting_offset;
123        $info_id3v2['tag_offset_end']   = $info_id3v2['tag_offset_start'] + $info_id3v2['headerlength'];
124
125
126        // Frames
127       
128        //     All ID3v2 frames consists of one frame header followed by one or more
129        //     fields containing the actual information. The header is always 10
130        //     bytes and laid out as follows:
131        //
132        //     Frame ID      $xx xx xx xx  (four characters)
133        //     Size      4 * %0xxxxxxx
134        //     Flags         $xx xx
135
136        $size_of_frames = $info_id3v2['headerlength'] - 10; // not including 10-byte initial header
137        if (@$info_id3v2['exthead']['length']) {
138            $size_of_frames -= ($info_id3v2['exthead']['length'] + 4);
139        }
140       
141        if (@$info_id3v2_flags['isfooter']) {
142            $size_of_frames -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
143        }
144       
145        if ($size_of_frames > 0) {
146            $frame_data = $this->fread($size_of_frames); // read all frames from file into $frame_data variable
147
148            //    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
149            if (@$info_id3v2_flags['unsynch'] && ($id3v2_major_version <= 3)) {
150                $frame_data = str_replace("\xFF\x00", "\xFF", $frame_data);
151            }
152       
153            //        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
154            //        of on tag level, making it easier to skip frames, increasing the streamability
155            //        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
156            //        there exists an unsynchronised frame, while the new unsynchronisation flag in
157            //        the frame header [S:4.1.2] indicates unsynchronisation.
158
159             //$frame_data_offset = 10 + (@$info_id3v2['exthead']['length'] ? $info_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)
160             $frame_data_offset = 10; // how many bytes into the stream - start from after the 10-byte header
161
162             //    Extended Header
163             if (@$info_id3v2_flags['exthead']) {
164                     $extended_header_offset = 0;
165
166                 if ($id3v2_major_version == 3) {
167
168                     // v2.3 definition:
169                     //Extended header size  $xx xx xx xx   // 32-bit integer
170                     //Extended Flags        $xx xx
171                     //     %x0000000 %00000000 // v2.3
172                     //     x - CRC data present
173                     //Size of padding       $xx xx xx xx
174
175                     $info_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 4), 0);
176                     $extended_header_offset += 4;
177
178                     $info_id3v2['exthead']['flag_bytes'] = 2;
179                     $info_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, $info_id3v2['exthead']['flag_bytes']));
180                     $extended_header_offset += $info_id3v2['exthead']['flag_bytes'];
181
182                     $info_id3v2['exthead']['flags']['crc'] = (bool) ($info_id3v2['exthead']['flag_raw'] & 0x8000);
183
184                     $info_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 4));
185                     $extended_header_offset += 4;
186
187                     if ($info_id3v2['exthead']['flags']['crc']) {
188                         $info_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 4));
189                         $extended_header_offset += 4;
190                     }
191                     $extended_header_offset += $info_id3v2['exthead']['padding_size'];
192
193                 } 
194                 
195                 elseif ($id3v2_major_version == 4) {
196
197                     // v2.4 definition:
198                     //Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
199                     //Number of flag bytes       $01
200                     //Extended Flags             $xx
201                     //     %0bcd0000 // v2.4
202                     //     b - Tag is an update
203                     //         Flag data length       $00
204                     //     c - CRC data present
205                     //         Flag data length       $05
206                     //         Total frame CRC    5 * %0xxxxxxx
207                     //     d - Tag restrictions
208                     //         Flag data length       $01
209
210                     $info_id3v2['exthead']['length']     = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 4), 1);
211                     $extended_header_offset += 4;
212
213                     $info_id3v2['exthead']['flag_bytes'] = 1;
214                     $info_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, $info_id3v2['exthead']['flag_bytes']));
215                     $extended_header_offset += $info_id3v2['exthead']['flag_bytes'];
216
217                     $info_id3v2['exthead']['flags']['update']       = (bool) ($info_id3v2['exthead']['flag_raw'] & 0x4000);
218                     $info_id3v2['exthead']['flags']['crc']          = (bool) ($info_id3v2['exthead']['flag_raw'] & 0x2000);
219                     $info_id3v2['exthead']['flags']['restrictions'] = (bool) ($info_id3v2['exthead']['flag_raw'] & 0x1000);
220
221                     if ($info_id3v2['exthead']['flags']['crc']) {
222                         $info_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 5), 1);
223                         $extended_header_offset += 5;
224                     }
225                     if ($info_id3v2['exthead']['flags']['restrictions']) {
226                         // %ppqrrstt
227                         $restrictions_raw = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 1));
228                         $extended_header_offset += 1;
229                         $info_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw && 0xC0) >> 6; // p - Tag size restrictions
230                         $info_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw && 0x20) >> 5; // q - Text encoding restrictions
231                         $info_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw && 0x18) >> 3; // r - Text fields size restrictions
232                         $info_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw && 0x04) >> 2; // s - Image encoding restrictions
233                             $info_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw && 0x03) >> 0; // t - Image size restrictions
234                     }
235
236                 }
237                 $frame_data_offset += $extended_header_offset;
238                 $frame_data = substr($frame_data, $extended_header_offset);
239             } // end extended header
240 
241           
242           
243           
244           
245           
246            while (isset($frame_data) && (strlen($frame_data) > 0)) { // cycle through until no more frame data is left to parse
247                if (strlen($frame_data) <= ($id3v2_major_version == 2 ? 6 : 10)) {
248                    // insufficient room left in ID3v2 header for actual data - must be padding
249                    $info_id3v2['padding']['start']  = $frame_data_offset;
250                    $info_id3v2['padding']['length'] = strlen($frame_data);
251                    $info_id3v2['padding']['valid']  = true;
252                    for ($i = 0; $i < $info_id3v2['padding']['length']; $i++) {
253                        if ($frame_data{$i} != "\x00") {
254                            $info_id3v2['padding']['valid'] = false;
255                            $info_id3v2['padding']['errorpos'] = $info_id3v2['padding']['start'] + $i;
256                            $getid3->warning('Invalid ID3v2 padding found at offset '.$info_id3v2['padding']['errorpos'].' (the remaining '.($info_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
257                            break;
258                        }
259                    }
260                    break; // skip rest of ID3v2 header
261                }
262               
263                if ($id3v2_major_version == 2) {
264                    // Frame ID  $xx xx xx (three characters)
265                    // Size      $xx xx xx (24-bit integer)
266                    // Flags     $xx xx
267
268                    $frame_header = substr($frame_data, 0, 6); // take next 6 bytes for header
269                    $frame_data    = substr($frame_data, 6);    // and leave the rest in $frame_data
270                    $frame_name   = substr($frame_header, 0, 3);
271                    $frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3));
272                    $frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
273
274
275                } elseif ($id3v2_major_version > 2) {
276
277                    // Frame ID  $xx xx xx xx (four characters)
278                    // Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
279                    // Flags     $xx xx
280
281                    $frame_header = substr($frame_data, 0, 10); // take next 10 bytes for header
282                    $frame_data    = substr($frame_data, 10);    // and leave the rest in $frame_data
283
284                    $frame_name = substr($frame_header, 0, 4);
285
286                    if ($id3v2_major_version == 3) {
287                        $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4)); // 32-bit integer
288
289                    } else { // ID3v2.4+
290                        $frame_size = getid3_lib::BigEndianSyncSafe2Int(substr($frame_header, 4, 4)); // 32-bit synchsafe integer (28-bit value)
291                    }
292
293                    if ($frame_size < (strlen($frame_data) + 4)) {
294                        $nextFrameID = substr($frame_data, $frame_size, 4);
295                        if (getid3_id3v2::IsValidID3v2FrameName($nextFrameID, $id3v2_major_version)) {
296                            // next frame is OK
297                        } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
298                            // MP3ext known broken frames - "ok" for the purposes of this test
299                        } elseif (($id3v2_major_version == 4) && (getid3_id3v2::IsValidID3v2FrameName(substr($frame_data, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4)), 4), 3))) {
300                            $getid3->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');
301                            $id3v2_major_version = 3;
302                            $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4)); // 32-bit integer
303                        }
304                    }
305
306
307                    $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
308                }
309
310                if ((($id3v2_major_version == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
311                    // padding encountered
312
313                    $info_id3v2['padding']['start']  = $frame_data_offset;
314                    $info_id3v2['padding']['length'] = strlen($frame_header) + strlen($frame_data);
315                    $info_id3v2['padding']['valid']  = true;
316                   
317                    $len = strlen($frame_data);
318                    for ($i = 0; $i < $len; $i++) {
319                        if ($frame_data{$i} != "\x00") {
320                            $info_id3v2['padding']['valid'] = false;
321                            $info_id3v2['padding']['errorpos'] = $info_id3v2['padding']['start'] + $i;
322                            $getid3->warning('Invalid ID3v2 padding found at offset '.$info_id3v2['padding']['errorpos'].' (the remaining '.($info_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
323                            break;
324                        }
325                    }
326                    break; // skip rest of ID3v2 header
327                }
328
329                if ($frame_name == 'COM ') {
330                    $getid3->warning('error parsing "'.$frame_name.'" ('.$frame_data_offset.' bytes into the ID3v2.'.$id3v2_major_version.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_major_version.'))). [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)]');
331                    $frame_name = 'COMM';
332                }
333                if (($frame_size <= strlen($frame_data)) && (getid3_id3v2::IsValidID3v2FrameName($frame_name, $id3v2_major_version))) {
334
335                    unset($parsed_frame);
336                    $parsed_frame['frame_name']      = $frame_name;
337                    $parsed_frame['frame_flags_raw'] = $frame_flags;
338                    $parsed_frame['data']            = substr($frame_data, 0, $frame_size);
339                    $parsed_frame['datalength']      = (int)($frame_size);
340                    $parsed_frame['dataoffset']      = $frame_data_offset;
341
342                    $this->ParseID3v2Frame($parsed_frame);
343                    $info_id3v2[$frame_name][] = $parsed_frame;
344
345                    $frame_data = substr($frame_data, $frame_size);
346
347                } else { // invalid frame length or FrameID
348
349                    if ($frame_size <= strlen($frame_data)) {
350
351                        if (getid3_id3v2::IsValidID3v2FrameName(substr($frame_data, $frame_size, 4), $id3v2_major_version)) {
352
353                            // next frame is valid, just skip the current frame
354                            $frame_data = substr($frame_data, $frame_size);
355                            $getid3->warning('Next ID3v2 frame is valid, skipping current frame.');
356
357                        } else {
358
359                            // next frame is invalid too, abort processing
360                            throw new getid3_exception('Next ID3v2 frame is also invalid, aborting processing.');
361
362                        }
363
364                    } elseif ($frame_size == strlen($frame_data)) {
365
366                        // this is the last frame, just skip
367                        $getid3->warning('This was the last ID3v2 frame.');
368
369                    } else {
370
371                        // next frame is invalid too, abort processing
372                        $frame_data = null;
373                        $getid3->warning('Invalid ID3v2 frame size, aborting.');
374
375                    }
376                    if (!getid3_id3v2::IsValidID3v2FrameName($frame_name, $id3v2_major_version)) {
377
378                        switch ($frame_name) {
379                           
380                            case "\x00\x00".'MP':
381                            case "\x00".'MP3':
382                            case ' MP3':
383                            case 'MP3e':
384                            case "\x00".'MP':
385                            case ' MP':
386                            case 'MP3':
387                                $getid3->warning('error parsing "'.$frame_name.'" ('.$frame_data_offset.' bytes into the ID3v2.'.$id3v2_major_version.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_major_version.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]');
388                                break;
389
390                            default:
391                                $getid3->warning('error parsing "'.$frame_name.'" ('.$frame_data_offset.' bytes into the ID3v2.'.$id3v2_major_version.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_major_version.'))).');
392                                break;
393                        }
394
395                    } elseif ($frame_size > strlen(@$frame_data)){
396
397                        throw new getid3_exception('error parsing "'.$frame_name.'" ('.$frame_data_offset.' bytes into the ID3v2.'.$id3v2_major_version.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($frame_data) ('.strlen($frame_data).')).');
398
399                    } else {
400
401                        throw new getid3_exception('error parsing "'.$frame_name.'" ('.$frame_data_offset.' bytes into the ID3v2.'.$id3v2_major_version.' tag).');
402
403                    }
404
405                }
406                $frame_data_offset += ($frame_size + ($id3v2_major_version == 2 ? 6 : 10));
407
408            }
409
410        }
411
412
413        //    Footer
414   
415        //    The footer is a copy of the header, but with a different identifier.
416        //        ID3v2 identifier           "3DI"
417        //        ID3v2 version              $04 00
418        //        ID3v2 flags                %abcd0000
419        //        ID3v2 size             4 * %0xxxxxxx
420
421        if (isset($info_id3v2_flags['isfooter']) && $info_id3v2_flags['isfooter']) {
422            $footer = fread ($getid3->fp, 10);
423            if (substr($footer, 0, 3) == '3DI') {
424                $info_id3v2['footer'] = true;
425                $info_id3v2['majorversion_footer'] = ord($footer{3});
426                $info_id3v2['minorversion_footer'] = ord($footer{4});
427            }
428            if ($info_id3v2['majorversion_footer'] <= 4) {
429                $id3_flags = ord($footer{5});
430                $info_id3v2_flags['unsynch_footer']  = (bool)($id3_flags & 0x80);
431                $info_id3v2_flags['extfoot_footer']  = (bool)($id3_flags & 0x40);
432                $info_id3v2_flags['experim_footer']  = (bool)($id3_flags & 0x20);
433                $info_id3v2_flags['isfooter_footer'] = (bool)($id3_flags & 0x10);
434
435                $info_id3v2['footerlength'] = getid3_lib::BigEndianSyncSafe2Int(substr($footer, 6, 4));
436            }
437        } // end footer
438
439        if (isset($info_id3v2['comments']['genre'])) {
440            foreach ($info_id3v2['comments']['genre'] as $key => $value) {
441                unset($info_id3v2['comments']['genre'][$key]);
442                $info_id3v2['comments'] = getid3_id3v2::array_merge_noclobber($info_id3v2['comments'], getid3_id3v2::ParseID3v2GenreString($value));
443            }
444        }
445
446        if (isset($info_id3v2['comments']['track'])) {
447            foreach ($info_id3v2['comments']['track'] as $key => $value) {
448                if (strstr($value, '/')) {
449                    list($info_id3v2['comments']['track'][$key], $info_id3v2['comments']['totaltracks'][$key]) = explode('/', $info_id3v2['comments']['track'][$key]);
450                }
451            }
452        }
453       
454        // Use year from recording time if year not set
455        if (!isset($info_id3v2['comments']['year']) && ereg('^([0-9]{4})', @$info_id3v2['comments']['recording_time'][0], $matches)) {
456                        $info_id3v2['comments']['year'] = array ($matches[1]);
457                }
458
459        // Set avdataoffset
460        $getid3->info['avdataoffset'] = $info_id3v2['headerlength'];
461        if (isset($info_id3v2['footer'])) {
462            $getid3->info['avdataoffset'] += 10;
463        }
464
465        return true;
466    }
467
468
469
470    private function ParseID3v2Frame(&$parsed_frame) {
471       
472        $getid3 = $this->getid3;
473
474        $id3v2_major_version = $getid3->info['id3v2']['majorversion'];
475
476        $frame_name_long  = getid3_id3v2::FrameNameLongLookup($parsed_frame['frame_name']);
477        if ($frame_name_long) {
478            $parsed_frame['framenamelong']  = $frame_name_long; 
479        }
480       
481        $frame_name_short = getid3_id3v2::FrameNameShortLookup($parsed_frame['frame_name']);
482        if ($frame_name_short) {
483            $parsed_frame['framenameshort']  = $frame_name_short; 
484        }
485
486        if ($id3v2_major_version >= 3) { // frame flags are not part of the ID3v2.2 standard
487
488            if ($id3v2_major_version == 3) {
489
490                //    Frame Header Flags
491                //    %abc00000 %ijk00000
492
493                $parsed_frame['flags']['TagAlterPreservation']  = (bool)($parsed_frame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
494                $parsed_frame['flags']['FileAlterPreservation'] = (bool)($parsed_frame['frame_flags_raw'] & 0x4000); // b - File alter preservation
495                $parsed_frame['flags']['ReadOnly']              = (bool)($parsed_frame['frame_flags_raw'] & 0x2000); // c - Read only
496                $parsed_frame['flags']['compression']           = (bool)($parsed_frame['frame_flags_raw'] & 0x0080); // i - Compression
497                $parsed_frame['flags']['Encryption']            = (bool)($parsed_frame['frame_flags_raw'] & 0x0040); // j - Encryption
498                $parsed_frame['flags']['GroupingIdentity']      = (bool)($parsed_frame['frame_flags_raw'] & 0x0020); // k - Grouping identity
499
500
501            } elseif ($id3v2_major_version == 4) {
502
503                //    Frame Header Flags
504                //    %0abc0000 %0h00kmnp
505
506                $parsed_frame['flags']['TagAlterPreservation']  = (bool)($parsed_frame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
507                $parsed_frame['flags']['FileAlterPreservation'] = (bool)($parsed_frame['frame_flags_raw'] & 0x2000); // b - File alter preservation
508                $parsed_frame['flags']['ReadOnly']              = (bool)($parsed_frame['frame_flags_raw'] & 0x1000); // c - Read only
509                $parsed_frame['flags']['GroupingIdentity']      = (bool)($parsed_frame['frame_flags_raw'] & 0x0040); // h - Grouping identity
510                $parsed_frame['flags']['compression']           = (bool)($parsed_frame['frame_flags_raw'] & 0x0008); // k - Compression
511                $parsed_frame['flags']['Encryption']            = (bool)($parsed_frame['frame_flags_raw'] & 0x0004); // m - Encryption
512                $parsed_frame['flags']['Unsynchronisation']     = (bool)($parsed_frame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
513                $parsed_frame['flags']['DataLengthIndicator']   = (bool)($parsed_frame['frame_flags_raw'] & 0x0001); // p - Data length indicator
514
515                // Frame-level de-unsynchronisation - ID3v2.4
516                if ($parsed_frame['flags']['Unsynchronisation']) {
517                    $parsed_frame['data'] = str_replace("\xFF\x00", "\xFF", $parsed_frame['data']);
518                }
519            }
520
521            //    Frame-level de-compression
522            if ($parsed_frame['flags']['compression']) {
523                $parsed_frame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], 0, 4));
524               
525                if (!function_exists('gzuncompress')) {
526                    $getid3->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsed_frame['frame_name'].'"');
527                } elseif ($decompressed_data = @gzuncompress(substr($parsed_frame['data'], 4))) {
528                    $parsed_frame['data'] = $decompressed_data;
529                } else {
530                    $getid3->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsed_frame['frame_name'].'"');
531                }
532            }
533        }
534
535
536        if (isset($parsed_frame['datalength']) && ($parsed_frame['datalength'] == 0)) {
537
538            $warning = 'Frame "'.$parsed_frame['frame_name'].'" at offset '.$parsed_frame['dataoffset'].' has no data portion';
539            switch ($parsed_frame['frame_name']) {
540                case 'WCOM':
541                    $warning .= ' (this is known to happen with files tagged by RioPort)';
542                    break;
543
544                default:
545                    break;
546            }
547            $getid3->warning($warning);
548            return true;
549        }
550       
551       
552        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'UFID')) ||   // 4.1   UFID Unique file identifier
553            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'UFI'))) {    // 4.1   UFI  Unique file identifier
554
555            //   There may be more than one 'UFID' frame in a tag,
556            //   but only one with the same 'Owner identifier'.
557            // <Header for 'Unique file identifier', ID: 'UFID'>
558            // Owner identifier        <text string> $00
559            // Identifier              <up to 64 bytes binary data>
560
561            $frame_terminator_pos = strpos($parsed_frame['data'], "\x00");
562            $frame_id_string = substr($parsed_frame['data'], 0, $frame_terminator_pos);
563            $parsed_frame['ownerid'] = $frame_id_string;
564            $parsed_frame['data'] = substr($parsed_frame['data'], $frame_terminator_pos + strlen("\x00"));
565            unset($parsed_frame['data']);
566            return true;
567        }
568
569
570        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'TXXX')) ||   // 4.2.2 TXXX User defined text information frame
571            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
572
573            //   There may be more than one 'TXXX' frame in each tag,
574            //   but only one with the same description.
575            // <Header for 'User defined text information frame', ID: 'TXXX'>
576            // Text encoding     $xx
577            // Description       <text string according to encoding> $00 (00)
578            // Value             <text string according to encoding>
579
580            $frame_offset = 0;
581            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
582
583            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
584                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
585            }
586            $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
587            if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
588                $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
589            }
590            $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
591            if (ord($frame_description) === 0) {
592                $frame_description = '';
593            }
594            $parsed_frame['encodingid']  = $frame_text_encoding;
595            $parsed_frame['encoding']    = $this->TextEncodingNameLookup($frame_text_encoding);
596
597            $parsed_frame['description'] = $frame_description;
598            $parsed_frame['data'] = substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)));
599            if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
600                $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = trim($getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['data']));
601            }
602            unset($parsed_frame['data']);
603            return true;
604        }
605       
606
607        if ($parsed_frame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
608
609            //   There may only be one text information frame of its kind in an tag.
610            // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
611            // excluding 'TXXX' described in 4.2.6.>
612            // Text encoding                $xx
613            // Information                  <text string(s) according to encoding>
614
615            $frame_offset = 0;
616            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
617            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
618                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
619            }
620
621            $parsed_frame['data'] = (string)substr($parsed_frame['data'], $frame_offset);
622
623            $parsed_frame['encodingid'] = $frame_text_encoding;
624            $parsed_frame['encoding']   = $this->TextEncodingNameLookup($frame_text_encoding);
625
626            if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
627               
628                // remove possible terminating \x00 (put by encoding id or software bug)
629                $string = $getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['data']);
630                if ($string[strlen($string)-1] = "\x00") {
631                    $string = substr($string, 0, strlen($string)-1);
632                }
633                $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $string;
634                unset($string);
635            }
636            return true;
637        }
638
639
640        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'WXXX')) ||   // 4.3.2 WXXX User defined URL link frame
641            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
642           
643            //   There may be more than one 'WXXX' frame in each tag,
644            //   but only one with the same description
645            // <Header for 'User defined URL link frame', ID: 'WXXX'>
646            // Text encoding     $xx
647            // Description       <text string according to encoding> $00 (00)
648            // URL               <text string>
649
650            $frame_offset = 0;
651            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
652            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
653                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
654            }
655            $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
656            if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
657                $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
658            }
659            $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
660
661            if (ord($frame_description) === 0) {
662                $frame_description = '';
663            }
664            $parsed_frame['data'] = substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)));
665
666            $frame_terminator_pos = strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding));
667            if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
668                $frame_terminator_pos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
669            }
670            if ($frame_terminator_pos) {
671                // there are null bytes after the data - this is not according to spec
672                // only use data up to first null byte
673                $frame_urldata = (string)substr($parsed_frame['data'], 0, $frame_terminator_pos);
674            } else {
675                // no null bytes following data, just use all data
676                $frame_urldata = (string)$parsed_frame['data'];
677            }
678
679            $parsed_frame['encodingid']  = $frame_text_encoding;
680            $parsed_frame['encoding']    = $this->TextEncodingNameLookup($frame_text_encoding);
681
682            $parsed_frame['url']         = $frame_urldata;
683            $parsed_frame['description'] = $frame_description;
684            if (!empty($parsed_frame['framenameshort']) && $parsed_frame['url']) {
685                $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['url']);
686            }
687            unset($parsed_frame['data']);
688            return true;
689        }
690
691
692        if ($parsed_frame['frame_name']{0} == 'W') {        // 4.3. W??? URL link frames
693
694            //   There may only be one URL link frame of its kind in a tag,
695            //   except when stated otherwise in the frame description
696            // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
697            // described in 4.3.2.>
698            // URL              <text string>
699
700            $parsed_frame['url'] = trim($parsed_frame['data']);
701            if (!empty($parsed_frame['framenameshort']) && $parsed_frame['url']) {
702                $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $parsed_frame['url'];
703            }
704            unset($parsed_frame['data']);
705            return true;
706        }
707
708
709        if ((($id3v2_major_version == 3) && ($parsed_frame['frame_name'] == 'IPLS')) ||    // 4.4  IPLS Involved people list (ID3v2.3 only)
710            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
711
712            //   There may only be one 'IPL' frame in each tag
713            // <Header for 'User defined URL link frame', ID: 'IPL'>
714            // Text encoding     $xx
715            // People list strings    <textstrings>
716
717            $frame_offset = 0;
718            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
719            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
720                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
721            }
722            $parsed_frame['encodingid'] = $frame_text_encoding;
723            $parsed_frame['encoding']   = $this->TextEncodingNameLookup($parsed_frame['encodingid']);
724
725            $parsed_frame['data']       = (string)substr($parsed_frame['data'], $frame_offset);
726            if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
727                $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['data']);
728            }
729            return true;
730        }
731
732
733        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'MCDI')) ||    // 4.4   MCDI Music CD identifier
734            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
735
736            //   There may only be one 'MCDI' frame in each tag
737            // <Header for 'Music CD identifier', ID: 'MCDI'>
738            // CD TOC                <binary data>
739
740            if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
741                $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $parsed_frame['data'];
742            }
743            return true;
744        }
745
746
747        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'ETCO')) ||    // 4.5   ETCO Event timing codes
748            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
749
750            //   There may only be one 'ETCO' frame in each tag
751            // <Header for 'Event timing codes', ID: 'ETCO'>
752            // Time stamp format    $xx
753            //   Where time stamp format is:
754            // $01  (32-bit value) MPEG frames from beginning of file
755            // $02  (32-bit value) milliseconds from beginning of file
756            //   Followed by a list of key events in the following format:
757            // Type of event   $xx
758            // Time stamp      $xx (xx ...)
759            //   The 'Time stamp' is set to zero if directly at the beginning of the sound
760            //   or after the previous event. All events MUST be sorted in chronological order.
761
762            $frame_offset = 0;
763            $parsed_frame['timestampformat'] = ord($parsed_frame['data']{$frame_offset++});
764
765            while ($frame_offset < strlen($parsed_frame['data'])) {
766                $parsed_frame['typeid']    = $parsed_frame['data']{$frame_offset++};
767                $parsed_frame['type']      = getid3_id3v2::ETCOEventLookup($parsed_frame['typeid']);
768                $parsed_frame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 4));
769                $frame_offset += 4;
770            }
771            unset($parsed_frame['data']);
772            return true;
773        }
774
775
776        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'MLLT')) ||     // 4.6   MLLT MPEG location lookup table
777            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'MLL'))) {      // 4.7   MLL MPEG location lookup table
778           
779            //   There may only be one 'MLLT' frame in each tag
780            // <Header for 'Location lookup table', ID: 'MLLT'>
781            // MPEG frames between reference  $xx xx
782            // Bytes between reference        $xx xx xx
783            // Milliseconds between reference $xx xx xx
784            // Bits for bytes deviation       $xx
785            // Bits for milliseconds dev.     $xx
786            //   Then for every reference the following data is included;
787            // Deviation in bytes         %xxx....
788            // Deviation in milliseconds  %xxx....
789
790            $frame_offset = 0;
791            $parsed_frame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], 0, 2));
792            $parsed_frame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], 2, 3));
793            $parsed_frame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], 5, 3));
794            $parsed_frame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int($parsed_frame['data'][8]);
795            $parsed_frame['bitsformsdeviation']      = getid3_lib::BigEndian2Int($parsed_frame['data'][9]);
796            $parsed_frame['data'] = substr($parsed_frame['data'], 10);
797           
798            while ($frame_offset < strlen($parsed_frame['data'])) {
799                $deviation_bitstream .= getid3_lib::BigEndian2Bin($parsed_frame['data']{$frame_offset++});
800            }
801            $reference_counter = 0;
802            while (strlen($deviation_bitstream) > 0) {
803                $parsed_frame[$reference_counter]['bytedeviation'] = bindec(substr($deviation_bitstream, 0, $parsed_frame['bitsforbytesdeviation']));
804                $parsed_frame[$reference_counter]['msdeviation']   = bindec(substr($deviation_bitstream, $parsed_frame['bitsforbytesdeviation'], $parsed_frame['bitsformsdeviation']));
805                $deviation_bitstream = substr($deviation_bitstream, $parsed_frame['bitsforbytesdeviation'] + $parsed_frame['bitsformsdeviation']);
806                $reference_counter++;
807            }
808            unset($parsed_frame['data']);
809            return true;
810        }
811
812
813        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'SYTC')) ||    // 4.7   SYTC Synchronised tempo codes
814            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'STC'))) {     // 4.8   STC  Synchronised tempo codes
815           
816            //   There may only be one 'SYTC' frame in each tag
817            // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
818            // Time stamp format   $xx
819            // Tempo data          <binary data>
820            //   Where time stamp format is:
821            // $01  (32-bit value) MPEG frames from beginning of file
822            // $02  (32-bit value) milliseconds from beginning of file
823
824            $frame_offset = 0;
825            $parsed_frame['timestampformat'] = ord($parsed_frame['data']{$frame_offset++});
826            $timestamp_counter = 0;
827            while ($frame_offset < strlen($parsed_frame['data'])) {
828                $parsed_frame[$timestamp_counter]['tempo'] = ord($parsed_frame['data']{$frame_offset++});
829                if ($parsed_frame[$timestamp_counter]['tempo'] == 255) {
830                    $parsed_frame[$timestamp_counter]['tempo'] += ord($parsed_frame['data']{$frame_offset++});
831                }
832                $parsed_frame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 4));
833                $frame_offset += 4;
834                $timestamp_counter++;
835            }
836            unset($parsed_frame['data']);
837            return true;
838        }
839
840 
841        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'USLT')) ||    // 4.8   USLT Unsynchronised lyric/text transcription
842            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
843           
844            //   There may be more than one 'Unsynchronised lyrics/text transcription' frame
845            //   in each tag, but only one with the same language and content descriptor.
846            // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
847            // Text encoding        $xx
848            // Language             $xx xx xx
849            // Content descriptor   <text string according to encoding> $00 (00)
850            // Lyrics/text          <full text string according to encoding>
851
852            $frame_offset = 0;
853            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
854            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
855                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
856            }
857            $frame_language = substr($parsed_frame['data'], $frame_offset, 3);
858            $frame_offset += 3;
859            if ($frame_offset > strlen($parsed_frame['data'])) {
860                $frame_offset = strlen($parsed_frame['data']) - 1;
861            }
862            $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
863            if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
864                $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
865            }
866            $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
867            if (ord($frame_description) === 0) {
868                $frame_description = '';
869            }
870            $parsed_frame['data'] = substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)));
871
872            $parsed_frame['encodingid']   = $frame_text_encoding;
873            $parsed_frame['encoding']     = $this->TextEncodingNameLookup($frame_text_encoding);
874
875            $parsed_frame['data']         = $parsed_frame['data'];
876            $parsed_frame['language']     = $frame_language;
877            $parsed_frame['languagename'] = getid3_id3v2::LanguageLookup($frame_language, false);
878            $parsed_frame['description']  = $frame_description;
879            if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
880                $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['data']);
881            }
882            unset($parsed_frame['data']);
883            return true;
884        }
885
886
887        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'SYLT')) ||    // 4.9   SYLT Synchronised lyric/text
888            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
889           
890            //   There may be more than one 'SYLT' frame in each tag,
891            //   but only one with the same language and content descriptor.
892            // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
893            // Text encoding        $xx
894            // Language             $xx xx xx
895            // Time stamp format    $xx
896            //   $01  (32-bit value) MPEG frames from beginning of file
897            //   $02  (32-bit value) milliseconds from beginning of file
898            // Content type         $xx
899            // Content descriptor   <text string according to encoding> $00 (00)
900            //   Terminated text to be synced (typically a syllable)
901            //   Sync identifier (terminator to above string)   $00 (00)
902            //   Time stamp                                     $xx (xx ...)
903
904            $frame_offset = 0;
905            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
906            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
907                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
908            }
909            $frame_language = substr($parsed_frame['data'], $frame_offset, 3);
910            $frame_offset += 3;
911            $parsed_frame['timestampformat'] = ord($parsed_frame['data']{$frame_offset++});
912            $parsed_frame['contenttypeid']   = ord($parsed_frame['data']{$frame_offset++});
913            $parsed_frame['contenttype']     = getid3_id3v2::SYTLContentTypeLookup($parsed_frame['contenttypeid']);
914            $parsed_frame['encodingid']      = $frame_text_encoding;
915            $parsed_frame['encoding']        = $this->TextEncodingNameLookup($frame_text_encoding);
916
917            $parsed_frame['language']        = $frame_language;
918            $parsed_frame['languagename']    = getid3_id3v2::LanguageLookup($frame_language, false);
919
920            $timestamp_index = 0;
921            $frame_remaining_data = substr($parsed_frame['data'], $frame_offset);
922            while (strlen($frame_remaining_data)) {
923                $frame_offset = 0;
924                $frame_terminator_pos = strpos($frame_remaining_data, getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding));
925                if ($frame_terminator_pos === false) {
926                    $frame_remaining_data = '';
927                } else {
928                    if (ord(substr($frame_remaining_data, $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
929                        $frame_terminator_pos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
930                    }
931                    $parsed_frame['lyrics'][$timestamp_index]['data'] = substr($frame_remaining_data, $frame_offset, $frame_terminator_pos - $frame_offset);
932
933                    $frame_remaining_data = substr($frame_remaining_data, $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)));
934                    if (($timestamp_index == 0) && (ord($frame_remaining_data{0}) != 0)) {
935                        // timestamp probably omitted for first data item
936                    } else {
937                        $parsed_frame['lyrics'][$timestamp_index]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remaining_data, 0, 4));
938                        $frame_remaining_data = substr($frame_remaining_data, 4);
939                    }
940                    $timestamp_index++;
941                }
942            }
943            unset($parsed_frame['data']);
944            return true;
945        }
946
947
948        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'COMM')) ||    // 4.10  COMM Comments
949            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
950           
951            //   There may be more than one comment frame in each tag,
952            //   but only one with the same language and content descriptor.
953            // <Header for 'Comment', ID: 'COMM'>
954            // Text encoding          $xx
955            // Language               $xx xx xx
956            // Short content descrip. <text string according to encoding> $00 (00)
957            // The actual text        <full text string according to encoding>
958
959            if (strlen($parsed_frame['data']) < 5) {
960
961                $getid3->warning('Invalid data (too short) for "'.$parsed_frame['frame_name'].'" frame at offset '.$parsed_frame['dataoffset']);
962                return true;
963            }
964
965            $frame_offset = 0;
966            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
967            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
968                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
969            }
970            $frame_language = substr($parsed_frame['data'], $frame_offset, 3);
971            $frame_offset += 3;
972            $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
973            if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
974                $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
975            }
976            $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
977            if (ord($frame_description) === 0) {
978                $frame_description = '';
979            }
980            $frame_text = (string)substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)));
981
982            $parsed_frame['encodingid']   = $frame_text_encoding;
983            $parsed_frame['encoding']     = $this->TextEncodingNameLookup($frame_text_encoding);
984
985            $parsed_frame['language']     = $frame_language;
986            $parsed_frame['languagename'] = getid3_id3v2::LanguageLookup($frame_language, false);
987            $parsed_frame['description']  = $frame_description;
988            $parsed_frame['data']         = $frame_text;
989            if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
990                $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['data']);
991            }
992            return true;
993        }
994           
995
996        if (($id3v2_major_version >= 4) && ($parsed_frame['frame_name'] == 'RVA2')) {   // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
997           
998            //   There may be more than one 'RVA2' frame in each tag,
999            //   but only one with the same identification string
1000            // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1001            // Identification          <text string> $00
1002            //   The 'identification' string is used to identify the situation and/or
1003            //   device where this adjustment should apply. The following is then
1004            //   repeated for every channel:
1005            // Type of channel         $xx
1006            // Volume adjustment       $xx xx
1007            // Bits representing peak  $xx
1008            // Peak volume             $xx (xx ...)
1009
1010            $frame_terminator_pos = strpos($parsed_frame['data'], "\x00");
1011            $frame_id_string = substr($parsed_frame['data'], 0, $frame_terminator_pos);
1012            if (ord($frame_id_string) === 0) {
1013                $frame_id_string = '';
1014            }
1015            $frame_remaining_data = substr($parsed_frame['data'], $frame_terminator_pos + strlen("\x00"));
1016            $parsed_frame['description'] = $frame_id_string;
1017           
1018            while (strlen($frame_remaining_data)) {
1019                $frame_offset = 0;
1020                $frame_channeltypeid = ord(substr($frame_remaining_data, $frame_offset++, 1));
1021                $parsed_frame[$frame_channeltypeid]['channeltypeid']  = $frame_channeltypeid;
1022                $parsed_frame[$frame_channeltypeid]['channeltype']    = getid3_id3v2::RVA2ChannelTypeLookup($frame_channeltypeid);
1023                $parsed_frame[$frame_channeltypeid]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remaining_data, $frame_offset, 2), true); // 16-bit signed
1024                $frame_offset += 2;
1025                $parsed_frame[$frame_channeltypeid]['bitspeakvolume'] = ord(substr($frame_remaining_data, $frame_offset++, 1));
1026                $frame_bytespeakvolume = ceil($parsed_frame[$frame_channeltypeid]['bitspeakvolume'] / 8);
1027                $parsed_frame[$frame_channeltypeid]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remaining_data, $frame_offset, $frame_bytespeakvolume));
1028                $frame_remaining_data = substr($frame_remaining_data, $frame_offset + $frame_bytespeakvolume);
1029            }
1030            unset($parsed_frame['data']);
1031            return true;
1032        }
1033       
1034
1035        if ((($id3v2_major_version == 3) && ($parsed_frame['frame_name'] == 'RVAD')) ||     // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
1036            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'RVA'))) {      // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
1037           
1038            //   There may only be one 'RVA' frame in each tag
1039            // <Header for 'Relative volume adjustment', ID: 'RVA'>
1040            // ID3v2.2 => Increment/decrement     %000000ba
1041            // ID3v2.3 => Increment/decrement     %00fedcba
1042            // Bits used for volume descr.        $xx
1043            // Relative volume change, right      $xx xx (xx ...) // a
1044            // Relative volume change, left       $xx xx (xx ...) // b
1045            // Peak volume right                  $xx xx (xx ...)
1046            // Peak volume left                   $xx xx (xx ...)
1047            //   ID3v2.3 only, optional (not present in ID3v2.2):
1048            // Relative volume change, right back $xx xx (xx ...) // c
1049            // Relative volume change, left back  $xx xx (xx ...) // d
1050            // Peak volume right back             $xx xx (xx ...)
1051            // Peak volume left back              $xx xx (xx ...)
1052            //   ID3v2.3 only, optional (not present in ID3v2.2):
1053            // Relative volume change, center     $xx xx (xx ...) // e
1054            // Peak volume center                 $xx xx (xx ...)
1055            //   ID3v2.3 only, optional (not present in ID3v2.2):
1056            // Relative volume change, bass       $xx xx (xx ...) // f
1057            // Peak volume bass                   $xx xx (xx ...)
1058
1059            $frame_offset = 0;
1060            $frame_incrdecrflags = getid3_lib::BigEndian2Bin($parsed_frame['data']{$frame_offset++});
1061            $parsed_frame['incdec']['right'] = (bool)substr($frame_incrdecrflags, 6, 1);
1062            $parsed_frame['incdec']['left']  = (bool)substr($frame_incrdecrflags, 7, 1);
1063            $parsed_frame['bitsvolume'] = ord($parsed_frame['data']{$frame_offset++});
1064            $frame_bytesvolume = ceil($parsed_frame['bitsvolume'] / 8);
1065            $parsed_frame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1066            if ($parsed_frame['incdec']['right'] === false) {
1067                $parsed_frame['volumechange']['right'] *= -1;
1068            }
1069            $frame_offset += $frame_bytesvolume;
1070            $parsed_frame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1071            if ($parsed_frame['incdec']['left'] === false) {
1072                $parsed_frame['volumechange']['left'] *= -1;
1073            }
1074            $frame_offset += $frame_bytesvolume;
1075            $parsed_frame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1076            $frame_offset += $frame_bytesvolume;
1077            $parsed_frame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1078            $frame_offset += $frame_bytesvolume;
1079            if ($id3v2_major_version == 3) {
1080                $parsed_frame['data'] = substr($parsed_frame['data'], $frame_offset);
1081                if (strlen($parsed_frame['data']) > 0) {
1082                    $parsed_frame['incdec']['rightrear'] = (bool)substr($frame_incrdecrflags, 4, 1);
1083                    $parsed_frame['incdec']['leftrear']  = (bool)substr($frame_incrdecrflags, 5, 1);
1084                    $parsed_frame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1085                    if ($parsed_frame['incdec']['rightrear'] === false) {
1086                        $parsed_frame['volumechange']['rightrear'] *= -1;
1087                    }
1088                    $frame_offset += $frame_bytesvolume;
1089                    $parsed_frame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1090                    if ($parsed_frame['incdec']['leftrear'] === false) {
1091                        $parsed_frame['volumechange']['leftrear'] *= -1;
1092                    }
1093                    $frame_offset += $frame_bytesvolume;
1094                    $parsed_frame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1095                    $frame_offset += $frame_bytesvolume;
1096                    $parsed_frame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1097                    $frame_offset += $frame_bytesvolume;
1098                }
1099                $parsed_frame['data'] = substr($parsed_frame['data'], $frame_offset);
1100                if (strlen($parsed_frame['data']) > 0) {
1101                    $parsed_frame['incdec']['center'] = (bool)substr($frame_incrdecrflags, 3, 1);
1102                    $parsed_frame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1103                    if ($parsed_frame['incdec']['center'] === false) {
1104                        $parsed_frame['volumechange']['center'] *= -1;
1105                    }
1106                    $frame_offset += $frame_bytesvolume;
1107                    $parsed_frame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1108                    $frame_offset += $frame_bytesvolume;
1109                }
1110                $parsed_frame['data'] = substr($parsed_frame['data'], $frame_offset);
1111                if (strlen($parsed_frame['data']) > 0) {
1112                    $parsed_frame['incdec']['bass'] = (bool)substr($frame_incrdecrflags, 2, 1);
1113                    $parsed_frame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1114                    if ($parsed_frame['incdec']['bass'] === false) {
1115                        $parsed_frame['volumechange']['bass'] *= -1;
1116                    }
1117                    $frame_offset += $frame_bytesvolume;
1118                    $parsed_frame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesvolume));
1119                    $frame_offset += $frame_bytesvolume;
1120                }
1121            }
1122            unset($parsed_frame['data']);
1123            return true;
1124        }
1125
1126
1127        if (($id3v2_major_version >= 4) && ($parsed_frame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
1128           
1129            //   There may be more than one 'EQU2' frame in each tag,
1130            //   but only one with the same identification string
1131            // <Header of 'Equalisation (2)', ID: 'EQU2'>
1132            // Interpolation method  $xx
1133            //   $00  Band
1134            //   $01  Linear
1135            // Identification        <text string> $00
1136            //   The following is then repeated for every adjustment point
1137            // Frequency          $xx xx
1138            // Volume adjustment  $xx xx
1139
1140            $frame_offset = 0;
1141            $frame_interpolationmethod = ord($parsed_frame['data']{$frame_offset++});
1142            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1143            $frame_id_string = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1144            if (ord($frame_id_string) === 0) {
1145                $frame_id_string = '';
1146            }
1147            $parsed_frame['description'] = $frame_id_string;
1148            $frame_remaining_data = substr($parsed_frame['data'], $frame_terminator_pos + strlen("\x00"));
1149            while (strlen($frame_remaining_data)) {
1150                $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remaining_data, 0, 2)) / 2;
1151                $parsed_frame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remaining_data, 2, 2), true);
1152                $frame_remaining_data = substr($frame_remaining_data, 4);
1153            }
1154            $parsed_frame['interpolationmethod'] = $frame_interpolationmethod;
1155            unset($parsed_frame['data']);
1156            return true;
1157        }
1158
1159
1160        if ((($id3v2_major_version == 3) && ($parsed_frame['frame_name'] == 'EQUA')) ||    // 4.12  EQUA Equalisation (ID3v2.3 only)
1161            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
1162           
1163            //   There may only be one 'EQUA' frame in each tag
1164            // <Header for 'Relative volume adjustment', ID: 'EQU'>
1165            // Adjustment bits    $xx
1166            //   This is followed by 2 bytes + ('adjustment bits' rounded up to the
1167            //   nearest byte) for every equalisation band in the following format,
1168            //   giving a frequency range of 0 - 32767Hz:
1169            // Increment/decrement   %x (MSB of the Frequency)
1170            // Frequency             (lower 15 bits)
1171            // Adjustment            $xx (xx ...)
1172
1173            $frame_offset = 0;
1174            $parsed_frame['adjustmentbits'] = $parsed_frame['data']{$frame_offset++};
1175            $frame_adjustment_bytes = ceil($parsed_frame['adjustmentbits'] / 8);
1176
1177            $frame_remaining_data = (string)substr($parsed_frame['data'], $frame_offset);
1178            while (strlen($frame_remaining_data) > 0) {
1179                $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remaining_data, 0, 2));
1180                $frame_incdec    = (bool)substr($frame_frequencystr, 0, 1);
1181                $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1182                $parsed_frame[$frame_frequency]['incdec'] = $frame_incdec;
1183                $parsed_frame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remaining_data, 2, $frame_adjustment_bytes));
1184                if ($parsed_frame[$frame_frequency]['incdec'] === false) {
1185                    $parsed_frame[$frame_frequency]['adjustment'] *= -1;
1186                }
1187                $frame_remaining_data = substr($frame_remaining_data, 2 + $frame_adjustment_bytes);
1188            }
1189            unset($parsed_frame['data']);
1190            return true;
1191        }
1192
1193
1194        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'RVRB')) ||    // 4.13  RVRB Reverb
1195            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
1196           
1197            //   There may only be one 'RVRB' frame in each tag.
1198            // <Header for 'Reverb', ID: 'RVRB'>
1199            // Reverb left (ms)                 $xx xx
1200            // Reverb right (ms)                $xx xx
1201            // Reverb bounces, left             $xx
1202            // Reverb bounces, right            $xx
1203            // Reverb feedback, left to left    $xx
1204            // Reverb feedback, left to right   $xx
1205            // Reverb feedback, right to right  $xx
1206            // Reverb feedback, right to left   $xx
1207            // Premix left to right             $xx
1208            // Premix right to left             $xx
1209
1210            $frame_offset = 0;
1211            $parsed_frame['left']  = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 2));
1212            $frame_offset += 2;
1213            $parsed_frame['right'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 2));
1214            $frame_offset += 2;
1215            $parsed_frame['bouncesL']   = ord($parsed_frame['data']{$frame_offset++});
1216            $parsed_frame['bouncesR']   = ord($parsed_frame['data']{$frame_offset++});
1217            $parsed_frame['feedbackLL'] = ord($parsed_frame['data']{$frame_offset++});
1218            $parsed_frame['feedbackLR'] = ord($parsed_frame['data']{$frame_offset++});
1219            $parsed_frame['feedbackRR'] = ord($parsed_frame['data']{$frame_offset++});
1220            $parsed_frame['feedbackRL'] = ord($parsed_frame['data']{$frame_offset++});
1221            $parsed_frame['premixLR']   = ord($parsed_frame['data']{$frame_offset++});
1222            $parsed_frame['premixRL']   = ord($parsed_frame['data']{$frame_offset++});
1223            unset($parsed_frame['data']);
1224            return true;
1225        }
1226
1227
1228        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'APIC')) ||    // 4.14  APIC Attached picture
1229           (($id3v2_major_version == 2)  && ($parsed_frame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
1230
1231            //   There may be several pictures attached to one file,
1232            //   each in their individual 'APIC' frame, but only one
1233            //   with the same content descriptor
1234            // <Header for 'Attached picture', ID: 'APIC'>
1235            // Text encoding      $xx
1236            // ID3v2.3+ => MIME type          <text string> $00
1237            // ID3v2.2  => Image format       $xx xx xx
1238            // Picture type       $xx
1239            // Description        <text string according to encoding> $00 (00)
1240            // Picture data       <binary data>
1241
1242            $frame_offset = 0;
1243            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
1244            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
1245                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1246            }
1247
1248            if ($id3v2_major_version == 2 && strlen($parsed_frame['data']) > $frame_offset) { 
1249                $frame_imagetype = substr($parsed_frame['data'], $frame_offset, 3);
1250                if (strtolower($frame_imagetype) == 'ima') {
1251                    // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1252                    // MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
1253                    $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1254                    $frame_mimetype = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1255                    if (ord($frame_mimetype) === 0) {
1256                        $frame_mimetype = '';
1257                    }
1258                    $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1259                    if ($frame_imagetype == 'JPEG') {
1260                        $frame_imagetype = 'JPG';
1261                    }
1262                    $frame_offset = $frame_terminator_pos + strlen("\x00");
1263                } else {
1264                    $frame_offset += 3;
1265                }
1266            }
1267           
1268            if ($id3v2_major_version > 2 && strlen($parsed_frame['data']) > $frame_offset) {
1269                $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1270                $frame_mimetype = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1271                if (ord($frame_mimetype) === 0) {
1272                    $frame_mimetype = '';
1273                }
1274                $frame_offset = $frame_terminator_pos + strlen("\x00");
1275            }
1276
1277            $frame_picturetype = ord($parsed_frame['data']{$frame_offset++});
1278
1279            $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
1280            if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
1281                $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
1282            }
1283            $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1284            if (ord($frame_description) === 0) {
1285                $frame_description = '';
1286            }
1287            $parsed_frame['encodingid']       = $frame_text_encoding;
1288            $parsed_frame['encoding']         = $this->TextEncodingNameLookup($frame_text_encoding);
1289
1290            if ($id3v2_major_version == 2) {
1291                $parsed_frame['imagetype']    = $frame_imagetype;
1292            } else {
1293                $parsed_frame['mime']         = $frame_mimetype;
1294            }
1295            $parsed_frame['picturetypeid']    = $frame_picturetype;
1296            $parsed_frame['picturetype']      = getid3_id3v2::APICPictureTypeLookup($frame_picturetype);
1297            $parsed_frame['description']      = $frame_description;
1298            $parsed_frame['data']             = substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)));
1299
1300            if ($getid3->option_tags_images) {
1301               
1302                $image_chunk_check = getid3_lib_image_size::get($parsed_frame['data']);
1303                if (($image_chunk_check[2] >= 1) && ($image_chunk_check[2] <= 3)) {
1304                    $parsed_frame['image_mime'] = image_type_to_mime_type($image_chunk_check[2]);
1305                   
1306                    if ($image_chunk_check[0]) {
1307                        $parsed_frame['image_width']  = $image_chunk_check[0];
1308                    }
1309                   
1310                    if ($image_chunk_check[1]) {
1311                        $parsed_frame['image_height'] = $image_chunk_check[1];
1312                    }
1313                   
1314                    $parsed_frame['image_bytes']      = strlen($parsed_frame['data']);
1315                }
1316            }
1317
1318            return true;
1319        }
1320
1321
1322        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'GEOB')) ||    // 4.15  GEOB General encapsulated object
1323            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
1324           
1325            //   There may be more than one 'GEOB' frame in each tag,
1326            //   but only one with the same content descriptor
1327            // <Header for 'General encapsulated object', ID: 'GEOB'>
1328            // Text encoding          $xx
1329            // MIME type              <text string> $00
1330            // Filename               <text string according to encoding> $00 (00)
1331            // Content description    <text string according to encoding> $00 (00)
1332            // Encapsulated object    <binary data>
1333
1334            $frame_offset = 0;
1335            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
1336            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
1337                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1338            }
1339            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1340            $frame_mimetype = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1341            if (ord($frame_mimetype) === 0) {
1342                $frame_mimetype = '';
1343            }
1344            $frame_offset = $frame_terminator_pos + strlen("\x00");
1345
1346            $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
1347            if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
1348                $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
1349            }
1350            $frame_filename = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1351            if (ord($frame_filename) === 0) {
1352                $frame_filename = '';
1353            }
1354            $frame_offset = $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding));
1355
1356            $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
1357            if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
1358                $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
1359            }
1360            $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1361            if (ord($frame_description) === 0) {
1362                $frame_description = '';
1363            }
1364            $frame_offset = $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding));
1365
1366            $parsed_frame['objectdata']  = (string)substr($parsed_frame['data'], $frame_offset);
1367            $parsed_frame['encodingid']  = $frame_text_encoding;
1368            $parsed_frame['encoding']    = $this->TextEncodingNameLookup($frame_text_encoding);
1369
1370            $parsed_frame['mime']        = $frame_mimetype;
1371            $parsed_frame['filename']    = $frame_filename;
1372            $parsed_frame['description'] = $frame_description;
1373            unset($parsed_frame['data']);
1374            return true;
1375        }
1376
1377
1378        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'PCNT')) ||     // 4.16  PCNT Play counter
1379            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'CNT'))) {      // 4.17  CNT  Play counter
1380           
1381            //   There may only be one 'PCNT' frame in each tag.
1382            //   When the counter reaches all one's, one byte is inserted in
1383            //   front of the counter thus making the counter eight bits bigger
1384            // <Header for 'Play counter', ID: 'PCNT'>
1385            // Counter        $xx xx xx xx (xx ...)
1386
1387            $parsed_frame['data'] = getid3_lib::BigEndian2Int($parsed_frame['data']);
1388            return true;
1389        }
1390
1391
1392        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'POPM')) ||      // 4.17  POPM Popularimeter
1393            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'POP'))) {       // 4.18  POP  Popularimeter
1394           
1395            //   There may be more than one 'POPM' frame in each tag,
1396            //   but only one with the same email address
1397            // <Header for 'Popularimeter', ID: 'POPM'>
1398            // Email to user   <text string> $00
1399            // Rating          $xx
1400            // Counter         $xx xx xx xx (xx ...)
1401
1402            $frame_offset = 0;
1403            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1404            $frame_email_address = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1405            if (ord($frame_email_address) === 0) {
1406                $frame_email_address = '';
1407            }
1408            $frame_offset = $frame_terminator_pos + strlen("\x00");
1409            $frame_rating = ord($parsed_frame['data']{$frame_offset++});
1410            $parsed_frame['data'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset));
1411            $parsed_frame['email']  = $frame_email_address;
1412            $parsed_frame['rating'] = $frame_rating;
1413            unset($parsed_frame['data']);
1414            return true;
1415        }
1416
1417
1418        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'RBUF')) ||     // 4.18  RBUF Recommended buffer size
1419            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'BUF'))) {      // 4.19  BUF  Recommended buffer size
1420           
1421            //   There may only be one 'RBUF' frame in each tag
1422            // <Header for 'Recommended buffer size', ID: 'RBUF'>
1423            // Buffer size               $xx xx xx
1424            // Embedded info flag        %0000000x
1425            // Offset to next tag        $xx xx xx xx
1426
1427            $frame_offset = 0;
1428            $parsed_frame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 3));
1429            $frame_offset += 3;
1430
1431            $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin($parsed_frame['data']{$frame_offset++});
1432            $parsed_frame['flags']['embededinfo'] = (bool)substr($frame_embeddedinfoflags, 7, 1);
1433            $parsed_frame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 4));
1434            unset($parsed_frame['data']);
1435            return true;
1436        }
1437
1438
1439        if (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
1440           
1441            //   There may be more than one 'CRM' frame in a tag,
1442            //   but only one with the same 'owner identifier'
1443            // <Header for 'Encrypted meta frame', ID: 'CRM'>
1444            // Owner identifier      <textstring> $00 (00)
1445            // Content/explanation   <textstring> $00 (00)
1446            // Encrypted datablock   <binary data>
1447
1448            $frame_offset = 0;
1449            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1450            $frame_owner_id = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1451            $frame_offset = $frame_terminator_pos + strlen("\x00");
1452
1453            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1454            $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1455            if (ord($frame_description) === 0) {
1456                $frame_description = '';
1457            }
1458            $frame_offset = $frame_terminator_pos + strlen("\x00");
1459
1460            $parsed_frame['ownerid']     = $frame_owner_id;
1461            $parsed_frame['data']        = (string)substr($parsed_frame['data'], $frame_offset);
1462            $parsed_frame['description'] = $frame_description;
1463            unset($parsed_frame['data']);
1464            return true;
1465        }
1466
1467
1468        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'AENC')) ||      // 4.19  AENC Audio encryption
1469            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'CRA'))) {       // 4.21  CRA  Audio encryption
1470           
1471            //   There may be more than one 'AENC' frames in a tag,
1472            //   but only one with the same 'Owner identifier'
1473            // <Header for 'Audio encryption', ID: 'AENC'>
1474            // Owner identifier   <text string> $00
1475            // Preview start      $xx xx
1476            // Preview length     $xx xx
1477            // Encryption info    <binary data>
1478
1479            $frame_offset = 0;
1480            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1481            $frame_owner_id = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1482            if (ord($frame_owner_id) === 0) {
1483                $frame_owner_id == '';
1484            }
1485            $frame_offset = $frame_terminator_pos + strlen("\x00");
1486            $parsed_frame['ownerid'] = $frame_owner_id;
1487            $parsed_frame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 2));
1488            $frame_offset += 2;
1489            $parsed_frame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 2));
1490            $frame_offset += 2;
1491            $parsed_frame['encryptioninfo'] = (string)substr($parsed_frame['data'], $frame_offset);
1492            unset($parsed_frame['data']);
1493            return true;
1494        }
1495
1496
1497        if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'LINK')) ||    // 4.20  LINK Linked information
1498            (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'LNK'))) {     // 4.22  LNK  Linked information
1499           
1500            //   There may be more than one 'LINK' frame in a tag,
1501            //   but only one with the same contents
1502            // <Header for 'Linked information', ID: 'LINK'>
1503            // ID3v2.3+ => Frame identifier   $xx xx xx xx
1504            // ID3v2.2  => Frame identifier   $xx xx xx
1505            // URL                            <text string> $00
1506            // ID and additional data         <text string(s)>
1507
1508            $frame_offset = 0;
1509            if ($id3v2_major_version == 2) {
1510                $parsed_frame['frameid'] = substr($parsed_frame['data'], $frame_offset, 3);
1511                $frame_offset += 3;
1512            } else {
1513                $parsed_frame['frameid'] = substr($parsed_frame['data'], $frame_offset, 4);
1514                $frame_offset += 4;
1515            }
1516
1517            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1518            $frame_url = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1519            if (ord($frame_url) === 0) {
1520                $frame_url = '';
1521            }
1522            $frame_offset = $frame_terminator_pos + strlen("\x00");
1523            $parsed_frame['url'] = $frame_url;
1524
1525            $parsed_frame['additionaldata'] = (string)substr($parsed_frame['data'], $frame_offset);
1526            if (!empty($parsed_frame['framenameshort']) && $parsed_frame['url']) {
1527                $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = utf8_encode($parsed_frame['url']);
1528            }
1529            unset($parsed_frame['data']);
1530            return true;
1531        }
1532
1533
1534        if (($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
1535           
1536            //   There may only be one 'POSS' frame in each tag
1537            // <Head for 'Position synchronisation', ID: 'POSS'>
1538            // Time stamp format         $xx
1539            // Position                  $xx (xx ...)
1540
1541            $frame_offset = 0;
1542            $parsed_frame['timestampformat'] = ord($parsed_frame['data']{$frame_offset++});
1543            $parsed_frame['position']        = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset));
1544            unset($parsed_frame['data']);
1545            return true;
1546        }
1547
1548
1549        if (($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
1550           
1551            //   There may be more than one 'Terms of use' frame in a tag,
1552            //   but only one with the same 'Language'
1553            // <Header for 'Terms of use frame', ID: 'USER'>
1554            // Text encoding        $xx
1555            // Language             $xx xx xx
1556            // The actual text      <text string according to encoding>
1557
1558            $frame_offset = 0;
1559            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
1560            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
1561                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1562            }
1563            $frame_language = substr($parsed_frame['data'], $frame_offset, 3);
1564            $frame_offset += 3;
1565            $parsed_frame['language']     = $frame_language;
1566            $parsed_frame['languagename'] = getid3_id3v2::LanguageLookup($frame_language, false);
1567            $parsed_frame['encodingid']   = $frame_text_encoding;
1568            $parsed_frame['encoding']     = $this->TextEncodingNameLookup($frame_text_encoding);
1569
1570            $parsed_frame['data']         = (string)substr($parsed_frame['data'], $frame_offset);
1571            if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
1572                $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['data']);
1573            }
1574            unset($parsed_frame['data']);
1575            return true;
1576        }
1577
1578
1579        if (($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
1580           
1581            //   There may only be one 'OWNE' frame in a tag
1582            // <Header for 'Ownership frame', ID: 'OWNE'>
1583            // Text encoding     $xx
1584            // Price paid        <text string> $00
1585            // Date of purch.    <text string>
1586            // Seller            <text string according to encoding>
1587
1588            $frame_offset = 0;
1589            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
1590            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
1591                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1592            }
1593            $parsed_frame['encodingid'] = $frame_text_encoding;
1594            $parsed_frame['encoding']   = $this->TextEncodingNameLookup($frame_text_encoding);
1595
1596            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1597            $frame_pricepaid = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1598            $frame_offset = $frame_terminator_pos + strlen("\x00");
1599
1600            $parsed_frame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1601            $parsed_frame['pricepaid']['currency']   = getid3_id3v2::LookupCurrencyUnits($parsed_frame['pricepaid']['currencyid']);
1602            $parsed_frame['pricepaid']['value']      = substr($frame_pricepaid, 3);
1603
1604            $parsed_frame['purchasedate'] = substr($parsed_frame['data'], $frame_offset, 8);
1605            if (!getid3_id3v2::IsValidDateStampString($parsed_frame['purchasedate'])) {
1606                $parsed_frame['purchasedateunix'] = gmmktime (0, 0, 0, substr($parsed_frame['purchasedate'], 4, 2), substr($parsed_frame['purchasedate'], 6, 2), substr($parsed_frame['purchasedate'], 0, 4));
1607            }
1608            $frame_offset += 8;
1609
1610            $parsed_frame['seller'] = (string)substr($parsed_frame['data'], $frame_offset);
1611            unset($parsed_frame['data']);
1612            return true;
1613        }
1614
1615
1616        if (($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
1617           
1618            //   There may be more than one 'commercial frame' in a tag,
1619            //   but no two may be identical
1620            // <Header for 'Commercial frame', ID: 'COMR'>
1621            // Text encoding      $xx
1622            // Price string       <text string> $00
1623            // Valid until        <text string>
1624            // Contact URL        <text string> $00
1625            // Received as        $xx
1626            // Name of seller     <text string according to encoding> $00 (00)
1627            // Description        <text string according to encoding> $00 (00)
1628            // Picture MIME type  <string> $00
1629            // Seller logo        <binary data>
1630
1631            $frame_offset = 0;
1632            $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
1633            if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
1634                $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1635            }
1636
1637            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1638            $frame_price_string = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1639            $frame_offset = $frame_terminator_pos + strlen("\x00");
1640            $frame_rawpricearray = explode('/', $frame_price_string);
1641            foreach ($frame_rawpricearray as $key => $val) {
1642                $frame_currencyid = substr($val, 0, 3);
1643                $parsed_frame['price'][$frame_currencyid]['currency'] = getid3_id3v2::LookupCurrencyUnits($frame_currencyid);
1644                $parsed_frame['price'][$frame_currencyid]['value']    = substr($val, 3);
1645            }
1646
1647            $frame_date_string = substr($parsed_frame['data'], $frame_offset, 8);
1648            $frame_offset += 8;
1649
1650            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1651            $frame_contacturl = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1652            $frame_offset = $frame_terminator_pos + strlen("\x00");
1653
1654            $frame_received_as_id = ord($parsed_frame['data']{$frame_offset++});
1655
1656            $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
1657            if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
1658                $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
1659            }
1660           
1661            $frame_sellername = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1662            if (ord($frame_sellername) === 0) {
1663                $frame_sellername = '';
1664            }
1665           
1666            $frame_offset = $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding));
1667
1668            $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
1669            if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
1670                $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
1671            }
1672           
1673            $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1674            if (ord($frame_description) === 0) {
1675                $frame_description = '';
1676            }
1677           
1678            $frame_offset = $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding));
1679
1680            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1681            $frame_mimetype = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1682            $frame_offset = $frame_terminator_pos + strlen("\x00");
1683
1684            $frame_sellerlogo = substr($parsed_frame['data'], $frame_offset);
1685
1686            $parsed_frame['encodingid']      = $frame_text_encoding;
1687            $parsed_frame['encoding']        = $this->TextEncodingNameLookup($frame_text_encoding);
1688
1689            $parsed_frame['pricevaliduntil'] = $frame_date_string;
1690            $parsed_frame['contacturl']      = $frame_contacturl;
1691            $parsed_frame['receivedasid']    = $frame_received_as_id;
1692            $parsed_frame['receivedas']      = getid3_id3v2::COMRReceivedAsLookup($frame_received_as_id);
1693            $parsed_frame['sellername']      = $frame_sellername;
1694            $parsed_frame['description']     = $frame_description;
1695            $parsed_frame['mime']            = $frame_mimetype;
1696            $parsed_frame['logo']            = $frame_sellerlogo;
1697            unset($parsed_frame['data']);
1698        }
1699
1700
1701        if (($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
1702           
1703            //   There may be several 'ENCR' frames in a tag,
1704            //   but only one containing the same symbol
1705            //   and only one containing the same owner identifier
1706            // <Header for 'Encryption method registration', ID: 'ENCR'>
1707            // Owner identifier    <text string> $00
1708            // Method symbol       $xx
1709            // Encryption data     <binary data>
1710
1711            $frame_offset = 0;
1712            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1713            $frame_owner_id = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1714            if (ord($frame_owner_id) === 0) {
1715                $frame_owner_id = '';
1716            }
1717            $frame_offset = $frame_terminator_pos + strlen("\x00");
1718
1719            $parsed_frame['ownerid']      = $frame_owner_id;
1720            $parsed_frame['methodsymbol'] = ord($parsed_frame['data']{$frame_offset++});
1721            $parsed_frame['data']         = (string)substr($parsed_frame['data'], $frame_offset);
1722            return true;
1723        }
1724       
1725
1726        if (($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)
1727
1728            //   There may be several 'GRID' frames in a tag,
1729            //   but only one containing the same symbol
1730            //   and only one containing the same owner identifier
1731            // <Header for 'Group ID registration', ID: 'GRID'>
1732            // Owner identifier      <text string> $00
1733            // Group symbol          $xx
1734            // Group dependent data  <binary data>
1735
1736            $frame_offset = 0;
1737            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1738            $frame_owner_id = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1739            if (ord($frame_owner_id) === 0) {
1740                $frame_owner_id = '';
1741            }
1742            $frame_offset = $frame_terminator_pos + strlen("\x00");
1743
1744            $parsed_frame['ownerid']       = $frame_owner_id;
1745            $parsed_frame['groupsymbol']   = ord($parsed_frame['data']{$frame_offset++});
1746            $parsed_frame['data']          = (string)substr($parsed_frame['data'], $frame_offset);
1747            return true;
1748        }
1749
1750
1751        if (($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
1752           
1753            //   The tag may contain more than one 'PRIV' frame
1754            //   but only with different contents
1755            // <Header for 'Private frame', ID: 'PRIV'>
1756            // Owner identifier      <text string> $00
1757            // The private data      <binary data>
1758
1759            $frame_offset = 0;
1760            $frame_terminator_pos = @strpos($parsed_frame['data'], "\x00", $frame_offset);
1761            $frame_owner_id = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
1762            if (ord($frame_owner_id) === 0) {
1763                $frame_owner_id = '';
1764            }
1765            $frame_offset = $frame_terminator_pos + strlen("\x00");
1766
1767            $parsed_frame['ownerid'] = $frame_owner_id;
1768            $parsed_frame['data']    = (string)substr($parsed_frame['data'], $frame_offset);
1769            return true;
1770        }
1771
1772
1773        if (($id3v2_major_version >= 4) && ($parsed_frame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
1774           
1775            //   There may be more than one 'signature frame' in a tag,
1776            //   but no two may be identical
1777            // <Header for 'Signature frame', ID: 'SIGN'>
1778            // Group symbol      $xx
1779            // Signature         <binary data>
1780
1781            $frame_offset = 0;
1782            $parsed_frame['groupsymbol'] = ord($parsed_frame['data']{$frame_offset++});
1783            $parsed_frame['data']        = (string)substr($parsed_frame['data'], $frame_offset);
1784            return true;
1785        }
1786
1787
1788        if (($id3v2_major_version >= 4) && ($parsed_frame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
1789           
1790            //   There may only be one 'seek frame' in a tag
1791            // <Header for 'Seek frame', ID: 'SEEK'>
1792            // Minimum offset to next tag       $xx xx xx xx
1793
1794            $frame_offset = 0;
1795            $parsed_frame['data'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 4));
1796            return true;
1797        }
1798
1799
1800        if (($id3v2_major_version >= 4) && ($parsed_frame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
1801           
1802            //   There may only be one 'audio seek point index' frame in a tag
1803            // <Header for 'Seek Point Index', ID: 'ASPI'>
1804            // Indexed data start (S)         $xx xx xx xx
1805            // Indexed data length (L)        $xx xx xx xx
1806            // Number of index points (N)     $xx xx
1807            // Bits per index point (b)       $xx
1808            //   Then for every index point the following data is included:
1809            // Fraction at index (Fi)          $xx (xx)
1810
1811            $frame_offset = 0;
1812            $parsed_frame['datastart'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 4));
1813            $frame_offset += 4;
1814            $parsed_frame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 4));
1815            $frame_offset += 4;
1816            $parsed_frame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 2));
1817            $frame_offset += 2;
1818            $parsed_frame['bitsperpoint'] = ord($parsed_frame['data']{$frame_offset++});
1819            $frame_bytesperpoint = ceil($parsed_frame['bitsperpoint'] / 8);
1820            for ($i = 0; $i < $frame_indexpoints; $i++) {
1821                $parsed_frame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, $frame_bytesperpoint));
1822                $frame_offset += $frame_bytesperpoint;
1823            }
1824            unset($parsed_frame['data']);
1825            return true;
1826        }
1827
1828
1829        if (($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1830
1831            // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1832            //   There may only be one 'RGAD' frame in a tag
1833            // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1834            // Peak Amplitude                      $xx $xx $xx $xx
1835            // Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
1836            // Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
1837            //   a - name code
1838            //   b - originator code
1839            //   c - sign bit
1840            //   d - replay gain adjustment
1841
1842            $frame_offset = 0;
1843           
1844            $parsed_frame['peakamplitude'] = (float)getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 4));
1845            $frame_offset += 4;
1846           
1847            $rg_track_adjustment = decbin(substr($parsed_frame['data'], $frame_offset, 2));
1848            $frame_offset += 2;
1849           
1850            $rg_album_adjustment = decbin(substr($parsed_frame['data'], $frame_offset, 2));
1851            $frame_offset += 2;
1852           
1853            $parsed_frame['raw']['track']['name']       = bindec(substr($rg_track_adjustment, 0, 3));
1854            $parsed_frame['raw']['track']['originator'] = bindec(substr($rg_track_adjustment, 3, 3));
1855            $parsed_frame['raw']['track']['signbit']    = bindec($rg_track_adjustment[6]);
1856            $parsed_frame['raw']['track']['adjustment'] = bindec(substr($rg_track_adjustment, 7, 9));
1857            $parsed_frame['raw']['album']['name']       = bindec(substr($rg_album_adjustment, 0, 3));
1858            $parsed_frame['raw']['album']['originator'] = bindec(substr($rg_album_adjustment, 3, 3));
1859            $parsed_frame['raw']['album']['signbit']    = bindec($rg_album_adjustment[6]);
1860            $parsed_frame['raw']['album']['adjustment'] = bindec(substr($rg_album_adjustment, 7, 9));
1861            $parsed_frame['track']['name']              = getid3_lib_replaygain::NameLookup($parsed_frame['raw']['track']['name']);
1862            $parsed_frame['track']['originator']        = getid3_lib_replaygain::OriginatorLookup($parsed_frame['raw']['track']['originator']);
1863            $parsed_frame['track']['adjustment']        = getid3_lib_replaygain::AdjustmentLookup($parsed_frame['raw']['track']['adjustment'], $parsed_frame['raw']['track']['signbit']);
1864            $parsed_frame['album']['name']              = getid3_lib_replaygain::NameLookup($parsed_frame['raw']['album']['name']);
1865            $parsed_frame['album']['originator']        = getid3_lib_replaygain::OriginatorLookup($parsed_frame['raw']['album']['originator']);
1866            $parsed_frame['album']['adjustment']        = getid3_lib_replaygain::AdjustmentLookup($parsed_frame['raw']['album']['adjustment'], $parsed_frame['raw']['album']['signbit']);
1867
1868            $getid3->info['replay_gain']['track']['peak']       = $parsed_frame['peakamplitude'];
1869            $getid3->info['replay_gain']['track']['originator'] = $parsed_frame['track']['originator'];
1870            $getid3->info['replay_gain']['track']['adjustment'] = $parsed_frame['track']['adjustment'];
1871            $getid3->info['replay_gain']['album']['originator'] = $parsed_frame['album']['originator'];
1872            $getid3->info['replay_gain']['album']['adjustment'] = $parsed_frame['album']['adjustment'];
1873
1874            unset($parsed_frame['data']);
1875            return true;
1876        }
1877
1878        return true;
1879    }
1880   
1881   
1882   
1883    private function TextEncodingNameLookup($encoding) {
1884       
1885        // Override specification - BRAINDEAD taggers
1886        if (!$encoding) {
1887            return $this->getid3->encoding_id3v2;
1888        }
1889           
1890        // http://www.id3.org/id3v2.4.0-structure.txt
1891        static $lookup = array (
1892            0   => 'ISO-8859-1', 
1893            1   => 'UTF-16', 
1894            2   => 'UTF-16BE', 
1895            3   => 'UTF-8', 
1896            255 => 'UTF-16BE'
1897        );
1898       
1899        return (isset($lookup[$encoding]) ? $lookup[$encoding] : 'ISO-8859-1');
1900    }
1901
1902
1903
1904    public static function ParseID3v2GenreString($genre_string) {
1905       
1906        // Parse genres into arrays of genreName and genreID
1907        // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
1908        // ID3v2.4.x: '21' $00 'Eurodisco' $00
1909
1910        $genre_string = trim($genre_string);
1911        $return_array = array ();
1912        if (strpos($genre_string, "\x00") !== false) {
1913            $unprocessed = trim($genre_string); // trailing nulls will cause an infinite loop.
1914            $genre_string = '';
1915            while (strpos($unprocessed, "\x00") !== false) {
1916                // convert null-seperated v2.4-format into v2.3 ()-seperated format
1917                $end_pos = strpos($unprocessed, "\x00");
1918                $genre_string .= '('.substr($unprocessed, 0, $end_pos).')';
1919                $unprocessed = substr($unprocessed, $end_pos + 1);
1920            }
1921            unset($unprocessed);
1922        }
1923        if (getid3_id3v1::LookupGenreID($genre_string)) {
1924
1925            $return_array['genre'][] = $genre_string;
1926
1927        } else {
1928
1929            while (strpos($genre_string, '(') !== false) {
1930
1931                $start_pos = strpos($genre_string, '(');
1932                $end_pos   = strpos($genre_string, ')');
1933                if (substr($genre_string, $start_pos + 1, 1) == '(') {
1934                    $genre_string = substr($genre_string, 0, $start_pos).substr($genre_string, $start_pos + 1);
1935                    $end_pos--;
1936                }
1937                $element      = substr($genre_string, $start_pos + 1, $end_pos - ($start_pos + 1));
1938                $genre_string = substr($genre_string, 0, $start_pos).substr($genre_string, $end_pos + 1);
1939               
1940                if (getid3_id3v1::LookupGenreName($element)) { // $element is a valid genre id/abbreviation
1941
1942                    if (empty($return_array['genre']) || !in_array(getid3_id3v1::LookupGenreName($element), $return_array['genre'])) { // avoid duplicate entires
1943                        $return_array['genre'][] = getid3_id3v1::LookupGenreName($element);
1944                    }
1945                } else {
1946
1947                    if (empty($return_array['genre']) || !in_array($element, $return_array['genre'])) { // avoid duplicate entires
1948                        $return_array['genre'][] = $element;
1949                    }
1950                }
1951            }
1952        }
1953        if ($genre_string) {
1954            if (empty($return_array['genre']) || !in_array($genre_string, $return_array['genre'])) { // avoid duplicate entires
1955                $return_array['genre'][] = $genre_string;
1956            }
1957        }
1958
1959        return $return_array;
1960    }
1961   
1962   
1963   
1964    public static function LookupCurrencyUnits($currency_id) {
1965
1966        static $lookup = array (
1967            'AED' => 'Dirhams',
1968            'AFA' => 'Afghanis',
1969            'ALL' => 'Leke',
1970            'AMD' => 'Drams',
1971            'ANG' => 'Guilders',
1972            'AOA' => 'Kwanza',
1973            'ARS' => 'Pesos',
1974            'ATS' => 'Schillings',
1975            'AUD' => 'Dollars',
1976            'AWG' => 'Guilders',
1977            'AZM' => 'Manats',
1978            'BAM' => 'Convertible Marka',
1979            'BBD' => 'Dollars',
1980            'BDT' => 'Taka',
1981            'BEF' => 'Francs',
1982            'BGL' => 'Leva',
1983            'BHD' => 'Dinars',
1984            'BIF' => 'Francs',
1985            'BMD' => 'Dollars',
1986            'BND' => 'Dollars',
1987            'BOB' => 'Bolivianos',
1988            'BRL' => 'Brazil Real',
1989            'BSD' => 'Dollars',
1990            'BTN' => 'Ngultrum',
1991            'BWP' => 'Pulas',
1992            'BYR' => 'Rubles',
1993            'BZD' => 'Dollars',
1994            'CAD' => 'Dollars',
1995            'CDF' => 'Congolese Francs',
1996            'CHF' => 'Francs',
1997            'CLP' => 'Pesos',
1998            'CNY' => 'Yuan Renminbi',
1999            'COP' => 'Pesos',
2000            'CRC' => 'Colones',
2001            'CUP' => 'Pesos',
2002            'CVE' => 'Escudos',
2003            'CYP' => 'Pounds',
2004            'CZK' => 'Koruny',
2005            'DEM' => 'Deutsche Marks',
2006            'DJF' => 'Francs',
2007            'DKK' => 'Kroner',
2008            'DOP' => 'Pesos',
2009            'DZD' => 'Algeria Dinars',
2010            'EEK' => 'Krooni',
2011            'EGP' => 'Pounds',
2012            'ERN' => 'Nakfa',
2013            'ESP' => 'Pesetas',
2014            'ETB' => 'Birr',
2015            'EUR' => 'Euro',
2016            'FIM' => 'Markkaa',
2017            'FJD' => 'Dollars',
2018            'FKP' => 'Pounds',
2019            'FRF' => 'Francs',
2020            'GBP' => 'Pounds',
2021            'GEL' => 'Lari',
2022            'GGP' => 'Pounds',
2023            'GHC' => 'Cedis',
2024            'GIP' => 'Pounds',
2025            'GMD' => 'Dalasi',
2026            'GNF' => 'Francs',
2027            'GRD' => 'Drachmae',
2028            'GTQ' => 'Quetzales',
2029            'GYD' => 'Dollars',
2030            'HKD' => 'Dollars',
2031            'HNL' => 'Lempiras',
2032            'HRK' => 'Kuna',
2033            'HTG' => 'Gourdes',
2034            'HUF' => 'Forints',
2035            'IDR' => 'Rupiahs',
2036            'IEP' => 'Pounds',
2037            'ILS' => 'New Shekels',
2038            'IMP' => 'Pounds',
2039            'INR' => 'Rupees',
2040            'IQD' => 'Dinars',
2041            'IRR' => 'Rials',
2042            'ISK' => 'Kronur',
2043            'ITL' => 'Lire',
2044            'JEP' => 'Pounds',
2045            'JMD' => 'Dollars',
2046            'JOD' => 'Dinars',
2047            'JPY' => 'Yen',
2048            'KES' => 'Shillings',
2049            'KGS' => 'Soms',
2050            'KHR' => 'Riels',
2051            'KMF' => 'Francs',
2052            'KPW' => 'Won',
2053            'KWD' => 'Dinars',
2054            'KYD' => 'Dollars',
2055            'KZT' => 'Tenge',
2056            'LAK' => 'Kips',
2057            'LBP' => 'Pounds',
2058            'LKR' => 'Rupees',
2059            'LRD' => 'Dollars',
2060            'LSL' => 'Maloti',
2061            'LTL' => 'Litai',
2062            'LUF' => 'Francs',
2063            'LVL' => 'Lati',
2064            'LYD' => 'Dinars',
2065            'MAD' => 'Dirhams',
2066            'MDL' => 'Lei',
2067            'MGF' => 'Malagasy Francs',
2068            'MKD' => 'Denars',
2069            'MMK' => 'Kyats',
2070            'MNT' => 'Tugriks',
2071            'MOP' => 'Patacas',
2072            'MRO' => 'Ouguiyas',
2073            'MTL' => 'Liri',
2074            'MUR' => 'Rupees',
2075            'MVR' => 'Rufiyaa',
2076            'MWK' => 'Kwachas',
2077            'MXN' => 'Pesos',
2078            'MYR' => 'Ringgits',
2079            'MZM' => 'Meticais',
2080            'NAD' => 'Dollars',
2081            'NGN' => 'Nairas',
2082            'NIO' => 'Gold Cordobas',
2083            'NLG' => 'Guilders',
2084            'NOK' => 'Krone',
2085            'NPR' => 'Nepal Rupees',
2086            'NZD' => 'Dollars',
2087            'OMR' => 'Rials',
2088            'PAB' => 'Balboa',
2089            'PEN' => 'Nuevos Soles',
2090            'PGK' => 'Kina',
2091            'PHP' => 'Pesos',
2092            'PKR' => 'Rupees',
2093            'PLN' => 'Zlotych',
2094            'PTE' => 'Escudos',
2095            'PYG' => 'Guarani',
2096            'QAR' => 'Rials',
2097            'ROL' => 'Lei',
2098            'RUR' => 'Rubles',
2099            'RWF' => 'Rwanda Francs',
2100            'SAR' => 'Riyals',
2101            'SBD' => 'Dollars',
2102            'SCR' => 'Rupees',
2103            'SDD' => 'Dinars',
2104            'SEK' => 'Kronor',
2105            'SGD' => 'Dollars',
2106            'SHP' => 'Pounds',
2107            'SIT' => 'Tolars',
2108            'SKK' => 'Koruny',
2109            'SLL' => 'Leones',
2110            'SOS' => 'Shillings',
2111            'SPL' => 'Luigini',
2112            'SRG' => 'Guilders',
2113            'STD' => 'Dobras',
2114            'SVC' => 'Colones',
2115            'SYP' => 'Pounds',
2116            'SZL' => 'Emalangeni',
2117            'THB' => 'Baht',
2118            'TJR' => 'Rubles',
2119            'TMM' => 'Manats',
2120            'TND' => 'Dinars',
2121            'TOP' => 'Pa\'anga',
2122            'TRL' => 'Liras',
2123            'TTD' => 'Dollars',
2124            'TVD' => 'Tuvalu Dollars',
2125            'TWD' => 'New Dollars',
2126            'TZS' => 'Shillings',
2127            'UAH' => 'Hryvnia',
2128            'UGX' => 'Shillings',
2129            'USD' => 'Dollars',
2130            'UYU' => 'Pesos',
2131            'UZS' => 'Sums',
2132            'VAL' => 'Lire',
2133            'VEB' => 'Bolivares',
2134            'VND' => 'Dong',
2135            'VUV' => 'Vatu',
2136            'WST' => 'Tala',
2137            'XAF' => 'Francs',
2138            'XAG' => 'Ounces',
2139            'XAU' => 'Ounces',
2140            'XCD' => 'Dollars',
2141            'XDR' => 'Special Drawing Rights',
2142            'XPD' => 'Ounces',
2143            'XPF' => 'Francs',
2144            'XPT' => 'Ounces',
2145            'YER' => 'Rials',
2146            'YUM' => 'New Dinars',
2147            'ZAR' => 'Rand',
2148            'ZMK' => 'Kwacha',
2149            'ZWD' => 'Zimbabwe Dollars'
2150        );
2151
2152        return @$lookup[$currency_id];
2153    }
2154
2155
2156
2157    public static function LookupCurrencyCountry($currency_id) {
2158
2159        static $lookup = array (
2160            'AED' => 'United Arab Emirates',
2161            'AFA' => 'Afghanistan',
2162            'ALL' => 'Albania',
2163            'AMD' => 'Armenia',
2164            'ANG' => 'Netherlands Antilles',
2165            'AOA' => 'Angola',
2166            'ARS' => 'Argentina',
2167            'ATS' => 'Austria',
2168            'AUD' => 'Australia',
2169            'AWG' => 'Aruba',
2170            'AZM' => 'Azerbaijan',
2171            'BAM' => 'Bosnia and Herzegovina',
2172            'BBD' => 'Barbados',
2173            'BDT' => 'Bangladesh',
2174            'BEF' => 'Belgium',
2175            'BGL' => 'Bulgaria',
2176            'BHD' => 'Bahrain',
2177            'BIF' => 'Burundi',
2178            'BMD' => 'Bermuda',
2179            'BND' => 'Brunei Darussalam',
2180            'BOB' => 'Bolivia',
2181            'BRL' => 'Brazil',
2182            'BSD' => 'Bahamas',
2183            'BTN' => 'Bhutan',
2184            'BWP' => 'Botswana',
2185            'BYR' => 'Belarus',
2186            'BZD' => 'Belize',
2187            'CAD' => 'Canada',
2188            'CDF' => 'Congo/Kinshasa',
2189            'CHF' => 'Switzerland',
2190            'CLP' => 'Chile',
2191            'CNY' => 'China',
2192            'COP' => 'Colombia',
2193            'CRC' => 'Costa Rica',
2194            'CUP' => 'Cuba',
2195            'CVE' => 'Cape Verde',
2196            'CYP' => 'Cyprus',
2197            'CZK' => 'Czech Republic',
2198            'DEM' => 'Germany',
2199            'DJF' => 'Djibouti',
2200            'DKK' => 'Denmark',
2201            'DOP' => 'Dominican Republic',
2202            'DZD' => 'Algeria',
2203            'EEK' => 'Estonia',
2204            'EGP' => 'Egypt',
2205            'ERN' => 'Eritrea',
2206            'ESP' => 'Spain',
2207            'ETB' => 'Ethiopia',
2208            'EUR' => 'Euro Member Countries',
2209            'FIM' => 'Finland',
2210            'FJD' => 'Fiji',
2211            'FKP' => 'Falkland Islands (Malvinas)',
2212            'FRF' => 'France',
2213            'GBP' => 'United Kingdom',
2214            'GEL' => 'Georgia',
2215            'GGP' => 'Guernsey',
2216            'GHC' => 'Ghana',
2217            'GIP' => 'Gibraltar',
2218            'GMD' => 'Gambia',
2219            'GNF' => 'Guinea',
2220            'GRD' => 'Greece',
2221            'GTQ' => 'Guatemala',
2222            'GYD' => 'Guyana',
2223            'HKD' => 'Hong Kong',
2224            'HNL' => 'Honduras',
2225            'HRK' => 'Croatia',
2226            'HTG' => 'Haiti',
2227            'HUF' => 'Hungary',
2228            'IDR' => 'Indonesia',
2229            'IEP' => 'Ireland (Eire)',
2230            'ILS' => 'Israel',
2231            'IMP' => 'Isle of Man',
2232            'INR' => 'India',
2233            'IQD' => 'Iraq',
2234            'IRR' => 'Iran',
2235            'ISK' => 'Iceland',
2236            'ITL' => 'Italy',
2237            'JEP' => 'Jersey',
2238            'JMD' => 'Jamaica',
2239            'JOD' => 'Jordan',
2240            'JPY' => 'Japan',
2241            'KES' => 'Kenya',
2242            'KGS' => 'Kyrgyzstan',
2243            'KHR' => 'Cambodia',
2244            'KMF' => 'Comoros',
2245            'KPW' => 'Korea',
2246            'KWD' => 'Kuwait',
2247            'KYD' => 'Cayman Islands',
2248            'KZT' => 'Kazakstan',
2249            'LAK' => 'Laos',
2250            'LBP' => 'Lebanon',
2251            'LKR' => 'Sri Lanka',
2252            'LRD' => 'Liberia',
2253            'LSL' => 'Lesotho',
2254            'LTL' => 'Lithuania',
2255            'LUF' => 'Luxembourg',
2256            'LVL' => 'Latvia',
2257            'LYD' => 'Libya',
2258            'MAD' => 'Morocco',
2259            'MDL' => 'Moldova',
2260            'MGF' => 'Madagascar',
2261            'MKD' => 'Macedonia',
2262            'MMK' => 'Myanmar (Burma)',
2263            'MNT' => 'Mongolia',
2264            'MOP' => 'Macau',
2265            'MRO' => 'Mauritania',
2266            'MTL' => 'Malta',
2267            'MUR' => 'Mauritius',
2268            'MVR' => 'Maldives (Maldive Islands)',
2269            'MWK' => 'Malawi',
2270            'MXN' => 'Mexico',
2271            'MYR' => 'Malaysia',
2272            'MZM' => 'Mozambique',
2273            'NAD' => 'Namibia',
2274            'NGN' => 'Nigeria',
2275            'NIO' => 'Nicaragua',
2276            'NLG' => 'Netherlands (Holland)',
2277            'NOK' => 'Norway',
2278            'NPR' => 'Nepal',
2279            'NZD' => 'New Zealand',
2280            'OMR' => 'Oman',
2281            'PAB' => 'Panama',
2282            'PEN' => 'Peru',
2283            'PGK' => 'Papua New Guinea',
2284            'PHP' => 'Philippines',
2285            'PKR' => 'Pakistan',
2286            'PLN' => 'Poland',
2287            'PTE' => 'Portugal',
2288            'PYG' => 'Paraguay',
2289            'QAR' => 'Qatar',
2290            'ROL' => 'Romania',
2291            'RUR' => 'Russia',
2292            'RWF' => 'Rwanda',
2293            'SAR' => 'Saudi Arabia',
2294            'SBD' => 'Solomon Islands',
2295            'SCR' => 'Seychelles',
2296            'SDD' => 'Sudan',
2297            'SEK' => 'Sweden',
2298            'SGD' => 'Singapore',
2299            'SHP' => 'Saint Helena',
2300            'SIT' => 'Slovenia',
2301            'SKK' => 'Slovakia',
2302            'SLL' => 'Sierra Leone',
2303            'SOS' => 'Somalia',
2304            'SPL' => 'Seborga',
2305            'SRG' => 'Suriname',
2306            'STD' => 'São Tome and Principe',
2307            'SVC' => 'El Salvador',
2308            'SYP' => 'Syria',
2309            'SZL' => 'Swaziland',
2310            'THB' => 'Thailand',
2311            'TJR' => 'Tajikistan',
2312            'TMM' => 'Turkmenistan',
2313            'TND' => 'Tunisia',
2314            'TOP' => 'Tonga',
2315            'TRL' => 'Turkey',
2316            'TTD' => 'Trinidad and Tobago',
2317            'TVD' => 'Tuvalu',
2318            'TWD' => 'Taiwan',
2319            'TZS' => 'Tanzania',
2320            'UAH' => 'Ukraine',
2321            'UGX' => 'Uganda',
2322            'USD' => 'United States of America',
2323            'UYU' => 'Uruguay',
2324            'UZS' => 'Uzbekistan',
2325            'VAL' => 'Vatican City',
2326            'VEB' => 'Venezuela',
2327            'VND' => 'Viet Nam',
2328            'VUV' => 'Vanuatu',
2329            'WST' => 'Samoa',
2330            'XAF' => 'Communauté Financière Africaine',
2331            'XAG' => 'Silver',
2332            'XAU' => 'Gold',
2333            'XCD' => 'East Caribbean',
2334            'XDR' => 'International Monetary Fund',
2335            'XPD' => 'Palladium',
2336            'XPF' => 'Comptoirs Français du Pacifique',
2337            'XPT' => 'Platinum',
2338            'YER' => 'Yemen',
2339            'YUM' => 'Yugoslavia',
2340            'ZAR' => 'South Africa',
2341            'ZMK' => 'Zambia',
2342            'ZWD' => 'Zimbabwe'
2343        );
2344       
2345        return @$lookup[$currency_id];
2346    }
2347
2348
2349
2350    public static function LanguageLookup($language_code, $case_sensitive=false) {
2351
2352        if (!$case_sensitive) {
2353            $language_code = strtolower($language_code);
2354        }
2355
2356        // http://www.id3.org/id3v2.4.0-structure.txt
2357        // [4.   ID3v2 frame overview]
2358        // The three byte language field, present in several frames, is used to
2359        // describe the language of the frame's content, according to ISO-639-2
2360        // [ISO-639-2]. The language should be represented in lower case. If the
2361        // language is not known the string "XXX" should be used.
2362
2363
2364        // ISO 639-2 - http://www.id3.org/iso639-2.html
2365
2366        static $lookup = array (
2367            'XXX' => 'unknown',
2368            'xxx' => 'unknown',
2369            'aar' => 'Afar',
2370            'abk' => 'Abkhazian',
2371            'ace' => 'Achinese',
2372            'ach' => 'Acoli',
2373            'ada' => 'Adangme',
2374            'afa' => 'Afro-Asiatic (Other)',
2375            'afh' => 'Afrihili',
2376            'afr' => 'Afrikaans',
2377            'aka' => 'Akan',
2378            'akk' => 'Akkadian',
2379            'alb' => 'Albanian',
2380            'ale' => 'Aleut',
2381            'alg' => 'Algonquian Languages',
2382            'amh' => 'Amharic',
2383            'ang' => 'English, Old (ca. 450-1100)',
2384            'apa' => 'Apache Languages',
2385            'ara' => 'Arabic',
2386            'arc' => 'Aramaic',
2387            'arm' => 'Armenian',
2388            'arn' => 'Araucanian',
2389            'arp' => 'Arapaho',
2390            'art' => 'Artificial (Other)',
2391            'arw' => 'Arawak',
2392            'asm' => 'Assamese',
2393            'ath' => 'Athapascan Languages',
2394            'ava' => 'Avaric',
2395            'ave' => 'Avestan',
2396            'awa' => 'Awadhi',
2397            'aym' => 'Aymara',
2398            'aze' => 'Azerbaijani',
2399            'bad' => 'Banda',
2400            'bai' => 'Bamileke Languages',
2401            'bak' => 'Bashkir',
2402            'bal' => 'Baluchi',
2403            'bam' => 'Bambara',
2404            'ban' => 'Balinese',
2405            'baq' => 'Basque',
2406            'bas' => 'Basa',
2407            'bat' => 'Baltic (Other)',
2408            'bej' => 'Beja',
2409            'bel' => 'Byelorussian',
2410            'bem' => 'Bemba',
2411            'ben' => 'Bengali',
2412            'ber' => 'Berber (Other)',
2413            'bho' => 'Bhojpuri',
2414            'bih' => 'Bihari',
2415            'bik' => 'Bikol',
2416            'bin' => 'Bini',
2417            'bis' => 'Bislama',
2418            'bla' => 'Siksika',
2419            'bnt' => 'Bantu (Other)',
2420            'bod' => 'Tibetan',
2421            'bra' => 'Braj',
2422            'bre' => 'Breton',
2423            'bua' => 'Buriat',
2424            'bug' => 'Buginese',
2425            'bul' => 'Bulgarian',
2426            'bur' => 'Burmese',
2427            'cad' => 'Caddo',
2428            'cai' => 'Central American Indian (Other)',
2429            'car' => 'Carib',
2430            'cat' => 'Catalan',
2431            'cau' => 'Caucasian (Other)',
2432            'ceb' => 'Cebuano',
2433            'cel' => 'Celtic (Other)',
2434            'ces' => 'Czech',
2435            'cha' => 'Chamorro',
2436            'chb' => 'Chibcha',
2437            'che' => 'Chechen',
2438            'chg' => 'Chagatai',
2439            'chi' => 'Chinese',
2440            'chm' => 'Mari',
2441            'chn' => 'Chinook jargon',
2442            'cho' => 'Choctaw',
2443            'chr' => 'Cherokee',
2444            'chu' => 'Church Slavic',
2445            'chv' => 'Chuvash',
2446            'chy' => 'Cheyenne',
2447            'cop' => 'Coptic',
2448            'cor' => 'Cornish',
2449            'cos' => 'Corsican',
2450            'cpe' => 'Creoles and Pidgins, English-based (Other)',
2451            'cpf' => 'Creoles and Pidgins, French-based (Other)',
2452            'cpp' => 'Creoles and Pidgins, Portuguese-based (Other)',
2453            'cre' => 'Cree',
2454            'crp' => 'Creoles and Pidgins (Other)',
2455            'cus' => 'Cushitic (Other)',
2456            'cym' => 'Welsh',
2457            'cze' => 'Czech',
2458            'dak' => 'Dakota',
2459            'dan' => 'Danish',
2460            'del' => 'Delaware',
2461            'deu' => 'German',
2462            'din' => 'Dinka',
2463            'div' => 'Divehi',
2464            'doi' => 'Dogri',
2465            'dra' => 'Dravidian (Other)',
2466            'dua' => 'Duala',
2467            'dum' => 'Dutch, Middle (ca. 1050-1350)',
2468            'dut' => 'Dutch',
2469            'dyu' => 'Dyula',
2470            'dzo' => 'Dzongkha',
2471            'efi' => 'Efik',
2472            'egy' => 'Egyptian (Ancient)',
2473            'eka' => 'Ekajuk',
2474            'ell' => 'Greek, Modern (1453-)',
2475            'elx' => 'Elamite',
2476            'eng' => 'English',
2477            'enm' => 'English, Middle (ca. 1100-1500)',
2478            'epo' => 'Esperanto',
2479            'esk' => 'Eskimo (Other)',
2480            'esl' => 'Spanish',
2481            'est' => 'Estonian',
2482            'eus' => 'Basque',
2483            'ewe' => 'Ewe',
2484            'ewo' => 'Ewondo',
2485            'fan' => 'Fang',
2486            'fao' => 'Faroese',
2487            'fas' => 'Persian',
2488            'fat' => 'Fanti',
2489            'fij' => 'Fijian',
2490            'fin' => 'Finnish',
2491            'fiu' => 'Finno-Ugrian (Other)',
2492            'fon' => 'Fon',
2493            'fra' => 'French',
2494            'fre' => 'French',
2495            'frm' => 'French, Middle (ca. 1400-1600)',
2496            'fro' => 'French, Old (842- ca. 1400)',
2497            'fry' => 'Frisian',
2498            'ful' => 'Fulah',
2499            'gaa' => 'Ga',
2500            'gae' => 'Gaelic (Scots)',
2501            'gai' => 'Irish',
2502            'gay' => 'Gayo',
2503            'gdh' => 'Gaelic (Scots)',
2504            'gem' => 'Germanic (Other)',
2505            'geo' => 'Georgian',
2506            'ger' => 'German',
2507            'gez' => 'Geez',
2508            'gil' => 'Gilbertese',
2509            'glg' => 'Gallegan',
2510            'gmh' => 'German, Middle High (ca. 1050-1500)',
2511            'goh' => 'German, Old High (ca. 750-1050)',
2512            'gon' => 'Gondi',
2513            'got' => 'Gothic',
2514            'grb' => 'Grebo',
2515            'grc' => 'Greek, Ancient (to 1453)',
2516            'gre' => 'Greek, Modern (1453-)',
2517            'grn' => 'Guarani',
2518            'guj' => 'Gujarati',
2519            'hai' => 'Haida',
2520            'hau' => 'Hausa',
2521            'haw' => 'Hawaiian',
2522            'heb' => 'Hebrew',
2523            'her' => 'Herero',
2524            'hil' => 'Hiligaynon',
2525            'him' => 'Himachali',
2526            'hin' => 'Hindi',
2527            'hmo' => 'Hiri Motu',
2528            'hun' => 'Hungarian',
2529            'hup' => 'Hupa',
2530            'hye' => 'Armenian',
2531            'iba' => 'Iban',
2532            'ibo' => 'Igbo',
2533            'ice' => 'Icelandic',
2534            'ijo' => 'Ijo',
2535            'iku' => 'Inuktitut',
2536            'ilo' => 'Iloko',
2537            'ina' => 'Interlingua (International Auxiliary language Association)',
2538            'inc' => 'Indic (Other)',
2539            'ind' => 'Indonesian',
2540            'ine' => 'Indo-European (Other)',
2541            'ine' => 'Interlingue',
2542            'ipk' => 'Inupiak',
2543            'ira' => 'Iranian (Other)',
2544            'iri' => 'Irish',
2545            'iro' => 'Iroquoian uages',
2546            'isl' => 'Icelandic',
2547            'ita' => 'Italian',
2548            'jav' => 'Javanese',
2549            'jaw' => 'Javanese',
2550            'jpn' => 'Japanese',
2551            'jpr' => 'Judeo-Persian',
2552            'jrb' => 'Judeo-Arabic',
2553            'kaa' => 'Kara-Kalpak',
2554            'kab' => 'Kabyle',
2555            'kac' => 'Kachin',
2556            'kal' => 'Greenlandic',
2557            'kam' => 'Kamba',
2558            'kan' => 'Kannada',
2559            'kar' => 'Karen',
2560            'kas' => 'Kashmiri',
2561            'kat' => 'Georgian',
2562            'kau' => 'Kanuri',
2563            'kaw' => 'Kawi',
2564            'kaz' => 'Kazakh',
2565            'kha' => 'Khasi',
2566            'khi' => 'Khoisan (Other)',
2567            'khm' => 'Khmer',
2568            'kho' => 'Khotanese',
2569            'kik' => 'Kikuyu',
2570            'kin' => 'Kinyarwanda',
2571            'kir' => 'Kirghiz',
2572            'kok' => 'Konkani',
2573            'kom' => 'Komi',
2574            'kon' => 'Kongo',
2575            'kor' => 'Korean',
2576            'kpe' => 'Kpelle',
2577            'kro' => 'Kru',
2578            'kru' => 'Kurukh',
2579            'kua' => 'Kuanyama',
2580            'kum' => 'Kumyk',
2581            'kur' => 'Kurdish',
2582            'kus' => 'Kusaie',
2583            'kut' => 'Kutenai',
2584            'lad' => 'Ladino',
2585            'lah' => 'Lahnda',
2586            'lam' => 'Lamba',
2587            'lao' => 'Lao',
2588            'lat' => 'Latin',
2589            'lav' => 'Latvian',
2590            'lez' => 'Lezghian',
2591            'lin' => 'Lingala',
2592            'lit' => 'Lithuanian',
2593            'lol' => 'Mongo',
2594            'loz' => 'Lozi',
2595            'ltz' => 'Letzeburgesch',
2596            'lub' => 'Luba-Katanga',
2597            'lug' => 'Ganda',
2598            'lui' => 'Luiseno',
2599            'lun' => 'Lunda',
2600            'luo' => 'Luo (Kenya and Tanzania)',
2601            'mac' => 'Macedonian',
2602            'mad' => 'Madurese',
2603            'mag' => 'Magahi',
2604            'mah' => 'Marshall',
2605            'mai' => 'Maithili',
2606            'mak' => 'Macedonian',
2607            'mak' => 'Makasar',
2608            'mal' => 'Malayalam',
2609            'man' => 'Mandingo',
2610            'mao' => 'Maori',
2611            'map' => 'Austronesian (Other)',
2612            'mar' => 'Marathi',
2613            'mas' => 'Masai',
2614            'max' => 'Manx',
2615            'may' => 'Malay',
2616            'men' => 'Mende',
2617            'mga' => 'Irish, Middle (900 - 1200)',
2618            'mic' => 'Micmac',
2619            'min' => 'Minangkabau',
2620            'mis' => 'Miscellaneous (Other)',
2621            'mkh' => 'Mon-Kmer (Other)',
2622            'mlg' => 'Malagasy',
2623            'mlt' => 'Maltese',
2624            'mni' => 'Manipuri',
2625            'mno' => 'Manobo Languages',
2626            'moh' => 'Mohawk',
2627            'mol' => 'Moldavian',
2628            'mon' => 'Mongolian',
2629            'mos' => 'Mossi',
2630            'mri' => 'Maori',
2631            'msa' => 'Malay',
2632            'mul' => 'Multiple Languages',
2633            'mun' => 'Munda Languages',
2634            'mus' => 'Creek',
2635            'mwr' => 'Marwari',
2636            'mya' => 'Burmese',
2637            'myn' => 'Mayan Languages',
2638            'nah' => 'Aztec',
2639            'nai' => 'North American Indian (Other)',
2640            'nau' => 'Nauru',
2641            'nav' => 'Navajo',
2642            'nbl' => 'Ndebele, South',
2643            'nde' => 'Ndebele, North',
2644            'ndo' => 'Ndongo',
2645            'nep' => 'Nepali',
2646            'new' => 'Newari',
2647            'nic' => 'Niger-Kordofanian (Other)',
2648            'niu' => 'Niuean',
2649            'nla' => 'Dutch',
2650            'nno' => 'Norwegian (Nynorsk)',
2651            'non' => 'Norse, Old',
2652            'nor' => 'Norwegian',
2653            'nso' => 'Sotho, Northern',
2654            'nub' => 'Nubian Languages',
2655            'nya' => 'Nyanja',
2656            'nym' => 'Nyamwezi',
2657            'nyn' => 'Nyankole',
2658            'nyo' => 'Nyoro',
2659            'nzi' => 'Nzima',
2660            'oci' => 'Langue d\'Oc (post 1500)',
2661            'oji' => 'Ojibwa',
2662            'ori' => 'Oriya',
2663            'orm' => 'Oromo',
2664            'osa' => 'Osage',
2665            'oss' => 'Ossetic',
2666            'ota' => 'Turkish, Ottoman (1500 - 1928)',
2667            'oto' => 'Otomian Languages',
2668            'paa' => 'Papuan-Australian (Other)',
2669            'pag' => 'Pangasinan',
2670            'pal' => 'Pahlavi',
2671            'pam' => 'Pampanga',
2672            'pan' => 'Panjabi',
2673            'pap' => 'Papiamento',
2674            'pau' => 'Palauan',
2675            'peo' => 'Persian, Old (ca 600 - 400 B.C.)',
2676            'per' => 'Persian',
2677            'phn' => 'Phoenician',
2678            'pli' => 'Pali',
2679            'pol' => 'Polish',
2680            'pon' => 'Ponape',
2681            'por' => 'Portuguese',
2682            'pra' => 'Prakrit uages',
2683            'pro' => 'Provencal, Old (to 1500)',
2684            'pus' => 'Pushto',
2685            'que' => 'Quechua',
2686            'raj' => 'Rajasthani',
2687            'rar' => 'Rarotongan',
2688            'roa' => 'Romance (Other)',
2689            'roh' => 'Rhaeto-Romance',
2690            'rom' => 'Romany',
2691            'ron' => 'Romanian',
2692            'rum' => 'Romanian',
2693            'run' => 'Rundi',
2694            'rus' => 'Russian',
2695            'sad' => 'Sandawe',
2696            'sag' => 'Sango',
2697            'sah' => 'Yakut',
2698            'sai' => 'South American Indian (Other)',
2699            'sal' => 'Salishan Languages',
2700            'sam' => 'Samaritan Aramaic',
2701            'san' => 'Sanskrit',
2702            'sco' => 'Scots',
2703            'scr' => 'Serbo-Croatian',
2704            'sel' => 'Selkup',
2705            'sem' => 'Semitic (Other)',
2706            'sga' => 'Irish, Old (to 900)',
2707            'shn' => 'Shan',
2708            'sid' => 'Sidamo',
2709            'sin' => 'Singhalese',
2710            'sio' => 'Siouan Languages',
2711            'sit' => 'Sino-Tibetan (Other)',
2712            'sla' => 'Slavic (Other)',
2713            'slk' => 'Slovak',
2714            'slo' => 'Slovak',
2715            'slv' => 'Slovenian',
2716            'smi' => 'Sami Languages',
2717            'smo' => 'Samoan',
2718            'sna' => 'Shona',
2719            'snd' => 'Sindhi',
2720            'sog' => 'Sogdian',
2721            'som' => 'Somali',
2722            'son' => 'Songhai',
2723            'sot' => 'Sotho, Southern',
2724            'spa' => 'Spanish',
2725            'sqi' => 'Albanian',
2726            'srd' => 'Sardinian',
2727            'srr' => 'Serer',
2728            'ssa' => 'Nilo-Saharan (Other)',
2729            'ssw' => 'Siswant',
2730            'ssw' => 'Swazi',
2731            'suk' => 'Sukuma',
2732            'sun' => 'Sudanese',
2733            'sus' => 'Susu',
2734            'sux' => 'Sumerian',
2735            'sve' => 'Swedish',
2736            'swa' => 'Swahili',
2737            'swe' => 'Swedish',
2738            'syr' => 'Syriac',
2739            'tah' => 'Tahitian',
2740            'tam' => 'Tamil',
2741            'tat' => 'Tatar',
2742            'tel' => 'Telugu',
2743            'tem' => 'Timne',
2744            'ter' => 'Tereno',
2745            'tgk' => 'Tajik',
2746            'tgl' => 'Tagalog',
2747            'tha' => 'Thai',
2748            'tib' => 'Tibetan',
2749            'tig' => 'Tigre',
2750            'tir' => 'Tigrinya',
2751            'tiv' => 'Tivi',
2752            'tli' => 'Tlingit',
2753            'tmh' => 'Tamashek',
2754            'tog' => 'Tonga (Nyasa)',
2755            'ton' => 'Tonga (Tonga Islands)',
2756            'tru' => 'Truk',
2757            'tsi' => 'Tsimshian',
2758            'tsn' => 'Tswana',
2759            'tso' => 'Tsonga',
2760            'tuk' => 'Turkmen',
2761            'tum' => 'Tumbuka',
2762            'tur' => 'Turkish',
2763            'tut' => 'Altaic (Other)',
2764            'twi' => 'Twi',
2765            'tyv' => 'Tuvinian',
2766            'uga' => 'Ugaritic',
2767            'uig' => 'Uighur',
2768            'ukr' => 'Ukrainian',
2769            'umb' => 'Umbundu',
2770            'und' => 'Undetermined',
2771            'urd' => 'Urdu',
2772            'uzb' => 'Uzbek',
2773            'vai' => 'Vai',
2774            'ven' => 'Venda',
2775            'vie' => 'Vietnamese',
2776            'vol' => 'Volapük',
2777            'vot' => 'Votic',
2778            'wak' => 'Wakashan Languages',
2779            'wal' => 'Walamo',
2780            'war' => 'Waray',
2781            'was' => 'Washo',
2782            'wel' => 'Welsh',
2783            'wen' => 'Sorbian Languages',
2784            'wol' => 'Wolof',
2785            'xho' => 'Xhosa',
2786            'yao' => 'Yao',
2787            'yap' => 'Yap',
2788            'yid' => 'Yiddish',
2789            'yor' => 'Yoruba',
2790            'zap' => 'Zapotec',
2791            'zen' => 'Zenaga',
2792            'zha' => 'Zhuang',
2793            'zho' => 'Chinese',
2794            'zul' => 'Zulu',
2795            'zun' => 'Zuni'
2796        );       
2797
2798        return @$lookup[$language_code];
2799    }
2800
2801
2802   
2803    public static function ETCOEventLookup($index) {
2804       
2805        if (($index >= 0x17) && ($index <= 0xDF)) {
2806            return 'reserved for future use';
2807        }
2808        if (($index >= 0xE0) && ($index <= 0xEF)) {
2809            return 'not predefined synch 0-F';
2810        }
2811        if (($index >= 0xF0) && ($index <= 0xFC)) {
2812            return 'reserved for future use';
2813        }
2814
2815        static $lookup = array (
2816            0x00 => 'padding (has no meaning)',
2817            0x01 => 'end of initial silence',
2818            0x02 => 'intro start',
2819            0x03 => 'main part start',
2820            0x04 => 'outro start',
2821            0x05 => 'outro end',
2822            0x06 => 'verse start',
2823            0x07 => 'refrain start',
2824            0x08 => 'interlude start',
2825            0x09 => 'theme start',
2826            0x0A => 'variation start',
2827            0x0B => 'key change',
2828            0x0C => 'time change',
2829            0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
2830            0x0E => 'sustained noise',
2831            0x0F => 'sustained noise end',
2832            0x10 => 'intro end',
2833            0x11 => 'main part end',
2834            0x12 => 'verse end',
2835            0x13 => 'refrain end',
2836            0x14 => 'theme end',
2837            0x15 => 'profanity',
2838            0x16 => 'profanity end',
2839            0xFD => 'audio end (start of silence)',
2840            0xFE => 'audio file ends',
2841            0xFF => 'one more byte of events follows'
2842        );
2843
2844        return @$lookup[$index];
2845    }
2846
2847
2848
2849    public static function SYTLContentTypeLookup($index) {
2850
2851        static $lookup = array (
2852            0x00 => 'other',
2853            0x01 => 'lyrics',
2854            0x02 => 'text transcription',
2855            0x03 => 'movement/part name', // (e.g. 'Adagio')
2856            0x04 => 'events',             // (e.g. 'Don Quijote enters the stage')
2857            0x05 => 'chord',              // (e.g. 'Bb F Fsus')
2858            0x06 => 'trivia/\'pop up\' information',
2859            0x07 => 'URLs to webpages',
2860            0x08 => 'URLs to images'
2861        );
2862
2863        return @$lookup[$index];
2864    }
2865
2866
2867
2868    public static function APICPictureTypeLookup($index, $return_array=false) {
2869
2870        static $lookup = array (
2871            0x00 => 'Other',
2872            0x01 => '32x32 pixels \'file icon\' (PNG only)',
2873            0x02 => 'Other file icon',
2874            0x03 => 'Cover (front)',
2875            0x04 => 'Cover (back)',
2876            0x05 => 'Leaflet page',
2877            0x06 => 'Media (e.g. label side of CD)',
2878            0x07 => 'Lead artist/lead performer/soloist',
2879            0x08 => 'Artist/performer',
2880            0x09 => 'Conductor',
2881            0x0A => 'Band/Orchestra',
2882            0x0B => 'Composer',
2883            0x0C => 'Lyricist/text writer',
2884            0x0D => 'Recording Location',
2885            0x0E => 'During recording',
2886            0x0F => 'During performance',
2887            0x10 => 'Movie/video screen capture',
2888            0x11 => 'A bright coloured fish',
2889            0x12 => 'Illustration',
2890            0x13 => 'Band/artist logotype',
2891            0x14 => 'Publisher/Studio logotype'
2892        );
2893       
2894        if ($return_array) {
2895            return $lookup;
2896        }
2897        return @$lookup[$index];
2898    }
2899
2900
2901
2902    public static function COMRReceivedAsLookup($index) {
2903
2904        static $lookup = array (
2905            0x00 => 'Other',
2906            0x01 => 'Standard CD album with other songs',
2907            0x02 => 'Compressed audio on CD',
2908            0x03 => 'File over the Internet',
2909            0x04 => 'Stream over the Internet',
2910            0x05 => 'As note sheets',
2911            0x06 => 'As note sheets in a book with other sheets',
2912            0x07 => 'Music on other media',
2913            0x08 => 'Non-musical merchandise'
2914        );
2915
2916        return (isset($lookup[$index]) ? $lookup[$index] : '');
2917    }
2918
2919
2920
2921    public static function RVA2ChannelTypeLookup($index) {
2922       
2923        static $lookup = array (
2924            0x00 => 'Other',
2925            0x01 => 'Master volume',
2926            0x02 => 'Front right',
2927            0x03 => 'Front left',
2928            0x04 => 'Back right',
2929            0x05 => 'Back left',
2930            0x06 => 'Front centre',
2931            0x07 => 'Back centre',
2932            0x08 => 'Subwoofer'
2933        );
2934
2935        return @$lookup[$index];
2936    }
2937
2938
2939
2940    public static function FrameNameLongLookup($frame_name) {
2941
2942        static $lookup = array (
2943            'AENC' => 'Audio encryption',
2944            'APIC' => 'Attached picture',
2945            'ASPI' => 'Audio seek point index',
2946            'BUF'  => 'Recommended buffer size',
2947            'CNT'  => 'Play counter',
2948            'COM'  => 'Comments',
2949            'COMM' => 'Comments',
2950            'COMR' => 'Commercial frame',
2951            'CRA'  => 'Audio encryption',
2952            'CRM'  => 'Encrypted meta frame',
2953            'ENCR' => 'Encryption method registration',
2954            'EQU'  => 'Equalisation',
2955            'EQU2' => 'Equalisation (2)',
2956            'EQUA' => 'Equalisation',
2957            'ETC'  => 'Event timing codes',
2958            'ETCO' => 'Event timing codes',
2959            'GEO'  => 'General encapsulated object',
2960            'GEOB' => 'General encapsulated object',
2961            'GRID' => 'Group identification registration',
2962            'IPL'  => 'Involved people list',
2963            'IPLS' => 'Involved people list',
2964            'LINK' => 'Linked information',
2965            'LNK'  => 'Linked information',
2966            'MCDI' => 'Music CD identifier',
2967            'MCI'  => 'Music CD Identifier',
2968            'MLL'  => 'MPEG location lookup table',
2969            'MLLT' => 'MPEG location lookup table',
2970            'OWNE' => 'Ownership frame',
2971            'PCNT' => 'Play counter',
2972            'PIC'  => 'Attached picture',
2973            'POP'  => 'Popularimeter',
2974            'POPM' => 'Popularimeter',
2975            'POSS' => 'Position synchronisation frame',
2976            'PRIV' => 'Private frame',
2977            'RBUF' => 'Recommended buffer size',
2978            'REV'  => 'Reverb',
2979            'RVA'  => 'Relative volume adjustment',
2980            'RVA2' => 'Relative volume adjustment (2)',
2981            'RVAD' => 'Relative volume adjustment',
2982            'RVRB' => 'Reverb',
2983            'SEEK' => 'Seek frame',
2984            'SIGN' => 'Signature frame',
2985            'SLT'  => 'Synchronised lyric/text',
2986            'STC'  => 'Synced tempo codes',
2987            'SYLT' => 'Synchronised lyric/text',
2988            'SYTC' => 'Synchronised tempo codes',
2989            'TAL'  => 'Album/Movie/Show title',
2990            'TALB' => 'Album/Movie/Show title',
2991            'TBP'  => 'BPM (Beats Per Minute)',
2992            'TBPM' => 'BPM (beats per minute)',
2993            'TCM'  => 'Composer',
2994            'TCO'  => 'Content type',
2995            'TCOM' => 'Composer',
2996            'TCON' => 'Content type',
2997            'TCOP' => 'Copyright message',
2998            'TCR'  => 'Copyright message',
2999            'TDA'  => 'Date',
3000            'TDAT' => 'Date',
3001            'TDEN' => 'Encoding time',
3002            'TDLY' => 'Playlist delay',
3003            'TDOR' => 'Original release time',
3004            'TDRC' => 'Recording time',
3005            'TDRL' => 'Release time',
3006            'TDTG' => 'Tagging time',
3007            'TDY'  => 'Playlist delay',
3008            'TEN'  => 'Encoded by',
3009            'TENC' => 'Encoded by',
3010            'TEXT' => 'Lyricist/Text writer',
3011            'TFLT' => 'File type',
3012            'TFT'  => 'File type',
3013            'TIM'  => 'Time',
3014            'TIME' => 'Time',
3015            'TIPL' => 'Involved people list',
3016            'TIT1' => 'Content group description',
3017            'TIT2' => 'Title/songname/content description',
3018            'TIT3' => 'Subtitle/Description refinement',
3019            'TKE'  => 'Initial key',
3020            'TKEY' => 'Initial key',
3021            'TLA'  => 'Language(s)',
3022            'TLAN' => 'Language(s)',
3023            'TLE'  => 'Length',
3024            'TLEN' => 'Length',
3025            'TMCL' => 'Musician credits list',
3026            'TMED' => 'Media type',
3027            'TMOO' => 'Mood',
3028            'TMT'  => 'Media type',
3029            'TOA'  => 'Original artist(s)/performer(s)',
3030            'TOAL' => 'Original album/movie/show title',
3031            'TOF'  => 'Original filename',
3032            'TOFN' => 'Original filename',
3033            'TOL'  => 'Original Lyricist(s)/text writer(s)',
3034            'TOLY' => 'Original lyricist(s)/text writer(s)',
3035            'TOPE' => 'Original artist(s)/performer(s)',
3036            'TOR'  => 'Original release year',
3037            'TORY' => 'Original release year',
3038            'TOT'  => 'Original album/Movie/Show title',
3039            'TOWN' => 'File owner/licensee',
3040            'TP1'  => 'Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group',
3041            'TP2'  => 'Band/Orchestra/Accompaniment',
3042            'TP3'  => 'Conductor/Performer refinement',
3043            'TP4'  => 'Interpreted, remixed, or otherwise modified by',
3044            'TPA'  => 'Part of a set',
3045            'TPB'  => 'Publisher',
3046            'TPE1' => 'Lead performer(s)/Soloist(s)',
3047            'TPE2' => 'Band/orchestra/accompaniment',
3048            'TPE3' => 'Conductor/performer refinement',
3049            'TPE4' => 'Interpreted, remixed, or otherwise modified by',
3050            'TPOS' => 'Part of a set',
3051            'TPRO' => 'Produced notice',
3052            'TPUB' => 'Publisher',
3053            'TRC'  => 'ISRC (International Standard Recording Code)',
3054            'TRCK' => 'Track number/Position in set',
3055            'TRD'  => 'Recording dates',
3056            'TRDA' => 'Recording dates',
3057            'TRK'  => 'Track number/Position in set',
3058            'TRSN' => 'Internet radio station name',
3059            'TRSO' => 'Internet radio station owner',
3060            'TSI'  => 'Size',
3061            'TSIZ' => 'Size',
3062            'TSOA' => 'Album sort order',
3063            'TSOP' => 'Performer sort order',
3064            'TSOT' => 'Title sort order',
3065            'TSRC' => 'ISRC (international standard recording code)',
3066            'TSS'  => 'Software/hardware and settings used for encoding',
3067            'TSSE' => 'Software/Hardware and settings used for encoding',
3068            'TSST' => 'Set subtitle',
3069            'TT1'  => 'Content group description',
3070            'TT2'  => 'Title/Songname/Content description',
3071            'TT3'  => 'Subtitle/Description refinement',
3072            'TXT'  => 'Lyricist/text writer',
3073            'TXX'  => 'User defined text information frame',
3074            'TXXX' => 'User defined text information frame',
3075            'TYE'  => 'Year',
3076            'TYER' => 'Year',
3077            'UFI'  => 'Unique file identifier',
3078            'UFID' => 'Unique file identifier',
3079            'ULT'  => 'Unsychronised lyric/text transcription',
3080            'USER' => 'Terms of use',
3081            'USLT' => 'Unsynchronised lyric/text transcription',
3082            'WAF'  => 'Official audio file webpage',
3083            'WAR'  => 'Official artist/performer webpage',
3084            'WAS'  => 'Official audio source webpage',
3085            'WCM'  => 'Commercial information',
3086            'WCOM' => 'Commercial information',
3087            'WCOP' => 'Copyright/Legal information',
3088            'WCP'  => 'Copyright/Legal information',
3089            'WOAF' => 'Official audio file webpage',
3090            'WOAR' => 'Official artist/performer webpage',
3091            'WOAS' => 'Official audio source webpage',
3092            'WORS' => 'Official Internet radio station homepage',
3093            'WPAY' => 'Payment',
3094            'WPB'  => 'Publishers official webpage',
3095            'WPUB' => 'Publishers official webpage',
3096            'WXX'  => 'User defined URL link frame',
3097            'WXXX' => 'User defined URL link frame',
3098            'TFEA' => 'Featured Artist',
3099            'TSTU' => 'Recording Studio',
3100            'rgad' => 'Replay Gain Adjustment'
3101        );       
3102       
3103        return @$lookup[$frame_name];
3104
3105        // Last three:
3106        // from Helium2 [www.helium2.com]
3107        // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3108    }
3109
3110
3111    public static function FrameNameShortLookup($frame_name) {
3112
3113        static $lookup = array (
3114            'COM'  => 'comment',
3115            'COMM' => 'comment',
3116            'TAL'  => 'album',
3117            'TALB' => 'album',
3118            'TBP'  => 'bpm',
3119            'TBPM' => 'bpm',
3120            'TCM'  => 'composer',
3121            'TCO'  => 'genre',
3122            'TCOM' => 'composer',
3123            'TCON' => 'genre',
3124            'TCOP' => 'copyright',
3125            'TCR'  => 'copyright',
3126            'TEN'  => 'encoded_by',
3127            'TENC' => 'encoded_by',
3128            'TEXT' => 'lyricist',
3129            'TIT1' => 'description',
3130            'TIT2' => 'title',
3131            'TIT3' => 'subtitle',
3132            'TLA'  => 'language',
3133            'TLAN' => 'language',
3134            'TLE'  => 'length',
3135            'TLEN' => 'length',
3136            'TMOO' => 'mood',
3137            'TOA'  => 'original_artist',
3138            'TOAL' => 'original_album',
3139            'TOF'  => 'original_filename',
3140            'TOFN' => 'original_filename',
3141            'TOL'  => 'original_lyricist',
3142            'TOLY' => 'original_lyricist',
3143            'TOPE' => 'original_artist',
3144            'TOT'  => 'original_album',
3145            'TP1'  => 'artist',
3146            'TP2'  => 'band',
3147            'TP3'  => 'conductor',
3148            'TP4'  => 'remixer',
3149            'TPB'  => 'publisher',
3150            'TPE1' => 'artist',
3151            'TPE2' => 'band',
3152            'TPE3' => 'conductor',
3153            'TPE4' => 'remixer',
3154            'TPUB' => 'publisher',
3155            'TRC'  => 'isrc',
3156            'TRCK' => 'track',
3157            'TRK'  => 'track',
3158            'TSI'  => 'size',
3159            'TSIZ' => 'size',
3160            'TSRC' => 'isrc',
3161            'TSS'  => 'encoder_settings',
3162            'TSSE' => 'encoder_settings',
3163            'TSST' => 'subtitle',
3164            'TT1'  => 'description',
3165            'TT2'  => 'title',
3166            'TT3'  => 'subtitle',
3167            'TXT'  => 'lyricist',
3168            'TXX'  => 'text',
3169            'TXXX' => 'text',
3170            'TYE'  => 'year',
3171            'TYER' => 'year',
3172            'UFI'  => 'unique_file_identifier',
3173            'UFID' => 'unique_file_identifier',
3174            'ULT'  => 'unsychronised_lyric',
3175            'USER' => 'terms_of_use',
3176            'USLT' => 'unsynchronised lyric',
3177            'WAF'  => 'url_file',
3178            'WAR'  => 'url_artist',
3179            'WAS'  => 'url_source',
3180            'WCOP' => 'copyright',
3181            'WCP'  => 'copyright',
3182            'WOAF' => 'url_file',
3183            'WOAR' => 'url_artist',
3184            'WOAS' => 'url_source',
3185            'WORS' => 'url_station',
3186            'WPB'  => 'url_publisher',
3187            'WPUB' => 'url_publisher',
3188            'WXX'  => 'url_user',
3189            'WXXX' => 'url_user',
3190            'TFEA' => 'featured_artist',
3191            'TSTU' => 'studio'
3192        );
3193
3194        return @$lookup[$frame_name];
3195    }
3196
3197
3198
3199    public static function TextEncodingTerminatorLookup($encoding) {
3200
3201        // http://www.id3.org/id3v2.4.0-structure.txt
3202        // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3203        // $00  ISO-8859-1. Terminated with $00.
3204        // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3205        // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3206        // $03  UTF-8 encoded Unicode. Terminated with $00.
3207
3208        static $lookup = array (
3209            0   => "\x00", 
3210            1   => "\x00\x00", 
3211            2   => "\x00\x00", 
3212            3   => "\x00", 
3213            255 => "\x00\x00"
3214        );
3215
3216        return @$lookup[$encoding];
3217    }
3218
3219
3220
3221    public static function IsValidID3v2FrameName($frame_name, $id3v2_major_version) {
3222
3223        switch ($id3v2_major_version) {
3224            case 2:
3225                return preg_match('/[A-Z][A-Z0-9]{2}/', $frame_name);
3226
3227            case 3:
3228            case 4:
3229                return preg_match('/[A-Z][A-Z0-9]{3}/', $frame_name);
3230        }
3231        return false;
3232    }
3233
3234
3235
3236    public static function IsValidDateStampString($date_stamp) {
3237
3238        if (strlen($date_stamp) != 8) {
3239            return false;
3240        }
3241        if ((int)$date_stamp) {
3242            return false;
3243        }
3244       
3245        $year  = substr($date_stamp, 0, 4);
3246        $month = substr($date_stamp, 4, 2);
3247        $day   = substr($date_stamp, 6, 2);
3248        if (!$year  ||  !$month  ||  !$day  ||  $month > 12  ||  $day > 31 ) {
3249            return false;
3250        }
3251        if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3252            return false;
3253        }
3254        if (($day > 29) && ($month == 2)) {
3255            return false;
3256        }
3257        return true;
3258    }
3259   
3260   
3261
3262    public static function array_merge_noclobber($array1, $array2) {
3263        if (!is_array($array1) || !is_array($array2)) {
3264            return false;
3265        }
3266        $newarray = $array1;
3267        foreach ($array2 as $key => $val) {
3268            if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
3269                $newarray[$key] = getid3_id3v2::array_merge_noclobber($newarray[$key], $val);
3270            } elseif (!isset($newarray[$key])) {
3271                $newarray[$key] = $val;
3272            }
3273        }
3274        return $newarray;
3275    }
3276
3277
3278}
3279
3280?>
Note: See TracBrowser for help on using the repository browser.