PageRenderTime 476ms CodeModel.GetById 161ms app.highlight 158ms RepoModel.GetById 94ms app.codeStats 2ms

/getid3/module.tag.id3v2.php

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

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