File indexing completed on 2025-01-26 05:25:52
0001 <?php 0002 ///////////////////////////////////////////////////////////////// 0003 /// getID3() by James Heinrich <info@getid3.org> // 0004 // available at http://getid3.sourceforge.net // 0005 // or http://www.getid3.org // 0006 // also https://github.com/JamesHeinrich/getID3 // 0007 ///////////////////////////////////////////////////////////////// 0008 // See readme.txt for more details // 0009 ///////////////////////////////////////////////////////////////// 0010 /// // 0011 // write.id3v2.php // 0012 // module for writing ID3v2 tags // 0013 // dependencies: module.tag.id3v2.php // 0014 // /// 0015 ///////////////////////////////////////////////////////////////// 0016 0017 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); 0018 0019 class getid3_write_id3v2 0020 { 0021 public $filename; 0022 public $tag_data; 0023 public $fread_buffer_size = 32768; // read buffer size in bytes 0024 public $paddedlength = 4096; // minimum length of ID3v2 tag in bytes 0025 public $majorversion = 3; // ID3v2 major version (2, 3 (recommended), 4) 0026 public $minorversion = 0; // ID3v2 minor version - always 0 0027 public $merge_existing_data = false; // if true, merge new data with existing tags; if false, delete old tag data and only write new tags 0028 public $id3v2_default_encodingid = 0; // default text encoding (ISO-8859-1) if not explicitly passed 0029 public $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. 0030 public $warnings = array(); // any non-critical errors will be stored here 0031 public $errors = array(); // any critical errors will be stored here 0032 0033 public function getid3_write_id3v2() { 0034 return true; 0035 } 0036 0037 public function WriteID3v2() { 0038 // File MUST be writeable - CHMOD(646) at least. It's best if the 0039 // directory is also writeable, because that method is both faster and less susceptible to errors. 0040 0041 if (!empty($this->filename) && (is_writeable($this->filename) || (!file_exists($this->filename) && is_writeable(dirname($this->filename))))) { 0042 // Initialize getID3 engine 0043 $getID3 = new getID3; 0044 $OldThisFileInfo = $getID3->analyze($this->filename); 0045 if (!getid3_lib::intValueSupported($OldThisFileInfo['filesize'])) { 0046 $this->errors[] = 'Unable to write ID3v2 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; 0047 fclose($fp_source); 0048 return false; 0049 } 0050 if ($this->merge_existing_data) { 0051 // merge with existing data 0052 if (!empty($OldThisFileInfo['id3v2'])) { 0053 $this->tag_data = $this->array_join_merge($OldThisFileInfo['id3v2'], $this->tag_data); 0054 } 0055 } 0056 $this->paddedlength = (isset($OldThisFileInfo['id3v2']['headerlength']) ? max($OldThisFileInfo['id3v2']['headerlength'], $this->paddedlength) : $this->paddedlength); 0057 0058 if ($NewID3v2Tag = $this->GenerateID3v2Tag()) { 0059 0060 if (file_exists($this->filename) && is_writeable($this->filename) && isset($OldThisFileInfo['id3v2']['headerlength']) && ($OldThisFileInfo['id3v2']['headerlength'] == strlen($NewID3v2Tag))) { 0061 0062 // best and fastest method - insert-overwrite existing tag (padded to length of old tag if neccesary) 0063 if (file_exists($this->filename)) { 0064 0065 if (is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'r+b'))) { 0066 rewind($fp); 0067 fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag)); 0068 fclose($fp); 0069 } else { 0070 $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")'; 0071 } 0072 0073 } else { 0074 0075 if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'wb'))) { 0076 rewind($fp); 0077 fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag)); 0078 fclose($fp); 0079 } else { 0080 $this->errors[] = 'Could not fopen("'.$this->filename.'", "wb")'; 0081 } 0082 0083 } 0084 0085 } else { 0086 0087 if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) { 0088 if (is_readable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'rb'))) { 0089 if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) { 0090 0091 fwrite($fp_temp, $NewID3v2Tag, strlen($NewID3v2Tag)); 0092 0093 rewind($fp_source); 0094 if (!empty($OldThisFileInfo['avdataoffset'])) { 0095 fseek($fp_source, $OldThisFileInfo['avdataoffset']); 0096 } 0097 0098 while ($buffer = fread($fp_source, $this->fread_buffer_size)) { 0099 fwrite($fp_temp, $buffer, strlen($buffer)); 0100 } 0101 0102 fclose($fp_temp); 0103 fclose($fp_source); 0104 copy($tempfilename, $this->filename); 0105 unlink($tempfilename); 0106 return true; 0107 0108 } else { 0109 $this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")'; 0110 } 0111 fclose($fp_source); 0112 0113 } else { 0114 $this->errors[] = 'Could not fopen("'.$this->filename.'", "rb")'; 0115 } 0116 } 0117 return false; 0118 0119 } 0120 0121 } else { 0122 0123 $this->errors[] = '$this->GenerateID3v2Tag() failed'; 0124 0125 } 0126 0127 if (!empty($this->errors)) { 0128 return false; 0129 } 0130 return true; 0131 } else { 0132 $this->errors[] = 'WriteID3v2() failed: !is_writeable('.$this->filename.')'; 0133 } 0134 return false; 0135 } 0136 0137 public function RemoveID3v2() { 0138 // File MUST be writeable - CHMOD(646) at least. It's best if the 0139 // directory is also writeable, because that method is both faster and less susceptible to errors. 0140 if (is_writeable(dirname($this->filename))) { 0141 0142 // preferred method - only one copying operation, minimal chance of corrupting 0143 // original file if script is interrupted, but required directory to be writeable 0144 if (is_readable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'rb'))) { 0145 0146 // Initialize getID3 engine 0147 $getID3 = new getID3; 0148 $OldThisFileInfo = $getID3->analyze($this->filename); 0149 if (!getid3_lib::intValueSupported($OldThisFileInfo['filesize'])) { 0150 $this->errors[] = 'Unable to remove ID3v2 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; 0151 fclose($fp_source); 0152 return false; 0153 } 0154 rewind($fp_source); 0155 if ($OldThisFileInfo['avdataoffset'] !== false) { 0156 fseek($fp_source, $OldThisFileInfo['avdataoffset']); 0157 } 0158 if (is_writable($this->filename) && is_file($this->filename) && ($fp_temp = fopen($this->filename.'getid3tmp', 'w+b'))) { 0159 while ($buffer = fread($fp_source, $this->fread_buffer_size)) { 0160 fwrite($fp_temp, $buffer, strlen($buffer)); 0161 } 0162 fclose($fp_temp); 0163 } else { 0164 $this->errors[] = 'Could not fopen("'.$this->filename.'getid3tmp", "w+b")'; 0165 } 0166 fclose($fp_source); 0167 } else { 0168 $this->errors[] = 'Could not fopen("'.$this->filename.'", "rb")'; 0169 } 0170 if (file_exists($this->filename)) { 0171 unlink($this->filename); 0172 } 0173 rename($this->filename.'getid3tmp', $this->filename); 0174 0175 } elseif (is_writable($this->filename)) { 0176 0177 // less desirable alternate method - double-copies the file, overwrites original file 0178 // and could corrupt source file if the script is interrupted or an error occurs. 0179 if (is_readable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'rb'))) { 0180 0181 // Initialize getID3 engine 0182 $getID3 = new getID3; 0183 $OldThisFileInfo = $getID3->analyze($this->filename); 0184 if (!getid3_lib::intValueSupported($OldThisFileInfo['filesize'])) { 0185 $this->errors[] = 'Unable to remove ID3v2 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; 0186 fclose($fp_source); 0187 return false; 0188 } 0189 rewind($fp_source); 0190 if ($OldThisFileInfo['avdataoffset'] !== false) { 0191 fseek($fp_source, $OldThisFileInfo['avdataoffset']); 0192 } 0193 if ($fp_temp = tmpfile()) { 0194 while ($buffer = fread($fp_source, $this->fread_buffer_size)) { 0195 fwrite($fp_temp, $buffer, strlen($buffer)); 0196 } 0197 fclose($fp_source); 0198 if (is_writable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'wb'))) { 0199 rewind($fp_temp); 0200 while ($buffer = fread($fp_temp, $this->fread_buffer_size)) { 0201 fwrite($fp_source, $buffer, strlen($buffer)); 0202 } 0203 fseek($fp_temp, -128, SEEK_END); 0204 fclose($fp_source); 0205 } else { 0206 $this->errors[] = 'Could not fopen("'.$this->filename.'", "wb")'; 0207 } 0208 fclose($fp_temp); 0209 } else { 0210 $this->errors[] = 'Could not create tmpfile()'; 0211 } 0212 } else { 0213 $this->errors[] = 'Could not fopen("'.$this->filename.'", "rb")'; 0214 } 0215 0216 } else { 0217 0218 $this->errors[] = 'Directory and file both not writeable'; 0219 0220 } 0221 0222 if (!empty($this->errors)) { 0223 return false; 0224 } 0225 return true; 0226 } 0227 0228 0229 public function GenerateID3v2TagFlags($flags) { 0230 switch ($this->majorversion) { 0231 case 4: 0232 // %abcd0000 0233 $flag = (!empty($flags['unsynchronisation']) ? '1' : '0'); // a - Unsynchronisation 0234 $flag .= (!empty($flags['extendedheader'] ) ? '1' : '0'); // b - Extended header 0235 $flag .= (!empty($flags['experimental'] ) ? '1' : '0'); // c - Experimental indicator 0236 $flag .= (!empty($flags['footer'] ) ? '1' : '0'); // d - Footer present 0237 $flag .= '0000'; 0238 break; 0239 0240 case 3: 0241 // %abc00000 0242 $flag = (!empty($flags['unsynchronisation']) ? '1' : '0'); // a - Unsynchronisation 0243 $flag .= (!empty($flags['extendedheader'] ) ? '1' : '0'); // b - Extended header 0244 $flag .= (!empty($flags['experimental'] ) ? '1' : '0'); // c - Experimental indicator 0245 $flag .= '00000'; 0246 break; 0247 0248 case 2: 0249 // %ab000000 0250 $flag = (!empty($flags['unsynchronisation']) ? '1' : '0'); // a - Unsynchronisation 0251 $flag .= (!empty($flags['compression'] ) ? '1' : '0'); // b - Compression 0252 $flag .= '000000'; 0253 break; 0254 0255 default: 0256 return false; 0257 break; 0258 } 0259 return chr(bindec($flag)); 0260 } 0261 0262 0263 public function GenerateID3v2FrameFlags($TagAlter=false, $FileAlter=false, $ReadOnly=false, $Compression=false, $Encryption=false, $GroupingIdentity=false, $Unsynchronisation=false, $DataLengthIndicator=false) { 0264 switch ($this->majorversion) { 0265 case 4: 0266 // %0abc0000 %0h00kmnp 0267 $flag1 = '0'; 0268 $flag1 .= $TagAlter ? '1' : '0'; // a - Tag alter preservation (true == discard) 0269 $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard) 0270 $flag1 .= $ReadOnly ? '1' : '0'; // c - Read only (true == read only) 0271 $flag1 .= '0000'; 0272 0273 $flag2 = '0'; 0274 $flag2 .= $GroupingIdentity ? '1' : '0'; // h - Grouping identity (true == contains group information) 0275 $flag2 .= '00'; 0276 $flag2 .= $Compression ? '1' : '0'; // k - Compression (true == compressed) 0277 $flag2 .= $Encryption ? '1' : '0'; // m - Encryption (true == encrypted) 0278 $flag2 .= $Unsynchronisation ? '1' : '0'; // n - Unsynchronisation (true == unsynchronised) 0279 $flag2 .= $DataLengthIndicator ? '1' : '0'; // p - Data length indicator (true == data length indicator added) 0280 break; 0281 0282 case 3: 0283 // %abc00000 %ijk00000 0284 $flag1 = $TagAlter ? '1' : '0'; // a - Tag alter preservation (true == discard) 0285 $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard) 0286 $flag1 .= $ReadOnly ? '1' : '0'; // c - Read only (true == read only) 0287 $flag1 .= '00000'; 0288 0289 $flag2 = $Compression ? '1' : '0'; // i - Compression (true == compressed) 0290 $flag2 .= $Encryption ? '1' : '0'; // j - Encryption (true == encrypted) 0291 $flag2 .= $GroupingIdentity ? '1' : '0'; // k - Grouping identity (true == contains group information) 0292 $flag2 .= '00000'; 0293 break; 0294 0295 default: 0296 return false; 0297 break; 0298 0299 } 0300 return chr(bindec($flag1)).chr(bindec($flag2)); 0301 } 0302 0303 public function GenerateID3v2FrameData($frame_name, $source_data_array) { 0304 if (!getid3_id3v2::IsValidID3v2FrameName($frame_name, $this->majorversion)) { 0305 return false; 0306 } 0307 $framedata = ''; 0308 0309 if (($this->majorversion < 3) || ($this->majorversion > 4)) { 0310 0311 $this->errors[] = 'Only ID3v2.3 and ID3v2.4 are supported in GenerateID3v2FrameData()'; 0312 0313 } else { // $this->majorversion 3 or 4 0314 0315 switch ($frame_name) { 0316 case 'UFID': 0317 // 4.1 UFID Unique file identifier 0318 // Owner identifier <text string> $00 0319 // Identifier <up to 64 bytes binary data> 0320 if (strlen($source_data_array['data']) > 64) { 0321 $this->errors[] = 'Identifier not allowed to be longer than 64 bytes in '.$frame_name.' (supplied data was '.strlen($source_data_array['data']).' bytes long)'; 0322 } else { 0323 $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00"; 0324 $framedata .= substr($source_data_array['data'], 0, 64); // max 64 bytes - truncate anything longer 0325 } 0326 break; 0327 0328 case 'TXXX': 0329 // 4.2.2 TXXX User defined text information frame 0330 // Text encoding $xx 0331 // Description <text string according to encoding> $00 (00) 0332 // Value <text string according to encoding> 0333 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0334 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion)) { 0335 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; 0336 } else { 0337 $framedata .= chr($source_data_array['encodingid']); 0338 $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 0339 $framedata .= $source_data_array['data']; 0340 } 0341 break; 0342 0343 case 'WXXX': 0344 // 4.3.2 WXXX User defined URL link frame 0345 // Text encoding $xx 0346 // Description <text string according to encoding> $00 (00) 0347 // URL <text string> 0348 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0349 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion)) { 0350 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; 0351 } elseif (!isset($source_data_array['data']) || !$this->IsValidURL($source_data_array['data'], false, false)) { 0352 //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; 0353 // probably should be an error, need to rewrite IsValidURL() to handle other encodings 0354 $this->warnings[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; 0355 } else { 0356 $framedata .= chr($source_data_array['encodingid']); 0357 $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 0358 $framedata .= $source_data_array['data']; 0359 } 0360 break; 0361 0362 case 'IPLS': 0363 // 4.4 IPLS Involved people list (ID3v2.3 only) 0364 // Text encoding $xx 0365 // People list strings <textstrings> 0366 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0367 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion)) { 0368 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; 0369 } else { 0370 $framedata .= chr($source_data_array['encodingid']); 0371 $framedata .= $source_data_array['data']; 0372 } 0373 break; 0374 0375 case 'MCDI': 0376 // 4.4 MCDI Music CD identifier 0377 // CD TOC <binary data> 0378 $framedata .= $source_data_array['data']; 0379 break; 0380 0381 case 'ETCO': 0382 // 4.5 ETCO Event timing codes 0383 // Time stamp format $xx 0384 // Where time stamp format is: 0385 // $01 (32-bit value) MPEG frames from beginning of file 0386 // $02 (32-bit value) milliseconds from beginning of file 0387 // Followed by a list of key events in the following format: 0388 // Type of event $xx 0389 // Time stamp $xx (xx ...) 0390 // The 'Time stamp' is set to zero if directly at the beginning of the sound 0391 // or after the previous event. All events MUST be sorted in chronological order. 0392 if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1)) { 0393 $this->errors[] = 'Invalid Time Stamp Format byte in '.$frame_name.' ('.$source_data_array['timestampformat'].')'; 0394 } else { 0395 $framedata .= chr($source_data_array['timestampformat']); 0396 foreach ($source_data_array as $key => $val) { 0397 if (!$this->ID3v2IsValidETCOevent($val['typeid'])) { 0398 $this->errors[] = 'Invalid Event Type byte in '.$frame_name.' ('.$val['typeid'].')'; 0399 } elseif (($key != 'timestampformat') && ($key != 'flags')) { 0400 if (($val['timestamp'] > 0) && ($previousETCOtimestamp >= $val['timestamp'])) { 0401 // The 'Time stamp' is set to zero if directly at the beginning of the sound 0402 // or after the previous event. All events MUST be sorted in chronological order. 0403 $this->errors[] = 'Out-of-order timestamp in '.$frame_name.' ('.$val['timestamp'].') for Event Type ('.$val['typeid'].')'; 0404 } else { 0405 $framedata .= chr($val['typeid']); 0406 $framedata .= getid3_lib::BigEndian2String($val['timestamp'], 4, false); 0407 } 0408 } 0409 } 0410 } 0411 break; 0412 0413 case 'MLLT': 0414 // 4.6 MLLT MPEG location lookup table 0415 // MPEG frames between reference $xx xx 0416 // Bytes between reference $xx xx xx 0417 // Milliseconds between reference $xx xx xx 0418 // Bits for bytes deviation $xx 0419 // Bits for milliseconds dev. $xx 0420 // Then for every reference the following data is included; 0421 // Deviation in bytes %xxx.... 0422 // Deviation in milliseconds %xxx.... 0423 if (($source_data_array['framesbetweenreferences'] > 0) && ($source_data_array['framesbetweenreferences'] <= 65535)) { 0424 $framedata .= getid3_lib::BigEndian2String($source_data_array['framesbetweenreferences'], 2, false); 0425 } else { 0426 $this->errors[] = 'Invalid MPEG Frames Between References in '.$frame_name.' ('.$source_data_array['framesbetweenreferences'].')'; 0427 } 0428 if (($source_data_array['bytesbetweenreferences'] > 0) && ($source_data_array['bytesbetweenreferences'] <= 16777215)) { 0429 $framedata .= getid3_lib::BigEndian2String($source_data_array['bytesbetweenreferences'], 3, false); 0430 } else { 0431 $this->errors[] = 'Invalid bytes Between References in '.$frame_name.' ('.$source_data_array['bytesbetweenreferences'].')'; 0432 } 0433 if (($source_data_array['msbetweenreferences'] > 0) && ($source_data_array['msbetweenreferences'] <= 16777215)) { 0434 $framedata .= getid3_lib::BigEndian2String($source_data_array['msbetweenreferences'], 3, false); 0435 } else { 0436 $this->errors[] = 'Invalid Milliseconds Between References in '.$frame_name.' ('.$source_data_array['msbetweenreferences'].')'; 0437 } 0438 if (!$this->IsWithinBitRange($source_data_array['bitsforbytesdeviation'], 8, false)) { 0439 if (($source_data_array['bitsforbytesdeviation'] % 4) == 0) { 0440 $framedata .= chr($source_data_array['bitsforbytesdeviation']); 0441 } else { 0442 $this->errors[] = 'Bits For Bytes Deviation in '.$frame_name.' ('.$source_data_array['bitsforbytesdeviation'].') must be a multiple of 4.'; 0443 } 0444 } else { 0445 $this->errors[] = 'Invalid Bits For Bytes Deviation in '.$frame_name.' ('.$source_data_array['bitsforbytesdeviation'].')'; 0446 } 0447 if (!$this->IsWithinBitRange($source_data_array['bitsformsdeviation'], 8, false)) { 0448 if (($source_data_array['bitsformsdeviation'] % 4) == 0) { 0449 $framedata .= chr($source_data_array['bitsformsdeviation']); 0450 } else { 0451 $this->errors[] = 'Bits For Milliseconds Deviation in '.$frame_name.' ('.$source_data_array['bitsforbytesdeviation'].') must be a multiple of 4.'; 0452 } 0453 } else { 0454 $this->errors[] = 'Invalid Bits For Milliseconds Deviation in '.$frame_name.' ('.$source_data_array['bitsformsdeviation'].')'; 0455 } 0456 foreach ($source_data_array as $key => $val) { 0457 if (($key != 'framesbetweenreferences') && ($key != 'bytesbetweenreferences') && ($key != 'msbetweenreferences') && ($key != 'bitsforbytesdeviation') && ($key != 'bitsformsdeviation') && ($key != 'flags')) { 0458 $unwrittenbitstream .= str_pad(getid3_lib::Dec2Bin($val['bytedeviation']), $source_data_array['bitsforbytesdeviation'], '0', STR_PAD_LEFT); 0459 $unwrittenbitstream .= str_pad(getid3_lib::Dec2Bin($val['msdeviation']), $source_data_array['bitsformsdeviation'], '0', STR_PAD_LEFT); 0460 } 0461 } 0462 for ($i = 0; $i < strlen($unwrittenbitstream); $i += 8) { 0463 $highnibble = bindec(substr($unwrittenbitstream, $i, 4)) << 4; 0464 $lownibble = bindec(substr($unwrittenbitstream, $i + 4, 4)); 0465 $framedata .= chr($highnibble & $lownibble); 0466 } 0467 break; 0468 0469 case 'SYTC': 0470 // 4.7 SYTC Synchronised tempo codes 0471 // Time stamp format $xx 0472 // Tempo data <binary data> 0473 // Where time stamp format is: 0474 // $01 (32-bit value) MPEG frames from beginning of file 0475 // $02 (32-bit value) milliseconds from beginning of file 0476 if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1)) { 0477 $this->errors[] = 'Invalid Time Stamp Format byte in '.$frame_name.' ('.$source_data_array['timestampformat'].')'; 0478 } else { 0479 $framedata .= chr($source_data_array['timestampformat']); 0480 foreach ($source_data_array as $key => $val) { 0481 if (!$this->ID3v2IsValidETCOevent($val['typeid'])) { 0482 $this->errors[] = 'Invalid Event Type byte in '.$frame_name.' ('.$val['typeid'].')'; 0483 } elseif (($key != 'timestampformat') && ($key != 'flags')) { 0484 if (($val['tempo'] < 0) || ($val['tempo'] > 510)) { 0485 $this->errors[] = 'Invalid Tempo (max = 510) in '.$frame_name.' ('.$val['tempo'].') at timestamp ('.$val['timestamp'].')'; 0486 } else { 0487 if ($val['tempo'] > 255) { 0488 $framedata .= chr(255); 0489 $val['tempo'] -= 255; 0490 } 0491 $framedata .= chr($val['tempo']); 0492 $framedata .= getid3_lib::BigEndian2String($val['timestamp'], 4, false); 0493 } 0494 } 0495 } 0496 } 0497 break; 0498 0499 case 'USLT': 0500 // 4.8 USLT Unsynchronised lyric/text transcription 0501 // Text encoding $xx 0502 // Language $xx xx xx 0503 // Content descriptor <text string according to encoding> $00 (00) 0504 // Lyrics/text <full text string according to encoding> 0505 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0506 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { 0507 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; 0508 } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') { 0509 $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')'; 0510 } else { 0511 $framedata .= chr($source_data_array['encodingid']); 0512 $framedata .= strtolower($source_data_array['language']); 0513 $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 0514 $framedata .= $source_data_array['data']; 0515 } 0516 break; 0517 0518 case 'SYLT': 0519 // 4.9 SYLT Synchronised lyric/text 0520 // Text encoding $xx 0521 // Language $xx xx xx 0522 // Time stamp format $xx 0523 // $01 (32-bit value) MPEG frames from beginning of file 0524 // $02 (32-bit value) milliseconds from beginning of file 0525 // Content type $xx 0526 // Content descriptor <text string according to encoding> $00 (00) 0527 // Terminated text to be synced (typically a syllable) 0528 // Sync identifier (terminator to above string) $00 (00) 0529 // Time stamp $xx (xx ...) 0530 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0531 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { 0532 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; 0533 } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') { 0534 $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')'; 0535 } elseif (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1)) { 0536 $this->errors[] = 'Invalid Time Stamp Format byte in '.$frame_name.' ('.$source_data_array['timestampformat'].')'; 0537 } elseif (!$this->ID3v2IsValidSYLTtype($source_data_array['contenttypeid'])) { 0538 $this->errors[] = 'Invalid Content Type byte in '.$frame_name.' ('.$source_data_array['contenttypeid'].')'; 0539 } elseif (!is_array($source_data_array['data'])) { 0540 $this->errors[] = 'Invalid Lyric/Timestamp data in '.$frame_name.' (must be an array)'; 0541 } else { 0542 $framedata .= chr($source_data_array['encodingid']); 0543 $framedata .= strtolower($source_data_array['language']); 0544 $framedata .= chr($source_data_array['timestampformat']); 0545 $framedata .= chr($source_data_array['contenttypeid']); 0546 $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 0547 ksort($source_data_array['data']); 0548 foreach ($source_data_array['data'] as $key => $val) { 0549 $framedata .= $val['data'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 0550 $framedata .= getid3_lib::BigEndian2String($val['timestamp'], 4, false); 0551 } 0552 } 0553 break; 0554 0555 case 'COMM': 0556 // 4.10 COMM Comments 0557 // Text encoding $xx 0558 // Language $xx xx xx 0559 // Short content descrip. <text string according to encoding> $00 (00) 0560 // The actual text <full text string according to encoding> 0561 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0562 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { 0563 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; 0564 } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') { 0565 $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')'; 0566 } else { 0567 $framedata .= chr($source_data_array['encodingid']); 0568 $framedata .= strtolower($source_data_array['language']); 0569 $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 0570 $framedata .= $source_data_array['data']; 0571 } 0572 break; 0573 0574 case 'RVA2': 0575 // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only) 0576 // Identification <text string> $00 0577 // The 'identification' string is used to identify the situation and/or 0578 // device where this adjustment should apply. The following is then 0579 // repeated for every channel: 0580 // Type of channel $xx 0581 // Volume adjustment $xx xx 0582 // Bits representing peak $xx 0583 // Peak volume $xx (xx ...) 0584 $framedata .= str_replace("\x00", '', $source_data_array['description'])."\x00"; 0585 foreach ($source_data_array as $key => $val) { 0586 if ($key != 'description') { 0587 $framedata .= chr($val['channeltypeid']); 0588 $framedata .= getid3_lib::BigEndian2String($val['volumeadjust'], 2, false, true); // signed 16-bit 0589 if (!$this->IsWithinBitRange($source_data_array['bitspeakvolume'], 8, false)) { 0590 $framedata .= chr($val['bitspeakvolume']); 0591 if ($val['bitspeakvolume'] > 0) { 0592 $framedata .= getid3_lib::BigEndian2String($val['peakvolume'], ceil($val['bitspeakvolume'] / 8), false, false); 0593 } 0594 } else { 0595 $this->errors[] = 'Invalid Bits Representing Peak Volume in '.$frame_name.' ('.$val['bitspeakvolume'].') (range = 0 to 255)'; 0596 } 0597 } 0598 } 0599 break; 0600 0601 case 'RVAD': 0602 // 4.12 RVAD Relative volume adjustment (ID3v2.3 only) 0603 // Increment/decrement %00fedcba 0604 // Bits used for volume descr. $xx 0605 // Relative volume change, right $xx xx (xx ...) // a 0606 // Relative volume change, left $xx xx (xx ...) // b 0607 // Peak volume right $xx xx (xx ...) 0608 // Peak volume left $xx xx (xx ...) 0609 // Relative volume change, right back $xx xx (xx ...) // c 0610 // Relative volume change, left back $xx xx (xx ...) // d 0611 // Peak volume right back $xx xx (xx ...) 0612 // Peak volume left back $xx xx (xx ...) 0613 // Relative volume change, center $xx xx (xx ...) // e 0614 // Peak volume center $xx xx (xx ...) 0615 // Relative volume change, bass $xx xx (xx ...) // f 0616 // Peak volume bass $xx xx (xx ...) 0617 if (!$this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false)) { 0618 $this->errors[] = 'Invalid Bits For Volume Description byte in '.$frame_name.' ('.$source_data_array['bitsvolume'].') (range = 1 to 255)'; 0619 } else { 0620 $incdecflag .= '00'; 0621 $incdecflag .= $source_data_array['incdec']['right'] ? '1' : '0'; // a - Relative volume change, right 0622 $incdecflag .= $source_data_array['incdec']['left'] ? '1' : '0'; // b - Relative volume change, left 0623 $incdecflag .= $source_data_array['incdec']['rightrear'] ? '1' : '0'; // c - Relative volume change, right back 0624 $incdecflag .= $source_data_array['incdec']['leftrear'] ? '1' : '0'; // d - Relative volume change, left back 0625 $incdecflag .= $source_data_array['incdec']['center'] ? '1' : '0'; // e - Relative volume change, center 0626 $incdecflag .= $source_data_array['incdec']['bass'] ? '1' : '0'; // f - Relative volume change, bass 0627 $framedata .= chr(bindec($incdecflag)); 0628 $framedata .= chr($source_data_array['bitsvolume']); 0629 $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['right'], ceil($source_data_array['bitsvolume'] / 8), false); 0630 $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['left'], ceil($source_data_array['bitsvolume'] / 8), false); 0631 $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['right'], ceil($source_data_array['bitsvolume'] / 8), false); 0632 $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['left'], ceil($source_data_array['bitsvolume'] / 8), false); 0633 if ($source_data_array['volumechange']['rightrear'] || $source_data_array['volumechange']['leftrear'] || 0634 $source_data_array['peakvolume']['rightrear'] || $source_data_array['peakvolume']['leftrear'] || 0635 $source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] || 0636 $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass']) { 0637 $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['rightrear'], ceil($source_data_array['bitsvolume']/8), false); 0638 $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['leftrear'], ceil($source_data_array['bitsvolume']/8), false); 0639 $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['rightrear'], ceil($source_data_array['bitsvolume']/8), false); 0640 $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['leftrear'], ceil($source_data_array['bitsvolume']/8), false); 0641 } 0642 if ($source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] || 0643 $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass']) { 0644 $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['center'], ceil($source_data_array['bitsvolume']/8), false); 0645 $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['center'], ceil($source_data_array['bitsvolume']/8), false); 0646 } 0647 if ($source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass']) { 0648 $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['bass'], ceil($source_data_array['bitsvolume']/8), false); 0649 $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['bass'], ceil($source_data_array['bitsvolume']/8), false); 0650 } 0651 } 0652 break; 0653 0654 case 'EQU2': 0655 // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only) 0656 // Interpolation method $xx 0657 // $00 Band 0658 // $01 Linear 0659 // Identification <text string> $00 0660 // The following is then repeated for every adjustment point 0661 // Frequency $xx xx 0662 // Volume adjustment $xx xx 0663 if (($source_data_array['interpolationmethod'] < 0) || ($source_data_array['interpolationmethod'] > 1)) { 0664 $this->errors[] = 'Invalid Interpolation Method byte in '.$frame_name.' ('.$source_data_array['interpolationmethod'].') (valid = 0 or 1)'; 0665 } else { 0666 $framedata .= chr($source_data_array['interpolationmethod']); 0667 $framedata .= str_replace("\x00", '', $source_data_array['description'])."\x00"; 0668 foreach ($source_data_array['data'] as $key => $val) { 0669 $framedata .= getid3_lib::BigEndian2String(intval(round($key * 2)), 2, false); 0670 $framedata .= getid3_lib::BigEndian2String($val, 2, false, true); // signed 16-bit 0671 } 0672 } 0673 break; 0674 0675 case 'EQUA': 0676 // 4.12 EQUA Equalisation (ID3v2.3 only) 0677 // Adjustment bits $xx 0678 // This is followed by 2 bytes + ('adjustment bits' rounded up to the 0679 // nearest byte) for every equalisation band in the following format, 0680 // giving a frequency range of 0 - 32767Hz: 0681 // Increment/decrement %x (MSB of the Frequency) 0682 // Frequency (lower 15 bits) 0683 // Adjustment $xx (xx ...) 0684 if (!$this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false)) { 0685 $this->errors[] = 'Invalid Adjustment Bits byte in '.$frame_name.' ('.$source_data_array['bitsvolume'].') (range = 1 to 255)'; 0686 } else { 0687 $framedata .= chr($source_data_array['adjustmentbits']); 0688 foreach ($source_data_array as $key => $val) { 0689 if ($key != 'bitsvolume') { 0690 if (($key > 32767) || ($key < 0)) { 0691 $this->errors[] = 'Invalid Frequency in '.$frame_name.' ('.$key.') (range = 0 to 32767)'; 0692 } else { 0693 if ($val >= 0) { 0694 // put MSB of frequency to 1 if increment, 0 if decrement 0695 $key |= 0x8000; 0696 } 0697 $framedata .= getid3_lib::BigEndian2String($key, 2, false); 0698 $framedata .= getid3_lib::BigEndian2String($val, ceil($source_data_array['adjustmentbits'] / 8), false); 0699 } 0700 } 0701 } 0702 } 0703 break; 0704 0705 case 'RVRB': 0706 // 4.13 RVRB Reverb 0707 // Reverb left (ms) $xx xx 0708 // Reverb right (ms) $xx xx 0709 // Reverb bounces, left $xx 0710 // Reverb bounces, right $xx 0711 // Reverb feedback, left to left $xx 0712 // Reverb feedback, left to right $xx 0713 // Reverb feedback, right to right $xx 0714 // Reverb feedback, right to left $xx 0715 // Premix left to right $xx 0716 // Premix right to left $xx 0717 if (!$this->IsWithinBitRange($source_data_array['left'], 16, false)) { 0718 $this->errors[] = 'Invalid Reverb Left in '.$frame_name.' ('.$source_data_array['left'].') (range = 0 to 65535)'; 0719 } elseif (!$this->IsWithinBitRange($source_data_array['right'], 16, false)) { 0720 $this->errors[] = 'Invalid Reverb Left in '.$frame_name.' ('.$source_data_array['right'].') (range = 0 to 65535)'; 0721 } elseif (!$this->IsWithinBitRange($source_data_array['bouncesL'], 8, false)) { 0722 $this->errors[] = 'Invalid Reverb Bounces, Left in '.$frame_name.' ('.$source_data_array['bouncesL'].') (range = 0 to 255)'; 0723 } elseif (!$this->IsWithinBitRange($source_data_array['bouncesR'], 8, false)) { 0724 $this->errors[] = 'Invalid Reverb Bounces, Right in '.$frame_name.' ('.$source_data_array['bouncesR'].') (range = 0 to 255)'; 0725 } elseif (!$this->IsWithinBitRange($source_data_array['feedbackLL'], 8, false)) { 0726 $this->errors[] = 'Invalid Reverb Feedback, Left-To-Left in '.$frame_name.' ('.$source_data_array['feedbackLL'].') (range = 0 to 255)'; 0727 } elseif (!$this->IsWithinBitRange($source_data_array['feedbackLR'], 8, false)) { 0728 $this->errors[] = 'Invalid Reverb Feedback, Left-To-Right in '.$frame_name.' ('.$source_data_array['feedbackLR'].') (range = 0 to 255)'; 0729 } elseif (!$this->IsWithinBitRange($source_data_array['feedbackRR'], 8, false)) { 0730 $this->errors[] = 'Invalid Reverb Feedback, Right-To-Right in '.$frame_name.' ('.$source_data_array['feedbackRR'].') (range = 0 to 255)'; 0731 } elseif (!$this->IsWithinBitRange($source_data_array['feedbackRL'], 8, false)) { 0732 $this->errors[] = 'Invalid Reverb Feedback, Right-To-Left in '.$frame_name.' ('.$source_data_array['feedbackRL'].') (range = 0 to 255)'; 0733 } elseif (!$this->IsWithinBitRange($source_data_array['premixLR'], 8, false)) { 0734 $this->errors[] = 'Invalid Premix, Left-To-Right in '.$frame_name.' ('.$source_data_array['premixLR'].') (range = 0 to 255)'; 0735 } elseif (!$this->IsWithinBitRange($source_data_array['premixRL'], 8, false)) { 0736 $this->errors[] = 'Invalid Premix, Right-To-Left in '.$frame_name.' ('.$source_data_array['premixRL'].') (range = 0 to 255)'; 0737 } else { 0738 $framedata .= getid3_lib::BigEndian2String($source_data_array['left'], 2, false); 0739 $framedata .= getid3_lib::BigEndian2String($source_data_array['right'], 2, false); 0740 $framedata .= chr($source_data_array['bouncesL']); 0741 $framedata .= chr($source_data_array['bouncesR']); 0742 $framedata .= chr($source_data_array['feedbackLL']); 0743 $framedata .= chr($source_data_array['feedbackLR']); 0744 $framedata .= chr($source_data_array['feedbackRR']); 0745 $framedata .= chr($source_data_array['feedbackRL']); 0746 $framedata .= chr($source_data_array['premixLR']); 0747 $framedata .= chr($source_data_array['premixRL']); 0748 } 0749 break; 0750 0751 case 'APIC': 0752 // 4.14 APIC Attached picture 0753 // Text encoding $xx 0754 // MIME type <text string> $00 0755 // Picture type $xx 0756 // Description <text string according to encoding> $00 (00) 0757 // Picture data <binary data> 0758 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0759 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { 0760 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; 0761 } elseif (!$this->ID3v2IsValidAPICpicturetype($source_data_array['picturetypeid'])) { 0762 $this->errors[] = 'Invalid Picture Type byte in '.$frame_name.' ('.$source_data_array['picturetypeid'].') for ID3v2.'.$this->majorversion; 0763 } elseif (($this->majorversion >= 3) && (!$this->ID3v2IsValidAPICimageformat($source_data_array['mime']))) { 0764 $this->errors[] = 'Invalid MIME Type in '.$frame_name.' ('.$source_data_array['mime'].') for ID3v2.'.$this->majorversion; 0765 } elseif (($source_data_array['mime'] == '-->') && (!$this->IsValidURL($source_data_array['data'], false, false))) { 0766 //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; 0767 // probably should be an error, need to rewrite IsValidURL() to handle other encodings 0768 $this->warnings[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; 0769 } else { 0770 $framedata .= chr($source_data_array['encodingid']); 0771 $framedata .= str_replace("\x00", '', $source_data_array['mime'])."\x00"; 0772 $framedata .= chr($source_data_array['picturetypeid']); 0773 $framedata .= (!empty($source_data_array['description']) ? $source_data_array['description'] : '').getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 0774 $framedata .= $source_data_array['data']; 0775 } 0776 break; 0777 0778 case 'GEOB': 0779 // 4.15 GEOB General encapsulated object 0780 // Text encoding $xx 0781 // MIME type <text string> $00 0782 // Filename <text string according to encoding> $00 (00) 0783 // Content description <text string according to encoding> $00 (00) 0784 // Encapsulated object <binary data> 0785 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0786 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { 0787 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; 0788 } elseif (!$this->IsValidMIMEstring($source_data_array['mime'])) { 0789 $this->errors[] = 'Invalid MIME Type in '.$frame_name.' ('.$source_data_array['mime'].')'; 0790 } elseif (!$source_data_array['description']) { 0791 $this->errors[] = 'Missing Description in '.$frame_name; 0792 } else { 0793 $framedata .= chr($source_data_array['encodingid']); 0794 $framedata .= str_replace("\x00", '', $source_data_array['mime'])."\x00"; 0795 $framedata .= $source_data_array['filename'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 0796 $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 0797 $framedata .= $source_data_array['data']; 0798 } 0799 break; 0800 0801 case 'PCNT': 0802 // 4.16 PCNT Play counter 0803 // When the counter reaches all one's, one byte is inserted in 0804 // front of the counter thus making the counter eight bits bigger 0805 // Counter $xx xx xx xx (xx ...) 0806 $framedata .= getid3_lib::BigEndian2String($source_data_array['data'], 4, false); 0807 break; 0808 0809 case 'POPM': 0810 // 4.17 POPM Popularimeter 0811 // When the counter reaches all one's, one byte is inserted in 0812 // front of the counter thus making the counter eight bits bigger 0813 // Email to user <text string> $00 0814 // Rating $xx 0815 // Counter $xx xx xx xx (xx ...) 0816 if (!$this->IsWithinBitRange($source_data_array['rating'], 8, false)) { 0817 $this->errors[] = 'Invalid Rating byte in '.$frame_name.' ('.$source_data_array['rating'].') (range = 0 to 255)'; 0818 } elseif (!IsValidEmail($source_data_array['email'])) { 0819 $this->errors[] = 'Invalid Email in '.$frame_name.' ('.$source_data_array['email'].')'; 0820 } else { 0821 $framedata .= str_replace("\x00", '', $source_data_array['email'])."\x00"; 0822 $framedata .= chr($source_data_array['rating']); 0823 $framedata .= getid3_lib::BigEndian2String($source_data_array['data'], 4, false); 0824 } 0825 break; 0826 0827 case 'RBUF': 0828 // 4.18 RBUF Recommended buffer size 0829 // Buffer size $xx xx xx 0830 // Embedded info flag %0000000x 0831 // Offset to next tag $xx xx xx xx 0832 if (!$this->IsWithinBitRange($source_data_array['buffersize'], 24, false)) { 0833 $this->errors[] = 'Invalid Buffer Size in '.$frame_name; 0834 } elseif (!$this->IsWithinBitRange($source_data_array['nexttagoffset'], 32, false)) { 0835 $this->errors[] = 'Invalid Offset To Next Tag in '.$frame_name; 0836 } else { 0837 $framedata .= getid3_lib::BigEndian2String($source_data_array['buffersize'], 3, false); 0838 $flag .= '0000000'; 0839 $flag .= $source_data_array['flags']['embededinfo'] ? '1' : '0'; 0840 $framedata .= chr(bindec($flag)); 0841 $framedata .= getid3_lib::BigEndian2String($source_data_array['nexttagoffset'], 4, false); 0842 } 0843 break; 0844 0845 case 'AENC': 0846 // 4.19 AENC Audio encryption 0847 // Owner identifier <text string> $00 0848 // Preview start $xx xx 0849 // Preview length $xx xx 0850 // Encryption info <binary data> 0851 if (!$this->IsWithinBitRange($source_data_array['previewstart'], 16, false)) { 0852 $this->errors[] = 'Invalid Preview Start in '.$frame_name.' ('.$source_data_array['previewstart'].')'; 0853 } elseif (!$this->IsWithinBitRange($source_data_array['previewlength'], 16, false)) { 0854 $this->errors[] = 'Invalid Preview Length in '.$frame_name.' ('.$source_data_array['previewlength'].')'; 0855 } else { 0856 $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00"; 0857 $framedata .= getid3_lib::BigEndian2String($source_data_array['previewstart'], 2, false); 0858 $framedata .= getid3_lib::BigEndian2String($source_data_array['previewlength'], 2, false); 0859 $framedata .= $source_data_array['encryptioninfo']; 0860 } 0861 break; 0862 0863 case 'LINK': 0864 // 4.20 LINK Linked information 0865 // Frame identifier $xx xx xx xx 0866 // URL <text string> $00 0867 // ID and additional data <text string(s)> 0868 if (!getid3_id3v2::IsValidID3v2FrameName($source_data_array['frameid'], $this->majorversion)) { 0869 $this->errors[] = 'Invalid Frame Identifier in '.$frame_name.' ('.$source_data_array['frameid'].')'; 0870 } elseif (!$this->IsValidURL($source_data_array['data'], true, false)) { 0871 //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; 0872 // probably should be an error, need to rewrite IsValidURL() to handle other encodings 0873 $this->warnings[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; 0874 } elseif ((($source_data_array['frameid'] == 'AENC') || ($source_data_array['frameid'] == 'APIC') || ($source_data_array['frameid'] == 'GEOB') || ($source_data_array['frameid'] == 'TXXX')) && ($source_data_array['additionaldata'] == '')) { 0875 $this->errors[] = 'Content Descriptor must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name; 0876 } elseif (($source_data_array['frameid'] == 'USER') && (getid3_id3v2::LanguageLookup($source_data_array['additionaldata'], true) == '')) { 0877 $this->errors[] = 'Language must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name; 0878 } elseif (($source_data_array['frameid'] == 'PRIV') && ($source_data_array['additionaldata'] == '')) { 0879 $this->errors[] = 'Owner Identifier must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name; 0880 } elseif ((($source_data_array['frameid'] == 'COMM') || ($source_data_array['frameid'] == 'SYLT') || ($source_data_array['frameid'] == 'USLT')) && ((getid3_id3v2::LanguageLookup(substr($source_data_array['additionaldata'], 0, 3), true) == '') || (substr($source_data_array['additionaldata'], 3) == ''))) { 0881 $this->errors[] = 'Language followed by Content Descriptor must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name; 0882 } else { 0883 $framedata .= $source_data_array['frameid']; 0884 $framedata .= str_replace("\x00", '', $source_data_array['data'])."\x00"; 0885 switch ($source_data_array['frameid']) { 0886 case 'COMM': 0887 case 'SYLT': 0888 case 'USLT': 0889 case 'PRIV': 0890 case 'USER': 0891 case 'AENC': 0892 case 'APIC': 0893 case 'GEOB': 0894 case 'TXXX': 0895 $framedata .= $source_data_array['additionaldata']; 0896 break; 0897 case 'ASPI': 0898 case 'ETCO': 0899 case 'EQU2': 0900 case 'MCID': 0901 case 'MLLT': 0902 case 'OWNE': 0903 case 'RVA2': 0904 case 'RVRB': 0905 case 'SYTC': 0906 case 'IPLS': 0907 case 'RVAD': 0908 case 'EQUA': 0909 // no additional data required 0910 break; 0911 case 'RBUF': 0912 if ($this->majorversion == 3) { 0913 // no additional data required 0914 } else { 0915 $this->errors[] = $source_data_array['frameid'].' is not a valid Frame Identifier in '.$frame_name.' (in ID3v2.'.$this->majorversion.')'; 0916 } 0917 0918 default: 0919 if ((substr($source_data_array['frameid'], 0, 1) == 'T') || (substr($source_data_array['frameid'], 0, 1) == 'W')) { 0920 // no additional data required 0921 } else { 0922 $this->errors[] = $source_data_array['frameid'].' is not a valid Frame Identifier in '.$frame_name.' (in ID3v2.'.$this->majorversion.')'; 0923 } 0924 break; 0925 } 0926 } 0927 break; 0928 0929 case 'POSS': 0930 // 4.21 POSS Position synchronisation frame (ID3v2.3+ only) 0931 // Time stamp format $xx 0932 // Position $xx (xx ...) 0933 if (($source_data_array['timestampformat'] < 1) || ($source_data_array['timestampformat'] > 2)) { 0934 $this->errors[] = 'Invalid Time Stamp Format in '.$frame_name.' ('.$source_data_array['timestampformat'].') (valid = 1 or 2)'; 0935 } elseif (!$this->IsWithinBitRange($source_data_array['position'], 32, false)) { 0936 $this->errors[] = 'Invalid Position in '.$frame_name.' ('.$source_data_array['position'].') (range = 0 to 4294967295)'; 0937 } else { 0938 $framedata .= chr($source_data_array['timestampformat']); 0939 $framedata .= getid3_lib::BigEndian2String($source_data_array['position'], 4, false); 0940 } 0941 break; 0942 0943 case 'USER': 0944 // 4.22 USER Terms of use (ID3v2.3+ only) 0945 // Text encoding $xx 0946 // Language $xx xx xx 0947 // The actual text <text string according to encoding> 0948 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0949 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { 0950 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].')'; 0951 } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') { 0952 $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')'; 0953 } else { 0954 $framedata .= chr($source_data_array['encodingid']); 0955 $framedata .= strtolower($source_data_array['language']); 0956 $framedata .= $source_data_array['data']; 0957 } 0958 break; 0959 0960 case 'OWNE': 0961 // 4.23 OWNE Ownership frame (ID3v2.3+ only) 0962 // Text encoding $xx 0963 // Price paid <text string> $00 0964 // Date of purch. <text string> 0965 // Seller <text string according to encoding> 0966 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0967 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { 0968 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].')'; 0969 } elseif (!$this->IsANumber($source_data_array['pricepaid']['value'], false)) { 0970 $this->errors[] = 'Invalid Price Paid in '.$frame_name.' ('.$source_data_array['pricepaid']['value'].')'; 0971 } elseif (!$this->IsValidDateStampString($source_data_array['purchasedate'])) { 0972 $this->errors[] = 'Invalid Date Of Purchase in '.$frame_name.' ('.$source_data_array['purchasedate'].') (format = YYYYMMDD)'; 0973 } else { 0974 $framedata .= chr($source_data_array['encodingid']); 0975 $framedata .= str_replace("\x00", '', $source_data_array['pricepaid']['value'])."\x00"; 0976 $framedata .= $source_data_array['purchasedate']; 0977 $framedata .= $source_data_array['seller']; 0978 } 0979 break; 0980 0981 case 'COMR': 0982 // 4.24 COMR Commercial frame (ID3v2.3+ only) 0983 // Text encoding $xx 0984 // Price string <text string> $00 0985 // Valid until <text string> 0986 // Contact URL <text string> $00 0987 // Received as $xx 0988 // Name of seller <text string according to encoding> $00 (00) 0989 // Description <text string according to encoding> $00 (00) 0990 // Picture MIME type <string> $00 0991 // Seller logo <binary data> 0992 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 0993 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { 0994 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].')'; 0995 } elseif (!$this->IsValidDateStampString($source_data_array['pricevaliduntil'])) { 0996 $this->errors[] = 'Invalid Valid Until date in '.$frame_name.' ('.$source_data_array['pricevaliduntil'].') (format = YYYYMMDD)'; 0997 } elseif (!$this->IsValidURL($source_data_array['contacturl'], false, true)) { 0998 $this->errors[] = 'Invalid Contact URL in '.$frame_name.' ('.$source_data_array['contacturl'].') (allowed schemes: http, https, ftp, mailto)'; 0999 } elseif (!$this->ID3v2IsValidCOMRreceivedAs($source_data_array['receivedasid'])) { 1000 $this->errors[] = 'Invalid Received As byte in '.$frame_name.' ('.$source_data_array['contacturl'].') (range = 0 to 8)'; 1001 } elseif (!$this->IsValidMIMEstring($source_data_array['mime'])) { 1002 $this->errors[] = 'Invalid MIME Type in '.$frame_name.' ('.$source_data_array['mime'].')'; 1003 } else { 1004 $framedata .= chr($source_data_array['encodingid']); 1005 unset($pricestring); 1006 foreach ($source_data_array['price'] as $key => $val) { 1007 if ($this->ID3v2IsValidPriceString($key.$val['value'])) { 1008 $pricestrings[] = $key.$val['value']; 1009 } else { 1010 $this->errors[] = 'Invalid Price String in '.$frame_name.' ('.$key.$val['value'].')'; 1011 } 1012 } 1013 $framedata .= implode('/', $pricestrings); 1014 $framedata .= $source_data_array['pricevaliduntil']; 1015 $framedata .= str_replace("\x00", '', $source_data_array['contacturl'])."\x00"; 1016 $framedata .= chr($source_data_array['receivedasid']); 1017 $framedata .= $source_data_array['sellername'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 1018 $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); 1019 $framedata .= $source_data_array['mime']."\x00"; 1020 $framedata .= $source_data_array['logo']; 1021 } 1022 break; 1023 1024 case 'ENCR': 1025 // 4.25 ENCR Encryption method registration (ID3v2.3+ only) 1026 // Owner identifier <text string> $00 1027 // Method symbol $xx 1028 // Encryption data <binary data> 1029 if (!$this->IsWithinBitRange($source_data_array['methodsymbol'], 8, false)) { 1030 $this->errors[] = 'Invalid Group Symbol in '.$frame_name.' ('.$source_data_array['methodsymbol'].') (range = 0 to 255)'; 1031 } else { 1032 $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00"; 1033 $framedata .= ord($source_data_array['methodsymbol']); 1034 $framedata .= $source_data_array['data']; 1035 } 1036 break; 1037 1038 case 'GRID': 1039 // 4.26 GRID Group identification registration (ID3v2.3+ only) 1040 // Owner identifier <text string> $00 1041 // Group symbol $xx 1042 // Group dependent data <binary data> 1043 if (!$this->IsWithinBitRange($source_data_array['groupsymbol'], 8, false)) { 1044 $this->errors[] = 'Invalid Group Symbol in '.$frame_name.' ('.$source_data_array['groupsymbol'].') (range = 0 to 255)'; 1045 } else { 1046 $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00"; 1047 $framedata .= ord($source_data_array['groupsymbol']); 1048 $framedata .= $source_data_array['data']; 1049 } 1050 break; 1051 1052 case 'PRIV': 1053 // 4.27 PRIV Private frame (ID3v2.3+ only) 1054 // Owner identifier <text string> $00 1055 // The private data <binary data> 1056 $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00"; 1057 $framedata .= $source_data_array['data']; 1058 break; 1059 1060 case 'SIGN': 1061 // 4.28 SIGN Signature frame (ID3v2.4+ only) 1062 // Group symbol $xx 1063 // Signature <binary data> 1064 if (!$this->IsWithinBitRange($source_data_array['groupsymbol'], 8, false)) { 1065 $this->errors[] = 'Invalid Group Symbol in '.$frame_name.' ('.$source_data_array['groupsymbol'].') (range = 0 to 255)'; 1066 } else { 1067 $framedata .= ord($source_data_array['groupsymbol']); 1068 $framedata .= $source_data_array['data']; 1069 } 1070 break; 1071 1072 case 'SEEK': 1073 // 4.29 SEEK Seek frame (ID3v2.4+ only) 1074 // Minimum offset to next tag $xx xx xx xx 1075 if (!$this->IsWithinBitRange($source_data_array['data'], 32, false)) { 1076 $this->errors[] = 'Invalid Minimum Offset in '.$frame_name.' ('.$source_data_array['data'].') (range = 0 to 4294967295)'; 1077 } else { 1078 $framedata .= getid3_lib::BigEndian2String($source_data_array['data'], 4, false); 1079 } 1080 break; 1081 1082 case 'ASPI': 1083 // 4.30 ASPI Audio seek point index (ID3v2.4+ only) 1084 // Indexed data start (S) $xx xx xx xx 1085 // Indexed data length (L) $xx xx xx xx 1086 // Number of index points (N) $xx xx 1087 // Bits per index point (b) $xx 1088 // Then for every index point the following data is included: 1089 // Fraction at index (Fi) $xx (xx) 1090 if (!$this->IsWithinBitRange($source_data_array['datastart'], 32, false)) { 1091 $this->errors[] = 'Invalid Indexed Data Start in '.$frame_name.' ('.$source_data_array['datastart'].') (range = 0 to 4294967295)'; 1092 } elseif (!$this->IsWithinBitRange($source_data_array['datalength'], 32, false)) { 1093 $this->errors[] = 'Invalid Indexed Data Length in '.$frame_name.' ('.$source_data_array['datalength'].') (range = 0 to 4294967295)'; 1094 } elseif (!$this->IsWithinBitRange($source_data_array['indexpoints'], 16, false)) { 1095 $this->errors[] = 'Invalid Number Of Index Points in '.$frame_name.' ('.$source_data_array['indexpoints'].') (range = 0 to 65535)'; 1096 } elseif (!$this->IsWithinBitRange($source_data_array['bitsperpoint'], 8, false)) { 1097 $this->errors[] = 'Invalid Bits Per Index Point in '.$frame_name.' ('.$source_data_array['bitsperpoint'].') (range = 0 to 255)'; 1098 } elseif ($source_data_array['indexpoints'] != count($source_data_array['indexes'])) { 1099 $this->errors[] = 'Number Of Index Points does not match actual supplied data in '.$frame_name; 1100 } else { 1101 $framedata .= getid3_lib::BigEndian2String($source_data_array['datastart'], 4, false); 1102 $framedata .= getid3_lib::BigEndian2String($source_data_array['datalength'], 4, false); 1103 $framedata .= getid3_lib::BigEndian2String($source_data_array['indexpoints'], 2, false); 1104 $framedata .= getid3_lib::BigEndian2String($source_data_array['bitsperpoint'], 1, false); 1105 foreach ($source_data_array['indexes'] as $key => $val) { 1106 $framedata .= getid3_lib::BigEndian2String($val, ceil($source_data_array['bitsperpoint'] / 8), false); 1107 } 1108 } 1109 break; 1110 1111 case 'RGAD': 1112 // RGAD Replay Gain Adjustment 1113 // http://privatewww.essex.ac.uk/~djmrob/replaygain/ 1114 // Peak Amplitude $xx $xx $xx $xx 1115 // Radio Replay Gain Adjustment %aaabbbcd %dddddddd 1116 // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd 1117 // a - name code 1118 // b - originator code 1119 // c - sign bit 1120 // d - replay gain adjustment 1121 1122 if (($source_data_array['track_adjustment'] > 51) || ($source_data_array['track_adjustment'] < -51)) { 1123 $this->errors[] = 'Invalid Track Adjustment in '.$frame_name.' ('.$source_data_array['track_adjustment'].') (range = -51.0 to +51.0)'; 1124 } elseif (($source_data_array['album_adjustment'] > 51) || ($source_data_array['album_adjustment'] < -51)) { 1125 $this->errors[] = 'Invalid Album Adjustment in '.$frame_name.' ('.$source_data_array['album_adjustment'].') (range = -51.0 to +51.0)'; 1126 } elseif (!$this->ID3v2IsValidRGADname($source_data_array['raw']['track_name'])) { 1127 $this->errors[] = 'Invalid Track Name Code in '.$frame_name.' ('.$source_data_array['raw']['track_name'].') (range = 0 to 2)'; 1128 } elseif (!$this->ID3v2IsValidRGADname($source_data_array['raw']['album_name'])) { 1129 $this->errors[] = 'Invalid Album Name Code in '.$frame_name.' ('.$source_data_array['raw']['album_name'].') (range = 0 to 2)'; 1130 } elseif (!$this->ID3v2IsValidRGADoriginator($source_data_array['raw']['track_originator'])) { 1131 $this->errors[] = 'Invalid Track Originator Code in '.$frame_name.' ('.$source_data_array['raw']['track_originator'].') (range = 0 to 3)'; 1132 } elseif (!$this->ID3v2IsValidRGADoriginator($source_data_array['raw']['album_originator'])) { 1133 $this->errors[] = 'Invalid Album Originator Code in '.$frame_name.' ('.$source_data_array['raw']['album_originator'].') (range = 0 to 3)'; 1134 } else { 1135 $framedata .= getid3_lib::Float2String($source_data_array['peakamplitude'], 32); 1136 $framedata .= getid3_lib::RGADgainString($source_data_array['raw']['track_name'], $source_data_array['raw']['track_originator'], $source_data_array['track_adjustment']); 1137 $framedata .= getid3_lib::RGADgainString($source_data_array['raw']['album_name'], $source_data_array['raw']['album_originator'], $source_data_array['album_adjustment']); 1138 } 1139 break; 1140 1141 default: 1142 if ((($this->majorversion == 2) && (strlen($frame_name) != 3)) || (($this->majorversion > 2) && (strlen($frame_name) != 4))) { 1143 $this->errors[] = 'Invalid frame name "'.$frame_name.'" for ID3v2.'.$this->majorversion; 1144 } elseif ($frame_name{0} == 'T') { 1145 // 4.2. T??? Text information frames 1146 // Text encoding $xx 1147 // Information <text string(s) according to encoding> 1148 $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); 1149 if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { 1150 $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; 1151 } else { 1152 $framedata .= chr($source_data_array['encodingid']); 1153 $framedata .= $source_data_array['data']; 1154 } 1155 } elseif ($frame_name{0} == 'W') { 1156 // 4.3. W??? URL link frames 1157 // URL <text string> 1158 if (!$this->IsValidURL($source_data_array['data'], false, false)) { 1159 //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; 1160 // probably should be an error, need to rewrite IsValidURL() to handle other encodings 1161 $this->warnings[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; 1162 } else { 1163 $framedata .= $source_data_array['data']; 1164 } 1165 } else { 1166 $this->errors[] = $frame_name.' not yet supported in $this->GenerateID3v2FrameData()'; 1167 } 1168 break; 1169 } 1170 } 1171 if (!empty($this->errors)) { 1172 return false; 1173 } 1174 return $framedata; 1175 } 1176 1177 public function ID3v2FrameIsAllowed($frame_name, $source_data_array) { 1178 static $PreviousFrames = array(); 1179 1180 if ($frame_name === null) { 1181 // if the writing functions are called multiple times, the static array needs to be 1182 // cleared - this can be done by calling $this->ID3v2FrameIsAllowed(null, '') 1183 $PreviousFrames = array(); 1184 return true; 1185 } 1186 1187 if ($this->majorversion == 4) { 1188 switch ($frame_name) { 1189 case 'UFID': 1190 case 'AENC': 1191 case 'ENCR': 1192 case 'GRID': 1193 if (!isset($source_data_array['ownerid'])) { 1194 $this->errors[] = '[ownerid] not specified for '.$frame_name; 1195 } elseif (in_array($frame_name.$source_data_array['ownerid'], $PreviousFrames)) { 1196 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID ('.$source_data_array['ownerid'].')'; 1197 } else { 1198 $PreviousFrames[] = $frame_name.$source_data_array['ownerid']; 1199 } 1200 break; 1201 1202 case 'TXXX': 1203 case 'WXXX': 1204 case 'RVA2': 1205 case 'EQU2': 1206 case 'APIC': 1207 case 'GEOB': 1208 if (!isset($source_data_array['description'])) { 1209 $this->errors[] = '[description] not specified for '.$frame_name; 1210 } elseif (in_array($frame_name.$source_data_array['description'], $PreviousFrames)) { 1211 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Description ('.$source_data_array['description'].')'; 1212 } else { 1213 $PreviousFrames[] = $frame_name.$source_data_array['description']; 1214 } 1215 break; 1216 1217 case 'USER': 1218 if (!isset($source_data_array['language'])) { 1219 $this->errors[] = '[language] not specified for '.$frame_name; 1220 } elseif (in_array($frame_name.$source_data_array['language'], $PreviousFrames)) { 1221 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language ('.$source_data_array['language'].')'; 1222 } else { 1223 $PreviousFrames[] = $frame_name.$source_data_array['language']; 1224 } 1225 break; 1226 1227 case 'USLT': 1228 case 'SYLT': 1229 case 'COMM': 1230 if (!isset($source_data_array['language'])) { 1231 $this->errors[] = '[language] not specified for '.$frame_name; 1232 } elseif (!isset($source_data_array['description'])) { 1233 $this->errors[] = '[description] not specified for '.$frame_name; 1234 } elseif (in_array($frame_name.$source_data_array['language'].$source_data_array['description'], $PreviousFrames)) { 1235 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')'; 1236 } else { 1237 $PreviousFrames[] = $frame_name.$source_data_array['language'].$source_data_array['description']; 1238 } 1239 break; 1240 1241 case 'POPM': 1242 if (!isset($source_data_array['email'])) { 1243 $this->errors[] = '[email] not specified for '.$frame_name; 1244 } elseif (in_array($frame_name.$source_data_array['email'], $PreviousFrames)) { 1245 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Email ('.$source_data_array['email'].')'; 1246 } else { 1247 $PreviousFrames[] = $frame_name.$source_data_array['email']; 1248 } 1249 break; 1250 1251 case 'IPLS': 1252 case 'MCDI': 1253 case 'ETCO': 1254 case 'MLLT': 1255 case 'SYTC': 1256 case 'RVRB': 1257 case 'PCNT': 1258 case 'RBUF': 1259 case 'POSS': 1260 case 'OWNE': 1261 case 'SEEK': 1262 case 'ASPI': 1263 case 'RGAD': 1264 if (in_array($frame_name, $PreviousFrames)) { 1265 $this->errors[] = 'Only one '.$frame_name.' tag allowed'; 1266 } else { 1267 $PreviousFrames[] = $frame_name; 1268 } 1269 break; 1270 1271 case 'LINK': 1272 // this isn't implemented quite right (yet) - it should check the target frame data for compliance 1273 // but right now it just allows one linked frame of each type, to be safe. 1274 if (!isset($source_data_array['frameid'])) { 1275 $this->errors[] = '[frameid] not specified for '.$frame_name; 1276 } elseif (in_array($frame_name.$source_data_array['frameid'], $PreviousFrames)) { 1277 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same FrameID ('.$source_data_array['frameid'].')'; 1278 } elseif (in_array($source_data_array['frameid'], $PreviousFrames)) { 1279 // no links to singleton tags 1280 $this->errors[] = 'Cannot specify a '.$frame_name.' tag to a singleton tag that already exists ('.$source_data_array['frameid'].')'; 1281 } else { 1282 $PreviousFrames[] = $frame_name.$source_data_array['frameid']; // only one linked tag of this type 1283 $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type 1284 } 1285 break; 1286 1287 case 'COMR': 1288 // There may be more than one 'commercial frame' in a tag, but no two may be identical 1289 // Checking isn't implemented at all (yet) - just assumes that it's OK. 1290 break; 1291 1292 case 'PRIV': 1293 case 'SIGN': 1294 if (!isset($source_data_array['ownerid'])) { 1295 $this->errors[] = '[ownerid] not specified for '.$frame_name; 1296 } elseif (!isset($source_data_array['data'])) { 1297 $this->errors[] = '[data] not specified for '.$frame_name; 1298 } elseif (in_array($frame_name.$source_data_array['ownerid'].$source_data_array['data'], $PreviousFrames)) { 1299 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID + Data ('.$source_data_array['ownerid'].' + '.$source_data_array['data'].')'; 1300 } else { 1301 $PreviousFrames[] = $frame_name.$source_data_array['ownerid'].$source_data_array['data']; 1302 } 1303 break; 1304 1305 default: 1306 if (($frame_name{0} != 'T') && ($frame_name{0} != 'W')) { 1307 $this->errors[] = 'Frame not allowed in ID3v2.'.$this->majorversion.': '.$frame_name; 1308 } 1309 break; 1310 } 1311 1312 } elseif ($this->majorversion == 3) { 1313 1314 switch ($frame_name) { 1315 case 'UFID': 1316 case 'AENC': 1317 case 'ENCR': 1318 case 'GRID': 1319 if (!isset($source_data_array['ownerid'])) { 1320 $this->errors[] = '[ownerid] not specified for '.$frame_name; 1321 } elseif (in_array($frame_name.$source_data_array['ownerid'], $PreviousFrames)) { 1322 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID ('.$source_data_array['ownerid'].')'; 1323 } else { 1324 $PreviousFrames[] = $frame_name.$source_data_array['ownerid']; 1325 } 1326 break; 1327 1328 case 'TXXX': 1329 case 'WXXX': 1330 case 'APIC': 1331 case 'GEOB': 1332 if (!isset($source_data_array['description'])) { 1333 $this->errors[] = '[description] not specified for '.$frame_name; 1334 } elseif (in_array($frame_name.$source_data_array['description'], $PreviousFrames)) { 1335 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Description ('.$source_data_array['description'].')'; 1336 } else { 1337 $PreviousFrames[] = $frame_name.$source_data_array['description']; 1338 } 1339 break; 1340 1341 case 'USER': 1342 if (!isset($source_data_array['language'])) { 1343 $this->errors[] = '[language] not specified for '.$frame_name; 1344 } elseif (in_array($frame_name.$source_data_array['language'], $PreviousFrames)) { 1345 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language ('.$source_data_array['language'].')'; 1346 } else { 1347 $PreviousFrames[] = $frame_name.$source_data_array['language']; 1348 } 1349 break; 1350 1351 case 'USLT': 1352 case 'SYLT': 1353 case 'COMM': 1354 if (!isset($source_data_array['language'])) { 1355 $this->errors[] = '[language] not specified for '.$frame_name; 1356 } elseif (!isset($source_data_array['description'])) { 1357 $this->errors[] = '[description] not specified for '.$frame_name; 1358 } elseif (in_array($frame_name.$source_data_array['language'].$source_data_array['description'], $PreviousFrames)) { 1359 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')'; 1360 } else { 1361 $PreviousFrames[] = $frame_name.$source_data_array['language'].$source_data_array['description']; 1362 } 1363 break; 1364 1365 case 'POPM': 1366 if (!isset($source_data_array['email'])) { 1367 $this->errors[] = '[email] not specified for '.$frame_name; 1368 } elseif (in_array($frame_name.$source_data_array['email'], $PreviousFrames)) { 1369 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Email ('.$source_data_array['email'].')'; 1370 } else { 1371 $PreviousFrames[] = $frame_name.$source_data_array['email']; 1372 } 1373 break; 1374 1375 case 'IPLS': 1376 case 'MCDI': 1377 case 'ETCO': 1378 case 'MLLT': 1379 case 'SYTC': 1380 case 'RVAD': 1381 case 'EQUA': 1382 case 'RVRB': 1383 case 'PCNT': 1384 case 'RBUF': 1385 case 'POSS': 1386 case 'OWNE': 1387 case 'RGAD': 1388 if (in_array($frame_name, $PreviousFrames)) { 1389 $this->errors[] = 'Only one '.$frame_name.' tag allowed'; 1390 } else { 1391 $PreviousFrames[] = $frame_name; 1392 } 1393 break; 1394 1395 case 'LINK': 1396 // this isn't implemented quite right (yet) - it should check the target frame data for compliance 1397 // but right now it just allows one linked frame of each type, to be safe. 1398 if (!isset($source_data_array['frameid'])) { 1399 $this->errors[] = '[frameid] not specified for '.$frame_name; 1400 } elseif (in_array($frame_name.$source_data_array['frameid'], $PreviousFrames)) { 1401 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same FrameID ('.$source_data_array['frameid'].')'; 1402 } elseif (in_array($source_data_array['frameid'], $PreviousFrames)) { 1403 // no links to singleton tags 1404 $this->errors[] = 'Cannot specify a '.$frame_name.' tag to a singleton tag that already exists ('.$source_data_array['frameid'].')'; 1405 } else { 1406 $PreviousFrames[] = $frame_name.$source_data_array['frameid']; // only one linked tag of this type 1407 $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type 1408 } 1409 break; 1410 1411 case 'COMR': 1412 // There may be more than one 'commercial frame' in a tag, but no two may be identical 1413 // Checking isn't implemented at all (yet) - just assumes that it's OK. 1414 break; 1415 1416 case 'PRIV': 1417 if (!isset($source_data_array['ownerid'])) { 1418 $this->errors[] = '[ownerid] not specified for '.$frame_name; 1419 } elseif (!isset($source_data_array['data'])) { 1420 $this->errors[] = '[data] not specified for '.$frame_name; 1421 } elseif (in_array($frame_name.$source_data_array['ownerid'].$source_data_array['data'], $PreviousFrames)) { 1422 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID + Data ('.$source_data_array['ownerid'].' + '.$source_data_array['data'].')'; 1423 } else { 1424 $PreviousFrames[] = $frame_name.$source_data_array['ownerid'].$source_data_array['data']; 1425 } 1426 break; 1427 1428 default: 1429 if (($frame_name{0} != 'T') && ($frame_name{0} != 'W')) { 1430 $this->errors[] = 'Frame not allowed in ID3v2.'.$this->majorversion.': '.$frame_name; 1431 } 1432 break; 1433 } 1434 1435 } elseif ($this->majorversion == 2) { 1436 1437 switch ($frame_name) { 1438 case 'UFI': 1439 case 'CRM': 1440 case 'CRA': 1441 if (!isset($source_data_array['ownerid'])) { 1442 $this->errors[] = '[ownerid] not specified for '.$frame_name; 1443 } elseif (in_array($frame_name.$source_data_array['ownerid'], $PreviousFrames)) { 1444 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID ('.$source_data_array['ownerid'].')'; 1445 } else { 1446 $PreviousFrames[] = $frame_name.$source_data_array['ownerid']; 1447 } 1448 break; 1449 1450 case 'TXX': 1451 case 'WXX': 1452 case 'PIC': 1453 case 'GEO': 1454 if (!isset($source_data_array['description'])) { 1455 $this->errors[] = '[description] not specified for '.$frame_name; 1456 } elseif (in_array($frame_name.$source_data_array['description'], $PreviousFrames)) { 1457 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Description ('.$source_data_array['description'].')'; 1458 } else { 1459 $PreviousFrames[] = $frame_name.$source_data_array['description']; 1460 } 1461 break; 1462 1463 case 'ULT': 1464 case 'SLT': 1465 case 'COM': 1466 if (!isset($source_data_array['language'])) { 1467 $this->errors[] = '[language] not specified for '.$frame_name; 1468 } elseif (!isset($source_data_array['description'])) { 1469 $this->errors[] = '[description] not specified for '.$frame_name; 1470 } elseif (in_array($frame_name.$source_data_array['language'].$source_data_array['description'], $PreviousFrames)) { 1471 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')'; 1472 } else { 1473 $PreviousFrames[] = $frame_name.$source_data_array['language'].$source_data_array['description']; 1474 } 1475 break; 1476 1477 case 'POP': 1478 if (!isset($source_data_array['email'])) { 1479 $this->errors[] = '[email] not specified for '.$frame_name; 1480 } elseif (in_array($frame_name.$source_data_array['email'], $PreviousFrames)) { 1481 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Email ('.$source_data_array['email'].')'; 1482 } else { 1483 $PreviousFrames[] = $frame_name.$source_data_array['email']; 1484 } 1485 break; 1486 1487 case 'IPL': 1488 case 'MCI': 1489 case 'ETC': 1490 case 'MLL': 1491 case 'STC': 1492 case 'RVA': 1493 case 'EQU': 1494 case 'REV': 1495 case 'CNT': 1496 case 'BUF': 1497 if (in_array($frame_name, $PreviousFrames)) { 1498 $this->errors[] = 'Only one '.$frame_name.' tag allowed'; 1499 } else { 1500 $PreviousFrames[] = $frame_name; 1501 } 1502 break; 1503 1504 case 'LNK': 1505 // this isn't implemented quite right (yet) - it should check the target frame data for compliance 1506 // but right now it just allows one linked frame of each type, to be safe. 1507 if (!isset($source_data_array['frameid'])) { 1508 $this->errors[] = '[frameid] not specified for '.$frame_name; 1509 } elseif (in_array($frame_name.$source_data_array['frameid'], $PreviousFrames)) { 1510 $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same FrameID ('.$source_data_array['frameid'].')'; 1511 } elseif (in_array($source_data_array['frameid'], $PreviousFrames)) { 1512 // no links to singleton tags 1513 $this->errors[] = 'Cannot specify a '.$frame_name.' tag to a singleton tag that already exists ('.$source_data_array['frameid'].')'; 1514 } else { 1515 $PreviousFrames[] = $frame_name.$source_data_array['frameid']; // only one linked tag of this type 1516 $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type 1517 } 1518 break; 1519 1520 default: 1521 if (($frame_name{0} != 'T') && ($frame_name{0} != 'W')) { 1522 $this->errors[] = 'Frame not allowed in ID3v2.'.$this->majorversion.': '.$frame_name; 1523 } 1524 break; 1525 } 1526 } 1527 1528 if (!empty($this->errors)) { 1529 return false; 1530 } 1531 return true; 1532 } 1533 1534 public function GenerateID3v2Tag($noerrorsonly=true) { 1535 $this->ID3v2FrameIsAllowed(null, ''); // clear static array in case this isn't the first call to $this->GenerateID3v2Tag() 1536 1537 $tagstring = ''; 1538 if (is_array($this->tag_data)) { 1539 foreach ($this->tag_data as $frame_name => $frame_rawinputdata) { 1540 foreach ($frame_rawinputdata as $irrelevantindex => $source_data_array) { 1541 if (getid3_id3v2::IsValidID3v2FrameName($frame_name, $this->majorversion)) { 1542 unset($frame_length); 1543 unset($frame_flags); 1544 $frame_data = false; 1545 if ($this->ID3v2FrameIsAllowed($frame_name, $source_data_array)) { 1546 if ($frame_data = $this->GenerateID3v2FrameData($frame_name, $source_data_array)) { 1547 $FrameUnsynchronisation = false; 1548 if ($this->majorversion >= 4) { 1549 // frame-level unsynchronisation 1550 $unsynchdata = $frame_data; 1551 if ($this->id3v2_use_unsynchronisation) { 1552 $unsynchdata = $this->Unsynchronise($frame_data); 1553 } 1554 if (strlen($unsynchdata) != strlen($frame_data)) { 1555 // unsynchronisation needed 1556 $FrameUnsynchronisation = true; 1557 $frame_data = $unsynchdata; 1558 if (isset($TagUnsynchronisation) && $TagUnsynchronisation === false) { 1559 // only set to true if ALL frames are unsynchronised 1560 } else { 1561 $TagUnsynchronisation = true; 1562 } 1563 } else { 1564 if (isset($TagUnsynchronisation)) { 1565 $TagUnsynchronisation = false; 1566 } 1567 } 1568 unset($unsynchdata); 1569 1570 $frame_length = getid3_lib::BigEndian2String(strlen($frame_data), 4, true); 1571 } else { 1572 $frame_length = getid3_lib::BigEndian2String(strlen($frame_data), 4, false); 1573 } 1574 $frame_flags = $this->GenerateID3v2FrameFlags($this->ID3v2FrameFlagsLookupTagAlter($frame_name), $this->ID3v2FrameFlagsLookupFileAlter($frame_name), false, false, false, false, $FrameUnsynchronisation, false); 1575 } 1576 } else { 1577 $this->errors[] = 'Frame "'.$frame_name.'" is NOT allowed'; 1578 } 1579 if ($frame_data === false) { 1580 $this->errors[] = '$this->GenerateID3v2FrameData() failed for "'.$frame_name.'"'; 1581 if ($noerrorsonly) { 1582 return false; 1583 } else { 1584 unset($frame_name); 1585 } 1586 } 1587 } else { 1588 // ignore any invalid frame names, including 'title', 'header', etc 1589 $this->warnings[] = 'Ignoring invalid ID3v2 frame type: "'.$frame_name.'"'; 1590 unset($frame_name); 1591 unset($frame_length); 1592 unset($frame_flags); 1593 unset($frame_data); 1594 } 1595 if (isset($frame_name) && isset($frame_length) && isset($frame_flags) && isset($frame_data)) { 1596 $tagstring .= $frame_name.$frame_length.$frame_flags.$frame_data; 1597 } 1598 } 1599 } 1600 1601 if (!isset($TagUnsynchronisation)) { 1602 $TagUnsynchronisation = false; 1603 } 1604 if (($this->majorversion <= 3) && $this->id3v2_use_unsynchronisation) { 1605 // tag-level unsynchronisation 1606 $unsynchdata = $this->Unsynchronise($tagstring); 1607 if (strlen($unsynchdata) != strlen($tagstring)) { 1608 // unsynchronisation needed 1609 $TagUnsynchronisation = true; 1610 $tagstring = $unsynchdata; 1611 } 1612 } 1613 1614 while ($this->paddedlength < (strlen($tagstring) + getid3_id3v2::ID3v2HeaderLength($this->majorversion))) { 1615 $this->paddedlength += 1024; 1616 } 1617 1618 $footer = false; // ID3v2 footers not yet supported in getID3() 1619 if (!$footer && ($this->paddedlength > (strlen($tagstring) + getid3_id3v2::ID3v2HeaderLength($this->majorversion)))) { 1620 // pad up to $paddedlength bytes if unpadded tag is shorter than $paddedlength 1621 // "Furthermore it MUST NOT have any padding when a tag footer is added to the tag." 1622 if (($this->paddedlength - strlen($tagstring) - getid3_id3v2::ID3v2HeaderLength($this->majorversion)) > 0) { 1623 $tagstring .= str_repeat("\x00", $this->paddedlength - strlen($tagstring) - getid3_id3v2::ID3v2HeaderLength($this->majorversion)); 1624 } 1625 } 1626 if ($this->id3v2_use_unsynchronisation && (substr($tagstring, strlen($tagstring) - 1, 1) == "\xFF")) { 1627 // special unsynchronisation case: 1628 // if last byte == $FF then appended a $00 1629 $TagUnsynchronisation = true; 1630 $tagstring .= "\x00"; 1631 } 1632 1633 $tagheader = 'ID3'; 1634 $tagheader .= chr($this->majorversion); 1635 $tagheader .= chr($this->minorversion); 1636 $tagheader .= $this->GenerateID3v2TagFlags(array('unsynchronisation'=>$TagUnsynchronisation)); 1637 $tagheader .= getid3_lib::BigEndian2String(strlen($tagstring), 4, true); 1638 1639 return $tagheader.$tagstring; 1640 } 1641 $this->errors[] = 'tag_data is not an array in GenerateID3v2Tag()'; 1642 return false; 1643 } 1644 1645 public function ID3v2IsValidPriceString($pricestring) { 1646 if (getid3_id3v2::LanguageLookup(substr($pricestring, 0, 3), true) == '') { 1647 return false; 1648 } elseif (!$this->IsANumber(substr($pricestring, 3), true)) { 1649 return false; 1650 } 1651 return true; 1652 } 1653 1654 public function ID3v2FrameFlagsLookupTagAlter($framename) { 1655 // unfinished 1656 switch ($framename) { 1657 case 'RGAD': 1658 $allow = true; 1659 default: 1660 $allow = false; 1661 break; 1662 } 1663 return $allow; 1664 } 1665 1666 public function ID3v2FrameFlagsLookupFileAlter($framename) { 1667 // unfinished 1668 switch ($framename) { 1669 case 'RGAD': 1670 return false; 1671 break; 1672 1673 default: 1674 return false; 1675 break; 1676 } 1677 } 1678 1679 public function ID3v2IsValidETCOevent($eventid) { 1680 if (($eventid < 0) || ($eventid > 0xFF)) { 1681 // outside range of 1 byte 1682 return false; 1683 } elseif (($eventid >= 0xF0) && ($eventid <= 0xFC)) { 1684 // reserved for future use 1685 return false; 1686 } elseif (($eventid >= 0x17) && ($eventid <= 0xDF)) { 1687 // reserved for future use 1688 return false; 1689 } elseif (($eventid >= 0x0E) && ($eventid <= 0x16) && ($this->majorversion == 2)) { 1690 // not defined in ID3v2.2 1691 return false; 1692 } elseif (($eventid >= 0x15) && ($eventid <= 0x16) && ($this->majorversion == 3)) { 1693 // not defined in ID3v2.3 1694 return false; 1695 } 1696 return true; 1697 } 1698 1699 public function ID3v2IsValidSYLTtype($contenttype) { 1700 if (($contenttype >= 0) && ($contenttype <= 8) && ($this->majorversion == 4)) { 1701 return true; 1702 } elseif (($contenttype >= 0) && ($contenttype <= 6) && ($this->majorversion == 3)) { 1703 return true; 1704 } 1705 return false; 1706 } 1707 1708 public function ID3v2IsValidRVA2channeltype($channeltype) { 1709 if (($channeltype >= 0) && ($channeltype <= 8) && ($this->majorversion == 4)) { 1710 return true; 1711 } 1712 return false; 1713 } 1714 1715 public function ID3v2IsValidAPICpicturetype($picturetype) { 1716 if (($picturetype >= 0) && ($picturetype <= 0x14) && ($this->majorversion >= 2) && ($this->majorversion <= 4)) { 1717 return true; 1718 } 1719 return false; 1720 } 1721 1722 public function ID3v2IsValidAPICimageformat($imageformat) { 1723 if ($imageformat == '-->') { 1724 return true; 1725 } elseif ($this->majorversion == 2) { 1726 if ((strlen($imageformat) == 3) && ($imageformat == strtoupper($imageformat))) { 1727 return true; 1728 } 1729 } elseif (($this->majorversion == 3) || ($this->majorversion == 4)) { 1730 if ($this->IsValidMIMEstring($imageformat)) { 1731 return true; 1732 } 1733 } 1734 return false; 1735 } 1736 1737 public function ID3v2IsValidCOMRreceivedAs($receivedas) { 1738 if (($this->majorversion >= 3) && ($receivedas >= 0) && ($receivedas <= 8)) { 1739 return true; 1740 } 1741 return false; 1742 } 1743 1744 public function ID3v2IsValidRGADname($RGADname) { 1745 if (($RGADname >= 0) && ($RGADname <= 2)) { 1746 return true; 1747 } 1748 return false; 1749 } 1750 1751 public function ID3v2IsValidRGADoriginator($RGADoriginator) { 1752 if (($RGADoriginator >= 0) && ($RGADoriginator <= 3)) { 1753 return true; 1754 } 1755 return false; 1756 } 1757 1758 public function ID3v2IsValidTextEncoding($textencodingbyte) { 1759 static $ID3v2IsValidTextEncoding_cache = array( 1760 2 => array(true, true), 1761 3 => array(true, true), 1762 4 => array(true, true, true, true)); 1763 return isset($ID3v2IsValidTextEncoding_cache[$this->majorversion][$textencodingbyte]); 1764 } 1765 1766 public function Unsynchronise($data) { 1767 // Whenever a false synchronisation is found within the tag, one zeroed 1768 // byte is inserted after the first false synchronisation byte. The 1769 // format of a correct sync that should be altered by ID3 encoders is as 1770 // follows: 1771 // %11111111 111xxxxx 1772 // And should be replaced with: 1773 // %11111111 00000000 111xxxxx 1774 // This has the side effect that all $FF 00 combinations have to be 1775 // altered, so they won't be affected by the decoding process. Therefore 1776 // all the $FF 00 combinations have to be replaced with the $FF 00 00 1777 // combination during the unsynchronisation. 1778 1779 $data = str_replace("\xFF\x00", "\xFF\x00\x00", $data); 1780 $unsyncheddata = ''; 1781 $datalength = strlen($data); 1782 for ($i = 0; $i < $datalength; $i++) { 1783 $thischar = $data{$i}; 1784 $unsyncheddata .= $thischar; 1785 if ($thischar == "\xFF") { 1786 $nextchar = ord($data{$i + 1}); 1787 if (($nextchar & 0xE0) == 0xE0) { 1788 // previous byte = 11111111, this byte = 111????? 1789 $unsyncheddata .= "\x00"; 1790 } 1791 } 1792 } 1793 return $unsyncheddata; 1794 } 1795 1796 public function is_hash($var) { 1797 // written by dev-nullØchristophe*vg 1798 // taken from http://www.php.net/manual/en/function.array-merge-recursive.php 1799 if (is_array($var)) { 1800 $keys = array_keys($var); 1801 $all_num = true; 1802 for ($i = 0; $i < count($keys); $i++) { 1803 if (is_string($keys[$i])) { 1804 return true; 1805 } 1806 } 1807 } 1808 return false; 1809 } 1810 1811 public function array_join_merge($arr1, $arr2) { 1812 // written by dev-nullØchristophe*vg 1813 // taken from http://www.php.net/manual/en/function.array-merge-recursive.php 1814 if (is_array($arr1) && is_array($arr2)) { 1815 // the same -> merge 1816 $new_array = array(); 1817 1818 if ($this->is_hash($arr1) && $this->is_hash($arr2)) { 1819 // hashes -> merge based on keys 1820 $keys = array_merge(array_keys($arr1), array_keys($arr2)); 1821 foreach ($keys as $key) { 1822 $new_array[$key] = $this->array_join_merge((isset($arr1[$key]) ? $arr1[$key] : ''), (isset($arr2[$key]) ? $arr2[$key] : '')); 1823 } 1824 } else { 1825 // two real arrays -> merge 1826 $new_array = array_reverse(array_unique(array_reverse(array_merge($arr1, $arr2)))); 1827 } 1828 return $new_array; 1829 } else { 1830 // not the same ... take new one if defined, else the old one stays 1831 return $arr2 ? $arr2 : $arr1; 1832 } 1833 } 1834 1835 public function IsValidMIMEstring($mimestring) { 1836 if ((strlen($mimestring) >= 3) && (strpos($mimestring, '/') > 0) && (strpos($mimestring, '/') < (strlen($mimestring) - 1))) { 1837 return true; 1838 } 1839 return false; 1840 } 1841 1842 public function IsWithinBitRange($number, $maxbits, $signed=false) { 1843 if ($signed) { 1844 if (($number > (0 - pow(2, $maxbits - 1))) && ($number <= pow(2, $maxbits - 1))) { 1845 return true; 1846 } 1847 } else { 1848 if (($number >= 0) && ($number <= pow(2, $maxbits))) { 1849 return true; 1850 } 1851 } 1852 return false; 1853 } 1854 1855 public function safe_parse_url($url) { 1856 $parts = @parse_url($url); 1857 $parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : ''); 1858 $parts['host'] = (isset($parts['host']) ? $parts['host'] : ''); 1859 $parts['user'] = (isset($parts['user']) ? $parts['user'] : ''); 1860 $parts['pass'] = (isset($parts['pass']) ? $parts['pass'] : ''); 1861 $parts['path'] = (isset($parts['path']) ? $parts['path'] : ''); 1862 $parts['query'] = (isset($parts['query']) ? $parts['query'] : ''); 1863 return $parts; 1864 } 1865 1866 public function IsValidURL($url, $allowUserPass=false) { 1867 if ($url == '') { 1868 return false; 1869 } 1870 if ($allowUserPass !== true) { 1871 if (strstr($url, '@')) { 1872 // in the format http://user:pass@example.com or http://user@example.com 1873 // but could easily be somebody incorrectly entering an email address in place of a URL 1874 return false; 1875 } 1876 } 1877 if ($parts = $this->safe_parse_url($url)) { 1878 if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher')) { 1879 return false; 1880 } elseif (!preg_match('#^[[:alnum:]]([-.]?[0-9a-z])*\\.[a-z]{2,3}$#i', $parts['host'], $regs) && !preg_match('#^[0-9]{1,3}(\\.[0-9]{1,3}){3}$#', $parts['host'])) { 1881 return false; 1882 } elseif (!preg_match('#^([[:alnum:]-]|[\\_])*$#i', $parts['user'], $regs)) { 1883 return false; 1884 } elseif (!preg_match('#^([[:alnum:]-]|[\\_])*$#i', $parts['pass'], $regs)) { 1885 return false; 1886 } elseif (!preg_match('#^[[:alnum:]/_\\.@~-]*$#i', $parts['path'], $regs)) { 1887 return false; 1888 } elseif (!empty($parts['query']) && !preg_match('#^[[:alnum:]?&=+:;_()%\\#/,\\.-]*$#i', $parts['query'], $regs)) { 1889 return false; 1890 } else { 1891 return true; 1892 } 1893 } 1894 return false; 1895 } 1896 1897 public static function ID3v2ShortFrameNameLookup($majorversion, $long_description) { 1898 $long_description = str_replace(' ', '_', strtolower(trim($long_description))); 1899 static $ID3v2ShortFrameNameLookup = array(); 1900 if (empty($ID3v2ShortFrameNameLookup)) { 1901 1902 // The following are unique to ID3v2.2 1903 $ID3v2ShortFrameNameLookup[2]['comment'] = 'COM'; 1904 $ID3v2ShortFrameNameLookup[2]['album'] = 'TAL'; 1905 $ID3v2ShortFrameNameLookup[2]['beats_per_minute'] = 'TBP'; 1906 $ID3v2ShortFrameNameLookup[2]['composer'] = 'TCM'; 1907 $ID3v2ShortFrameNameLookup[2]['genre'] = 'TCO'; 1908 $ID3v2ShortFrameNameLookup[2]['itunescompilation'] = 'TCP'; 1909 $ID3v2ShortFrameNameLookup[2]['copyright'] = 'TCR'; 1910 $ID3v2ShortFrameNameLookup[2]['encoded_by'] = 'TEN'; 1911 $ID3v2ShortFrameNameLookup[2]['language'] = 'TLA'; 1912 $ID3v2ShortFrameNameLookup[2]['length'] = 'TLE'; 1913 $ID3v2ShortFrameNameLookup[2]['original_artist'] = 'TOA'; 1914 $ID3v2ShortFrameNameLookup[2]['original_filename'] = 'TOF'; 1915 $ID3v2ShortFrameNameLookup[2]['original_lyricist'] = 'TOL'; 1916 $ID3v2ShortFrameNameLookup[2]['original_album_title'] = 'TOT'; 1917 $ID3v2ShortFrameNameLookup[2]['artist'] = 'TP1'; 1918 $ID3v2ShortFrameNameLookup[2]['band'] = 'TP2'; 1919 $ID3v2ShortFrameNameLookup[2]['conductor'] = 'TP3'; 1920 $ID3v2ShortFrameNameLookup[2]['remixer'] = 'TP4'; 1921 $ID3v2ShortFrameNameLookup[2]['publisher'] = 'TPB'; 1922 $ID3v2ShortFrameNameLookup[2]['isrc'] = 'TRC'; 1923 $ID3v2ShortFrameNameLookup[2]['tracknumber'] = 'TRK'; 1924 $ID3v2ShortFrameNameLookup[2]['size'] = 'TSI'; 1925 $ID3v2ShortFrameNameLookup[2]['encoder_settings'] = 'TSS'; 1926 $ID3v2ShortFrameNameLookup[2]['description'] = 'TT1'; 1927 $ID3v2ShortFrameNameLookup[2]['title'] = 'TT2'; 1928 $ID3v2ShortFrameNameLookup[2]['subtitle'] = 'TT3'; 1929 $ID3v2ShortFrameNameLookup[2]['lyricist'] = 'TXT'; 1930 $ID3v2ShortFrameNameLookup[2]['user_text'] = 'TXX'; 1931 $ID3v2ShortFrameNameLookup[2]['year'] = 'TYE'; 1932 $ID3v2ShortFrameNameLookup[2]['unique_file_identifier'] = 'UFI'; 1933 $ID3v2ShortFrameNameLookup[2]['unsynchronised_lyrics'] = 'ULT'; 1934 $ID3v2ShortFrameNameLookup[2]['url_file'] = 'WAF'; 1935 $ID3v2ShortFrameNameLookup[2]['url_artist'] = 'WAR'; 1936 $ID3v2ShortFrameNameLookup[2]['url_source'] = 'WAS'; 1937 $ID3v2ShortFrameNameLookup[2]['copyright_information'] = 'WCP'; 1938 $ID3v2ShortFrameNameLookup[2]['url_publisher'] = 'WPB'; 1939 $ID3v2ShortFrameNameLookup[2]['url_user'] = 'WXX'; 1940 1941 // The following are common to ID3v2.3 and ID3v2.4 1942 $ID3v2ShortFrameNameLookup[3]['audio_encryption'] = 'AENC'; 1943 $ID3v2ShortFrameNameLookup[3]['attached_picture'] = 'APIC'; 1944 $ID3v2ShortFrameNameLookup[3]['comment'] = 'COMM'; 1945 $ID3v2ShortFrameNameLookup[3]['commercial'] = 'COMR'; 1946 $ID3v2ShortFrameNameLookup[3]['encryption_method_registration'] = 'ENCR'; 1947 $ID3v2ShortFrameNameLookup[3]['event_timing_codes'] = 'ETCO'; 1948 $ID3v2ShortFrameNameLookup[3]['general_encapsulated_object'] = 'GEOB'; 1949 $ID3v2ShortFrameNameLookup[3]['group_identification_registration'] = 'GRID'; 1950 $ID3v2ShortFrameNameLookup[3]['linked_information'] = 'LINK'; 1951 $ID3v2ShortFrameNameLookup[3]['music_cd_identifier'] = 'MCDI'; 1952 $ID3v2ShortFrameNameLookup[3]['mpeg_location_lookup_table'] = 'MLLT'; 1953 $ID3v2ShortFrameNameLookup[3]['ownership'] = 'OWNE'; 1954 $ID3v2ShortFrameNameLookup[3]['play_counter'] = 'PCNT'; 1955 $ID3v2ShortFrameNameLookup[3]['popularimeter'] = 'POPM'; 1956 $ID3v2ShortFrameNameLookup[3]['position_synchronisation'] = 'POSS'; 1957 $ID3v2ShortFrameNameLookup[3]['private'] = 'PRIV'; 1958 $ID3v2ShortFrameNameLookup[3]['recommended_buffer_size'] = 'RBUF'; 1959 $ID3v2ShortFrameNameLookup[3]['reverb'] = 'RVRB'; 1960 $ID3v2ShortFrameNameLookup[3]['synchronised_lyrics'] = 'SYLT'; 1961 $ID3v2ShortFrameNameLookup[3]['synchronised_tempo_codes'] = 'SYTC'; 1962 $ID3v2ShortFrameNameLookup[3]['album'] = 'TALB'; 1963 $ID3v2ShortFrameNameLookup[3]['beats_per_minute'] = 'TBPM'; 1964 $ID3v2ShortFrameNameLookup[3]['itunescompilation'] = 'TCMP'; 1965 $ID3v2ShortFrameNameLookup[3]['composer'] = 'TCOM'; 1966 $ID3v2ShortFrameNameLookup[3]['genre'] = 'TCON'; 1967 $ID3v2ShortFrameNameLookup[3]['copyright'] = 'TCOP'; 1968 $ID3v2ShortFrameNameLookup[3]['playlist_delay'] = 'TDLY'; 1969 $ID3v2ShortFrameNameLookup[3]['encoded_by'] = 'TENC'; 1970 $ID3v2ShortFrameNameLookup[3]['lyricist'] = 'TEXT'; 1971 $ID3v2ShortFrameNameLookup[3]['file_type'] = 'TFLT'; 1972 $ID3v2ShortFrameNameLookup[3]['content_group_description'] = 'TIT1'; 1973 $ID3v2ShortFrameNameLookup[3]['title'] = 'TIT2'; 1974 $ID3v2ShortFrameNameLookup[3]['subtitle'] = 'TIT3'; 1975 $ID3v2ShortFrameNameLookup[3]['initial_key'] = 'TKEY'; 1976 $ID3v2ShortFrameNameLookup[3]['language'] = 'TLAN'; 1977 $ID3v2ShortFrameNameLookup[3]['length'] = 'TLEN'; 1978 $ID3v2ShortFrameNameLookup[3]['media_type'] = 'TMED'; 1979 $ID3v2ShortFrameNameLookup[3]['original_album_title'] = 'TOAL'; 1980 $ID3v2ShortFrameNameLookup[3]['original_filename'] = 'TOFN'; 1981 $ID3v2ShortFrameNameLookup[3]['original_lyricist'] = 'TOLY'; 1982 $ID3v2ShortFrameNameLookup[3]['original_artist'] = 'TOPE'; 1983 $ID3v2ShortFrameNameLookup[3]['file_owner'] = 'TOWN'; 1984 $ID3v2ShortFrameNameLookup[3]['artist'] = 'TPE1'; 1985 $ID3v2ShortFrameNameLookup[3]['band'] = 'TPE2'; 1986 $ID3v2ShortFrameNameLookup[3]['conductor'] = 'TPE3'; 1987 $ID3v2ShortFrameNameLookup[3]['remixer'] = 'TPE4'; 1988 $ID3v2ShortFrameNameLookup[3]['part_of_a_set'] = 'TPOS'; 1989 $ID3v2ShortFrameNameLookup[3]['publisher'] = 'TPUB'; 1990 $ID3v2ShortFrameNameLookup[3]['tracknumber'] = 'TRCK'; 1991 $ID3v2ShortFrameNameLookup[3]['internet_radio_station_name'] = 'TRSN'; 1992 $ID3v2ShortFrameNameLookup[3]['internet_radio_station_owner'] = 'TRSO'; 1993 $ID3v2ShortFrameNameLookup[3]['isrc'] = 'TSRC'; 1994 $ID3v2ShortFrameNameLookup[3]['encoder_settings'] = 'TSSE'; 1995 $ID3v2ShortFrameNameLookup[3]['user_text'] = 'TXXX'; 1996 $ID3v2ShortFrameNameLookup[3]['unique_file_identifier'] = 'UFID'; 1997 $ID3v2ShortFrameNameLookup[3]['terms_of_use'] = 'USER'; 1998 $ID3v2ShortFrameNameLookup[3]['unsynchronised_lyrics'] = 'USLT'; 1999 $ID3v2ShortFrameNameLookup[3]['commercial'] = 'WCOM'; 2000 $ID3v2ShortFrameNameLookup[3]['copyright_information'] = 'WCOP'; 2001 $ID3v2ShortFrameNameLookup[3]['url_file'] = 'WOAF'; 2002 $ID3v2ShortFrameNameLookup[3]['url_artist'] = 'WOAR'; 2003 $ID3v2ShortFrameNameLookup[3]['url_source'] = 'WOAS'; 2004 $ID3v2ShortFrameNameLookup[3]['url_station'] = 'WORS'; 2005 $ID3v2ShortFrameNameLookup[3]['payment'] = 'WPAY'; 2006 $ID3v2ShortFrameNameLookup[3]['url_publisher'] = 'WPUB'; 2007 $ID3v2ShortFrameNameLookup[3]['url_user'] = 'WXXX'; 2008 2009 // The above are common to ID3v2.3 and ID3v2.4 2010 // so copy them to ID3v2.4 before adding specifics for 2.3 and 2.4 2011 $ID3v2ShortFrameNameLookup[4] = $ID3v2ShortFrameNameLookup[3]; 2012 2013 // The following are unique to ID3v2.3 2014 $ID3v2ShortFrameNameLookup[3]['equalisation'] = 'EQUA'; 2015 $ID3v2ShortFrameNameLookup[3]['involved_people_list'] = 'IPLS'; 2016 $ID3v2ShortFrameNameLookup[3]['relative_volume_adjustment'] = 'RVAD'; 2017 $ID3v2ShortFrameNameLookup[3]['date'] = 'TDAT'; 2018 $ID3v2ShortFrameNameLookup[3]['time'] = 'TIME'; 2019 $ID3v2ShortFrameNameLookup[3]['original_release_year'] = 'TORY'; 2020 $ID3v2ShortFrameNameLookup[3]['recording_dates'] = 'TRDA'; 2021 $ID3v2ShortFrameNameLookup[3]['size'] = 'TSIZ'; 2022 $ID3v2ShortFrameNameLookup[3]['year'] = 'TYER'; 2023 2024 2025 // The following are unique to ID3v2.4 2026 $ID3v2ShortFrameNameLookup[4]['audio_seek_point_index'] = 'ASPI'; 2027 $ID3v2ShortFrameNameLookup[4]['equalisation'] = 'EQU2'; 2028 $ID3v2ShortFrameNameLookup[4]['relative_volume_adjustment'] = 'RVA2'; 2029 $ID3v2ShortFrameNameLookup[4]['seek'] = 'SEEK'; 2030 $ID3v2ShortFrameNameLookup[4]['signature'] = 'SIGN'; 2031 $ID3v2ShortFrameNameLookup[4]['encoding_time'] = 'TDEN'; 2032 $ID3v2ShortFrameNameLookup[4]['original_release_time'] = 'TDOR'; 2033 $ID3v2ShortFrameNameLookup[4]['recording_time'] = 'TDRC'; 2034 $ID3v2ShortFrameNameLookup[4]['release_time'] = 'TDRL'; 2035 $ID3v2ShortFrameNameLookup[4]['tagging_time'] = 'TDTG'; 2036 $ID3v2ShortFrameNameLookup[4]['involved_people_list'] = 'TIPL'; 2037 $ID3v2ShortFrameNameLookup[4]['musician_credits_list'] = 'TMCL'; 2038 $ID3v2ShortFrameNameLookup[4]['mood'] = 'TMOO'; 2039 $ID3v2ShortFrameNameLookup[4]['produced_notice'] = 'TPRO'; 2040 $ID3v2ShortFrameNameLookup[4]['album_sort_order'] = 'TSOA'; 2041 $ID3v2ShortFrameNameLookup[4]['performer_sort_order'] = 'TSOP'; 2042 $ID3v2ShortFrameNameLookup[4]['title_sort_order'] = 'TSOT'; 2043 $ID3v2ShortFrameNameLookup[4]['set_subtitle'] = 'TSST'; 2044 } 2045 return (isset($ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)]) ? $ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)] : ''); 2046 2047 } 2048 2049 } 2050