source: extensions/charlies_content/getid3/getid3/write.id3v2.php

Last change on this file was 3544, checked in by vdigital, 15 years ago

Change: getid3 upgraded to -> 1.7.9

  • Property svn:eol-style set to LF
  • Property svn:keywords set to Author Date Id Revision
File size: 99.8 KB
Line 
1<?php
2/////////////////////////////////////////////////////////////////
3/// getID3() by James Heinrich <info@getid3.org>               //
4//  available at http://getid3.sourceforge.net                 //
5//            or http://www.getid3.org                         //
6/////////////////////////////////////////////////////////////////
7// See readme.txt for more details                             //
8/////////////////////////////////////////////////////////////////
9///                                                            //
10// write.id3v2.php                                             //
11// module for writing ID3v2 tags                               //
12// dependencies: module.tag.id3v2.php                          //
13//                                                            ///
14/////////////////////////////////////////////////////////////////
15
16getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
17
18class getid3_write_id3v2
19{
20        var $filename;
21        var $tag_data;
22        var $paddedlength                = 4096;     // minimum length of ID3v2 tag in bytes
23        var $majorversion                = 3;        // ID3v2 major version (2, 3 (recommended), 4)
24        var $minorversion                = 0;        // ID3v2 minor version - always 0
25        var $merge_existing_data         = false;    // if true, merge new data with existing tags; if false, delete old tag data and only write new tags
26        var $id3v2_default_encodingid    = 0;        // default text encoding (ISO-8859-1) if not explicitly passed
27        var $id3v2_use_unsynchronisation = false;    // the specs say it should be TRUE, but most other ID3v2-aware programs are broken if unsynchronization is used, so by default don't use it.
28        var $warnings                    = array();  // any non-critical errors will be stored here
29        var $errors                      = array();  // any critical errors will be stored here
30
31        function getid3_write_id3v2() {
32                return true;
33        }
34
35        function WriteID3v2() {
36                // File MUST be writeable - CHMOD(646) at least. It's best if the
37                // directory is also writeable, because that method is both faster and less susceptible to errors.
38
39                if (is_writeable($this->filename) || (!file_exists($this->filename) && is_writeable(dirname($this->filename)))) {
40                        // Initialize getID3 engine
41                        $getID3 = new getID3;
42                        $OldThisFileInfo = $getID3->analyze($this->filename);
43                        if ($OldThisFileInfo['filesize'] >= pow(2, 31)) {
44                                $this->errors[] = 'Unable to write ID3v2 because file is larger than 2GB';
45                                fclose($fp_source);
46                                return false;
47                        }
48                        if ($this->merge_existing_data) {
49                                // merge with existing data
50                                if (!empty($OldThisFileInfo['id3v2'])) {
51                                        $this->tag_data = $this->array_join_merge($OldThisFileInfo['id3v2'], $this->tag_data);
52                                }
53                        }
54                        $this->paddedlength = max(@$OldThisFileInfo['id3v2']['headerlength'], $this->paddedlength);
55
56                        if ($NewID3v2Tag = $this->GenerateID3v2Tag()) {
57
58                                if (file_exists($this->filename) && is_writeable($this->filename) && isset($OldThisFileInfo['id3v2']['headerlength']) && ($OldThisFileInfo['id3v2']['headerlength'] == strlen($NewID3v2Tag))) {
59
60                                        // best and fastest method - insert-overwrite existing tag (padded to length of old tag if neccesary)
61                                        if (file_exists($this->filename)) {
62
63                                                ob_start();
64                                                if ($fp = fopen($this->filename, 'r+b')) {
65                                                        rewind($fp);
66                                                        fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
67                                                        fclose($fp);
68                                                } else {
69                                                        $this->errors[] = 'Could not open '.$this->filename.' mode "r+b" - '.strip_tags(ob_get_contents());
70                                                }
71                                                ob_end_clean();
72
73                                        } else {
74
75                                                ob_start();
76                                                if ($fp = fopen($this->filename, 'wb')) {
77                                                        rewind($fp);
78                                                        fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
79                                                        fclose($fp);
80                                                } else {
81                                                        $this->errors[] = 'Could not open '.$this->filename.' mode "wb" - '.strip_tags(ob_get_contents());
82                                                }
83                                                ob_end_clean();
84
85                                        }
86
87                                } else {
88
89                                        if ($tempfilename = tempnam('*', 'getID3')) {
90                                                ob_start();
91                                                if ($fp_source = fopen($this->filename, 'rb')) {
92                                                        if ($fp_temp = fopen($tempfilename, 'wb')) {
93
94                                                                fwrite($fp_temp, $NewID3v2Tag, strlen($NewID3v2Tag));
95
96                                                                rewind($fp_source);
97                                                                if (!empty($OldThisFileInfo['avdataoffset'])) {
98                                                                        fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
99                                                                }
100
101                                                                while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) {
102                                                                        fwrite($fp_temp, $buffer, strlen($buffer));
103                                                                }
104
105                                                                fclose($fp_temp);
106                                                                fclose($fp_source);
107                                                                copy($tempfilename, $this->filename);
108                                                                unlink($tempfilename);
109                                                                ob_end_clean();
110                                                                return true;
111
112                                                        } else {
113
114                                                                $this->errors[] = 'Could not open '.$tempfilename.' mode "wb" - '.strip_tags(ob_get_contents());
115
116                                                        }
117                                                        fclose($fp_source);
118
119                                                } else {
120
121                                                        $this->errors[] = 'Could not open '.$this->filename.' mode "rb" - '.strip_tags(ob_get_contents());
122
123                                                }
124                                                ob_end_clean();
125                                        }
126                                        return false;
127
128                                }
129
130                        } else {
131
132                                $this->errors[] = '$this->GenerateID3v2Tag() failed';
133
134                        }
135
136                        if (!empty($this->errors)) {
137                                return false;
138                        }
139                        return true;
140                } else {
141                        $this->errors[] = '!is_writeable('.$this->filename.')';
142                }
143                return false;
144        }
145
146        function RemoveID3v2() {
147                // File MUST be writeable - CHMOD(646) at least. It's best if the
148                // directory is also writeable, because that method is both faster and less susceptible to errors.
149                if (is_writeable(dirname($this->filename))) {
150
151                        // preferred method - only one copying operation, minimal chance of corrupting
152                        // original file if script is interrupted, but required directory to be writeable
153                        if ($fp_source = @fopen($this->filename, 'rb')) {
154                                // Initialize getID3 engine
155                                $getID3 = new getID3;
156                                $OldThisFileInfo = $getID3->analyze($this->filename);
157                                if ($OldThisFileInfo['filesize'] >= pow(2, 31)) {
158                                        $this->errors[] = 'Unable to remove ID3v2 because file is larger than 2GB';
159                                        fclose($fp_source);
160                                        return false;
161                                }
162                                rewind($fp_source);
163                                if ($OldThisFileInfo['avdataoffset'] !== false) {
164                                        fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
165                                }
166                                if ($fp_temp = @fopen($this->filename.'getid3tmp', 'w+b')) {
167                                        while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) {
168                                                fwrite($fp_temp, $buffer, strlen($buffer));
169                                        }
170                                        fclose($fp_temp);
171                                } else {
172                                        $this->errors[] = 'Could not open '.$this->filename.'getid3tmp mode "w+b"';
173                                }
174                                fclose($fp_source);
175                        } else {
176                                $this->errors[] = 'Could not open '.$this->filename.' mode "rb"';
177                        }
178                        if (file_exists($this->filename)) {
179                                unlink($this->filename);
180                        }
181                        rename($this->filename.'getid3tmp', $this->filename);
182
183                } elseif (is_writable($this->filename)) {
184
185                        // less desirable alternate method - double-copies the file, overwrites original file
186                        // and could corrupt source file if the script is interrupted or an error occurs.
187                        if ($fp_source = @fopen($this->filename, 'rb')) {
188                                // Initialize getID3 engine
189                                $getID3 = new getID3;
190                                $OldThisFileInfo = $getID3->analyze($this->filename);
191                                if ($OldThisFileInfo['filesize'] >= pow(2, 31)) {
192                                        $this->errors[] = 'Unable to remove ID3v2 because file is larger than 2GB';
193                                        fclose($fp_source);
194                                        return false;
195                                }
196                                rewind($fp_source);
197                                if ($OldThisFileInfo['avdataoffset'] !== false) {
198                                        fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
199                                }
200                                if ($fp_temp = tmpfile()) {
201                                        while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) {
202                                                fwrite($fp_temp, $buffer, strlen($buffer));
203                                        }
204                                        fclose($fp_source);
205                                        if ($fp_source = @fopen($this->filename, 'wb')) {
206                                                rewind($fp_temp);
207                                                while ($buffer = fread($fp_temp, GETID3_FREAD_BUFFER_SIZE)) {
208                                                        fwrite($fp_source, $buffer, strlen($buffer));
209                                                }
210                                                fseek($fp_temp, -128, SEEK_END);
211                                                fclose($fp_source);
212                                        } else {
213                                                $this->errors[] = 'Could not open '.$this->filename.' mode "wb"';
214                                        }
215                                        fclose($fp_temp);
216                                } else {
217                                        $this->errors[] = 'Could not create tmpfile()';
218                                }
219                        } else {
220                                $this->errors[] = 'Could not open '.$this->filename.' mode "rb"';
221                        }
222
223                } else {
224
225                        $this->errors[] = 'Directory and file both not writeable';
226
227                }
228
229                if (!empty($this->errors)) {
230                        return false;
231                }
232                return true;
233        }
234
235
236        function GenerateID3v2TagFlags($flags) {
237                switch ($this->majorversion) {
238                        case 4:
239                                // %abcd0000
240                                $flag  = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
241                                $flag .= (@$flags['extendedheader']    ? '1' : '0'); // b - Extended header
242                                $flag .= (@$flags['experimental']      ? '1' : '0'); // c - Experimental indicator
243                                $flag .= (@$flags['footer']            ? '1' : '0'); // d - Footer present
244                                $flag .= '0000';
245                                break;
246
247                        case 3:
248                                // %abc00000
249                                $flag  = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
250                                $flag .= (@$flags['extendedheader']    ? '1' : '0'); // b - Extended header
251                                $flag .= (@$flags['experimental']      ? '1' : '0'); // c - Experimental indicator
252                                $flag .= '00000';
253                                break;
254
255                        case 2:
256                                // %ab000000
257                                $flag  = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
258                                $flag .= (@$flags['compression']       ? '1' : '0'); // b - Compression
259                                $flag .= '000000';
260                                break;
261
262                        default:
263                                return false;
264                                break;
265                }
266                return chr(bindec($flag));
267        }
268
269
270        function GenerateID3v2FrameFlags($TagAlter=false, $FileAlter=false, $ReadOnly=false, $Compression=false, $Encryption=false, $GroupingIdentity=false, $Unsynchronisation=false, $DataLengthIndicator=false) {
271                switch ($this->majorversion) {
272                        case 4:
273                                // %0abc0000 %0h00kmnp
274                                $flag1  = '0';
275                                $flag1 .= $TagAlter  ? '1' : '0'; // a - Tag alter preservation (true == discard)
276                                $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard)
277                                $flag1 .= $ReadOnly  ? '1' : '0'; // c - Read only (true == read only)
278                                $flag1 .= '0000';
279
280                                $flag2  = '0';
281                                $flag2 .= $GroupingIdentity    ? '1' : '0'; // h - Grouping identity (true == contains group information)
282                                $flag2 .= '00';
283                                $flag2 .= $Compression         ? '1' : '0'; // k - Compression (true == compressed)
284                                $flag2 .= $Encryption          ? '1' : '0'; // m - Encryption (true == encrypted)
285                                $flag2 .= $Unsynchronisation   ? '1' : '0'; // n - Unsynchronisation (true == unsynchronised)
286                                $flag2 .= $DataLengthIndicator ? '1' : '0'; // p - Data length indicator (true == data length indicator added)
287                                break;
288
289                        case 3:
290                                // %abc00000 %ijk00000
291                                $flag1  = $TagAlter  ? '1' : '0';  // a - Tag alter preservation (true == discard)
292                                $flag1 .= $FileAlter ? '1' : '0';  // b - File alter preservation (true == discard)
293                                $flag1 .= $ReadOnly  ? '1' : '0';  // c - Read only (true == read only)
294                                $flag1 .= '00000';
295
296                                $flag2  = $Compression      ? '1' : '0';      // i - Compression (true == compressed)
297                                $flag2 .= $Encryption       ? '1' : '0';      // j - Encryption (true == encrypted)
298                                $flag2 .= $GroupingIdentity ? '1' : '0';      // k - Grouping identity (true == contains group information)
299                                $flag2 .= '00000';
300                                break;
301
302                        default:
303                                return false;
304                                break;
305
306                }
307                return chr(bindec($flag1)).chr(bindec($flag2));
308        }
309
310        function GenerateID3v2FrameData($frame_name, $source_data_array) {
311                if (!getid3_id3v2::IsValidID3v2FrameName($frame_name, $this->majorversion)) {
312                        return false;
313                }
314                $framedata = '';
315
316                if (($this->majorversion < 3) || ($this->majorversion > 4)) {
317
318                        $this->errors[] = 'Only ID3v2.3 and ID3v2.4 are supported in GenerateID3v2FrameData()';
319
320                } else { // $this->majorversion 3 or 4
321
322                        switch ($frame_name) {
323                                case 'UFID':
324                                        // 4.1   UFID Unique file identifier
325                                        // Owner identifier        <text string> $00
326                                        // Identifier              <up to 64 bytes binary data>
327                                        if (strlen($source_data_array['data']) > 64) {
328                                                $this->errors[] = 'Identifier not allowed to be longer than 64 bytes in '.$frame_name.' (supplied data was '.strlen($source_data_array['data']).' bytes long)';
329                                        } else {
330                                                $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00";
331                                                $framedata .= substr($source_data_array['data'], 0, 64); // max 64 bytes - truncate anything longer
332                                        }
333                                        break;
334
335                                case 'TXXX':
336                                        // 4.2.2 TXXX User defined text information frame
337                                        // Text encoding     $xx
338                                        // Description       <text string according to encoding> $00 (00)
339                                        // Value             <text string according to encoding>
340                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
341                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion)) {
342                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion;
343                                        } else {
344                                                $framedata .= chr($source_data_array['encodingid']);
345                                                $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
346                                                $framedata .= $source_data_array['data'];
347                                        }
348                                        break;
349
350                                case 'WXXX':
351                                        // 4.3.2 WXXX User defined URL link frame
352                                        // Text encoding     $xx
353                                        // Description       <text string according to encoding> $00 (00)
354                                        // URL               <text string>
355                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
356                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion)) {
357                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion;
358                                        } elseif (!isset($source_data_array['data']) || !$this->IsValidURL($source_data_array['data'], false, false)) {
359                                                //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
360                                                // probably should be an error, need to rewrite IsValidURL() to handle other encodings
361                                                $this->warnings[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
362                                        } else {
363                                                $framedata .= chr($source_data_array['encodingid']);
364                                                $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
365                                                $framedata .= $source_data_array['data'];
366                                        }
367                                        break;
368
369                                case 'IPLS':
370                                        // 4.4  IPLS Involved people list (ID3v2.3 only)
371                                        // Text encoding     $xx
372                                        // People list strings    <textstrings>
373                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
374                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion)) {
375                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion;
376                                        } else {
377                                                $framedata .= chr($source_data_array['encodingid']);
378                                                $framedata .= $source_data_array['data'];
379                                        }
380                                        break;
381
382                                case 'MCDI':
383                                        // 4.4   MCDI Music CD identifier
384                                        // CD TOC                <binary data>
385                                        $framedata .= $source_data_array['data'];
386                                        break;
387
388                                case 'ETCO':
389                                        // 4.5   ETCO Event timing codes
390                                        // Time stamp format    $xx
391                                        //   Where time stamp format is:
392                                        // $01  (32-bit value) MPEG frames from beginning of file
393                                        // $02  (32-bit value) milliseconds from beginning of file
394                                        //   Followed by a list of key events in the following format:
395                                        // Type of event   $xx
396                                        // Time stamp      $xx (xx ...)
397                                        //   The 'Time stamp' is set to zero if directly at the beginning of the sound
398                                        //   or after the previous event. All events MUST be sorted in chronological order.
399                                        if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1)) {
400                                                $this->errors[] = 'Invalid Time Stamp Format byte in '.$frame_name.' ('.$source_data_array['timestampformat'].')';
401                                        } else {
402                                                $framedata .= chr($source_data_array['timestampformat']);
403                                                foreach ($source_data_array as $key => $val) {
404                                                        if (!$this->ID3v2IsValidETCOevent($val['typeid'])) {
405                                                                $this->errors[] = 'Invalid Event Type byte in '.$frame_name.' ('.$val['typeid'].')';
406                                                        } elseif (($key != 'timestampformat') && ($key != 'flags')) {
407                                                                if (($val['timestamp'] > 0) && ($previousETCOtimestamp >= $val['timestamp'])) {
408                                                                        //   The 'Time stamp' is set to zero if directly at the beginning of the sound
409                                                                        //   or after the previous event. All events MUST be sorted in chronological order.
410                                                                        $this->errors[] = 'Out-of-order timestamp in '.$frame_name.' ('.$val['timestamp'].') for Event Type ('.$val['typeid'].')';
411                                                                } else {
412                                                                        $framedata .= chr($val['typeid']);
413                                                                        $framedata .= getid3_lib::BigEndian2String($val['timestamp'], 4, false);
414                                                                }
415                                                        }
416                                                }
417                                        }
418                                        break;
419
420                                case 'MLLT':
421                                        // 4.6   MLLT MPEG location lookup table
422                                        // MPEG frames between reference  $xx xx
423                                        // Bytes between reference        $xx xx xx
424                                        // Milliseconds between reference $xx xx xx
425                                        // Bits for bytes deviation       $xx
426                                        // Bits for milliseconds dev.     $xx
427                                        //   Then for every reference the following data is included;
428                                        // Deviation in bytes         %xxx....
429                                        // Deviation in milliseconds  %xxx....
430                                        if (($source_data_array['framesbetweenreferences'] > 0) && ($source_data_array['framesbetweenreferences'] <= 65535)) {
431                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['framesbetweenreferences'], 2, false);
432                                        } else {
433                                                $this->errors[] = 'Invalid MPEG Frames Between References in '.$frame_name.' ('.$source_data_array['framesbetweenreferences'].')';
434                                        }
435                                        if (($source_data_array['bytesbetweenreferences'] > 0) && ($source_data_array['bytesbetweenreferences'] <= 16777215)) {
436                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['bytesbetweenreferences'], 3, false);
437                                        } else {
438                                                $this->errors[] = 'Invalid bytes Between References in '.$frame_name.' ('.$source_data_array['bytesbetweenreferences'].')';
439                                        }
440                                        if (($source_data_array['msbetweenreferences'] > 0) && ($source_data_array['msbetweenreferences'] <= 16777215)) {
441                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['msbetweenreferences'], 3, false);
442                                        } else {
443                                                $this->errors[] = 'Invalid Milliseconds Between References in '.$frame_name.' ('.$source_data_array['msbetweenreferences'].')';
444                                        }
445                                        if (!$this->IsWithinBitRange($source_data_array['bitsforbytesdeviation'], 8, false)) {
446                                                if (($source_data_array['bitsforbytesdeviation'] % 4) == 0) {
447                                                        $framedata .= chr($source_data_array['bitsforbytesdeviation']);
448                                                } else {
449                                                        $this->errors[] = 'Bits For Bytes Deviation in '.$frame_name.' ('.$source_data_array['bitsforbytesdeviation'].') must be a multiple of 4.';
450                                                }
451                                        } else {
452                                                $this->errors[] = 'Invalid Bits For Bytes Deviation in '.$frame_name.' ('.$source_data_array['bitsforbytesdeviation'].')';
453                                        }
454                                        if (!$this->IsWithinBitRange($source_data_array['bitsformsdeviation'], 8, false)) {
455                                                if (($source_data_array['bitsformsdeviation'] % 4) == 0) {
456                                                        $framedata .= chr($source_data_array['bitsformsdeviation']);
457                                                } else {
458                                                        $this->errors[] = 'Bits For Milliseconds Deviation in '.$frame_name.' ('.$source_data_array['bitsforbytesdeviation'].') must be a multiple of 4.';
459                                                }
460                                        } else {
461                                                $this->errors[] = 'Invalid Bits For Milliseconds Deviation in '.$frame_name.' ('.$source_data_array['bitsformsdeviation'].')';
462                                        }
463                                        foreach ($source_data_array as $key => $val) {
464                                                if (($key != 'framesbetweenreferences') && ($key != 'bytesbetweenreferences') && ($key != 'msbetweenreferences') && ($key != 'bitsforbytesdeviation') && ($key != 'bitsformsdeviation') && ($key != 'flags')) {
465                                                        $unwrittenbitstream .= str_pad(getid3_lib::Dec2Bin($val['bytedeviation']), $source_data_array['bitsforbytesdeviation'], '0', STR_PAD_LEFT);
466                                                        $unwrittenbitstream .= str_pad(getid3_lib::Dec2Bin($val['msdeviation']),   $source_data_array['bitsformsdeviation'],    '0', STR_PAD_LEFT);
467                                                }
468                                        }
469                                        for ($i = 0; $i < strlen($unwrittenbitstream); $i += 8) {
470                                                $highnibble = bindec(substr($unwrittenbitstream, $i, 4)) << 4;
471                                                $lownibble  = bindec(substr($unwrittenbitstream, $i + 4, 4));
472                                                $framedata .= chr($highnibble & $lownibble);
473                                        }
474                                        break;
475
476                                case 'SYTC':
477                                        // 4.7   SYTC Synchronised tempo codes
478                                        // Time stamp format   $xx
479                                        // Tempo data          <binary data>
480                                        //   Where time stamp format is:
481                                        // $01  (32-bit value) MPEG frames from beginning of file
482                                        // $02  (32-bit value) milliseconds from beginning of file
483                                        if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1)) {
484                                                $this->errors[] = 'Invalid Time Stamp Format byte in '.$frame_name.' ('.$source_data_array['timestampformat'].')';
485                                        } else {
486                                                $framedata .= chr($source_data_array['timestampformat']);
487                                                foreach ($source_data_array as $key => $val) {
488                                                        if (!$this->ID3v2IsValidETCOevent($val['typeid'])) {
489                                                                $this->errors[] = 'Invalid Event Type byte in '.$frame_name.' ('.$val['typeid'].')';
490                                                        } elseif (($key != 'timestampformat') && ($key != 'flags')) {
491                                                                if (($val['tempo'] < 0) || ($val['tempo'] > 510)) {
492                                                                        $this->errors[] = 'Invalid Tempo (max = 510) in '.$frame_name.' ('.$val['tempo'].') at timestamp ('.$val['timestamp'].')';
493                                                                } else {
494                                                                        if ($val['tempo'] > 255) {
495                                                                                $framedata .= chr(255);
496                                                                                $val['tempo'] -= 255;
497                                                                        }
498                                                                        $framedata .= chr($val['tempo']);
499                                                                        $framedata .= getid3_lib::BigEndian2String($val['timestamp'], 4, false);
500                                                                }
501                                                        }
502                                                }
503                                        }
504                                        break;
505
506                                case 'USLT':
507                                        // 4.8   USLT Unsynchronised lyric/text transcription
508                                        // Text encoding        $xx
509                                        // Language             $xx xx xx
510                                        // Content descriptor   <text string according to encoding> $00 (00)
511                                        // Lyrics/text          <full text string according to encoding>
512                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
513                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) {
514                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion;
515                                        } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') {
516                                                $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')';
517                                        } else {
518                                                $framedata .= chr($source_data_array['encodingid']);
519                                                $framedata .= strtolower($source_data_array['language']);
520                                                $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
521                                                $framedata .= $source_data_array['data'];
522                                        }
523                                        break;
524
525                                case 'SYLT':
526                                        // 4.9   SYLT Synchronised lyric/text
527                                        // Text encoding        $xx
528                                        // Language             $xx xx xx
529                                        // Time stamp format    $xx
530                                        //   $01  (32-bit value) MPEG frames from beginning of file
531                                        //   $02  (32-bit value) milliseconds from beginning of file
532                                        // Content type         $xx
533                                        // Content descriptor   <text string according to encoding> $00 (00)
534                                        //   Terminated text to be synced (typically a syllable)
535                                        //   Sync identifier (terminator to above string)   $00 (00)
536                                        //   Time stamp                                     $xx (xx ...)
537                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
538                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) {
539                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion;
540                                        } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') {
541                                                $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')';
542                                        } elseif (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1)) {
543                                                $this->errors[] = 'Invalid Time Stamp Format byte in '.$frame_name.' ('.$source_data_array['timestampformat'].')';
544                                        } elseif (!$this->ID3v2IsValidSYLTtype($source_data_array['contenttypeid'])) {
545                                                $this->errors[] = 'Invalid Content Type byte in '.$frame_name.' ('.$source_data_array['contenttypeid'].')';
546                                        } elseif (!is_array($source_data_array['data'])) {
547                                                $this->errors[] = 'Invalid Lyric/Timestamp data in '.$frame_name.' (must be an array)';
548                                        } else {
549                                                $framedata .= chr($source_data_array['encodingid']);
550                                                $framedata .= strtolower($source_data_array['language']);
551                                                $framedata .= chr($source_data_array['timestampformat']);
552                                                $framedata .= chr($source_data_array['contenttypeid']);
553                                                $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
554                                                ksort($source_data_array['data']);
555                                                foreach ($source_data_array['data'] as $key => $val) {
556                                                        $framedata .= $val['data'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
557                                                        $framedata .= getid3_lib::BigEndian2String($val['timestamp'], 4, false);
558                                                }
559                                        }
560                                        break;
561
562                                case 'COMM':
563                                        // 4.10  COMM Comments
564                                        // Text encoding          $xx
565                                        // Language               $xx xx xx
566                                        // Short content descrip. <text string according to encoding> $00 (00)
567                                        // The actual text        <full text string according to encoding>
568                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
569                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) {
570                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion;
571                                        } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') {
572                                                $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')';
573                                        } else {
574                                                $framedata .= chr($source_data_array['encodingid']);
575                                                $framedata .= strtolower($source_data_array['language']);
576                                                $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
577                                                $framedata .= $source_data_array['data'];
578                                        }
579                                        break;
580
581                                case 'RVA2':
582                                        // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
583                                        // Identification          <text string> $00
584                                        //   The 'identification' string is used to identify the situation and/or
585                                        //   device where this adjustment should apply. The following is then
586                                        //   repeated for every channel:
587                                        // Type of channel         $xx
588                                        // Volume adjustment       $xx xx
589                                        // Bits representing peak  $xx
590                                        // Peak volume             $xx (xx ...)
591                                        $framedata .= str_replace("\x00", '', $source_data_array['description'])."\x00";
592                                        foreach ($source_data_array as $key => $val) {
593                                                if ($key != 'description') {
594                                                        $framedata .= chr($val['channeltypeid']);
595                                                        $framedata .= getid3_lib::BigEndian2String($val['volumeadjust'], 2, false, true); // signed 16-bit
596                                                        if (!$this->IsWithinBitRange($source_data_array['bitspeakvolume'], 8, false)) {
597                                                                $framedata .= chr($val['bitspeakvolume']);
598                                                                if ($val['bitspeakvolume'] > 0) {
599                                                                        $framedata .= getid3_lib::BigEndian2String($val['peakvolume'], ceil($val['bitspeakvolume'] / 8), false, false);
600                                                                }
601                                                        } else {
602                                                                $this->errors[] = 'Invalid Bits Representing Peak Volume in '.$frame_name.' ('.$val['bitspeakvolume'].') (range = 0 to 255)';
603                                                        }
604                                                }
605                                        }
606                                        break;
607
608                                case 'RVAD':
609                                        // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
610                                        // Increment/decrement     %00fedcba
611                                        // Bits used for volume descr.        $xx
612                                        // Relative volume change, right      $xx xx (xx ...) // a
613                                        // Relative volume change, left       $xx xx (xx ...) // b
614                                        // Peak volume right                  $xx xx (xx ...)
615                                        // Peak volume left                   $xx xx (xx ...)
616                                        // Relative volume change, right back $xx xx (xx ...) // c
617                                        // Relative volume change, left back  $xx xx (xx ...) // d
618                                        // Peak volume right back             $xx xx (xx ...)
619                                        // Peak volume left back              $xx xx (xx ...)
620                                        // Relative volume change, center     $xx xx (xx ...) // e
621                                        // Peak volume center                 $xx xx (xx ...)
622                                        // Relative volume change, bass       $xx xx (xx ...) // f
623                                        // Peak volume bass                   $xx xx (xx ...)
624                                        if (!$this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false)) {
625                                                $this->errors[] = 'Invalid Bits For Volume Description byte in '.$frame_name.' ('.$source_data_array['bitsvolume'].') (range = 1 to 255)';
626                                        } else {
627                                                $incdecflag .= '00';
628                                                $incdecflag .= $source_data_array['incdec']['right']     ? '1' : '0';     // a - Relative volume change, right
629                                                $incdecflag .= $source_data_array['incdec']['left']      ? '1' : '0';      // b - Relative volume change, left
630                                                $incdecflag .= $source_data_array['incdec']['rightrear'] ? '1' : '0'; // c - Relative volume change, right back
631                                                $incdecflag .= $source_data_array['incdec']['leftrear']  ? '1' : '0';  // d - Relative volume change, left back
632                                                $incdecflag .= $source_data_array['incdec']['center']    ? '1' : '0';    // e - Relative volume change, center
633                                                $incdecflag .= $source_data_array['incdec']['bass']      ? '1' : '0';      // f - Relative volume change, bass
634                                                $framedata .= chr(bindec($incdecflag));
635                                                $framedata .= chr($source_data_array['bitsvolume']);
636                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['right'], ceil($source_data_array['bitsvolume'] / 8), false);
637                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['left'],  ceil($source_data_array['bitsvolume'] / 8), false);
638                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['right'], ceil($source_data_array['bitsvolume'] / 8), false);
639                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['left'],  ceil($source_data_array['bitsvolume'] / 8), false);
640                                                if ($source_data_array['volumechange']['rightrear'] || $source_data_array['volumechange']['leftrear'] ||
641                                                        $source_data_array['peakvolume']['rightrear'] || $source_data_array['peakvolume']['leftrear'] ||
642                                                        $source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] ||
643                                                        $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass']) {
644                                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['rightrear'], ceil($source_data_array['bitsvolume']/8), false);
645                                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['leftrear'],  ceil($source_data_array['bitsvolume']/8), false);
646                                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['rightrear'], ceil($source_data_array['bitsvolume']/8), false);
647                                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['leftrear'],  ceil($source_data_array['bitsvolume']/8), false);
648                                                }
649                                                if ($source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] ||
650                                                        $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass']) {
651                                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['center'], ceil($source_data_array['bitsvolume']/8), false);
652                                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['center'], ceil($source_data_array['bitsvolume']/8), false);
653                                                }
654                                                if ($source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass']) {
655                                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['bass'], ceil($source_data_array['bitsvolume']/8), false);
656                                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['bass'], ceil($source_data_array['bitsvolume']/8), false);
657                                                }
658                                        }
659                                        break;
660
661                                case 'EQU2':
662                                        // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
663                                        // Interpolation method  $xx
664                                        //   $00  Band
665                                        //   $01  Linear
666                                        // Identification        <text string> $00
667                                        //   The following is then repeated for every adjustment point
668                                        // Frequency          $xx xx
669                                        // Volume adjustment  $xx xx
670                                        if (($source_data_array['interpolationmethod'] < 0) || ($source_data_array['interpolationmethod'] > 1)) {
671                                                $this->errors[] = 'Invalid Interpolation Method byte in '.$frame_name.' ('.$source_data_array['interpolationmethod'].') (valid = 0 or 1)';
672                                        } else {
673                                                $framedata .= chr($source_data_array['interpolationmethod']);
674                                                $framedata .= str_replace("\x00", '', $source_data_array['description'])."\x00";
675                                                foreach ($source_data_array['data'] as $key => $val) {
676                                                        $framedata .= getid3_lib::BigEndian2String(intval(round($key * 2)), 2, false);
677                                                        $framedata .= getid3_lib::BigEndian2String($val, 2, false, true); // signed 16-bit
678                                                }
679                                        }
680                                        break;
681
682                                case 'EQUA':
683                                        // 4.12  EQUA Equalisation (ID3v2.3 only)
684                                        // Adjustment bits    $xx
685                                        //   This is followed by 2 bytes + ('adjustment bits' rounded up to the
686                                        //   nearest byte) for every equalisation band in the following format,
687                                        //   giving a frequency range of 0 - 32767Hz:
688                                        // Increment/decrement   %x (MSB of the Frequency)
689                                        // Frequency             (lower 15 bits)
690                                        // Adjustment            $xx (xx ...)
691                                        if (!$this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false)) {
692                                                $this->errors[] = 'Invalid Adjustment Bits byte in '.$frame_name.' ('.$source_data_array['bitsvolume'].') (range = 1 to 255)';
693                                        } else {
694                                                $framedata .= chr($source_data_array['adjustmentbits']);
695                                                foreach ($source_data_array as $key => $val) {
696                                                        if ($key != 'bitsvolume') {
697                                                                if (($key > 32767) || ($key < 0)) {
698                                                                        $this->errors[] = 'Invalid Frequency in '.$frame_name.' ('.$key.') (range = 0 to 32767)';
699                                                                } else {
700                                                                        if ($val >= 0) {
701                                                                                // put MSB of frequency to 1 if increment, 0 if decrement
702                                                                                $key |= 0x8000;
703                                                                        }
704                                                                        $framedata .= getid3_lib::BigEndian2String($key, 2, false);
705                                                                        $framedata .= getid3_lib::BigEndian2String($val, ceil($source_data_array['adjustmentbits'] / 8), false);
706                                                                }
707                                                        }
708                                                }
709                                        }
710                                        break;
711
712                                case 'RVRB':
713                                        // 4.13  RVRB Reverb
714                                        // Reverb left (ms)                 $xx xx
715                                        // Reverb right (ms)                $xx xx
716                                        // Reverb bounces, left             $xx
717                                        // Reverb bounces, right            $xx
718                                        // Reverb feedback, left to left    $xx
719                                        // Reverb feedback, left to right   $xx
720                                        // Reverb feedback, right to right  $xx
721                                        // Reverb feedback, right to left   $xx
722                                        // Premix left to right             $xx
723                                        // Premix right to left             $xx
724                                        if (!$this->IsWithinBitRange($source_data_array['left'], 16, false)) {
725                                                $this->errors[] = 'Invalid Reverb Left in '.$frame_name.' ('.$source_data_array['left'].') (range = 0 to 65535)';
726                                        } elseif (!$this->IsWithinBitRange($source_data_array['right'], 16, false)) {
727                                                $this->errors[] = 'Invalid Reverb Left in '.$frame_name.' ('.$source_data_array['right'].') (range = 0 to 65535)';
728                                        } elseif (!$this->IsWithinBitRange($source_data_array['bouncesL'], 8, false)) {
729                                                $this->errors[] = 'Invalid Reverb Bounces, Left in '.$frame_name.' ('.$source_data_array['bouncesL'].') (range = 0 to 255)';
730                                        } elseif (!$this->IsWithinBitRange($source_data_array['bouncesR'], 8, false)) {
731                                                $this->errors[] = 'Invalid Reverb Bounces, Right in '.$frame_name.' ('.$source_data_array['bouncesR'].') (range = 0 to 255)';
732                                        } elseif (!$this->IsWithinBitRange($source_data_array['feedbackLL'], 8, false)) {
733                                                $this->errors[] = 'Invalid Reverb Feedback, Left-To-Left in '.$frame_name.' ('.$source_data_array['feedbackLL'].') (range = 0 to 255)';
734                                        } elseif (!$this->IsWithinBitRange($source_data_array['feedbackLR'], 8, false)) {
735                                                $this->errors[] = 'Invalid Reverb Feedback, Left-To-Right in '.$frame_name.' ('.$source_data_array['feedbackLR'].') (range = 0 to 255)';
736                                        } elseif (!$this->IsWithinBitRange($source_data_array['feedbackRR'], 8, false)) {
737                                                $this->errors[] = 'Invalid Reverb Feedback, Right-To-Right in '.$frame_name.' ('.$source_data_array['feedbackRR'].') (range = 0 to 255)';
738                                        } elseif (!$this->IsWithinBitRange($source_data_array['feedbackRL'], 8, false)) {
739                                                $this->errors[] = 'Invalid Reverb Feedback, Right-To-Left in '.$frame_name.' ('.$source_data_array['feedbackRL'].') (range = 0 to 255)';
740                                        } elseif (!$this->IsWithinBitRange($source_data_array['premixLR'], 8, false)) {
741                                                $this->errors[] = 'Invalid Premix, Left-To-Right in '.$frame_name.' ('.$source_data_array['premixLR'].') (range = 0 to 255)';
742                                        } elseif (!$this->IsWithinBitRange($source_data_array['premixRL'], 8, false)) {
743                                                $this->errors[] = 'Invalid Premix, Right-To-Left in '.$frame_name.' ('.$source_data_array['premixRL'].') (range = 0 to 255)';
744                                        } else {
745                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['left'], 2, false);
746                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['right'], 2, false);
747                                                $framedata .= chr($source_data_array['bouncesL']);
748                                                $framedata .= chr($source_data_array['bouncesR']);
749                                                $framedata .= chr($source_data_array['feedbackLL']);
750                                                $framedata .= chr($source_data_array['feedbackLR']);
751                                                $framedata .= chr($source_data_array['feedbackRR']);
752                                                $framedata .= chr($source_data_array['feedbackRL']);
753                                                $framedata .= chr($source_data_array['premixLR']);
754                                                $framedata .= chr($source_data_array['premixRL']);
755                                        }
756                                        break;
757
758                                case 'APIC':
759                                        // 4.14  APIC Attached picture
760                                        // Text encoding      $xx
761                                        // MIME type          <text string> $00
762                                        // Picture type       $xx
763                                        // Description        <text string according to encoding> $00 (00)
764                                        // Picture data       <binary data>
765                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
766                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) {
767                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion;
768                                        } elseif (!$this->ID3v2IsValidAPICpicturetype($source_data_array['picturetypeid'])) {
769                                                $this->errors[] = 'Invalid Picture Type byte in '.$frame_name.' ('.$source_data_array['picturetypeid'].') for ID3v2.'.$this->majorversion;
770                                        } elseif (($this->majorversion >= 3) && (!$this->ID3v2IsValidAPICimageformat($source_data_array['mime']))) {
771                                                $this->errors[] = 'Invalid MIME Type in '.$frame_name.' ('.$source_data_array['mime'].') for ID3v2.'.$this->majorversion;
772                                        } elseif (($source_data_array['mime'] == '-->') && (!$this->IsValidURL($source_data_array['data'], false, false))) {
773                                                //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
774                                                // probably should be an error, need to rewrite IsValidURL() to handle other encodings
775                                                $this->warnings[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
776                                        } else {
777                                                $framedata .= chr($source_data_array['encodingid']);
778                                                $framedata .= str_replace("\x00", '', $source_data_array['mime'])."\x00";
779                                                $framedata .= chr($source_data_array['picturetypeid']);
780                                                $framedata .= @$source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
781                                                $framedata .= $source_data_array['data'];
782                                        }
783                                        break;
784
785                                case 'GEOB':
786                                        // 4.15  GEOB General encapsulated object
787                                        // Text encoding          $xx
788                                        // MIME type              <text string> $00
789                                        // Filename               <text string according to encoding> $00 (00)
790                                        // Content description    <text string according to encoding> $00 (00)
791                                        // Encapsulated object    <binary data>
792                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
793                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) {
794                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion;
795                                        } elseif (!$this->IsValidMIMEstring($source_data_array['mime'])) {
796                                                $this->errors[] = 'Invalid MIME Type in '.$frame_name.' ('.$source_data_array['mime'].')';
797                                        } elseif (!$source_data_array['description']) {
798                                                $this->errors[] = 'Missing Description in '.$frame_name;
799                                        } else {
800                                                $framedata .= chr($source_data_array['encodingid']);
801                                                $framedata .= str_replace("\x00", '', $source_data_array['mime'])."\x00";
802                                                $framedata .= $source_data_array['filename'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
803                                                $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
804                                                $framedata .= $source_data_array['data'];
805                                        }
806                                        break;
807
808                                case 'PCNT':
809                                        // 4.16  PCNT Play counter
810                                        //   When the counter reaches all one's, one byte is inserted in
811                                        //   front of the counter thus making the counter eight bits bigger
812                                        // Counter        $xx xx xx xx (xx ...)
813                                        $framedata .= getid3_lib::BigEndian2String($source_data_array['data'], 4, false);
814                                        break;
815
816                                case 'POPM':
817                                        // 4.17  POPM Popularimeter
818                                        //   When the counter reaches all one's, one byte is inserted in
819                                        //   front of the counter thus making the counter eight bits bigger
820                                        // Email to user   <text string> $00
821                                        // Rating          $xx
822                                        // Counter         $xx xx xx xx (xx ...)
823                                        if (!$this->IsWithinBitRange($source_data_array['rating'], 8, false)) {
824                                                $this->errors[] = 'Invalid Rating byte in '.$frame_name.' ('.$source_data_array['rating'].') (range = 0 to 255)';
825                                        } elseif (!IsValidEmail($source_data_array['email'])) {
826                                                $this->errors[] = 'Invalid Email in '.$frame_name.' ('.$source_data_array['email'].')';
827                                        } else {
828                                                $framedata .= str_replace("\x00", '', $source_data_array['email'])."\x00";
829                                                $framedata .= chr($source_data_array['rating']);
830                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['data'], 4, false);
831                                        }
832                                        break;
833
834                                case 'RBUF':
835                                        // 4.18  RBUF Recommended buffer size
836                                        // Buffer size               $xx xx xx
837                                        // Embedded info flag        %0000000x
838                                        // Offset to next tag        $xx xx xx xx
839                                        if (!$this->IsWithinBitRange($source_data_array['buffersize'], 24, false)) {
840                                                $this->errors[] = 'Invalid Buffer Size in '.$frame_name;
841                                        } elseif (!$this->IsWithinBitRange($source_data_array['nexttagoffset'], 32, false)) {
842                                                $this->errors[] = 'Invalid Offset To Next Tag in '.$frame_name;
843                                        } else {
844                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['buffersize'], 3, false);
845                                                $flag .= '0000000';
846                                                $flag .= $source_data_array['flags']['embededinfo'] ? '1' : '0';
847                                                $framedata .= chr(bindec($flag));
848                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['nexttagoffset'], 4, false);
849                                        }
850                                        break;
851
852                                case 'AENC':
853                                        // 4.19  AENC Audio encryption
854                                        // Owner identifier   <text string> $00
855                                        // Preview start      $xx xx
856                                        // Preview length     $xx xx
857                                        // Encryption info    <binary data>
858                                        if (!$this->IsWithinBitRange($source_data_array['previewstart'], 16, false)) {
859                                                $this->errors[] = 'Invalid Preview Start in '.$frame_name.' ('.$source_data_array['previewstart'].')';
860                                        } elseif (!$this->IsWithinBitRange($source_data_array['previewlength'], 16, false)) {
861                                                $this->errors[] = 'Invalid Preview Length in '.$frame_name.' ('.$source_data_array['previewlength'].')';
862                                        } else {
863                                                $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00";
864                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['previewstart'], 2, false);
865                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['previewlength'], 2, false);
866                                                $framedata .= $source_data_array['encryptioninfo'];
867                                        }
868                                        break;
869
870                                case 'LINK':
871                                        // 4.20  LINK Linked information
872                                        // Frame identifier               $xx xx xx xx
873                                        // URL                            <text string> $00
874                                        // ID and additional data         <text string(s)>
875                                        if (!getid3_id3v2::IsValidID3v2FrameName($source_data_array['frameid'], $this->majorversion)) {
876                                                $this->errors[] = 'Invalid Frame Identifier in '.$frame_name.' ('.$source_data_array['frameid'].')';
877                                        } elseif (!$this->IsValidURL($source_data_array['data'], true, false)) {
878                                                //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
879                                                // probably should be an error, need to rewrite IsValidURL() to handle other encodings
880                                                $this->warnings[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
881                                        } elseif ((($source_data_array['frameid'] == 'AENC') || ($source_data_array['frameid'] == 'APIC') || ($source_data_array['frameid'] == 'GEOB') || ($source_data_array['frameid'] == 'TXXX')) && ($source_data_array['additionaldata'] == '')) {
882                                                $this->errors[] = 'Content Descriptor must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name;
883                                        } elseif (($source_data_array['frameid'] == 'USER') && (getid3_id3v2::LanguageLookup($source_data_array['additionaldata'], true) == '')) {
884                                                $this->errors[] = 'Language must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name;
885                                        } elseif (($source_data_array['frameid'] == 'PRIV') && ($source_data_array['additionaldata'] == '')) {
886                                                $this->errors[] = 'Owner Identifier must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name;
887                                        } elseif ((($source_data_array['frameid'] == 'COMM') || ($source_data_array['frameid'] == 'SYLT') || ($source_data_array['frameid'] == 'USLT')) && ((getid3_id3v2::LanguageLookup(substr($source_data_array['additionaldata'], 0, 3), true) == '') || (substr($source_data_array['additionaldata'], 3) == ''))) {
888                                                $this->errors[] = 'Language followed by Content Descriptor must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name;
889                                        } else {
890                                                $framedata .= $source_data_array['frameid'];
891                                                $framedata .= str_replace("\x00", '', $source_data_array['data'])."\x00";
892                                                switch ($source_data_array['frameid']) {
893                                                        case 'COMM':
894                                                        case 'SYLT':
895                                                        case 'USLT':
896                                                        case 'PRIV':
897                                                        case 'USER':
898                                                        case 'AENC':
899                                                        case 'APIC':
900                                                        case 'GEOB':
901                                                        case 'TXXX':
902                                                                $framedata .= $source_data_array['additionaldata'];
903                                                                break;
904                                                        case 'ASPI':
905                                                        case 'ETCO':
906                                                        case 'EQU2':
907                                                        case 'MCID':
908                                                        case 'MLLT':
909                                                        case 'OWNE':
910                                                        case 'RVA2':
911                                                        case 'RVRB':
912                                                        case 'SYTC':
913                                                        case 'IPLS':
914                                                        case 'RVAD':
915                                                        case 'EQUA':
916                                                                // no additional data required
917                                                                break;
918                                                        case 'RBUF':
919                                                                if ($this->majorversion == 3) {
920                                                                        // no additional data required
921                                                                } else {
922                                                                        $this->errors[] = $source_data_array['frameid'].' is not a valid Frame Identifier in '.$frame_name.' (in ID3v2.'.$this->majorversion.')';
923                                                                }
924
925                                                        default:
926                                                                if ((substr($source_data_array['frameid'], 0, 1) == 'T') || (substr($source_data_array['frameid'], 0, 1) == 'W')) {
927                                                                        // no additional data required
928                                                                } else {
929                                                                        $this->errors[] = $source_data_array['frameid'].' is not a valid Frame Identifier in '.$frame_name.' (in ID3v2.'.$this->majorversion.')';
930                                                                }
931                                                                break;
932                                                }
933                                        }
934                                        break;
935
936                                case 'POSS':
937                                        // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
938                                        // Time stamp format         $xx
939                                        // Position                  $xx (xx ...)
940                                        if (($source_data_array['timestampformat'] < 1) || ($source_data_array['timestampformat'] > 2)) {
941                                                $this->errors[] = 'Invalid Time Stamp Format in '.$frame_name.' ('.$source_data_array['timestampformat'].') (valid = 1 or 2)';
942                                        } elseif (!$this->IsWithinBitRange($source_data_array['position'], 32, false)) {
943                                                $this->errors[] = 'Invalid Position in '.$frame_name.' ('.$source_data_array['position'].') (range = 0 to 4294967295)';
944                                        } else {
945                                                $framedata .= chr($source_data_array['timestampformat']);
946                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['position'], 4, false);
947                                        }
948                                        break;
949
950                                case 'USER':
951                                        // 4.22  USER Terms of use (ID3v2.3+ only)
952                                        // Text encoding        $xx
953                                        // Language             $xx xx xx
954                                        // The actual text      <text string according to encoding>
955                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
956                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) {
957                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].')';
958                                        } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') {
959                                                $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')';
960                                        } else {
961                                                $framedata .= chr($source_data_array['encodingid']);
962                                                $framedata .= strtolower($source_data_array['language']);
963                                                $framedata .= $source_data_array['data'];
964                                        }
965                                        break;
966
967                                case 'OWNE':
968                                        // 4.23  OWNE Ownership frame (ID3v2.3+ only)
969                                        // Text encoding     $xx
970                                        // Price paid        <text string> $00
971                                        // Date of purch.    <text string>
972                                        // Seller            <text string according to encoding>
973                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
974                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) {
975                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].')';
976                                        } elseif (!$this->IsANumber($source_data_array['pricepaid']['value'], false)) {
977                                                $this->errors[] = 'Invalid Price Paid in '.$frame_name.' ('.$source_data_array['pricepaid']['value'].')';
978                                        } elseif (!$this->IsValidDateStampString($source_data_array['purchasedate'])) {
979                                                $this->errors[] = 'Invalid Date Of Purchase in '.$frame_name.' ('.$source_data_array['purchasedate'].') (format = YYYYMMDD)';
980                                        } else {
981                                                $framedata .= chr($source_data_array['encodingid']);
982                                                $framedata .= str_replace("\x00", '', $source_data_array['pricepaid']['value'])."\x00";
983                                                $framedata .= $source_data_array['purchasedate'];
984                                                $framedata .= $source_data_array['seller'];
985                                        }
986                                        break;
987
988                                case 'COMR':
989                                        // 4.24  COMR Commercial frame (ID3v2.3+ only)
990                                        // Text encoding      $xx
991                                        // Price string       <text string> $00
992                                        // Valid until        <text string>
993                                        // Contact URL        <text string> $00
994                                        // Received as        $xx
995                                        // Name of seller     <text string according to encoding> $00 (00)
996                                        // Description        <text string according to encoding> $00 (00)
997                                        // Picture MIME type  <string> $00
998                                        // Seller logo        <binary data>
999                                        $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
1000                                        if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) {
1001                                                $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].')';
1002                                        } elseif (!$this->IsValidDateStampString($source_data_array['pricevaliduntil'])) {
1003                                                $this->errors[] = 'Invalid Valid Until date in '.$frame_name.' ('.$source_data_array['pricevaliduntil'].') (format = YYYYMMDD)';
1004                                        } elseif (!$this->IsValidURL($source_data_array['contacturl'], false, true)) {
1005                                                $this->errors[] = 'Invalid Contact URL in '.$frame_name.' ('.$source_data_array['contacturl'].') (allowed schemes: http, https, ftp, mailto)';
1006                                        } elseif (!$this->ID3v2IsValidCOMRreceivedAs($source_data_array['receivedasid'])) {
1007                                                $this->errors[] = 'Invalid Received As byte in '.$frame_name.' ('.$source_data_array['contacturl'].') (range = 0 to 8)';
1008                                        } elseif (!$this->IsValidMIMEstring($source_data_array['mime'])) {
1009                                                $this->errors[] = 'Invalid MIME Type in '.$frame_name.' ('.$source_data_array['mime'].')';
1010                                        } else {
1011                                                $framedata .= chr($source_data_array['encodingid']);
1012                                                unset($pricestring);
1013                                                foreach ($source_data_array['price'] as $key => $val) {
1014                                                        if ($this->ID3v2IsValidPriceString($key.$val['value'])) {
1015                                                                $pricestrings[] = $key.$val['value'];
1016                                                        } else {
1017                                                                $this->errors[] = 'Invalid Price String in '.$frame_name.' ('.$key.$val['value'].')';
1018                                                        }
1019                                                }
1020                                                $framedata .= implode('/', $pricestrings);
1021                                                $framedata .= $source_data_array['pricevaliduntil'];
1022                                                $framedata .= str_replace("\x00", '', $source_data_array['contacturl'])."\x00";
1023                                                $framedata .= chr($source_data_array['receivedasid']);
1024                                                $framedata .= $source_data_array['sellername'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
1025                                                $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']);
1026                                                $framedata .= $source_data_array['mime']."\x00";
1027                                                $framedata .= $source_data_array['logo'];
1028                                        }
1029                                        break;
1030
1031                                case 'ENCR':
1032                                        // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
1033                                        // Owner identifier    <text string> $00
1034                                        // Method symbol       $xx
1035                                        // Encryption data     <binary data>
1036                                        if (!$this->IsWithinBitRange($source_data_array['methodsymbol'], 8, false)) {
1037                                                $this->errors[] = 'Invalid Group Symbol in '.$frame_name.' ('.$source_data_array['methodsymbol'].') (range = 0 to 255)';
1038                                        } else {
1039                                                $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00";
1040                                                $framedata .= ord($source_data_array['methodsymbol']);
1041                                                $framedata .= $source_data_array['data'];
1042                                        }
1043                                        break;
1044
1045                                case 'GRID':
1046                                        // 4.26  GRID Group identification registration (ID3v2.3+ only)
1047                                        // Owner identifier      <text string> $00
1048                                        // Group symbol          $xx
1049                                        // Group dependent data  <binary data>
1050                                        if (!$this->IsWithinBitRange($source_data_array['groupsymbol'], 8, false)) {
1051                                                $this->errors[] = 'Invalid Group Symbol in '.$frame_name.' ('.$source_data_array['groupsymbol'].') (range = 0 to 255)';
1052                                        } else {
1053                                                $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00";
1054                                                $framedata .= ord($source_data_array['groupsymbol']);
1055                                                $framedata .= $source_data_array['data'];
1056                                        }
1057                                        break;
1058
1059                                case 'PRIV':
1060                                        // 4.27  PRIV Private frame (ID3v2.3+ only)
1061                                        // Owner identifier      <text string> $00
1062                                        // The private data      <binary data>
1063                                        $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00";
1064                                        $framedata .= $source_data_array['data'];
1065                                        break;
1066
1067                                case 'SIGN':
1068                                        // 4.28  SIGN Signature frame (ID3v2.4+ only)
1069                                        // Group symbol      $xx
1070                                        // Signature         <binary data>
1071                                        if (!$this->IsWithinBitRange($source_data_array['groupsymbol'], 8, false)) {
1072                                                $this->errors[] = 'Invalid Group Symbol in '.$frame_name.' ('.$source_data_array['groupsymbol'].') (range = 0 to 255)';
1073                                        } else {
1074                                                $framedata .= ord($source_data_array['groupsymbol']);
1075                                                $framedata .= $source_data_array['data'];
1076                                        }
1077                                        break;
1078
1079                                case 'SEEK':
1080                                        // 4.29  SEEK Seek frame (ID3v2.4+ only)
1081                                        // Minimum offset to next tag       $xx xx xx xx
1082                                        if (!$this->IsWithinBitRange($source_data_array['data'], 32, false)) {
1083                                                $this->errors[] = 'Invalid Minimum Offset in '.$frame_name.' ('.$source_data_array['data'].') (range = 0 to 4294967295)';
1084                                        } else {
1085                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['data'], 4, false);
1086                                        }
1087                                        break;
1088
1089                                case 'ASPI':
1090                                        // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
1091                                        // Indexed data start (S)         $xx xx xx xx
1092                                        // Indexed data length (L)        $xx xx xx xx
1093                                        // Number of index points (N)     $xx xx
1094                                        // Bits per index point (b)       $xx
1095                                        //   Then for every index point the following data is included:
1096                                        // Fraction at index (Fi)          $xx (xx)
1097                                        if (!$this->IsWithinBitRange($source_data_array['datastart'], 32, false)) {
1098                                                $this->errors[] = 'Invalid Indexed Data Start in '.$frame_name.' ('.$source_data_array['datastart'].') (range = 0 to 4294967295)';
1099                                        } elseif (!$this->IsWithinBitRange($source_data_array['datalength'], 32, false)) {
1100                                                $this->errors[] = 'Invalid Indexed Data Length in '.$frame_name.' ('.$source_data_array['datalength'].') (range = 0 to 4294967295)';
1101                                        } elseif (!$this->IsWithinBitRange($source_data_array['indexpoints'], 16, false)) {
1102                                                $this->errors[] = 'Invalid Number Of Index Points in '.$frame_name.' ('.$source_data_array['indexpoints'].') (range = 0 to 65535)';
1103                                        } elseif (!$this->IsWithinBitRange($source_data_array['bitsperpoint'], 8, false)) {
1104                                                $this->errors[] = 'Invalid Bits Per Index Point in '.$frame_name.' ('.$source_data_array['bitsperpoint'].') (range = 0 to 255)';
1105                                        } elseif ($source_data_array['indexpoints'] != count($source_data_array['indexes'])) {
1106                                                $this->errors[] = 'Number Of Index Points does not match actual supplied data in '.$frame_name;
1107                                        } else {
1108                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['datastart'], 4, false);
1109                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['datalength'], 4, false);
1110                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['indexpoints'], 2, false);
1111                                                $framedata .= getid3_lib::BigEndian2String($source_data_array['bitsperpoint'], 1, false);
1112                                                foreach ($source_data_array['indexes'] as $key => $val) {
1113                                                        $framedata .= getid3_lib::BigEndian2String($val, ceil($source_data_array['bitsperpoint'] / 8), false);
1114                                                }
1115                                        }
1116                                        break;
1117
1118                                case 'RGAD':
1119                                        //   RGAD Replay Gain Adjustment
1120                                        //   http://privatewww.essex.ac.uk/~djmrob/replaygain/
1121                                        // Peak Amplitude                     $xx $xx $xx $xx
1122                                        // Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
1123                                        // Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
1124                                        //   a - name code
1125                                        //   b - originator code
1126                                        //   c - sign bit
1127                                        //   d - replay gain adjustment
1128
1129                                        if (($source_data_array['track_adjustment'] > 51) || ($source_data_array['track_adjustment'] < -51)) {
1130                                                $this->errors[] = 'Invalid Track Adjustment in '.$frame_name.' ('.$source_data_array['track_adjustment'].') (range = -51.0 to +51.0)';
1131                                        } elseif (($source_data_array['album_adjustment'] > 51) || ($source_data_array['album_adjustment'] < -51)) {
1132                                                $this->errors[] = 'Invalid Album Adjustment in '.$frame_name.' ('.$source_data_array['album_adjustment'].') (range = -51.0 to +51.0)';
1133                                        } elseif (!$this->ID3v2IsValidRGADname($source_data_array['raw']['track_name'])) {
1134                                                $this->errors[] = 'Invalid Track Name Code in '.$frame_name.' ('.$source_data_array['raw']['track_name'].') (range = 0 to 2)';
1135                                        } elseif (!$this->ID3v2IsValidRGADname($source_data_array['raw']['album_name'])) {
1136                                                $this->errors[] = 'Invalid Album Name Code in '.$frame_name.' ('.$source_data_array['raw']['album_name'].') (range = 0 to 2)';
1137                                        } elseif (!$this->ID3v2IsValidRGADoriginator($source_data_array['raw']['track_originator'])) {
1138                                                $this->errors[] = 'Invalid Track Originator Code in '.$frame_name.' ('.$source_data_array['raw']['track_originator'].') (range = 0 to 3)';
1139                                        } elseif (!$this->ID3v2IsValidRGADoriginator($source_data_array['raw']['album_originator'])) {
1140                                                $this->errors[] = 'Invalid Album Originator Code in '.$frame_name.' ('.$source_data_array['raw']['album_originator'].') (range = 0 to 3)';
1141                                        } else {
1142                                                $framedata .= getid3_lib::Float2String($source_data_array['peakamplitude'], 32);
1143                                                $framedata .= getid3_lib::RGADgainString($source_data_array['raw']['track_name'], $source_data_array['raw']['track_originator'], $source_data_array['track_adjustment']);
1144                                                $framedata .= getid3_lib::RGADgainString($source_data_array['raw']['album_name'], $source_data_array['raw']['album_originator'], $source_data_array['album_adjustment']);
1145                                        }
1146                                        break;
1147
1148                                default:
1149                                        if ($frame_name{0} == 'T') {
1150                                                // 4.2. T???  Text information frames
1151                                                // Text encoding                $xx
1152                                                // Information                  <text string(s) according to encoding>
1153                                                $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
1154                                                if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) {
1155                                                        $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion;
1156                                                } else {
1157                                                        $framedata .= chr($source_data_array['encodingid']);
1158                                                        $framedata .= $source_data_array['data'];
1159                                                }
1160                                        } elseif ($frame_name{0} == 'W') {
1161                                                // 4.3. W???  URL link frames
1162                                                // URL              <text string>
1163                                                if (!$this->IsValidURL($source_data_array['data'], false, false)) {
1164                                                        //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
1165                                                        // probably should be an error, need to rewrite IsValidURL() to handle other encodings
1166                                                        $this->warnings[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
1167                                                } else {
1168                                                        $framedata .= $source_data_array['data'];
1169                                                }
1170                                        } else {
1171                                                $this->errors[] = $frame_name.' not yet supported in $this->GenerateID3v2FrameData()';
1172                                        }
1173                                        break;
1174                        }
1175                }
1176                if (!empty($this->errors)) {
1177                        return false;
1178                }
1179                return $framedata;
1180        }
1181
1182        function ID3v2FrameIsAllowed($frame_name, $source_data_array) {
1183                static $PreviousFrames = array();
1184
1185                if ($frame_name === null) {
1186                        // if the writing functions are called multiple times, the static array needs to be
1187                        // cleared - this can be done by calling $this->ID3v2FrameIsAllowed(null, '')
1188                        $PreviousFrames = array();
1189                        return true;
1190                }
1191
1192                if ($this->majorversion == 4) {
1193                        switch ($frame_name) {
1194                                case 'UFID':
1195                                case 'AENC':
1196                                case 'ENCR':
1197                                case 'GRID':
1198                                        if (!isset($source_data_array['ownerid'])) {
1199                                                $this->errors[] = '[ownerid] not specified for '.$frame_name;
1200                                        } elseif (in_array($frame_name.$source_data_array['ownerid'], $PreviousFrames)) {
1201                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID ('.$source_data_array['ownerid'].')';
1202                                        } else {
1203                                                $PreviousFrames[] = $frame_name.$source_data_array['ownerid'];
1204                                        }
1205                                        break;
1206
1207                                case 'TXXX':
1208                                case 'WXXX':
1209                                case 'RVA2':
1210                                case 'EQU2':
1211                                case 'APIC':
1212                                case 'GEOB':
1213                                        if (!isset($source_data_array['description'])) {
1214                                                $this->errors[] = '[description] not specified for '.$frame_name;
1215                                        } elseif (in_array($frame_name.$source_data_array['description'], $PreviousFrames)) {
1216                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Description ('.$source_data_array['description'].')';
1217                                        } else {
1218                                                $PreviousFrames[] = $frame_name.$source_data_array['description'];
1219                                        }
1220                                        break;
1221
1222                                case 'USER':
1223                                        if (!isset($source_data_array['language'])) {
1224                                                $this->errors[] = '[language] not specified for '.$frame_name;
1225                                        } elseif (in_array($frame_name.$source_data_array['language'], $PreviousFrames)) {
1226                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language ('.$source_data_array['language'].')';
1227                                        } else {
1228                                                $PreviousFrames[] = $frame_name.$source_data_array['language'];
1229                                        }
1230                                        break;
1231
1232                                case 'USLT':
1233                                case 'SYLT':
1234                                case 'COMM':
1235                                        if (!isset($source_data_array['language'])) {
1236                                                $this->errors[] = '[language] not specified for '.$frame_name;
1237                                        } elseif (!isset($source_data_array['description'])) {
1238                                                $this->errors[] = '[description] not specified for '.$frame_name;
1239                                        } elseif (in_array($frame_name.$source_data_array['language'].$source_data_array['description'], $PreviousFrames)) {
1240                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')';
1241                                        } else {
1242                                                $PreviousFrames[] = $frame_name.$source_data_array['language'].$source_data_array['description'];
1243                                        }
1244                                        break;
1245
1246                                case 'POPM':
1247                                        if (!isset($source_data_array['email'])) {
1248                                                $this->errors[] = '[email] not specified for '.$frame_name;
1249                                        } elseif (in_array($frame_name.$source_data_array['email'], $PreviousFrames)) {
1250                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Email ('.$source_data_array['email'].')';
1251                                        } else {
1252                                                $PreviousFrames[] = $frame_name.$source_data_array['email'];
1253                                        }
1254                                        break;
1255
1256                                case 'IPLS':
1257                                case 'MCDI':
1258                                case 'ETCO':
1259                                case 'MLLT':
1260                                case 'SYTC':
1261                                case 'RVRB':
1262                                case 'PCNT':
1263                                case 'RBUF':
1264                                case 'POSS':
1265                                case 'OWNE':
1266                                case 'SEEK':
1267                                case 'ASPI':
1268                                case 'RGAD':
1269                                        if (in_array($frame_name, $PreviousFrames)) {
1270                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed';
1271                                        } else {
1272                                                $PreviousFrames[] = $frame_name;
1273                                        }
1274                                        break;
1275
1276                                case 'LINK':
1277                                        // this isn't implemented quite right (yet) - it should check the target frame data for compliance
1278                                        // but right now it just allows one linked frame of each type, to be safe.
1279                                        if (!isset($source_data_array['frameid'])) {
1280                                                $this->errors[] = '[frameid] not specified for '.$frame_name;
1281                                        } elseif (in_array($frame_name.$source_data_array['frameid'], $PreviousFrames)) {
1282                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same FrameID ('.$source_data_array['frameid'].')';
1283                                        } elseif (in_array($source_data_array['frameid'], $PreviousFrames)) {
1284                                                // no links to singleton tags
1285                                                $this->errors[] = 'Cannot specify a '.$frame_name.' tag to a singleton tag that already exists ('.$source_data_array['frameid'].')';
1286                                        } else {
1287                                                $PreviousFrames[] = $frame_name.$source_data_array['frameid']; // only one linked tag of this type
1288                                                $PreviousFrames[] = $source_data_array['frameid'];             // no non-linked singleton tags of this type
1289                                        }
1290                                        break;
1291
1292                                case 'COMR':
1293                                        //   There may be more than one 'commercial frame' in a tag, but no two may be identical
1294                                        // Checking isn't implemented at all (yet) - just assumes that it's OK.
1295                                        break;
1296
1297                                case 'PRIV':
1298                                case 'SIGN':
1299                                        if (!isset($source_data_array['ownerid'])) {
1300                                                $this->errors[] = '[ownerid] not specified for '.$frame_name;
1301                                        } elseif (!isset($source_data_array['data'])) {
1302                                                $this->errors[] = '[data] not specified for '.$frame_name;
1303                                        } elseif (in_array($frame_name.$source_data_array['ownerid'].$source_data_array['data'], $PreviousFrames)) {
1304                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID + Data ('.$source_data_array['ownerid'].' + '.$source_data_array['data'].')';
1305                                        } else {
1306                                                $PreviousFrames[] = $frame_name.$source_data_array['ownerid'].$source_data_array['data'];
1307                                        }
1308                                        break;
1309
1310                                default:
1311                                        if (($frame_name{0} != 'T') && ($frame_name{0} != 'W')) {
1312                                                $this->errors[] = 'Frame not allowed in ID3v2.'.$this->majorversion.': '.$frame_name;
1313                                        }
1314                                        break;
1315                        }
1316
1317                } elseif ($this->majorversion == 3) {
1318
1319                        switch ($frame_name) {
1320                                case 'UFID':
1321                                case 'AENC':
1322                                case 'ENCR':
1323                                case 'GRID':
1324                                        if (!isset($source_data_array['ownerid'])) {
1325                                                $this->errors[] = '[ownerid] not specified for '.$frame_name;
1326                                        } elseif (in_array($frame_name.$source_data_array['ownerid'], $PreviousFrames)) {
1327                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID ('.$source_data_array['ownerid'].')';
1328                                        } else {
1329                                                $PreviousFrames[] = $frame_name.$source_data_array['ownerid'];
1330                                        }
1331                                        break;
1332
1333                                case 'TXXX':
1334                                case 'WXXX':
1335                                case 'APIC':
1336                                case 'GEOB':
1337                                        if (!isset($source_data_array['description'])) {
1338                                                $this->errors[] = '[description] not specified for '.$frame_name;
1339                                        } elseif (in_array($frame_name.$source_data_array['description'], $PreviousFrames)) {
1340                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Description ('.$source_data_array['description'].')';
1341                                        } else {
1342                                                $PreviousFrames[] = $frame_name.$source_data_array['description'];
1343                                        }
1344                                        break;
1345
1346                                case 'USER':
1347                                        if (!isset($source_data_array['language'])) {
1348                                                $this->errors[] = '[language] not specified for '.$frame_name;
1349                                        } elseif (in_array($frame_name.$source_data_array['language'], $PreviousFrames)) {
1350                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language ('.$source_data_array['language'].')';
1351                                        } else {
1352                                                $PreviousFrames[] = $frame_name.$source_data_array['language'];
1353                                        }
1354                                        break;
1355
1356                                case 'USLT':
1357                                case 'SYLT':
1358                                case 'COMM':
1359                                        if (!isset($source_data_array['language'])) {
1360                                                $this->errors[] = '[language] not specified for '.$frame_name;
1361                                        } elseif (!isset($source_data_array['description'])) {
1362                                                $this->errors[] = '[description] not specified for '.$frame_name;
1363                                        } elseif (in_array($frame_name.$source_data_array['language'].$source_data_array['description'], $PreviousFrames)) {
1364                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')';
1365                                        } else {
1366                                                $PreviousFrames[] = $frame_name.$source_data_array['language'].$source_data_array['description'];
1367                                        }
1368                                        break;
1369
1370                                case 'POPM':
1371                                        if (!isset($source_data_array['email'])) {
1372                                                $this->errors[] = '[email] not specified for '.$frame_name;
1373                                        } elseif (in_array($frame_name.$source_data_array['email'], $PreviousFrames)) {
1374                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Email ('.$source_data_array['email'].')';
1375                                        } else {
1376                                                $PreviousFrames[] = $frame_name.$source_data_array['email'];
1377                                        }
1378                                        break;
1379
1380                                case 'IPLS':
1381                                case 'MCDI':
1382                                case 'ETCO':
1383                                case 'MLLT':
1384                                case 'SYTC':
1385                                case 'RVAD':
1386                                case 'EQUA':
1387                                case 'RVRB':
1388                                case 'PCNT':
1389                                case 'RBUF':
1390                                case 'POSS':
1391                                case 'OWNE':
1392                                case 'RGAD':
1393                                        if (in_array($frame_name, $PreviousFrames)) {
1394                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed';
1395                                        } else {
1396                                                $PreviousFrames[] = $frame_name;
1397                                        }
1398                                        break;
1399
1400                                case 'LINK':
1401                                        // this isn't implemented quite right (yet) - it should check the target frame data for compliance
1402                                        // but right now it just allows one linked frame of each type, to be safe.
1403                                        if (!isset($source_data_array['frameid'])) {
1404                                                $this->errors[] = '[frameid] not specified for '.$frame_name;
1405                                        } elseif (in_array($frame_name.$source_data_array['frameid'], $PreviousFrames)) {
1406                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same FrameID ('.$source_data_array['frameid'].')';
1407                                        } elseif (in_array($source_data_array['frameid'], $PreviousFrames)) {
1408                                                // no links to singleton tags
1409                                                $this->errors[] = 'Cannot specify a '.$frame_name.' tag to a singleton tag that already exists ('.$source_data_array['frameid'].')';
1410                                        } else {
1411                                                $PreviousFrames[] = $frame_name.$source_data_array['frameid']; // only one linked tag of this type
1412                                                $PreviousFrames[] = $source_data_array['frameid'];             // no non-linked singleton tags of this type
1413                                        }
1414                                        break;
1415
1416                                case 'COMR':
1417                                        //   There may be more than one 'commercial frame' in a tag, but no two may be identical
1418                                        // Checking isn't implemented at all (yet) - just assumes that it's OK.
1419                                        break;
1420
1421                                case 'PRIV':
1422                                        if (!isset($source_data_array['ownerid'])) {
1423                                                $this->errors[] = '[ownerid] not specified for '.$frame_name;
1424                                        } elseif (!isset($source_data_array['data'])) {
1425                                                $this->errors[] = '[data] not specified for '.$frame_name;
1426                                        } elseif (in_array($frame_name.$source_data_array['ownerid'].$source_data_array['data'], $PreviousFrames)) {
1427                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID + Data ('.$source_data_array['ownerid'].' + '.$source_data_array['data'].')';
1428                                        } else {
1429                                                $PreviousFrames[] = $frame_name.$source_data_array['ownerid'].$source_data_array['data'];
1430                                        }
1431                                        break;
1432
1433                                default:
1434                                        if (($frame_name{0} != 'T') && ($frame_name{0} != 'W')) {
1435                                                $this->errors[] = 'Frame not allowed in ID3v2.'.$this->majorversion.': '.$frame_name;
1436                                        }
1437                                        break;
1438                        }
1439
1440                } elseif ($this->majorversion == 2) {
1441
1442                        switch ($frame_name) {
1443                                case 'UFI':
1444                                case 'CRM':
1445                                case 'CRA':
1446                                        if (!isset($source_data_array['ownerid'])) {
1447                                                $this->errors[] = '[ownerid] not specified for '.$frame_name;
1448                                        } elseif (in_array($frame_name.$source_data_array['ownerid'], $PreviousFrames)) {
1449                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID ('.$source_data_array['ownerid'].')';
1450                                        } else {
1451                                                $PreviousFrames[] = $frame_name.$source_data_array['ownerid'];
1452                                        }
1453                                        break;
1454
1455                                case 'TXX':
1456                                case 'WXX':
1457                                case 'PIC':
1458                                case 'GEO':
1459                                        if (!isset($source_data_array['description'])) {
1460                                                $this->errors[] = '[description] not specified for '.$frame_name;
1461                                        } elseif (in_array($frame_name.$source_data_array['description'], $PreviousFrames)) {
1462                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Description ('.$source_data_array['description'].')';
1463                                        } else {
1464                                                $PreviousFrames[] = $frame_name.$source_data_array['description'];
1465                                        }
1466                                        break;
1467
1468                                case 'ULT':
1469                                case 'SLT':
1470                                case 'COM':
1471                                        if (!isset($source_data_array['language'])) {
1472                                                $this->errors[] = '[language] not specified for '.$frame_name;
1473                                        } elseif (!isset($source_data_array['description'])) {
1474                                                $this->errors[] = '[description] not specified for '.$frame_name;
1475                                        } elseif (in_array($frame_name.$source_data_array['language'].$source_data_array['description'], $PreviousFrames)) {
1476                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')';
1477                                        } else {
1478                                                $PreviousFrames[] = $frame_name.$source_data_array['language'].$source_data_array['description'];
1479                                        }
1480                                        break;
1481
1482                                case 'POP':
1483                                        if (!isset($source_data_array['email'])) {
1484                                                $this->errors[] = '[email] not specified for '.$frame_name;
1485                                        } elseif (in_array($frame_name.$source_data_array['email'], $PreviousFrames)) {
1486                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Email ('.$source_data_array['email'].')';
1487                                        } else {
1488                                                $PreviousFrames[] = $frame_name.$source_data_array['email'];
1489                                        }
1490                                        break;
1491
1492                                case 'IPL':
1493                                case 'MCI':
1494                                case 'ETC':
1495                                case 'MLL':
1496                                case 'STC':
1497                                case 'RVA':
1498                                case 'EQU':
1499                                case 'REV':
1500                                case 'CNT':
1501                                case 'BUF':
1502                                        if (in_array($frame_name, $PreviousFrames)) {
1503                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed';
1504                                        } else {
1505                                                $PreviousFrames[] = $frame_name;
1506                                        }
1507                                        break;
1508
1509                                case 'LNK':
1510                                        // this isn't implemented quite right (yet) - it should check the target frame data for compliance
1511                                        // but right now it just allows one linked frame of each type, to be safe.
1512                                        if (!isset($source_data_array['frameid'])) {
1513                                                $this->errors[] = '[frameid] not specified for '.$frame_name;
1514                                        } elseif (in_array($frame_name.$source_data_array['frameid'], $PreviousFrames)) {
1515                                                $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same FrameID ('.$source_data_array['frameid'].')';
1516                                        } elseif (in_array($source_data_array['frameid'], $PreviousFrames)) {
1517                                                // no links to singleton tags
1518                                                $this->errors[] = 'Cannot specify a '.$frame_name.' tag to a singleton tag that already exists ('.$source_data_array['frameid'].')';
1519                                        } else {
1520                                                $PreviousFrames[] = $frame_name.$source_data_array['frameid']; // only one linked tag of this type
1521                                                $PreviousFrames[] = $source_data_array['frameid'];             // no non-linked singleton tags of this type
1522                                        }
1523                                        break;
1524
1525                                default:
1526                                        if (($frame_name{0} != 'T') && ($frame_name{0} != 'W')) {
1527                                                $this->errors[] = 'Frame not allowed in ID3v2.'.$this->majorversion.': '.$frame_name;
1528                                        }
1529                                        break;
1530                        }
1531                }
1532
1533                if (!empty($this->errors)) {
1534                        return false;
1535                }
1536                return true;
1537        }
1538
1539        function GenerateID3v2Tag($noerrorsonly=true) {
1540                $this->ID3v2FrameIsAllowed(null, ''); // clear static array in case this isn't the first call to $this->GenerateID3v2Tag()
1541
1542                $tagstring = '';
1543                if (is_array($this->tag_data)) {
1544                        foreach ($this->tag_data as $frame_name => $frame_rawinputdata) {
1545                                foreach ($frame_rawinputdata as $irrelevantindex => $source_data_array) {
1546                                        if (getid3_id3v2::IsValidID3v2FrameName($frame_name, $this->majorversion)) {
1547                                                unset($frame_length);
1548                                                unset($frame_flags);
1549                                                $frame_data = false;
1550                                                if ($this->ID3v2FrameIsAllowed($frame_name, $source_data_array)) {
1551                                                        if ($frame_data = $this->GenerateID3v2FrameData($frame_name, $source_data_array)) {
1552                                                                $FrameUnsynchronisation = false;
1553                                                                if ($this->majorversion >= 4) {
1554                                                                        // frame-level unsynchronisation
1555                                                                        $unsynchdata = $frame_data;
1556                                                                        if ($this->id3v2_use_unsynchronisation) {
1557                                                                                $unsynchdata = $this->Unsynchronise($frame_data);
1558                                                                        }
1559                                                                        if (strlen($unsynchdata) != strlen($frame_data)) {
1560                                                                                // unsynchronisation needed
1561                                                                                $FrameUnsynchronisation = true;
1562                                                                                $frame_data = $unsynchdata;
1563                                                                                if (isset($TagUnsynchronisation) && $TagUnsynchronisation === false) {
1564                                                                                        // only set to true if ALL frames are unsynchronised
1565                                                                                } else {
1566                                                                                        $TagUnsynchronisation = true;
1567                                                                                }
1568                                                                        } else {
1569                                                                                if (isset($TagUnsynchronisation)) {
1570                                                                                        $TagUnsynchronisation = false;
1571                                                                                }
1572                                                                        }
1573                                                                        unset($unsynchdata);
1574
1575                                                                        $frame_length = getid3_lib::BigEndian2String(strlen($frame_data), 4, true);
1576                                                                } else {
1577                                                                        $frame_length = getid3_lib::BigEndian2String(strlen($frame_data), 4, false);
1578                                                                }
1579                                                                $frame_flags  = $this->GenerateID3v2FrameFlags($this->ID3v2FrameFlagsLookupTagAlter($frame_name), $this->ID3v2FrameFlagsLookupFileAlter($frame_name), false, false, false, false, $FrameUnsynchronisation, false);
1580                                                        }
1581                                                } else {
1582                                                        $this->errors[] = 'Frame "'.$frame_name.'" is NOT allowed';
1583                                                }
1584                                                if ($frame_data === false) {
1585                                                        $this->errors[] = '$this->GenerateID3v2FrameData() failed for "'.$frame_name.'"';
1586                                                        if ($noerrorsonly) {
1587                                                                return false;
1588                                                        } else {
1589                                                                unset($frame_name);
1590                                                        }
1591                                                }
1592                                        } else {
1593                                                // ignore any invalid frame names, including 'title', 'header', etc
1594                                                $this->warnings[] = 'Ignoring invalid ID3v2 frame type: "'.$frame_name.'"';
1595                                                unset($frame_name);
1596                                                unset($frame_length);
1597                                                unset($frame_flags);
1598                                                unset($frame_data);
1599                                        }
1600                                        if (isset($frame_name) && isset($frame_length) && isset($frame_flags) && isset($frame_data)) {
1601                                                $tagstring .= $frame_name.$frame_length.$frame_flags.$frame_data;
1602                                        }
1603                                }
1604                        }
1605
1606                        if (!isset($TagUnsynchronisation)) {
1607                                $TagUnsynchronisation = false;
1608                        }
1609                        if (($this->majorversion <= 3) && $this->id3v2_use_unsynchronisation) {
1610                                // tag-level unsynchronisation
1611                                $unsynchdata = $this->Unsynchronise($tagstring);
1612                                if (strlen($unsynchdata) != strlen($tagstring)) {
1613                                        // unsynchronisation needed
1614                                        $TagUnsynchronisation = true;
1615                                        $tagstring = $unsynchdata;
1616                                }
1617                        }
1618
1619                        while ($this->paddedlength < (strlen($tagstring) + getid3_id3v2::ID3v2HeaderLength($this->majorversion))) {
1620                                $this->paddedlength += 1024;
1621                        }
1622
1623                        $footer = false; // ID3v2 footers not yet supported in getID3()
1624                        if (!$footer && ($this->paddedlength > (strlen($tagstring) + getid3_id3v2::ID3v2HeaderLength($this->majorversion)))) {
1625                                // pad up to $paddedlength bytes if unpadded tag is shorter than $paddedlength
1626                                // "Furthermore it MUST NOT have any padding when a tag footer is added to the tag."
1627                                $tagstring .= @str_repeat("\x00", $this->paddedlength - strlen($tagstring) - getid3_id3v2::ID3v2HeaderLength($this->majorversion));
1628                        }
1629                        if ($this->id3v2_use_unsynchronisation && (substr($tagstring, strlen($tagstring) - 1, 1) == "\xFF")) {
1630                                // special unsynchronisation case:
1631                                // if last byte == $FF then appended a $00
1632                                $TagUnsynchronisation = true;
1633                                $tagstring .= "\x00";
1634                        }
1635
1636                        $tagheader  = 'ID3';
1637                        $tagheader .= chr($this->majorversion);
1638                        $tagheader .= chr($this->minorversion);
1639                        $tagheader .= $this->GenerateID3v2TagFlags(array('unsynchronisation'=>$TagUnsynchronisation));
1640                        $tagheader .= getid3_lib::BigEndian2String(strlen($tagstring), 4, true);
1641
1642                        return $tagheader.$tagstring;
1643                }
1644                $this->errors[] = 'tag_data is not an array in GenerateID3v2Tag()';
1645                return false;
1646        }
1647
1648        function ID3v2IsValidPriceString($pricestring) {
1649                if (getid3_id3v2::LanguageLookup(substr($pricestring, 0, 3), true) == '') {
1650                        return false;
1651                } elseif (!$this->IsANumber(substr($pricestring, 3), true)) {
1652                        return false;
1653                }
1654                return true;
1655        }
1656
1657        function ID3v2FrameFlagsLookupTagAlter($framename) {
1658                // unfinished
1659                switch ($framename) {
1660                        case 'RGAD':
1661                                $allow = true;
1662                        default:
1663                                $allow = false;
1664                                break;
1665                }
1666                return $allow;
1667        }
1668
1669        function ID3v2FrameFlagsLookupFileAlter($framename) {
1670                // unfinished
1671                switch ($framename) {
1672                        case 'RGAD':
1673                                return false;
1674                                break;
1675
1676                        default:
1677                                return false;
1678                                break;
1679                }
1680        }
1681
1682        function ID3v2IsValidETCOevent($eventid) {
1683                if (($eventid < 0) || ($eventid > 0xFF)) {
1684                        // outside range of 1 byte
1685                        return false;
1686                } elseif (($eventid >= 0xF0) && ($eventid <= 0xFC)) {
1687                        // reserved for future use
1688                        return false;
1689                } elseif (($eventid >= 0x17) && ($eventid <= 0xDF)) {
1690                        // reserved for future use
1691                        return false;
1692                } elseif (($eventid >= 0x0E) && ($eventid <= 0x16) && ($this->majorversion == 2)) {
1693                        // not defined in ID3v2.2
1694                        return false;
1695                } elseif (($eventid >= 0x15) && ($eventid <= 0x16) && ($this->majorversion == 3)) {
1696                        // not defined in ID3v2.3
1697                        return false;
1698                }
1699                return true;
1700        }
1701
1702        function ID3v2IsValidSYLTtype($contenttype) {
1703                if (($contenttype >= 0) && ($contenttype <= 8) && ($this->majorversion == 4)) {
1704                        return true;
1705                } elseif (($contenttype >= 0) && ($contenttype <= 6) && ($this->majorversion == 3)) {
1706                        return true;
1707                }
1708                return false;
1709        }
1710
1711        function ID3v2IsValidRVA2channeltype($channeltype) {
1712                if (($channeltype >= 0) && ($channeltype <= 8) && ($this->majorversion == 4)) {
1713                        return true;
1714                }
1715                return false;
1716        }
1717
1718        function ID3v2IsValidAPICpicturetype($picturetype) {
1719                if (($picturetype >= 0) && ($picturetype <= 0x14) && ($this->majorversion >= 2) && ($this->majorversion <= 4)) {
1720                        return true;
1721                }
1722                return false;
1723        }
1724
1725        function ID3v2IsValidAPICimageformat($imageformat) {
1726                if ($imageformat == '-->') {
1727                        return true;
1728                } elseif ($this->majorversion == 2) {
1729                        if ((strlen($imageformat) == 3) && ($imageformat == strtoupper($imageformat))) {
1730                                return true;
1731                        }
1732                } elseif (($this->majorversion == 3) || ($this->majorversion == 4)) {
1733                        if ($this->IsValidMIMEstring($imageformat)) {
1734                                return true;
1735                        }
1736                }
1737                return false;
1738        }
1739
1740        function ID3v2IsValidCOMRreceivedAs($receivedas) {
1741                if (($this->majorversion >= 3) && ($receivedas >= 0) && ($receivedas <= 8)) {
1742                        return true;
1743                }
1744                return false;
1745        }
1746
1747        function ID3v2IsValidRGADname($RGADname) {
1748                if (($RGADname >= 0) && ($RGADname <= 2)) {
1749                        return true;
1750                }
1751                return false;
1752        }
1753
1754        function ID3v2IsValidRGADoriginator($RGADoriginator) {
1755                if (($RGADoriginator >= 0) && ($RGADoriginator <= 3)) {
1756                        return true;
1757                }
1758                return false;
1759        }
1760
1761        function ID3v2IsValidTextEncoding($textencodingbyte) {
1762                static $ID3v2IsValidTextEncoding_cache = array(
1763                        2 => array(true, true),
1764                        3 => array(true, true),
1765                        4 => array(true, true, true, true));
1766                return isset($ID3v2IsValidTextEncoding_cache[$this->majorversion][$textencodingbyte]);
1767        }
1768
1769        function Unsynchronise($data) {
1770                // Whenever a false synchronisation is found within the tag, one zeroed
1771                // byte is inserted after the first false synchronisation byte. The
1772                // format of a correct sync that should be altered by ID3 encoders is as
1773                // follows:
1774                //      %11111111 111xxxxx
1775                // And should be replaced with:
1776                //      %11111111 00000000 111xxxxx
1777                // This has the side effect that all $FF 00 combinations have to be
1778                // altered, so they won't be affected by the decoding process. Therefore
1779                // all the $FF 00 combinations have to be replaced with the $FF 00 00
1780                // combination during the unsynchronisation.
1781
1782                $data = str_replace("\xFF\x00", "\xFF\x00\x00", $data);
1783                $unsyncheddata = '';
1784                $datalength = strlen($data);
1785                for ($i = 0; $i < $datalength; $i++) {
1786                        $thischar = $data{$i};
1787                        $unsyncheddata .= $thischar;
1788                        if ($thischar == "\xFF") {
1789                                $nextchar = ord($data{$i + 1});
1790                                if (($nextchar & 0xE0) == 0xE0) {
1791                                        // previous byte = 11111111, this byte = 111?????
1792                                        $unsyncheddata .= "\x00";
1793                                }
1794                        }
1795                }
1796                return $unsyncheddata;
1797        }
1798
1799        function is_hash($var) {
1800                // written by dev-nullØchristophe*vg
1801                // taken from http://www.php.net/manual/en/function.array-merge-recursive.php
1802                if (is_array($var)) {
1803                        $keys = array_keys($var);
1804                        $all_num = true;
1805                        for ($i = 0; $i < count($keys); $i++) {
1806                                if (is_string($keys[$i])) {
1807                                        return true;
1808                                }
1809                        }
1810                }
1811                return false;
1812        }
1813
1814        function array_join_merge($arr1, $arr2) {
1815                // written by dev-nullØchristophe*vg
1816                // taken from http://www.php.net/manual/en/function.array-merge-recursive.php
1817                if (is_array($arr1) && is_array($arr2)) {
1818                        // the same -> merge
1819                        $new_array = array();
1820
1821                        if ($this->is_hash($arr1) && $this->is_hash($arr2)) {
1822                                // hashes -> merge based on keys
1823                                $keys = array_merge(array_keys($arr1), array_keys($arr2));
1824                                foreach ($keys as $key) {
1825                                        $new_array[$key] = $this->array_join_merge(@$arr1[$key], @$arr2[$key]);
1826                                }
1827                        } else {
1828                                // two real arrays -> merge
1829                                $new_array = array_reverse(array_unique(array_reverse(array_merge($arr1, $arr2))));
1830                        }
1831                        return $new_array;
1832                } else {
1833                        // not the same ... take new one if defined, else the old one stays
1834                        return $arr2 ? $arr2 : $arr1;
1835                }
1836        }
1837
1838        function IsValidMIMEstring($mimestring) {
1839                if ((strlen($mimestring) >= 3) && (strpos($mimestring, '/') > 0) && (strpos($mimestring, '/') < (strlen($mimestring) - 1))) {
1840                        return true;
1841                }
1842                return false;
1843        }
1844
1845        function IsWithinBitRange($number, $maxbits, $signed=false) {
1846                if ($signed) {
1847                        if (($number > (0 - pow(2, $maxbits - 1))) && ($number <= pow(2, $maxbits - 1))) {
1848                                return true;
1849                        }
1850                } else {
1851                        if (($number >= 0) && ($number <= pow(2, $maxbits))) {
1852                                return true;
1853                        }
1854                }
1855                return false;
1856        }
1857
1858        function safe_parse_url($url) {
1859                $parts = @parse_url($url);
1860                $parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : '');
1861                $parts['host']   = (isset($parts['host'])   ? $parts['host']   : '');
1862                $parts['user']   = (isset($parts['user'])   ? $parts['user']   : '');
1863                $parts['pass']   = (isset($parts['pass'])   ? $parts['pass']   : '');
1864                $parts['path']   = (isset($parts['path'])   ? $parts['path']   : '');
1865                $parts['query']  = (isset($parts['query'])  ? $parts['query']  : '');
1866                return $parts;
1867        }
1868
1869        function IsValidURL($url, $allowUserPass=false) {
1870                if ($url == '') {
1871                        return false;
1872                }
1873                if ($allowUserPass !== true) {
1874                        if (strstr($url, '@')) {
1875                                // in the format http://user:pass@example.com  or http://user@example.com
1876                                // but could easily be somebody incorrectly entering an email address in place of a URL
1877                                return false;
1878                        }
1879                }
1880                if ($parts = $this->safe_parse_url($url)) {
1881                        if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher')) {
1882                                return false;
1883                        } elseif (!eregi("^[[:alnum:]]([-.]?[0-9a-z])*\.[a-z]{2,3}$", $parts['host'], $regs) && !IsValidDottedIP($parts['host'])) {
1884                                return false;
1885                        } elseif (!eregi("^([[:alnum:]-]|[\_])*$", $parts['user'], $regs)) {
1886                                return false;
1887                        } elseif (!eregi("^([[:alnum:]-]|[\_])*$", $parts['pass'], $regs)) {
1888                                return false;
1889                        } elseif (!eregi("^[[:alnum:]/_\.@~-]*$", $parts['path'], $regs)) {
1890                                return false;
1891                        } elseif (!eregi("^[[:alnum:]?&=+:;_()%#/,\.-]*$", $parts['query'], $regs)) {
1892                                return false;
1893                        } else {
1894                                return true;
1895                        }
1896                }
1897                return false;
1898        }
1899
1900        function ID3v2ShortFrameNameLookup($majorversion, $long_description) {
1901                $long_description = str_replace(' ', '_', strtolower(trim($long_description)));
1902                static $ID3v2ShortFrameNameLookup = array();
1903                if (empty($ID3v2ShortFrameNameLookup)) {
1904
1905                        // The following are unique to ID3v2.2
1906                        $ID3v2ShortFrameNameLookup[2]['comment']                                          = 'COM';
1907                        $ID3v2ShortFrameNameLookup[2]['album']                                            = 'TAL';
1908                        $ID3v2ShortFrameNameLookup[2]['beats_per_minute']                                 = 'TBP';
1909                        $ID3v2ShortFrameNameLookup[2]['composer']                                         = 'TCM';
1910                        $ID3v2ShortFrameNameLookup[2]['genre']                                            = 'TCO';
1911                        $ID3v2ShortFrameNameLookup[2]['itunescompilation']                                = 'TCP';
1912                        $ID3v2ShortFrameNameLookup[2]['copyright']                                        = 'TCR';
1913                        $ID3v2ShortFrameNameLookup[2]['encoded_by']                                       = 'TEN';
1914                        $ID3v2ShortFrameNameLookup[2]['language']                                         = 'TLA';
1915                        $ID3v2ShortFrameNameLookup[2]['length']                                           = 'TLE';
1916                        $ID3v2ShortFrameNameLookup[2]['original_artist']                                  = 'TOA';
1917                        $ID3v2ShortFrameNameLookup[2]['original_filename']                                = 'TOF';
1918                        $ID3v2ShortFrameNameLookup[2]['original_lyricist']                                = 'TOL';
1919                        $ID3v2ShortFrameNameLookup[2]['original_album_title']                             = 'TOT';
1920                        $ID3v2ShortFrameNameLookup[2]['artist']                                           = 'TP1';
1921                        $ID3v2ShortFrameNameLookup[2]['band']                                             = 'TP2';
1922                        $ID3v2ShortFrameNameLookup[2]['conductor']                                        = 'TP3';
1923                        $ID3v2ShortFrameNameLookup[2]['remixer']                                          = 'TP4';
1924                        $ID3v2ShortFrameNameLookup[2]['publisher']                                        = 'TPB';
1925                        $ID3v2ShortFrameNameLookup[2]['isrc']                                             = 'TRC';
1926                        $ID3v2ShortFrameNameLookup[2]['tracknumber']                                      = 'TRK';
1927                        $ID3v2ShortFrameNameLookup[2]['size']                                             = 'TSI';
1928                        $ID3v2ShortFrameNameLookup[2]['encoder_settings']                                 = 'TSS';
1929                        $ID3v2ShortFrameNameLookup[2]['description']                                      = 'TT1';
1930                        $ID3v2ShortFrameNameLookup[2]['title']                                            = 'TT2';
1931                        $ID3v2ShortFrameNameLookup[2]['subtitle']                                         = 'TT3';
1932                        $ID3v2ShortFrameNameLookup[2]['lyricist']                                         = 'TXT';
1933                        $ID3v2ShortFrameNameLookup[2]['user_text']                                        = 'TXX';
1934                        $ID3v2ShortFrameNameLookup[2]['year']                                             = 'TYE';
1935                        $ID3v2ShortFrameNameLookup[2]['unique_file_identifier']                           = 'UFI';
1936                        $ID3v2ShortFrameNameLookup[2]['unsynchronised_lyrics']                            = 'ULT';
1937                        $ID3v2ShortFrameNameLookup[2]['url_file']                                         = 'WAF';
1938                        $ID3v2ShortFrameNameLookup[2]['url_artist']                                       = 'WAR';
1939                        $ID3v2ShortFrameNameLookup[2]['url_source']                                       = 'WAS';
1940                        $ID3v2ShortFrameNameLookup[2]['copyright_information']                            = 'WCP';
1941                        $ID3v2ShortFrameNameLookup[2]['url_publisher']                                    = 'WPB';
1942                        $ID3v2ShortFrameNameLookup[2]['url_user']                                         = 'WXX';
1943
1944                        // The following are common to ID3v2.3 and ID3v2.4
1945                        $ID3v2ShortFrameNameLookup[3]['audio_encryption']                                 = 'AENC';
1946                        $ID3v2ShortFrameNameLookup[3]['attached_picture']                                 = 'APIC';
1947                        $ID3v2ShortFrameNameLookup[3]['comment']                                          = 'COMM';
1948                        $ID3v2ShortFrameNameLookup[3]['commercial']                                       = 'COMR';
1949                        $ID3v2ShortFrameNameLookup[3]['encryption_method_registration']                   = 'ENCR';
1950                        $ID3v2ShortFrameNameLookup[3]['event_timing_codes']                               = 'ETCO';
1951                        $ID3v2ShortFrameNameLookup[3]['general_encapsulated_object']                      = 'GEOB';
1952                        $ID3v2ShortFrameNameLookup[3]['group_identification_registration']                = 'GRID';
1953                        $ID3v2ShortFrameNameLookup[3]['linked_information']                               = 'LINK';
1954                        $ID3v2ShortFrameNameLookup[3]['music_cd_identifier']                              = 'MCDI';
1955                        $ID3v2ShortFrameNameLookup[3]['mpeg_location_lookup_table']                       = 'MLLT';
1956                        $ID3v2ShortFrameNameLookup[3]['ownership']                                        = 'OWNE';
1957                        $ID3v2ShortFrameNameLookup[3]['play_counter']                                     = 'PCNT';
1958                        $ID3v2ShortFrameNameLookup[3]['popularimeter']                                    = 'POPM';
1959                        $ID3v2ShortFrameNameLookup[3]['position_synchronisation']                         = 'POSS';
1960                        $ID3v2ShortFrameNameLookup[3]['private']                                          = 'PRIV';
1961                        $ID3v2ShortFrameNameLookup[3]['recommended_buffer_size']                          = 'RBUF';
1962                        $ID3v2ShortFrameNameLookup[3]['reverb']                                           = 'RVRB';
1963                        $ID3v2ShortFrameNameLookup[3]['synchronised_lyrics']                              = 'SYLT';
1964                        $ID3v2ShortFrameNameLookup[3]['synchronised_tempo_codes']                         = 'SYTC';
1965                        $ID3v2ShortFrameNameLookup[3]['album']                                            = 'TALB';
1966                        $ID3v2ShortFrameNameLookup[3]['beats_per_minute']                                 = 'TBPM';
1967                        $ID3v2ShortFrameNameLookup[3]['itunescompilation']                                = 'TCMP';
1968                        $ID3v2ShortFrameNameLookup[3]['composer']                                         = 'TCOM';
1969                        $ID3v2ShortFrameNameLookup[3]['genre']                                            = 'TCON';
1970                        $ID3v2ShortFrameNameLookup[3]['copyright']                                        = 'TCOP';
1971                        $ID3v2ShortFrameNameLookup[3]['playlist_delay']                                   = 'TDLY';
1972                        $ID3v2ShortFrameNameLookup[3]['encoded_by']                                       = 'TENC';
1973                        $ID3v2ShortFrameNameLookup[3]['lyricist']                                         = 'TEXT';
1974                        $ID3v2ShortFrameNameLookup[3]['file_type']                                        = 'TFLT';
1975                        $ID3v2ShortFrameNameLookup[3]['content_group_description']                        = 'TIT1';
1976                        $ID3v2ShortFrameNameLookup[3]['title']                                            = 'TIT2';
1977                        $ID3v2ShortFrameNameLookup[3]['subtitle']                                         = 'TIT3';
1978                        $ID3v2ShortFrameNameLookup[3]['initial_key']                                      = 'TKEY';
1979                        $ID3v2ShortFrameNameLookup[3]['language']                                         = 'TLAN';
1980                        $ID3v2ShortFrameNameLookup[3]['length']                                           = 'TLEN';
1981                        $ID3v2ShortFrameNameLookup[3]['media_type']                                       = 'TMED';
1982                        $ID3v2ShortFrameNameLookup[3]['original_album_title']                             = 'TOAL';
1983                        $ID3v2ShortFrameNameLookup[3]['original_filename']                                = 'TOFN';
1984                        $ID3v2ShortFrameNameLookup[3]['original_lyricist']                                = 'TOLY';
1985                        $ID3v2ShortFrameNameLookup[3]['original_artist']                                  = 'TOPE';
1986                        $ID3v2ShortFrameNameLookup[3]['file_owner']                                       = 'TOWN';
1987                        $ID3v2ShortFrameNameLookup[3]['artist']                                           = 'TPE1';
1988                        $ID3v2ShortFrameNameLookup[3]['band']                                             = 'TPE2';
1989                        $ID3v2ShortFrameNameLookup[3]['conductor']                                        = 'TPE3';
1990                        $ID3v2ShortFrameNameLookup[3]['remixer']                                          = 'TPE4';
1991                        $ID3v2ShortFrameNameLookup[3]['part_of_a_set']                                    = 'TPOS';
1992                        $ID3v2ShortFrameNameLookup[3]['publisher']                                        = 'TPUB';
1993                        $ID3v2ShortFrameNameLookup[3]['tracknumber']                                      = 'TRCK';
1994                        $ID3v2ShortFrameNameLookup[3]['internet_radio_station_name']                      = 'TRSN';
1995                        $ID3v2ShortFrameNameLookup[3]['internet_radio_station_owner']                     = 'TRSO';
1996                        $ID3v2ShortFrameNameLookup[3]['isrc']                                             = 'TSRC';
1997                        $ID3v2ShortFrameNameLookup[3]['encoder_settings']                                 = 'TSSE';
1998                        $ID3v2ShortFrameNameLookup[3]['user_text']                                        = 'TXXX';
1999                        $ID3v2ShortFrameNameLookup[3]['unique_file_identifier']                           = 'UFID';
2000                        $ID3v2ShortFrameNameLookup[3]['terms_of_use']                                     = 'USER';
2001                        $ID3v2ShortFrameNameLookup[3]['unsynchronised_lyrics']                            = 'USLT';
2002                        $ID3v2ShortFrameNameLookup[3]['commercial']                                       = 'WCOM';
2003                        $ID3v2ShortFrameNameLookup[3]['copyright_information']                            = 'WCOP';
2004                        $ID3v2ShortFrameNameLookup[3]['url_file']                                         = 'WOAF';
2005                        $ID3v2ShortFrameNameLookup[3]['url_artist']                                       = 'WOAR';
2006                        $ID3v2ShortFrameNameLookup[3]['url_source']                                       = 'WOAS';
2007                        $ID3v2ShortFrameNameLookup[3]['url_station']                                      = 'WORS';
2008                        $ID3v2ShortFrameNameLookup[3]['payment']                                          = 'WPAY';
2009                        $ID3v2ShortFrameNameLookup[3]['url_publisher']                                    = 'WPUB';
2010                        $ID3v2ShortFrameNameLookup[3]['url_user']                                         = 'WXXX';
2011
2012                        // The above are common to ID3v2.3 and ID3v2.4
2013                        // so copy them to ID3v2.4 before adding specifics for 2.3 and 2.4
2014                        $ID3v2ShortFrameNameLookup[4] = $ID3v2ShortFrameNameLookup[3];
2015
2016                        // The following are unique to ID3v2.3
2017                        $ID3v2ShortFrameNameLookup[3]['equalisation']                                     = 'EQUA';
2018                        $ID3v2ShortFrameNameLookup[3]['involved_people_list']                             = 'IPLS';
2019                        $ID3v2ShortFrameNameLookup[3]['relative_volume_adjustment']                       = 'RVAD';
2020                        $ID3v2ShortFrameNameLookup[3]['date']                                             = 'TDAT';
2021                        $ID3v2ShortFrameNameLookup[3]['time']                                             = 'TIME';
2022                        $ID3v2ShortFrameNameLookup[3]['original_release_year']                            = 'TORY';
2023                        $ID3v2ShortFrameNameLookup[3]['recording_dates']                                  = 'TRDA';
2024                        $ID3v2ShortFrameNameLookup[3]['size']                                             = 'TSIZ';
2025                        $ID3v2ShortFrameNameLookup[3]['year']                                             = 'TYER';
2026
2027
2028                        // The following are unique to ID3v2.4
2029                        $ID3v2ShortFrameNameLookup[4]['audio_seek_point_index']                           = 'ASPI';
2030                        $ID3v2ShortFrameNameLookup[4]['equalisation']                                     = 'EQU2';
2031                        $ID3v2ShortFrameNameLookup[4]['relative_volume_adjustment']                       = 'RVA2';
2032                        $ID3v2ShortFrameNameLookup[4]['seek']                                             = 'SEEK';
2033                        $ID3v2ShortFrameNameLookup[4]['signature']                                        = 'SIGN';
2034                        $ID3v2ShortFrameNameLookup[4]['encoding_time']                                    = 'TDEN';
2035                        $ID3v2ShortFrameNameLookup[4]['original_release_time']                            = 'TDOR';
2036                        $ID3v2ShortFrameNameLookup[4]['recording_time']                                   = 'TDRC';
2037                        $ID3v2ShortFrameNameLookup[4]['release_time']                                     = 'TDRL';
2038                        $ID3v2ShortFrameNameLookup[4]['tagging_time']                                     = 'TDTG';
2039                        $ID3v2ShortFrameNameLookup[4]['involved_people_list']                             = 'TIPL';
2040                        $ID3v2ShortFrameNameLookup[4]['musician_credits_list']                            = 'TMCL';
2041                        $ID3v2ShortFrameNameLookup[4]['mood']                                             = 'TMOO';
2042                        $ID3v2ShortFrameNameLookup[4]['produced_notice']                                  = 'TPRO';
2043                        $ID3v2ShortFrameNameLookup[4]['album_sort_order']                                 = 'TSOA';
2044                        $ID3v2ShortFrameNameLookup[4]['performer_sort_order']                             = 'TSOP';
2045                        $ID3v2ShortFrameNameLookup[4]['title_sort_order']                                 = 'TSOT';
2046                        $ID3v2ShortFrameNameLookup[4]['set_subtitle']                                     = 'TSST';
2047                }
2048                return @$ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)];
2049
2050        }
2051
2052}
2053
2054?>
Note: See TracBrowser for help on using the repository browser.