PageRenderTime 158ms CodeModel.GetById 60ms app.highlight 51ms RepoModel.GetById 29ms app.codeStats 2ms

/common/libraries/plugin/getid3/write.id3v2.php

https://bitbucket.org/renaatdemuynck/chamilo
PHP | 2637 lines | 2159 code | 166 blank | 312 comment | 339 complexity | 2e6f9ddef7a2f0f3585b9fcbb032e6ab MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1<?php
  2/////////////////////////////////////////////////////////////////
  3/// getID3() by James Heinrich <info@getid3.org>               //
  4//  available at http://getid3.sourceforge.net                 //
  5//            or http://www.getid3.org                         //
  6/////////////////////////////////////////////////////////////////
  7// See readme.txt for more details                             //
  8/////////////////////////////////////////////////////////////////
  9///                                                            //
 10// write.id3v2.php                                             //
 11// module for writing ID3v2 tags                               //
 12// dependencies: module.tag.id3v2.php                          //
 13//                                                            ///
 14/////////////////////////////////////////////////////////////////
 15
 16
 17getid3_lib :: IncludeDependency(GETID3_INCLUDEPATH . 'module.tag.id3v2.php', __FILE__, true);
 18
 19class getid3_write_id3v2
 20{
 21    var $filename;
 22    var $tag_data;
 23    var $paddedlength = 4096; // minimum length of ID3v2 tag in bytes
 24    var $majorversion = 3; // ID3v2 major version (2, 3 (recommended), 4)
 25    var $minorversion = 0; // ID3v2 minor version - always 0
 26    var $merge_existing_data = false; // if true, merge new data with existing tags; if false, delete old tag data and only write new tags
 27    var $id3v2_default_encodingid = 0; // default text encoding (ISO-8859-1) if not explicitly passed
 28    var $id3v2_use_unsynchronisation = false; // the specs say it should be TRUE, but most other ID3v2-aware programs are broken if unsynchronization is used, so by default don't use it.
 29    var $warnings = array(); // any non-critical errors will be stored here
 30    var $errors = array(); // any critical errors will be stored here
 31
 32    
 33    function __construct()
 34    {
 35        return true;
 36    }
 37
 38    function WriteID3v2()
 39    {
 40        // File MUST be writeable - CHMOD(646) at least. It's best if the
 41        // directory is also writeable, because that method is both faster and less susceptible to errors.
 42        
 43
 44        if (is_writeable($this->filename) || (! file_exists($this->filename) && is_writeable(dirname($this->filename))))
 45        {
 46            // Initialize getID3 engine
 47            $getID3 = new getID3();
 48            $OldThisFileInfo = $getID3->analyze($this->filename);
 49            if ($OldThisFileInfo['filesize'] >= pow(2, 31))
 50            {
 51                $this->errors[] = 'Unable to write ID3v2 because file is larger than 2GB';
 52                fclose($fp_source);
 53                return false;
 54            }
 55            if ($this->merge_existing_data)
 56            {
 57                // merge with existing data
 58                if (! empty($OldThisFileInfo['id3v2']))
 59                {
 60                    $this->tag_data = $this->array_join_merge($OldThisFileInfo['id3v2'], $this->tag_data);
 61                }
 62            }
 63            $this->paddedlength = max(@$OldThisFileInfo['id3v2']['headerlength'], $this->paddedlength);
 64            
 65            if ($NewID3v2Tag = $this->GenerateID3v2Tag())
 66            {
 67                
 68                if (file_exists($this->filename) && is_writeable($this->filename) && isset($OldThisFileInfo['id3v2']['headerlength']) && ($OldThisFileInfo['id3v2']['headerlength'] == strlen($NewID3v2Tag)))
 69                {
 70                    
 71                    // best and fastest method - insert-overwrite existing tag (padded to length of old tag if neccesary)
 72                    if (file_exists($this->filename))
 73                    {
 74                        
 75                        ob_start();
 76                        if ($fp = fopen($this->filename, 'r+b'))
 77                        {
 78                            rewind($fp);
 79                            fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
 80                            fclose($fp);
 81                        }
 82                        else
 83                        {
 84                            $this->errors[] = 'Could not open ' . $this->filename . ' mode "r+b" - ' . strip_tags(ob_get_contents());
 85                        }
 86                        ob_end_clean();
 87                    
 88                    }
 89                    else
 90                    {
 91                        
 92                        ob_start();
 93                        if ($fp = fopen($this->filename, 'wb'))
 94                        {
 95                            rewind($fp);
 96                            fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
 97                            fclose($fp);
 98                        }
 99                        else
100                        {
101                            $this->errors[] = 'Could not open ' . $this->filename . ' mode "wb" - ' . strip_tags(ob_get_contents());
102                        }
103                        ob_end_clean();
104                    
105                    }
106                
107                }
108                else
109                {
110                    
111                    if ($tempfilename = tempnam('*', 'getID3'))
112                    {
113                        ob_start();
114                        if ($fp_source = fopen($this->filename, 'rb'))
115                        {
116                            if ($fp_temp = fopen($tempfilename, 'wb'))
117                            {
118                                
119                                fwrite($fp_temp, $NewID3v2Tag, strlen($NewID3v2Tag));
120                                
121                                rewind($fp_source);
122                                if (! empty($OldThisFileInfo['avdataoffset']))
123                                {
124                                    fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
125                                }
126                                
127                                while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE))
128                                {
129                                    fwrite($fp_temp, $buffer, strlen($buffer));
130                                }
131                                
132                                fclose($fp_temp);
133                                fclose($fp_source);
134                                copy($tempfilename, $this->filename);
135                                unlink($tempfilename);
136                                ob_end_clean();
137                                return true;
138                            
139                            }
140                            else
141                            {
142                                
143                                $this->errors[] = 'Could not open ' . $tempfilename . ' mode "wb" - ' . strip_tags(ob_get_contents());
144                            
145                            }
146                            fclose($fp_source);
147                        
148                        }
149                        else
150                        {
151                            
152                            $this->errors[] = 'Could not open ' . $this->filename . ' mode "rb" - ' . strip_tags(ob_get_contents());
153                        
154                        }
155                        ob_end_clean();
156                    }
157                    return false;
158                
159                }
160            
161            }
162            else
163            {
164                
165                $this->errors[] = '$this->GenerateID3v2Tag() failed';
166            
167            }
168            
169            if (! empty($this->errors))
170            {
171                return false;
172            }
173            return true;
174        }
175        else
176        {
177            $this->errors[] = '!is_writeable(' . $this->filename . ')';
178        }
179        return false;
180    }
181
182    function RemoveID3v2()
183    {
184        // File MUST be writeable - CHMOD(646) at least. It's best if the
185        // directory is also writeable, because that method is both faster and less susceptible to errors.
186        if (is_writeable(dirname($this->filename)))
187        {
188            
189            // preferred method - only one copying operation, minimal chance of corrupting
190            // original file if script is interrupted, but required directory to be writeable
191            if ($fp_source = @fopen($this->filename, 'rb'))
192            {
193                // Initialize getID3 engine
194                $getID3 = new getID3();
195                $OldThisFileInfo = $getID3->analyze($this->filename);
196                if ($OldThisFileInfo['filesize'] >= pow(2, 31))
197                {
198                    $this->errors[] = 'Unable to remove ID3v2 because file is larger than 2GB';
199                    fclose($fp_source);
200                    return false;
201                }
202                rewind($fp_source);
203                if ($OldThisFileInfo['avdataoffset'] !== false)
204                {
205                    fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
206                }
207                if ($fp_temp = @fopen($this->filename . 'getid3tmp', 'w+b'))
208                {
209                    while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE))
210                    {
211                        fwrite($fp_temp, $buffer, strlen($buffer));
212                    }
213                    fclose($fp_temp);
214                }
215                else
216                {
217                    $this->errors[] = 'Could not open ' . $this->filename . 'getid3tmp mode "w+b"';
218                }
219                fclose($fp_source);
220            }
221            else
222            {
223                $this->errors[] = 'Could not open ' . $this->filename . ' mode "rb"';
224            }
225            if (file_exists($this->filename))
226            {
227                unlink($this->filename);
228            }
229            rename($this->filename . 'getid3tmp', $this->filename);
230        
231        }
232        elseif (is_writable($this->filename))
233        {
234            
235            // less desirable alternate method - double-copies the file, overwrites original file
236            // and could corrupt source file if the script is interrupted or an error occurs.
237            if ($fp_source = @fopen($this->filename, 'rb'))
238            {
239                // Initialize getID3 engine
240                $getID3 = new getID3();
241                $OldThisFileInfo = $getID3->analyze($this->filename);
242                if ($OldThisFileInfo['filesize'] >= pow(2, 31))
243                {
244                    $this->errors[] = 'Unable to remove ID3v2 because file is larger than 2GB';
245                    fclose($fp_source);
246                    return false;
247                }
248                rewind($fp_source);
249                if ($OldThisFileInfo['avdataoffset'] !== false)
250                {
251                    fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
252                }
253                if ($fp_temp = tmpfile())
254                {
255                    while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE))
256                    {
257                        fwrite($fp_temp, $buffer, strlen($buffer));
258                    }
259                    fclose($fp_source);
260                    if ($fp_source = @fopen($this->filename, 'wb'))
261                    {
262                        rewind($fp_temp);
263                        while ($buffer = fread($fp_temp, GETID3_FREAD_BUFFER_SIZE))
264                        {
265                            fwrite($fp_source, $buffer, strlen($buffer));
266                        }
267                        fseek($fp_temp, - 128, SEEK_END);
268                        fclose($fp_source);
269                    }
270                    else
271                    {
272                        $this->errors[] = 'Could not open ' . $this->filename . ' mode "wb"';
273                    }
274                    fclose($fp_temp);
275                }
276                else
277                {
278                    $this->errors[] = 'Could not create tmpfile()';
279                }
280            }
281            else
282            {
283                $this->errors[] = 'Could not open ' . $this->filename . ' mode "rb"';
284            }
285        
286        }
287        else
288        {
289            
290            $this->errors[] = 'Directory and file both not writeable';
291        
292        }
293        
294        if (! empty($this->errors))
295        {
296            return false;
297        }
298        return true;
299    }
300
301    function GenerateID3v2TagFlags($flags)
302    {
303        switch ($this->majorversion)
304        {
305            case 4 :
306                // %abcd0000
307                $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
308                $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header
309                $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator
310                $flag .= (@$flags['footer'] ? '1' : '0'); // d - Footer present
311                $flag .= '0000';
312                break;
313            
314            case 3 :
315                // %abc00000
316                $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
317                $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header
318                $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator
319                $flag .= '00000';
320                break;
321            
322            case 2 :
323                // %ab000000
324                $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
325                $flag .= (@$flags['compression'] ? '1' : '0'); // b - Compression
326                $flag .= '000000';
327                break;
328            
329            default :
330                return false;
331                break;
332        }
333        return chr(bindec($flag));
334    }
335
336    function GenerateID3v2FrameFlags($TagAlter = false, $FileAlter = false, $ReadOnly = false, $Compression = false, $Encryption = false, $GroupingIdentity = false, $Unsynchronisation = false, $DataLengthIndicator = false)
337    {
338        switch ($this->majorversion)
339        {
340            case 4 :
341                // %0abc0000 %0h00kmnp
342                $flag1 = '0';
343                $flag1 .= $TagAlter ? '1' : '0'; // a - Tag alter preservation (true == discard)
344                $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard)
345                $flag1 .= $ReadOnly ? '1' : '0'; // c - Read only (true == read only)
346                $flag1 .= '0000';
347                
348                $flag2 = '0';
349                $flag2 .= $GroupingIdentity ? '1' : '0'; // h - Grouping identity (true == contains group information)
350                $flag2 .= '00';
351                $flag2 .= $Compression ? '1' : '0'; // k - Compression (true == compressed)
352                $flag2 .= $Encryption ? '1' : '0'; // m - Encryption (true == encrypted)
353                $flag2 .= $Unsynchronisation ? '1' : '0'; // n - Unsynchronisation (true == unsynchronised)
354                $flag2 .= $DataLengthIndicator ? '1' : '0'; // p - Data length indicator (true == data length indicator added)
355                break;
356            
357            case 3 :
358                // %abc00000 %ijk00000
359                $flag1 = $TagAlter ? '1' : '0'; // a - Tag alter preservation (true == discard)
360                $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard)
361                $flag1 .= $ReadOnly ? '1' : '0'; // c - Read only (true == read only)
362                $flag1 .= '00000';
363                
364                $flag2 = $Compression ? '1' : '0'; // i - Compression (true == compressed)
365                $flag2 .= $Encryption ? '1' : '0'; // j - Encryption (true == encrypted)
366                $flag2 .= $GroupingIdentity ? '1' : '0'; // k - Grouping identity (true == contains group information)
367                $flag2 .= '00000';
368                break;
369            
370            default :
371                return false;
372                break;
373        
374        }
375        return chr(bindec($flag1)) . chr(bindec($flag2));
376    }
377
378    function GenerateID3v2FrameData($frame_name, $source_data_array)
379    {
380        if (! getid3_id3v2 :: IsValidID3v2FrameName($frame_name, $this->majorversion))
381        {
382            return false;
383        }
384        $framedata = '';
385        
386        if (($this->majorversion < 3) || ($this->majorversion > 4))
387        {
388            
389            $this->errors[] = 'Only ID3v2.3 and ID3v2.4 are supported in GenerateID3v2FrameData()';
390        
391        }
392        else
393        { // $this->majorversion 3 or 4
394            
395
396            switch ($frame_name)
397            {
398                case 'UFID' :
399                    // 4.1   UFID Unique file identifier
400                    // Owner identifier        <text string> $00
401                    // Identifier              <up to 64 bytes binary data>
402                    if (strlen($source_data_array['data']) > 64)
403                    {
404                        $this->errors[] = 'Identifier not allowed to be longer than 64 bytes in ' . $frame_name . ' (supplied data was ' . strlen($source_data_array['data']) . ' bytes long)';
405                    }
406                    else
407                    {
408                        $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
409                        $framedata .= substr($source_data_array['data'], 0, 64); // max 64 bytes - truncate anything longer
410                    }
411                    break;
412                
413                case 'TXXX' :
414                    // 4.2.2 TXXX User defined text information frame
415                    // Text encoding     $xx
416                    // Description       <text string according to encoding> $00 (00)
417                    // Value             <text string according to encoding>
418                    $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
419                    if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion))
420                    {
421                        $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
422                    }
423                    else
424                    {
425                        $framedata .= chr($source_data_array['encodingid']);
426                        $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
427                        $framedata .= $source_data_array['data'];
428                    }
429                    break;
430                
431                case 'WXXX' :
432                    // 4.3.2 WXXX User defined URL link frame
433                    // Text encoding     $xx
434                    // Description       <text string according to encoding> $00 (00)
435                    // URL               <text string>
436                    $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
437                    if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion))
438                    {
439                        $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
440                    }
441                    elseif (! isset($source_data_array['data']) || ! $this->IsValidURL($source_data_array['data'], false, false))
442                    {
443                        //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
444                        // probably should be an error, need to rewrite IsValidURL() to handle other encodings
445                        $this->warnings[] = 'Invalid URL in ' . $frame_name . ' (' . $source_data_array['data'] . ')';
446                    }
447                    else
448                    {
449                        $framedata .= chr($source_data_array['encodingid']);
450                        $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
451                        $framedata .= $source_data_array['data'];
452                    }
453                    break;
454                
455                case 'IPLS' :
456                    // 4.4  IPLS Involved people list (ID3v2.3 only)
457                    // Text encoding     $xx
458                    // People list strings    <textstrings>
459                    $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
460                    if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion))
461                    {
462                        $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
463                    }
464                    else
465                    {
466                        $framedata .= chr($source_data_array['encodingid']);
467                        $framedata .= $source_data_array['data'];
468                    }
469                    break;
470                
471                case 'MCDI' :
472                    // 4.4   MCDI Music CD identifier
473                    // CD TOC                <binary data>
474                    $framedata .= $source_data_array['data'];
475                    break;
476                
477                case 'ETCO' :
478                    // 4.5   ETCO Event timing codes
479                    // Time stamp format    $xx
480                    //   Where time stamp format is:
481                    // $01  (32-bit value) MPEG frames from beginning of file
482                    // $02  (32-bit value) milliseconds from beginning of file
483                    //   Followed by a list of key events in the following format:
484                    // Type of event   $xx
485                    // Time stamp      $xx (xx ...)
486                    //   The 'Time stamp' is set to zero if directly at the beginning of the sound
487                    //   or after the previous event. All events MUST be sorted in chronological order.
488                    if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1))
489                    {
490                        $this->errors[] = 'Invalid Time Stamp Format byte in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ')';
491                    }
492                    else
493                    {
494                        $framedata .= chr($source_data_array['timestampformat']);
495                        foreach ($source_data_array as $key => $val)
496                        {
497                            if (! $this->ID3v2IsValidETCOevent($val['typeid']))
498                            {
499                                $this->errors[] = 'Invalid Event Type byte in ' . $frame_name . ' (' . $val['typeid'] . ')';
500                            }
501                            elseif (($key != 'timestampformat') && ($key != 'flags'))
502                            {
503                                if (($val['timestamp'] > 0) && ($previousETCOtimestamp >= $val['timestamp']))
504                                {
505                                    //   The 'Time stamp' is set to zero if directly at the beginning of the sound
506                                    //   or after the previous event. All events MUST be sorted in chronological order.
507                                    $this->errors[] = 'Out-of-order timestamp in ' . $frame_name . ' (' . $val['timestamp'] . ') for Event Type (' . $val['typeid'] . ')';
508                                }
509                                else
510                                {
511                                    $framedata .= chr($val['typeid']);
512                                    $framedata .= getid3_lib :: BigEndian2String($val['timestamp'], 4, false);
513                                }
514                            }
515                        }
516                    }
517                    break;
518                
519                case 'MLLT' :
520                    // 4.6   MLLT MPEG location lookup table
521                    // MPEG frames between reference  $xx xx
522                    // Bytes between reference        $xx xx xx
523                    // Milliseconds between reference $xx xx xx
524                    // Bits for bytes deviation       $xx
525                    // Bits for milliseconds dev.     $xx
526                    //   Then for every reference the following data is included;
527                    // Deviation in bytes         %xxx....
528                    // Deviation in milliseconds  %xxx....
529                    if (($source_data_array['framesbetweenreferences'] > 0) && ($source_data_array['framesbetweenreferences'] <= 65535))
530                    {
531                        $framedata .= getid3_lib :: BigEndian2String($source_data_array['framesbetweenreferences'], 2, false);
532                    }
533                    else
534                    {
535                        $this->errors[] = 'Invalid MPEG Frames Between References in ' . $frame_name . ' (' . $source_data_array['framesbetweenreferences'] . ')';
536                    }
537                    if (($source_data_array['bytesbetweenreferences'] > 0) && ($source_data_array['bytesbetweenreferences'] <= 16777215))
538                    {
539                        $framedata .= getid3_lib :: BigEndian2String($source_data_array['bytesbetweenreferences'], 3, false);
540                    }
541                    else
542                    {
543                        $this->errors[] = 'Invalid bytes Between References in ' . $frame_name . ' (' . $source_data_array['bytesbetweenreferences'] . ')';
544                    }
545                    if (($source_data_array['msbetweenreferences'] > 0) && ($source_data_array['msbetweenreferences'] <= 16777215))
546                    {
547                        $framedata .= getid3_lib :: BigEndian2String($source_data_array['msbetweenreferences'], 3, false);
548                    }
549                    else
550                    {
551                        $this->errors[] = 'Invalid Milliseconds Between References in ' . $frame_name . ' (' . $source_data_array['msbetweenreferences'] . ')';
552                    }
553                    if (! $this->IsWithinBitRange($source_data_array['bitsforbytesdeviation'], 8, false))
554                    {
555                        if (($source_data_array['bitsforbytesdeviation'] % 4) == 0)
556                        {
557                            $framedata .= chr($source_data_array['bitsforbytesdeviation']);
558                        }
559                        else
560                        {
561                            $this->errors[] = 'Bits For Bytes Deviation in ' . $frame_name . ' (' . $source_data_array['bitsforbytesdeviation'] . ') must be a multiple of 4.';
562                        }
563                    }
564                    else
565                    {
566                        $this->errors[] = 'Invalid Bits For Bytes Deviation in ' . $frame_name . ' (' . $source_data_array['bitsforbytesdeviation'] . ')';
567                    }
568                    if (! $this->IsWithinBitRange($source_data_array['bitsformsdeviation'], 8, false))
569                    {
570                        if (($source_data_array['bitsformsdeviation'] % 4) == 0)
571                        {
572                            $framedata .= chr($source_data_array['bitsformsdeviation']);
573                        }
574                        else
575                        {
576                            $this->errors[] = 'Bits For Milliseconds Deviation in ' . $frame_name . ' (' . $source_data_array['bitsforbytesdeviation'] . ') must be a multiple of 4.';
577                        }
578                    }
579                    else
580                    {
581                        $this->errors[] = 'Invalid Bits For Milliseconds Deviation in ' . $frame_name . ' (' . $source_data_array['bitsformsdeviation'] . ')';
582                    }
583                    foreach ($source_data_array as $key => $val)
584                    {
585                        if (($key != 'framesbetweenreferences') && ($key != 'bytesbetweenreferences') && ($key != 'msbetweenreferences') && ($key != 'bitsforbytesdeviation') && ($key != 'bitsformsdeviation') && ($key != 'flags'))
586                        {
587                            $unwrittenbitstream .= str_pad(getid3_lib :: Dec2Bin($val['bytedeviation']), $source_data_array['bitsforbytesdeviation'], '0', STR_PAD_LEFT);
588                            $unwrittenbitstream .= str_pad(getid3_lib :: Dec2Bin($val['msdeviation']), $source_data_array['bitsformsdeviation'], '0', STR_PAD_LEFT);
589                        }
590                    }
591                    for($i = 0; $i < strlen($unwrittenbitstream); $i += 8)
592                    {
593                        $highnibble = bindec(substr($unwrittenbitstream, $i, 4)) << 4;
594                        $lownibble = bindec(substr($unwrittenbitstream, $i + 4, 4));
595                        $framedata .= chr($highnibble & $lownibble);
596                    }
597                    break;
598                
599                case 'SYTC' :
600                    // 4.7   SYTC Synchronised tempo codes
601                    // Time stamp format   $xx
602                    // Tempo data          <binary data>
603                    //   Where time stamp format is:
604                    // $01  (32-bit value) MPEG frames from beginning of file
605                    // $02  (32-bit value) milliseconds from beginning of file
606                    if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1))
607                    {
608                        $this->errors[] = 'Invalid Time Stamp Format byte in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ')';
609                    }
610                    else
611                    {
612                        $framedata .= chr($source_data_array['timestampformat']);
613                        foreach ($source_data_array as $key => $val)
614                        {
615                            if (! $this->ID3v2IsValidETCOevent($val['typeid']))
616                            {
617                                $this->errors[] = 'Invalid Event Type byte in ' . $frame_name . ' (' . $val['typeid'] . ')';
618                            }
619                            elseif (($key != 'timestampformat') && ($key != 'flags'))
620                            {
621                                if (($val['tempo'] < 0) || ($val['tempo'] > 510))
622                                {
623                                    $this->errors[] = 'Invalid Tempo (max = 510) in ' . $frame_name . ' (' . $val['tempo'] . ') at timestamp (' . $val['timestamp'] . ')';
624                                }
625                                else
626                                {
627                                    if ($val['tempo'] > 255)
628                                    {
629                                        $framedata .= chr(255);
630                                        $val['tempo'] -= 255;
631                                    }
632                                    $framedata .= chr($val['tempo']);
633                                    $framedata .= getid3_lib :: BigEndian2String($val['timestamp'], 4, false);
634                                }
635                            }
636                        }
637                    }
638                    break;
639                
640                case 'USLT' :
641                    // 4.8   USLT Unsynchronised lyric/text transcription
642                    // Text encoding        $xx
643                    // Language             $xx xx xx
644                    // Content descriptor   <text string according to encoding> $00 (00)
645                    // Lyrics/text          <full text string according to encoding>
646                    $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
647                    if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
648                    {
649                        $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
650                    }
651                    elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
652                    {
653                        $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
654                    }
655                    else
656                    {
657                        $framedata .= chr($source_data_array['encodingid']);
658                        $framedata .= strtolower($source_data_array['language']);
659                        $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
660                        $framedata .= $source_data_array['data'];
661                    }
662                    break;
663                
664                case 'SYLT' :
665                    // 4.9   SYLT Synchronised lyric/text
666                    // Text encoding        $xx
667                    // Language             $xx xx xx
668                    // Time stamp format    $xx
669                    //   $01  (32-bit value) MPEG frames from beginning of file
670                    //   $02  (32-bit value) milliseconds from beginning of file
671                    // Content type         $xx
672                    // Content descriptor   <text string according to encoding> $00 (00)
673                    //   Terminated text to be synced (typically a syllable)
674                    //   Sync identifier (terminator to above string)   $00 (00)
675                    //   Time stamp                                     $xx (xx ...)
676                    $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
677                    if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
678                    {
679                        $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
680                    }
681                    elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
682                    {
683                        $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
684                    }
685                    elseif (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1))
686                    {
687                        $this->errors[] = 'Invalid Time Stamp Format byte in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ')';
688                    }
689                    elseif (! $this->ID3v2IsValidSYLTtype($source_data_array['contenttypeid']))
690                    {
691                        $this->errors[] = 'Invalid Content Type byte in ' . $frame_name . ' (' . $source_data_array['contenttypeid'] . ')';
692                    }
693                    elseif (! is_array($source_data_array['data']))
694                    {
695                        $this->errors[] = 'Invalid Lyric/Timestamp data in ' . $frame_name . ' (must be an array)';
696                    }
697                    else
698                    {
699                        $framedata .= chr($source_data_array['encodingid']);
700                        $framedata .= strtolower($source_data_array['language']);
701                        $framedata .= chr($source_data_array['timestampformat']);
702                        $framedata .= chr($source_data_array['contenttypeid']);
703                        $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
704                        ksort($source_data_array['data']);
705                        foreach ($source_data_array['data'] as $key => $val)
706                        {
707                            $framedata .= $val['data'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
708                            $framedata .= getid3_lib :: BigEndian2String($val['timestamp'], 4, false);
709                        }
710                    }
711                    break;
712                
713                case 'COMM' :
714                    // 4.10  COMM Comments
715                    // Text encoding          $xx
716                    // Language               $xx xx xx
717                    // Short content descrip. <text string according to encoding> $00 (00)
718                    // The actual text        <full text string according to encoding>
719                    $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
720                    if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
721                    {
722                        $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
723                    }
724                    elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
725                    {
726                        $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
727                    }
728                    else
729                    {
730                        $framedata .= chr($source_data_array['encodingid']);
731                        $framedata .= strtolower($source_data_array['language']);
732                        $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
733                        $framedata .= $source_data_array['data'];
734                    }
735                    break;
736                
737                case 'RVA2' :
738                    // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
739                    // Identification          <text string> $00
740                    //   The 'identification' string is used to identify the situation and/or
741                    //   device where this adjustment should apply. The following is then
742                    //   repeated for every channel:
743                    // Type of channel         $xx
744                    // Volume adjustment       $xx xx
745                    // Bits representing peak  $xx
746                    // Peak volume             $xx (xx ...)
747                    $framedata .= str_replace("\x00", '', $source_data_array['description']) . "\x00";
748                    foreach ($source_data_array as $key => $val)
749                    {
750                        if ($key != 'description')
751                        {
752                            $framedata .= chr($val['channeltypeid']);
753                            $framedata .= getid3_lib :: BigEndian2String($val['volumeadjust'], 2, false, true); // signed 16-bit
754                            if (! $this->IsWithinBitRange($source_data_array['bitspeakvolume'], 8, false))
755                            {
756                                $framedata .= chr($val['bitspeakvolume']);
757                                if ($val['bitspeakvolume'] > 0)
758                                {
759                                    $framedata .= getid3_lib :: BigEndian2String($val['peakvolume'], ceil($val['bitspeakvolume'] / 8), false, false);
760                                }
761                            }
762                            else
763                            {
764                                $this->errors[] = 'Invalid Bits Representing Peak Volume in ' . $frame_name . ' (' . $val['bitspeakvolume'] . ') (range = 0 to 255)';
765                            }
766                        }
767                    }
768                    break;
769                
770                case 'RVAD' :
771                    // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
772                    // Increment/decrement     %00fedcba
773                    // Bits used for volume descr.        $xx
774                    // Relative volume change, right      $xx xx (xx ...) // a
775                    // Relative volume change, left       $xx xx (xx ...) // b
776                    // Peak volume right                  $xx xx (xx ...)
777                    // Peak volume left                   $xx xx (xx ...)
778                    // Relative volume change, right back $xx xx (xx ...) // c
779                    // Relative volume change, left back  $xx xx (xx ...) // d
780                    // Peak volume right back             $xx xx (xx ...)
781                    // Peak volume left back              $xx xx (xx ...)
782                    // Relative volume change, center     $xx xx (xx ...) // e
783                    // Peak volume center                 $xx xx (xx ...)
784                    // Relative volume change, bass       $xx xx (xx ...) // f
785                    // Peak volume bass                   $xx xx (xx ...)
786                    if (! $this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false))
787                    {
788                        $this->errors[] = 'Invalid Bits For Volume Description byte in ' . $frame_name . ' (' . $source_data_array['bitsvolume'] . ') (range = 1 to 255)';
789                    }
790                    else
791                    {
792                        $incdecflag .= '00';
793                        $incdecflag .= $source_data_array['incdec']['right'] ? '1' : '0'; // a - Relative volume change, right
794                        $incdecflag .= $source_data_array['incdec']['left'] ? '1' : '0'; // b - Relative volume change, left
795                        $incdecflag .= $source_data_array['incdec']['rightrear'] ? '1' : '0'; // c - Relative volume change, right back
796                        $incdecflag .= $source_data_array['incdec']['leftrear'] ? '1' : '0'; // d - Relative volume change, left back
797                        $incdecflag .= $source_data_array['incdec']['center'] ? '1' : '0'; // e - Relative volume change, center
798                        $incdecflag .= $source_data_array['incdec']['bass'] ? '1' : '0'; // f - Relative volume change, bass
799                        $framedata .= chr(bindec($incdecflag));
800                        $framedata .= chr($source_data_array['bitsvolume']);
801                        $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['right'], ceil($source_data_array['bitsvolume'] / 8), false);
802                        $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['left'], ceil($source_data_array['bitsvolume'] / 8), false);
803                        $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['right'], ceil($source_data_array['bitsvolume'] / 8), false);
804                        $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['left'], ceil($source_data_array['bitsvolume'] / 8), false);
805                        if ($source_data_array['volumechange']['rightrear'] || $source_data_array['volumechange']['leftrear'] || $source_data_array['peakvolume']['rightrear'] || $source_data_array['peakvolume']['leftrear'] || $source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] || $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass'])
806                        {
807                            $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['rightrear'], ceil($source_data_array['bitsvolume'] / 8), false);
808                            $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['leftrear'], ceil($source_data_array['bitsvolume'] / 8), false);
809                            $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['rightrear'], ceil($source_data_array['bitsvolume'] / 8), false);
810                            $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['leftrear'], ceil($source_data_array['bitsvolume'] / 8), false);
811                        }
812                        if ($source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] || $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass'])
813                        {
814                            $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['center'], ceil($source_data_array['bitsvolume'] / 8), false);
815                            $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['center'], ceil($source_data_array['bitsvolume'] / 8), false);
816                        }
817                        if ($source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass'])
818                        {
819                            $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['bass'], ceil($source_data_array['bitsvolume'] / 8), false);
820                            $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['bass'], ceil($source_data_array['bitsvolume'] / 8), false);
821                        }
822                    }
823                    break;
824                
825                case 'EQU2' :
826                    // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
827                    // Interpolation method  $xx
828                    //   $00  Band
829                    //   $01  Linear
830                    // Identification        <text string> $00
831                    //   The following is then repeated for every adjustment point
832                    // Frequency          $xx xx
833                    // Volume adjustment  $xx xx
834                    if (($source_data_array['interpolationmethod'] < 0) || ($source_data_array['interpolationmethod'] > 1))
835                    {
836                        $this->errors[] = 'Invalid Interpolation Method byte in ' . $frame_name . ' (' . $source_data_array['interpolationmethod'] . ') (valid = 0 or 1)';
837                    }
838                    else
839                    {
840                        $framedata .= chr($source_data_array['interpolationmethod']);
841                        $framedata .= str_replace("\x00", '', $source_data_array['description']) . "\x00";
842                        foreach ($source_data_array['data'] as $key => $val)
843                        {
844                            $framedata .= getid3_lib :: BigEndian2String(intval(round($key * 2)), 2, false);
845                            $framedata .= getid3_lib :: BigEndian2String($val, 2, false, true); // signed 16-bit
846                        }
847                    }
848                    break;
849                
850                case 'EQUA' :
851                    // 4.12  EQUA Equalisation (ID3v2.3 only)
852                    // Adjustment bits    $xx
853                    //   This is followed by 2 bytes + ('adjustment bits' rounded up to the
854                    //   nearest byte) for every equalisation band in the following format,
855                    //   giving a frequency range of 0 - 32767Hz:
856                    // Increment/decrement   %x (MSB of the Frequency)
857                    // Frequency             (lower 15 bits)
858                    // Adjustment            $xx (xx ...)
859                    if (! $this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false))
860                    {
861                        $this->errors[] = 'Invalid Adjustment Bits byte in ' . $frame_name . ' (' . $source_data_array['bitsvolume'] . ') (range = 1 to 255)';
862                    }
863                    else
864                    {
865                        $framedata .= chr($source_data_array['adjustmentbits']);
866                        foreach ($source_data_array as $key => $val)
867                        {
868                            if ($key != 'bitsvolume')
869                            {
870                                if (($key > 32767) || ($key < 0))
871                                {
872                                    $this->errors[] = 'Invalid Frequency in ' . $frame_name . ' (' . $key . ') (range = 0 to 32767)';
873                                }
874                                else
875                                {
876                                    if ($val >= 0)
877                                    {
878                                        // put MSB of frequency to 1 if increment, 0 if decrement
879                                        $key |= 0x8000;
880                                    }
881                                    $framedata .= getid3_lib :: BigEndian2String($key, 2, false);
882                                    $framedata .= getid3_lib :: BigEndian2String($val, ceil($source_data_array['adjustmentbits'] / 8), false);
883                                }
884                            }
885                        }
886                    }
887                    break;
888                
889                case 'RVRB' :
890                    // 4.13  RVRB Reverb
891                    // Reverb left (ms)                 $xx xx
892                    // Reverb right (ms)                $xx xx
893                    // Reverb bounces, lef…

Large files files are truncated, but you can click here to view the full file