3742 lines
		
	
	
	
		
			147 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
		
		
			
		
	
	
			3742 lines
		
	
	
	
		
			147 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
|  | <?php | ||
|  | /////////////////////////////////////////////////////////////////
 | ||
|  | /// getID3() by James Heinrich <info@getid3.org>               //
 | ||
|  | //  available at http://getid3.sourceforge.net                 //
 | ||
|  | //            or http://www.getid3.org                         //
 | ||
|  | //          also https://github.com/JamesHeinrich/getID3       //
 | ||
|  | /////////////////////////////////////////////////////////////////
 | ||
|  | // See readme.txt for more details                             //
 | ||
|  | /////////////////////////////////////////////////////////////////
 | ||
|  | ///                                                            //
 | ||
|  | // module.tag.id3v2.php                                        //
 | ||
|  | // module for analyzing ID3v2 tags                             //
 | ||
|  | // dependencies: module.tag.id3v1.php                          //
 | ||
|  | //                                                            ///
 | ||
|  | /////////////////////////////////////////////////////////////////
 | ||
|  | 
 | ||
|  | getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); | ||
|  | 
 | ||
|  | class getid3_id3v2 extends getid3_handler | ||
|  | { | ||
|  | 	public $StartingOffset = 0; | ||
|  | 
 | ||
|  | 	public function Analyze() { | ||
|  | 		$info = &$this->getid3->info; | ||
|  | 
 | ||
|  | 		//    Overall tag structure:
 | ||
|  | 		//        +-----------------------------+
 | ||
|  | 		//        |      Header (10 bytes)      |
 | ||
|  | 		//        +-----------------------------+
 | ||
|  | 		//        |       Extended Header       |
 | ||
|  | 		//        | (variable length, OPTIONAL) |
 | ||
|  | 		//        +-----------------------------+
 | ||
|  | 		//        |   Frames (variable length)  |
 | ||
|  | 		//        +-----------------------------+
 | ||
|  | 		//        |           Padding           |
 | ||
|  | 		//        | (variable length, OPTIONAL) |
 | ||
|  | 		//        +-----------------------------+
 | ||
|  | 		//        | Footer (10 bytes, OPTIONAL) |
 | ||
|  | 		//        +-----------------------------+
 | ||
|  | 
 | ||
|  | 		//    Header
 | ||
|  | 		//        ID3v2/file identifier      "ID3"
 | ||
|  | 		//        ID3v2 version              $04 00
 | ||
|  | 		//        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
 | ||
|  | 		//        ID3v2 size             4 * %0xxxxxxx
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		// shortcuts
 | ||
|  | 		$info['id3v2']['header'] = true; | ||
|  | 		$thisfile_id3v2                  = &$info['id3v2']; | ||
|  | 		$thisfile_id3v2['flags']         =  array(); | ||
|  | 		$thisfile_id3v2_flags            = &$thisfile_id3v2['flags']; | ||
|  | 
 | ||
|  | 
 | ||
|  | 		$this->fseek($this->StartingOffset); | ||
|  | 		$header = $this->fread(10); | ||
|  | 		if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) { | ||
|  | 
 | ||
|  | 			$thisfile_id3v2['majorversion'] = ord($header{3}); | ||
|  | 			$thisfile_id3v2['minorversion'] = ord($header{4}); | ||
|  | 
 | ||
|  | 			// shortcut
 | ||
|  | 			$id3v2_majorversion = &$thisfile_id3v2['majorversion']; | ||
|  | 
 | ||
|  | 		} else { | ||
|  | 
 | ||
|  | 			unset($info['id3v2']); | ||
|  | 			return false; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
 | ||
|  | 
 | ||
|  | 			$this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']); | ||
|  | 			return false; | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		$id3_flags = ord($header{5}); | ||
|  | 		switch ($id3v2_majorversion) { | ||
|  | 			case 2: | ||
|  | 				// %ab000000 in v2.2
 | ||
|  | 				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
 | ||
|  | 				$thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
 | ||
|  | 				break; | ||
|  | 
 | ||
|  | 			case 3: | ||
|  | 				// %abc00000 in v2.3
 | ||
|  | 				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
 | ||
|  | 				$thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
 | ||
|  | 				$thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
 | ||
|  | 				break; | ||
|  | 
 | ||
|  | 			case 4: | ||
|  | 				// %abcd0000 in v2.4
 | ||
|  | 				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
 | ||
|  | 				$thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
 | ||
|  | 				$thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
 | ||
|  | 				$thisfile_id3v2_flags['isfooter']    = (bool) ($id3_flags & 0x10); // d - Footer present
 | ||
|  | 				break; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		$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
 | ||
|  | 
 | ||
|  | 		$thisfile_id3v2['tag_offset_start'] = $this->StartingOffset; | ||
|  | 		$thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength']; | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		// create 'encoding' key - used by getid3::HandleAllTags()
 | ||
|  | 		// in ID3v2 every field can have it's own encoding type
 | ||
|  | 		// so force everything to UTF-8 so it can be handled consistantly
 | ||
|  | 		$thisfile_id3v2['encoding'] = 'UTF-8'; | ||
|  | 
 | ||
|  | 
 | ||
|  | 	//    Frames
 | ||
|  | 
 | ||
|  | 	//        All ID3v2 frames consists of one frame header followed by one or more
 | ||
|  | 	//        fields containing the actual information. The header is always 10
 | ||
|  | 	//        bytes and laid out as follows:
 | ||
|  | 	//
 | ||
|  | 	//        Frame ID      $xx xx xx xx  (four characters)
 | ||
|  | 	//        Size      4 * %0xxxxxxx
 | ||
|  | 	//        Flags         $xx xx
 | ||
|  | 
 | ||
|  | 		$sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
 | ||
|  | 		if (!empty($thisfile_id3v2['exthead']['length'])) { | ||
|  | 			$sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4); | ||
|  | 		} | ||
|  | 		if (!empty($thisfile_id3v2_flags['isfooter'])) { | ||
|  | 			$sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
 | ||
|  | 		} | ||
|  | 		if ($sizeofframes > 0) { | ||
|  | 
 | ||
|  | 			$framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
 | ||
|  | 
 | ||
|  | 			//    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
 | ||
|  | 			if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) { | ||
|  | 				$framedata = $this->DeUnsynchronise($framedata); | ||
|  | 			} | ||
|  | 			//        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
 | ||
|  | 			//        of on tag level, making it easier to skip frames, increasing the streamability
 | ||
|  | 			//        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
 | ||
|  | 			//        there exists an unsynchronised frame, while the new unsynchronisation flag in
 | ||
|  | 			//        the frame header [S:4.1.2] indicates unsynchronisation.
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 			//$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)
 | ||
|  | 			$framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 			//    Extended Header
 | ||
|  | 			if (!empty($thisfile_id3v2_flags['exthead'])) { | ||
|  | 				$extended_header_offset = 0; | ||
|  | 
 | ||
|  | 				if ($id3v2_majorversion == 3) { | ||
|  | 
 | ||
|  | 					// v2.3 definition:
 | ||
|  | 					//Extended header size  $xx xx xx xx   // 32-bit integer
 | ||
|  | 					//Extended Flags        $xx xx
 | ||
|  | 					//     %x0000000 %00000000 // v2.3
 | ||
|  | 					//     x - CRC data present
 | ||
|  | 					//Size of padding       $xx xx xx xx
 | ||
|  | 
 | ||
|  | 					$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0); | ||
|  | 					$extended_header_offset += 4; | ||
|  | 
 | ||
|  | 					$thisfile_id3v2['exthead']['flag_bytes'] = 2; | ||
|  | 					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes'])); | ||
|  | 					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes']; | ||
|  | 
 | ||
|  | 					$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000); | ||
|  | 
 | ||
|  | 					$thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4)); | ||
|  | 					$extended_header_offset += 4; | ||
|  | 
 | ||
|  | 					if ($thisfile_id3v2['exthead']['flags']['crc']) { | ||
|  | 						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4)); | ||
|  | 						$extended_header_offset += 4; | ||
|  | 					} | ||
|  | 					$extended_header_offset += $thisfile_id3v2['exthead']['padding_size']; | ||
|  | 
 | ||
|  | 				} elseif ($id3v2_majorversion == 4) { | ||
|  | 
 | ||
|  | 					// v2.4 definition:
 | ||
|  | 					//Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
 | ||
|  | 					//Number of flag bytes       $01
 | ||
|  | 					//Extended Flags             $xx
 | ||
|  | 					//     %0bcd0000 // v2.4
 | ||
|  | 					//     b - Tag is an update
 | ||
|  | 					//         Flag data length       $00
 | ||
|  | 					//     c - CRC data present
 | ||
|  | 					//         Flag data length       $05
 | ||
|  | 					//         Total frame CRC    5 * %0xxxxxxx
 | ||
|  | 					//     d - Tag restrictions
 | ||
|  | 					//         Flag data length       $01
 | ||
|  | 
 | ||
|  | 					$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true); | ||
|  | 					$extended_header_offset += 4; | ||
|  | 
 | ||
|  | 					$thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
 | ||
|  | 					$extended_header_offset += 1; | ||
|  | 
 | ||
|  | 					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes'])); | ||
|  | 					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes']; | ||
|  | 
 | ||
|  | 					$thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40); | ||
|  | 					$thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20); | ||
|  | 					$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10); | ||
|  | 
 | ||
|  | 					if ($thisfile_id3v2['exthead']['flags']['update']) { | ||
|  | 						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
 | ||
|  | 						$extended_header_offset += 1; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					if ($thisfile_id3v2['exthead']['flags']['crc']) { | ||
|  | 						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
 | ||
|  | 						$extended_header_offset += 1; | ||
|  | 						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false); | ||
|  | 						$extended_header_offset += $ext_header_chunk_length; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					if ($thisfile_id3v2['exthead']['flags']['restrictions']) { | ||
|  | 						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
 | ||
|  | 						$extended_header_offset += 1; | ||
|  | 
 | ||
|  | 						// %ppqrrstt
 | ||
|  | 						$restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); | ||
|  | 						$extended_header_offset += 1; | ||
|  | 						$thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
 | ||
|  | 						$thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
 | ||
|  | 						$thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
 | ||
|  | 						$thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
 | ||
|  | 						$thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
 | ||
|  | 
 | ||
|  | 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize']  = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']); | ||
|  | 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc']  = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']); | ||
|  | 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']); | ||
|  | 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']); | ||
|  | 						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']); | ||
|  | 					} | ||
|  | 
 | ||
|  | 					if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) { | ||
|  | 						$this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')'); | ||
|  | 					} | ||
|  | 				} | ||
|  | 
 | ||
|  | 				$framedataoffset += $extended_header_offset; | ||
|  | 				$framedata = substr($framedata, $extended_header_offset); | ||
|  | 			} // end extended header
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 			while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
 | ||
|  | 				if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) { | ||
|  | 					// insufficient room left in ID3v2 header for actual data - must be padding
 | ||
|  | 					$thisfile_id3v2['padding']['start']  = $framedataoffset; | ||
|  | 					$thisfile_id3v2['padding']['length'] = strlen($framedata); | ||
|  | 					$thisfile_id3v2['padding']['valid']  = true; | ||
|  | 					for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) { | ||
|  | 						if ($framedata{$i} != "\x00") { | ||
|  | 							$thisfile_id3v2['padding']['valid'] = false; | ||
|  | 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; | ||
|  | 							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'); | ||
|  | 							break; | ||
|  | 						} | ||
|  | 					} | ||
|  | 					break; // skip rest of ID3v2 header
 | ||
|  | 				} | ||
|  | 				if ($id3v2_majorversion == 2) { | ||
|  | 					// Frame ID  $xx xx xx (three characters)
 | ||
|  | 					// Size      $xx xx xx (24-bit integer)
 | ||
|  | 					// Flags     $xx xx
 | ||
|  | 
 | ||
|  | 					$frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
 | ||
|  | 					$framedata    = substr($framedata, 6);    // and leave the rest in $framedata
 | ||
|  | 					$frame_name   = substr($frame_header, 0, 3); | ||
|  | 					$frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0); | ||
|  | 					$frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
 | ||
|  | 
 | ||
|  | 				} elseif ($id3v2_majorversion > 2) { | ||
|  | 
 | ||
|  | 					// Frame ID  $xx xx xx xx (four characters)
 | ||
|  | 					// Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
 | ||
|  | 					// Flags     $xx xx
 | ||
|  | 
 | ||
|  | 					$frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
 | ||
|  | 					$framedata    = substr($framedata, 10);    // and leave the rest in $framedata
 | ||
|  | 
 | ||
|  | 					$frame_name = substr($frame_header, 0, 4); | ||
|  | 					if ($id3v2_majorversion == 3) { | ||
|  | 						$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
 | ||
|  | 					} else { // ID3v2.4+
 | ||
|  | 						$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
 | ||
|  | 					} | ||
|  | 
 | ||
|  | 					if ($frame_size < (strlen($framedata) + 4)) { | ||
|  | 						$nextFrameID = substr($framedata, $frame_size, 4); | ||
|  | 						if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) { | ||
|  | 							// next frame is OK
 | ||
|  | 						} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) { | ||
|  | 							// MP3ext known broken frames - "ok" for the purposes of this test
 | ||
|  | 						} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) { | ||
|  | 							$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'); | ||
|  | 							$id3v2_majorversion = 3; | ||
|  | 							$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
 | ||
|  | 						} | ||
|  | 					} | ||
|  | 
 | ||
|  | 
 | ||
|  | 					$frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2)); | ||
|  | 				} | ||
|  | 
 | ||
|  | 				if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) { | ||
|  | 					// padding encountered
 | ||
|  | 
 | ||
|  | 					$thisfile_id3v2['padding']['start']  = $framedataoffset; | ||
|  | 					$thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata); | ||
|  | 					$thisfile_id3v2['padding']['valid']  = true; | ||
|  | 
 | ||
|  | 					$len = strlen($framedata); | ||
|  | 					for ($i = 0; $i < $len; $i++) { | ||
|  | 						if ($framedata{$i} != "\x00") { | ||
|  | 							$thisfile_id3v2['padding']['valid'] = false; | ||
|  | 							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; | ||
|  | 							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'); | ||
|  | 							break; | ||
|  | 						} | ||
|  | 					} | ||
|  | 					break; // skip rest of ID3v2 header
 | ||
|  | 				} | ||
|  | 
 | ||
|  | 				if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) { | ||
|  | 					$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.'); | ||
|  | 					$frame_name = $iTunesBrokenFrameNameFixed; | ||
|  | 				} | ||
|  | 				if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { | ||
|  | 
 | ||
|  | 					unset($parsedFrame); | ||
|  | 					$parsedFrame['frame_name']      = $frame_name; | ||
|  | 					$parsedFrame['frame_flags_raw'] = $frame_flags; | ||
|  | 					$parsedFrame['data']            = substr($framedata, 0, $frame_size); | ||
|  | 					$parsedFrame['datalength']      = getid3_lib::CastAsInt($frame_size); | ||
|  | 					$parsedFrame['dataoffset']      = $framedataoffset; | ||
|  | 
 | ||
|  | 					$this->ParseID3v2Frame($parsedFrame); | ||
|  | 					$thisfile_id3v2[$frame_name][] = $parsedFrame; | ||
|  | 
 | ||
|  | 					$framedata = substr($framedata, $frame_size); | ||
|  | 
 | ||
|  | 				} else { // invalid frame length or FrameID
 | ||
|  | 
 | ||
|  | 					if ($frame_size <= strlen($framedata)) { | ||
|  | 
 | ||
|  | 						if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) { | ||
|  | 
 | ||
|  | 							// next frame is valid, just skip the current frame
 | ||
|  | 							$framedata = substr($framedata, $frame_size); | ||
|  | 							$this->warning('Next ID3v2 frame is valid, skipping current frame.'); | ||
|  | 
 | ||
|  | 						} else { | ||
|  | 
 | ||
|  | 							// next frame is invalid too, abort processing
 | ||
|  | 							//unset($framedata);
 | ||
|  | 							$framedata = null; | ||
|  | 							$this->error('Next ID3v2 frame is also invalid, aborting processing.'); | ||
|  | 
 | ||
|  | 						} | ||
|  | 
 | ||
|  | 					} elseif ($frame_size == strlen($framedata)) { | ||
|  | 
 | ||
|  | 						// this is the last frame, just skip
 | ||
|  | 						$this->warning('This was the last ID3v2 frame.'); | ||
|  | 
 | ||
|  | 					} else { | ||
|  | 
 | ||
|  | 						// next frame is invalid too, abort processing
 | ||
|  | 						//unset($framedata);
 | ||
|  | 						$framedata = null; | ||
|  | 						$this->warning('Invalid ID3v2 frame size, aborting.'); | ||
|  | 
 | ||
|  | 					} | ||
|  | 					if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) { | ||
|  | 
 | ||
|  | 						switch ($frame_name) { | ||
|  | 							case "\x00\x00".'MP': | ||
|  | 							case "\x00".'MP3': | ||
|  | 							case ' MP3': | ||
|  | 							case 'MP3e': | ||
|  | 							case "\x00".'MP': | ||
|  | 							case ' MP': | ||
|  | 							case 'MP3': | ||
|  | 								$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/)"]'); | ||
|  | 								break; | ||
|  | 
 | ||
|  | 							default: | ||
|  | 								$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).'); | ||
|  | 								break; | ||
|  | 						} | ||
|  | 
 | ||
|  | 					} elseif (!isset($framedata) || ($frame_size > strlen($framedata))) { | ||
|  | 
 | ||
|  | 						$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').')).'); | ||
|  | 
 | ||
|  | 					} else { | ||
|  | 
 | ||
|  | 						$this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).'); | ||
|  | 
 | ||
|  | 					} | ||
|  | 
 | ||
|  | 				} | ||
|  | 				$framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion)); | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	//    Footer
 | ||
|  | 
 | ||
|  | 	//    The footer is a copy of the header, but with a different identifier.
 | ||
|  | 	//        ID3v2 identifier           "3DI"
 | ||
|  | 	//        ID3v2 version              $04 00
 | ||
|  | 	//        ID3v2 flags                %abcd0000
 | ||
|  | 	//        ID3v2 size             4 * %0xxxxxxx
 | ||
|  | 
 | ||
|  | 		if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) { | ||
|  | 			$footer = $this->fread(10); | ||
|  | 			if (substr($footer, 0, 3) == '3DI') { | ||
|  | 				$thisfile_id3v2['footer'] = true; | ||
|  | 				$thisfile_id3v2['majorversion_footer'] = ord($footer{3}); | ||
|  | 				$thisfile_id3v2['minorversion_footer'] = ord($footer{4}); | ||
|  | 			} | ||
|  | 			if ($thisfile_id3v2['majorversion_footer'] <= 4) { | ||
|  | 				$id3_flags = ord(substr($footer{5})); | ||
|  | 				$thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80); | ||
|  | 				$thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40); | ||
|  | 				$thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20); | ||
|  | 				$thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10); | ||
|  | 
 | ||
|  | 				$thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1); | ||
|  | 			} | ||
|  | 		} // end footer
 | ||
|  | 
 | ||
|  | 		if (isset($thisfile_id3v2['comments']['genre'])) { | ||
|  | 			$genres = array(); | ||
|  | 			foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) { | ||
|  | 				foreach ($this->ParseID3v2GenreString($value) as $genre) { | ||
|  | 					$genres[] = $genre; | ||
|  | 				} | ||
|  | 			} | ||
|  | 			$thisfile_id3v2['comments']['genre'] = array_unique($genres); | ||
|  | 			unset($key, $value, $genres, $genre); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if (isset($thisfile_id3v2['comments']['track'])) { | ||
|  | 			foreach ($thisfile_id3v2['comments']['track'] as $key => $value) { | ||
|  | 				if (strstr($value, '/')) { | ||
|  | 					list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		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)) { | ||
|  | 			$thisfile_id3v2['comments']['year'] = array($matches[1]); | ||
|  | 		} | ||
|  | 
 | ||
|  | 
 | ||
|  | 		if (!empty($thisfile_id3v2['TXXX'])) { | ||
|  | 			// MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
 | ||
|  | 			foreach ($thisfile_id3v2['TXXX'] as $txxx_array) { | ||
|  | 				switch ($txxx_array['description']) { | ||
|  | 					case 'replaygain_track_gain': | ||
|  | 						if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) { | ||
|  | 							$info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); | ||
|  | 						} | ||
|  | 						break; | ||
|  | 					case 'replaygain_track_peak': | ||
|  | 						if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) { | ||
|  | 							$info['replay_gain']['track']['peak'] = floatval($txxx_array['data']); | ||
|  | 						} | ||
|  | 						break; | ||
|  | 					case 'replaygain_album_gain': | ||
|  | 						if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) { | ||
|  | 							$info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); | ||
|  | 						} | ||
|  | 						break; | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 
 | ||
|  | 		// Set avdataoffset
 | ||
|  | 		$info['avdataoffset'] = $thisfile_id3v2['headerlength']; | ||
|  | 		if (isset($thisfile_id3v2['footer'])) { | ||
|  | 			$info['avdataoffset'] += 10; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		return true; | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	public function ParseID3v2GenreString($genrestring) { | ||
|  | 		// Parse genres into arrays of genreName and genreID
 | ||
|  | 		// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
 | ||
|  | 		// ID3v2.4.x: '21' $00 'Eurodisco' $00
 | ||
|  | 		$clean_genres = array(); | ||
|  | 
 | ||
|  | 		// hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
 | ||
|  | 		if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) { | ||
|  | 			// note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
 | ||
|  | 			// replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
 | ||
|  | 			if (preg_match('#/#', $genrestring)) { | ||
|  | 				$genrestring = str_replace('/', "\x00", $genrestring); | ||
|  | 				$genrestring = str_replace('Pop'."\x00".'Funk', 'Pop/Funk', $genrestring); | ||
|  | 				$genrestring = str_replace('Rock'."\x00".'Rock', 'Folk/Rock', $genrestring); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
 | ||
|  | 			if (preg_match('#;#', $genrestring)) { | ||
|  | 				$genrestring = str_replace(';', "\x00", $genrestring); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 
 | ||
|  | 		if (strpos($genrestring, "\x00") === false) { | ||
|  | 			$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		$genre_elements = explode("\x00", $genrestring); | ||
|  | 		foreach ($genre_elements as $element) { | ||
|  | 			$element = trim($element); | ||
|  | 			if ($element) { | ||
|  | 				if (preg_match('#^[0-9]{1,3}#', $element)) { | ||
|  | 					$clean_genres[] = getid3_id3v1::LookupGenreName($element); | ||
|  | 				} else { | ||
|  | 					$clean_genres[] = str_replace('((', '(', $element); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return $clean_genres; | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	public function ParseID3v2Frame(&$parsedFrame) { | ||
|  | 
 | ||
|  | 		// shortcuts
 | ||
|  | 		$info = &$this->getid3->info; | ||
|  | 		$id3v2_majorversion = $info['id3v2']['majorversion']; | ||
|  | 
 | ||
|  | 		$parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']); | ||
|  | 		if (empty($parsedFrame['framenamelong'])) { | ||
|  | 			unset($parsedFrame['framenamelong']); | ||
|  | 		} | ||
|  | 		$parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']); | ||
|  | 		if (empty($parsedFrame['framenameshort'])) { | ||
|  | 			unset($parsedFrame['framenameshort']); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
 | ||
|  | 			if ($id3v2_majorversion == 3) { | ||
|  | 				//    Frame Header Flags
 | ||
|  | 				//    %abc00000 %ijk00000
 | ||
|  | 				$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
 | ||
|  | 				$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
 | ||
|  | 				$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
 | ||
|  | 				$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
 | ||
|  | 				$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
 | ||
|  | 				$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
 | ||
|  | 
 | ||
|  | 			} elseif ($id3v2_majorversion == 4) { | ||
|  | 				//    Frame Header Flags
 | ||
|  | 				//    %0abc0000 %0h00kmnp
 | ||
|  | 				$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
 | ||
|  | 				$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
 | ||
|  | 				$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
 | ||
|  | 				$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
 | ||
|  | 				$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
 | ||
|  | 				$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
 | ||
|  | 				$parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
 | ||
|  | 				$parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
 | ||
|  | 
 | ||
|  | 				// Frame-level de-unsynchronisation - ID3v2.4
 | ||
|  | 				if ($parsedFrame['flags']['Unsynchronisation']) { | ||
|  | 					$parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']); | ||
|  | 				} | ||
|  | 
 | ||
|  | 				if ($parsedFrame['flags']['DataLengthIndicator']) { | ||
|  | 					$parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1); | ||
|  | 					$parsedFrame['data']                  =                           substr($parsedFrame['data'], 4); | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			//    Frame-level de-compression
 | ||
|  | 			if ($parsedFrame['flags']['compression']) { | ||
|  | 				$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4)); | ||
|  | 				if (!function_exists('gzuncompress')) { | ||
|  | 					$this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'); | ||
|  | 				} else { | ||
|  | 					if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { | ||
|  | 					//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
 | ||
|  | 						$parsedFrame['data'] = $decompresseddata; | ||
|  | 						unset($decompresseddata); | ||
|  | 					} else { | ||
|  | 						$this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'); | ||
|  | 					} | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if (!empty($parsedFrame['flags']['DataLengthIndicator'])) { | ||
|  | 			if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) { | ||
|  | 				$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'); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) { | ||
|  | 
 | ||
|  | 			$warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion'; | ||
|  | 			switch ($parsedFrame['frame_name']) { | ||
|  | 				case 'WCOM': | ||
|  | 					$warning .= ' (this is known to happen with files tagged by RioPort)'; | ||
|  | 					break; | ||
|  | 
 | ||
|  | 				default: | ||
|  | 					break; | ||
|  | 			} | ||
|  | 			$this->warning($warning); | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
 | ||
|  | 			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
 | ||
|  | 			//   There may be more than one 'UFID' frame in a tag,
 | ||
|  | 			//   but only one with the same 'Owner identifier'.
 | ||
|  | 			// <Header for 'Unique file identifier', ID: 'UFID'>
 | ||
|  | 			// Owner identifier        <text string> $00
 | ||
|  | 			// Identifier              <up to 64 bytes binary data>
 | ||
|  | 			$exploded = explode("\x00", $parsedFrame['data'], 2); | ||
|  | 			$parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : ''); | ||
|  | 			$parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : ''); | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
 | ||
|  | 			//   There may be more than one 'TXXX' frame in each tag,
 | ||
|  | 			//   but only one with the same description.
 | ||
|  | 			// <Header for 'User defined text information frame', ID: 'TXXX'>
 | ||
|  | 			// Text encoding     $xx
 | ||
|  | 			// Description       <text string according to encoding> $00 (00)
 | ||
|  | 			// Value             <text string according to encoding>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 				$frame_textencoding_terminator = "\x00"; | ||
|  | 			} | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); | ||
|  | 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 			} | ||
|  | 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { | ||
|  | 				// if description only contains a BOM or terminator then make it blank
 | ||
|  | 				$frame_description = ''; | ||
|  | 			} | ||
|  | 			$parsedFrame['encodingid']  = $frame_textencoding; | ||
|  | 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 			$parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description)); | ||
|  | 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); | ||
|  | 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { | ||
|  | 				$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); | ||
|  | 				if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) { | ||
|  | 					$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); | ||
|  | 				} else { | ||
|  | 					$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); | ||
|  | 				} | ||
|  | 			} | ||
|  | 			//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
 | ||
|  | 			//   There may only be one text information frame of its kind in an tag.
 | ||
|  | 			// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
 | ||
|  | 			// excluding 'TXXX' described in 4.2.6.>
 | ||
|  | 			// Text encoding                $xx
 | ||
|  | 			// Information                  <text string(s) according to encoding>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 
 | ||
|  | 			$parsedFrame['encodingid'] = $frame_textencoding; | ||
|  | 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { | ||
|  | 				// ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
 | ||
|  | 				// This of course breaks when an artist name contains slash character, e.g. "AC/DC"
 | ||
|  | 				// MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
 | ||
|  | 				// getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
 | ||
|  | 				switch ($parsedFrame['encoding']) { | ||
|  | 					case 'UTF-16': | ||
|  | 					case 'UTF-16BE': | ||
|  | 					case 'UTF-16LE': | ||
|  | 						$wordsize = 2; | ||
|  | 						break; | ||
|  | 					case 'ISO-8859-1': | ||
|  | 					case 'UTF-8': | ||
|  | 					default: | ||
|  | 						$wordsize = 1; | ||
|  | 						break; | ||
|  | 				} | ||
|  | 				$Txxx_elements = array(); | ||
|  | 				$Txxx_elements_start_offset = 0; | ||
|  | 				for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) { | ||
|  | 					if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) { | ||
|  | 						$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset); | ||
|  | 						$Txxx_elements_start_offset = $i + $wordsize; | ||
|  | 					} | ||
|  | 				} | ||
|  | 				$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset); | ||
|  | 				foreach ($Txxx_elements as $Txxx_element) { | ||
|  | 					$string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element); | ||
|  | 					if (!empty($string)) { | ||
|  | 						$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; | ||
|  | 					} | ||
|  | 				} | ||
|  | 				unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset); | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
 | ||
|  | 			//   There may be more than one 'WXXX' frame in each tag,
 | ||
|  | 			//   but only one with the same description
 | ||
|  | 			// <Header for 'User defined URL link frame', ID: 'WXXX'>
 | ||
|  | 			// Text encoding     $xx
 | ||
|  | 			// Description       <text string according to encoding> $00 (00)
 | ||
|  | 			// URL               <text string>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 				$frame_textencoding_terminator = "\x00"; | ||
|  | 			} | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); | ||
|  | 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 			} | ||
|  | 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { | ||
|  | 				// if description only contains a BOM or terminator then make it blank
 | ||
|  | 				$frame_description = ''; | ||
|  | 			} | ||
|  | 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator); | ||
|  | 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 			} | ||
|  | 			if ($frame_terminatorpos) { | ||
|  | 				// there are null bytes after the data - this is not according to spec
 | ||
|  | 				// only use data up to first null byte
 | ||
|  | 				$frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos); | ||
|  | 			} else { | ||
|  | 				// no null bytes following data, just use all data
 | ||
|  | 				$frame_urldata = (string) $parsedFrame['data']; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			$parsedFrame['encodingid']  = $frame_textencoding; | ||
|  | 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 			$parsedFrame['url']         = $frame_urldata; | ||
|  | 			$parsedFrame['description'] = $frame_description; | ||
|  | 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { | ||
|  | 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']); | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
 | ||
|  | 			//   There may only be one URL link frame of its kind in a tag,
 | ||
|  | 			//   except when stated otherwise in the frame description
 | ||
|  | 			// <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
 | ||
|  | 			// described in 4.3.2.>
 | ||
|  | 			// URL              <text string>
 | ||
|  | 
 | ||
|  | 			$parsedFrame['url'] = trim($parsedFrame['data']); | ||
|  | 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { | ||
|  | 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url']; | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
 | ||
|  | 			// http://id3.org/id3v2.3.0#sec4.4
 | ||
|  | 			//   There may only be one 'IPL' frame in each tag
 | ||
|  | 			// <Header for 'User defined URL link frame', ID: 'IPL'>
 | ||
|  | 			// Text encoding     $xx
 | ||
|  | 			// People list strings    <textstrings>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 			} | ||
|  | 			$parsedFrame['encodingid'] = $frame_textencoding; | ||
|  | 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']); | ||
|  | 			$parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 
 | ||
|  | 			// http://www.getid3.org/phpBB3/viewtopic.php?t=1369
 | ||
|  | 			// "this tag typically contains null terminated strings, which are associated in pairs"
 | ||
|  | 			// "there are users that use the tag incorrectly"
 | ||
|  | 			$IPLS_parts = array(); | ||
|  | 			if (strpos($parsedFrame['data_raw'], "\x00") !== false) { | ||
|  | 				$IPLS_parts_unsorted = array(); | ||
|  | 				if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) { | ||
|  | 					// 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
 | ||
|  | 					$thisILPS  = ''; | ||
|  | 					for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) { | ||
|  | 						$twobytes = substr($parsedFrame['data_raw'], $i, 2); | ||
|  | 						if ($twobytes === "\x00\x00") { | ||
|  | 							$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); | ||
|  | 							$thisILPS  = ''; | ||
|  | 						} else { | ||
|  | 							$thisILPS .= $twobytes; | ||
|  | 						} | ||
|  | 					} | ||
|  | 					if (strlen($thisILPS) > 2) { // 2-byte BOM
 | ||
|  | 						$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); | ||
|  | 					} | ||
|  | 				} else { | ||
|  | 					// ISO-8859-1 or UTF-8 or other single-byte-null character set
 | ||
|  | 					$IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']); | ||
|  | 				} | ||
|  | 				if (count($IPLS_parts_unsorted) == 1) { | ||
|  | 					// just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
 | ||
|  | 					foreach ($IPLS_parts_unsorted as $key => $value) { | ||
|  | 						$IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value); | ||
|  | 						$position = ''; | ||
|  | 						foreach ($IPLS_parts_sorted as $person) { | ||
|  | 							$IPLS_parts[] = array('position'=>$position, 'person'=>$person); | ||
|  | 						} | ||
|  | 					} | ||
|  | 				} elseif ((count($IPLS_parts_unsorted) % 2) == 0) { | ||
|  | 					$position = ''; | ||
|  | 					$person   = ''; | ||
|  | 					foreach ($IPLS_parts_unsorted as $key => $value) { | ||
|  | 						if (($key % 2) == 0) { | ||
|  | 							$position = $value; | ||
|  | 						} else { | ||
|  | 							$person   = $value; | ||
|  | 							$IPLS_parts[] = array('position'=>$position, 'person'=>$person); | ||
|  | 							$position = ''; | ||
|  | 							$person   = ''; | ||
|  | 						} | ||
|  | 					} | ||
|  | 				} else { | ||
|  | 					foreach ($IPLS_parts_unsorted as $key => $value) { | ||
|  | 						$IPLS_parts[] = array($value); | ||
|  | 					} | ||
|  | 				} | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 				$IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']); | ||
|  | 			} | ||
|  | 			$parsedFrame['data'] = $IPLS_parts; | ||
|  | 
 | ||
|  | 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { | ||
|  | 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; | ||
|  | 			} | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
 | ||
|  | 			//   There may only be one 'MCDI' frame in each tag
 | ||
|  | 			// <Header for 'Music CD identifier', ID: 'MCDI'>
 | ||
|  | 			// CD TOC                <binary data>
 | ||
|  | 
 | ||
|  | 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { | ||
|  | 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; | ||
|  | 			} | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
 | ||
|  | 			//   There may only be one 'ETCO' frame in each tag
 | ||
|  | 			// <Header for 'Event timing codes', ID: 'ETCO'>
 | ||
|  | 			// Time stamp format    $xx
 | ||
|  | 			//   Where time stamp format is:
 | ||
|  | 			// $01  (32-bit value) MPEG frames from beginning of file
 | ||
|  | 			// $02  (32-bit value) milliseconds from beginning of file
 | ||
|  | 			//   Followed by a list of key events in the following format:
 | ||
|  | 			// Type of event   $xx
 | ||
|  | 			// Time stamp      $xx (xx ...)
 | ||
|  | 			//   The 'Time stamp' is set to zero if directly at the beginning of the sound
 | ||
|  | 			//   or after the previous event. All events MUST be sorted in chronological order.
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 
 | ||
|  | 			while ($frame_offset < strlen($parsedFrame['data'])) { | ||
|  | 				$parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1); | ||
|  | 				$parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']); | ||
|  | 				$parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 				$frame_offset += 4; | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
 | ||
|  | 			//   There may only be one 'MLLT' frame in each tag
 | ||
|  | 			// <Header for 'Location lookup table', ID: 'MLLT'>
 | ||
|  | 			// MPEG frames between reference  $xx xx
 | ||
|  | 			// Bytes between reference        $xx xx xx
 | ||
|  | 			// Milliseconds between reference $xx xx xx
 | ||
|  | 			// Bits for bytes deviation       $xx
 | ||
|  | 			// Bits for milliseconds dev.     $xx
 | ||
|  | 			//   Then for every reference the following data is included;
 | ||
|  | 			// Deviation in bytes         %xxx....
 | ||
|  | 			// Deviation in milliseconds  %xxx....
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2)); | ||
|  | 			$parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3)); | ||
|  | 			$parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3)); | ||
|  | 			$parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1)); | ||
|  | 			$parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1)); | ||
|  | 			$parsedFrame['data'] = substr($parsedFrame['data'], 10); | ||
|  | 			while ($frame_offset < strlen($parsedFrame['data'])) { | ||
|  | 				$deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			} | ||
|  | 			$reference_counter = 0; | ||
|  | 			while (strlen($deviationbitstream) > 0) { | ||
|  | 				$parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation'])); | ||
|  | 				$parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation'])); | ||
|  | 				$deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']); | ||
|  | 				$reference_counter++; | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
 | ||
|  | 				  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
 | ||
|  | 			//   There may only be one 'SYTC' frame in each tag
 | ||
|  | 			// <Header for 'Synchronised tempo codes', ID: 'SYTC'>
 | ||
|  | 			// Time stamp format   $xx
 | ||
|  | 			// Tempo data          <binary data>
 | ||
|  | 			//   Where time stamp format is:
 | ||
|  | 			// $01  (32-bit value) MPEG frames from beginning of file
 | ||
|  | 			// $02  (32-bit value) milliseconds from beginning of file
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$timestamp_counter = 0; | ||
|  | 			while ($frame_offset < strlen($parsedFrame['data'])) { | ||
|  | 				$parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 				if ($parsedFrame[$timestamp_counter]['tempo'] == 255) { | ||
|  | 					$parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 				} | ||
|  | 				$parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 				$frame_offset += 4; | ||
|  | 				$timestamp_counter++; | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
 | ||
|  | 			//   There may be more than one 'Unsynchronised lyrics/text transcription' frame
 | ||
|  | 			//   in each tag, but only one with the same language and content descriptor.
 | ||
|  | 			// <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
 | ||
|  | 			// Text encoding        $xx
 | ||
|  | 			// Language             $xx xx xx
 | ||
|  | 			// Content descriptor   <text string according to encoding> $00 (00)
 | ||
|  | 			// Lyrics/text          <full text string according to encoding>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 				$frame_textencoding_terminator = "\x00"; | ||
|  | 			} | ||
|  | 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3); | ||
|  | 			$frame_offset += 3; | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); | ||
|  | 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 			} | ||
|  | 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { | ||
|  | 				// if description only contains a BOM or terminator then make it blank
 | ||
|  | 				$frame_description = ''; | ||
|  | 			} | ||
|  | 			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); | ||
|  | 
 | ||
|  | 			$parsedFrame['encodingid']   = $frame_textencoding; | ||
|  | 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 			$parsedFrame['language']     = $frame_language; | ||
|  | 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); | ||
|  | 			$parsedFrame['description']  = $frame_description; | ||
|  | 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { | ||
|  | 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
 | ||
|  | 			//   There may be more than one 'SYLT' frame in each tag,
 | ||
|  | 			//   but only one with the same language and content descriptor.
 | ||
|  | 			// <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
 | ||
|  | 			// Text encoding        $xx
 | ||
|  | 			// Language             $xx xx xx
 | ||
|  | 			// Time stamp format    $xx
 | ||
|  | 			//   $01  (32-bit value) MPEG frames from beginning of file
 | ||
|  | 			//   $02  (32-bit value) milliseconds from beginning of file
 | ||
|  | 			// Content type         $xx
 | ||
|  | 			// Content descriptor   <text string according to encoding> $00 (00)
 | ||
|  | 			//   Terminated text to be synced (typically a syllable)
 | ||
|  | 			//   Sync identifier (terminator to above string)   $00 (00)
 | ||
|  | 			//   Time stamp                                     $xx (xx ...)
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 				$frame_textencoding_terminator = "\x00"; | ||
|  | 			} | ||
|  | 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3); | ||
|  | 			$frame_offset += 3; | ||
|  | 			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']); | ||
|  | 			$parsedFrame['encodingid']      = $frame_textencoding; | ||
|  | 			$parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 			$parsedFrame['language']        = $frame_language; | ||
|  | 			$parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false); | ||
|  | 
 | ||
|  | 			$timestampindex = 0; | ||
|  | 			$frame_remainingdata = substr($parsedFrame['data'], $frame_offset); | ||
|  | 			while (strlen($frame_remainingdata)) { | ||
|  | 				$frame_offset = 0; | ||
|  | 				$frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator); | ||
|  | 				if ($frame_terminatorpos === false) { | ||
|  | 					$frame_remainingdata = ''; | ||
|  | 				} else { | ||
|  | 					if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 						$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 					} | ||
|  | 					$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 
 | ||
|  | 					$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator)); | ||
|  | 					if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) { | ||
|  | 						// timestamp probably omitted for first data item
 | ||
|  | 					} else { | ||
|  | 						$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4)); | ||
|  | 						$frame_remainingdata = substr($frame_remainingdata, 4); | ||
|  | 					} | ||
|  | 					$timestampindex++; | ||
|  | 				} | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
 | ||
|  | 			//   There may be more than one comment frame in each tag,
 | ||
|  | 			//   but only one with the same language and content descriptor.
 | ||
|  | 			// <Header for 'Comment', ID: 'COMM'>
 | ||
|  | 			// Text encoding          $xx
 | ||
|  | 			// Language               $xx xx xx
 | ||
|  | 			// Short content descrip. <text string according to encoding> $00 (00)
 | ||
|  | 			// The actual text        <full text string according to encoding>
 | ||
|  | 
 | ||
|  | 			if (strlen($parsedFrame['data']) < 5) { | ||
|  | 
 | ||
|  | 				$this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']); | ||
|  | 
 | ||
|  | 			} else { | ||
|  | 
 | ||
|  | 				$frame_offset = 0; | ||
|  | 				$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 				$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); | ||
|  | 				if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 					$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 					$frame_textencoding_terminator = "\x00"; | ||
|  | 				} | ||
|  | 				$frame_language = substr($parsedFrame['data'], $frame_offset, 3); | ||
|  | 				$frame_offset += 3; | ||
|  | 				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); | ||
|  | 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 				} | ||
|  | 				$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 				if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { | ||
|  | 					// if description only contains a BOM or terminator then make it blank
 | ||
|  | 					$frame_description = ''; | ||
|  | 				} | ||
|  | 				$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); | ||
|  | 
 | ||
|  | 				$parsedFrame['encodingid']   = $frame_textencoding; | ||
|  | 				$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 				$parsedFrame['language']     = $frame_language; | ||
|  | 				$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); | ||
|  | 				$parsedFrame['description']  = $frame_description; | ||
|  | 				$parsedFrame['data']         = $frame_text; | ||
|  | 				if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { | ||
|  | 					$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); | ||
|  | 					if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) { | ||
|  | 						$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); | ||
|  | 					} else { | ||
|  | 						$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); | ||
|  | 					} | ||
|  | 				} | ||
|  | 
 | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
 | ||
|  | 			//   There may be more than one 'RVA2' frame in each tag,
 | ||
|  | 			//   but only one with the same identification string
 | ||
|  | 			// <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
 | ||
|  | 			// Identification          <text string> $00
 | ||
|  | 			//   The 'identification' string is used to identify the situation and/or
 | ||
|  | 			//   device where this adjustment should apply. The following is then
 | ||
|  | 			//   repeated for every channel:
 | ||
|  | 			// Type of channel         $xx
 | ||
|  | 			// Volume adjustment       $xx xx
 | ||
|  | 			// Bits representing peak  $xx
 | ||
|  | 			// Peak volume             $xx (xx ...)
 | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00"); | ||
|  | 			$frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos); | ||
|  | 			if (ord($frame_idstring) === 0) { | ||
|  | 				$frame_idstring = ''; | ||
|  | 			} | ||
|  | 			$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); | ||
|  | 			$parsedFrame['description'] = $frame_idstring; | ||
|  | 			$RVA2channelcounter = 0; | ||
|  | 			while (strlen($frame_remainingdata) >= 5) { | ||
|  | 				$frame_offset = 0; | ||
|  | 				$frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1)); | ||
|  | 				$parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid; | ||
|  | 				$parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid); | ||
|  | 				$parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
 | ||
|  | 				$frame_offset += 2; | ||
|  | 				$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1)); | ||
|  | 				if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) { | ||
|  | 					$this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value'); | ||
|  | 					break; | ||
|  | 				} | ||
|  | 				$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8); | ||
|  | 				$parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume)); | ||
|  | 				$frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume); | ||
|  | 				$RVA2channelcounter++; | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
 | ||
|  | 				  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
 | ||
|  | 			//   There may only be one 'RVA' frame in each tag
 | ||
|  | 			// <Header for 'Relative volume adjustment', ID: 'RVA'>
 | ||
|  | 			// ID3v2.2 => Increment/decrement     %000000ba
 | ||
|  | 			// ID3v2.3 => Increment/decrement     %00fedcba
 | ||
|  | 			// Bits used for volume descr.        $xx
 | ||
|  | 			// Relative volume change, right      $xx xx (xx ...) // a
 | ||
|  | 			// Relative volume change, left       $xx xx (xx ...) // b
 | ||
|  | 			// Peak volume right                  $xx xx (xx ...)
 | ||
|  | 			// Peak volume left                   $xx xx (xx ...)
 | ||
|  | 			//   ID3v2.3 only, optional (not present in ID3v2.2):
 | ||
|  | 			// Relative volume change, right back $xx xx (xx ...) // c
 | ||
|  | 			// Relative volume change, left back  $xx xx (xx ...) // d
 | ||
|  | 			// Peak volume right back             $xx xx (xx ...)
 | ||
|  | 			// Peak volume left back              $xx xx (xx ...)
 | ||
|  | 			//   ID3v2.3 only, optional (not present in ID3v2.2):
 | ||
|  | 			// Relative volume change, center     $xx xx (xx ...) // e
 | ||
|  | 			// Peak volume center                 $xx xx (xx ...)
 | ||
|  | 			//   ID3v2.3 only, optional (not present in ID3v2.2):
 | ||
|  | 			// Relative volume change, bass       $xx xx (xx ...) // f
 | ||
|  | 			// Peak volume bass                   $xx xx (xx ...)
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1); | ||
|  | 			$parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1); | ||
|  | 			$parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8); | ||
|  | 			$parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 			if ($parsedFrame['incdec']['right'] === false) { | ||
|  | 				$parsedFrame['volumechange']['right'] *= -1; | ||
|  | 			} | ||
|  | 			$frame_offset += $frame_bytesvolume; | ||
|  | 			$parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 			if ($parsedFrame['incdec']['left'] === false) { | ||
|  | 				$parsedFrame['volumechange']['left'] *= -1; | ||
|  | 			} | ||
|  | 			$frame_offset += $frame_bytesvolume; | ||
|  | 			$parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 			$frame_offset += $frame_bytesvolume; | ||
|  | 			$parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 			$frame_offset += $frame_bytesvolume; | ||
|  | 			if ($id3v2_majorversion == 3) { | ||
|  | 				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); | ||
|  | 				if (strlen($parsedFrame['data']) > 0) { | ||
|  | 					$parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1); | ||
|  | 					$parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1); | ||
|  | 					$parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 					if ($parsedFrame['incdec']['rightrear'] === false) { | ||
|  | 						$parsedFrame['volumechange']['rightrear'] *= -1; | ||
|  | 					} | ||
|  | 					$frame_offset += $frame_bytesvolume; | ||
|  | 					$parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 					if ($parsedFrame['incdec']['leftrear'] === false) { | ||
|  | 						$parsedFrame['volumechange']['leftrear'] *= -1; | ||
|  | 					} | ||
|  | 					$frame_offset += $frame_bytesvolume; | ||
|  | 					$parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 					$frame_offset += $frame_bytesvolume; | ||
|  | 					$parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 					$frame_offset += $frame_bytesvolume; | ||
|  | 				} | ||
|  | 				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); | ||
|  | 				if (strlen($parsedFrame['data']) > 0) { | ||
|  | 					$parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1); | ||
|  | 					$parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 					if ($parsedFrame['incdec']['center'] === false) { | ||
|  | 						$parsedFrame['volumechange']['center'] *= -1; | ||
|  | 					} | ||
|  | 					$frame_offset += $frame_bytesvolume; | ||
|  | 					$parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 					$frame_offset += $frame_bytesvolume; | ||
|  | 				} | ||
|  | 				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); | ||
|  | 				if (strlen($parsedFrame['data']) > 0) { | ||
|  | 					$parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1); | ||
|  | 					$parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 					if ($parsedFrame['incdec']['bass'] === false) { | ||
|  | 						$parsedFrame['volumechange']['bass'] *= -1; | ||
|  | 					} | ||
|  | 					$frame_offset += $frame_bytesvolume; | ||
|  | 					$parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); | ||
|  | 					$frame_offset += $frame_bytesvolume; | ||
|  | 				} | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
 | ||
|  | 			//   There may be more than one 'EQU2' frame in each tag,
 | ||
|  | 			//   but only one with the same identification string
 | ||
|  | 			// <Header of 'Equalisation (2)', ID: 'EQU2'>
 | ||
|  | 			// Interpolation method  $xx
 | ||
|  | 			//   $00  Band
 | ||
|  | 			//   $01  Linear
 | ||
|  | 			// Identification        <text string> $00
 | ||
|  | 			//   The following is then repeated for every adjustment point
 | ||
|  | 			// Frequency          $xx xx
 | ||
|  | 			// Volume adjustment  $xx xx
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (ord($frame_idstring) === 0) { | ||
|  | 				$frame_idstring = ''; | ||
|  | 			} | ||
|  | 			$parsedFrame['description'] = $frame_idstring; | ||
|  | 			$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); | ||
|  | 			while (strlen($frame_remainingdata)) { | ||
|  | 				$frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2; | ||
|  | 				$parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true); | ||
|  | 				$frame_remainingdata = substr($frame_remainingdata, 4); | ||
|  | 			} | ||
|  | 			$parsedFrame['interpolationmethod'] = $frame_interpolationmethod; | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
 | ||
|  | 			//   There may only be one 'EQUA' frame in each tag
 | ||
|  | 			// <Header for 'Relative volume adjustment', ID: 'EQU'>
 | ||
|  | 			// Adjustment bits    $xx
 | ||
|  | 			//   This is followed by 2 bytes + ('adjustment bits' rounded up to the
 | ||
|  | 			//   nearest byte) for every equalisation band in the following format,
 | ||
|  | 			//   giving a frequency range of 0 - 32767Hz:
 | ||
|  | 			// Increment/decrement   %x (MSB of the Frequency)
 | ||
|  | 			// Frequency             (lower 15 bits)
 | ||
|  | 			// Adjustment            $xx (xx ...)
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1); | ||
|  | 			$frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8); | ||
|  | 
 | ||
|  | 			$frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 			while (strlen($frame_remainingdata) > 0) { | ||
|  | 				$frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2)); | ||
|  | 				$frame_incdec    = (bool) substr($frame_frequencystr, 0, 1); | ||
|  | 				$frame_frequency = bindec(substr($frame_frequencystr, 1, 15)); | ||
|  | 				$parsedFrame[$frame_frequency]['incdec'] = $frame_incdec; | ||
|  | 				$parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes)); | ||
|  | 				if ($parsedFrame[$frame_frequency]['incdec'] === false) { | ||
|  | 					$parsedFrame[$frame_frequency]['adjustment'] *= -1; | ||
|  | 				} | ||
|  | 				$frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes); | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
 | ||
|  | 			//   There may only be one 'RVRB' frame in each tag.
 | ||
|  | 			// <Header for 'Reverb', ID: 'RVRB'>
 | ||
|  | 			// Reverb left (ms)                 $xx xx
 | ||
|  | 			// Reverb right (ms)                $xx xx
 | ||
|  | 			// Reverb bounces, left             $xx
 | ||
|  | 			// Reverb bounces, right            $xx
 | ||
|  | 			// Reverb feedback, left to left    $xx
 | ||
|  | 			// Reverb feedback, left to right   $xx
 | ||
|  | 			// Reverb feedback, right to right  $xx
 | ||
|  | 			// Reverb feedback, right to left   $xx
 | ||
|  | 			// Premix left to right             $xx
 | ||
|  | 			// Premix right to left             $xx
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); | ||
|  | 			$frame_offset += 2; | ||
|  | 			$parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); | ||
|  | 			$frame_offset += 2; | ||
|  | 			$parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
 | ||
|  | 			//   There may be several pictures attached to one file,
 | ||
|  | 			//   each in their individual 'APIC' frame, but only one
 | ||
|  | 			//   with the same content descriptor
 | ||
|  | 			// <Header for 'Attached picture', ID: 'APIC'>
 | ||
|  | 			// Text encoding      $xx
 | ||
|  | 			// ID3v2.3+ => MIME type          <text string> $00
 | ||
|  | 			// ID3v2.2  => Image format       $xx xx xx
 | ||
|  | 			// Picture type       $xx
 | ||
|  | 			// Description        <text string according to encoding> $00 (00)
 | ||
|  | 			// Picture data       <binary data>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 				$frame_textencoding_terminator = "\x00"; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) { | ||
|  | 				$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3); | ||
|  | 				if (strtolower($frame_imagetype) == 'ima') { | ||
|  | 					// complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
 | ||
|  | 					// MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
 | ||
|  | 					$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 					$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 					if (ord($frame_mimetype) === 0) { | ||
|  | 						$frame_mimetype = ''; | ||
|  | 					} | ||
|  | 					$frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype))); | ||
|  | 					if ($frame_imagetype == 'JPEG') { | ||
|  | 						$frame_imagetype = 'JPG'; | ||
|  | 					} | ||
|  | 					$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 				} else { | ||
|  | 					$frame_offset += 3; | ||
|  | 				} | ||
|  | 			} | ||
|  | 			if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) { | ||
|  | 				$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 				$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 				if (ord($frame_mimetype) === 0) { | ||
|  | 					$frame_mimetype = ''; | ||
|  | 				} | ||
|  | 				$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 
 | ||
|  | 			if ($frame_offset >= $parsedFrame['datalength']) { | ||
|  | 				$this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset)); | ||
|  | 			} else { | ||
|  | 				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); | ||
|  | 				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 				} | ||
|  | 				$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 				if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { | ||
|  | 					// if description only contains a BOM or terminator then make it blank
 | ||
|  | 					$frame_description = ''; | ||
|  | 				} | ||
|  | 				$parsedFrame['encodingid']       = $frame_textencoding; | ||
|  | 				$parsedFrame['encoding']         = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 				if ($id3v2_majorversion == 2) { | ||
|  | 					$parsedFrame['imagetype']    = $frame_imagetype; | ||
|  | 				} else { | ||
|  | 					$parsedFrame['mime']         = $frame_mimetype; | ||
|  | 				} | ||
|  | 				$parsedFrame['picturetypeid']    = $frame_picturetype; | ||
|  | 				$parsedFrame['picturetype']      = $this->APICPictureTypeLookup($frame_picturetype); | ||
|  | 				$parsedFrame['description']      = $frame_description; | ||
|  | 				$parsedFrame['data']             = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); | ||
|  | 				$parsedFrame['datalength']       = strlen($parsedFrame['data']); | ||
|  | 
 | ||
|  | 				$parsedFrame['image_mime'] = ''; | ||
|  | 				$imageinfo = array(); | ||
|  | 				if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) { | ||
|  | 					if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { | ||
|  | 						$parsedFrame['image_mime']       = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); | ||
|  | 						if ($imagechunkcheck[0]) { | ||
|  | 							$parsedFrame['image_width']  = $imagechunkcheck[0]; | ||
|  | 						} | ||
|  | 						if ($imagechunkcheck[1]) { | ||
|  | 							$parsedFrame['image_height'] = $imagechunkcheck[1]; | ||
|  | 						} | ||
|  | 					} | ||
|  | 				} | ||
|  | 
 | ||
|  | 				do { | ||
|  | 					if ($this->getid3->option_save_attachments === false) { | ||
|  | 						// skip entirely
 | ||
|  | 						unset($parsedFrame['data']); | ||
|  | 						break; | ||
|  | 					} | ||
|  | 					if ($this->getid3->option_save_attachments === true) { | ||
|  | 						// great
 | ||
|  | /* | ||
|  | 					} elseif (is_int($this->getid3->option_save_attachments)) { | ||
|  | 						if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) { | ||
|  | 							// too big, skip
 | ||
|  | 							$this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)'); | ||
|  | 							unset($parsedFrame['data']); | ||
|  | 							break; | ||
|  | 						} | ||
|  | */ | ||
|  | 					} elseif (is_string($this->getid3->option_save_attachments)) { | ||
|  | 						$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); | ||
|  | 						if (!is_dir($dir) || !getID3::is_writable($dir)) { | ||
|  | 							// cannot write, skip
 | ||
|  | 							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)'); | ||
|  | 							unset($parsedFrame['data']); | ||
|  | 							break; | ||
|  | 						} | ||
|  | 					} | ||
|  | 					// if we get this far, must be OK
 | ||
|  | 					if (is_string($this->getid3->option_save_attachments)) { | ||
|  | 						$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset; | ||
|  | 						if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) { | ||
|  | 							file_put_contents($destination_filename, $parsedFrame['data']); | ||
|  | 						} else { | ||
|  | 							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)'); | ||
|  | 						} | ||
|  | 						$parsedFrame['data_filename'] = $destination_filename; | ||
|  | 						unset($parsedFrame['data']); | ||
|  | 					} else { | ||
|  | 						if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { | ||
|  | 							if (!isset($info['id3v2']['comments']['picture'])) { | ||
|  | 								$info['id3v2']['comments']['picture'] = array(); | ||
|  | 							} | ||
|  | 							$comments_picture_data = array(); | ||
|  | 							foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { | ||
|  | 								if (isset($parsedFrame[$picture_key])) { | ||
|  | 									$comments_picture_data[$picture_key] = $parsedFrame[$picture_key]; | ||
|  | 								} | ||
|  | 							} | ||
|  | 							$info['id3v2']['comments']['picture'][] = $comments_picture_data; | ||
|  | 							unset($comments_picture_data); | ||
|  | 						} | ||
|  | 					} | ||
|  | 				} while (false); | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
 | ||
|  | 			//   There may be more than one 'GEOB' frame in each tag,
 | ||
|  | 			//   but only one with the same content descriptor
 | ||
|  | 			// <Header for 'General encapsulated object', ID: 'GEOB'>
 | ||
|  | 			// Text encoding          $xx
 | ||
|  | 			// MIME type              <text string> $00
 | ||
|  | 			// Filename               <text string according to encoding> $00 (00)
 | ||
|  | 			// Content description    <text string according to encoding> $00 (00)
 | ||
|  | 			// Encapsulated object    <binary data>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 				$frame_textencoding_terminator = "\x00"; | ||
|  | 			} | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (ord($frame_mimetype) === 0) { | ||
|  | 				$frame_mimetype = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); | ||
|  | 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 			} | ||
|  | 			$frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (ord($frame_filename) === 0) { | ||
|  | 				$frame_filename = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); | ||
|  | 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 			} | ||
|  | 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { | ||
|  | 				// if description only contains a BOM or terminator then make it blank
 | ||
|  | 				$frame_description = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); | ||
|  | 
 | ||
|  | 			$parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 			$parsedFrame['encodingid']  = $frame_textencoding; | ||
|  | 			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 			$parsedFrame['mime']        = $frame_mimetype; | ||
|  | 			$parsedFrame['filename']    = $frame_filename; | ||
|  | 			$parsedFrame['description'] = $frame_description; | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
 | ||
|  | 			//   There may only be one 'PCNT' frame in each tag.
 | ||
|  | 			//   When the counter reaches all one's, one byte is inserted in
 | ||
|  | 			//   front of the counter thus making the counter eight bits bigger
 | ||
|  | 			// <Header for 'Play counter', ID: 'PCNT'>
 | ||
|  | 			// Counter        $xx xx xx xx (xx ...)
 | ||
|  | 
 | ||
|  | 			$parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
 | ||
|  | 			//   There may be more than one 'POPM' frame in each tag,
 | ||
|  | 			//   but only one with the same email address
 | ||
|  | 			// <Header for 'Popularimeter', ID: 'POPM'>
 | ||
|  | 			// Email to user   <text string> $00
 | ||
|  | 			// Rating          $xx
 | ||
|  | 			// Counter         $xx xx xx xx (xx ...)
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (ord($frame_emailaddress) === 0) { | ||
|  | 				$frame_emailaddress = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 			$frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); | ||
|  | 			$parsedFrame['email']   = $frame_emailaddress; | ||
|  | 			$parsedFrame['rating']  = $frame_rating; | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
 | ||
|  | 			//   There may only be one 'RBUF' frame in each tag
 | ||
|  | 			// <Header for 'Recommended buffer size', ID: 'RBUF'>
 | ||
|  | 			// Buffer size               $xx xx xx
 | ||
|  | 			// Embedded info flag        %0000000x
 | ||
|  | 			// Offset to next tag        $xx xx xx xx
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3)); | ||
|  | 			$frame_offset += 3; | ||
|  | 
 | ||
|  | 			$frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1); | ||
|  | 			$parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
 | ||
|  | 			//   There may be more than one 'CRM' frame in a tag,
 | ||
|  | 			//   but only one with the same 'owner identifier'
 | ||
|  | 			// <Header for 'Encrypted meta frame', ID: 'CRM'>
 | ||
|  | 			// Owner identifier      <textstring> $00 (00)
 | ||
|  | 			// Content/explanation   <textstring> $00 (00)
 | ||
|  | 			// Encrypted datablock   <binary data>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { | ||
|  | 				// if description only contains a BOM or terminator then make it blank
 | ||
|  | 				$frame_description = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 
 | ||
|  | 			$parsedFrame['ownerid']     = $frame_ownerid; | ||
|  | 			$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 			$parsedFrame['description'] = $frame_description; | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
 | ||
|  | 			//   There may be more than one 'AENC' frames in a tag,
 | ||
|  | 			//   but only one with the same 'Owner identifier'
 | ||
|  | 			// <Header for 'Audio encryption', ID: 'AENC'>
 | ||
|  | 			// Owner identifier   <text string> $00
 | ||
|  | 			// Preview start      $xx xx
 | ||
|  | 			// Preview length     $xx xx
 | ||
|  | 			// Encryption info    <binary data>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (ord($frame_ownerid) === 0) { | ||
|  | 				$frame_ownerid = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 			$parsedFrame['ownerid'] = $frame_ownerid; | ||
|  | 			$parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); | ||
|  | 			$frame_offset += 2; | ||
|  | 			$parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); | ||
|  | 			$frame_offset += 2; | ||
|  | 			$parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
 | ||
|  | 				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {    // 4.22  LNK  Linked information
 | ||
|  | 			//   There may be more than one 'LINK' frame in a tag,
 | ||
|  | 			//   but only one with the same contents
 | ||
|  | 			// <Header for 'Linked information', ID: 'LINK'>
 | ||
|  | 			// ID3v2.3+ => Frame identifier   $xx xx xx xx
 | ||
|  | 			// ID3v2.2  => Frame identifier   $xx xx xx
 | ||
|  | 			// URL                            <text string> $00
 | ||
|  | 			// ID and additional data         <text string(s)>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			if ($id3v2_majorversion == 2) { | ||
|  | 				$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3); | ||
|  | 				$frame_offset += 3; | ||
|  | 			} else { | ||
|  | 				$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4); | ||
|  | 				$frame_offset += 4; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (ord($frame_url) === 0) { | ||
|  | 				$frame_url = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 			$parsedFrame['url'] = $frame_url; | ||
|  | 
 | ||
|  | 			$parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { | ||
|  | 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']); | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
 | ||
|  | 			//   There may only be one 'POSS' frame in each tag
 | ||
|  | 			// <Head for 'Position synchronisation', ID: 'POSS'>
 | ||
|  | 			// Time stamp format         $xx
 | ||
|  | 			// Position                  $xx (xx ...)
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
 | ||
|  | 			//   There may be more than one 'Terms of use' frame in a tag,
 | ||
|  | 			//   but only one with the same 'Language'
 | ||
|  | 			// <Header for 'Terms of use frame', ID: 'USER'>
 | ||
|  | 			// Text encoding        $xx
 | ||
|  | 			// Language             $xx xx xx
 | ||
|  | 			// The actual text      <text string according to encoding>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 			} | ||
|  | 			$frame_language = substr($parsedFrame['data'], $frame_offset, 3); | ||
|  | 			$frame_offset += 3; | ||
|  | 			$parsedFrame['language']     = $frame_language; | ||
|  | 			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); | ||
|  | 			$parsedFrame['encodingid']   = $frame_textencoding; | ||
|  | 			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 			$parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { | ||
|  | 				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
 | ||
|  | 			//   There may only be one 'OWNE' frame in a tag
 | ||
|  | 			// <Header for 'Ownership frame', ID: 'OWNE'>
 | ||
|  | 			// Text encoding     $xx
 | ||
|  | 			// Price paid        <text string> $00
 | ||
|  | 			// Date of purch.    <text string>
 | ||
|  | 			// Seller            <text string according to encoding>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 			} | ||
|  | 			$parsedFrame['encodingid'] = $frame_textencoding; | ||
|  | 			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 
 | ||
|  | 			$parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3); | ||
|  | 			$parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']); | ||
|  | 			$parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3); | ||
|  | 
 | ||
|  | 			$parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8); | ||
|  | 			if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) { | ||
|  | 				$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4)); | ||
|  | 			} | ||
|  | 			$frame_offset += 8; | ||
|  | 
 | ||
|  | 			$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
 | ||
|  | 			//   There may be more than one 'commercial frame' in a tag,
 | ||
|  | 			//   but no two may be identical
 | ||
|  | 			// <Header for 'Commercial frame', ID: 'COMR'>
 | ||
|  | 			// Text encoding      $xx
 | ||
|  | 			// Price string       <text string> $00
 | ||
|  | 			// Valid until        <text string>
 | ||
|  | 			// Contact URL        <text string> $00
 | ||
|  | 			// Received as        $xx
 | ||
|  | 			// Name of seller     <text string according to encoding> $00 (00)
 | ||
|  | 			// Description        <text string according to encoding> $00 (00)
 | ||
|  | 			// Picture MIME type  <string> $00
 | ||
|  | 			// Seller logo        <binary data>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); | ||
|  | 			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { | ||
|  | 				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); | ||
|  | 				$frame_textencoding_terminator = "\x00"; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 			$frame_rawpricearray = explode('/', $frame_pricestring); | ||
|  | 			foreach ($frame_rawpricearray as $key => $val) { | ||
|  | 				$frame_currencyid = substr($val, 0, 3); | ||
|  | 				$parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid); | ||
|  | 				$parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			$frame_datestring = substr($parsedFrame['data'], $frame_offset, 8); | ||
|  | 			$frame_offset += 8; | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 
 | ||
|  | 			$frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); | ||
|  | 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 			} | ||
|  | 			$frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (ord($frame_sellername) === 0) { | ||
|  | 				$frame_sellername = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); | ||
|  | 			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { | ||
|  | 				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
 | ||
|  | 			} | ||
|  | 			$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { | ||
|  | 				// if description only contains a BOM or terminator then make it blank
 | ||
|  | 				$frame_description = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); | ||
|  | 
 | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 
 | ||
|  | 			$frame_sellerlogo = substr($parsedFrame['data'], $frame_offset); | ||
|  | 
 | ||
|  | 			$parsedFrame['encodingid']        = $frame_textencoding; | ||
|  | 			$parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding); | ||
|  | 
 | ||
|  | 			$parsedFrame['pricevaliduntil']   = $frame_datestring; | ||
|  | 			$parsedFrame['contacturl']        = $frame_contacturl; | ||
|  | 			$parsedFrame['receivedasid']      = $frame_receivedasid; | ||
|  | 			$parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid); | ||
|  | 			$parsedFrame['sellername']        = $frame_sellername; | ||
|  | 			$parsedFrame['description']       = $frame_description; | ||
|  | 			$parsedFrame['mime']              = $frame_mimetype; | ||
|  | 			$parsedFrame['logo']              = $frame_sellerlogo; | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
 | ||
|  | 			//   There may be several 'ENCR' frames in a tag,
 | ||
|  | 			//   but only one containing the same symbol
 | ||
|  | 			//   and only one containing the same owner identifier
 | ||
|  | 			// <Header for 'Encryption method registration', ID: 'ENCR'>
 | ||
|  | 			// Owner identifier    <text string> $00
 | ||
|  | 			// Method symbol       $xx
 | ||
|  | 			// Encryption data     <binary data>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (ord($frame_ownerid) === 0) { | ||
|  | 				$frame_ownerid = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 
 | ||
|  | 			$parsedFrame['ownerid']      = $frame_ownerid; | ||
|  | 			$parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)
 | ||
|  | 
 | ||
|  | 			//   There may be several 'GRID' frames in a tag,
 | ||
|  | 			//   but only one containing the same symbol
 | ||
|  | 			//   and only one containing the same owner identifier
 | ||
|  | 			// <Header for 'Group ID registration', ID: 'GRID'>
 | ||
|  | 			// Owner identifier      <text string> $00
 | ||
|  | 			// Group symbol          $xx
 | ||
|  | 			// Group dependent data  <binary data>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (ord($frame_ownerid) === 0) { | ||
|  | 				$frame_ownerid = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 
 | ||
|  | 			$parsedFrame['ownerid']       = $frame_ownerid; | ||
|  | 			$parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
 | ||
|  | 			//   The tag may contain more than one 'PRIV' frame
 | ||
|  | 			//   but only with different contents
 | ||
|  | 			// <Header for 'Private frame', ID: 'PRIV'>
 | ||
|  | 			// Owner identifier      <text string> $00
 | ||
|  | 			// The private data      <binary data>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); | ||
|  | 			if (ord($frame_ownerid) === 0) { | ||
|  | 				$frame_ownerid = ''; | ||
|  | 			} | ||
|  | 			$frame_offset = $frame_terminatorpos + strlen("\x00"); | ||
|  | 
 | ||
|  | 			$parsedFrame['ownerid'] = $frame_ownerid; | ||
|  | 			$parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
 | ||
|  | 			//   There may be more than one 'signature frame' in a tag,
 | ||
|  | 			//   but no two may be identical
 | ||
|  | 			// <Header for 'Signature frame', ID: 'SIGN'>
 | ||
|  | 			// Group symbol      $xx
 | ||
|  | 			// Signature         <binary data>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
 | ||
|  | 			//   There may only be one 'seek frame' in a tag
 | ||
|  | 			// <Header for 'Seek frame', ID: 'SEEK'>
 | ||
|  | 			// Minimum offset to next tag       $xx xx xx xx
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
 | ||
|  | 			//   There may only be one 'audio seek point index' frame in a tag
 | ||
|  | 			// <Header for 'Seek Point Index', ID: 'ASPI'>
 | ||
|  | 			// Indexed data start (S)         $xx xx xx xx
 | ||
|  | 			// Indexed data length (L)        $xx xx xx xx
 | ||
|  | 			// Number of index points (N)     $xx xx
 | ||
|  | 			// Bits per index point (b)       $xx
 | ||
|  | 			//   Then for every index point the following data is included:
 | ||
|  | 			// Fraction at index (Fi)          $xx (xx)
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 			$frame_offset += 4; | ||
|  | 			$parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 			$frame_offset += 4; | ||
|  | 			$parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); | ||
|  | 			$frame_offset += 2; | ||
|  | 			$parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); | ||
|  | 			$frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8); | ||
|  | 			for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) { | ||
|  | 				$parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint)); | ||
|  | 				$frame_offset += $frame_bytesperpoint; | ||
|  | 			} | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
 | ||
|  | 			// http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
 | ||
|  | 			//   There may only be one 'RGAD' frame in a tag
 | ||
|  | 			// <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
 | ||
|  | 			// Peak Amplitude                      $xx $xx $xx $xx
 | ||
|  | 			// Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
 | ||
|  | 			// Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
 | ||
|  | 			//   a - name code
 | ||
|  | 			//   b - originator code
 | ||
|  | 			//   c - sign bit
 | ||
|  | 			//   d - replay gain adjustment
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			$parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 			$frame_offset += 4; | ||
|  | 			$rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); | ||
|  | 			$frame_offset += 2; | ||
|  | 			$rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); | ||
|  | 			$frame_offset += 2; | ||
|  | 			$parsedFrame['raw']['track']['name']       = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3)); | ||
|  | 			$parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3)); | ||
|  | 			$parsedFrame['raw']['track']['signbit']    = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1)); | ||
|  | 			$parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9)); | ||
|  | 			$parsedFrame['raw']['album']['name']       = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3)); | ||
|  | 			$parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3)); | ||
|  | 			$parsedFrame['raw']['album']['signbit']    = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1)); | ||
|  | 			$parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9)); | ||
|  | 			$parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']); | ||
|  | 			$parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']); | ||
|  | 			$parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']); | ||
|  | 			$parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']); | ||
|  | 			$parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']); | ||
|  | 			$parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']); | ||
|  | 
 | ||
|  | 			$info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude']; | ||
|  | 			$info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; | ||
|  | 			$info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; | ||
|  | 			$info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; | ||
|  | 			$info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; | ||
|  | 
 | ||
|  | 			unset($parsedFrame['data']); | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
 | ||
|  | 			// http://id3.org/id3v2-chapters-1.0
 | ||
|  | 			// <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
 | ||
|  | 			// Element ID      <text string> $00
 | ||
|  | 			// Start time      $xx xx xx xx
 | ||
|  | 			// End time        $xx xx xx xx
 | ||
|  |             // Start offset    $xx xx xx xx
 | ||
|  |             // End offset      $xx xx xx xx
 | ||
|  |             // <Optional embedded sub-frames>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); | ||
|  | 			$frame_offset += strlen($parsedFrame['element_id']."\x00"); | ||
|  | 			$parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 			$frame_offset += 4; | ||
|  | 			$parsedFrame['time_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 			$frame_offset += 4; | ||
|  | 			if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") { | ||
|  | 				// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
 | ||
|  | 				$parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 			} | ||
|  | 			$frame_offset += 4; | ||
|  | 			if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") { | ||
|  | 				// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
 | ||
|  | 				$parsedFrame['offset_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 			} | ||
|  | 			$frame_offset += 4; | ||
|  | 
 | ||
|  | 			if ($frame_offset < strlen($parsedFrame['data'])) { | ||
|  | 				$parsedFrame['subframes'] = array(); | ||
|  | 				while ($frame_offset < strlen($parsedFrame['data'])) { | ||
|  | 					// <Optional embedded sub-frames>
 | ||
|  | 					$subframe = array(); | ||
|  | 					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4); | ||
|  | 					$frame_offset += 4; | ||
|  | 					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 					$frame_offset += 4; | ||
|  | 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); | ||
|  | 					$frame_offset += 2; | ||
|  | 					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) { | ||
|  | 						$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)'); | ||
|  | 						break; | ||
|  | 					} | ||
|  | 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']); | ||
|  | 					$frame_offset += $subframe['size']; | ||
|  | 
 | ||
|  | 					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); | ||
|  | 					$subframe['text']       =     substr($subframe_rawdata, 1); | ||
|  | 					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']); | ||
|  | 					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));; | ||
|  | 					switch (substr($encoding_converted_text, 0, 2)) { | ||
|  | 						case "\xFF\xFE": | ||
|  | 						case "\xFE\xFF": | ||
|  | 							switch (strtoupper($info['id3v2']['encoding'])) { | ||
|  | 								case 'ISO-8859-1': | ||
|  | 								case 'UTF-8': | ||
|  | 									$encoding_converted_text = substr($encoding_converted_text, 2); | ||
|  | 									// remove unwanted byte-order-marks
 | ||
|  | 									break; | ||
|  | 								default: | ||
|  | 									// ignore
 | ||
|  | 									break; | ||
|  | 							} | ||
|  | 							break; | ||
|  | 						default: | ||
|  | 							// do not remove BOM
 | ||
|  | 							break; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) { | ||
|  | 						if ($subframe['name'] == 'TIT2') { | ||
|  | 							$parsedFrame['chapter_name']        = $encoding_converted_text; | ||
|  | 						} elseif ($subframe['name'] == 'TIT3') { | ||
|  | 							$parsedFrame['chapter_description'] = $encoding_converted_text; | ||
|  | 						} | ||
|  | 						$parsedFrame['subframes'][] = $subframe; | ||
|  | 					} else { | ||
|  | 						$this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)'); | ||
|  | 					} | ||
|  | 				} | ||
|  | 				unset($subframe_rawdata, $subframe, $encoding_converted_text); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			$id3v2_chapter_entry = array(); | ||
|  | 			foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') as $id3v2_chapter_key) { | ||
|  | 				if (isset($parsedFrame[$id3v2_chapter_key])) { | ||
|  | 					$id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key]; | ||
|  | 				} | ||
|  | 			} | ||
|  | 			if (!isset($info['id3v2']['chapters'])) { | ||
|  | 				$info['id3v2']['chapters'] = array(); | ||
|  | 			} | ||
|  | 			$info['id3v2']['chapters'][] = $id3v2_chapter_entry; | ||
|  | 			unset($id3v2_chapter_entry, $id3v2_chapter_key); | ||
|  | 
 | ||
|  | 
 | ||
|  | 		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
 | ||
|  | 			// http://id3.org/id3v2-chapters-1.0
 | ||
|  | 			// <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC">           (10 bytes)
 | ||
|  | 			// Element ID      <text string> $00
 | ||
|  | 			// CTOC flags        %xx
 | ||
|  | 			// Entry count       $xx
 | ||
|  | 			// Child Element ID  <string>$00   /* zero or more child CHAP or CTOC entries */
 | ||
|  |             // <Optional embedded sub-frames>
 | ||
|  | 
 | ||
|  | 			$frame_offset = 0; | ||
|  | 			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); | ||
|  | 			$frame_offset += strlen($parsedFrame['element_id']."\x00"); | ||
|  | 			$ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1)); | ||
|  | 			$frame_offset += 1; | ||
|  | 			$parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1)); | ||
|  | 			$frame_offset += 1; | ||
|  | 
 | ||
|  | 			$terminator_position = null; | ||
|  | 			for ($i = 0; $i < $parsedFrame['entry_count']; $i++) { | ||
|  | 				$terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset); | ||
|  | 				$parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset); | ||
|  | 				$frame_offset = $terminator_position + 1; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			$parsedFrame['ctoc_flags']['ordered']   = (bool) ($ctoc_flags_raw & 0x01); | ||
|  | 			$parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03); | ||
|  | 
 | ||
|  | 			unset($ctoc_flags_raw, $terminator_position); | ||
|  | 
 | ||
|  | 			if ($frame_offset < strlen($parsedFrame['data'])) { | ||
|  | 				$parsedFrame['subframes'] = array(); | ||
|  | 				while ($frame_offset < strlen($parsedFrame['data'])) { | ||
|  | 					// <Optional embedded sub-frames>
 | ||
|  | 					$subframe = array(); | ||
|  | 					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4); | ||
|  | 					$frame_offset += 4; | ||
|  | 					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); | ||
|  | 					$frame_offset += 4; | ||
|  | 					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); | ||
|  | 					$frame_offset += 2; | ||
|  | 					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) { | ||
|  | 						$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)'); | ||
|  | 						break; | ||
|  | 					} | ||
|  | 					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']); | ||
|  | 					$frame_offset += $subframe['size']; | ||
|  | 
 | ||
|  | 					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); | ||
|  | 					$subframe['text']       =     substr($subframe_rawdata, 1); | ||
|  | 					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']); | ||
|  | 					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));; | ||
|  | 					switch (substr($encoding_converted_text, 0, 2)) { | ||
|  | 						case "\xFF\xFE": | ||
|  | 						case "\xFE\xFF": | ||
|  | 							switch (strtoupper($info['id3v2']['encoding'])) { | ||
|  | 								case 'ISO-8859-1': | ||
|  | 								case 'UTF-8': | ||
|  | 									$encoding_converted_text = substr($encoding_converted_text, 2); | ||
|  | 									// remove unwanted byte-order-marks
 | ||
|  | 									break; | ||
|  | 								default: | ||
|  | 									// ignore
 | ||
|  | 									break; | ||
|  | 							} | ||
|  | 							break; | ||
|  | 						default: | ||
|  | 							// do not remove BOM
 | ||
|  | 							break; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) { | ||
|  | 						if ($subframe['name'] == 'TIT2') { | ||
|  | 							$parsedFrame['toc_name']        = $encoding_converted_text; | ||
|  | 						} elseif ($subframe['name'] == 'TIT3') { | ||
|  | 							$parsedFrame['toc_description'] = $encoding_converted_text; | ||
|  | 						} | ||
|  | 						$parsedFrame['subframes'][] = $subframe; | ||
|  | 					} else { | ||
|  | 						$this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)'); | ||
|  | 					} | ||
|  | 				} | ||
|  | 				unset($subframe_rawdata, $subframe, $encoding_converted_text); | ||
|  | 			} | ||
|  | 
 | ||
|  | 		} | ||
|  | 
 | ||
|  | 		return true; | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	public function DeUnsynchronise($data) { | ||
|  | 		return str_replace("\xFF\x00", "\xFF", $data); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) { | ||
|  | 		static $LookupExtendedHeaderRestrictionsTagSizeLimits = array( | ||
|  | 			0x00 => 'No more than 128 frames and 1 MB total tag size', | ||
|  | 			0x01 => 'No more than 64 frames and 128 KB total tag size', | ||
|  | 			0x02 => 'No more than 32 frames and 40 KB total tag size', | ||
|  | 			0x03 => 'No more than 32 frames and 4 KB total tag size', | ||
|  | 		); | ||
|  | 		return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : ''); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function LookupExtendedHeaderRestrictionsTextEncodings($index) { | ||
|  | 		static $LookupExtendedHeaderRestrictionsTextEncodings = array( | ||
|  | 			0x00 => 'No restrictions', | ||
|  | 			0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8', | ||
|  | 		); | ||
|  | 		return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : ''); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function LookupExtendedHeaderRestrictionsTextFieldSize($index) { | ||
|  | 		static $LookupExtendedHeaderRestrictionsTextFieldSize = array( | ||
|  | 			0x00 => 'No restrictions', | ||
|  | 			0x01 => 'No string is longer than 1024 characters', | ||
|  | 			0x02 => 'No string is longer than 128 characters', | ||
|  | 			0x03 => 'No string is longer than 30 characters', | ||
|  | 		); | ||
|  | 		return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : ''); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function LookupExtendedHeaderRestrictionsImageEncoding($index) { | ||
|  | 		static $LookupExtendedHeaderRestrictionsImageEncoding = array( | ||
|  | 			0x00 => 'No restrictions', | ||
|  | 			0x01 => 'Images are encoded only with PNG or JPEG', | ||
|  | 		); | ||
|  | 		return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : ''); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function LookupExtendedHeaderRestrictionsImageSizeSize($index) { | ||
|  | 		static $LookupExtendedHeaderRestrictionsImageSizeSize = array( | ||
|  | 			0x00 => 'No restrictions', | ||
|  | 			0x01 => 'All images are 256x256 pixels or smaller', | ||
|  | 			0x02 => 'All images are 64x64 pixels or smaller', | ||
|  | 			0x03 => 'All images are exactly 64x64 pixels, unless required otherwise', | ||
|  | 		); | ||
|  | 		return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : ''); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function LookupCurrencyUnits($currencyid) { | ||
|  | 
 | ||
|  | 		$begin = __LINE__; | ||
|  | 
 | ||
|  | 		/** This is not a comment! | ||
|  | 
 | ||
|  | 
 | ||
|  | 			AED	Dirhams | ||
|  | 			AFA	Afghanis | ||
|  | 			ALL	Leke | ||
|  | 			AMD	Drams | ||
|  | 			ANG	Guilders | ||
|  | 			AOA	Kwanza | ||
|  | 			ARS	Pesos | ||
|  | 			ATS	Schillings | ||
|  | 			AUD	Dollars | ||
|  | 			AWG	Guilders | ||
|  | 			AZM	Manats | ||
|  | 			BAM	Convertible Marka | ||
|  | 			BBD	Dollars | ||
|  | 			BDT	Taka | ||
|  | 			BEF	Francs | ||
|  | 			BGL	Leva | ||
|  | 			BHD	Dinars | ||
|  | 			BIF	Francs | ||
|  | 			BMD	Dollars | ||
|  | 			BND	Dollars | ||
|  | 			BOB	Bolivianos | ||
|  | 			BRL	Brazil Real | ||
|  | 			BSD	Dollars | ||
|  | 			BTN	Ngultrum | ||
|  | 			BWP	Pulas | ||
|  | 			BYR	Rubles | ||
|  | 			BZD	Dollars | ||
|  | 			CAD	Dollars | ||
|  | 			CDF	Congolese Francs | ||
|  | 			CHF	Francs | ||
|  | 			CLP	Pesos | ||
|  | 			CNY	Yuan Renminbi | ||
|  | 			COP	Pesos | ||
|  | 			CRC	Colones | ||
|  | 			CUP	Pesos | ||
|  | 			CVE	Escudos | ||
|  | 			CYP	Pounds | ||
|  | 			CZK	Koruny | ||
|  | 			DEM	Deutsche Marks | ||
|  | 			DJF	Francs | ||
|  | 			DKK	Kroner | ||
|  | 			DOP	Pesos | ||
|  | 			DZD	Algeria Dinars | ||
|  | 			EEK	Krooni | ||
|  | 			EGP	Pounds | ||
|  | 			ERN	Nakfa | ||
|  | 			ESP	Pesetas | ||
|  | 			ETB	Birr | ||
|  | 			EUR	Euro | ||
|  | 			FIM	Markkaa | ||
|  | 			FJD	Dollars | ||
|  | 			FKP	Pounds | ||
|  | 			FRF	Francs | ||
|  | 			GBP	Pounds | ||
|  | 			GEL	Lari | ||
|  | 			GGP	Pounds | ||
|  | 			GHC	Cedis | ||
|  | 			GIP	Pounds | ||
|  | 			GMD	Dalasi | ||
|  | 			GNF	Francs | ||
|  | 			GRD	Drachmae | ||
|  | 			GTQ	Quetzales | ||
|  | 			GYD	Dollars | ||
|  | 			HKD	Dollars | ||
|  | 			HNL	Lempiras | ||
|  | 			HRK	Kuna | ||
|  | 			HTG	Gourdes | ||
|  | 			HUF	Forints | ||
|  | 			IDR	Rupiahs | ||
|  | 			IEP	Pounds | ||
|  | 			ILS	New Shekels | ||
|  | 			IMP	Pounds | ||
|  | 			INR	Rupees | ||
|  | 			IQD	Dinars | ||
|  | 			IRR	Rials | ||
|  | 			ISK	Kronur | ||
|  | 			ITL	Lire | ||
|  | 			JEP	Pounds | ||
|  | 			JMD	Dollars | ||
|  | 			JOD	Dinars | ||
|  | 			JPY	Yen | ||
|  | 			KES	Shillings | ||
|  | 			KGS	Soms | ||
|  | 			KHR	Riels | ||
|  | 			KMF	Francs | ||
|  | 			KPW	Won | ||
|  | 			KWD	Dinars | ||
|  | 			KYD	Dollars | ||
|  | 			KZT	Tenge | ||
|  | 			LAK	Kips | ||
|  | 			LBP	Pounds | ||
|  | 			LKR	Rupees | ||
|  | 			LRD	Dollars | ||
|  | 			LSL	Maloti | ||
|  | 			LTL	Litai | ||
|  | 			LUF	Francs | ||
|  | 			LVL	Lati | ||
|  | 			LYD	Dinars | ||
|  | 			MAD	Dirhams | ||
|  | 			MDL	Lei | ||
|  | 			MGF	Malagasy Francs | ||
|  | 			MKD	Denars | ||
|  | 			MMK	Kyats | ||
|  | 			MNT	Tugriks | ||
|  | 			MOP	Patacas | ||
|  | 			MRO	Ouguiyas | ||
|  | 			MTL	Liri | ||
|  | 			MUR	Rupees | ||
|  | 			MVR	Rufiyaa | ||
|  | 			MWK	Kwachas | ||
|  | 			MXN	Pesos | ||
|  | 			MYR	Ringgits | ||
|  | 			MZM	Meticais | ||
|  | 			NAD	Dollars | ||
|  | 			NGN	Nairas | ||
|  | 			NIO	Gold Cordobas | ||
|  | 			NLG	Guilders | ||
|  | 			NOK	Krone | ||
|  | 			NPR	Nepal Rupees | ||
|  | 			NZD	Dollars | ||
|  | 			OMR	Rials | ||
|  | 			PAB	Balboa | ||
|  | 			PEN	Nuevos Soles | ||
|  | 			PGK	Kina | ||
|  | 			PHP	Pesos | ||
|  | 			PKR	Rupees | ||
|  | 			PLN	Zlotych | ||
|  | 			PTE	Escudos | ||
|  | 			PYG	Guarani | ||
|  | 			QAR	Rials | ||
|  | 			ROL	Lei | ||
|  | 			RUR	Rubles | ||
|  | 			RWF	Rwanda Francs | ||
|  | 			SAR	Riyals | ||
|  | 			SBD	Dollars | ||
|  | 			SCR	Rupees | ||
|  | 			SDD	Dinars | ||
|  | 			SEK	Kronor | ||
|  | 			SGD	Dollars | ||
|  | 			SHP	Pounds | ||
|  | 			SIT	Tolars | ||
|  | 			SKK	Koruny | ||
|  | 			SLL	Leones | ||
|  | 			SOS	Shillings | ||
|  | 			SPL	Luigini | ||
|  | 			SRG	Guilders | ||
|  | 			STD	Dobras | ||
|  | 			SVC	Colones | ||
|  | 			SYP	Pounds | ||
|  | 			SZL	Emalangeni | ||
|  | 			THB	Baht | ||
|  | 			TJR	Rubles | ||
|  | 			TMM	Manats | ||
|  | 			TND	Dinars | ||
|  | 			TOP	Pa'anga | ||
|  | 			TRL	Liras | ||
|  | 			TTD	Dollars | ||
|  | 			TVD	Tuvalu Dollars | ||
|  | 			TWD	New Dollars | ||
|  | 			TZS	Shillings | ||
|  | 			UAH	Hryvnia | ||
|  | 			UGX	Shillings | ||
|  | 			USD	Dollars | ||
|  | 			UYU	Pesos | ||
|  | 			UZS	Sums | ||
|  | 			VAL	Lire | ||
|  | 			VEB	Bolivares | ||
|  | 			VND	Dong | ||
|  | 			VUV	Vatu | ||
|  | 			WST	Tala | ||
|  | 			XAF	Francs | ||
|  | 			XAG	Ounces | ||
|  | 			XAU	Ounces | ||
|  | 			XCD	Dollars | ||
|  | 			XDR	Special Drawing Rights | ||
|  | 			XPD	Ounces | ||
|  | 			XPF	Francs | ||
|  | 			XPT	Ounces | ||
|  | 			YER	Rials | ||
|  | 			YUM	New Dinars | ||
|  | 			ZAR	Rand | ||
|  | 			ZMK	Kwacha | ||
|  | 			ZWD	Zimbabwe Dollars | ||
|  | 
 | ||
|  | 		*/ | ||
|  | 
 | ||
|  | 		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units'); | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	public function LookupCurrencyCountry($currencyid) { | ||
|  | 
 | ||
|  | 		$begin = __LINE__; | ||
|  | 
 | ||
|  | 		/** This is not a comment! | ||
|  | 
 | ||
|  | 			AED	United Arab Emirates | ||
|  | 			AFA	Afghanistan | ||
|  | 			ALL	Albania | ||
|  | 			AMD	Armenia | ||
|  | 			ANG	Netherlands Antilles | ||
|  | 			AOA	Angola | ||
|  | 			ARS	Argentina | ||
|  | 			ATS	Austria | ||
|  | 			AUD	Australia | ||
|  | 			AWG	Aruba | ||
|  | 			AZM	Azerbaijan | ||
|  | 			BAM	Bosnia and Herzegovina | ||
|  | 			BBD	Barbados | ||
|  | 			BDT	Bangladesh | ||
|  | 			BEF	Belgium | ||
|  | 			BGL	Bulgaria | ||
|  | 			BHD	Bahrain | ||
|  | 			BIF	Burundi | ||
|  | 			BMD	Bermuda | ||
|  | 			BND	Brunei Darussalam | ||
|  | 			BOB	Bolivia | ||
|  | 			BRL	Brazil | ||
|  | 			BSD	Bahamas | ||
|  | 			BTN	Bhutan | ||
|  | 			BWP	Botswana | ||
|  | 			BYR	Belarus | ||
|  | 			BZD	Belize | ||
|  | 			CAD	Canada | ||
|  | 			CDF	Congo/Kinshasa | ||
|  | 			CHF	Switzerland | ||
|  | 			CLP	Chile | ||
|  | 			CNY	China | ||
|  | 			COP	Colombia | ||
|  | 			CRC	Costa Rica | ||
|  | 			CUP	Cuba | ||
|  | 			CVE	Cape Verde | ||
|  | 			CYP	Cyprus | ||
|  | 			CZK	Czech Republic | ||
|  | 			DEM	Germany | ||
|  | 			DJF	Djibouti | ||
|  | 			DKK	Denmark | ||
|  | 			DOP	Dominican Republic | ||
|  | 			DZD	Algeria | ||
|  | 			EEK	Estonia | ||
|  | 			EGP	Egypt | ||
|  | 			ERN	Eritrea | ||
|  | 			ESP	Spain | ||
|  | 			ETB	Ethiopia | ||
|  | 			EUR	Euro Member Countries | ||
|  | 			FIM	Finland | ||
|  | 			FJD	Fiji | ||
|  | 			FKP	Falkland Islands (Malvinas) | ||
|  | 			FRF	France | ||
|  | 			GBP	United Kingdom | ||
|  | 			GEL	Georgia | ||
|  | 			GGP	Guernsey | ||
|  | 			GHC	Ghana | ||
|  | 			GIP	Gibraltar | ||
|  | 			GMD	Gambia | ||
|  | 			GNF	Guinea | ||
|  | 			GRD	Greece | ||
|  | 			GTQ	Guatemala | ||
|  | 			GYD	Guyana | ||
|  | 			HKD	Hong Kong | ||
|  | 			HNL	Honduras | ||
|  | 			HRK	Croatia | ||
|  | 			HTG	Haiti | ||
|  | 			HUF	Hungary | ||
|  | 			IDR	Indonesia | ||
|  | 			IEP	Ireland (Eire) | ||
|  | 			ILS	Israel | ||
|  | 			IMP	Isle of Man | ||
|  | 			INR	India | ||
|  | 			IQD	Iraq | ||
|  | 			IRR	Iran | ||
|  | 			ISK	Iceland | ||
|  | 			ITL	Italy | ||
|  | 			JEP	Jersey | ||
|  | 			JMD	Jamaica | ||
|  | 			JOD	Jordan | ||
|  | 			JPY	Japan | ||
|  | 			KES	Kenya | ||
|  | 			KGS	Kyrgyzstan | ||
|  | 			KHR	Cambodia | ||
|  | 			KMF	Comoros | ||
|  | 			KPW	Korea | ||
|  | 			KWD	Kuwait | ||
|  | 			KYD	Cayman Islands | ||
|  | 			KZT	Kazakstan | ||
|  | 			LAK	Laos | ||
|  | 			LBP	Lebanon | ||
|  | 			LKR	Sri Lanka | ||
|  | 			LRD	Liberia | ||
|  | 			LSL	Lesotho | ||
|  | 			LTL	Lithuania | ||
|  | 			LUF	Luxembourg | ||
|  | 			LVL	Latvia | ||
|  | 			LYD	Libya | ||
|  | 			MAD	Morocco | ||
|  | 			MDL	Moldova | ||
|  | 			MGF	Madagascar | ||
|  | 			MKD	Macedonia | ||
|  | 			MMK	Myanmar (Burma) | ||
|  | 			MNT	Mongolia | ||
|  | 			MOP	Macau | ||
|  | 			MRO	Mauritania | ||
|  | 			MTL	Malta | ||
|  | 			MUR	Mauritius | ||
|  | 			MVR	Maldives (Maldive Islands) | ||
|  | 			MWK	Malawi | ||
|  | 			MXN	Mexico | ||
|  | 			MYR	Malaysia | ||
|  | 			MZM	Mozambique | ||
|  | 			NAD	Namibia | ||
|  | 			NGN	Nigeria | ||
|  | 			NIO	Nicaragua | ||
|  | 			NLG	Netherlands (Holland) | ||
|  | 			NOK	Norway | ||
|  | 			NPR	Nepal | ||
|  | 			NZD	New Zealand | ||
|  | 			OMR	Oman | ||
|  | 			PAB	Panama | ||
|  | 			PEN	Peru | ||
|  | 			PGK	Papua New Guinea | ||
|  | 			PHP	Philippines | ||
|  | 			PKR	Pakistan | ||
|  | 			PLN	Poland | ||
|  | 			PTE	Portugal | ||
|  | 			PYG	Paraguay | ||
|  | 			QAR	Qatar | ||
|  | 			ROL	Romania | ||
|  | 			RUR	Russia | ||
|  | 			RWF	Rwanda | ||
|  | 			SAR	Saudi Arabia | ||
|  | 			SBD	Solomon Islands | ||
|  | 			SCR	Seychelles | ||
|  | 			SDD	Sudan | ||
|  | 			SEK	Sweden | ||
|  | 			SGD	Singapore | ||
|  | 			SHP	Saint Helena | ||
|  | 			SIT	Slovenia | ||
|  | 			SKK	Slovakia | ||
|  | 			SLL	Sierra Leone | ||
|  | 			SOS	Somalia | ||
|  | 			SPL	Seborga | ||
|  | 			SRG	Suriname | ||
|  | 			STD	São Tome and Principe | ||
|  | 			SVC	El Salvador | ||
|  | 			SYP	Syria | ||
|  | 			SZL	Swaziland | ||
|  | 			THB	Thailand | ||
|  | 			TJR	Tajikistan | ||
|  | 			TMM	Turkmenistan | ||
|  | 			TND	Tunisia | ||
|  | 			TOP	Tonga | ||
|  | 			TRL	Turkey | ||
|  | 			TTD	Trinidad and Tobago | ||
|  | 			TVD	Tuvalu | ||
|  | 			TWD	Taiwan | ||
|  | 			TZS	Tanzania | ||
|  | 			UAH	Ukraine | ||
|  | 			UGX	Uganda | ||
|  | 			USD	United States of America | ||
|  | 			UYU	Uruguay | ||
|  | 			UZS	Uzbekistan | ||
|  | 			VAL	Vatican City | ||
|  | 			VEB	Venezuela | ||
|  | 			VND	Viet Nam | ||
|  | 			VUV	Vanuatu | ||
|  | 			WST	Samoa | ||
|  | 			XAF	Communauté Financière Africaine | ||
|  | 			XAG	Silver | ||
|  | 			XAU	Gold | ||
|  | 			XCD	East Caribbean | ||
|  | 			XDR	International Monetary Fund | ||
|  | 			XPD	Palladium | ||
|  | 			XPF	Comptoirs Français du Pacifique | ||
|  | 			XPT	Platinum | ||
|  | 			YER	Yemen | ||
|  | 			YUM	Yugoslavia | ||
|  | 			ZAR	South Africa | ||
|  | 			ZMK	Zambia | ||
|  | 			ZWD	Zimbabwe | ||
|  | 
 | ||
|  | 		*/ | ||
|  | 
 | ||
|  | 		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country'); | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 	public static function LanguageLookup($languagecode, $casesensitive=false) { | ||
|  | 
 | ||
|  | 		if (!$casesensitive) { | ||
|  | 			$languagecode = strtolower($languagecode); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// http://www.id3.org/id3v2.4.0-structure.txt
 | ||
|  | 		// [4.   ID3v2 frame overview]
 | ||
|  | 		// The three byte language field, present in several frames, is used to
 | ||
|  | 		// describe the language of the frame's content, according to ISO-639-2
 | ||
|  | 		// [ISO-639-2]. The language should be represented in lower case. If the
 | ||
|  | 		// language is not known the string "XXX" should be used.
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 		// ISO 639-2 - http://www.id3.org/iso639-2.html
 | ||
|  | 
 | ||
|  | 		$begin = __LINE__; | ||
|  | 
 | ||
|  | 		/** This is not a comment! | ||
|  | 
 | ||
|  | 			XXX	unknown | ||
|  | 			xxx	unknown | ||
|  | 			aar	Afar | ||
|  | 			abk	Abkhazian | ||
|  | 			ace	Achinese | ||
|  | 			ach	Acoli | ||
|  | 			ada	Adangme | ||
|  | 			afa	Afro-Asiatic (Other) | ||
|  | 			afh	Afrihili | ||
|  | 			afr	Afrikaans | ||
|  | 			aka	Akan | ||
|  | 			akk	Akkadian | ||
|  | 			alb	Albanian | ||
|  | 			ale	Aleut | ||
|  | 			alg	Algonquian Languages | ||
|  | 			amh	Amharic | ||
|  | 			ang	English, Old (ca. 450-1100) | ||
|  | 			apa	Apache Languages | ||
|  | 			ara	Arabic | ||
|  | 			arc	Aramaic | ||
|  | 			arm	Armenian | ||
|  | 			arn	Araucanian | ||
|  | 			arp	Arapaho | ||
|  | 			art	Artificial (Other) | ||
|  | 			arw	Arawak | ||
|  | 			asm	Assamese | ||
|  | 			ath	Athapascan Languages | ||
|  | 			ava	Avaric | ||
|  | 			ave	Avestan | ||
|  | 			awa	Awadhi | ||
|  | 			aym	Aymara | ||
|  | 			aze	Azerbaijani | ||
|  | 			bad	Banda | ||
|  | 			bai	Bamileke Languages | ||
|  | 			bak	Bashkir | ||
|  | 			bal	Baluchi | ||
|  | 			bam	Bambara | ||
|  | 			ban	Balinese | ||
|  | 			baq	Basque | ||
|  | 			bas	Basa | ||
|  | 			bat	Baltic (Other) | ||
|  | 			bej	Beja | ||
|  | 			bel	Byelorussian | ||
|  | 			bem	Bemba | ||
|  | 			ben	Bengali | ||
|  | 			ber	Berber (Other) | ||
|  | 			bho	Bhojpuri | ||
|  | 			bih	Bihari | ||
|  | 			bik	Bikol | ||
|  | 			bin	Bini | ||
|  | 			bis	Bislama | ||
|  | 			bla	Siksika | ||
|  | 			bnt	Bantu (Other) | ||
|  | 			bod	Tibetan | ||
|  | 			bra	Braj | ||
|  | 			bre	Breton | ||
|  | 			bua	Buriat | ||
|  | 			bug	Buginese | ||
|  | 			bul	Bulgarian | ||
|  | 			bur	Burmese | ||
|  | 			cad	Caddo | ||
|  | 			cai	Central American Indian (Other) | ||
|  | 			car	Carib | ||
|  | 			cat	Catalan | ||
|  | 			cau	Caucasian (Other) | ||
|  | 			ceb	Cebuano | ||
|  | 			cel	Celtic (Other) | ||
|  | 			ces	Czech | ||
|  | 			cha	Chamorro | ||
|  | 			chb	Chibcha | ||
|  | 			che	Chechen | ||
|  | 			chg	Chagatai | ||
|  | 			chi	Chinese | ||
|  | 			chm	Mari | ||
|  | 			chn	Chinook jargon | ||
|  | 			cho	Choctaw | ||
|  | 			chr	Cherokee | ||
|  | 			chu	Church Slavic | ||
|  | 			chv	Chuvash | ||
|  | 			chy	Cheyenne | ||
|  | 			cop	Coptic | ||
|  | 			cor	Cornish | ||
|  | 			cos	Corsican | ||
|  | 			cpe	Creoles and Pidgins, English-based (Other) | ||
|  | 			cpf	Creoles and Pidgins, French-based (Other) | ||
|  | 			cpp	Creoles and Pidgins, Portuguese-based (Other) | ||
|  | 			cre	Cree | ||
|  | 			crp	Creoles and Pidgins (Other) | ||
|  | 			cus	Cushitic (Other) | ||
|  | 			cym	Welsh | ||
|  | 			cze	Czech | ||
|  | 			dak	Dakota | ||
|  | 			dan	Danish | ||
|  | 			del	Delaware | ||
|  | 			deu	German | ||
|  | 			din	Dinka | ||
|  | 			div	Divehi | ||
|  | 			doi	Dogri | ||
|  | 			dra	Dravidian (Other) | ||
|  | 			dua	Duala | ||
|  | 			dum	Dutch, Middle (ca. 1050-1350) | ||
|  | 			dut	Dutch | ||
|  | 			dyu	Dyula | ||
|  | 			dzo	Dzongkha | ||
|  | 			efi	Efik | ||
|  | 			egy	Egyptian (Ancient) | ||
|  | 			eka	Ekajuk | ||
|  | 			ell	Greek, Modern (1453-) | ||
|  | 			elx	Elamite | ||
|  | 			eng	English | ||
|  | 			enm	English, Middle (ca. 1100-1500) | ||
|  | 			epo	Esperanto | ||
|  | 			esk	Eskimo (Other) | ||
|  | 			esl	Spanish | ||
|  | 			est	Estonian | ||
|  | 			eus	Basque | ||
|  | 			ewe	Ewe | ||
|  | 			ewo	Ewondo | ||
|  | 			fan	Fang | ||
|  | 			fao	Faroese | ||
|  | 			fas	Persian | ||
|  | 			fat	Fanti | ||
|  | 			fij	Fijian | ||
|  | 			fin	Finnish | ||
|  | 			fiu	Finno-Ugrian (Other) | ||
|  | 			fon	Fon | ||
|  | 			fra	French | ||
|  | 			fre	French | ||
|  | 			frm	French, Middle (ca. 1400-1600) | ||
|  | 			fro	French, Old (842- ca. 1400) | ||
|  | 			fry	Frisian | ||
|  | 			ful	Fulah | ||
|  | 			gaa	Ga | ||
|  | 			gae	Gaelic (Scots) | ||
|  | 			gai	Irish | ||
|  | 			gay	Gayo | ||
|  | 			gdh	Gaelic (Scots) | ||
|  | 			gem	Germanic (Other) | ||
|  | 			geo	Georgian | ||
|  | 			ger	German | ||
|  | 			gez	Geez | ||
|  | 			gil	Gilbertese | ||
|  | 			glg	Gallegan | ||
|  | 			gmh	German, Middle High (ca. 1050-1500) | ||
|  | 			goh	German, Old High (ca. 750-1050) | ||
|  | 			gon	Gondi | ||
|  | 			got	Gothic | ||
|  | 			grb	Grebo | ||
|  | 			grc	Greek, Ancient (to 1453) | ||
|  | 			gre	Greek, Modern (1453-) | ||
|  | 			grn	Guarani | ||
|  | 			guj	Gujarati | ||
|  | 			hai	Haida | ||
|  | 			hau	Hausa | ||
|  | 			haw	Hawaiian | ||
|  | 			heb	Hebrew | ||
|  | 			her	Herero | ||
|  | 			hil	Hiligaynon | ||
|  | 			him	Himachali | ||
|  | 			hin	Hindi | ||
|  | 			hmo	Hiri Motu | ||
|  | 			hun	Hungarian | ||
|  | 			hup	Hupa | ||
|  | 			hye	Armenian | ||
|  | 			iba	Iban | ||
|  | 			ibo	Igbo | ||
|  | 			ice	Icelandic | ||
|  | 			ijo	Ijo | ||
|  | 			iku	Inuktitut | ||
|  | 			ilo	Iloko | ||
|  | 			ina	Interlingua (International Auxiliary language Association) | ||
|  | 			inc	Indic (Other) | ||
|  | 			ind	Indonesian | ||
|  | 			ine	Indo-European (Other) | ||
|  | 			ine	Interlingue | ||
|  | 			ipk	Inupiak | ||
|  | 			ira	Iranian (Other) | ||
|  | 			iri	Irish | ||
|  | 			iro	Iroquoian uages | ||
|  | 			isl	Icelandic | ||
|  | 			ita	Italian | ||
|  | 			jav	Javanese | ||
|  | 			jaw	Javanese | ||
|  | 			jpn	Japanese | ||
|  | 			jpr	Judeo-Persian | ||
|  | 			jrb	Judeo-Arabic | ||
|  | 			kaa	Kara-Kalpak | ||
|  | 			kab	Kabyle | ||
|  | 			kac	Kachin | ||
|  | 			kal	Greenlandic | ||
|  | 			kam	Kamba | ||
|  | 			kan	Kannada | ||
|  | 			kar	Karen | ||
|  | 			kas	Kashmiri | ||
|  | 			kat	Georgian | ||
|  | 			kau	Kanuri | ||
|  | 			kaw	Kawi | ||
|  | 			kaz	Kazakh | ||
|  | 			kha	Khasi | ||
|  | 			khi	Khoisan (Other) | ||
|  | 			khm	Khmer | ||
|  | 			kho	Khotanese | ||
|  | 			kik	Kikuyu | ||
|  | 			kin	Kinyarwanda | ||
|  | 			kir	Kirghiz | ||
|  | 			kok	Konkani | ||
|  | 			kom	Komi | ||
|  | 			kon	Kongo | ||
|  | 			kor	Korean | ||
|  | 			kpe	Kpelle | ||
|  | 			kro	Kru | ||
|  | 			kru	Kurukh | ||
|  | 			kua	Kuanyama | ||
|  | 			kum	Kumyk | ||
|  | 			kur	Kurdish | ||
|  | 			kus	Kusaie | ||
|  | 			kut	Kutenai | ||
|  | 			lad	Ladino | ||
|  | 			lah	Lahnda | ||
|  | 			lam	Lamba | ||
|  | 			lao	Lao | ||
|  | 			lat	Latin | ||
|  | 			lav	Latvian | ||
|  | 			lez	Lezghian | ||
|  | 			lin	Lingala | ||
|  | 			lit	Lithuanian | ||
|  | 			lol	Mongo | ||
|  | 			loz	Lozi | ||
|  | 			ltz	Letzeburgesch | ||
|  | 			lub	Luba-Katanga | ||
|  | 			lug	Ganda | ||
|  | 			lui	Luiseno | ||
|  | 			lun	Lunda | ||
|  | 			luo	Luo (Kenya and Tanzania) | ||
|  | 			mac	Macedonian | ||
|  | 			mad	Madurese | ||
|  | 			mag	Magahi | ||
|  | 			mah	Marshall | ||
|  | 			mai	Maithili | ||
|  | 			mak	Macedonian | ||
|  | 			mak	Makasar | ||
|  | 			mal	Malayalam | ||
|  | 			man	Mandingo | ||
|  | 			mao	Maori | ||
|  | 			map	Austronesian (Other) | ||
|  | 			mar	Marathi | ||
|  | 			mas	Masai | ||
|  | 			max	Manx | ||
|  | 			may	Malay | ||
|  | 			men	Mende | ||
|  | 			mga	Irish, Middle (900 - 1200) | ||
|  | 			mic	Micmac | ||
|  | 			min	Minangkabau | ||
|  | 			mis	Miscellaneous (Other) | ||
|  | 			mkh	Mon-Kmer (Other) | ||
|  | 			mlg	Malagasy | ||
|  | 			mlt	Maltese | ||
|  | 			mni	Manipuri | ||
|  | 			mno	Manobo Languages | ||
|  | 			moh	Mohawk | ||
|  | 			mol	Moldavian | ||
|  | 			mon	Mongolian | ||
|  | 			mos	Mossi | ||
|  | 			mri	Maori | ||
|  | 			msa	Malay | ||
|  | 			mul	Multiple Languages | ||
|  | 			mun	Munda Languages | ||
|  | 			mus	Creek | ||
|  | 			mwr	Marwari | ||
|  | 			mya	Burmese | ||
|  | 			myn	Mayan Languages | ||
|  | 			nah	Aztec | ||
|  | 			nai	North American Indian (Other) | ||
|  | 			nau	Nauru | ||
|  | 			nav	Navajo | ||
|  | 			nbl	Ndebele, South | ||
|  | 			nde	Ndebele, North | ||
|  | 			ndo	Ndongo | ||
|  | 			nep	Nepali | ||
|  | 			new	Newari | ||
|  | 			nic	Niger-Kordofanian (Other) | ||
|  | 			niu	Niuean | ||
|  | 			nla	Dutch | ||
|  | 			nno	Norwegian (Nynorsk) | ||
|  | 			non	Norse, Old | ||
|  | 			nor	Norwegian | ||
|  | 			nso	Sotho, Northern | ||
|  | 			nub	Nubian Languages | ||
|  | 			nya	Nyanja | ||
|  | 			nym	Nyamwezi | ||
|  | 			nyn	Nyankole | ||
|  | 			nyo	Nyoro | ||
|  | 			nzi	Nzima | ||
|  | 			oci	Langue d'Oc (post 1500) | ||
|  | 			oji	Ojibwa | ||
|  | 			ori	Oriya | ||
|  | 			orm	Oromo | ||
|  | 			osa	Osage | ||
|  | 			oss	Ossetic | ||
|  | 			ota	Turkish, Ottoman (1500 - 1928) | ||
|  | 			oto	Otomian Languages | ||
|  | 			paa	Papuan-Australian (Other) | ||
|  | 			pag	Pangasinan | ||
|  | 			pal	Pahlavi | ||
|  | 			pam	Pampanga | ||
|  | 			pan	Panjabi | ||
|  | 			pap	Papiamento | ||
|  | 			pau	Palauan | ||
|  | 			peo	Persian, Old (ca 600 - 400 B.C.) | ||
|  | 			per	Persian | ||
|  | 			phn	Phoenician | ||
|  | 			pli	Pali | ||
|  | 			pol	Polish | ||
|  | 			pon	Ponape | ||
|  | 			por	Portuguese | ||
|  | 			pra	Prakrit uages | ||
|  | 			pro	Provencal, Old (to 1500) | ||
|  | 			pus	Pushto | ||
|  | 			que	Quechua | ||
|  | 			raj	Rajasthani | ||
|  | 			rar	Rarotongan | ||
|  | 			roa	Romance (Other) | ||
|  | 			roh	Rhaeto-Romance | ||
|  | 			rom	Romany | ||
|  | 			ron	Romanian | ||
|  | 			rum	Romanian | ||
|  | 			run	Rundi | ||
|  | 			rus	Russian | ||
|  | 			sad	Sandawe | ||
|  | 			sag	Sango | ||
|  | 			sah	Yakut | ||
|  | 			sai	South American Indian (Other) | ||
|  | 			sal	Salishan Languages | ||
|  | 			sam	Samaritan Aramaic | ||
|  | 			san	Sanskrit | ||
|  | 			sco	Scots | ||
|  | 			scr	Serbo-Croatian | ||
|  | 			sel	Selkup | ||
|  | 			sem	Semitic (Other) | ||
|  | 			sga	Irish, Old (to 900) | ||
|  | 			shn	Shan | ||
|  | 			sid	Sidamo | ||
|  | 			sin	Singhalese | ||
|  | 			sio	Siouan Languages | ||
|  | 			sit	Sino-Tibetan (Other) | ||
|  | 			sla	Slavic (Other) | ||
|  | 			slk	Slovak | ||
|  | 			slo	Slovak | ||
|  | 			slv	Slovenian | ||
|  | 			smi	Sami Languages | ||
|  | 			smo	Samoan | ||
|  | 			sna	Shona | ||
|  | 			snd	Sindhi | ||
|  | 			sog	Sogdian | ||
|  | 			som	Somali | ||
|  | 			son	Songhai | ||
|  | 			sot	Sotho, Southern | ||
|  | 			spa	Spanish | ||
|  | 			sqi	Albanian | ||
|  | 			srd	Sardinian | ||
|  | 			srr	Serer | ||
|  | 			ssa	Nilo-Saharan (Other) | ||
|  | 			ssw	Siswant | ||
|  | 			ssw	Swazi | ||
|  | 			suk	Sukuma | ||
|  | 			sun	Sudanese | ||
|  | 			sus	Susu | ||
|  | 			sux	Sumerian | ||
|  | 			sve	Swedish | ||
|  | 			swa	Swahili | ||
|  | 			swe	Swedish | ||
|  | 			syr	Syriac | ||
|  | 			tah	Tahitian | ||
|  | 			tam	Tamil | ||
|  | 			tat	Tatar | ||
|  | 			tel	Telugu | ||
|  | 			tem	Timne | ||
|  | 			ter	Tereno | ||
|  | 			tgk	Tajik | ||
|  | 			tgl	Tagalog | ||
|  | 			tha	Thai | ||
|  | 			tib	Tibetan | ||
|  | 			tig	Tigre | ||
|  | 			tir	Tigrinya | ||
|  | 			tiv	Tivi | ||
|  | 			tli	Tlingit | ||
|  | 			tmh	Tamashek | ||
|  | 			tog	Tonga (Nyasa) | ||
|  | 			ton	Tonga (Tonga Islands) | ||
|  | 			tru	Truk | ||
|  | 			tsi	Tsimshian | ||
|  | 			tsn	Tswana | ||
|  | 			tso	Tsonga | ||
|  | 			tuk	Turkmen | ||
|  | 			tum	Tumbuka | ||
|  | 			tur	Turkish | ||
|  | 			tut	Altaic (Other) | ||
|  | 			twi	Twi | ||
|  | 			tyv	Tuvinian | ||
|  | 			uga	Ugaritic | ||
|  | 			uig	Uighur | ||
|  | 			ukr	Ukrainian | ||
|  | 			umb	Umbundu | ||
|  | 			und	Undetermined | ||
|  | 			urd	Urdu | ||
|  | 			uzb	Uzbek | ||
|  | 			vai	Vai | ||
|  | 			ven	Venda | ||
|  | 			vie	Vietnamese | ||
|  | 			vol	Volapük | ||
|  | 			vot	Votic | ||
|  | 			wak	Wakashan Languages | ||
|  | 			wal	Walamo | ||
|  | 			war	Waray | ||
|  | 			was	Washo | ||
|  | 			wel	Welsh | ||
|  | 			wen	Sorbian Languages | ||
|  | 			wol	Wolof | ||
|  | 			xho	Xhosa | ||
|  | 			yao	Yao | ||
|  | 			yap	Yap | ||
|  | 			yid	Yiddish | ||
|  | 			yor	Yoruba | ||
|  | 			zap	Zapotec | ||
|  | 			zen	Zenaga | ||
|  | 			zha	Zhuang | ||
|  | 			zho	Chinese | ||
|  | 			zul	Zulu | ||
|  | 			zun	Zuni | ||
|  | 
 | ||
|  | 		*/ | ||
|  | 
 | ||
|  | 		return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode'); | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	public static function ETCOEventLookup($index) { | ||
|  | 		if (($index >= 0x17) && ($index <= 0xDF)) { | ||
|  | 			return 'reserved for future use'; | ||
|  | 		} | ||
|  | 		if (($index >= 0xE0) && ($index <= 0xEF)) { | ||
|  | 			return 'not predefined synch 0-F'; | ||
|  | 		} | ||
|  | 		if (($index >= 0xF0) && ($index <= 0xFC)) { | ||
|  | 			return 'reserved for future use'; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		static $EventLookup = array( | ||
|  | 			0x00 => 'padding (has no meaning)', | ||
|  | 			0x01 => 'end of initial silence', | ||
|  | 			0x02 => 'intro start', | ||
|  | 			0x03 => 'main part start', | ||
|  | 			0x04 => 'outro start', | ||
|  | 			0x05 => 'outro end', | ||
|  | 			0x06 => 'verse start', | ||
|  | 			0x07 => 'refrain start', | ||
|  | 			0x08 => 'interlude start', | ||
|  | 			0x09 => 'theme start', | ||
|  | 			0x0A => 'variation start', | ||
|  | 			0x0B => 'key change', | ||
|  | 			0x0C => 'time change', | ||
|  | 			0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)', | ||
|  | 			0x0E => 'sustained noise', | ||
|  | 			0x0F => 'sustained noise end', | ||
|  | 			0x10 => 'intro end', | ||
|  | 			0x11 => 'main part end', | ||
|  | 			0x12 => 'verse end', | ||
|  | 			0x13 => 'refrain end', | ||
|  | 			0x14 => 'theme end', | ||
|  | 			0x15 => 'profanity', | ||
|  | 			0x16 => 'profanity end', | ||
|  | 			0xFD => 'audio end (start of silence)', | ||
|  | 			0xFE => 'audio file ends', | ||
|  | 			0xFF => 'one more byte of events follows' | ||
|  | 		); | ||
|  | 
 | ||
|  | 		return (isset($EventLookup[$index]) ? $EventLookup[$index] : ''); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function SYTLContentTypeLookup($index) { | ||
|  | 		static $SYTLContentTypeLookup = array( | ||
|  | 			0x00 => 'other', | ||
|  | 			0x01 => 'lyrics', | ||
|  | 			0x02 => 'text transcription', | ||
|  | 			0x03 => 'movement/part name', // (e.g. 'Adagio')
 | ||
|  | 			0x04 => 'events',             // (e.g. 'Don Quijote enters the stage')
 | ||
|  | 			0x05 => 'chord',              // (e.g. 'Bb F Fsus')
 | ||
|  | 			0x06 => 'trivia/\'pop up\' information', | ||
|  | 			0x07 => 'URLs to webpages', | ||
|  | 			0x08 => 'URLs to images' | ||
|  | 		); | ||
|  | 
 | ||
|  | 		return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : ''); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function APICPictureTypeLookup($index, $returnarray=false) { | ||
|  | 		static $APICPictureTypeLookup = array( | ||
|  | 			0x00 => 'Other', | ||
|  | 			0x01 => '32x32 pixels \'file icon\' (PNG only)', | ||
|  | 			0x02 => 'Other file icon', | ||
|  | 			0x03 => 'Cover (front)', | ||
|  | 			0x04 => 'Cover (back)', | ||
|  | 			0x05 => 'Leaflet page', | ||
|  | 			0x06 => 'Media (e.g. label side of CD)', | ||
|  | 			0x07 => 'Lead artist/lead performer/soloist', | ||
|  | 			0x08 => 'Artist/performer', | ||
|  | 			0x09 => 'Conductor', | ||
|  | 			0x0A => 'Band/Orchestra', | ||
|  | 			0x0B => 'Composer', | ||
|  | 			0x0C => 'Lyricist/text writer', | ||
|  | 			0x0D => 'Recording Location', | ||
|  | 			0x0E => 'During recording', | ||
|  | 			0x0F => 'During performance', | ||
|  | 			0x10 => 'Movie/video screen capture', | ||
|  | 			0x11 => 'A bright coloured fish', | ||
|  | 			0x12 => 'Illustration', | ||
|  | 			0x13 => 'Band/artist logotype', | ||
|  | 			0x14 => 'Publisher/Studio logotype' | ||
|  | 		); | ||
|  | 		if ($returnarray) { | ||
|  | 			return $APICPictureTypeLookup; | ||
|  | 		} | ||
|  | 		return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : ''); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function COMRReceivedAsLookup($index) { | ||
|  | 		static $COMRReceivedAsLookup = array( | ||
|  | 			0x00 => 'Other', | ||
|  | 			0x01 => 'Standard CD album with other songs', | ||
|  | 			0x02 => 'Compressed audio on CD', | ||
|  | 			0x03 => 'File over the Internet', | ||
|  | 			0x04 => 'Stream over the Internet', | ||
|  | 			0x05 => 'As note sheets', | ||
|  | 			0x06 => 'As note sheets in a book with other sheets', | ||
|  | 			0x07 => 'Music on other media', | ||
|  | 			0x08 => 'Non-musical merchandise' | ||
|  | 		); | ||
|  | 
 | ||
|  | 		return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : ''); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function RVA2ChannelTypeLookup($index) { | ||
|  | 		static $RVA2ChannelTypeLookup = array( | ||
|  | 			0x00 => 'Other', | ||
|  | 			0x01 => 'Master volume', | ||
|  | 			0x02 => 'Front right', | ||
|  | 			0x03 => 'Front left', | ||
|  | 			0x04 => 'Back right', | ||
|  | 			0x05 => 'Back left', | ||
|  | 			0x06 => 'Front centre', | ||
|  | 			0x07 => 'Back centre', | ||
|  | 			0x08 => 'Subwoofer' | ||
|  | 		); | ||
|  | 
 | ||
|  | 		return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : ''); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function FrameNameLongLookup($framename) { | ||
|  | 
 | ||
|  | 		$begin = __LINE__; | ||
|  | 
 | ||
|  | 		/** This is not a comment! | ||
|  | 
 | ||
|  | 			AENC	Audio encryption | ||
|  | 			APIC	Attached picture | ||
|  | 			ASPI	Audio seek point index | ||
|  | 			BUF	Recommended buffer size | ||
|  | 			CNT	Play counter | ||
|  | 			COM	Comments | ||
|  | 			COMM	Comments | ||
|  | 			COMR	Commercial frame | ||
|  | 			CRA	Audio encryption | ||
|  | 			CRM	Encrypted meta frame | ||
|  | 			ENCR	Encryption method registration | ||
|  | 			EQU	Equalisation | ||
|  | 			EQU2	Equalisation (2) | ||
|  | 			EQUA	Equalisation | ||
|  | 			ETC	Event timing codes | ||
|  | 			ETCO	Event timing codes | ||
|  | 			GEO	General encapsulated object | ||
|  | 			GEOB	General encapsulated object | ||
|  | 			GRID	Group identification registration | ||
|  | 			IPL	Involved people list | ||
|  | 			IPLS	Involved people list | ||
|  | 			LINK	Linked information | ||
|  | 			LNK	Linked information | ||
|  | 			MCDI	Music CD identifier | ||
|  | 			MCI	Music CD Identifier | ||
|  | 			MLL	MPEG location lookup table | ||
|  | 			MLLT	MPEG location lookup table | ||
|  | 			OWNE	Ownership frame | ||
|  | 			PCNT	Play counter | ||
|  | 			PIC	Attached picture | ||
|  | 			POP	Popularimeter | ||
|  | 			POPM	Popularimeter | ||
|  | 			POSS	Position synchronisation frame | ||
|  | 			PRIV	Private frame | ||
|  | 			RBUF	Recommended buffer size | ||
|  | 			REV	Reverb | ||
|  | 			RVA	Relative volume adjustment | ||
|  | 			RVA2	Relative volume adjustment (2) | ||
|  | 			RVAD	Relative volume adjustment | ||
|  | 			RVRB	Reverb | ||
|  | 			SEEK	Seek frame | ||
|  | 			SIGN	Signature frame | ||
|  | 			SLT	Synchronised lyric/text | ||
|  | 			STC	Synced tempo codes | ||
|  | 			SYLT	Synchronised lyric/text | ||
|  | 			SYTC	Synchronised tempo codes | ||
|  | 			TAL	Album/Movie/Show title | ||
|  | 			TALB	Album/Movie/Show title | ||
|  | 			TBP	BPM (Beats Per Minute) | ||
|  | 			TBPM	BPM (beats per minute) | ||
|  | 			TCM	Composer | ||
|  | 			TCMP	Part of a compilation | ||
|  | 			TCO	Content type | ||
|  | 			TCOM	Composer | ||
|  | 			TCON	Content type | ||
|  | 			TCOP	Copyright message | ||
|  | 			TCP	Part of a compilation | ||
|  | 			TCR	Copyright message | ||
|  | 			TDA	Date | ||
|  | 			TDAT	Date | ||
|  | 			TDEN	Encoding time | ||
|  | 			TDLY	Playlist delay | ||
|  | 			TDOR	Original release time | ||
|  | 			TDRC	Recording time | ||
|  | 			TDRL	Release time | ||
|  | 			TDTG	Tagging time | ||
|  | 			TDY	Playlist delay | ||
|  | 			TEN	Encoded by | ||
|  | 			TENC	Encoded by | ||
|  | 			TEXT	Lyricist/Text writer | ||
|  | 			TFLT	File type | ||
|  | 			TFT	File type | ||
|  | 			TIM	Time | ||
|  | 			TIME	Time | ||
|  | 			TIPL	Involved people list | ||
|  | 			TIT1	Content group description | ||
|  | 			TIT2	Title/songname/content description | ||
|  | 			TIT3	Subtitle/Description refinement | ||
|  | 			TKE	Initial key | ||
|  | 			TKEY	Initial key | ||
|  | 			TLA	Language(s) | ||
|  | 			TLAN	Language(s) | ||
|  | 			TLE	Length | ||
|  | 			TLEN	Length | ||
|  | 			TMCL	Musician credits list | ||
|  | 			TMED	Media type | ||
|  | 			TMOO	Mood | ||
|  | 			TMT	Media type | ||
|  | 			TOA	Original artist(s)/performer(s) | ||
|  | 			TOAL	Original album/movie/show title | ||
|  | 			TOF	Original filename | ||
|  | 			TOFN	Original filename | ||
|  | 			TOL	Original Lyricist(s)/text writer(s) | ||
|  | 			TOLY	Original lyricist(s)/text writer(s) | ||
|  | 			TOPE	Original artist(s)/performer(s) | ||
|  | 			TOR	Original release year | ||
|  | 			TORY	Original release year | ||
|  | 			TOT	Original album/Movie/Show title | ||
|  | 			TOWN	File owner/licensee | ||
|  | 			TP1	Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group | ||
|  | 			TP2	Band/Orchestra/Accompaniment | ||
|  | 			TP3	Conductor/Performer refinement | ||
|  | 			TP4	Interpreted, remixed, or otherwise modified by | ||
|  | 			TPA	Part of a set | ||
|  | 			TPB	Publisher | ||
|  | 			TPE1	Lead performer(s)/Soloist(s) | ||
|  | 			TPE2	Band/orchestra/accompaniment | ||
|  | 			TPE3	Conductor/performer refinement | ||
|  | 			TPE4	Interpreted, remixed, or otherwise modified by | ||
|  | 			TPOS	Part of a set | ||
|  | 			TPRO	Produced notice | ||
|  | 			TPUB	Publisher | ||
|  | 			TRC	ISRC (International Standard Recording Code) | ||
|  | 			TRCK	Track number/Position in set | ||
|  | 			TRD	Recording dates | ||
|  | 			TRDA	Recording dates | ||
|  | 			TRK	Track number/Position in set | ||
|  | 			TRSN	Internet radio station name | ||
|  | 			TRSO	Internet radio station owner | ||
|  | 			TS2	Album-Artist sort order | ||
|  | 			TSA	Album sort order | ||
|  | 			TSC	Composer sort order | ||
|  | 			TSI	Size | ||
|  | 			TSIZ	Size | ||
|  | 			TSO2	Album-Artist sort order | ||
|  | 			TSOA	Album sort order | ||
|  | 			TSOC	Composer sort order | ||
|  | 			TSOP	Performer sort order | ||
|  | 			TSOT	Title sort order | ||
|  | 			TSP	Performer sort order | ||
|  | 			TSRC	ISRC (international standard recording code) | ||
|  | 			TSS	Software/hardware and settings used for encoding | ||
|  | 			TSSE	Software/Hardware and settings used for encoding | ||
|  | 			TSST	Set subtitle | ||
|  | 			TST	Title sort order | ||
|  | 			TT1	Content group description | ||
|  | 			TT2	Title/Songname/Content description | ||
|  | 			TT3	Subtitle/Description refinement | ||
|  | 			TXT	Lyricist/text writer | ||
|  | 			TXX	User defined text information frame | ||
|  | 			TXXX	User defined text information frame | ||
|  | 			TYE	Year | ||
|  | 			TYER	Year | ||
|  | 			UFI	Unique file identifier | ||
|  | 			UFID	Unique file identifier | ||
|  | 			ULT	Unsychronised lyric/text transcription | ||
|  | 			USER	Terms of use | ||
|  | 			USLT	Unsynchronised lyric/text transcription | ||
|  | 			WAF	Official audio file webpage | ||
|  | 			WAR	Official artist/performer webpage | ||
|  | 			WAS	Official audio source webpage | ||
|  | 			WCM	Commercial information | ||
|  | 			WCOM	Commercial information | ||
|  | 			WCOP	Copyright/Legal information | ||
|  | 			WCP	Copyright/Legal information | ||
|  | 			WOAF	Official audio file webpage | ||
|  | 			WOAR	Official artist/performer webpage | ||
|  | 			WOAS	Official audio source webpage | ||
|  | 			WORS	Official Internet radio station homepage | ||
|  | 			WPAY	Payment | ||
|  | 			WPB	Publishers official webpage | ||
|  | 			WPUB	Publishers official webpage | ||
|  | 			WXX	User defined URL link frame | ||
|  | 			WXXX	User defined URL link frame | ||
|  | 			TFEA	Featured Artist | ||
|  | 			TSTU	Recording Studio | ||
|  | 			rgad	Replay Gain Adjustment | ||
|  | 
 | ||
|  | 		*/ | ||
|  | 
 | ||
|  | 		return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long'); | ||
|  | 
 | ||
|  | 		// Last three:
 | ||
|  | 		// from Helium2 [www.helium2.com]
 | ||
|  | 		// from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	public static function FrameNameShortLookup($framename) { | ||
|  | 
 | ||
|  | 		$begin = __LINE__; | ||
|  | 
 | ||
|  | 		/** This is not a comment! | ||
|  | 
 | ||
|  | 			AENC	audio_encryption | ||
|  | 			APIC	attached_picture | ||
|  | 			ASPI	audio_seek_point_index | ||
|  | 			BUF	recommended_buffer_size | ||
|  | 			CNT	play_counter | ||
|  | 			COM	comment | ||
|  | 			COMM	comment | ||
|  | 			COMR	commercial_frame | ||
|  | 			CRA	audio_encryption | ||
|  | 			CRM	encrypted_meta_frame | ||
|  | 			ENCR	encryption_method_registration | ||
|  | 			EQU	equalisation | ||
|  | 			EQU2	equalisation | ||
|  | 			EQUA	equalisation | ||
|  | 			ETC	event_timing_codes | ||
|  | 			ETCO	event_timing_codes | ||
|  | 			GEO	general_encapsulated_object | ||
|  | 			GEOB	general_encapsulated_object | ||
|  | 			GRID	group_identification_registration | ||
|  | 			IPL	involved_people_list | ||
|  | 			IPLS	involved_people_list | ||
|  | 			LINK	linked_information | ||
|  | 			LNK	linked_information | ||
|  | 			MCDI	music_cd_identifier | ||
|  | 			MCI	music_cd_identifier | ||
|  | 			MLL	mpeg_location_lookup_table | ||
|  | 			MLLT	mpeg_location_lookup_table | ||
|  | 			OWNE	ownership_frame | ||
|  | 			PCNT	play_counter | ||
|  | 			PIC	attached_picture | ||
|  | 			POP	popularimeter | ||
|  | 			POPM	popularimeter | ||
|  | 			POSS	position_synchronisation_frame | ||
|  | 			PRIV	private_frame | ||
|  | 			RBUF	recommended_buffer_size | ||
|  | 			REV	reverb | ||
|  | 			RVA	relative_volume_adjustment | ||
|  | 			RVA2	relative_volume_adjustment | ||
|  | 			RVAD	relative_volume_adjustment | ||
|  | 			RVRB	reverb | ||
|  | 			SEEK	seek_frame | ||
|  | 			SIGN	signature_frame | ||
|  | 			SLT	synchronised_lyric | ||
|  | 			STC	synced_tempo_codes | ||
|  | 			SYLT	synchronised_lyric | ||
|  | 			SYTC	synchronised_tempo_codes | ||
|  | 			TAL	album | ||
|  | 			TALB	album | ||
|  | 			TBP	bpm | ||
|  | 			TBPM	bpm | ||
|  | 			TCM	composer | ||
|  | 			TCMP	part_of_a_compilation | ||
|  | 			TCO	genre | ||
|  | 			TCOM	composer | ||
|  | 			TCON	genre | ||
|  | 			TCOP	copyright_message | ||
|  | 			TCP	part_of_a_compilation | ||
|  | 			TCR	copyright_message | ||
|  | 			TDA	date | ||
|  | 			TDAT	date | ||
|  | 			TDEN	encoding_time | ||
|  | 			TDLY	playlist_delay | ||
|  | 			TDOR	original_release_time | ||
|  | 			TDRC	recording_time | ||
|  | 			TDRL	release_time | ||
|  | 			TDTG	tagging_time | ||
|  | 			TDY	playlist_delay | ||
|  | 			TEN	encoded_by | ||
|  | 			TENC	encoded_by | ||
|  | 			TEXT	lyricist | ||
|  | 			TFLT	file_type | ||
|  | 			TFT	file_type | ||
|  | 			TIM	time | ||
|  | 			TIME	time | ||
|  | 			TIPL	involved_people_list | ||
|  | 			TIT1	content_group_description | ||
|  | 			TIT2	title | ||
|  | 			TIT3	subtitle | ||
|  | 			TKE	initial_key | ||
|  | 			TKEY	initial_key | ||
|  | 			TLA	language | ||
|  | 			TLAN	language | ||
|  | 			TLE	length | ||
|  | 			TLEN	length | ||
|  | 			TMCL	musician_credits_list | ||
|  | 			TMED	media_type | ||
|  | 			TMOO	mood | ||
|  | 			TMT	media_type | ||
|  | 			TOA	original_artist | ||
|  | 			TOAL	original_album | ||
|  | 			TOF	original_filename | ||
|  | 			TOFN	original_filename | ||
|  | 			TOL	original_lyricist | ||
|  | 			TOLY	original_lyricist | ||
|  | 			TOPE	original_artist | ||
|  | 			TOR	original_year | ||
|  | 			TORY	original_year | ||
|  | 			TOT	original_album | ||
|  | 			TOWN	file_owner | ||
|  | 			TP1	artist | ||
|  | 			TP2	band | ||
|  | 			TP3	conductor | ||
|  | 			TP4	remixer | ||
|  | 			TPA	part_of_a_set | ||
|  | 			TPB	publisher | ||
|  | 			TPE1	artist | ||
|  | 			TPE2	band | ||
|  | 			TPE3	conductor | ||
|  | 			TPE4	remixer | ||
|  | 			TPOS	part_of_a_set | ||
|  | 			TPRO	produced_notice | ||
|  | 			TPUB	publisher | ||
|  | 			TRC	isrc | ||
|  | 			TRCK	track_number | ||
|  | 			TRD	recording_dates | ||
|  | 			TRDA	recording_dates | ||
|  | 			TRK	track_number | ||
|  | 			TRSN	internet_radio_station_name | ||
|  | 			TRSO	internet_radio_station_owner | ||
|  | 			TS2	album_artist_sort_order | ||
|  | 			TSA	album_sort_order | ||
|  | 			TSC	composer_sort_order | ||
|  | 			TSI	size | ||
|  | 			TSIZ	size | ||
|  | 			TSO2	album_artist_sort_order | ||
|  | 			TSOA	album_sort_order | ||
|  | 			TSOC	composer_sort_order | ||
|  | 			TSOP	performer_sort_order | ||
|  | 			TSOT	title_sort_order | ||
|  | 			TSP	performer_sort_order | ||
|  | 			TSRC	isrc | ||
|  | 			TSS	encoder_settings | ||
|  | 			TSSE	encoder_settings | ||
|  | 			TSST	set_subtitle | ||
|  | 			TST	title_sort_order | ||
|  | 			TT1	content_group_description | ||
|  | 			TT2	title | ||
|  | 			TT3	subtitle | ||
|  | 			TXT	lyricist | ||
|  | 			TXX	text | ||
|  | 			TXXX	text | ||
|  | 			TYE	year | ||
|  | 			TYER	year | ||
|  | 			UFI	unique_file_identifier | ||
|  | 			UFID	unique_file_identifier | ||
|  | 			ULT	unsychronised_lyric | ||
|  | 			USER	terms_of_use | ||
|  | 			USLT	unsynchronised_lyric | ||
|  | 			WAF	url_file | ||
|  | 			WAR	url_artist | ||
|  | 			WAS	url_source | ||
|  | 			WCM	commercial_information | ||
|  | 			WCOM	commercial_information | ||
|  | 			WCOP	copyright | ||
|  | 			WCP	copyright | ||
|  | 			WOAF	url_file | ||
|  | 			WOAR	url_artist | ||
|  | 			WOAS	url_source | ||
|  | 			WORS	url_station | ||
|  | 			WPAY	url_payment | ||
|  | 			WPB	url_publisher | ||
|  | 			WPUB	url_publisher | ||
|  | 			WXX	url_user | ||
|  | 			WXXX	url_user | ||
|  | 			TFEA	featured_artist | ||
|  | 			TSTU	recording_studio | ||
|  | 			rgad	replay_gain_adjustment | ||
|  | 
 | ||
|  | 		*/ | ||
|  | 
 | ||
|  | 		return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short'); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function TextEncodingTerminatorLookup($encoding) { | ||
|  | 		// http://www.id3.org/id3v2.4.0-structure.txt
 | ||
|  | 		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
 | ||
|  | 		static $TextEncodingTerminatorLookup = array( | ||
|  | 			0   => "\x00",     // $00  ISO-8859-1. Terminated with $00.
 | ||
|  | 			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.
 | ||
|  | 			2   => "\x00\x00", // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
 | ||
|  | 			3   => "\x00",     // $03  UTF-8 encoded Unicode. Terminated with $00.
 | ||
|  | 			255 => "\x00\x00" | ||
|  | 		); | ||
|  | 		return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00"); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function TextEncodingNameLookup($encoding) { | ||
|  | 		// http://www.id3.org/id3v2.4.0-structure.txt
 | ||
|  | 		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
 | ||
|  | 		static $TextEncodingNameLookup = array( | ||
|  | 			0   => 'ISO-8859-1', // $00  ISO-8859-1. Terminated with $00.
 | ||
|  | 			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.
 | ||
|  | 			2   => 'UTF-16BE',   // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
 | ||
|  | 			3   => 'UTF-8',      // $03  UTF-8 encoded Unicode. Terminated with $00.
 | ||
|  | 			255 => 'UTF-16BE' | ||
|  | 		); | ||
|  | 		return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1'); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function IsValidID3v2FrameName($framename, $id3v2majorversion) { | ||
|  | 		switch ($id3v2majorversion) { | ||
|  | 			case 2: | ||
|  | 				return preg_match('#[A-Z][A-Z0-9]{2}#', $framename); | ||
|  | 				break; | ||
|  | 
 | ||
|  | 			case 3: | ||
|  | 			case 4: | ||
|  | 				return preg_match('#[A-Z][A-Z0-9]{3}#', $framename); | ||
|  | 				break; | ||
|  | 		} | ||
|  | 		return false; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { | ||
|  | 		for ($i = 0; $i < strlen($numberstring); $i++) { | ||
|  | 			if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) { | ||
|  | 				if (($numberstring{$i} == '.') && $allowdecimal) { | ||
|  | 					// allowed
 | ||
|  | 				} elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) { | ||
|  | 					// allowed
 | ||
|  | 				} else { | ||
|  | 					return false; | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return true; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function IsValidDateStampString($datestamp) { | ||
|  | 		if (strlen($datestamp) != 8) { | ||
|  | 			return false; | ||
|  | 		} | ||
|  | 		if (!self::IsANumber($datestamp, false)) { | ||
|  | 			return false; | ||
|  | 		} | ||
|  | 		$year  = substr($datestamp, 0, 4); | ||
|  | 		$month = substr($datestamp, 4, 2); | ||
|  | 		$day   = substr($datestamp, 6, 2); | ||
|  | 		if (($year == 0) || ($month == 0) || ($day == 0)) { | ||
|  | 			return false; | ||
|  | 		} | ||
|  | 		if ($month > 12) { | ||
|  | 			return false; | ||
|  | 		} | ||
|  | 		if ($day > 31) { | ||
|  | 			return false; | ||
|  | 		} | ||
|  | 		if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) { | ||
|  | 			return false; | ||
|  | 		} | ||
|  | 		if (($day > 29) && ($month == 2)) { | ||
|  | 			return false; | ||
|  | 		} | ||
|  | 		return true; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function ID3v2HeaderLength($majorversion) { | ||
|  | 		return (($majorversion == 2) ? 6 : 10); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function ID3v22iTunesBrokenFrameName($frame_name) { | ||
|  | 		// iTunes (multiple versions) has been known to write ID3v2.3 style frames
 | ||
|  | 		// but use ID3v2.2 frame names, right-padded using either [space] or [null]
 | ||
|  | 		// to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
 | ||
|  | 		// This function will detect and translate the corrupt frame name into ID3v2.3 standard.
 | ||
|  | 		static $ID3v22_iTunes_BrokenFrames = array( | ||
|  | 			'BUF' => 'RBUF', // Recommended buffer size
 | ||
|  | 			'CNT' => 'PCNT', // Play counter
 | ||
|  | 			'COM' => 'COMM', // Comments
 | ||
|  | 			'CRA' => 'AENC', // Audio encryption
 | ||
|  | 			'EQU' => 'EQUA', // Equalisation
 | ||
|  | 			'ETC' => 'ETCO', // Event timing codes
 | ||
|  | 			'GEO' => 'GEOB', // General encapsulated object
 | ||
|  | 			'IPL' => 'IPLS', // Involved people list
 | ||
|  | 			'LNK' => 'LINK', // Linked information
 | ||
|  | 			'MCI' => 'MCDI', // Music CD identifier
 | ||
|  | 			'MLL' => 'MLLT', // MPEG location lookup table
 | ||
|  | 			'PIC' => 'APIC', // Attached picture
 | ||
|  | 			'POP' => 'POPM', // Popularimeter
 | ||
|  | 			'REV' => 'RVRB', // Reverb
 | ||
|  | 			'RVA' => 'RVAD', // Relative volume adjustment
 | ||
|  | 			'SLT' => 'SYLT', // Synchronised lyric/text
 | ||
|  | 			'STC' => 'SYTC', // Synchronised tempo codes
 | ||
|  | 			'TAL' => 'TALB', // Album/Movie/Show title
 | ||
|  | 			'TBP' => 'TBPM', // BPM (beats per minute)
 | ||
|  | 			'TCM' => 'TCOM', // Composer
 | ||
|  | 			'TCO' => 'TCON', // Content type
 | ||
|  | 			'TCP' => 'TCMP', // Part of a compilation
 | ||
|  | 			'TCR' => 'TCOP', // Copyright message
 | ||
|  | 			'TDA' => 'TDAT', // Date
 | ||
|  | 			'TDY' => 'TDLY', // Playlist delay
 | ||
|  | 			'TEN' => 'TENC', // Encoded by
 | ||
|  | 			'TFT' => 'TFLT', // File type
 | ||
|  | 			'TIM' => 'TIME', // Time
 | ||
|  | 			'TKE' => 'TKEY', // Initial key
 | ||
|  | 			'TLA' => 'TLAN', // Language(s)
 | ||
|  | 			'TLE' => 'TLEN', // Length
 | ||
|  | 			'TMT' => 'TMED', // Media type
 | ||
|  | 			'TOA' => 'TOPE', // Original artist(s)/performer(s)
 | ||
|  | 			'TOF' => 'TOFN', // Original filename
 | ||
|  | 			'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
 | ||
|  | 			'TOR' => 'TORY', // Original release year
 | ||
|  | 			'TOT' => 'TOAL', // Original album/movie/show title
 | ||
|  | 			'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
 | ||
|  | 			'TP2' => 'TPE2', // Band/orchestra/accompaniment
 | ||
|  | 			'TP3' => 'TPE3', // Conductor/performer refinement
 | ||
|  | 			'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
 | ||
|  | 			'TPA' => 'TPOS', // Part of a set
 | ||
|  | 			'TPB' => 'TPUB', // Publisher
 | ||
|  | 			'TRC' => 'TSRC', // ISRC (international standard recording code)
 | ||
|  | 			'TRD' => 'TRDA', // Recording dates
 | ||
|  | 			'TRK' => 'TRCK', // Track number/Position in set
 | ||
|  | 			'TS2' => 'TSO2', // Album-Artist sort order
 | ||
|  | 			'TSA' => 'TSOA', // Album sort order
 | ||
|  | 			'TSC' => 'TSOC', // Composer sort order
 | ||
|  | 			'TSI' => 'TSIZ', // Size
 | ||
|  | 			'TSP' => 'TSOP', // Performer sort order
 | ||
|  | 			'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
 | ||
|  | 			'TST' => 'TSOT', // Title sort order
 | ||
|  | 			'TT1' => 'TIT1', // Content group description
 | ||
|  | 			'TT2' => 'TIT2', // Title/songname/content description
 | ||
|  | 			'TT3' => 'TIT3', // Subtitle/Description refinement
 | ||
|  | 			'TXT' => 'TEXT', // Lyricist/Text writer
 | ||
|  | 			'TXX' => 'TXXX', // User defined text information frame
 | ||
|  | 			'TYE' => 'TYER', // Year
 | ||
|  | 			'UFI' => 'UFID', // Unique file identifier
 | ||
|  | 			'ULT' => 'USLT', // Unsynchronised lyric/text transcription
 | ||
|  | 			'WAF' => 'WOAF', // Official audio file webpage
 | ||
|  | 			'WAR' => 'WOAR', // Official artist/performer webpage
 | ||
|  | 			'WAS' => 'WOAS', // Official audio source webpage
 | ||
|  | 			'WCM' => 'WCOM', // Commercial information
 | ||
|  | 			'WCP' => 'WCOP', // Copyright/Legal information
 | ||
|  | 			'WPB' => 'WPUB', // Publishers official webpage
 | ||
|  | 			'WXX' => 'WXXX', // User defined URL link frame
 | ||
|  | 		); | ||
|  | 		if (strlen($frame_name) == 4) { | ||
|  | 			if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) { | ||
|  | 				if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) { | ||
|  | 					return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)]; | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return false; | ||
|  | 	} | ||
|  | 
 | ||
|  | } |