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

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

+ Add Charlies' content to depository

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