1<?php
2
3/////////////////////////////////////////////////////////////////
4/// getID3() by James Heinrich <info@getid3.org>               //
5//  available at https://github.com/JamesHeinrich/getID3       //
6//            or https://www.getid3.org                        //
7//            or http://getid3.sourceforge.net                 //
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
17if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
18	exit;
19}
20getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
21
22class getid3_id3v2 extends getid3_handler
23{
24	public $StartingOffset = 0;
25
26	/**
27	 * @return bool
28	 */
29	public function Analyze() {
30		$info = &$this->getid3->info;
31
32		//    Overall tag structure:
33		//        +-----------------------------+
34		//        |      Header (10 bytes)      |
35		//        +-----------------------------+
36		//        |       Extended Header       |
37		//        | (variable length, OPTIONAL) |
38		//        +-----------------------------+
39		//        |   Frames (variable length)  |
40		//        +-----------------------------+
41		//        |           Padding           |
42		//        | (variable length, OPTIONAL) |
43		//        +-----------------------------+
44		//        | Footer (10 bytes, OPTIONAL) |
45		//        +-----------------------------+
46
47		//    Header
48		//        ID3v2/file identifier      "ID3"
49		//        ID3v2 version              $04 00
50		//        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
51		//        ID3v2 size             4 * %0xxxxxxx
52
53
54		// shortcuts
55		$info['id3v2']['header'] = true;
56		$thisfile_id3v2                  = &$info['id3v2'];
57		$thisfile_id3v2['flags']         =  array();
58		$thisfile_id3v2_flags            = &$thisfile_id3v2['flags'];
59
60
61		$this->fseek($this->StartingOffset);
62		$header = $this->fread(10);
63		if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
64
65			$thisfile_id3v2['majorversion'] = ord($header[3]);
66			$thisfile_id3v2['minorversion'] = ord($header[4]);
67
68			// shortcut
69			$id3v2_majorversion = &$thisfile_id3v2['majorversion'];
70
71		} else {
72
73			unset($info['id3v2']);
74			return false;
75
76		}
77
78		if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
79
80			$this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
81			return false;
82
83		}
84
85		$id3_flags = ord($header[5]);
86		switch ($id3v2_majorversion) {
87			case 2:
88				// %ab000000 in v2.2
89				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
90				$thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
91				break;
92
93			case 3:
94				// %abc00000 in v2.3
95				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
96				$thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
97				$thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
98				break;
99
100			case 4:
101				// %abcd0000 in v2.4
102				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
103				$thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
104				$thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
105				$thisfile_id3v2_flags['isfooter']    = (bool) ($id3_flags & 0x10); // d - Footer present
106				break;
107		}
108
109		$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
110
111		$thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
112		$thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
113
114
115
116		// create 'encoding' key - used by getid3::HandleAllTags()
117		// in ID3v2 every field can have it's own encoding type
118		// so force everything to UTF-8 so it can be handled consistantly
119		$thisfile_id3v2['encoding'] = 'UTF-8';
120
121
122	//    Frames
123
124	//        All ID3v2 frames consists of one frame header followed by one or more
125	//        fields containing the actual information. The header is always 10
126	//        bytes and laid out as follows:
127	//
128	//        Frame ID      $xx xx xx xx  (four characters)
129	//        Size      4 * %0xxxxxxx
130	//        Flags         $xx xx
131
132		$sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
133		if (!empty($thisfile_id3v2['exthead']['length'])) {
134			$sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
135		}
136		if (!empty($thisfile_id3v2_flags['isfooter'])) {
137			$sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
138		}
139		if ($sizeofframes > 0) {
140
141			$framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
142
143			//    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
144			if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
145				$framedata = $this->DeUnsynchronise($framedata);
146			}
147			//        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
148			//        of on tag level, making it easier to skip frames, increasing the streamability
149			//        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
150			//        there exists an unsynchronised frame, while the new unsynchronisation flag in
151			//        the frame header [S:4.1.2] indicates unsynchronisation.
152
153
154			//$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)
155			$framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
156
157
158			//    Extended Header
159			if (!empty($thisfile_id3v2_flags['exthead'])) {
160				$extended_header_offset = 0;
161
162				if ($id3v2_majorversion == 3) {
163
164					// v2.3 definition:
165					//Extended header size  $xx xx xx xx   // 32-bit integer
166					//Extended Flags        $xx xx
167					//     %x0000000 %00000000 // v2.3
168					//     x - CRC data present
169					//Size of padding       $xx xx xx xx
170
171					$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
172					$extended_header_offset += 4;
173
174					$thisfile_id3v2['exthead']['flag_bytes'] = 2;
175					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
176					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
177
178					$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
179
180					$thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
181					$extended_header_offset += 4;
182
183					if ($thisfile_id3v2['exthead']['flags']['crc']) {
184						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
185						$extended_header_offset += 4;
186					}
187					$extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
188
189				} elseif ($id3v2_majorversion == 4) {
190
191					// v2.4 definition:
192					//Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
193					//Number of flag bytes       $01
194					//Extended Flags             $xx
195					//     %0bcd0000 // v2.4
196					//     b - Tag is an update
197					//         Flag data length       $00
198					//     c - CRC data present
199					//         Flag data length       $05
200					//         Total frame CRC    5 * %0xxxxxxx
201					//     d - Tag restrictions
202					//         Flag data length       $01
203
204					$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
205					$extended_header_offset += 4;
206
207					$thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
208					$extended_header_offset += 1;
209
210					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
211					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
212
213					$thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
214					$thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
215					$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
216
217					if ($thisfile_id3v2['exthead']['flags']['update']) {
218						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
219						$extended_header_offset += 1;
220					}
221
222					if ($thisfile_id3v2['exthead']['flags']['crc']) {
223						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
224						$extended_header_offset += 1;
225						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
226						$extended_header_offset += $ext_header_chunk_length;
227					}
228
229					if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
230						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
231						$extended_header_offset += 1;
232
233						// %ppqrrstt
234						$restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
235						$extended_header_offset += 1;
236						$thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
237						$thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
238						$thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
239						$thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
240						$thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
241
242						$thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize']  = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
243						$thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc']  = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
244						$thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
245						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
246						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
247					}
248
249					if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
250						$this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
251					}
252				}
253
254				$framedataoffset += $extended_header_offset;
255				$framedata = substr($framedata, $extended_header_offset);
256			} // end extended header
257
258
259			while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
260				if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
261					// insufficient room left in ID3v2 header for actual data - must be padding
262					$thisfile_id3v2['padding']['start']  = $framedataoffset;
263					$thisfile_id3v2['padding']['length'] = strlen($framedata);
264					$thisfile_id3v2['padding']['valid']  = true;
265					for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
266						if ($framedata[$i] != "\x00") {
267							$thisfile_id3v2['padding']['valid'] = false;
268							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
269							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
270							break;
271						}
272					}
273					break; // skip rest of ID3v2 header
274				}
275				$frame_header = null;
276				$frame_name   = null;
277				$frame_size   = null;
278				$frame_flags  = null;
279				if ($id3v2_majorversion == 2) {
280					// Frame ID  $xx xx xx (three characters)
281					// Size      $xx xx xx (24-bit integer)
282					// Flags     $xx xx
283
284					$frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
285					$framedata    = substr($framedata, 6);    // and leave the rest in $framedata
286					$frame_name   = substr($frame_header, 0, 3);
287					$frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
288					$frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
289
290				} elseif ($id3v2_majorversion > 2) {
291
292					// Frame ID  $xx xx xx xx (four characters)
293					// Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
294					// Flags     $xx xx
295
296					$frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
297					$framedata    = substr($framedata, 10);    // and leave the rest in $framedata
298
299					$frame_name = substr($frame_header, 0, 4);
300					if ($id3v2_majorversion == 3) {
301						$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
302					} else { // ID3v2.4+
303						$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
304					}
305
306					if ($frame_size < (strlen($framedata) + 4)) {
307						$nextFrameID = substr($framedata, $frame_size, 4);
308						if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
309							// next frame is OK
310						} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
311							// MP3ext known broken frames - "ok" for the purposes of this test
312						} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
313							$this->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');
314							$id3v2_majorversion = 3;
315							$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
316						}
317					}
318
319
320					$frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
321				}
322
323				if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
324					// padding encountered
325
326					$thisfile_id3v2['padding']['start']  = $framedataoffset;
327					$thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
328					$thisfile_id3v2['padding']['valid']  = true;
329
330					$len = strlen($framedata);
331					for ($i = 0; $i < $len; $i++) {
332						if ($framedata[$i] != "\x00") {
333							$thisfile_id3v2['padding']['valid'] = false;
334							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
335							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
336							break;
337						}
338					}
339					break; // skip rest of ID3v2 header
340				}
341
342				if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
343					$this->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", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
344					$frame_name = $iTunesBrokenFrameNameFixed;
345				}
346				if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
347
348					unset($parsedFrame);
349					$parsedFrame['frame_name']      = $frame_name;
350					$parsedFrame['frame_flags_raw'] = $frame_flags;
351					$parsedFrame['data']            = substr($framedata, 0, $frame_size);
352					$parsedFrame['datalength']      = getid3_lib::CastAsInt($frame_size);
353					$parsedFrame['dataoffset']      = $framedataoffset;
354
355					$this->ParseID3v2Frame($parsedFrame);
356					$thisfile_id3v2[$frame_name][] = $parsedFrame;
357
358					$framedata = substr($framedata, $frame_size);
359
360				} else { // invalid frame length or FrameID
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							$this->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							$this->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						$this->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						$this->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								$this->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								$this->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						$this->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						$this->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
428	//    Footer
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		if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
437			$footer = $this->fread(10);
438			if (substr($footer, 0, 3) == '3DI') {
439				$thisfile_id3v2['footer'] = true;
440				$thisfile_id3v2['majorversion_footer'] = ord($footer[3]);
441				$thisfile_id3v2['minorversion_footer'] = ord($footer[4]);
442			}
443			if ($thisfile_id3v2['majorversion_footer'] <= 4) {
444				$id3_flags = ord($footer[5]);
445				$thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
446				$thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
447				$thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
448				$thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
449
450				$thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
451			}
452		} // end footer
453
454		if (isset($thisfile_id3v2['comments']['genre'])) {
455			$genres = array();
456			foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
457				foreach ($this->ParseID3v2GenreString($value) as $genre) {
458					$genres[] = $genre;
459				}
460			}
461			$thisfile_id3v2['comments']['genre'] = array_unique($genres);
462			unset($key, $value, $genres, $genre);
463		}
464
465		if (isset($thisfile_id3v2['comments']['track_number'])) {
466			foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) {
467				if (strstr($value, '/')) {
468					list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]);
469				}
470			}
471		}
472
473		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)) {
474			$thisfile_id3v2['comments']['year'] = array($matches[1]);
475		}
476
477
478		if (!empty($thisfile_id3v2['TXXX'])) {
479			// MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
480			foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
481				switch ($txxx_array['description']) {
482					case 'replaygain_track_gain':
483						if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
484							$info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
485						}
486						break;
487					case 'replaygain_track_peak':
488						if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
489							$info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
490						}
491						break;
492					case 'replaygain_album_gain':
493						if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
494							$info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
495						}
496						break;
497				}
498			}
499		}
500
501
502		// Set avdataoffset
503		$info['avdataoffset'] = $thisfile_id3v2['headerlength'];
504		if (isset($thisfile_id3v2['footer'])) {
505			$info['avdataoffset'] += 10;
506		}
507
508		return true;
509	}
510
511	/**
512	 * @param string $genrestring
513	 *
514	 * @return array
515	 */
516	public function ParseID3v2GenreString($genrestring) {
517		// Parse genres into arrays of genreName and genreID
518		// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
519		// ID3v2.4.x: '21' $00 'Eurodisco' $00
520		$clean_genres = array();
521
522		// hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
523		if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
524			// note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
525			// replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
526			if (strpos($genrestring, '/') !== false) {
527				$LegitimateSlashedGenreList = array(  // https://github.com/JamesHeinrich/getID3/issues/223
528					'Pop/Funk',    // ID3v1 genre #62 - https://en.wikipedia.org/wiki/ID3#standard
529					'Cut-up/DJ',   // Discogs - https://www.discogs.com/style/cut-up/dj
530					'RnB/Swing',   // Discogs - https://www.discogs.com/style/rnb/swing
531					'Funk / Soul', // Discogs (note spaces) - https://www.discogs.com/genre/funk+%2F+soul
532				);
533				$genrestring = str_replace('/', "\x00", $genrestring);
534				foreach ($LegitimateSlashedGenreList as $SlashedGenre) {
535					$genrestring = str_ireplace(str_replace('/', "\x00", $SlashedGenre), $SlashedGenre, $genrestring);
536				}
537			}
538
539			// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
540			if (strpos($genrestring, ';') !== false) {
541				$genrestring = str_replace(';', "\x00", $genrestring);
542			}
543		}
544
545
546		if (strpos($genrestring, "\x00") === false) {
547			$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
548		}
549
550		$genre_elements = explode("\x00", $genrestring);
551		foreach ($genre_elements as $element) {
552			$element = trim($element);
553			if ($element) {
554				if (preg_match('#^[0-9]{1,3}$#', $element)) {
555					$clean_genres[] = getid3_id3v1::LookupGenreName($element);
556				} else {
557					$clean_genres[] = str_replace('((', '(', $element);
558				}
559			}
560		}
561		return $clean_genres;
562	}
563
564	/**
565	 * @param array $parsedFrame
566	 *
567	 * @return bool
568	 */
569	public function ParseID3v2Frame(&$parsedFrame) {
570
571		// shortcuts
572		$info = &$this->getid3->info;
573		$id3v2_majorversion = $info['id3v2']['majorversion'];
574
575		$parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
576		if (empty($parsedFrame['framenamelong'])) {
577			unset($parsedFrame['framenamelong']);
578		}
579		$parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
580		if (empty($parsedFrame['framenameshort'])) {
581			unset($parsedFrame['framenameshort']);
582		}
583
584		if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
585			if ($id3v2_majorversion == 3) {
586				//    Frame Header Flags
587				//    %abc00000 %ijk00000
588				$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
589				$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
590				$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
591				$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
592				$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
593				$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
594
595			} elseif ($id3v2_majorversion == 4) {
596				//    Frame Header Flags
597				//    %0abc0000 %0h00kmnp
598				$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
599				$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
600				$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
601				$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
602				$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
603				$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
604				$parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
605				$parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
606
607				// Frame-level de-unsynchronisation - ID3v2.4
608				if ($parsedFrame['flags']['Unsynchronisation']) {
609					$parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
610				}
611
612				if ($parsedFrame['flags']['DataLengthIndicator']) {
613					$parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
614					$parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
615				}
616			}
617
618			//    Frame-level de-compression
619			if ($parsedFrame['flags']['compression']) {
620				$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
621				if (!function_exists('gzuncompress')) {
622					$this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
623				} else {
624					if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
625					//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
626						$parsedFrame['data'] = $decompresseddata;
627						unset($decompresseddata);
628					} else {
629						$this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
630					}
631				}
632			}
633		}
634
635		if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
636			if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
637				$this->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');
638			}
639		}
640
641		if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
642
643			$warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
644			switch ($parsedFrame['frame_name']) {
645				case 'WCOM':
646					$warning .= ' (this is known to happen with files tagged by RioPort)';
647					break;
648
649				default:
650					break;
651			}
652			$this->warning($warning);
653
654		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
655			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
656			//   There may be more than one 'UFID' frame in a tag,
657			//   but only one with the same 'Owner identifier'.
658			// <Header for 'Unique file identifier', ID: 'UFID'>
659			// Owner identifier        <text string> $00
660			// Identifier              <up to 64 bytes binary data>
661			$exploded = explode("\x00", $parsedFrame['data'], 2);
662			$parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
663			$parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : '');
664
665		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
666				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
667			//   There may be more than one 'TXXX' frame in each tag,
668			//   but only one with the same description.
669			// <Header for 'User defined text information frame', ID: 'TXXX'>
670			// Text encoding     $xx
671			// Description       <text string according to encoding> $00 (00)
672			// Value             <text string according to encoding>
673
674			$frame_offset = 0;
675			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
676			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
677			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
678				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
679				$frame_textencoding_terminator = "\x00";
680			}
681			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
682			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
683				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
684			}
685			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
686			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
687			$parsedFrame['encodingid']  = $frame_textencoding;
688			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
689
690			$parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description']));
691			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
692			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
693			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
694				$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
695				if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
696					$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
697				} else {
698					$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
699				}
700			}
701			//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
702
703
704		} elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame
705			//   There may only be one text information frame of its kind in an tag.
706			// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
707			// excluding 'TXXX' described in 4.2.6.>
708			// Text encoding                $xx
709			// Information                  <text string(s) according to encoding>
710
711			$frame_offset = 0;
712			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
713			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
714				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
715			}
716
717			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
718			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
719
720			$parsedFrame['encodingid'] = $frame_textencoding;
721			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
722			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
723				// ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
724				// This of course breaks when an artist name contains slash character, e.g. "AC/DC"
725				// MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
726				// getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
727				switch ($parsedFrame['encoding']) {
728					case 'UTF-16':
729					case 'UTF-16BE':
730					case 'UTF-16LE':
731						$wordsize = 2;
732						break;
733					case 'ISO-8859-1':
734					case 'UTF-8':
735					default:
736						$wordsize = 1;
737						break;
738				}
739				$Txxx_elements = array();
740				$Txxx_elements_start_offset = 0;
741				for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
742					if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
743						$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
744						$Txxx_elements_start_offset = $i + $wordsize;
745					}
746				}
747				$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
748				foreach ($Txxx_elements as $Txxx_element) {
749					$string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
750					if (!empty($string)) {
751						$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
752					}
753				}
754				unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
755			}
756
757		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
758				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
759			//   There may be more than one 'WXXX' frame in each tag,
760			//   but only one with the same description
761			// <Header for 'User defined URL link frame', ID: 'WXXX'>
762			// Text encoding     $xx
763			// Description       <text string according to encoding> $00 (00)
764			// URL               <text string>
765
766			$frame_offset = 0;
767			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
768			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
769			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
770				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
771				$frame_textencoding_terminator = "\x00";
772			}
773			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
774			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
775				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
776			}
777			$parsedFrame['encodingid']  = $frame_textencoding;
778			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
779			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);           // according to the frame text encoding
780			$parsedFrame['url']         = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1
781			$parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator);
782			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
783
784			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
785				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
786			}
787			unset($parsedFrame['data']);
788
789
790		} elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames
791			//   There may only be one URL link frame of its kind in a tag,
792			//   except when stated otherwise in the frame description
793			// <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
794			// described in 4.3.2.>
795			// URL              <text string>
796
797			$parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1
798			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
799				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
800			}
801			unset($parsedFrame['data']);
802
803
804		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
805				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
806			// http://id3.org/id3v2.3.0#sec4.4
807			//   There may only be one 'IPL' frame in each tag
808			// <Header for 'User defined URL link frame', ID: 'IPL'>
809			// Text encoding     $xx
810			// People list strings    <textstrings>
811
812			$frame_offset = 0;
813			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
814			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
815				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
816			}
817			$parsedFrame['encodingid'] = $frame_textencoding;
818			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
819			$parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);
820
821			// https://www.getid3.org/phpBB3/viewtopic.php?t=1369
822			// "this tag typically contains null terminated strings, which are associated in pairs"
823			// "there are users that use the tag incorrectly"
824			$IPLS_parts = array();
825			if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
826				$IPLS_parts_unsorted = array();
827				if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
828					// 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
829					$thisILPS  = '';
830					for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
831						$twobytes = substr($parsedFrame['data_raw'], $i, 2);
832						if ($twobytes === "\x00\x00") {
833							$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
834							$thisILPS  = '';
835						} else {
836							$thisILPS .= $twobytes;
837						}
838					}
839					if (strlen($thisILPS) > 2) { // 2-byte BOM
840						$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
841					}
842				} else {
843					// ISO-8859-1 or UTF-8 or other single-byte-null character set
844					$IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
845				}
846				if (count($IPLS_parts_unsorted) == 1) {
847					// just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
848					foreach ($IPLS_parts_unsorted as $key => $value) {
849						$IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
850						$position = '';
851						foreach ($IPLS_parts_sorted as $person) {
852							$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
853						}
854					}
855				} elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
856					$position = '';
857					$person   = '';
858					foreach ($IPLS_parts_unsorted as $key => $value) {
859						if (($key % 2) == 0) {
860							$position = $value;
861						} else {
862							$person   = $value;
863							$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
864							$position = '';
865							$person   = '';
866						}
867					}
868				} else {
869					foreach ($IPLS_parts_unsorted as $key => $value) {
870						$IPLS_parts[] = array($value);
871					}
872				}
873
874			} else {
875				$IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
876			}
877			$parsedFrame['data'] = $IPLS_parts;
878
879			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
880				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
881			}
882
883
884		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
885				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
886			//   There may only be one 'MCDI' frame in each tag
887			// <Header for 'Music CD identifier', ID: 'MCDI'>
888			// CD TOC                <binary data>
889
890			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
891				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
892			}
893
894
895		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
896				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
897			//   There may only be one 'ETCO' frame in each tag
898			// <Header for 'Event timing codes', ID: 'ETCO'>
899			// Time stamp format    $xx
900			//   Where time stamp format is:
901			// $01  (32-bit value) MPEG frames from beginning of file
902			// $02  (32-bit value) milliseconds from beginning of file
903			//   Followed by a list of key events in the following format:
904			// Type of event   $xx
905			// Time stamp      $xx (xx ...)
906			//   The 'Time stamp' is set to zero if directly at the beginning of the sound
907			//   or after the previous event. All events MUST be sorted in chronological order.
908
909			$frame_offset = 0;
910			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
911
912			while ($frame_offset < strlen($parsedFrame['data'])) {
913				$parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1);
914				$parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']);
915				$parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
916				$frame_offset += 4;
917			}
918			unset($parsedFrame['data']);
919
920
921		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
922				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
923			//   There may only be one 'MLLT' frame in each tag
924			// <Header for 'Location lookup table', ID: 'MLLT'>
925			// MPEG frames between reference  $xx xx
926			// Bytes between reference        $xx xx xx
927			// Milliseconds between reference $xx xx xx
928			// Bits for bytes deviation       $xx
929			// Bits for milliseconds dev.     $xx
930			//   Then for every reference the following data is included;
931			// Deviation in bytes         %xxx....
932			// Deviation in milliseconds  %xxx....
933
934			$frame_offset = 0;
935			$parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
936			$parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
937			$parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
938			$parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
939			$parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
940			$parsedFrame['data'] = substr($parsedFrame['data'], 10);
941			$deviationbitstream = '';
942			while ($frame_offset < strlen($parsedFrame['data'])) {
943				$deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
944			}
945			$reference_counter = 0;
946			while (strlen($deviationbitstream) > 0) {
947				$parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
948				$parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
949				$deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
950				$reference_counter++;
951			}
952			unset($parsedFrame['data']);
953
954
955		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
956				  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
957			//   There may only be one 'SYTC' frame in each tag
958			// <Header for 'Synchronised tempo codes', ID: 'SYTC'>
959			// Time stamp format   $xx
960			// Tempo data          <binary data>
961			//   Where time stamp format is:
962			// $01  (32-bit value) MPEG frames from beginning of file
963			// $02  (32-bit value) milliseconds from beginning of file
964
965			$frame_offset = 0;
966			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
967			$timestamp_counter = 0;
968			while ($frame_offset < strlen($parsedFrame['data'])) {
969				$parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
970				if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
971					$parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
972				}
973				$parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
974				$frame_offset += 4;
975				$timestamp_counter++;
976			}
977			unset($parsedFrame['data']);
978
979
980		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
981				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
982			//   There may be more than one 'Unsynchronised lyrics/text transcription' frame
983			//   in each tag, but only one with the same language and content descriptor.
984			// <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
985			// Text encoding        $xx
986			// Language             $xx xx xx
987			// Content descriptor   <text string according to encoding> $00 (00)
988			// Lyrics/text          <full text string according to encoding>
989
990			$frame_offset = 0;
991			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
992			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
993			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
994				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
995				$frame_textencoding_terminator = "\x00";
996			}
997			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
998			$frame_offset += 3;
999			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1000			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1001				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1002			}
1003			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1004			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1005			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1006			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
1007
1008			$parsedFrame['encodingid']   = $frame_textencoding;
1009			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1010
1011			$parsedFrame['language']     = $frame_language;
1012			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1013			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1014				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1015			}
1016			unset($parsedFrame['data']);
1017
1018
1019		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
1020				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
1021			//   There may be more than one 'SYLT' frame in each tag,
1022			//   but only one with the same language and content descriptor.
1023			// <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
1024			// Text encoding        $xx
1025			// Language             $xx xx xx
1026			// Time stamp format    $xx
1027			//   $01  (32-bit value) MPEG frames from beginning of file
1028			//   $02  (32-bit value) milliseconds from beginning of file
1029			// Content type         $xx
1030			// Content descriptor   <text string according to encoding> $00 (00)
1031			//   Terminated text to be synced (typically a syllable)
1032			//   Sync identifier (terminator to above string)   $00 (00)
1033			//   Time stamp                                     $xx (xx ...)
1034
1035			$frame_offset = 0;
1036			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1037			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1038			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1039				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1040				$frame_textencoding_terminator = "\x00";
1041			}
1042			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1043			$frame_offset += 3;
1044			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1045			$parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1046			$parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1047			$parsedFrame['encodingid']      = $frame_textencoding;
1048			$parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding);
1049
1050			$parsedFrame['language']        = $frame_language;
1051			$parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);
1052
1053			$timestampindex = 0;
1054			$frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1055			while (strlen($frame_remainingdata)) {
1056				$frame_offset = 0;
1057				$frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
1058				if ($frame_terminatorpos === false) {
1059					$frame_remainingdata = '';
1060				} else {
1061					if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1062						$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1063					}
1064					$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1065
1066					$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
1067					if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) {
1068						// timestamp probably omitted for first data item
1069					} else {
1070						$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1071						$frame_remainingdata = substr($frame_remainingdata, 4);
1072					}
1073					$timestampindex++;
1074				}
1075			}
1076			unset($parsedFrame['data']);
1077
1078
1079		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
1080				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
1081			//   There may be more than one comment frame in each tag,
1082			//   but only one with the same language and content descriptor.
1083			// <Header for 'Comment', ID: 'COMM'>
1084			// Text encoding          $xx
1085			// Language               $xx xx xx
1086			// Short content descrip. <text string according to encoding> $00 (00)
1087			// The actual text        <full text string according to encoding>
1088
1089			if (strlen($parsedFrame['data']) < 5) {
1090
1091				$this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
1092
1093			} else {
1094
1095				$frame_offset = 0;
1096				$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1097				$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1098				if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1099					$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1100					$frame_textencoding_terminator = "\x00";
1101				}
1102				$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1103				$frame_offset += 3;
1104				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1105				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1106					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1107				}
1108				$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1109				$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1110				$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1111				$frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator);
1112
1113				$parsedFrame['encodingid']   = $frame_textencoding;
1114				$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1115
1116				$parsedFrame['language']     = $frame_language;
1117				$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1118				$parsedFrame['data']         = $frame_text;
1119				if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1120					$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
1121					if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
1122						$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1123					} else {
1124						$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1125					}
1126				}
1127
1128			}
1129
1130		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1131			//   There may be more than one 'RVA2' frame in each tag,
1132			//   but only one with the same identification string
1133			// <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1134			// Identification          <text string> $00
1135			//   The 'identification' string is used to identify the situation and/or
1136			//   device where this adjustment should apply. The following is then
1137			//   repeated for every channel:
1138			// Type of channel         $xx
1139			// Volume adjustment       $xx xx
1140			// Bits representing peak  $xx
1141			// Peak volume             $xx (xx ...)
1142
1143			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1144			$frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1145			if (ord($frame_idstring) === 0) {
1146				$frame_idstring = '';
1147			}
1148			$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1149			$parsedFrame['description'] = $frame_idstring;
1150			$RVA2channelcounter = 0;
1151			while (strlen($frame_remainingdata) >= 5) {
1152				$frame_offset = 0;
1153				$frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1154				$parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid;
1155				$parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1156				$parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1157				$frame_offset += 2;
1158				$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1159				if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1160					$this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
1161					break;
1162				}
1163				$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1164				$parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1165				$frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1166				$RVA2channelcounter++;
1167			}
1168			unset($parsedFrame['data']);
1169
1170
1171		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
1172				  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
1173			//   There may only be one 'RVA' frame in each tag
1174			// <Header for 'Relative volume adjustment', ID: 'RVA'>
1175			// ID3v2.2 => Increment/decrement     %000000ba
1176			// ID3v2.3 => Increment/decrement     %00fedcba
1177			// Bits used for volume descr.        $xx
1178			// Relative volume change, right      $xx xx (xx ...) // a
1179			// Relative volume change, left       $xx xx (xx ...) // b
1180			// Peak volume right                  $xx xx (xx ...)
1181			// Peak volume left                   $xx xx (xx ...)
1182			//   ID3v2.3 only, optional (not present in ID3v2.2):
1183			// Relative volume change, right back $xx xx (xx ...) // c
1184			// Relative volume change, left back  $xx xx (xx ...) // d
1185			// Peak volume right back             $xx xx (xx ...)
1186			// Peak volume left back              $xx xx (xx ...)
1187			//   ID3v2.3 only, optional (not present in ID3v2.2):
1188			// Relative volume change, center     $xx xx (xx ...) // e
1189			// Peak volume center                 $xx xx (xx ...)
1190			//   ID3v2.3 only, optional (not present in ID3v2.2):
1191			// Relative volume change, bass       $xx xx (xx ...) // f
1192			// Peak volume bass                   $xx xx (xx ...)
1193
1194			$frame_offset = 0;
1195			$frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1196			$parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1197			$parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1);
1198			$parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1199			$frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1200			$parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1201			if ($parsedFrame['incdec']['right'] === false) {
1202				$parsedFrame['volumechange']['right'] *= -1;
1203			}
1204			$frame_offset += $frame_bytesvolume;
1205			$parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1206			if ($parsedFrame['incdec']['left'] === false) {
1207				$parsedFrame['volumechange']['left'] *= -1;
1208			}
1209			$frame_offset += $frame_bytesvolume;
1210			$parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1211			$frame_offset += $frame_bytesvolume;
1212			$parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1213			$frame_offset += $frame_bytesvolume;
1214			if ($id3v2_majorversion == 3) {
1215				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1216				if (strlen($parsedFrame['data']) > 0) {
1217					$parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1218					$parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1);
1219					$parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1220					if ($parsedFrame['incdec']['rightrear'] === false) {
1221						$parsedFrame['volumechange']['rightrear'] *= -1;
1222					}
1223					$frame_offset += $frame_bytesvolume;
1224					$parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1225					if ($parsedFrame['incdec']['leftrear'] === false) {
1226						$parsedFrame['volumechange']['leftrear'] *= -1;
1227					}
1228					$frame_offset += $frame_bytesvolume;
1229					$parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1230					$frame_offset += $frame_bytesvolume;
1231					$parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1232					$frame_offset += $frame_bytesvolume;
1233				}
1234				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1235				if (strlen($parsedFrame['data']) > 0) {
1236					$parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1237					$parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1238					if ($parsedFrame['incdec']['center'] === false) {
1239						$parsedFrame['volumechange']['center'] *= -1;
1240					}
1241					$frame_offset += $frame_bytesvolume;
1242					$parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1243					$frame_offset += $frame_bytesvolume;
1244				}
1245				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1246				if (strlen($parsedFrame['data']) > 0) {
1247					$parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1248					$parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1249					if ($parsedFrame['incdec']['bass'] === false) {
1250						$parsedFrame['volumechange']['bass'] *= -1;
1251					}
1252					$frame_offset += $frame_bytesvolume;
1253					$parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1254					$frame_offset += $frame_bytesvolume;
1255				}
1256			}
1257			unset($parsedFrame['data']);
1258
1259
1260		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
1261			//   There may be more than one 'EQU2' frame in each tag,
1262			//   but only one with the same identification string
1263			// <Header of 'Equalisation (2)', ID: 'EQU2'>
1264			// Interpolation method  $xx
1265			//   $00  Band
1266			//   $01  Linear
1267			// Identification        <text string> $00
1268			//   The following is then repeated for every adjustment point
1269			// Frequency          $xx xx
1270			// Volume adjustment  $xx xx
1271
1272			$frame_offset = 0;
1273			$frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1274			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1275			$frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1276			if (ord($frame_idstring) === 0) {
1277				$frame_idstring = '';
1278			}
1279			$parsedFrame['description'] = $frame_idstring;
1280			$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1281			while (strlen($frame_remainingdata)) {
1282				$frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1283				$parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1284				$frame_remainingdata = substr($frame_remainingdata, 4);
1285			}
1286			$parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1287			unset($parsedFrame['data']);
1288
1289
1290		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
1291				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
1292			//   There may only be one 'EQUA' frame in each tag
1293			// <Header for 'Relative volume adjustment', ID: 'EQU'>
1294			// Adjustment bits    $xx
1295			//   This is followed by 2 bytes + ('adjustment bits' rounded up to the
1296			//   nearest byte) for every equalisation band in the following format,
1297			//   giving a frequency range of 0 - 32767Hz:
1298			// Increment/decrement   %x (MSB of the Frequency)
1299			// Frequency             (lower 15 bits)
1300			// Adjustment            $xx (xx ...)
1301
1302			$frame_offset = 0;
1303			$parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1304			$frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1305
1306			$frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1307			while (strlen($frame_remainingdata) > 0) {
1308				$frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1309				$frame_incdec    = (bool) substr($frame_frequencystr, 0, 1);
1310				$frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1311				$parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1312				$parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1313				if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1314					$parsedFrame[$frame_frequency]['adjustment'] *= -1;
1315				}
1316				$frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1317			}
1318			unset($parsedFrame['data']);
1319
1320
1321		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
1322				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
1323			//   There may only be one 'RVRB' frame in each tag.
1324			// <Header for 'Reverb', ID: 'RVRB'>
1325			// Reverb left (ms)                 $xx xx
1326			// Reverb right (ms)                $xx xx
1327			// Reverb bounces, left             $xx
1328			// Reverb bounces, right            $xx
1329			// Reverb feedback, left to left    $xx
1330			// Reverb feedback, left to right   $xx
1331			// Reverb feedback, right to right  $xx
1332			// Reverb feedback, right to left   $xx
1333			// Premix left to right             $xx
1334			// Premix right to left             $xx
1335
1336			$frame_offset = 0;
1337			$parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1338			$frame_offset += 2;
1339			$parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1340			$frame_offset += 2;
1341			$parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1342			$parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1343			$parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1344			$parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1345			$parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1346			$parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1347			$parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1348			$parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1349			unset($parsedFrame['data']);
1350
1351
1352		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
1353				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
1354			//   There may be several pictures attached to one file,
1355			//   each in their individual 'APIC' frame, but only one
1356			//   with the same content descriptor
1357			// <Header for 'Attached picture', ID: 'APIC'>
1358			// Text encoding      $xx
1359			// ID3v2.3+ => MIME type          <text string> $00
1360			// ID3v2.2  => Image format       $xx xx xx
1361			// Picture type       $xx
1362			// Description        <text string according to encoding> $00 (00)
1363			// Picture data       <binary data>
1364
1365			$frame_offset = 0;
1366			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1367			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1368			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1369				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1370				$frame_textencoding_terminator = "\x00";
1371			}
1372
1373			if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1374				$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1375				if (strtolower($frame_imagetype) == 'ima') {
1376					// complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1377					// MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
1378					$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1379					$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1380					if (ord($frame_mimetype) === 0) {
1381						$frame_mimetype = '';
1382					}
1383					$frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1384					if ($frame_imagetype == 'JPEG') {
1385						$frame_imagetype = 'JPG';
1386					}
1387					$frame_offset = $frame_terminatorpos + strlen("\x00");
1388				} else {
1389					$frame_offset += 3;
1390				}
1391			}
1392			if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1393				$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1394				$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1395				if (ord($frame_mimetype) === 0) {
1396					$frame_mimetype = '';
1397				}
1398				$frame_offset = $frame_terminatorpos + strlen("\x00");
1399			}
1400
1401			$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1402
1403			if ($frame_offset >= $parsedFrame['datalength']) {
1404				$this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
1405			} else {
1406				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1407				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1408					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1409				}
1410				$parsedFrame['description']   = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1411				$parsedFrame['description']   = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1412				$parsedFrame['encodingid']    = $frame_textencoding;
1413				$parsedFrame['encoding']      = $this->TextEncodingNameLookup($frame_textencoding);
1414
1415				if ($id3v2_majorversion == 2) {
1416					$parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null;
1417				} else {
1418					$parsedFrame['mime']      = isset($frame_mimetype) ? $frame_mimetype : null;
1419				}
1420				$parsedFrame['picturetypeid'] = $frame_picturetype;
1421				$parsedFrame['picturetype']   = $this->APICPictureTypeLookup($frame_picturetype);
1422				$parsedFrame['data']          = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1423				$parsedFrame['datalength']    = strlen($parsedFrame['data']);
1424
1425				$parsedFrame['image_mime']    = '';
1426				$imageinfo = array();
1427				if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
1428					if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1429						$parsedFrame['image_mime']       = image_type_to_mime_type($imagechunkcheck[2]);
1430						if ($imagechunkcheck[0]) {
1431							$parsedFrame['image_width']  = $imagechunkcheck[0];
1432						}
1433						if ($imagechunkcheck[1]) {
1434							$parsedFrame['image_height'] = $imagechunkcheck[1];
1435						}
1436					}
1437				}
1438
1439				do {
1440					if ($this->getid3->option_save_attachments === false) {
1441						// skip entirely
1442						unset($parsedFrame['data']);
1443						break;
1444					}
1445					$dir = '';
1446					if ($this->getid3->option_save_attachments === true) {
1447						// great
1448/*
1449					} elseif (is_int($this->getid3->option_save_attachments)) {
1450						if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1451							// too big, skip
1452							$this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
1453							unset($parsedFrame['data']);
1454							break;
1455						}
1456*/
1457					} elseif (is_string($this->getid3->option_save_attachments)) {
1458						$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1459						if (!is_dir($dir) || !getID3::is_writable($dir)) {
1460							// cannot write, skip
1461							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
1462							unset($parsedFrame['data']);
1463							break;
1464						}
1465					}
1466					// if we get this far, must be OK
1467					if (is_string($this->getid3->option_save_attachments)) {
1468						$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1469						if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
1470							file_put_contents($destination_filename, $parsedFrame['data']);
1471						} else {
1472							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
1473						}
1474						$parsedFrame['data_filename'] = $destination_filename;
1475						unset($parsedFrame['data']);
1476					} else {
1477						if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1478							if (!isset($info['id3v2']['comments']['picture'])) {
1479								$info['id3v2']['comments']['picture'] = array();
1480							}
1481							$comments_picture_data = array();
1482							foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
1483								if (isset($parsedFrame[$picture_key])) {
1484									$comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
1485								}
1486							}
1487							$info['id3v2']['comments']['picture'][] = $comments_picture_data;
1488							unset($comments_picture_data);
1489						}
1490					}
1491				} while (false);
1492			}
1493
1494		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
1495				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
1496			//   There may be more than one 'GEOB' frame in each tag,
1497			//   but only one with the same content descriptor
1498			// <Header for 'General encapsulated object', ID: 'GEOB'>
1499			// Text encoding          $xx
1500			// MIME type              <text string> $00
1501			// Filename               <text string according to encoding> $00 (00)
1502			// Content description    <text string according to encoding> $00 (00)
1503			// Encapsulated object    <binary data>
1504
1505			$frame_offset = 0;
1506			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1507			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1508			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1509				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1510				$frame_textencoding_terminator = "\x00";
1511			}
1512			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1513			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1514			if (ord($frame_mimetype) === 0) {
1515				$frame_mimetype = '';
1516			}
1517			$frame_offset = $frame_terminatorpos + strlen("\x00");
1518
1519			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1520			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1521				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1522			}
1523			$frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1524			if (ord($frame_filename) === 0) {
1525				$frame_filename = '';
1526			}
1527			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1528
1529			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1530			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1531				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1532			}
1533			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1534			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1535			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1536
1537			$parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
1538			$parsedFrame['encodingid']  = $frame_textencoding;
1539			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
1540
1541			$parsedFrame['mime']        = $frame_mimetype;
1542			$parsedFrame['filename']    = $frame_filename;
1543			unset($parsedFrame['data']);
1544
1545
1546		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
1547				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
1548			//   There may only be one 'PCNT' frame in each tag.
1549			//   When the counter reaches all one's, one byte is inserted in
1550			//   front of the counter thus making the counter eight bits bigger
1551			// <Header for 'Play counter', ID: 'PCNT'>
1552			// Counter        $xx xx xx xx (xx ...)
1553
1554			$parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);
1555
1556
1557		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
1558				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
1559			//   There may be more than one 'POPM' frame in each tag,
1560			//   but only one with the same email address
1561			// <Header for 'Popularimeter', ID: 'POPM'>
1562			// Email to user   <text string> $00
1563			// Rating          $xx
1564			// Counter         $xx xx xx xx (xx ...)
1565
1566			$frame_offset = 0;
1567			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1568			$frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1569			if (ord($frame_emailaddress) === 0) {
1570				$frame_emailaddress = '';
1571			}
1572			$frame_offset = $frame_terminatorpos + strlen("\x00");
1573			$frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1574			$parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1575			$parsedFrame['email']   = $frame_emailaddress;
1576			$parsedFrame['rating']  = $frame_rating;
1577			unset($parsedFrame['data']);
1578
1579
1580		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
1581				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
1582			//   There may only be one 'RBUF' frame in each tag
1583			// <Header for 'Recommended buffer size', ID: 'RBUF'>
1584			// Buffer size               $xx xx xx
1585			// Embedded info flag        %0000000x
1586			// Offset to next tag        $xx xx xx xx
1587
1588			$frame_offset = 0;
1589			$parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1590			$frame_offset += 3;
1591
1592			$frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1593			$parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1594			$parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1595			unset($parsedFrame['data']);
1596
1597
1598		} elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
1599			//   There may be more than one 'CRM' frame in a tag,
1600			//   but only one with the same 'owner identifier'
1601			// <Header for 'Encrypted meta frame', ID: 'CRM'>
1602			// Owner identifier      <textstring> $00 (00)
1603			// Content/explanation   <textstring> $00 (00)
1604			// Encrypted datablock   <binary data>
1605
1606			$frame_offset = 0;
1607			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1608			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1609			$frame_offset = $frame_terminatorpos + strlen("\x00");
1610
1611			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1612			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1613			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1614			$frame_offset = $frame_terminatorpos + strlen("\x00");
1615
1616			$parsedFrame['ownerid']     = $frame_ownerid;
1617			$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1618			unset($parsedFrame['data']);
1619
1620
1621		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
1622				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
1623			//   There may be more than one 'AENC' frames in a tag,
1624			//   but only one with the same 'Owner identifier'
1625			// <Header for 'Audio encryption', ID: 'AENC'>
1626			// Owner identifier   <text string> $00
1627			// Preview start      $xx xx
1628			// Preview length     $xx xx
1629			// Encryption info    <binary data>
1630
1631			$frame_offset = 0;
1632			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1633			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1634			if (ord($frame_ownerid) === 0) {
1635				$frame_ownerid = '';
1636			}
1637			$frame_offset = $frame_terminatorpos + strlen("\x00");
1638			$parsedFrame['ownerid'] = $frame_ownerid;
1639			$parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1640			$frame_offset += 2;
1641			$parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1642			$frame_offset += 2;
1643			$parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1644			unset($parsedFrame['data']);
1645
1646
1647		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
1648				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {    // 4.22  LNK  Linked information
1649			//   There may be more than one 'LINK' frame in a tag,
1650			//   but only one with the same contents
1651			// <Header for 'Linked information', ID: 'LINK'>
1652			// ID3v2.3+ => Frame identifier   $xx xx xx xx
1653			// ID3v2.2  => Frame identifier   $xx xx xx
1654			// URL                            <text string> $00
1655			// ID and additional data         <text string(s)>
1656
1657			$frame_offset = 0;
1658			if ($id3v2_majorversion == 2) {
1659				$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1660				$frame_offset += 3;
1661			} else {
1662				$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1663				$frame_offset += 4;
1664			}
1665
1666			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1667			$frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1668			if (ord($frame_url) === 0) {
1669				$frame_url = '';
1670			}
1671			$frame_offset = $frame_terminatorpos + strlen("\x00");
1672			$parsedFrame['url'] = $frame_url;
1673
1674			$parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1675			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1676				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
1677			}
1678			unset($parsedFrame['data']);
1679
1680
1681		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
1682			//   There may only be one 'POSS' frame in each tag
1683			// <Head for 'Position synchronisation', ID: 'POSS'>
1684			// Time stamp format         $xx
1685			// Position                  $xx (xx ...)
1686
1687			$frame_offset = 0;
1688			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1689			$parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1690			unset($parsedFrame['data']);
1691
1692
1693		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
1694			//   There may be more than one 'Terms of use' frame in a tag,
1695			//   but only one with the same 'Language'
1696			// <Header for 'Terms of use frame', ID: 'USER'>
1697			// Text encoding        $xx
1698			// Language             $xx xx xx
1699			// The actual text      <text string according to encoding>
1700
1701			$frame_offset = 0;
1702			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1703			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1704				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1705			}
1706			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1707			$frame_offset += 3;
1708			$parsedFrame['language']     = $frame_language;
1709			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1710			$parsedFrame['encodingid']   = $frame_textencoding;
1711			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);
1712
1713			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1714			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
1715			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1716				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1717			}
1718			unset($parsedFrame['data']);
1719
1720
1721		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
1722			//   There may only be one 'OWNE' frame in a tag
1723			// <Header for 'Ownership frame', ID: 'OWNE'>
1724			// Text encoding     $xx
1725			// Price paid        <text string> $00
1726			// Date of purch.    <text string>
1727			// Seller            <text string according to encoding>
1728
1729			$frame_offset = 0;
1730			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1731			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1732				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1733			}
1734			$parsedFrame['encodingid'] = $frame_textencoding;
1735			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
1736
1737			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1738			$frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1739			$frame_offset = $frame_terminatorpos + strlen("\x00");
1740
1741			$parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1742			$parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1743			$parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);
1744
1745			$parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1746			if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1747				$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1748			}
1749			$frame_offset += 8;
1750
1751			$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1752			$parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding));
1753			unset($parsedFrame['data']);
1754
1755
1756		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
1757			//   There may be more than one 'commercial frame' in a tag,
1758			//   but no two may be identical
1759			// <Header for 'Commercial frame', ID: 'COMR'>
1760			// Text encoding      $xx
1761			// Price string       <text string> $00
1762			// Valid until        <text string>
1763			// Contact URL        <text string> $00
1764			// Received as        $xx
1765			// Name of seller     <text string according to encoding> $00 (00)
1766			// Description        <text string according to encoding> $00 (00)
1767			// Picture MIME type  <string> $00
1768			// Seller logo        <binary data>
1769
1770			$frame_offset = 0;
1771			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1772			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1773			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1774				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1775				$frame_textencoding_terminator = "\x00";
1776			}
1777
1778			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1779			$frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1780			$frame_offset = $frame_terminatorpos + strlen("\x00");
1781			$frame_rawpricearray = explode('/', $frame_pricestring);
1782			foreach ($frame_rawpricearray as $key => $val) {
1783				$frame_currencyid = substr($val, 0, 3);
1784				$parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1785				$parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3);
1786			}
1787
1788			$frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1789			$frame_offset += 8;
1790
1791			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1792			$frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1793			$frame_offset = $frame_terminatorpos + strlen("\x00");
1794
1795			$frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1796
1797			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1798			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1799				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1800			}
1801			$frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1802			if (ord($frame_sellername) === 0) {
1803				$frame_sellername = '';
1804			}
1805			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1806
1807			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1808			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1809				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1810			}
1811			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1812			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1813			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1814
1815			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1816			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1817			$frame_offset = $frame_terminatorpos + strlen("\x00");
1818
1819			$frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1820
1821			$parsedFrame['encodingid']        = $frame_textencoding;
1822			$parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);
1823
1824			$parsedFrame['pricevaliduntil']   = $frame_datestring;
1825			$parsedFrame['contacturl']        = $frame_contacturl;
1826			$parsedFrame['receivedasid']      = $frame_receivedasid;
1827			$parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
1828			$parsedFrame['sellername']        = $frame_sellername;
1829			$parsedFrame['mime']              = $frame_mimetype;
1830			$parsedFrame['logo']              = $frame_sellerlogo;
1831			unset($parsedFrame['data']);
1832
1833
1834		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
1835			//   There may be several 'ENCR' frames in a tag,
1836			//   but only one containing the same symbol
1837			//   and only one containing the same owner identifier
1838			// <Header for 'Encryption method registration', ID: 'ENCR'>
1839			// Owner identifier    <text string> $00
1840			// Method symbol       $xx
1841			// Encryption data     <binary data>
1842
1843			$frame_offset = 0;
1844			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1845			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1846			if (ord($frame_ownerid) === 0) {
1847				$frame_ownerid = '';
1848			}
1849			$frame_offset = $frame_terminatorpos + strlen("\x00");
1850
1851			$parsedFrame['ownerid']      = $frame_ownerid;
1852			$parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1853			$parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);
1854
1855
1856		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)
1857
1858			//   There may be several 'GRID' frames in a tag,
1859			//   but only one containing the same symbol
1860			//   and only one containing the same owner identifier
1861			// <Header for 'Group ID registration', ID: 'GRID'>
1862			// Owner identifier      <text string> $00
1863			// Group symbol          $xx
1864			// Group dependent data  <binary data>
1865
1866			$frame_offset = 0;
1867			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1868			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1869			if (ord($frame_ownerid) === 0) {
1870				$frame_ownerid = '';
1871			}
1872			$frame_offset = $frame_terminatorpos + strlen("\x00");
1873
1874			$parsedFrame['ownerid']       = $frame_ownerid;
1875			$parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1876			$parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);
1877
1878
1879		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
1880			//   The tag may contain more than one 'PRIV' frame
1881			//   but only with different contents
1882			// <Header for 'Private frame', ID: 'PRIV'>
1883			// Owner identifier      <text string> $00
1884			// The private data      <binary data>
1885
1886			$frame_offset = 0;
1887			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1888			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1889			if (ord($frame_ownerid) === 0) {
1890				$frame_ownerid = '';
1891			}
1892			$frame_offset = $frame_terminatorpos + strlen("\x00");
1893
1894			$parsedFrame['ownerid'] = $frame_ownerid;
1895			$parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);
1896
1897
1898		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
1899			//   There may be more than one 'signature frame' in a tag,
1900			//   but no two may be identical
1901			// <Header for 'Signature frame', ID: 'SIGN'>
1902			// Group symbol      $xx
1903			// Signature         <binary data>
1904
1905			$frame_offset = 0;
1906			$parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1907			$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
1908
1909
1910		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
1911			//   There may only be one 'seek frame' in a tag
1912			// <Header for 'Seek frame', ID: 'SEEK'>
1913			// Minimum offset to next tag       $xx xx xx xx
1914
1915			$frame_offset = 0;
1916			$parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1917
1918
1919		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
1920			//   There may only be one 'audio seek point index' frame in a tag
1921			// <Header for 'Seek Point Index', ID: 'ASPI'>
1922			// Indexed data start (S)         $xx xx xx xx
1923			// Indexed data length (L)        $xx xx xx xx
1924			// Number of index points (N)     $xx xx
1925			// Bits per index point (b)       $xx
1926			//   Then for every index point the following data is included:
1927			// Fraction at index (Fi)          $xx (xx)
1928
1929			$frame_offset = 0;
1930			$parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1931			$frame_offset += 4;
1932			$parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1933			$frame_offset += 4;
1934			$parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1935			$frame_offset += 2;
1936			$parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1937			$frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1938			for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
1939				$parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1940				$frame_offset += $frame_bytesperpoint;
1941			}
1942			unset($parsedFrame['data']);
1943
1944		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1945			// http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1946			//   There may only be one 'RGAD' frame in a tag
1947			// <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1948			// Peak Amplitude                      $xx $xx $xx $xx
1949			// Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
1950			// Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
1951			//   a - name code
1952			//   b - originator code
1953			//   c - sign bit
1954			//   d - replay gain adjustment
1955
1956			$frame_offset = 0;
1957			$parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1958			$frame_offset += 4;
1959			$rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1960			$frame_offset += 2;
1961			$rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1962			$frame_offset += 2;
1963			$parsedFrame['raw']['track']['name']       = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1964			$parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1965			$parsedFrame['raw']['track']['signbit']    = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1966			$parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1967			$parsedFrame['raw']['album']['name']       = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1968			$parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1969			$parsedFrame['raw']['album']['signbit']    = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
1970			$parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
1971			$parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1972			$parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1973			$parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1974			$parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1975			$parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1976			$parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1977
1978			$info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude'];
1979			$info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1980			$info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1981			$info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1982			$info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1983
1984			unset($parsedFrame['data']);
1985
1986		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
1987			// http://id3.org/id3v2-chapters-1.0
1988			// <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
1989			// Element ID      <text string> $00
1990			// Start time      $xx xx xx xx
1991			// End time        $xx xx xx xx
1992			// Start offset    $xx xx xx xx
1993			// End offset      $xx xx xx xx
1994			// <Optional embedded sub-frames>
1995
1996			$frame_offset = 0;
1997			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
1998			$frame_offset += strlen($parsedFrame['element_id']."\x00");
1999			$parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2000			$frame_offset += 4;
2001			$parsedFrame['time_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2002			$frame_offset += 4;
2003			if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
2004				// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2005				$parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2006			}
2007			$frame_offset += 4;
2008			if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
2009				// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2010				$parsedFrame['offset_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2011			}
2012			$frame_offset += 4;
2013
2014			if ($frame_offset < strlen($parsedFrame['data'])) {
2015				$parsedFrame['subframes'] = array();
2016				while ($frame_offset < strlen($parsedFrame['data'])) {
2017					// <Optional embedded sub-frames>
2018					$subframe = array();
2019					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
2020					$frame_offset += 4;
2021					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2022					$frame_offset += 4;
2023					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2024					$frame_offset += 2;
2025					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2026						$this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
2027						break;
2028					}
2029					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2030					$frame_offset += $subframe['size'];
2031
2032					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2033					$subframe['text']       =     substr($subframe_rawdata, 1);
2034					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
2035					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));
2036					switch (substr($encoding_converted_text, 0, 2)) {
2037						case "\xFF\xFE":
2038						case "\xFE\xFF":
2039							switch (strtoupper($info['id3v2']['encoding'])) {
2040								case 'ISO-8859-1':
2041								case 'UTF-8':
2042									$encoding_converted_text = substr($encoding_converted_text, 2);
2043									// remove unwanted byte-order-marks
2044									break;
2045								default:
2046									// ignore
2047									break;
2048							}
2049							break;
2050						default:
2051							// do not remove BOM
2052							break;
2053					}
2054
2055					switch ($subframe['name']) {
2056						case 'TIT2':
2057							$parsedFrame['chapter_name']        = $encoding_converted_text;
2058							$parsedFrame['subframes'][] = $subframe;
2059							break;
2060						case 'TIT3':
2061							$parsedFrame['chapter_description'] = $encoding_converted_text;
2062							$parsedFrame['subframes'][] = $subframe;
2063							break;
2064						case 'WXXX':
2065							list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
2066							$parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
2067							$parsedFrame['subframes'][] = $subframe;
2068							break;
2069						case 'APIC':
2070							if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) {
2071								list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches;
2072								$subframe['image_mime']   = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime));
2073								$subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype);
2074								$subframe['description']  = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description));
2075								if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) {
2076									// the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16)
2077									// the above regex assumes one byte, if it's actually two then strip the second one here
2078									$subframe_apic_picturedata = substr($subframe_apic_picturedata, 1);
2079								}
2080								$subframe['data'] = $subframe_apic_picturedata;
2081								unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata);
2082								unset($subframe['text'], $parsedFrame['text']);
2083								$parsedFrame['subframes'][] = $subframe;
2084								$parsedFrame['picture_present'] = true;
2085							} else {
2086								$this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format');
2087							}
2088							break;
2089						default:
2090							$this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)');
2091							break;
2092					}
2093				}
2094				unset($subframe_rawdata, $subframe, $encoding_converted_text);
2095				unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC
2096			}
2097
2098			$id3v2_chapter_entry = array();
2099			foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) {
2100				if (isset($parsedFrame[$id3v2_chapter_key])) {
2101					$id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
2102				}
2103			}
2104			if (!isset($info['id3v2']['chapters'])) {
2105				$info['id3v2']['chapters'] = array();
2106			}
2107			$info['id3v2']['chapters'][] = $id3v2_chapter_entry;
2108			unset($id3v2_chapter_entry, $id3v2_chapter_key);
2109
2110
2111		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
2112			// http://id3.org/id3v2-chapters-1.0
2113			// <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC">           (10 bytes)
2114			// Element ID      <text string> $00
2115			// CTOC flags        %xx
2116			// Entry count       $xx
2117			// Child Element ID  <string>$00   /* zero or more child CHAP or CTOC entries */
2118			// <Optional embedded sub-frames>
2119
2120			$frame_offset = 0;
2121			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2122			$frame_offset += strlen($parsedFrame['element_id']."\x00");
2123			$ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
2124			$frame_offset += 1;
2125			$parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
2126			$frame_offset += 1;
2127
2128			$terminator_position = null;
2129			for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
2130				$terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
2131				$parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
2132				$frame_offset = $terminator_position + 1;
2133			}
2134
2135			$parsedFrame['ctoc_flags']['ordered']   = (bool) ($ctoc_flags_raw & 0x01);
2136			$parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
2137
2138			unset($ctoc_flags_raw, $terminator_position);
2139
2140			if ($frame_offset < strlen($parsedFrame['data'])) {
2141				$parsedFrame['subframes'] = array();
2142				while ($frame_offset < strlen($parsedFrame['data'])) {
2143					// <Optional embedded sub-frames>
2144					$subframe = array();
2145					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
2146					$frame_offset += 4;
2147					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2148					$frame_offset += 4;
2149					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2150					$frame_offset += 2;
2151					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2152						$this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
2153						break;
2154					}
2155					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2156					$frame_offset += $subframe['size'];
2157
2158					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2159					$subframe['text']       =     substr($subframe_rawdata, 1);
2160					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
2161					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
2162					switch (substr($encoding_converted_text, 0, 2)) {
2163						case "\xFF\xFE":
2164						case "\xFE\xFF":
2165							switch (strtoupper($info['id3v2']['encoding'])) {
2166								case 'ISO-8859-1':
2167								case 'UTF-8':
2168									$encoding_converted_text = substr($encoding_converted_text, 2);
2169									// remove unwanted byte-order-marks
2170									break;
2171								default:
2172									// ignore
2173									break;
2174							}
2175							break;
2176						default:
2177							// do not remove BOM
2178							break;
2179					}
2180
2181					if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
2182						if ($subframe['name'] == 'TIT2') {
2183							$parsedFrame['toc_name']        = $encoding_converted_text;
2184						} elseif ($subframe['name'] == 'TIT3') {
2185							$parsedFrame['toc_description'] = $encoding_converted_text;
2186						}
2187						$parsedFrame['subframes'][] = $subframe;
2188					} else {
2189						$this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
2190					}
2191				}
2192				unset($subframe_rawdata, $subframe, $encoding_converted_text);
2193			}
2194
2195		}
2196
2197		return true;
2198	}
2199
2200	/**
2201	 * @param string $data
2202	 *
2203	 * @return string
2204	 */
2205	public function DeUnsynchronise($data) {
2206		return str_replace("\xFF\x00", "\xFF", $data);
2207	}
2208
2209	/**
2210	 * @param int $index
2211	 *
2212	 * @return string
2213	 */
2214	public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
2215		static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
2216			0x00 => 'No more than 128 frames and 1 MB total tag size',
2217			0x01 => 'No more than 64 frames and 128 KB total tag size',
2218			0x02 => 'No more than 32 frames and 40 KB total tag size',
2219			0x03 => 'No more than 32 frames and 4 KB total tag size',
2220		);
2221		return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
2222	}
2223
2224	/**
2225	 * @param int $index
2226	 *
2227	 * @return string
2228	 */
2229	public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
2230		static $LookupExtendedHeaderRestrictionsTextEncodings = array(
2231			0x00 => 'No restrictions',
2232			0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
2233		);
2234		return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
2235	}
2236
2237	/**
2238	 * @param int $index
2239	 *
2240	 * @return string
2241	 */
2242	public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
2243		static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
2244			0x00 => 'No restrictions',
2245			0x01 => 'No string is longer than 1024 characters',
2246			0x02 => 'No string is longer than 128 characters',
2247			0x03 => 'No string is longer than 30 characters',
2248		);
2249		return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
2250	}
2251
2252	/**
2253	 * @param int $index
2254	 *
2255	 * @return string
2256	 */
2257	public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
2258		static $LookupExtendedHeaderRestrictionsImageEncoding = array(
2259			0x00 => 'No restrictions',
2260			0x01 => 'Images are encoded only with PNG or JPEG',
2261		);
2262		return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
2263	}
2264
2265	/**
2266	 * @param int $index
2267	 *
2268	 * @return string
2269	 */
2270	public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
2271		static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
2272			0x00 => 'No restrictions',
2273			0x01 => 'All images are 256x256 pixels or smaller',
2274			0x02 => 'All images are 64x64 pixels or smaller',
2275			0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
2276		);
2277		return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
2278	}
2279
2280	/**
2281	 * @param string $currencyid
2282	 *
2283	 * @return string
2284	 */
2285	public function LookupCurrencyUnits($currencyid) {
2286
2287		$begin = __LINE__;
2288
2289		/** This is not a comment!
2290
2291
2292			AED	Dirhams
2293			AFA	Afghanis
2294			ALL	Leke
2295			AMD	Drams
2296			ANG	Guilders
2297			AOA	Kwanza
2298			ARS	Pesos
2299			ATS	Schillings
2300			AUD	Dollars
2301			AWG	Guilders
2302			AZM	Manats
2303			BAM	Convertible Marka
2304			BBD	Dollars
2305			BDT	Taka
2306			BEF	Francs
2307			BGL	Leva
2308			BHD	Dinars
2309			BIF	Francs
2310			BMD	Dollars
2311			BND	Dollars
2312			BOB	Bolivianos
2313			BRL	Brazil Real
2314			BSD	Dollars
2315			BTN	Ngultrum
2316			BWP	Pulas
2317			BYR	Rubles
2318			BZD	Dollars
2319			CAD	Dollars
2320			CDF	Congolese Francs
2321			CHF	Francs
2322			CLP	Pesos
2323			CNY	Yuan Renminbi
2324			COP	Pesos
2325			CRC	Colones
2326			CUP	Pesos
2327			CVE	Escudos
2328			CYP	Pounds
2329			CZK	Koruny
2330			DEM	Deutsche Marks
2331			DJF	Francs
2332			DKK	Kroner
2333			DOP	Pesos
2334			DZD	Algeria Dinars
2335			EEK	Krooni
2336			EGP	Pounds
2337			ERN	Nakfa
2338			ESP	Pesetas
2339			ETB	Birr
2340			EUR	Euro
2341			FIM	Markkaa
2342			FJD	Dollars
2343			FKP	Pounds
2344			FRF	Francs
2345			GBP	Pounds
2346			GEL	Lari
2347			GGP	Pounds
2348			GHC	Cedis
2349			GIP	Pounds
2350			GMD	Dalasi
2351			GNF	Francs
2352			GRD	Drachmae
2353			GTQ	Quetzales
2354			GYD	Dollars
2355			HKD	Dollars
2356			HNL	Lempiras
2357			HRK	Kuna
2358			HTG	Gourdes
2359			HUF	Forints
2360			IDR	Rupiahs
2361			IEP	Pounds
2362			ILS	New Shekels
2363			IMP	Pounds
2364			INR	Rupees
2365			IQD	Dinars
2366			IRR	Rials
2367			ISK	Kronur
2368			ITL	Lire
2369			JEP	Pounds
2370			JMD	Dollars
2371			JOD	Dinars
2372			JPY	Yen
2373			KES	Shillings
2374			KGS	Soms
2375			KHR	Riels
2376			KMF	Francs
2377			KPW	Won
2378			KWD	Dinars
2379			KYD	Dollars
2380			KZT	Tenge
2381			LAK	Kips
2382			LBP	Pounds
2383			LKR	Rupees
2384			LRD	Dollars
2385			LSL	Maloti
2386			LTL	Litai
2387			LUF	Francs
2388			LVL	Lati
2389			LYD	Dinars
2390			MAD	Dirhams
2391			MDL	Lei
2392			MGF	Malagasy Francs
2393			MKD	Denars
2394			MMK	Kyats
2395			MNT	Tugriks
2396			MOP	Patacas
2397			MRO	Ouguiyas
2398			MTL	Liri
2399			MUR	Rupees
2400			MVR	Rufiyaa
2401			MWK	Kwachas
2402			MXN	Pesos
2403			MYR	Ringgits
2404			MZM	Meticais
2405			NAD	Dollars
2406			NGN	Nairas
2407			NIO	Gold Cordobas
2408			NLG	Guilders
2409			NOK	Krone
2410			NPR	Nepal Rupees
2411			NZD	Dollars
2412			OMR	Rials
2413			PAB	Balboa
2414			PEN	Nuevos Soles
2415			PGK	Kina
2416			PHP	Pesos
2417			PKR	Rupees
2418			PLN	Zlotych
2419			PTE	Escudos
2420			PYG	Guarani
2421			QAR	Rials
2422			ROL	Lei
2423			RUR	Rubles
2424			RWF	Rwanda Francs
2425			SAR	Riyals
2426			SBD	Dollars
2427			SCR	Rupees
2428			SDD	Dinars
2429			SEK	Kronor
2430			SGD	Dollars
2431			SHP	Pounds
2432			SIT	Tolars
2433			SKK	Koruny
2434			SLL	Leones
2435			SOS	Shillings
2436			SPL	Luigini
2437			SRG	Guilders
2438			STD	Dobras
2439			SVC	Colones
2440			SYP	Pounds
2441			SZL	Emalangeni
2442			THB	Baht
2443			TJR	Rubles
2444			TMM	Manats
2445			TND	Dinars
2446			TOP	Pa'anga
2447			TRL	Liras
2448			TTD	Dollars
2449			TVD	Tuvalu Dollars
2450			TWD	New Dollars
2451			TZS	Shillings
2452			UAH	Hryvnia
2453			UGX	Shillings
2454			USD	Dollars
2455			UYU	Pesos
2456			UZS	Sums
2457			VAL	Lire
2458			VEB	Bolivares
2459			VND	Dong
2460			VUV	Vatu
2461			WST	Tala
2462			XAF	Francs
2463			XAG	Ounces
2464			XAU	Ounces
2465			XCD	Dollars
2466			XDR	Special Drawing Rights
2467			XPD	Ounces
2468			XPF	Francs
2469			XPT	Ounces
2470			YER	Rials
2471			YUM	New Dinars
2472			ZAR	Rand
2473			ZMK	Kwacha
2474			ZWD	Zimbabwe Dollars
2475
2476		*/
2477
2478		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2479	}
2480
2481	/**
2482	 * @param string $currencyid
2483	 *
2484	 * @return string
2485	 */
2486	public function LookupCurrencyCountry($currencyid) {
2487
2488		$begin = __LINE__;
2489
2490		/** This is not a comment!
2491
2492			AED	United Arab Emirates
2493			AFA	Afghanistan
2494			ALL	Albania
2495			AMD	Armenia
2496			ANG	Netherlands Antilles
2497			AOA	Angola
2498			ARS	Argentina
2499			ATS	Austria
2500			AUD	Australia
2501			AWG	Aruba
2502			AZM	Azerbaijan
2503			BAM	Bosnia and Herzegovina
2504			BBD	Barbados
2505			BDT	Bangladesh
2506			BEF	Belgium
2507			BGL	Bulgaria
2508			BHD	Bahrain
2509			BIF	Burundi
2510			BMD	Bermuda
2511			BND	Brunei Darussalam
2512			BOB	Bolivia
2513			BRL	Brazil
2514			BSD	Bahamas
2515			BTN	Bhutan
2516			BWP	Botswana
2517			BYR	Belarus
2518			BZD	Belize
2519			CAD	Canada
2520			CDF	Congo/Kinshasa
2521			CHF	Switzerland
2522			CLP	Chile
2523			CNY	China
2524			COP	Colombia
2525			CRC	Costa Rica
2526			CUP	Cuba
2527			CVE	Cape Verde
2528			CYP	Cyprus
2529			CZK	Czech Republic
2530			DEM	Germany
2531			DJF	Djibouti
2532			DKK	Denmark
2533			DOP	Dominican Republic
2534			DZD	Algeria
2535			EEK	Estonia
2536			EGP	Egypt
2537			ERN	Eritrea
2538			ESP	Spain
2539			ETB	Ethiopia
2540			EUR	Euro Member Countries
2541			FIM	Finland
2542			FJD	Fiji
2543			FKP	Falkland Islands (Malvinas)
2544			FRF	France
2545			GBP	United Kingdom
2546			GEL	Georgia
2547			GGP	Guernsey
2548			GHC	Ghana
2549			GIP	Gibraltar
2550			GMD	Gambia
2551			GNF	Guinea
2552			GRD	Greece
2553			GTQ	Guatemala
2554			GYD	Guyana
2555			HKD	Hong Kong
2556			HNL	Honduras
2557			HRK	Croatia
2558			HTG	Haiti
2559			HUF	Hungary
2560			IDR	Indonesia
2561			IEP	Ireland (Eire)
2562			ILS	Israel
2563			IMP	Isle of Man
2564			INR	India
2565			IQD	Iraq
2566			IRR	Iran
2567			ISK	Iceland
2568			ITL	Italy
2569			JEP	Jersey
2570			JMD	Jamaica
2571			JOD	Jordan
2572			JPY	Japan
2573			KES	Kenya
2574			KGS	Kyrgyzstan
2575			KHR	Cambodia
2576			KMF	Comoros
2577			KPW	Korea
2578			KWD	Kuwait
2579			KYD	Cayman Islands
2580			KZT	Kazakstan
2581			LAK	Laos
2582			LBP	Lebanon
2583			LKR	Sri Lanka
2584			LRD	Liberia
2585			LSL	Lesotho
2586			LTL	Lithuania
2587			LUF	Luxembourg
2588			LVL	Latvia
2589			LYD	Libya
2590			MAD	Morocco
2591			MDL	Moldova
2592			MGF	Madagascar
2593			MKD	Macedonia
2594			MMK	Myanmar (Burma)
2595			MNT	Mongolia
2596			MOP	Macau
2597			MRO	Mauritania
2598			MTL	Malta
2599			MUR	Mauritius
2600			MVR	Maldives (Maldive Islands)
2601			MWK	Malawi
2602			MXN	Mexico
2603			MYR	Malaysia
2604			MZM	Mozambique
2605			NAD	Namibia
2606			NGN	Nigeria
2607			NIO	Nicaragua
2608			NLG	Netherlands (Holland)
2609			NOK	Norway
2610			NPR	Nepal
2611			NZD	New Zealand
2612			OMR	Oman
2613			PAB	Panama
2614			PEN	Peru
2615			PGK	Papua New Guinea
2616			PHP	Philippines
2617			PKR	Pakistan
2618			PLN	Poland
2619			PTE	Portugal
2620			PYG	Paraguay
2621			QAR	Qatar
2622			ROL	Romania
2623			RUR	Russia
2624			RWF	Rwanda
2625			SAR	Saudi Arabia
2626			SBD	Solomon Islands
2627			SCR	Seychelles
2628			SDD	Sudan
2629			SEK	Sweden
2630			SGD	Singapore
2631			SHP	Saint Helena
2632			SIT	Slovenia
2633			SKK	Slovakia
2634			SLL	Sierra Leone
2635			SOS	Somalia
2636			SPL	Seborga
2637			SRG	Suriname
2638			STD	São Tome and Principe
2639			SVC	El Salvador
2640			SYP	Syria
2641			SZL	Swaziland
2642			THB	Thailand
2643			TJR	Tajikistan
2644			TMM	Turkmenistan
2645			TND	Tunisia
2646			TOP	Tonga
2647			TRL	Turkey
2648			TTD	Trinidad and Tobago
2649			TVD	Tuvalu
2650			TWD	Taiwan
2651			TZS	Tanzania
2652			UAH	Ukraine
2653			UGX	Uganda
2654			USD	United States of America
2655			UYU	Uruguay
2656			UZS	Uzbekistan
2657			VAL	Vatican City
2658			VEB	Venezuela
2659			VND	Viet Nam
2660			VUV	Vanuatu
2661			WST	Samoa
2662			XAF	Communauté Financière Africaine
2663			XAG	Silver
2664			XAU	Gold
2665			XCD	East Caribbean
2666			XDR	International Monetary Fund
2667			XPD	Palladium
2668			XPF	Comptoirs Français du Pacifique
2669			XPT	Platinum
2670			YER	Yemen
2671			YUM	Yugoslavia
2672			ZAR	South Africa
2673			ZMK	Zambia
2674			ZWD	Zimbabwe
2675
2676		*/
2677
2678		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2679	}
2680
2681	/**
2682	 * @param string $languagecode
2683	 * @param bool   $casesensitive
2684	 *
2685	 * @return string
2686	 */
2687	public static function LanguageLookup($languagecode, $casesensitive=false) {
2688
2689		if (!$casesensitive) {
2690			$languagecode = strtolower($languagecode);
2691		}
2692
2693		// http://www.id3.org/id3v2.4.0-structure.txt
2694		// [4.   ID3v2 frame overview]
2695		// The three byte language field, present in several frames, is used to
2696		// describe the language of the frame's content, according to ISO-639-2
2697		// [ISO-639-2]. The language should be represented in lower case. If the
2698		// language is not known the string "XXX" should be used.
2699
2700
2701		// ISO 639-2 - http://www.id3.org/iso639-2.html
2702
2703		$begin = __LINE__;
2704
2705		/** This is not a comment!
2706
2707			XXX	unknown
2708			xxx	unknown
2709			aar	Afar
2710			abk	Abkhazian
2711			ace	Achinese
2712			ach	Acoli
2713			ada	Adangme
2714			afa	Afro-Asiatic (Other)
2715			afh	Afrihili
2716			afr	Afrikaans
2717			aka	Akan
2718			akk	Akkadian
2719			alb	Albanian
2720			ale	Aleut
2721			alg	Algonquian Languages
2722			amh	Amharic
2723			ang	English, Old (ca. 450-1100)
2724			apa	Apache Languages
2725			ara	Arabic
2726			arc	Aramaic
2727			arm	Armenian
2728			arn	Araucanian
2729			arp	Arapaho
2730			art	Artificial (Other)
2731			arw	Arawak
2732			asm	Assamese
2733			ath	Athapascan Languages
2734			ava	Avaric
2735			ave	Avestan
2736			awa	Awadhi
2737			aym	Aymara
2738			aze	Azerbaijani
2739			bad	Banda
2740			bai	Bamileke Languages
2741			bak	Bashkir
2742			bal	Baluchi
2743			bam	Bambara
2744			ban	Balinese
2745			baq	Basque
2746			bas	Basa
2747			bat	Baltic (Other)
2748			bej	Beja
2749			bel	Byelorussian
2750			bem	Bemba
2751			ben	Bengali
2752			ber	Berber (Other)
2753			bho	Bhojpuri
2754			bih	Bihari
2755			bik	Bikol
2756			bin	Bini
2757			bis	Bislama
2758			bla	Siksika
2759			bnt	Bantu (Other)
2760			bod	Tibetan
2761			bra	Braj
2762			bre	Breton
2763			bua	Buriat
2764			bug	Buginese
2765			bul	Bulgarian
2766			bur	Burmese
2767			cad	Caddo
2768			cai	Central American Indian (Other)
2769			car	Carib
2770			cat	Catalan
2771			cau	Caucasian (Other)
2772			ceb	Cebuano
2773			cel	Celtic (Other)
2774			ces	Czech
2775			cha	Chamorro
2776			chb	Chibcha
2777			che	Chechen
2778			chg	Chagatai
2779			chi	Chinese
2780			chm	Mari
2781			chn	Chinook jargon
2782			cho	Choctaw
2783			chr	Cherokee
2784			chu	Church Slavic
2785			chv	Chuvash
2786			chy	Cheyenne
2787			cop	Coptic
2788			cor	Cornish
2789			cos	Corsican
2790			cpe	Creoles and Pidgins, English-based (Other)
2791			cpf	Creoles and Pidgins, French-based (Other)
2792			cpp	Creoles and Pidgins, Portuguese-based (Other)
2793			cre	Cree
2794			crp	Creoles and Pidgins (Other)
2795			cus	Cushitic (Other)
2796			cym	Welsh
2797			cze	Czech
2798			dak	Dakota
2799			dan	Danish
2800			del	Delaware
2801			deu	German
2802			din	Dinka
2803			div	Divehi
2804			doi	Dogri
2805			dra	Dravidian (Other)
2806			dua	Duala
2807			dum	Dutch, Middle (ca. 1050-1350)
2808			dut	Dutch
2809			dyu	Dyula
2810			dzo	Dzongkha
2811			efi	Efik
2812			egy	Egyptian (Ancient)
2813			eka	Ekajuk
2814			ell	Greek, Modern (1453-)
2815			elx	Elamite
2816			eng	English
2817			enm	English, Middle (ca. 1100-1500)
2818			epo	Esperanto
2819			esk	Eskimo (Other)
2820			esl	Spanish
2821			est	Estonian
2822			eus	Basque
2823			ewe	Ewe
2824			ewo	Ewondo
2825			fan	Fang
2826			fao	Faroese
2827			fas	Persian
2828			fat	Fanti
2829			fij	Fijian
2830			fin	Finnish
2831			fiu	Finno-Ugrian (Other)
2832			fon	Fon
2833			fra	French
2834			fre	French
2835			frm	French, Middle (ca. 1400-1600)
2836			fro	French, Old (842- ca. 1400)
2837			fry	Frisian
2838			ful	Fulah
2839			gaa	Ga
2840			gae	Gaelic (Scots)
2841			gai	Irish
2842			gay	Gayo
2843			gdh	Gaelic (Scots)
2844			gem	Germanic (Other)
2845			geo	Georgian
2846			ger	German
2847			gez	Geez
2848			gil	Gilbertese
2849			glg	Gallegan
2850			gmh	German, Middle High (ca. 1050-1500)
2851			goh	German, Old High (ca. 750-1050)
2852			gon	Gondi
2853			got	Gothic
2854			grb	Grebo
2855			grc	Greek, Ancient (to 1453)
2856			gre	Greek, Modern (1453-)
2857			grn	Guarani
2858			guj	Gujarati
2859			hai	Haida
2860			hau	Hausa
2861			haw	Hawaiian
2862			heb	Hebrew
2863			her	Herero
2864			hil	Hiligaynon
2865			him	Himachali
2866			hin	Hindi
2867			hmo	Hiri Motu
2868			hun	Hungarian
2869			hup	Hupa
2870			hye	Armenian
2871			iba	Iban
2872			ibo	Igbo
2873			ice	Icelandic
2874			ijo	Ijo
2875			iku	Inuktitut
2876			ilo	Iloko
2877			ina	Interlingua (International Auxiliary language Association)
2878			inc	Indic (Other)
2879			ind	Indonesian
2880			ine	Indo-European (Other)
2881			ine	Interlingue
2882			ipk	Inupiak
2883			ira	Iranian (Other)
2884			iri	Irish
2885			iro	Iroquoian uages
2886			isl	Icelandic
2887			ita	Italian
2888			jav	Javanese
2889			jaw	Javanese
2890			jpn	Japanese
2891			jpr	Judeo-Persian
2892			jrb	Judeo-Arabic
2893			kaa	Kara-Kalpak
2894			kab	Kabyle
2895			kac	Kachin
2896			kal	Greenlandic
2897			kam	Kamba
2898			kan	Kannada
2899			kar	Karen
2900			kas	Kashmiri
2901			kat	Georgian
2902			kau	Kanuri
2903			kaw	Kawi
2904			kaz	Kazakh
2905			kha	Khasi
2906			khi	Khoisan (Other)
2907			khm	Khmer
2908			kho	Khotanese
2909			kik	Kikuyu
2910			kin	Kinyarwanda
2911			kir	Kirghiz
2912			kok	Konkani
2913			kom	Komi
2914			kon	Kongo
2915			kor	Korean
2916			kpe	Kpelle
2917			kro	Kru
2918			kru	Kurukh
2919			kua	Kuanyama
2920			kum	Kumyk
2921			kur	Kurdish
2922			kus	Kusaie
2923			kut	Kutenai
2924			lad	Ladino
2925			lah	Lahnda
2926			lam	Lamba
2927			lao	Lao
2928			lat	Latin
2929			lav	Latvian
2930			lez	Lezghian
2931			lin	Lingala
2932			lit	Lithuanian
2933			lol	Mongo
2934			loz	Lozi
2935			ltz	Letzeburgesch
2936			lub	Luba-Katanga
2937			lug	Ganda
2938			lui	Luiseno
2939			lun	Lunda
2940			luo	Luo (Kenya and Tanzania)
2941			mac	Macedonian
2942			mad	Madurese
2943			mag	Magahi
2944			mah	Marshall
2945			mai	Maithili
2946			mak	Macedonian
2947			mak	Makasar
2948			mal	Malayalam
2949			man	Mandingo
2950			mao	Maori
2951			map	Austronesian (Other)
2952			mar	Marathi
2953			mas	Masai
2954			max	Manx
2955			may	Malay
2956			men	Mende
2957			mga	Irish, Middle (900 - 1200)
2958			mic	Micmac
2959			min	Minangkabau
2960			mis	Miscellaneous (Other)
2961			mkh	Mon-Kmer (Other)
2962			mlg	Malagasy
2963			mlt	Maltese
2964			mni	Manipuri
2965			mno	Manobo Languages
2966			moh	Mohawk
2967			mol	Moldavian
2968			mon	Mongolian
2969			mos	Mossi
2970			mri	Maori
2971			msa	Malay
2972			mul	Multiple Languages
2973			mun	Munda Languages
2974			mus	Creek
2975			mwr	Marwari
2976			mya	Burmese
2977			myn	Mayan Languages
2978			nah	Aztec
2979			nai	North American Indian (Other)
2980			nau	Nauru
2981			nav	Navajo
2982			nbl	Ndebele, South
2983			nde	Ndebele, North
2984			ndo	Ndongo
2985			nep	Nepali
2986			new	Newari
2987			nic	Niger-Kordofanian (Other)
2988			niu	Niuean
2989			nla	Dutch
2990			nno	Norwegian (Nynorsk)
2991			non	Norse, Old
2992			nor	Norwegian
2993			nso	Sotho, Northern
2994			nub	Nubian Languages
2995			nya	Nyanja
2996			nym	Nyamwezi
2997			nyn	Nyankole
2998			nyo	Nyoro
2999			nzi	Nzima
3000			oci	Langue d'Oc (post 1500)
3001			oji	Ojibwa
3002			ori	Oriya
3003			orm	Oromo
3004			osa	Osage
3005			oss	Ossetic
3006			ota	Turkish, Ottoman (1500 - 1928)
3007			oto	Otomian Languages
3008			paa	Papuan-Australian (Other)
3009			pag	Pangasinan
3010			pal	Pahlavi
3011			pam	Pampanga
3012			pan	Panjabi
3013			pap	Papiamento
3014			pau	Palauan
3015			peo	Persian, Old (ca 600 - 400 B.C.)
3016			per	Persian
3017			phn	Phoenician
3018			pli	Pali
3019			pol	Polish
3020			pon	Ponape
3021			por	Portuguese
3022			pra	Prakrit uages
3023			pro	Provencal, Old (to 1500)
3024			pus	Pushto
3025			que	Quechua
3026			raj	Rajasthani
3027			rar	Rarotongan
3028			roa	Romance (Other)
3029			roh	Rhaeto-Romance
3030			rom	Romany
3031			ron	Romanian
3032			rum	Romanian
3033			run	Rundi
3034			rus	Russian
3035			sad	Sandawe
3036			sag	Sango
3037			sah	Yakut
3038			sai	South American Indian (Other)
3039			sal	Salishan Languages
3040			sam	Samaritan Aramaic
3041			san	Sanskrit
3042			sco	Scots
3043			scr	Serbo-Croatian
3044			sel	Selkup
3045			sem	Semitic (Other)
3046			sga	Irish, Old (to 900)
3047			shn	Shan
3048			sid	Sidamo
3049			sin	Singhalese
3050			sio	Siouan Languages
3051			sit	Sino-Tibetan (Other)
3052			sla	Slavic (Other)
3053			slk	Slovak
3054			slo	Slovak
3055			slv	Slovenian
3056			smi	Sami Languages
3057			smo	Samoan
3058			sna	Shona
3059			snd	Sindhi
3060			sog	Sogdian
3061			som	Somali
3062			son	Songhai
3063			sot	Sotho, Southern
3064			spa	Spanish
3065			sqi	Albanian
3066			srd	Sardinian
3067			srr	Serer
3068			ssa	Nilo-Saharan (Other)
3069			ssw	Siswant
3070			ssw	Swazi
3071			suk	Sukuma
3072			sun	Sudanese
3073			sus	Susu
3074			sux	Sumerian
3075			sve	Swedish
3076			swa	Swahili
3077			swe	Swedish
3078			syr	Syriac
3079			tah	Tahitian
3080			tam	Tamil
3081			tat	Tatar
3082			tel	Telugu
3083			tem	Timne
3084			ter	Tereno
3085			tgk	Tajik
3086			tgl	Tagalog
3087			tha	Thai
3088			tib	Tibetan
3089			tig	Tigre
3090			tir	Tigrinya
3091			tiv	Tivi
3092			tli	Tlingit
3093			tmh	Tamashek
3094			tog	Tonga (Nyasa)
3095			ton	Tonga (Tonga Islands)
3096			tru	Truk
3097			tsi	Tsimshian
3098			tsn	Tswana
3099			tso	Tsonga
3100			tuk	Turkmen
3101			tum	Tumbuka
3102			tur	Turkish
3103			tut	Altaic (Other)
3104			twi	Twi
3105			tyv	Tuvinian
3106			uga	Ugaritic
3107			uig	Uighur
3108			ukr	Ukrainian
3109			umb	Umbundu
3110			und	Undetermined
3111			urd	Urdu
3112			uzb	Uzbek
3113			vai	Vai
3114			ven	Venda
3115			vie	Vietnamese
3116			vol	Volapük
3117			vot	Votic
3118			wak	Wakashan Languages
3119			wal	Walamo
3120			war	Waray
3121			was	Washo
3122			wel	Welsh
3123			wen	Sorbian Languages
3124			wol	Wolof
3125			xho	Xhosa
3126			yao	Yao
3127			yap	Yap
3128			yid	Yiddish
3129			yor	Yoruba
3130			zap	Zapotec
3131			zen	Zenaga
3132			zha	Zhuang
3133			zho	Chinese
3134			zul	Zulu
3135			zun	Zuni
3136
3137		*/
3138
3139		return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
3140	}
3141
3142	/**
3143	 * @param int $index
3144	 *
3145	 * @return string
3146	 */
3147	public static function ETCOEventLookup($index) {
3148		if (($index >= 0x17) && ($index <= 0xDF)) {
3149			return 'reserved for future use';
3150		}
3151		if (($index >= 0xE0) && ($index <= 0xEF)) {
3152			return 'not predefined synch 0-F';
3153		}
3154		if (($index >= 0xF0) && ($index <= 0xFC)) {
3155			return 'reserved for future use';
3156		}
3157
3158		static $EventLookup = array(
3159			0x00 => 'padding (has no meaning)',
3160			0x01 => 'end of initial silence',
3161			0x02 => 'intro start',
3162			0x03 => 'main part start',
3163			0x04 => 'outro start',
3164			0x05 => 'outro end',
3165			0x06 => 'verse start',
3166			0x07 => 'refrain start',
3167			0x08 => 'interlude start',
3168			0x09 => 'theme start',
3169			0x0A => 'variation start',
3170			0x0B => 'key change',
3171			0x0C => 'time change',
3172			0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
3173			0x0E => 'sustained noise',
3174			0x0F => 'sustained noise end',
3175			0x10 => 'intro end',
3176			0x11 => 'main part end',
3177			0x12 => 'verse end',
3178			0x13 => 'refrain end',
3179			0x14 => 'theme end',
3180			0x15 => 'profanity',
3181			0x16 => 'profanity end',
3182			0xFD => 'audio end (start of silence)',
3183			0xFE => 'audio file ends',
3184			0xFF => 'one more byte of events follows'
3185		);
3186
3187		return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
3188	}
3189
3190	/**
3191	 * @param int $index
3192	 *
3193	 * @return string
3194	 */
3195	public static function SYTLContentTypeLookup($index) {
3196		static $SYTLContentTypeLookup = array(
3197			0x00 => 'other',
3198			0x01 => 'lyrics',
3199			0x02 => 'text transcription',
3200			0x03 => 'movement/part name', // (e.g. 'Adagio')
3201			0x04 => 'events',             // (e.g. 'Don Quijote enters the stage')
3202			0x05 => 'chord',              // (e.g. 'Bb F Fsus')
3203			0x06 => 'trivia/\'pop up\' information',
3204			0x07 => 'URLs to webpages',
3205			0x08 => 'URLs to images'
3206		);
3207
3208		return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
3209	}
3210
3211	/**
3212	 * @param int   $index
3213	 * @param bool $returnarray
3214	 *
3215	 * @return array|string
3216	 */
3217	public static function APICPictureTypeLookup($index, $returnarray=false) {
3218		static $APICPictureTypeLookup = array(
3219			0x00 => 'Other',
3220			0x01 => '32x32 pixels \'file icon\' (PNG only)',
3221			0x02 => 'Other file icon',
3222			0x03 => 'Cover (front)',
3223			0x04 => 'Cover (back)',
3224			0x05 => 'Leaflet page',
3225			0x06 => 'Media (e.g. label side of CD)',
3226			0x07 => 'Lead artist/lead performer/soloist',
3227			0x08 => 'Artist/performer',
3228			0x09 => 'Conductor',
3229			0x0A => 'Band/Orchestra',
3230			0x0B => 'Composer',
3231			0x0C => 'Lyricist/text writer',
3232			0x0D => 'Recording Location',
3233			0x0E => 'During recording',
3234			0x0F => 'During performance',
3235			0x10 => 'Movie/video screen capture',
3236			0x11 => 'A bright coloured fish',
3237			0x12 => 'Illustration',
3238			0x13 => 'Band/artist logotype',
3239			0x14 => 'Publisher/Studio logotype'
3240		);
3241		if ($returnarray) {
3242			return $APICPictureTypeLookup;
3243		}
3244		return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
3245	}
3246
3247	/**
3248	 * @param int $index
3249	 *
3250	 * @return string
3251	 */
3252	public static function COMRReceivedAsLookup($index) {
3253		static $COMRReceivedAsLookup = array(
3254			0x00 => 'Other',
3255			0x01 => 'Standard CD album with other songs',
3256			0x02 => 'Compressed audio on CD',
3257			0x03 => 'File over the Internet',
3258			0x04 => 'Stream over the Internet',
3259			0x05 => 'As note sheets',
3260			0x06 => 'As note sheets in a book with other sheets',
3261			0x07 => 'Music on other media',
3262			0x08 => 'Non-musical merchandise'
3263		);
3264
3265		return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
3266	}
3267
3268	/**
3269	 * @param int $index
3270	 *
3271	 * @return string
3272	 */
3273	public static function RVA2ChannelTypeLookup($index) {
3274		static $RVA2ChannelTypeLookup = array(
3275			0x00 => 'Other',
3276			0x01 => 'Master volume',
3277			0x02 => 'Front right',
3278			0x03 => 'Front left',
3279			0x04 => 'Back right',
3280			0x05 => 'Back left',
3281			0x06 => 'Front centre',
3282			0x07 => 'Back centre',
3283			0x08 => 'Subwoofer'
3284		);
3285
3286		return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
3287	}
3288
3289	/**
3290	 * @param string $framename
3291	 *
3292	 * @return string
3293	 */
3294	public static function FrameNameLongLookup($framename) {
3295
3296		$begin = __LINE__;
3297
3298		/** This is not a comment!
3299
3300			AENC	Audio encryption
3301			APIC	Attached picture
3302			ASPI	Audio seek point index
3303			BUF	Recommended buffer size
3304			CNT	Play counter
3305			COM	Comments
3306			COMM	Comments
3307			COMR	Commercial frame
3308			CRA	Audio encryption
3309			CRM	Encrypted meta frame
3310			ENCR	Encryption method registration
3311			EQU	Equalisation
3312			EQU2	Equalisation (2)
3313			EQUA	Equalisation
3314			ETC	Event timing codes
3315			ETCO	Event timing codes
3316			GEO	General encapsulated object
3317			GEOB	General encapsulated object
3318			GRID	Group identification registration
3319			IPL	Involved people list
3320			IPLS	Involved people list
3321			LINK	Linked information
3322			LNK	Linked information
3323			MCDI	Music CD identifier
3324			MCI	Music CD Identifier
3325			MLL	MPEG location lookup table
3326			MLLT	MPEG location lookup table
3327			OWNE	Ownership frame
3328			PCNT	Play counter
3329			PIC	Attached picture
3330			POP	Popularimeter
3331			POPM	Popularimeter
3332			POSS	Position synchronisation frame
3333			PRIV	Private frame
3334			RBUF	Recommended buffer size
3335			REV	Reverb
3336			RVA	Relative volume adjustment
3337			RVA2	Relative volume adjustment (2)
3338			RVAD	Relative volume adjustment
3339			RVRB	Reverb
3340			SEEK	Seek frame
3341			SIGN	Signature frame
3342			SLT	Synchronised lyric/text
3343			STC	Synced tempo codes
3344			SYLT	Synchronised lyric/text
3345			SYTC	Synchronised tempo codes
3346			TAL	Album/Movie/Show title
3347			TALB	Album/Movie/Show title
3348			TBP	BPM (Beats Per Minute)
3349			TBPM	BPM (beats per minute)
3350			TCM	Composer
3351			TCMP	Part of a compilation
3352			TCO	Content type
3353			TCOM	Composer
3354			TCON	Content type
3355			TCOP	Copyright message
3356			TCP	Part of a compilation
3357			TCR	Copyright message
3358			TDA	Date
3359			TDAT	Date
3360			TDEN	Encoding time
3361			TDLY	Playlist delay
3362			TDOR	Original release time
3363			TDRC	Recording time
3364			TDRL	Release time
3365			TDTG	Tagging time
3366			TDY	Playlist delay
3367			TEN	Encoded by
3368			TENC	Encoded by
3369			TEXT	Lyricist/Text writer
3370			TFLT	File type
3371			TFT	File type
3372			TIM	Time
3373			TIME	Time
3374			TIPL	Involved people list
3375			TIT1	Content group description
3376			TIT2	Title/songname/content description
3377			TIT3	Subtitle/Description refinement
3378			TKE	Initial key
3379			TKEY	Initial key
3380			TLA	Language(s)
3381			TLAN	Language(s)
3382			TLE	Length
3383			TLEN	Length
3384			TMCL	Musician credits list
3385			TMED	Media type
3386			TMOO	Mood
3387			TMT	Media type
3388			TOA	Original artist(s)/performer(s)
3389			TOAL	Original album/movie/show title
3390			TOF	Original filename
3391			TOFN	Original filename
3392			TOL	Original Lyricist(s)/text writer(s)
3393			TOLY	Original lyricist(s)/text writer(s)
3394			TOPE	Original artist(s)/performer(s)
3395			TOR	Original release year
3396			TORY	Original release year
3397			TOT	Original album/Movie/Show title
3398			TOWN	File owner/licensee
3399			TP1	Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3400			TP2	Band/Orchestra/Accompaniment
3401			TP3	Conductor/Performer refinement
3402			TP4	Interpreted, remixed, or otherwise modified by
3403			TPA	Part of a set
3404			TPB	Publisher
3405			TPE1	Lead performer(s)/Soloist(s)
3406			TPE2	Band/orchestra/accompaniment
3407			TPE3	Conductor/performer refinement
3408			TPE4	Interpreted, remixed, or otherwise modified by
3409			TPOS	Part of a set
3410			TPRO	Produced notice
3411			TPUB	Publisher
3412			TRC	ISRC (International Standard Recording Code)
3413			TRCK	Track number/Position in set
3414			TRD	Recording dates
3415			TRDA	Recording dates
3416			TRK	Track number/Position in set
3417			TRSN	Internet radio station name
3418			TRSO	Internet radio station owner
3419			TS2	Album-Artist sort order
3420			TSA	Album sort order
3421			TSC	Composer sort order
3422			TSI	Size
3423			TSIZ	Size
3424			TSO2	Album-Artist sort order
3425			TSOA	Album sort order
3426			TSOC	Composer sort order
3427			TSOP	Performer sort order
3428			TSOT	Title sort order
3429			TSP	Performer sort order
3430			TSRC	ISRC (international standard recording code)
3431			TSS	Software/hardware and settings used for encoding
3432			TSSE	Software/Hardware and settings used for encoding
3433			TSST	Set subtitle
3434			TST	Title sort order
3435			TT1	Content group description
3436			TT2	Title/Songname/Content description
3437			TT3	Subtitle/Description refinement
3438			TXT	Lyricist/text writer
3439			TXX	User defined text information frame
3440			TXXX	User defined text information frame
3441			TYE	Year
3442			TYER	Year
3443			UFI	Unique file identifier
3444			UFID	Unique file identifier
3445			ULT	Unsynchronised lyric/text transcription
3446			USER	Terms of use
3447			USLT	Unsynchronised lyric/text transcription
3448			WAF	Official audio file webpage
3449			WAR	Official artist/performer webpage
3450			WAS	Official audio source webpage
3451			WCM	Commercial information
3452			WCOM	Commercial information
3453			WCOP	Copyright/Legal information
3454			WCP	Copyright/Legal information
3455			WOAF	Official audio file webpage
3456			WOAR	Official artist/performer webpage
3457			WOAS	Official audio source webpage
3458			WORS	Official Internet radio station homepage
3459			WPAY	Payment
3460			WPB	Publishers official webpage
3461			WPUB	Publishers official webpage
3462			WXX	User defined URL link frame
3463			WXXX	User defined URL link frame
3464			TFEA	Featured Artist
3465			TSTU	Recording Studio
3466			rgad	Replay Gain Adjustment
3467
3468		*/
3469
3470		return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
3471
3472		// Last three:
3473		// from Helium2 [www.helium2.com]
3474		// from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3475	}
3476
3477	/**
3478	 * @param string $framename
3479	 *
3480	 * @return string
3481	 */
3482	public static function FrameNameShortLookup($framename) {
3483
3484		$begin = __LINE__;
3485
3486		/** This is not a comment!
3487
3488			AENC	audio_encryption
3489			APIC	attached_picture
3490			ASPI	audio_seek_point_index
3491			BUF	recommended_buffer_size
3492			CNT	play_counter
3493			COM	comment
3494			COMM	comment
3495			COMR	commercial_frame
3496			CRA	audio_encryption
3497			CRM	encrypted_meta_frame
3498			ENCR	encryption_method_registration
3499			EQU	equalisation
3500			EQU2	equalisation
3501			EQUA	equalisation
3502			ETC	event_timing_codes
3503			ETCO	event_timing_codes
3504			GEO	general_encapsulated_object
3505			GEOB	general_encapsulated_object
3506			GRID	group_identification_registration
3507			IPL	involved_people_list
3508			IPLS	involved_people_list
3509			LINK	linked_information
3510			LNK	linked_information
3511			MCDI	music_cd_identifier
3512			MCI	music_cd_identifier
3513			MLL	mpeg_location_lookup_table
3514			MLLT	mpeg_location_lookup_table
3515			OWNE	ownership_frame
3516			PCNT	play_counter
3517			PIC	attached_picture
3518			POP	popularimeter
3519			POPM	popularimeter
3520			POSS	position_synchronisation_frame
3521			PRIV	private_frame
3522			RBUF	recommended_buffer_size
3523			REV	reverb
3524			RVA	relative_volume_adjustment
3525			RVA2	relative_volume_adjustment
3526			RVAD	relative_volume_adjustment
3527			RVRB	reverb
3528			SEEK	seek_frame
3529			SIGN	signature_frame
3530			SLT	synchronised_lyric
3531			STC	synced_tempo_codes
3532			SYLT	synchronised_lyric
3533			SYTC	synchronised_tempo_codes
3534			TAL	album
3535			TALB	album
3536			TBP	bpm
3537			TBPM	bpm
3538			TCM	composer
3539			TCMP	part_of_a_compilation
3540			TCO	genre
3541			TCOM	composer
3542			TCON	genre
3543			TCOP	copyright_message
3544			TCP	part_of_a_compilation
3545			TCR	copyright_message
3546			TDA	date
3547			TDAT	date
3548			TDEN	encoding_time
3549			TDLY	playlist_delay
3550			TDOR	original_release_time
3551			TDRC	recording_time
3552			TDRL	release_time
3553			TDTG	tagging_time
3554			TDY	playlist_delay
3555			TEN	encoded_by
3556			TENC	encoded_by
3557			TEXT	lyricist
3558			TFLT	file_type
3559			TFT	file_type
3560			TIM	time
3561			TIME	time
3562			TIPL	involved_people_list
3563			TIT1	content_group_description
3564			TIT2	title
3565			TIT3	subtitle
3566			TKE	initial_key
3567			TKEY	initial_key
3568			TLA	language
3569			TLAN	language
3570			TLE	length
3571			TLEN	length
3572			TMCL	musician_credits_list
3573			TMED	media_type
3574			TMOO	mood
3575			TMT	media_type
3576			TOA	original_artist
3577			TOAL	original_album
3578			TOF	original_filename
3579			TOFN	original_filename
3580			TOL	original_lyricist
3581			TOLY	original_lyricist
3582			TOPE	original_artist
3583			TOR	original_year
3584			TORY	original_year
3585			TOT	original_album
3586			TOWN	file_owner
3587			TP1	artist
3588			TP2	band
3589			TP3	conductor
3590			TP4	remixer
3591			TPA	part_of_a_set
3592			TPB	publisher
3593			TPE1	artist
3594			TPE2	band
3595			TPE3	conductor
3596			TPE4	remixer
3597			TPOS	part_of_a_set
3598			TPRO	produced_notice
3599			TPUB	publisher
3600			TRC	isrc
3601			TRCK	track_number
3602			TRD	recording_dates
3603			TRDA	recording_dates
3604			TRK	track_number
3605			TRSN	internet_radio_station_name
3606			TRSO	internet_radio_station_owner
3607			TS2	album_artist_sort_order
3608			TSA	album_sort_order
3609			TSC	composer_sort_order
3610			TSI	size
3611			TSIZ	size
3612			TSO2	album_artist_sort_order
3613			TSOA	album_sort_order
3614			TSOC	composer_sort_order
3615			TSOP	performer_sort_order
3616			TSOT	title_sort_order
3617			TSP	performer_sort_order
3618			TSRC	isrc
3619			TSS	encoder_settings
3620			TSSE	encoder_settings
3621			TSST	set_subtitle
3622			TST	title_sort_order
3623			TT1	content_group_description
3624			TT2	title
3625			TT3	subtitle
3626			TXT	lyricist
3627			TXX	text
3628			TXXX	text
3629			TYE	year
3630			TYER	year
3631			UFI	unique_file_identifier
3632			UFID	unique_file_identifier
3633			ULT	unsynchronised_lyric
3634			USER	terms_of_use
3635			USLT	unsynchronised_lyric
3636			WAF	url_file
3637			WAR	url_artist
3638			WAS	url_source
3639			WCM	commercial_information
3640			WCOM	commercial_information
3641			WCOP	copyright
3642			WCP	copyright
3643			WOAF	url_file
3644			WOAR	url_artist
3645			WOAS	url_source
3646			WORS	url_station
3647			WPAY	url_payment
3648			WPB	url_publisher
3649			WPUB	url_publisher
3650			WXX	url_user
3651			WXXX	url_user
3652			TFEA	featured_artist
3653			TSTU	recording_studio
3654			rgad	replay_gain_adjustment
3655
3656		*/
3657
3658		return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
3659	}
3660
3661	/**
3662	 * @param string $encoding
3663	 *
3664	 * @return string
3665	 */
3666	public static function TextEncodingTerminatorLookup($encoding) {
3667		// http://www.id3.org/id3v2.4.0-structure.txt
3668		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3669		static $TextEncodingTerminatorLookup = array(
3670			0   => "\x00",     // $00  ISO-8859-1. Terminated with $00.
3671			1   => "\x00\x00", // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3672			2   => "\x00\x00", // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3673			3   => "\x00",     // $03  UTF-8 encoded Unicode. Terminated with $00.
3674			255 => "\x00\x00"
3675		);
3676		return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
3677	}
3678
3679	/**
3680	 * @param int $encoding
3681	 *
3682	 * @return string
3683	 */
3684	public static function TextEncodingNameLookup($encoding) {
3685		// http://www.id3.org/id3v2.4.0-structure.txt
3686		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3687		static $TextEncodingNameLookup = array(
3688			0   => 'ISO-8859-1', // $00  ISO-8859-1. Terminated with $00.
3689			1   => 'UTF-16',     // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3690			2   => 'UTF-16BE',   // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3691			3   => 'UTF-8',      // $03  UTF-8 encoded Unicode. Terminated with $00.
3692			255 => 'UTF-16BE'
3693		);
3694		return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3695	}
3696
3697	/**
3698	 * @param string $string
3699	 * @param string $terminator
3700	 *
3701	 * @return string
3702	 */
3703	public static function RemoveStringTerminator($string, $terminator) {
3704		// Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present.
3705		// https://github.com/JamesHeinrich/getID3/issues/121
3706		// https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227
3707		if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) {
3708			$string = substr($string, 0, -strlen($terminator));
3709		}
3710		return $string;
3711	}
3712
3713	/**
3714	 * @param string $string
3715	 *
3716	 * @return string
3717	 */
3718	public static function MakeUTF16emptyStringEmpty($string) {
3719		if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
3720			// if string only contains a BOM or terminator then make it actually an empty string
3721			$string = '';
3722		}
3723		return $string;
3724	}
3725
3726	/**
3727	 * @param string $framename
3728	 * @param int    $id3v2majorversion
3729	 *
3730	 * @return bool|int
3731	 */
3732	public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3733		switch ($id3v2majorversion) {
3734			case 2:
3735				return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3736
3737			case 3:
3738			case 4:
3739				return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3740		}
3741		return false;
3742	}
3743
3744	/**
3745	 * @param string $numberstring
3746	 * @param bool   $allowdecimal
3747	 * @param bool   $allownegative
3748	 *
3749	 * @return bool
3750	 */
3751	public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3752		for ($i = 0; $i < strlen($numberstring); $i++) {
3753			if ((chr($numberstring[$i]) < chr('0')) || (chr($numberstring[$i]) > chr('9'))) {
3754				if (($numberstring[$i] == '.') && $allowdecimal) {
3755					// allowed
3756				} elseif (($numberstring[$i] == '-') && $allownegative && ($i == 0)) {
3757					// allowed
3758				} else {
3759					return false;
3760				}
3761			}
3762		}
3763		return true;
3764	}
3765
3766	/**
3767	 * @param string $datestamp
3768	 *
3769	 * @return bool
3770	 */
3771	public static function IsValidDateStampString($datestamp) {
3772		if (strlen($datestamp) != 8) {
3773			return false;
3774		}
3775		if (!self::IsANumber($datestamp, false)) {
3776			return false;
3777		}
3778		$year  = substr($datestamp, 0, 4);
3779		$month = substr($datestamp, 4, 2);
3780		$day   = substr($datestamp, 6, 2);
3781		if (($year == 0) || ($month == 0) || ($day == 0)) {
3782			return false;
3783		}
3784		if ($month > 12) {
3785			return false;
3786		}
3787		if ($day > 31) {
3788			return false;
3789		}
3790		if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3791			return false;
3792		}
3793		if (($day > 29) && ($month == 2)) {
3794			return false;
3795		}
3796		return true;
3797	}
3798
3799	/**
3800	 * @param int $majorversion
3801	 *
3802	 * @return int
3803	 */
3804	public static function ID3v2HeaderLength($majorversion) {
3805		return (($majorversion == 2) ? 6 : 10);
3806	}
3807
3808	/**
3809	 * @param string $frame_name
3810	 *
3811	 * @return string|false
3812	 */
3813	public static function ID3v22iTunesBrokenFrameName($frame_name) {
3814		// iTunes (multiple versions) has been known to write ID3v2.3 style frames
3815		// but use ID3v2.2 frame names, right-padded using either [space] or [null]
3816		// to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
3817		// This function will detect and translate the corrupt frame name into ID3v2.3 standard.
3818		static $ID3v22_iTunes_BrokenFrames = array(
3819			'BUF' => 'RBUF', // Recommended buffer size
3820			'CNT' => 'PCNT', // Play counter
3821			'COM' => 'COMM', // Comments
3822			'CRA' => 'AENC', // Audio encryption
3823			'EQU' => 'EQUA', // Equalisation
3824			'ETC' => 'ETCO', // Event timing codes
3825			'GEO' => 'GEOB', // General encapsulated object
3826			'IPL' => 'IPLS', // Involved people list
3827			'LNK' => 'LINK', // Linked information
3828			'MCI' => 'MCDI', // Music CD identifier
3829			'MLL' => 'MLLT', // MPEG location lookup table
3830			'PIC' => 'APIC', // Attached picture
3831			'POP' => 'POPM', // Popularimeter
3832			'REV' => 'RVRB', // Reverb
3833			'RVA' => 'RVAD', // Relative volume adjustment
3834			'SLT' => 'SYLT', // Synchronised lyric/text
3835			'STC' => 'SYTC', // Synchronised tempo codes
3836			'TAL' => 'TALB', // Album/Movie/Show title
3837			'TBP' => 'TBPM', // BPM (beats per minute)
3838			'TCM' => 'TCOM', // Composer
3839			'TCO' => 'TCON', // Content type
3840			'TCP' => 'TCMP', // Part of a compilation
3841			'TCR' => 'TCOP', // Copyright message
3842			'TDA' => 'TDAT', // Date
3843			'TDY' => 'TDLY', // Playlist delay
3844			'TEN' => 'TENC', // Encoded by
3845			'TFT' => 'TFLT', // File type
3846			'TIM' => 'TIME', // Time
3847			'TKE' => 'TKEY', // Initial key
3848			'TLA' => 'TLAN', // Language(s)
3849			'TLE' => 'TLEN', // Length
3850			'TMT' => 'TMED', // Media type
3851			'TOA' => 'TOPE', // Original artist(s)/performer(s)
3852			'TOF' => 'TOFN', // Original filename
3853			'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
3854			'TOR' => 'TORY', // Original release year
3855			'TOT' => 'TOAL', // Original album/movie/show title
3856			'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
3857			'TP2' => 'TPE2', // Band/orchestra/accompaniment
3858			'TP3' => 'TPE3', // Conductor/performer refinement
3859			'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
3860			'TPA' => 'TPOS', // Part of a set
3861			'TPB' => 'TPUB', // Publisher
3862			'TRC' => 'TSRC', // ISRC (international standard recording code)
3863			'TRD' => 'TRDA', // Recording dates
3864			'TRK' => 'TRCK', // Track number/Position in set
3865			'TS2' => 'TSO2', // Album-Artist sort order
3866			'TSA' => 'TSOA', // Album sort order
3867			'TSC' => 'TSOC', // Composer sort order
3868			'TSI' => 'TSIZ', // Size
3869			'TSP' => 'TSOP', // Performer sort order
3870			'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
3871			'TST' => 'TSOT', // Title sort order
3872			'TT1' => 'TIT1', // Content group description
3873			'TT2' => 'TIT2', // Title/songname/content description
3874			'TT3' => 'TIT3', // Subtitle/Description refinement
3875			'TXT' => 'TEXT', // Lyricist/Text writer
3876			'TXX' => 'TXXX', // User defined text information frame
3877			'TYE' => 'TYER', // Year
3878			'UFI' => 'UFID', // Unique file identifier
3879			'ULT' => 'USLT', // Unsynchronised lyric/text transcription
3880			'WAF' => 'WOAF', // Official audio file webpage
3881			'WAR' => 'WOAR', // Official artist/performer webpage
3882			'WAS' => 'WOAS', // Official audio source webpage
3883			'WCM' => 'WCOM', // Commercial information
3884			'WCP' => 'WCOP', // Copyright/Legal information
3885			'WPB' => 'WPUB', // Publishers official webpage
3886			'WXX' => 'WXXX', // User defined URL link frame
3887		);
3888		if (strlen($frame_name) == 4) {
3889			if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) {
3890				if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) {
3891					return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)];
3892				}
3893			}
3894		}
3895		return false;
3896	}
3897
3898}
3899
3900