PageRenderTime 76ms CodeModel.GetById 23ms app.highlight 42ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/modules/metadata/getid3/write.id3v2.php

https://github.com/umonkey/molinos-cms
PHP | 2052 lines | 1578 code | 162 blank | 312 comment | 428 complexity | 7848a496025a6e40954da6bf0a1badce MD5 | raw file

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

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

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