PageRenderTime 703ms CodeModel.GetById 63ms app.highlight 456ms RepoModel.GetById 26ms app.codeStats 8ms

/sys/plugins/id3/getid3/module.tag.id3v2.php

https://bitbucket.org/DESURE/dcms
PHP | 3425 lines | 1514 code | 337 blank | 1574 comment | 498 complexity | 7310d7e0c88ead9d20392aaade5b1ca3 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//          also https://github.com/JamesHeinrich/getID3       //
  7/////////////////////////////////////////////////////////////////
  8// See readme.txt for more details                             //
  9/////////////////////////////////////////////////////////////////
 10///                                                            //
 11// module.tag.id3v2.php                                        //
 12// module for analyzing ID3v2 tags                             //
 13// dependencies: module.tag.id3v1.php                          //
 14//                                                            ///
 15/////////////////////////////////////////////////////////////////
 16
 17getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
 18
 19class getid3_id3v2 extends getid3_handler
 20{
 21	public $StartingOffset = 0;
 22
 23	public function Analyze() {
 24		$info = &$this->getid3->info;
 25
 26		//    Overall tag structure:
 27		//        +-----------------------------+
 28		//        |      Header (10 bytes)      |
 29		//        +-----------------------------+
 30		//        |       Extended Header       |
 31		//        | (variable length, OPTIONAL) |
 32		//        +-----------------------------+
 33		//        |   Frames (variable length)  |
 34		//        +-----------------------------+
 35		//        |           Padding           |
 36		//        | (variable length, OPTIONAL) |
 37		//        +-----------------------------+
 38		//        | Footer (10 bytes, OPTIONAL) |
 39		//        +-----------------------------+
 40
 41		//    Header
 42		//        ID3v2/file identifier      "ID3"
 43		//        ID3v2 version              $04 00
 44		//        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
 45		//        ID3v2 size             4 * %0xxxxxxx
 46
 47
 48		// shortcuts
 49		$info['id3v2']['header'] = true;
 50		$thisfile_id3v2                  = &$info['id3v2'];
 51		$thisfile_id3v2['flags']         =  array();
 52		$thisfile_id3v2_flags            = &$thisfile_id3v2['flags'];
 53
 54
 55		$this->fseek($this->StartingOffset);
 56		$header = $this->fread(10);
 57		if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
 58
 59			$thisfile_id3v2['majorversion'] = ord($header{3});
 60			$thisfile_id3v2['minorversion'] = ord($header{4});
 61
 62			// shortcut
 63			$id3v2_majorversion = &$thisfile_id3v2['majorversion'];
 64
 65		} else {
 66
 67			unset($info['id3v2']);
 68			return false;
 69
 70		}
 71
 72		if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
 73
 74			$info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
 75			return false;
 76
 77		}
 78
 79		$id3_flags = ord($header{5});
 80		switch ($id3v2_majorversion) {
 81			case 2:
 82				// %ab000000 in v2.2
 83				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
 84				$thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
 85				break;
 86
 87			case 3:
 88				// %abc00000 in v2.3
 89				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
 90				$thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
 91				$thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
 92				break;
 93
 94			case 4:
 95				// %abcd0000 in v2.4
 96				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
 97				$thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
 98				$thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
 99				$thisfile_id3v2_flags['isfooter']    = (bool) ($id3_flags & 0x10); // d - Footer present
100				break;
101		}
102
103		$thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
104
105		$thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
106		$thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
107
108
109
110		// create 'encoding' key - used by getid3::HandleAllTags()
111		// in ID3v2 every field can have it's own encoding type
112		// so force everything to UTF-8 so it can be handled consistantly
113		$thisfile_id3v2['encoding'] = 'UTF-8';
114
115
116	//    Frames
117
118	//        All ID3v2 frames consists of one frame header followed by one or more
119	//        fields containing the actual information. The header is always 10
120	//        bytes and laid out as follows:
121	//
122	//        Frame ID      $xx xx xx xx  (four characters)
123	//        Size      4 * %0xxxxxxx
124	//        Flags         $xx xx
125
126		$sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
127		if (!empty($thisfile_id3v2['exthead']['length'])) {
128			$sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
129		}
130		if (!empty($thisfile_id3v2_flags['isfooter'])) {
131			$sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
132		}
133		if ($sizeofframes > 0) {
134
135			$framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
136
137			//    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
138			if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
139				$framedata = $this->DeUnsynchronise($framedata);
140			}
141			//        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
142			//        of on tag level, making it easier to skip frames, increasing the streamability
143			//        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
144			//        there exists an unsynchronised frame, while the new unsynchronisation flag in
145			//        the frame header [S:4.1.2] indicates unsynchronisation.
146
147
148			//$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
149			$framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
150
151
152			//    Extended Header
153			if (!empty($thisfile_id3v2_flags['exthead'])) {
154				$extended_header_offset = 0;
155
156				if ($id3v2_majorversion == 3) {
157
158					// v2.3 definition:
159					//Extended header size  $xx xx xx xx   // 32-bit integer
160					//Extended Flags        $xx xx
161					//     %x0000000 %00000000 // v2.3
162					//     x - CRC data present
163					//Size of padding       $xx xx xx xx
164
165					$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
166					$extended_header_offset += 4;
167
168					$thisfile_id3v2['exthead']['flag_bytes'] = 2;
169					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
170					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
171
172					$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
173
174					$thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
175					$extended_header_offset += 4;
176
177					if ($thisfile_id3v2['exthead']['flags']['crc']) {
178						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
179						$extended_header_offset += 4;
180					}
181					$extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
182
183				} elseif ($id3v2_majorversion == 4) {
184
185					// v2.4 definition:
186					//Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
187					//Number of flag bytes       $01
188					//Extended Flags             $xx
189					//     %0bcd0000 // v2.4
190					//     b - Tag is an update
191					//         Flag data length       $00
192					//     c - CRC data present
193					//         Flag data length       $05
194					//         Total frame CRC    5 * %0xxxxxxx
195					//     d - Tag restrictions
196					//         Flag data length       $01
197
198					$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
199					$extended_header_offset += 4;
200
201					$thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
202					$extended_header_offset += 1;
203
204					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
205					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
206
207					$thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
208					$thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
209					$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
210
211					if ($thisfile_id3v2['exthead']['flags']['update']) {
212						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
213						$extended_header_offset += 1;
214					}
215
216					if ($thisfile_id3v2['exthead']['flags']['crc']) {
217						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
218						$extended_header_offset += 1;
219						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
220						$extended_header_offset += $ext_header_chunk_length;
221					}
222
223					if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
224						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
225						$extended_header_offset += 1;
226
227						// %ppqrrstt
228						$restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
229						$extended_header_offset += 1;
230						$thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
231						$thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
232						$thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
233						$thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
234						$thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
235
236						$thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize']  = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
237						$thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc']  = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
238						$thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
239						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
240						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
241					}
242
243					if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
244						$info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
245					}
246				}
247
248				$framedataoffset += $extended_header_offset;
249				$framedata = substr($framedata, $extended_header_offset);
250			} // end extended header
251
252
253			while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
254				if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
255					// insufficient room left in ID3v2 header for actual data - must be padding
256					$thisfile_id3v2['padding']['start']  = $framedataoffset;
257					$thisfile_id3v2['padding']['length'] = strlen($framedata);
258					$thisfile_id3v2['padding']['valid']  = true;
259					for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
260						if ($framedata{$i} != "\x00") {
261							$thisfile_id3v2['padding']['valid'] = false;
262							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
263							$info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
264							break;
265						}
266					}
267					break; // skip rest of ID3v2 header
268				}
269				if ($id3v2_majorversion == 2) {
270					// Frame ID  $xx xx xx (three characters)
271					// Size      $xx xx xx (24-bit integer)
272					// Flags     $xx xx
273
274					$frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
275					$framedata    = substr($framedata, 6);    // and leave the rest in $framedata
276					$frame_name   = substr($frame_header, 0, 3);
277					$frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
278					$frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
279
280				} elseif ($id3v2_majorversion > 2) {
281
282					// Frame ID  $xx xx xx xx (four characters)
283					// Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
284					// Flags     $xx xx
285
286					$frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
287					$framedata    = substr($framedata, 10);    // and leave the rest in $framedata
288
289					$frame_name = substr($frame_header, 0, 4);
290					if ($id3v2_majorversion == 3) {
291						$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
292					} else { // ID3v2.4+
293						$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
294					}
295
296					if ($frame_size < (strlen($framedata) + 4)) {
297						$nextFrameID = substr($framedata, $frame_size, 4);
298						if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
299							// next frame is OK
300						} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
301							// MP3ext known broken frames - "ok" for the purposes of this test
302						} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
303							$info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
304							$id3v2_majorversion = 3;
305							$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
306						}
307					}
308
309
310					$frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
311				}
312
313				if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
314					// padding encountered
315
316					$thisfile_id3v2['padding']['start']  = $framedataoffset;
317					$thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
318					$thisfile_id3v2['padding']['valid']  = true;
319
320					$len = strlen($framedata);
321					for ($i = 0; $i < $len; $i++) {
322						if ($framedata{$i} != "\x00") {
323							$thisfile_id3v2['padding']['valid'] = false;
324							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
325							$info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
326							break;
327						}
328					}
329					break; // skip rest of ID3v2 header
330				}
331
332				if ($frame_name == 'COM ') {
333					$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
334					$frame_name = 'COMM';
335				}
336				if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
337
338					unset($parsedFrame);
339					$parsedFrame['frame_name']      = $frame_name;
340					$parsedFrame['frame_flags_raw'] = $frame_flags;
341					$parsedFrame['data']            = substr($framedata, 0, $frame_size);
342					$parsedFrame['datalength']      = getid3_lib::CastAsInt($frame_size);
343					$parsedFrame['dataoffset']      = $framedataoffset;
344
345					$this->ParseID3v2Frame($parsedFrame);
346					$thisfile_id3v2[$frame_name][] = $parsedFrame;
347
348					$framedata = substr($framedata, $frame_size);
349
350				} else { // invalid frame length or FrameID
351
352					if ($frame_size <= strlen($framedata)) {
353
354						if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
355
356							// next frame is valid, just skip the current frame
357							$framedata = substr($framedata, $frame_size);
358							$info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
359
360						} else {
361
362							// next frame is invalid too, abort processing
363							//unset($framedata);
364							$framedata = null;
365							$info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
366
367						}
368
369					} elseif ($frame_size == strlen($framedata)) {
370
371						// this is the last frame, just skip
372						$info['warning'][] = 'This was the last ID3v2 frame.';
373
374					} else {
375
376						// next frame is invalid too, abort processing
377						//unset($framedata);
378						$framedata = null;
379						$info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
380
381					}
382					if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
383
384						switch ($frame_name) {
385							case "\x00\x00".'MP':
386							case "\x00".'MP3':
387							case ' MP3':
388							case 'MP3e':
389							case "\x00".'MP':
390							case ' MP':
391							case 'MP3':
392								$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
393								break;
394
395							default:
396								$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
397								break;
398						}
399
400					} elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
401
402						$info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).';
403
404					} else {
405
406						$info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
407
408					}
409
410				}
411				$framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
412
413			}
414
415		}
416
417
418	//    Footer
419
420	//    The footer is a copy of the header, but with a different identifier.
421	//        ID3v2 identifier           "3DI"
422	//        ID3v2 version              $04 00
423	//        ID3v2 flags                %abcd0000
424	//        ID3v2 size             4 * %0xxxxxxx
425
426		if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
427			$footer = $this->fread(10);
428			if (substr($footer, 0, 3) == '3DI') {
429				$thisfile_id3v2['footer'] = true;
430				$thisfile_id3v2['majorversion_footer'] = ord($footer{3});
431				$thisfile_id3v2['minorversion_footer'] = ord($footer{4});
432			}
433			if ($thisfile_id3v2['majorversion_footer'] <= 4) {
434				$id3_flags = ord(substr($footer{5}));
435				$thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
436				$thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
437				$thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
438				$thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
439
440				$thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
441			}
442		} // end footer
443
444		if (isset($thisfile_id3v2['comments']['genre'])) {
445			foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
446				unset($thisfile_id3v2['comments']['genre'][$key]);
447				$thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
448			}
449		}
450
451		if (isset($thisfile_id3v2['comments']['track'])) {
452			foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
453				if (strstr($value, '/')) {
454					list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
455				}
456			}
457		}
458
459		if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
460			$thisfile_id3v2['comments']['year'] = array($matches[1]);
461		}
462
463
464		if (!empty($thisfile_id3v2['TXXX'])) {
465			// MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
466			foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
467				switch ($txxx_array['description']) {
468					case 'replaygain_track_gain':
469						if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
470							$info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
471						}
472						break;
473					case 'replaygain_track_peak':
474						if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
475							$info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
476						}
477						break;
478					case 'replaygain_album_gain':
479						if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
480							$info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
481						}
482						break;
483				}
484			}
485		}
486
487
488		// Set avdataoffset
489		$info['avdataoffset'] = $thisfile_id3v2['headerlength'];
490		if (isset($thisfile_id3v2['footer'])) {
491			$info['avdataoffset'] += 10;
492		}
493
494		return true;
495	}
496
497
498	public function ParseID3v2GenreString($genrestring) {
499		// Parse genres into arrays of genreName and genreID
500		// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
501		// ID3v2.4.x: '21' $00 'Eurodisco' $00
502		$clean_genres = array();
503		if (strpos($genrestring, "\x00") === false) {
504			$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
505		}
506		$genre_elements = explode("\x00", $genrestring);
507		foreach ($genre_elements as $element) {
508			$element = trim($element);
509			if ($element) {
510				if (preg_match('#^[0-9]{1,3}#', $element)) {
511					$clean_genres[] = getid3_id3v1::LookupGenreName($element);
512				} else {
513					$clean_genres[] = str_replace('((', '(', $element);
514				}
515			}
516		}
517		return $clean_genres;
518	}
519
520
521	public function ParseID3v2Frame(&$parsedFrame) {
522
523		// shortcuts
524		$info = &$this->getid3->info;
525		$id3v2_majorversion = $info['id3v2']['majorversion'];
526
527		$parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
528		if (empty($parsedFrame['framenamelong'])) {
529			unset($parsedFrame['framenamelong']);
530		}
531		$parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
532		if (empty($parsedFrame['framenameshort'])) {
533			unset($parsedFrame['framenameshort']);
534		}
535
536		if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
537			if ($id3v2_majorversion == 3) {
538				//    Frame Header Flags
539				//    %abc00000 %ijk00000
540				$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
541				$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
542				$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
543				$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
544				$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
545				$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
546
547			} elseif ($id3v2_majorversion == 4) {
548				//    Frame Header Flags
549				//    %0abc0000 %0h00kmnp
550				$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
551				$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
552				$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
553				$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
554				$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
555				$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
556				$parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
557				$parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
558
559				// Frame-level de-unsynchronisation - ID3v2.4
560				if ($parsedFrame['flags']['Unsynchronisation']) {
561					$parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
562				}
563
564				if ($parsedFrame['flags']['DataLengthIndicator']) {
565					$parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
566					$parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
567				}
568			}
569
570			//    Frame-level de-compression
571			if ($parsedFrame['flags']['compression']) {
572				$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
573				if (!function_exists('gzuncompress')) {
574					$info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
575				} else {
576					if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
577					//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
578						$parsedFrame['data'] = $decompresseddata;
579						unset($decompresseddata);
580					} else {
581						$info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
582					}
583				}
584			}
585		}
586
587		if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
588			if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
589				$info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
590			}
591		}
592
593		if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
594
595			$warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
596			switch ($parsedFrame['frame_name']) {
597				case 'WCOM':
598					$warning .= ' (this is known to happen with files tagged by RioPort)';
599					break;
600
601				default:
602					break;
603			}
604			$info['warning'][] = $warning;
605
606		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
607			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
608			//   There may be more than one 'UFID' frame in a tag,
609			//   but only one with the same 'Owner identifier'.
610			// <Header for 'Unique file identifier', ID: 'UFID'>
611			// Owner identifier        <text string> $00
612			// Identifier              <up to 64 bytes binary data>
613			$exploded = explode("\x00", $parsedFrame['data'], 2);
614			$parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
615			$parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : '');
616
617		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
618				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
619			//   There may be more than one 'TXXX' frame in each tag,
620			//   but only one with the same description.
621			// <Header for 'User defined text information frame', ID: 'TXXX'>
622			// Text encoding     $xx
623			// Description       <text string according to encoding> $00 (00)
624			// Value             <text string according to encoding>
625
626			$frame_offset = 0;
627			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
628
629			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
630				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
631			}
632			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
633			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
634				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
635			}
636			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
637			if (ord($frame_description) === 0) {
638				$frame_description = '';
639			}
640			$parsedFrame['encodingid']  = $frame_textencoding;
641			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
642
643			$parsedFrame['description'] = $frame_description;
644			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
645			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
646				$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
647				if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
648					$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
649				} else {
650					$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
651				}
652			}
653			//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
654
655
656		} elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
657			//   There may only be one text information frame of its kind in an tag.
658			// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
659			// excluding 'TXXX' described in 4.2.6.>
660			// Text encoding                $xx
661			// Information                  <text string(s) according to encoding>
662
663			$frame_offset = 0;
664			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
665			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
666				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
667			}
668
669			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
670
671			$parsedFrame['encodingid'] = $frame_textencoding;
672			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
673
674			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
675				// ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
676				// This of course breaks when an artist name contains slash character, e.g. "AC/DC"
677				// MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
678				// getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
679				switch ($parsedFrame['encoding']) {
680					case 'UTF-16':
681					case 'UTF-16BE':
682					case 'UTF-16LE':
683						$wordsize = 2;
684						break;
685					case 'ISO-8859-1':
686					case 'UTF-8':
687					default:
688						$wordsize = 1;
689						break;
690				}
691				$Txxx_elements = array();
692				$Txxx_elements_start_offset = 0;
693				for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
694					if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
695						$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
696						$Txxx_elements_start_offset = $i + $wordsize;
697					}
698				}
699				$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
700				foreach ($Txxx_elements as $Txxx_element) {
701					$string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
702					if (!empty($string)) {
703						$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
704					}
705				}
706				unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
707			}
708
709		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
710				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
711			//   There may be more than one 'WXXX' frame in each tag,
712			//   but only one with the same description
713			// <Header for 'User defined URL link frame', ID: 'WXXX'>
714			// Text encoding     $xx
715			// Description       <text string according to encoding> $00 (00)
716			// URL               <text string>
717
718			$frame_offset = 0;
719			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
720			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
721				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
722			}
723			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
724			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
725				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
726			}
727			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
728
729			if (ord($frame_description) === 0) {
730				$frame_description = '';
731			}
732			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
733
734			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
735			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
736				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
737			}
738			if ($frame_terminatorpos) {
739				// there are null bytes after the data - this is not according to spec
740				// only use data up to first null byte
741				$frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
742			} else {
743				// no null bytes following data, just use all data
744				$frame_urldata = (string) $parsedFrame['data'];
745			}
746
747			$parsedFrame['encodingid']  = $frame_textencoding;
748			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
749
750			$parsedFrame['url']         = $frame_urldata;
751			$parsedFrame['description'] = $frame_description;
752			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
753				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
754			}
755			unset($parsedFrame['data']);
756
757
758		} elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
759			//   There may only be one URL link frame of its kind in a tag,
760			//   except when stated otherwise in the frame description
761			// <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
762			// described in 4.3.2.>
763			// URL              <text string>
764
765			$parsedFrame['url'] = trim($parsedFrame['data']);
766			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
767				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
768			}
769			unset($parsedFrame['data']);
770
771
772		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
773				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
774			// http://id3.org/id3v2.3.0#sec4.4
775			//   There may only be one 'IPL' frame in each tag
776			// <Header for 'User defined URL link frame', ID: 'IPL'>
777			// Text encoding     $xx
778			// People list strings    <textstrings>
779
780			$frame_offset = 0;
781			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
782			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
783				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
784			}
785			$parsedFrame['encodingid'] = $frame_textencoding;
786			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
787			$parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
788
789			// http://www.getid3.org/phpBB3/viewtopic.php?t=1369
790			// "this tag typically contains null terminated strings, which are associated in pairs"
791			// "there are users that use the tag incorrectly"
792			$IPLS_parts = array();
793			if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
794				$IPLS_parts_unsorted = array();
795				if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
796					// UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
797					$thisILPS  = '';
798					for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
799						$twobytes = substr($parsedFrame['data_raw'], $i, 2);
800						if ($twobytes === "\x00\x00") {
801							$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
802							$thisILPS  = '';
803						} else {
804							$thisILPS .= $twobytes;
805						}
806					}
807					if (strlen($thisILPS) > 2) { // 2-byte BOM
808						$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
809					}
810				} else {
811					// ISO-8859-1 or UTF-8 or other single-byte-null character set
812					$IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
813				}
814				if (count($IPLS_parts_unsorted) == 1) {
815					// just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
816					foreach ($IPLS_parts_unsorted as $key => $value) {
817						$IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
818						$position = '';
819						foreach ($IPLS_parts_sorted as $person) {
820							$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
821						}
822					}
823				} elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
824					$position = '';
825					$person   = '';
826					foreach ($IPLS_parts_unsorted as $key => $value) {
827						if (($key % 2) == 0) {
828							$position = $value;
829						} else {
830							$person   = $value;
831							$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
832							$position = '';
833							$person   = '';
834						}
835					}
836				} else {
837					foreach ($IPLS_parts_unsorted as $key => $value) {
838						$IPLS_parts[] = array($value);
839					}
840				}
841
842			} else {
843				$IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
844			}
845			$parsedFrame['data'] = $IPLS_parts;
846
847			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
848				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
849			}
850
851
852		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
853				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
854			//   There may only be one 'MCDI' frame in each tag
855			// <Header for 'Music CD identifier', ID: 'MCDI'>
856			// CD TOC                <binary data>
857
858			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
859				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
860			}
861
862
863		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
864				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
865			//   There may only be one 'ETCO' frame in each tag
866			// <Header for 'Event timing codes', ID: 'ETCO'>
867			// Time stamp format    $xx
868			//   Where time stamp format is:
869			// $01  (32-bit value) MPEG frames from beginning of file
870			// $02  (32-bit value) milliseconds from beginning of file
871			//   Followed by a list of key events in the following format:
872			// Type of event   $xx
873			// Time stamp      $xx (xx ...)
874			//   The 'Time stamp' is set to zero if directly at the beginning of the sound
875			//   or after the previous event. All events MUST be sorted in chronological order.
876
877			$frame_offset = 0;
878			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
879
880			while ($frame_offset < strlen($parsedFrame['data'])) {
881				$parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1);
882				$parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']);
883				$parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
884				$frame_offset += 4;
885			}
886			unset($parsedFrame['data']);
887
888
889		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
890				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
891			//   There may only be one 'MLLT' frame in each tag
892			// <Header for 'Location lookup table', ID: 'MLLT'>
893			// MPEG frames between reference  $xx xx
894			// Bytes between reference        $xx xx xx
895			// Milliseconds between reference $xx xx xx
896			// Bits for bytes deviation       $xx
897			// Bits for milliseconds dev.     $xx
898			//   Then for every reference the following data is included;
899			// Deviation in bytes         %xxx....
900			// Deviation in milliseconds  %xxx....
901
902			$frame_offset = 0;
903			$parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
904			$parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
905			$parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
906			$parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
907			$parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
908			$parsedFrame['data'] = substr($parsedFrame['data'], 10);
909			while ($frame_offset < strlen($parsedFrame['data'])) {
910				$deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
911			}
912			$reference_counter = 0;
913			while (strlen($deviationbitstream) > 0) {
914				$parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
915				$parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
916				$deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
917				$reference_counter++;
918			}
919			unset($parsedFrame['data']);
920
921
922		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
923				  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
924			//   There may only be one 'SYTC' frame in each tag
925			// <Header for 'Synchronised tempo codes', ID: 'SYTC'>
926			// Time stamp format   $xx
927			// Tempo data          <binary data>
928			//   Where time stamp format is:
929			// $01  (32-bit value) MPEG frames from beginning of file
930			// $02  (32-bit value) milliseconds from beginning of file
931
932			$frame_offset = 0;
933			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
934			$timestamp_counter = 0;
935			while ($frame_offset < strlen($parsedFrame['data'])) {
936				$parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
937				if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
938					$parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
939				}
940				$parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
941				$frame_offset += 4;
942				$timestamp_counter++;
943			}
944			unset($parsedFrame['data']);
945
946
947		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
948				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
949			//   There may be more than one 'Unsynchronised lyrics/text transcription' frame
950			//   in each tag, but only one with the same language and content descriptor.
951			// <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
952			// Text encoding        $xx
953			// Language             $xx xx xx
954			// Content descriptor   <text string according to encoding> $00 (00)
955			// Lyrics/text          <full text string according to encoding>
956
957			$frame_offset = 0;
958			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
959			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
960				$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
961			}
962			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
963			$frame_offset += 3;
964			$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
965			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
966				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
967			}
968			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
969			if (ord($frame_description) === 0) {
970				$frame_description = '';
971			}
972			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
973
974			$parsedFrame['encodingid']   = $frame_textencoding;
975			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
976
977			$parsedFrame['data']         = $parsedFrame['data'];
978			$parsedFrame['language']     = $frame_language;
979			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
980			$parsedFrame['description']  = $frame_description;
981			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
982				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
983			}
984			unset($parsedFrame['data']);
985
986
987		} elseif ((($id3v2_majorversion >= 3) &&

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