source: spip-zone/_core_/branches/spip-3.1/plugins/medias/lib/getid3/write.id3v2.php @ 113163

Last change on this file since 113163 was 113163, checked in by spip.franck@…, 5 months ago

Mise à jour de la lib getid en version 1.9.16, nous étions en 1.9.12
https://github.com/JamesHeinrich/getID3/blob/master/changelog.txt

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