ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

module.audio.flac.php (19669B)


      1 <?php
      2 
      3 /////////////////////////////////////////////////////////////////
      4 /// getID3() by James Heinrich <info@getid3.org>               //
      5 //  available at https://github.com/JamesHeinrich/getID3       //
      6 //            or https://www.getid3.org                        //
      7 //            or http://getid3.sourceforge.net                 //
      8 //  see readme.txt for more details                            //
      9 /////////////////////////////////////////////////////////////////
     10 //                                                             //
     11 // module.audio.flac.php                                       //
     12 // module for analyzing FLAC and OggFLAC audio files           //
     13 // dependencies: module.audio.ogg.php                          //
     14 //                                                            ///
     15 /////////////////////////////////////////////////////////////////
     16 
     17 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
     18 	exit;
     19 }
     20 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);
     21 
     22 /**
     23 * @tutorial http://flac.sourceforge.net/format.html
     24 */
     25 class getid3_flac extends getid3_handler
     26 {
     27 	const syncword = 'fLaC';
     28 
     29 	/**
     30 	 * @return bool
     31 	 */
     32 	public function Analyze() {
     33 		$info = &$this->getid3->info;
     34 
     35 		$this->fseek($info['avdataoffset']);
     36 		$StreamMarker = $this->fread(4);
     37 		if ($StreamMarker != self::syncword) {
     38 			return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"');
     39 		}
     40 		$info['fileformat']            = 'flac';
     41 		$info['audio']['dataformat']   = 'flac';
     42 		$info['audio']['bitrate_mode'] = 'vbr';
     43 		$info['audio']['lossless']     = true;
     44 
     45 		// parse flac container
     46 		return $this->parseMETAdata();
     47 	}
     48 
     49 	/**
     50 	 * @return bool
     51 	 */
     52 	public function parseMETAdata() {
     53 		$info = &$this->getid3->info;
     54 		do {
     55 			$BlockOffset   = $this->ftell();
     56 			$BlockHeader   = $this->fread(4);
     57 			$LBFBT         = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1));  // LBFBT = LastBlockFlag + BlockType
     58 			$LastBlockFlag = (bool) ($LBFBT & 0x80);
     59 			$BlockType     =        ($LBFBT & 0x7F);
     60 			$BlockLength   = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3));
     61 			$BlockTypeText = self::metaBlockTypeLookup($BlockType);
     62 
     63 			if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) {
     64 				$this->warning('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file');
     65 				break;
     66 			}
     67 			if ($BlockLength < 1) {
     68 				if ($BlockTypeText != 'reserved') {
     69 					// probably supposed to be zero-length
     70 					$this->warning('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockTypeText.') at offset '.$BlockOffset.' is zero bytes');
     71 					continue;
     72 				}
     73 				$this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid');
     74 				break;
     75 			}
     76 
     77 			$info['flac'][$BlockTypeText]['raw'] = array();
     78 			$BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw'];
     79 
     80 			$BlockTypeText_raw['offset']          = $BlockOffset;
     81 			$BlockTypeText_raw['last_meta_block'] = $LastBlockFlag;
     82 			$BlockTypeText_raw['block_type']      = $BlockType;
     83 			$BlockTypeText_raw['block_type_text'] = $BlockTypeText;
     84 			$BlockTypeText_raw['block_length']    = $BlockLength;
     85 			if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically
     86 				$BlockTypeText_raw['block_data']  = $this->fread($BlockLength);
     87 			}
     88 
     89 			switch ($BlockTypeText) {
     90 				case 'STREAMINFO':     // 0x00
     91 					if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) {
     92 						return false;
     93 					}
     94 					break;
     95 
     96 				case 'PADDING':        // 0x01
     97 					unset($info['flac']['PADDING']); // ignore
     98 					break;
     99 
    100 				case 'APPLICATION':    // 0x02
    101 					if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) {
    102 						return false;
    103 					}
    104 					break;
    105 
    106 				case 'SEEKTABLE':      // 0x03
    107 					if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) {
    108 						return false;
    109 					}
    110 					break;
    111 
    112 				case 'VORBIS_COMMENT': // 0x04
    113 					if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) {
    114 						return false;
    115 					}
    116 					break;
    117 
    118 				case 'CUESHEET':       // 0x05
    119 					if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) {
    120 						return false;
    121 					}
    122 					break;
    123 
    124 				case 'PICTURE':        // 0x06
    125 					if (!$this->parsePICTURE()) {
    126 						return false;
    127 					}
    128 					break;
    129 
    130 				default:
    131 					$this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset);
    132 			}
    133 
    134 			unset($info['flac'][$BlockTypeText]['raw']);
    135 			$info['avdataoffset'] = $this->ftell();
    136 		}
    137 		while ($LastBlockFlag === false);
    138 
    139 		// handle tags
    140 		if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) {
    141 			$info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments'];
    142 		}
    143 		if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) {
    144 			$info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']);
    145 		}
    146 
    147 		// copy attachments to 'comments' array if nesesary
    148 		if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) {
    149 			foreach ($info['flac']['PICTURE'] as $entry) {
    150 				if (!empty($entry['data'])) {
    151 					if (!isset($info['flac']['comments']['picture'])) {
    152 						$info['flac']['comments']['picture'] = array();
    153 					}
    154 					$comments_picture_data = array();
    155 					foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
    156 						if (isset($entry[$picture_key])) {
    157 							$comments_picture_data[$picture_key] = $entry[$picture_key];
    158 						}
    159 					}
    160 					$info['flac']['comments']['picture'][] = $comments_picture_data;
    161 					unset($comments_picture_data);
    162 				}
    163 			}
    164 		}
    165 
    166 		if (isset($info['flac']['STREAMINFO'])) {
    167 			if (!$this->isDependencyFor('matroska')) {
    168 				$info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
    169 			}
    170 			$info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
    171 			if ($info['flac']['uncompressed_audio_bytes'] == 0) {
    172 				return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero');
    173 			}
    174 			if (!empty($info['flac']['compressed_audio_bytes'])) {
    175 				$info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
    176 			}
    177 		}
    178 
    179 		// set md5_data_source - built into flac 0.5+
    180 		if (isset($info['flac']['STREAMINFO']['audio_signature'])) {
    181 
    182 			if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
    183 				$this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
    184 			}
    185 			else {
    186 				$info['md5_data_source'] = '';
    187 				$md5 = $info['flac']['STREAMINFO']['audio_signature'];
    188 				for ($i = 0; $i < strlen($md5); $i++) {
    189 					$info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
    190 				}
    191 				if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
    192 					unset($info['md5_data_source']);
    193 				}
    194 			}
    195 		}
    196 
    197 		if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) {
    198 			$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
    199 			if ($info['audio']['bits_per_sample'] == 8) {
    200 				// special case
    201 				// must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
    202 				// MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
    203 				$this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file');
    204 			}
    205 		}
    206 
    207 		return true;
    208 	}
    209 
    210 
    211 	/**
    212 	 * @param string $BlockData
    213 	 *
    214 	 * @return array
    215 	 */
    216 	public static function parseSTREAMINFOdata($BlockData) {
    217 		$streaminfo = array();
    218 		$streaminfo['min_block_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2));
    219 		$streaminfo['max_block_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2));
    220 		$streaminfo['min_frame_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3));
    221 		$streaminfo['max_frame_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3));
    222 
    223 		$SRCSBSS                       = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8));
    224 		$streaminfo['sample_rate']     = getid3_lib::Bin2Dec(substr($SRCSBSS,  0, 20));
    225 		$streaminfo['channels']        = getid3_lib::Bin2Dec(substr($SRCSBSS, 20,  3)) + 1;
    226 		$streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23,  5)) + 1;
    227 		$streaminfo['samples_stream']  = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36));
    228 
    229 		$streaminfo['audio_signature'] =                           substr($BlockData, 18, 16);
    230 
    231 		return $streaminfo;
    232 	}
    233 
    234 	/**
    235 	 * @param string $BlockData
    236 	 *
    237 	 * @return bool
    238 	 */
    239 	private function parseSTREAMINFO($BlockData) {
    240 		$info = &$this->getid3->info;
    241 
    242 		$info['flac']['STREAMINFO'] = self::parseSTREAMINFOdata($BlockData);
    243 
    244 		if (!empty($info['flac']['STREAMINFO']['sample_rate'])) {
    245 
    246 			$info['audio']['bitrate_mode']    = 'vbr';
    247 			$info['audio']['sample_rate']     = $info['flac']['STREAMINFO']['sample_rate'];
    248 			$info['audio']['channels']        = $info['flac']['STREAMINFO']['channels'];
    249 			$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
    250 			$info['playtime_seconds']         = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate'];
    251 			if ($info['playtime_seconds'] > 0) {
    252 				if (!$this->isDependencyFor('matroska')) {
    253 					$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
    254 				}
    255 				else {
    256 					$this->warning('Cannot determine audio bitrate because total stream size is unknown');
    257 				}
    258 			}
    259 
    260 		} else {
    261 			return $this->error('Corrupt METAdata block: STREAMINFO');
    262 		}
    263 
    264 		return true;
    265 	}
    266 
    267 	/**
    268 	 * @param string $BlockData
    269 	 *
    270 	 * @return bool
    271 	 */
    272 	private function parseAPPLICATION($BlockData) {
    273 		$info = &$this->getid3->info;
    274 
    275 		$ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4));
    276 		$info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID);
    277 		$info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4);
    278 
    279 		return true;
    280 	}
    281 
    282 	/**
    283 	 * @param string $BlockData
    284 	 *
    285 	 * @return bool
    286 	 */
    287 	private function parseSEEKTABLE($BlockData) {
    288 		$info = &$this->getid3->info;
    289 
    290 		$offset = 0;
    291 		$BlockLength = strlen($BlockData);
    292 		$placeholderpattern = str_repeat("\xFF", 8);
    293 		while ($offset < $BlockLength) {
    294 			$SampleNumberString = substr($BlockData, $offset, 8);
    295 			$offset += 8;
    296 			if ($SampleNumberString == $placeholderpattern) {
    297 
    298 				// placeholder point
    299 				getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
    300 				$offset += 10;
    301 
    302 			} else {
    303 
    304 				$SampleNumber                                        = getid3_lib::BigEndian2Int($SampleNumberString);
    305 				$info['flac']['SEEKTABLE'][$SampleNumber]['offset']  = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
    306 				$offset += 8;
    307 				$info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2));
    308 				$offset += 2;
    309 
    310 			}
    311 		}
    312 
    313 		return true;
    314 	}
    315 
    316 	/**
    317 	 * @param string $BlockData
    318 	 *
    319 	 * @return bool
    320 	 */
    321 	private function parseVORBIS_COMMENT($BlockData) {
    322 		$info = &$this->getid3->info;
    323 
    324 		$getid3_ogg = new getid3_ogg($this->getid3);
    325 		if ($this->isDependencyFor('matroska')) {
    326 			$getid3_ogg->setStringMode($this->data_string);
    327 		}
    328 		$getid3_ogg->ParseVorbisComments();
    329 		if (isset($info['ogg'])) {
    330 			unset($info['ogg']['comments_raw']);
    331 			$info['flac']['VORBIS_COMMENT'] = $info['ogg'];
    332 			unset($info['ogg']);
    333 		}
    334 
    335 		unset($getid3_ogg);
    336 
    337 		return true;
    338 	}
    339 
    340 	/**
    341 	 * @param string $BlockData
    342 	 *
    343 	 * @return bool
    344 	 */
    345 	private function parseCUESHEET($BlockData) {
    346 		$info = &$this->getid3->info;
    347 		$offset = 0;
    348 		$info['flac']['CUESHEET']['media_catalog_number'] =                              trim(substr($BlockData, $offset, 128), "\0");
    349 		$offset += 128;
    350 		$info['flac']['CUESHEET']['lead_in_samples']      =         getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
    351 		$offset += 8;
    352 		$info['flac']['CUESHEET']['flags']['is_cd']       = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80);
    353 		$offset += 1;
    354 
    355 		$offset += 258; // reserved
    356 
    357 		$info['flac']['CUESHEET']['number_tracks']        =         getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
    358 		$offset += 1;
    359 
    360 		for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
    361 			$TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
    362 			$offset += 8;
    363 			$TrackNumber       = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
    364 			$offset += 1;
    365 
    366 			$info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset']         = $TrackSampleOffset;
    367 
    368 			$info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc']                  =                           substr($BlockData, $offset, 12);
    369 			$offset += 12;
    370 
    371 			$TrackFlagsRaw                                                             = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
    372 			$offset += 1;
    373 			$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio']     = (bool) ($TrackFlagsRaw & 0x80);
    374 			$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);
    375 
    376 			$offset += 13; // reserved
    377 
    378 			$info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']          = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
    379 			$offset += 1;
    380 
    381 			for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
    382 				$IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
    383 				$offset += 8;
    384 				$IndexNumber       = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
    385 				$offset += 1;
    386 
    387 				$offset += 3; // reserved
    388 
    389 				$info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
    390 			}
    391 		}
    392 
    393 		return true;
    394 	}
    395 
    396 	/**
    397 	 * Parse METADATA_BLOCK_PICTURE flac structure and extract attachment
    398 	 * External usage: audio.ogg
    399 	 *
    400 	 * @return bool
    401 	 */
    402 	public function parsePICTURE() {
    403 		$info = &$this->getid3->info;
    404 
    405 		$picture['typeid']         = getid3_lib::BigEndian2Int($this->fread(4));
    406 		$picture['picturetype']    = self::pictureTypeLookup($picture['typeid']);
    407 		$picture['image_mime']     = $this->fread(getid3_lib::BigEndian2Int($this->fread(4)));
    408 		$descr_length              = getid3_lib::BigEndian2Int($this->fread(4));
    409 		if ($descr_length) {
    410 			$picture['description'] = $this->fread($descr_length);
    411 		}
    412 		$picture['image_width']    = getid3_lib::BigEndian2Int($this->fread(4));
    413 		$picture['image_height']   = getid3_lib::BigEndian2Int($this->fread(4));
    414 		$picture['color_depth']    = getid3_lib::BigEndian2Int($this->fread(4));
    415 		$picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4));
    416 		$picture['datalength']     = getid3_lib::BigEndian2Int($this->fread(4));
    417 
    418 		if ($picture['image_mime'] == '-->') {
    419 			$picture['data'] = $this->fread($picture['datalength']);
    420 		} else {
    421 			$picture['data'] = $this->saveAttachment(
    422 				str_replace('/', '_', $picture['picturetype']).'_'.$this->ftell(),
    423 				$this->ftell(),
    424 				$picture['datalength'],
    425 				$picture['image_mime']);
    426 		}
    427 
    428 		$info['flac']['PICTURE'][] = $picture;
    429 
    430 		return true;
    431 	}
    432 
    433 	/**
    434 	 * @param int $blocktype
    435 	 *
    436 	 * @return string
    437 	 */
    438 	public static function metaBlockTypeLookup($blocktype) {
    439 		static $lookup = array(
    440 			0 => 'STREAMINFO',
    441 			1 => 'PADDING',
    442 			2 => 'APPLICATION',
    443 			3 => 'SEEKTABLE',
    444 			4 => 'VORBIS_COMMENT',
    445 			5 => 'CUESHEET',
    446 			6 => 'PICTURE',
    447 		);
    448 		return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved');
    449 	}
    450 
    451 	/**
    452 	 * @param int $applicationid
    453 	 *
    454 	 * @return string
    455 	 */
    456 	public static function applicationIDLookup($applicationid) {
    457 		// http://flac.sourceforge.net/id.html
    458 		static $lookup = array(
    459 			0x41544348 => 'FlacFile',                                                                           // "ATCH"
    460 			0x42534F4C => 'beSolo',                                                                             // "BSOL"
    461 			0x42554753 => 'Bugs Player',                                                                        // "BUGS"
    462 			0x43756573 => 'GoldWave cue points (specification)',                                                // "Cues"
    463 			0x46696361 => 'CUE Splitter',                                                                       // "Fica"
    464 			0x46746F6C => 'flac-tools',                                                                         // "Ftol"
    465 			0x4D4F5442 => 'MOTB MetaCzar',                                                                      // "MOTB"
    466 			0x4D505345 => 'MP3 Stream Editor',                                                                  // "MPSE"
    467 			0x4D754D4C => 'MusicML: Music Metadata Language',                                                   // "MuML"
    468 			0x52494646 => 'Sound Devices RIFF chunk storage',                                                   // "RIFF"
    469 			0x5346464C => 'Sound Font FLAC',                                                                    // "SFFL"
    470 			0x534F4E59 => 'Sony Creative Software',                                                             // "SONY"
    471 			0x5351455A => 'flacsqueeze',                                                                        // "SQEZ"
    472 			0x54745776 => 'TwistedWave',                                                                        // "TtWv"
    473 			0x55495453 => 'UITS Embedding tools',                                                               // "UITS"
    474 			0x61696666 => 'FLAC AIFF chunk storage',                                                            // "aiff"
    475 			0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks',  // "imag"
    476 			0x7065656D => 'Parseable Embedded Extensible Metadata (specification)',                             // "peem"
    477 			0x71667374 => 'QFLAC Studio',                                                                       // "qfst"
    478 			0x72696666 => 'FLAC RIFF chunk storage',                                                            // "riff"
    479 			0x74756E65 => 'TagTuner',                                                                           // "tune"
    480 			0x78626174 => 'XBAT',                                                                               // "xbat"
    481 			0x786D6364 => 'xmcd',                                                                               // "xmcd"
    482 		);
    483 		return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved');
    484 	}
    485 
    486 	/**
    487 	 * @param int $type_id
    488 	 *
    489 	 * @return string
    490 	 */
    491 	public static function pictureTypeLookup($type_id) {
    492 		static $lookup = array (
    493 			 0 => 'Other',
    494 			 1 => '32x32 pixels \'file icon\' (PNG only)',
    495 			 2 => 'Other file icon',
    496 			 3 => 'Cover (front)',
    497 			 4 => 'Cover (back)',
    498 			 5 => 'Leaflet page',
    499 			 6 => 'Media (e.g. label side of CD)',
    500 			 7 => 'Lead artist/lead performer/soloist',
    501 			 8 => 'Artist/performer',
    502 			 9 => 'Conductor',
    503 			10 => 'Band/Orchestra',
    504 			11 => 'Composer',
    505 			12 => 'Lyricist/text writer',
    506 			13 => 'Recording Location',
    507 			14 => 'During recording',
    508 			15 => 'During performance',
    509 			16 => 'Movie/video screen capture',
    510 			17 => 'A bright coloured fish',
    511 			18 => 'Illustration',
    512 			19 => 'Band/artist logotype',
    513 			20 => 'Publisher/Studio logotype',
    514 		);
    515 		return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
    516 	}
    517 
    518 }