wp/wp-includes/ID3/module.audio-video.quicktime.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 16 a86126ab1dd4
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
    33 
    33 
    34 		$this->fseek($info['avdataoffset']);
    34 		$this->fseek($info['avdataoffset']);
    35 
    35 
    36 		$offset      = 0;
    36 		$offset      = 0;
    37 		$atomcounter = 0;
    37 		$atomcounter = 0;
    38 
    38 		$atom_data_read_buffer_size = max($this->getid3->option_fread_buffer_size * 1024, ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 4) : 1024)); // set read buffer to 25% of PHP memory limit (if one is specified), otherwise use option_fread_buffer_size [default: 32MB]
    39 		while ($offset < $info['avdataend']) {
    39 		while ($offset < $info['avdataend']) {
    40 			if (!getid3_lib::intValueSupported($offset)) {
    40 			if (!getid3_lib::intValueSupported($offset)) {
    41 				$info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions';
    41 				$this->error('Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions');
    42 				break;
    42 				break;
    43 			}
    43 			}
    44 			$this->fseek($offset);
    44 			$this->fseek($offset);
    45 			$AtomHeader = $this->fread(8);
    45 			$AtomHeader = $this->fread(8);
    46 
    46 
    55 			$info['quicktime'][$atomname]['name']   = $atomname;
    55 			$info['quicktime'][$atomname]['name']   = $atomname;
    56 			$info['quicktime'][$atomname]['size']   = $atomsize;
    56 			$info['quicktime'][$atomname]['size']   = $atomsize;
    57 			$info['quicktime'][$atomname]['offset'] = $offset;
    57 			$info['quicktime'][$atomname]['offset'] = $offset;
    58 
    58 
    59 			if (($offset + $atomsize) > $info['avdataend']) {
    59 			if (($offset + $atomsize) > $info['avdataend']) {
    60 				$info['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)';
    60 				$this->error('Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)');
    61 				return false;
    61 				return false;
    62 			}
    62 			}
    63 
    63 
    64 			if ($atomsize == 0) {
    64 			if ($atomsize == 0) {
    65 				// Furthermore, for historical reasons the list of atoms is optionally
    65 				// Furthermore, for historical reasons the list of atoms is optionally
    66 				// terminated by a 32-bit integer set to 0. If you are writing a program
    66 				// terminated by a 32-bit integer set to 0. If you are writing a program
    67 				// to read user data atoms, you should allow for the terminating 0.
    67 				// to read user data atoms, you should allow for the terminating 0.
    68 				break;
    68 				break;
    69 			}
    69 			}
    70 			$atomHierarchy = array();
    70 			$atomHierarchy = array();
    71 			$info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize, round($this->getid3->memory_limit / 2))), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms);
    71 			$info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize, $atom_data_read_buffer_size)), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms);
    72 
    72 
    73 			$offset += $atomsize;
    73 			$offset += $atomsize;
    74 			$atomcounter++;
    74 			$atomcounter++;
    75 		}
    75 		}
    76 
    76 
    77 		if (!empty($info['avdataend_tmp'])) {
    77 		if (!empty($info['avdataend_tmp'])) {
    78 			// this value is assigned to a temp value and then erased because
    78 			// this value is assigned to a temp value and then erased because
    79 			// otherwise any atoms beyond the 'mdat' atom would not get parsed
    79 			// otherwise any atoms beyond the 'mdat' atom would not get parsed
    80 			$info['avdataend'] = $info['avdataend_tmp'];
    80 			$info['avdataend'] = $info['avdataend_tmp'];
    81 			unset($info['avdataend_tmp']);
    81 			unset($info['avdataend_tmp']);
       
    82 		}
       
    83 
       
    84 		if (!empty($info['quicktime']['comments']['chapters']) && is_array($info['quicktime']['comments']['chapters']) && (count($info['quicktime']['comments']['chapters']) > 0)) {
       
    85 			$durations = $this->quicktime_time_to_sample_table($info);
       
    86 			for ($i = 0; $i < count($info['quicktime']['comments']['chapters']); $i++) {
       
    87 				$bookmark = array();
       
    88 				$bookmark['title'] = $info['quicktime']['comments']['chapters'][$i];
       
    89 				if (isset($durations[$i])) {
       
    90 					$bookmark['duration_sample'] = $durations[$i]['sample_duration'];
       
    91 					if ($i > 0) {
       
    92 						$bookmark['start_sample'] = $info['quicktime']['bookmarks'][($i - 1)]['start_sample'] + $info['quicktime']['bookmarks'][($i - 1)]['duration_sample'];
       
    93 					} else {
       
    94 						$bookmark['start_sample'] = 0;
       
    95 					}
       
    96 					if ($time_scale = $this->quicktime_bookmark_time_scale($info)) {
       
    97 						$bookmark['duration_seconds'] = $bookmark['duration_sample'] / $time_scale;
       
    98 						$bookmark['start_seconds']    = $bookmark['start_sample']    / $time_scale;
       
    99 					}
       
   100 				}
       
   101 				$info['quicktime']['bookmarks'][] = $bookmark;
       
   102 			}
       
   103 		}
       
   104 
       
   105 		if (isset($info['quicktime']['temp_meta_key_names'])) {
       
   106 			unset($info['quicktime']['temp_meta_key_names']);
       
   107 		}
       
   108 
       
   109 		if (!empty($info['quicktime']['comments']['location.ISO6709'])) {
       
   110 			// https://en.wikipedia.org/wiki/ISO_6709
       
   111 			foreach ($info['quicktime']['comments']['location.ISO6709'] as $ISO6709string) {
       
   112 				$latitude  = false;
       
   113 				$longitude = false;
       
   114 				$altitude  = false;
       
   115 				if (preg_match('#^([\\+\\-])([0-9]{2}|[0-9]{4}|[0-9]{6})(\\.[0-9]+)?([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?(([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?)?/$#', $ISO6709string, $matches)) {
       
   116 					@list($dummy, $lat_sign, $lat_deg, $lat_deg_dec, $lon_sign, $lon_deg, $lon_deg_dec, $dummy, $alt_sign, $alt_deg, $alt_deg_dec) = $matches;
       
   117 
       
   118 					if (strlen($lat_deg) == 2) {        // [+-]DD.D
       
   119 						$latitude = floatval(ltrim($lat_deg, '0').$lat_deg_dec);
       
   120 					} elseif (strlen($lat_deg) == 4) {  // [+-]DDMM.M
       
   121 						$latitude = floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0').$lat_deg_dec / 60);
       
   122 					} elseif (strlen($lat_deg) == 6) {  // [+-]DDMMSS.S
       
   123 						$latitude = floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec / 3600);
       
   124 					}
       
   125 
       
   126 					if (strlen($lon_deg) == 3) {        // [+-]DDD.D
       
   127 						$longitude = floatval(ltrim($lon_deg, '0').$lon_deg_dec);
       
   128 					} elseif (strlen($lon_deg) == 5) {  // [+-]DDDMM.M
       
   129 						$longitude = floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0').$lon_deg_dec / 60);
       
   130 					} elseif (strlen($lon_deg) == 7) {  // [+-]DDDMMSS.S
       
   131 						$longitude = floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec / 3600);
       
   132 					}
       
   133 
       
   134 					if (strlen($alt_deg) == 3) {        // [+-]DDD.D
       
   135 						$altitude = floatval(ltrim($alt_deg, '0').$alt_deg_dec);
       
   136 					} elseif (strlen($alt_deg) == 5) {  // [+-]DDDMM.M
       
   137 						$altitude = floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0').$alt_deg_dec / 60);
       
   138 					} elseif (strlen($alt_deg) == 7) {  // [+-]DDDMMSS.S
       
   139 						$altitude = floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec / 3600);
       
   140 					}
       
   141 
       
   142 					if ($latitude !== false) {
       
   143 						$info['quicktime']['comments']['gps_latitude'][]  = (($lat_sign == '-') ? -1 : 1) * floatval($latitude);
       
   144 					}
       
   145 					if ($longitude !== false) {
       
   146 						$info['quicktime']['comments']['gps_longitude'][] = (($lon_sign == '-') ? -1 : 1) * floatval($longitude);
       
   147 					}
       
   148 					if ($altitude !== false) {
       
   149 						$info['quicktime']['comments']['gps_altitude'][]  = (($alt_sign == '-') ? -1 : 1) * floatval($altitude);
       
   150 					}
       
   151 				}
       
   152 				if ($latitude === false) {
       
   153 					$this->warning('location.ISO6709 string not parsed correctly: "'.$ISO6709string.'", please submit as a bug');
       
   154 				}
       
   155 				break;
       
   156 			}
    82 		}
   157 		}
    83 
   158 
    84 		if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) {
   159 		if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) {
    85 			$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
   160 			$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
    86 		}
   161 		}
    96 					$info['video']['frame_rate'] = $samples_per_second;
   171 					$info['video']['frame_rate'] = $samples_per_second;
    97 					break;
   172 					break;
    98 				}
   173 				}
    99 			}
   174 			}
   100 		}
   175 		}
   101 		if (($info['audio']['dataformat'] == 'mp4') && empty($info['video']['resolution_x'])) {
   176 		if ($info['audio']['dataformat'] == 'mp4') {
   102 			$info['fileformat'] = 'mp4';
   177 			$info['fileformat'] = 'mp4';
   103 			$info['mime_type']  = 'audio/mp4';
   178 			if (empty($info['video']['resolution_x'])) {
   104 			unset($info['video']['dataformat']);
   179 				$info['mime_type']  = 'audio/mp4';
       
   180 				unset($info['video']['dataformat']);
       
   181 			} else {
       
   182 				$info['mime_type']  = 'video/mp4';
       
   183 			}
   105 		}
   184 		}
   106 
   185 
   107 		if (!$this->ReturnAtomData) {
   186 		if (!$this->ReturnAtomData) {
   108 			unset($info['quicktime']['moov']);
   187 			unset($info['quicktime']['moov']);
   109 		}
   188 		}
   118 		return true;
   197 		return true;
   119 	}
   198 	}
   120 
   199 
   121 	public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
   200 	public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
   122 		// http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm
   201 		// http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm
       
   202 		// https://code.google.com/p/mp4v2/wiki/iTunesMetadata
   123 
   203 
   124 		$info = &$this->getid3->info;
   204 		$info = &$this->getid3->info;
   125 
   205 
   126 		$atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see http://www.getid3.org/phpBB3/viewtopic.php?t=1717
   206 		$atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see http://www.getid3.org/phpBB3/viewtopic.php?t=1717
   127 		array_push($atomHierarchy, $atomname);
   207 		array_push($atomHierarchy, $atomname);
   220 					$info['quicktime']['video']['frame_count'] = $framecount;
   300 					$info['quicktime']['video']['frame_count'] = $framecount;
   221 				}
   301 				}
   222 				break;
   302 				break;
   223 
   303 
   224 
   304 
       
   305 			case "\xA9".'alb': // ALBum
       
   306 			case "\xA9".'ART': //
       
   307 			case "\xA9".'art': // ARTist
       
   308 			case "\xA9".'aut': //
       
   309 			case "\xA9".'cmt': // CoMmenT
       
   310 			case "\xA9".'com': // COMposer
       
   311 			case "\xA9".'cpy': //
       
   312 			case "\xA9".'day': // content created year
       
   313 			case "\xA9".'dir': //
       
   314 			case "\xA9".'ed1': //
       
   315 			case "\xA9".'ed2': //
       
   316 			case "\xA9".'ed3': //
       
   317 			case "\xA9".'ed4': //
       
   318 			case "\xA9".'ed5': //
       
   319 			case "\xA9".'ed6': //
       
   320 			case "\xA9".'ed7': //
       
   321 			case "\xA9".'ed8': //
       
   322 			case "\xA9".'ed9': //
       
   323 			case "\xA9".'enc': //
       
   324 			case "\xA9".'fmt': //
       
   325 			case "\xA9".'gen': // GENre
       
   326 			case "\xA9".'grp': // GRouPing
       
   327 			case "\xA9".'hst': //
       
   328 			case "\xA9".'inf': //
       
   329 			case "\xA9".'lyr': // LYRics
       
   330 			case "\xA9".'mak': //
       
   331 			case "\xA9".'mod': //
       
   332 			case "\xA9".'nam': // full NAMe
       
   333 			case "\xA9".'ope': //
       
   334 			case "\xA9".'PRD': //
       
   335 			case "\xA9".'prf': //
       
   336 			case "\xA9".'req': //
       
   337 			case "\xA9".'src': //
       
   338 			case "\xA9".'swr': //
       
   339 			case "\xA9".'too': // encoder
       
   340 			case "\xA9".'trk': // TRacK
       
   341 			case "\xA9".'url': //
       
   342 			case "\xA9".'wrn': //
       
   343 			case "\xA9".'wrt': // WRiTer
       
   344 			case '----': // itunes specific
   225 			case 'aART': // Album ARTist
   345 			case 'aART': // Album ARTist
       
   346 			case 'akID': // iTunes store account type
       
   347 			case 'apID': // Purchase Account
       
   348 			case 'atID': //
   226 			case 'catg': // CaTeGory
   349 			case 'catg': // CaTeGory
       
   350 			case 'cmID': //
       
   351 			case 'cnID': //
   227 			case 'covr': // COVeR artwork
   352 			case 'covr': // COVeR artwork
   228 			case 'cpil': // ComPILation
   353 			case 'cpil': // ComPILation
   229 			case 'cprt': // CoPyRighT
   354 			case 'cprt': // CoPyRighT
   230 			case 'desc': // DESCription
   355 			case 'desc': // DESCription
   231 			case 'disk': // DISK number
   356 			case 'disk': // DISK number
   232 			case 'egid': // Episode Global ID
   357 			case 'egid': // Episode Global ID
       
   358 			case 'geID': //
   233 			case 'gnre': // GeNRE
   359 			case 'gnre': // GeNRE
       
   360 			case 'hdvd': // HD ViDeo
   234 			case 'keyw': // KEYWord
   361 			case 'keyw': // KEYWord
   235 			case 'ldes':
   362 			case 'ldes': // Long DEScription
   236 			case 'pcst': // PodCaST
   363 			case 'pcst': // PodCaST
   237 			case 'pgap': // GAPless Playback
   364 			case 'pgap': // GAPless Playback
       
   365 			case 'plID': //
   238 			case 'purd': // PURchase Date
   366 			case 'purd': // PURchase Date
   239 			case 'purl': // Podcast URL
   367 			case 'purl': // Podcast URL
   240 			case 'rati':
   368 			case 'rati': //
   241 			case 'rndu':
   369 			case 'rndu': //
   242 			case 'rpdu':
   370 			case 'rpdu': //
   243 			case 'rtng': // RaTiNG
   371 			case 'rtng': // RaTiNG
   244 			case 'stik':
   372 			case 'sfID': // iTunes store country
       
   373 			case 'soaa': // SOrt Album Artist
       
   374 			case 'soal': // SOrt ALbum
       
   375 			case 'soar': // SOrt ARtist
       
   376 			case 'soco': // SOrt COmposer
       
   377 			case 'sonm': // SOrt NaMe
       
   378 			case 'sosn': // SOrt Show Name
       
   379 			case 'stik': //
   245 			case 'tmpo': // TeMPO (BPM)
   380 			case 'tmpo': // TeMPO (BPM)
   246 			case 'trkn': // TRacK Number
   381 			case 'trkn': // TRacK Number
       
   382 			case 'tven': // tvEpisodeID
   247 			case 'tves': // TV EpiSode
   383 			case 'tves': // TV EpiSode
   248 			case 'tvnn': // TV Network Name
   384 			case 'tvnn': // TV Network Name
   249 			case 'tvsh': // TV SHow Name
   385 			case 'tvsh': // TV SHow Name
   250 			case 'tvsn': // TV SeasoN
   386 			case 'tvsn': // TV SeasoN
   251 			case 'akID': // iTunes store account type
       
   252 			case 'apID':
       
   253 			case 'atID':
       
   254 			case 'cmID':
       
   255 			case 'cnID':
       
   256 			case 'geID':
       
   257 			case 'plID':
       
   258 			case 'sfID': // iTunes store country
       
   259 			case "\xA9".'alb': // ALBum
       
   260 			case "\xA9".'art': // ARTist
       
   261 			case "\xA9".'ART':
       
   262 			case "\xA9".'aut':
       
   263 			case "\xA9".'cmt': // CoMmenT
       
   264 			case "\xA9".'com': // COMposer
       
   265 			case "\xA9".'cpy':
       
   266 			case "\xA9".'day': // content created year
       
   267 			case "\xA9".'dir':
       
   268 			case "\xA9".'ed1':
       
   269 			case "\xA9".'ed2':
       
   270 			case "\xA9".'ed3':
       
   271 			case "\xA9".'ed4':
       
   272 			case "\xA9".'ed5':
       
   273 			case "\xA9".'ed6':
       
   274 			case "\xA9".'ed7':
       
   275 			case "\xA9".'ed8':
       
   276 			case "\xA9".'ed9':
       
   277 			case "\xA9".'enc':
       
   278 			case "\xA9".'fmt':
       
   279 			case "\xA9".'gen': // GENre
       
   280 			case "\xA9".'grp': // GRouPing
       
   281 			case "\xA9".'hst':
       
   282 			case "\xA9".'inf':
       
   283 			case "\xA9".'lyr': // LYRics
       
   284 			case "\xA9".'mak':
       
   285 			case "\xA9".'mod':
       
   286 			case "\xA9".'nam': // full NAMe
       
   287 			case "\xA9".'ope':
       
   288 			case "\xA9".'PRD':
       
   289 			case "\xA9".'prd':
       
   290 			case "\xA9".'prf':
       
   291 			case "\xA9".'req':
       
   292 			case "\xA9".'src':
       
   293 			case "\xA9".'swr':
       
   294 			case "\xA9".'too': // encoder
       
   295 			case "\xA9".'trk': // TRacK
       
   296 			case "\xA9".'url':
       
   297 			case "\xA9".'wrn':
       
   298 			case "\xA9".'wrt': // WRiTer
       
   299 			case '----': // itunes specific
       
   300 				if ($atom_parent == 'udta') {
   387 				if ($atom_parent == 'udta') {
   301 					// User data atom handler
   388 					// User data atom handler
   302 					$atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
   389 					$atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
   303 					$atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2));
   390 					$atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2));
   304 					$atom_structure['data']        =                           substr($atom_data, 4);
   391 					$atom_structure['data']        =                           substr($atom_data, 4);
   316 						while ($atomoffset < strlen($atom_data)) {
   403 						while ($atomoffset < strlen($atom_data)) {
   317 							$boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset,     2));
   404 							$boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset,     2));
   318 							$boxsmalltype =                           substr($atom_data, $atomoffset + 2, 2);
   405 							$boxsmalltype =                           substr($atom_data, $atomoffset + 2, 2);
   319 							$boxsmalldata =                           substr($atom_data, $atomoffset + 4, $boxsmallsize);
   406 							$boxsmalldata =                           substr($atom_data, $atomoffset + 4, $boxsmallsize);
   320 							if ($boxsmallsize <= 1) {
   407 							if ($boxsmallsize <= 1) {
   321 								$info['warning'][] = 'Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset);
   408 								$this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
   322 								$atom_structure['data'] = null;
   409 								$atom_structure['data'] = null;
   323 								$atomoffset = strlen($atom_data);
   410 								$atomoffset = strlen($atom_data);
   324 								break;
   411 								break;
   325 							}
   412 							}
   326 							switch ($boxsmalltype) {
   413 							switch ($boxsmalltype) {
   327 								case "\x10\xB5":
   414 								case "\x10\xB5":
   328 									$atom_structure['data'] = $boxsmalldata;
   415 									$atom_structure['data'] = $boxsmalldata;
   329 									break;
   416 									break;
   330 								default:
   417 								default:
   331 									$info['warning'][] = 'Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset;
   418 									$this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset);
   332 									$atom_structure['data'] = $atom_data;
   419 									$atom_structure['data'] = $atom_data;
   333 									break;
   420 									break;
   334 							}
   421 							}
   335 							$atomoffset += (4 + $boxsmallsize);
   422 							$atomoffset += (4 + $boxsmallsize);
   336 						}
   423 						}
   338 						while ($atomoffset < strlen($atom_data)) {
   425 						while ($atomoffset < strlen($atom_data)) {
   339 							$boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4));
   426 							$boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4));
   340 							$boxtype =                           substr($atom_data, $atomoffset + 4, 4);
   427 							$boxtype =                           substr($atom_data, $atomoffset + 4, 4);
   341 							$boxdata =                           substr($atom_data, $atomoffset + 8, $boxsize - 8);
   428 							$boxdata =                           substr($atom_data, $atomoffset + 8, $boxsize - 8);
   342 							if ($boxsize <= 1) {
   429 							if ($boxsize <= 1) {
   343 								$info['warning'][] = 'Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset);
   430 								$this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
   344 								$atom_structure['data'] = null;
   431 								$atom_structure['data'] = null;
   345 								$atomoffset = strlen($atom_data);
   432 								$atomoffset = strlen($atom_data);
   346 								break;
   433 								break;
   347 							}
   434 							}
   348 							$atomoffset += $boxsize;
   435 							$atomoffset += $boxsize;
   359 									switch ($atom_structure['flags_raw']) {
   446 									switch ($atom_structure['flags_raw']) {
   360 										case  0: // data flag
   447 										case  0: // data flag
   361 										case 21: // tmpo/cpil flag
   448 										case 21: // tmpo/cpil flag
   362 											switch ($atomname) {
   449 											switch ($atomname) {
   363 												case 'cpil':
   450 												case 'cpil':
       
   451 												case 'hdvd':
   364 												case 'pcst':
   452 												case 'pcst':
   365 												case 'pgap':
   453 												case 'pgap':
       
   454 													// 8-bit integer (boolean)
   366 													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
   455 													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
   367 													break;
   456 													break;
   368 
   457 
   369 												case 'tmpo':
   458 												case 'tmpo':
       
   459 													// 16-bit integer
   370 													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2));
   460 													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2));
   371 													break;
   461 													break;
   372 
   462 
   373 												case 'disk':
   463 												case 'disk':
   374 												case 'trkn':
   464 												case 'trkn':
       
   465 													// binary
   375 													$num       = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2));
   466 													$num       = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2));
   376 													$num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2));
   467 													$num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2));
   377 													$atom_structure['data']  = empty($num) ? '' : $num;
   468 													$atom_structure['data']  = empty($num) ? '' : $num;
   378 													$atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total;
   469 													$atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total;
   379 													break;
   470 													break;
   380 
   471 
   381 												case 'gnre':
   472 												case 'gnre':
       
   473 													// enum
   382 													$GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
   474 													$GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
   383 													$atom_structure['data']    = getid3_id3v1::LookupGenreName($GenreID - 1);
   475 													$atom_structure['data']    = getid3_id3v1::LookupGenreName($GenreID - 1);
   384 													break;
   476 													break;
   385 
   477 
   386 												case 'rtng':
   478 												case 'rtng':
       
   479 													// 8-bit integer
   387 													$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
   480 													$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
   388 													$atom_structure['data']    = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]);
   481 													$atom_structure['data']    = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]);
   389 													break;
   482 													break;
   390 
   483 
   391 												case 'stik':
   484 												case 'stik':
       
   485 													// 8-bit integer (enum)
   392 													$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
   486 													$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
   393 													$atom_structure['data']    = $this->QuicktimeSTIKLookup($atom_structure[$atomname]);
   487 													$atom_structure['data']    = $this->QuicktimeSTIKLookup($atom_structure[$atomname]);
   394 													break;
   488 													break;
   395 
   489 
   396 												case 'sfID':
   490 												case 'sfID':
       
   491 													// 32-bit integer
   397 													$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
   492 													$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
   398 													$atom_structure['data']    = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]);
   493 													$atom_structure['data']    = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]);
   399 													break;
   494 													break;
   400 
   495 
   401 												case 'egid':
   496 												case 'egid':
   402 												case 'purl':
   497 												case 'purl':
   403 													$atom_structure['data'] = substr($boxdata, 8);
   498 													$atom_structure['data'] = substr($boxdata, 8);
   404 													break;
   499 													break;
   405 
   500 
       
   501 												case 'plID':
       
   502 													// 64-bit integer
       
   503 													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8));
       
   504 													break;
       
   505 
       
   506 												case 'covr':
       
   507 													$atom_structure['data'] = substr($boxdata, 8);
       
   508 													// not a foolproof check, but better than nothing
       
   509 													if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
       
   510 														$atom_structure['image_mime'] = 'image/jpeg';
       
   511 													} elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
       
   512 														$atom_structure['image_mime'] = 'image/png';
       
   513 													} elseif (preg_match('#^GIF#', $atom_structure['data'])) {
       
   514 														$atom_structure['image_mime'] = 'image/gif';
       
   515 													}
       
   516 													break;
       
   517 
       
   518 												case 'atID':
       
   519 												case 'cnID':
       
   520 												case 'geID':
       
   521 												case 'tves':
       
   522 												case 'tvsn':
   406 												default:
   523 												default:
       
   524 													// 32-bit integer
   407 													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
   525 													$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
   408 											}
   526 											}
   409 											break;
   527 											break;
   410 
   528 
   411 										case  1: // text flag
   529 										case  1: // text flag
   412 										case 13: // image flag
   530 										case 13: // image flag
   413 										default:
   531 										default:
   414 											$atom_structure['data'] = substr($boxdata, 8);
   532 											$atom_structure['data'] = substr($boxdata, 8);
   415 											if ($atomname == 'covr') {
   533 											if ($atomname == 'covr') {
   416 												// not a foolproof check, but better than nothing
   534 												// not a foolproof check, but better than nothing
   417 												if (preg_match('#^\xFF\xD8\xFF#', $atom_structure['data'])) {
   535 												if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
   418 													$atom_structure['image_mime'] = 'image/jpeg';
   536 													$atom_structure['image_mime'] = 'image/jpeg';
   419 												} elseif (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $atom_structure['data'])) {
   537 												} elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
   420 													$atom_structure['image_mime'] = 'image/png';
   538 													$atom_structure['image_mime'] = 'image/png';
   421 												} elseif (preg_match('#^GIF#', $atom_structure['data'])) {
   539 												} elseif (preg_match('#^GIF#', $atom_structure['data'])) {
   422 													$atom_structure['image_mime'] = 'image/gif';
   540 													$atom_structure['image_mime'] = 'image/gif';
   423 												}
   541 												}
   424 											}
   542 											}
   426 
   544 
   427 									}
   545 									}
   428 									break;
   546 									break;
   429 
   547 
   430 								default:
   548 								default:
   431 									$info['warning'][] = 'Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset;
   549 									$this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset);
   432 									$atom_structure['data'] = $atom_data;
   550 									$atom_structure['data'] = $atom_data;
   433 
   551 
   434 							}
   552 							}
   435 						}
   553 						}
   436 					}
   554 					}
   474 
   592 
   475 				$CompressedFileData = substr($atom_data, 4);
   593 				$CompressedFileData = substr($atom_data, 4);
   476 				if ($UncompressedHeader = @gzuncompress($CompressedFileData)) {
   594 				if ($UncompressedHeader = @gzuncompress($CompressedFileData)) {
   477 					$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms);
   595 					$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms);
   478 				} else {
   596 				} else {
   479 					$info['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atom_structure['offset'];
   597 					$this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']);
   480 				}
   598 				}
   481 				break;
   599 				break;
   482 
   600 
   483 
   601 
   484 			case 'dcom': // Data COMpression atom
   602 			case 'dcom': // Data COMpression atom
   593 				$ptv_lookup[3] = 'full';
   711 				$ptv_lookup[3] = 'full';
   594 				$ptv_lookup[4] = 'current';
   712 				$ptv_lookup[4] = 'current';
   595 				if (isset($ptv_lookup[$atom_structure['display_size_raw']])) {
   713 				if (isset($ptv_lookup[$atom_structure['display_size_raw']])) {
   596 					$atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
   714 					$atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
   597 				} else {
   715 				} else {
   598 					$info['warning'][] = 'unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')';
   716 					$this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')');
   599 				}
   717 				}
   600 				break;
   718 				break;
   601 
   719 
   602 
   720 
   603 			case 'stsd': // Sample Table Sample Description atom
   721 			case 'stsd': // Sample Table Sample Description atom
   604 				$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
   722 				$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
   605 				$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
   723 				$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
   606 				$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
   724 				$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
       
   725 
       
   726 				// see: https://github.com/JamesHeinrich/getID3/issues/111
       
   727 				// Some corrupt files have been known to have high bits set in the number_entries field
       
   728 				// This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000
       
   729 				// Workaround: mask off the upper byte and throw a warning if it's nonzero
       
   730 				if ($atom_structure['number_entries'] > 0x000FFFFF) {
       
   731 					if ($atom_structure['number_entries'] > 0x00FFFFFF) {
       
   732 						$this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Ignoring upper byte and interpreting this as 0x'.getid3_lib::PrintHexBytes(substr($atom_data, 5, 3), true, false).' = '.($atom_structure['number_entries'] & 0x00FFFFFF));
       
   733 						$atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF);
       
   734 					} else {
       
   735 						$this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Please report this to info@getid3.org referencing bug report #111');
       
   736 					}
       
   737 				}
       
   738 
   607 				$stsdEntriesDataOffset = 8;
   739 				$stsdEntriesDataOffset = 8;
   608 				for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
   740 				for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
   609 					$atom_structure['sample_description_table'][$i]['size']             = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4));
   741 					$atom_structure['sample_description_table'][$i]['size']             = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4));
   610 					$stsdEntriesDataOffset += 4;
   742 					$stsdEntriesDataOffset += 4;
   611 					$atom_structure['sample_description_table'][$i]['data_format']      =                           substr($atom_data, $stsdEntriesDataOffset, 4);
   743 					$atom_structure['sample_description_table'][$i]['data_format']      =                           substr($atom_data, $stsdEntriesDataOffset, 4);
   797 				$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
   929 				$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
   798 				$sttsEntriesDataOffset = 8;
   930 				$sttsEntriesDataOffset = 8;
   799 				//$FrameRateCalculatorArray = array();
   931 				//$FrameRateCalculatorArray = array();
   800 				$frames_count = 0;
   932 				$frames_count = 0;
   801 
   933 
   802 				$max_stts_entries_to_scan = min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']);
   934 				$max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']);
   803 				if ($max_stts_entries_to_scan < $atom_structure['number_entries']) {
   935 				if ($max_stts_entries_to_scan < $atom_structure['number_entries']) {
   804 					$info['warning'][] = 'QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($this->getid3->memory_limit / 1048576).'MB).';
   936 					$this->warning('QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).');
   805 				}
   937 				}
   806 				for ($i = 0; $i < $max_stts_entries_to_scan; $i++) {
   938 				for ($i = 0; $i < $max_stts_entries_to_scan; $i++) {
   807 					$atom_structure['time_to_sample_table'][$i]['sample_count']    = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
   939 					$atom_structure['time_to_sample_table'][$i]['sample_count']    = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
   808 					$sttsEntriesDataOffset += 4;
   940 					$sttsEntriesDataOffset += 4;
   809 					$atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
   941 					$atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
   926 				$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
  1058 				$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
   927 				$drefDataOffset = 8;
  1059 				$drefDataOffset = 8;
   928 				for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
  1060 				for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
   929 					$atom_structure['data_references'][$i]['size']                    = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4));
  1061 					$atom_structure['data_references'][$i]['size']                    = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4));
   930 					$drefDataOffset += 4;
  1062 					$drefDataOffset += 4;
   931 					$atom_structure['data_references'][$i]['type']                    =               substr($atom_data, $drefDataOffset, 4);
  1063 					$atom_structure['data_references'][$i]['type']                    =                           substr($atom_data, $drefDataOffset, 4);
   932 					$drefDataOffset += 4;
  1064 					$drefDataOffset += 4;
   933 					$atom_structure['data_references'][$i]['version']                 = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 1));
  1065 					$atom_structure['data_references'][$i]['version']                 = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 1));
   934 					$drefDataOffset += 1;
  1066 					$drefDataOffset += 1;
   935 					$atom_structure['data_references'][$i]['flags_raw']               = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 3)); // hardcoded: 0x0000
  1067 					$atom_structure['data_references'][$i]['flags_raw']               = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 3)); // hardcoded: 0x0000
   936 					$drefDataOffset += 3;
  1068 					$drefDataOffset += 3;
   937 					$atom_structure['data_references'][$i]['data']                    =               substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));
  1069 					$atom_structure['data_references'][$i]['data']                    =                           substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));
   938 					$drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3);
  1070 					$drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3);
   939 
  1071 
   940 					$atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001);
  1072 					$atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001);
   941 				}
  1073 				}
   942 				break;
  1074 				break;
   999 				$atom_structure['duration']              = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
  1131 				$atom_structure['duration']              = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
  1000 				$atom_structure['language_id']           = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2));
  1132 				$atom_structure['language_id']           = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2));
  1001 				$atom_structure['quality']               = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2));
  1133 				$atom_structure['quality']               = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2));
  1002 
  1134 
  1003 				if ($atom_structure['time_scale'] == 0) {
  1135 				if ($atom_structure['time_scale'] == 0) {
  1004 					$info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero';
  1136 					$this->error('Corrupt Quicktime file: mdhd.time_scale == zero');
  1005 					return false;
  1137 					return false;
  1006 				}
  1138 				}
  1007 				$info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
  1139 				$info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
  1008 
  1140 
  1009 				$atom_structure['creation_time_unix']    = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
  1141 				$atom_structure['creation_time_unix']    = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
  1010 				$atom_structure['modify_time_unix']      = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
  1142 				$atom_structure['modify_time_unix']      = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
  1011 				$atom_structure['playtime_seconds']      = $atom_structure['duration'] / $atom_structure['time_scale'];
  1143 				$atom_structure['playtime_seconds']      = $atom_structure['duration'] / $atom_structure['time_scale'];
  1012 				$atom_structure['language']              = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
  1144 				$atom_structure['language']              = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
  1017 
  1149 
  1018 
  1150 
  1019 			case 'pnot': // Preview atom
  1151 			case 'pnot': // Preview atom
  1020 				$atom_structure['modification_date']      = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4)); // "standard Macintosh format"
  1152 				$atom_structure['modification_date']      = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4)); // "standard Macintosh format"
  1021 				$atom_structure['version_number']         = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2)); // hardcoded: 0x00
  1153 				$atom_structure['version_number']         = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2)); // hardcoded: 0x00
  1022 				$atom_structure['atom_type']              =               substr($atom_data,  6, 4);        // usually: 'PICT'
  1154 				$atom_structure['atom_type']              =                           substr($atom_data,  6, 4);        // usually: 'PICT'
  1023 				$atom_structure['atom_index']             = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01
  1155 				$atom_structure['atom_index']             = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01
  1024 
  1156 
  1025 				$atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']);
  1157 				$atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']);
  1026 				break;
  1158 				break;
  1027 
  1159 
  1028 
  1160 
  1029 			case 'crgn': // Clipping ReGioN atom
  1161 			case 'crgn': // Clipping ReGioN atom
  1030 				$atom_structure['region_size']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 2)); // The Region size, Region boundary box,
  1162 				$atom_structure['region_size']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 2)); // The Region size, Region boundary box,
  1031 				$atom_structure['boundary_box']  = getid3_lib::BigEndian2Int(substr($atom_data,  2, 8)); // and Clipping region data fields
  1163 				$atom_structure['boundary_box']  = getid3_lib::BigEndian2Int(substr($atom_data,  2, 8)); // and Clipping region data fields
  1032 				$atom_structure['clipping_data'] =               substr($atom_data, 10);           // constitute a QuickDraw region.
  1164 				$atom_structure['clipping_data'] =                           substr($atom_data, 10);           // constitute a QuickDraw region.
  1033 				break;
  1165 				break;
  1034 
  1166 
  1035 
  1167 
  1036 			case 'load': // track LOAD settings atom
  1168 			case 'load': // track LOAD settings atom
  1037 				$atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4));
  1169 				$atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4));
  1113 				$atom_structure['selection_duration'] =   getid3_lib::BigEndian2Int(substr($atom_data, 88, 4));
  1245 				$atom_structure['selection_duration'] =   getid3_lib::BigEndian2Int(substr($atom_data, 88, 4));
  1114 				$atom_structure['current_time']       =   getid3_lib::BigEndian2Int(substr($atom_data, 92, 4));
  1246 				$atom_structure['current_time']       =   getid3_lib::BigEndian2Int(substr($atom_data, 92, 4));
  1115 				$atom_structure['next_track_id']      =   getid3_lib::BigEndian2Int(substr($atom_data, 96, 4));
  1247 				$atom_structure['next_track_id']      =   getid3_lib::BigEndian2Int(substr($atom_data, 96, 4));
  1116 
  1248 
  1117 				if ($atom_structure['time_scale'] == 0) {
  1249 				if ($atom_structure['time_scale'] == 0) {
  1118 					$info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero';
  1250 					$this->error('Corrupt Quicktime file: mvhd.time_scale == zero');
  1119 					return false;
  1251 					return false;
  1120 				}
  1252 				}
  1121 				$atom_structure['creation_time_unix']        = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
  1253 				$atom_structure['creation_time_unix']        = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
  1122 				$atom_structure['modify_time_unix']          = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
  1254 				$atom_structure['modify_time_unix']          = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
  1123 				$info['quicktime']['time_scale']    = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
  1255 				$info['quicktime']['time_scale']    = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
  1124 				$info['quicktime']['display_scale'] = $atom_structure['matrix_a'];
  1256 				$info['quicktime']['display_scale'] = $atom_structure['matrix_a'];
  1125 				$info['playtime_seconds']           = $atom_structure['duration'] / $atom_structure['time_scale'];
  1257 				$info['playtime_seconds']           = $atom_structure['duration'] / $atom_structure['time_scale'];
  1126 				break;
  1258 				break;
  1127 
  1259 
  1128 
  1260 
  1238 						break;
  1370 						break;
  1239 					}
  1371 					}
  1240 				}
  1372 				}
  1241 
  1373 
  1242 				// check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field
  1374 				// check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field
  1243 				while  (($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2)))
  1375 				while (($mdat_offset < (strlen($atom_data) - 8))
       
  1376 					&& ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2)))
  1244 					&& ($chapter_string_length < 1000)
  1377 					&& ($chapter_string_length < 1000)
  1245 					&& ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2))
  1378 					&& ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2))
  1246 					&& preg_match('#^[\x20-\xFF]+$#', substr($atom_data, $mdat_offset + 2, $chapter_string_length), $chapter_matches)) {
  1379 					&& preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) {
       
  1380 						list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches;
  1247 						$mdat_offset += (2 + $chapter_string_length);
  1381 						$mdat_offset += (2 + $chapter_string_length);
  1248 						@$info['quicktime']['comments']['chapters'][] = $chapter_matches[0];
  1382 						@$info['quicktime']['comments']['chapters'][] = $chapter_string;
  1249 				}
  1383 
  1250 
  1384 						// "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled)
       
  1385 						if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8
       
  1386 							$mdat_offset += 12;
       
  1387 						}
       
  1388 				}
  1251 
  1389 
  1252 
  1390 
  1253 				if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) {
  1391 				if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) {
  1254 
  1392 
  1255 					$info['avdataoffset'] = $atom_structure['offset'] + 8;                       // $info['quicktime'][$atomname]['offset'] + 8;
  1393 					$info['avdataoffset'] = $atom_structure['offset'] + 8;                       // $info['quicktime'][$atomname]['offset'] + 8;
  1263 					$getid3_mp3 = new getid3_mp3($getid3_temp);
  1401 					$getid3_mp3 = new getid3_mp3($getid3_temp);
  1264 					if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) {
  1402 					if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) {
  1265 						$getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false);
  1403 						$getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false);
  1266 						if (!empty($getid3_temp->info['warning'])) {
  1404 						if (!empty($getid3_temp->info['warning'])) {
  1267 							foreach ($getid3_temp->info['warning'] as $value) {
  1405 							foreach ($getid3_temp->info['warning'] as $value) {
  1268 								$info['warning'][] = $value;
  1406 								$this->warning($value);
  1269 							}
  1407 							}
  1270 						}
  1408 						}
  1271 						if (!empty($getid3_temp->info['mpeg'])) {
  1409 						if (!empty($getid3_temp->info['mpeg'])) {
  1272 							$info['mpeg'] = $getid3_temp->info['mpeg'];
  1410 							$info['mpeg'] = $getid3_temp->info['mpeg'];
  1273 							if (isset($info['mpeg']['audio'])) {
  1411 							if (isset($info['mpeg']['audio'])) {
  1366 					$info['quicktime']['comments']['gps_longitude'][] = floatval($longitude);
  1504 					$info['quicktime']['comments']['gps_longitude'][] = floatval($longitude);
  1367 					if (!empty($altitude)) {
  1505 					if (!empty($altitude)) {
  1368 						$info['quicktime']['comments']['gps_altitude'][] = floatval($altitude);
  1506 						$info['quicktime']['comments']['gps_altitude'][] = floatval($altitude);
  1369 					}
  1507 					}
  1370 				} else {
  1508 				} else {
  1371 					$info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.';
  1509 					$this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.');
  1372 				}
  1510 				}
  1373 				break;
  1511 				break;
  1374 
  1512 
  1375 			case 'NCDT':
  1513 			case 'NCDT':
  1376 				// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
  1514 				// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
  1395 			case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html
  1533 			case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html
  1396 				$atom_structure['data'] = $atom_data;
  1534 				$atom_structure['data'] = $atom_data;
  1397 				break;
  1535 				break;
  1398 
  1536 
  1399 			case "\x00\x00\x00\x00":
  1537 			case "\x00\x00\x00\x00":
       
  1538 				// some kind of metacontainer, may contain a big data dump such as:
       
  1539 				// mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4
       
  1540 				// http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt
       
  1541 
       
  1542 				$atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
       
  1543 				$atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
       
  1544 				$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
       
  1545 				//$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
       
  1546 				break;
       
  1547 
  1400 			case 'meta': // METAdata atom
  1548 			case 'meta': // METAdata atom
  1401 				// some kind of metacontainer, may contain a big data dump such as:
  1549 				// https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html
  1402 				// mdta keys  mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst   data DEApple 0  (data DE2011-05-11T17:54:04+0200 2  *data DE+52.4936+013.3897+040.247/   data DE4.3.1  data DEiPhone 4
  1550 
  1403 				// http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt
  1551 				$atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
  1404 
  1552 				$atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
  1405 	            $atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
  1553 				$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
  1406 	            $atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
       
  1407 	            $atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
       
  1408 				//$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
       
  1409 				break;
  1554 				break;
  1410 
  1555 
  1411 			case 'data': // metaDATA atom
  1556 			case 'data': // metaDATA atom
       
  1557 				static $metaDATAkey = 1; // real ugly, but so is the QuickTime structure that stores keys and values in different multinested locations that are hard to relate to each other
  1412 				// seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data
  1558 				// seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data
  1413 				$atom_structure['language'] =                           substr($atom_data, 4 + 0, 2);
  1559 				$atom_structure['language'] =                           substr($atom_data, 4 + 0, 2);
  1414 				$atom_structure['unknown']  = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2));
  1560 				$atom_structure['unknown']  = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2));
  1415 				$atom_structure['data']     =                           substr($atom_data, 4 + 4);
  1561 				$atom_structure['data']     =                           substr($atom_data, 4 + 4);
  1416 				break;
  1562 				$atom_structure['key_name'] = @$info['quicktime']['temp_meta_key_names'][$metaDATAkey++];
       
  1563 
       
  1564 				if ($atom_structure['key_name'] && $atom_structure['data']) {
       
  1565 					@$info['quicktime']['comments'][str_replace('com.apple.quicktime.', '', $atom_structure['key_name'])][] = $atom_structure['data'];
       
  1566 				}
       
  1567 				break;
       
  1568 
       
  1569 			case 'keys': // KEYS that may be present in the metadata atom.
       
  1570 				// https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21
       
  1571 				// The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom.
       
  1572 				// This list is indexed starting with 1; 0 is a reserved index value. The metadata item keys atom is a full atom with an atom type of "keys".
       
  1573 				$atom_structure['version']       = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
       
  1574 				$atom_structure['flags_raw']     = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
       
  1575 				$atom_structure['entry_count']   = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
       
  1576 				$keys_atom_offset = 8;
       
  1577 				for ($i = 1; $i <= $atom_structure['entry_count']; $i++) {
       
  1578 					$atom_structure['keys'][$i]['key_size']      = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4));
       
  1579 					$atom_structure['keys'][$i]['key_namespace'] =                           substr($atom_data, $keys_atom_offset + 4, 4);
       
  1580 					$atom_structure['keys'][$i]['key_value']     =                           substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8);
       
  1581 					$keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace
       
  1582 
       
  1583 					$info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value'];
       
  1584 				}
       
  1585 				break;
       
  1586 
       
  1587 			case 'gps ':
       
  1588 				// https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730
       
  1589 				// The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data.
       
  1590 				// The first row is version/metadata/notsure, I skip that.
       
  1591 				// The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file.
       
  1592 
       
  1593 				$GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size
       
  1594 				if (strlen($atom_data) > 0) {
       
  1595 					if ((strlen($atom_data) % $GPS_rowsize) == 0) {
       
  1596 						$atom_structure['gps_toc'] = array();
       
  1597 						foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) {
       
  1598 							$atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize));
       
  1599 						}
       
  1600 
       
  1601 						$atom_structure['gps_entries'] = array();
       
  1602 						$previous_offset = $this->ftell();
       
  1603 						foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) {
       
  1604 							if ($key == 0) {
       
  1605 								// "The first row is version/metadata/notsure, I skip that."
       
  1606 								continue;
       
  1607 							}
       
  1608 							$this->fseek($gps_pointer['offset']);
       
  1609 							$GPS_free_data = $this->fread($gps_pointer['size']);
       
  1610 
       
  1611 							/*
       
  1612 							// 2017-05-10: I see some of the data, notably the Hour-Minute-Second, but cannot reconcile the rest of the data. However, the NMEA "GPRMC" line is there and relatively easy to parse, so I'm using that instead
       
  1613 
       
  1614 							// https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730
       
  1615 							// The structure of the GPS data atom (the 'free' atoms mentioned above) is following:
       
  1616 							// hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from('<IIIIIIssssfff',data, 48)
       
  1617 							// For those unfamiliar with python struct:
       
  1618 							// I = int
       
  1619 							// s = is string (size 1, in this case)
       
  1620 							// f = float
       
  1621 
       
  1622 							//$atom_structure['gps_entries'][$key] = unpack('Vhour/Vminute/Vsecond/Vyear/Vmonth/Vday/Vactive/Vlatitude_b/Vlongitude_b/Vunknown2/flatitude/flongitude/fspeed', substr($GPS_free_data, 48));
       
  1623 							*/
       
  1624 
       
  1625 							// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
       
  1626 							// $GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67
       
  1627 							// $GPRMC,002454,A,3553.5295,N,13938.6570,E,0.0,43.1,180700,7.1,W,A*3F
       
  1628 							// $GPRMC,094347.000,A,5342.0061,N,00737.9908,W,0.01,156.75,140217,,,A*7D
       
  1629 							if (preg_match('#\\$GPRMC,([0-9\\.]*),([AV]),([0-9\\.]*),([NS]),([0-9\\.]*),([EW]),([0-9\\.]*),([0-9\\.]*),([0-9]*),([0-9\\.]*),([EW]?)(,[A])?(\\*[0-9A-F]{2})#', $GPS_free_data, $matches)) {
       
  1630 								$GPS_this_GPRMC = array();
       
  1631 								list(
       
  1632 									$GPS_this_GPRMC['raw']['gprmc'],
       
  1633 									$GPS_this_GPRMC['raw']['timestamp'],
       
  1634 									$GPS_this_GPRMC['raw']['status'],
       
  1635 									$GPS_this_GPRMC['raw']['latitude'],
       
  1636 									$GPS_this_GPRMC['raw']['latitude_direction'],
       
  1637 									$GPS_this_GPRMC['raw']['longitude'],
       
  1638 									$GPS_this_GPRMC['raw']['longitude_direction'],
       
  1639 									$GPS_this_GPRMC['raw']['knots'],
       
  1640 									$GPS_this_GPRMC['raw']['angle'],
       
  1641 									$GPS_this_GPRMC['raw']['datestamp'],
       
  1642 									$GPS_this_GPRMC['raw']['variation'],
       
  1643 									$GPS_this_GPRMC['raw']['variation_direction'],
       
  1644 									$dummy,
       
  1645 									$GPS_this_GPRMC['raw']['checksum'],
       
  1646 								) = $matches;
       
  1647 
       
  1648 								$hour   = substr($GPS_this_GPRMC['raw']['timestamp'], 0, 2);
       
  1649 								$minute = substr($GPS_this_GPRMC['raw']['timestamp'], 2, 2);
       
  1650 								$second = substr($GPS_this_GPRMC['raw']['timestamp'], 4, 2);
       
  1651 								$ms     = substr($GPS_this_GPRMC['raw']['timestamp'], 6);    // may contain decimal seconds
       
  1652 								$day   = substr($GPS_this_GPRMC['raw']['datestamp'], 0, 2);
       
  1653 								$month = substr($GPS_this_GPRMC['raw']['datestamp'], 2, 2);
       
  1654 								$year  = substr($GPS_this_GPRMC['raw']['datestamp'], 4, 2);
       
  1655 								$year += (($year > 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess
       
  1656 								$GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms;
       
  1657 
       
  1658 								$GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void
       
  1659 
       
  1660 								foreach (array('latitude','longitude') as $latlon) {
       
  1661 									preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches);
       
  1662 									list($dummy, $deg, $min) = $matches;
       
  1663 									$GPS_this_GPRMC[$latlon] = $deg + ($min / 60);
       
  1664 								}
       
  1665 								$GPS_this_GPRMC['latitude']  *= (($GPS_this_GPRMC['raw']['latitude_direction']  == 'S') ? -1 : 1);
       
  1666 								$GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1);
       
  1667 
       
  1668 								$GPS_this_GPRMC['heading']    = $GPS_this_GPRMC['raw']['angle'];
       
  1669 								$GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots'];
       
  1670 								$GPS_this_GPRMC['speed_kmh']  = $GPS_this_GPRMC['raw']['knots'] * 1.852;
       
  1671 								if ($GPS_this_GPRMC['raw']['variation']) {
       
  1672 									$GPS_this_GPRMC['variation']  = $GPS_this_GPRMC['raw']['variation'];
       
  1673 									$GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1);
       
  1674 								}
       
  1675 
       
  1676 								$atom_structure['gps_entries'][$key] = $GPS_this_GPRMC;
       
  1677 
       
  1678 								@$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array(
       
  1679 									'latitude'  => $GPS_this_GPRMC['latitude'],
       
  1680 									'longitude' => $GPS_this_GPRMC['longitude'],
       
  1681 									'speed_kmh' => $GPS_this_GPRMC['speed_kmh'],
       
  1682 									'heading'   => $GPS_this_GPRMC['heading'],
       
  1683 								);
       
  1684 
       
  1685 							} else {
       
  1686 								$this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']);
       
  1687 							}
       
  1688 						}
       
  1689 						$this->fseek($previous_offset);
       
  1690 
       
  1691 					} else {
       
  1692 						$this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset);
       
  1693 					}
       
  1694 				} else {
       
  1695 					$this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset);
       
  1696 				}
       
  1697 				break;
       
  1698 
       
  1699 			case 'loci':// 3GP location (El Loco)
       
  1700                                 $info['quicktime']['comments']['gps_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
       
  1701                                 $info['quicktime']['comments']['gps_lang'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
       
  1702                                 $loffset = 0;
       
  1703                                 $info['quicktime']['comments']['gps_location'] = $this->LociString(substr($atom_data, 6), $loffset);
       
  1704                                 $loci_data=substr($atom_data, 6 + $loffset);
       
  1705                                 $info['quicktime']['comments']['gps_role'] = getid3_lib::BigEndian2Int(substr($loci_data, 0, 1));
       
  1706                                 $info['quicktime']['comments']['gps_longitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4));
       
  1707                                 $info['quicktime']['comments']['gps_latitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4));
       
  1708                                 $info['quicktime']['comments']['gps_altitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4));
       
  1709                                 $info['quicktime']['comments']['gps_body'] = $this->LociString(substr($loci_data, 13), $loffset);
       
  1710                                 $info['quicktime']['comments']['gps_notes'] = $this->LociString(substr($loci_data, 13 + $loffset), $loffset);
       
  1711                                 break;
  1417 
  1712 
  1418 			default:
  1713 			default:
  1419 				$info['warning'][] = 'Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset;
  1714 				$this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset);
  1420 				$atom_structure['data'] = $atom_data;
  1715 				$atom_structure['data'] = $atom_data;
  1421 				break;
  1716 				break;
  1422 		}
  1717 		}
  1423 		array_pop($atomHierarchy);
  1718 		array_pop($atomHierarchy);
  1424 		return $atom_structure;
  1719 		return $atom_structure;
  1438 			$subatomdata =                           substr($atom_data, $subatomoffset + 8, $subatomsize - 8);
  1733 			$subatomdata =                           substr($atom_data, $subatomoffset + 8, $subatomsize - 8);
  1439 			if ($subatomsize == 0) {
  1734 			if ($subatomsize == 0) {
  1440 				// Furthermore, for historical reasons the list of atoms is optionally
  1735 				// Furthermore, for historical reasons the list of atoms is optionally
  1441 				// terminated by a 32-bit integer set to 0. If you are writing a program
  1736 				// terminated by a 32-bit integer set to 0. If you are writing a program
  1442 				// to read user data atoms, you should allow for the terminating 0.
  1737 				// to read user data atoms, you should allow for the terminating 0.
       
  1738 				if (strlen($atom_data) > 12) {
       
  1739 					$subatomoffset += 4;
       
  1740 					continue;
       
  1741 				}
  1443 				return $atom_structure;
  1742 				return $atom_structure;
  1444 			}
  1743 			}
  1445 
  1744 
  1446 			$atom_structure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms);
  1745 			$atom_structure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms);
  1447 
  1746 
  1751 
  2050 
  1752 	public function QuicktimeIODSaudioProfileName($audio_profile_id) {
  2051 	public function QuicktimeIODSaudioProfileName($audio_profile_id) {
  1753 		static $QuicktimeIODSaudioProfileNameLookup = array();
  2052 		static $QuicktimeIODSaudioProfileNameLookup = array();
  1754 		if (empty($QuicktimeIODSaudioProfileNameLookup)) {
  2053 		if (empty($QuicktimeIODSaudioProfileNameLookup)) {
  1755 			$QuicktimeIODSaudioProfileNameLookup = array(
  2054 			$QuicktimeIODSaudioProfileNameLookup = array(
  1756 			    0x00 => 'ISO Reserved (0x00)',
  2055 				0x00 => 'ISO Reserved (0x00)',
  1757 			    0x01 => 'Main Audio Profile @ Level 1',
  2056 				0x01 => 'Main Audio Profile @ Level 1',
  1758 			    0x02 => 'Main Audio Profile @ Level 2',
  2057 				0x02 => 'Main Audio Profile @ Level 2',
  1759 			    0x03 => 'Main Audio Profile @ Level 3',
  2058 				0x03 => 'Main Audio Profile @ Level 3',
  1760 			    0x04 => 'Main Audio Profile @ Level 4',
  2059 				0x04 => 'Main Audio Profile @ Level 4',
  1761 			    0x05 => 'Scalable Audio Profile @ Level 1',
  2060 				0x05 => 'Scalable Audio Profile @ Level 1',
  1762 			    0x06 => 'Scalable Audio Profile @ Level 2',
  2061 				0x06 => 'Scalable Audio Profile @ Level 2',
  1763 			    0x07 => 'Scalable Audio Profile @ Level 3',
  2062 				0x07 => 'Scalable Audio Profile @ Level 3',
  1764 			    0x08 => 'Scalable Audio Profile @ Level 4',
  2063 				0x08 => 'Scalable Audio Profile @ Level 4',
  1765 			    0x09 => 'Speech Audio Profile @ Level 1',
  2064 				0x09 => 'Speech Audio Profile @ Level 1',
  1766 			    0x0A => 'Speech Audio Profile @ Level 2',
  2065 				0x0A => 'Speech Audio Profile @ Level 2',
  1767 			    0x0B => 'Synthetic Audio Profile @ Level 1',
  2066 				0x0B => 'Synthetic Audio Profile @ Level 1',
  1768 			    0x0C => 'Synthetic Audio Profile @ Level 2',
  2067 				0x0C => 'Synthetic Audio Profile @ Level 2',
  1769 			    0x0D => 'Synthetic Audio Profile @ Level 3',
  2068 				0x0D => 'Synthetic Audio Profile @ Level 3',
  1770 			    0x0E => 'High Quality Audio Profile @ Level 1',
  2069 				0x0E => 'High Quality Audio Profile @ Level 1',
  1771 			    0x0F => 'High Quality Audio Profile @ Level 2',
  2070 				0x0F => 'High Quality Audio Profile @ Level 2',
  1772 			    0x10 => 'High Quality Audio Profile @ Level 3',
  2071 				0x10 => 'High Quality Audio Profile @ Level 3',
  1773 			    0x11 => 'High Quality Audio Profile @ Level 4',
  2072 				0x11 => 'High Quality Audio Profile @ Level 4',
  1774 			    0x12 => 'High Quality Audio Profile @ Level 5',
  2073 				0x12 => 'High Quality Audio Profile @ Level 5',
  1775 			    0x13 => 'High Quality Audio Profile @ Level 6',
  2074 				0x13 => 'High Quality Audio Profile @ Level 6',
  1776 			    0x14 => 'High Quality Audio Profile @ Level 7',
  2075 				0x14 => 'High Quality Audio Profile @ Level 7',
  1777 			    0x15 => 'High Quality Audio Profile @ Level 8',
  2076 				0x15 => 'High Quality Audio Profile @ Level 8',
  1778 			    0x16 => 'Low Delay Audio Profile @ Level 1',
  2077 				0x16 => 'Low Delay Audio Profile @ Level 1',
  1779 			    0x17 => 'Low Delay Audio Profile @ Level 2',
  2078 				0x17 => 'Low Delay Audio Profile @ Level 2',
  1780 			    0x18 => 'Low Delay Audio Profile @ Level 3',
  2079 				0x18 => 'Low Delay Audio Profile @ Level 3',
  1781 			    0x19 => 'Low Delay Audio Profile @ Level 4',
  2080 				0x19 => 'Low Delay Audio Profile @ Level 4',
  1782 			    0x1A => 'Low Delay Audio Profile @ Level 5',
  2081 				0x1A => 'Low Delay Audio Profile @ Level 5',
  1783 			    0x1B => 'Low Delay Audio Profile @ Level 6',
  2082 				0x1B => 'Low Delay Audio Profile @ Level 6',
  1784 			    0x1C => 'Low Delay Audio Profile @ Level 7',
  2083 				0x1C => 'Low Delay Audio Profile @ Level 7',
  1785 			    0x1D => 'Low Delay Audio Profile @ Level 8',
  2084 				0x1D => 'Low Delay Audio Profile @ Level 8',
  1786 			    0x1E => 'Natural Audio Profile @ Level 1',
  2085 				0x1E => 'Natural Audio Profile @ Level 1',
  1787 			    0x1F => 'Natural Audio Profile @ Level 2',
  2086 				0x1F => 'Natural Audio Profile @ Level 2',
  1788 			    0x20 => 'Natural Audio Profile @ Level 3',
  2087 				0x20 => 'Natural Audio Profile @ Level 3',
  1789 			    0x21 => 'Natural Audio Profile @ Level 4',
  2088 				0x21 => 'Natural Audio Profile @ Level 4',
  1790 			    0x22 => 'Mobile Audio Internetworking Profile @ Level 1',
  2089 				0x22 => 'Mobile Audio Internetworking Profile @ Level 1',
  1791 			    0x23 => 'Mobile Audio Internetworking Profile @ Level 2',
  2090 				0x23 => 'Mobile Audio Internetworking Profile @ Level 2',
  1792 			    0x24 => 'Mobile Audio Internetworking Profile @ Level 3',
  2091 				0x24 => 'Mobile Audio Internetworking Profile @ Level 3',
  1793 			    0x25 => 'Mobile Audio Internetworking Profile @ Level 4',
  2092 				0x25 => 'Mobile Audio Internetworking Profile @ Level 4',
  1794 			    0x26 => 'Mobile Audio Internetworking Profile @ Level 5',
  2093 				0x26 => 'Mobile Audio Internetworking Profile @ Level 5',
  1795 			    0x27 => 'Mobile Audio Internetworking Profile @ Level 6',
  2094 				0x27 => 'Mobile Audio Internetworking Profile @ Level 6',
  1796 			    0x28 => 'AAC Profile @ Level 1',
  2095 				0x28 => 'AAC Profile @ Level 1',
  1797 			    0x29 => 'AAC Profile @ Level 2',
  2096 				0x29 => 'AAC Profile @ Level 2',
  1798 			    0x2A => 'AAC Profile @ Level 4',
  2097 				0x2A => 'AAC Profile @ Level 4',
  1799 			    0x2B => 'AAC Profile @ Level 5',
  2098 				0x2B => 'AAC Profile @ Level 5',
  1800 			    0x2C => 'High Efficiency AAC Profile @ Level 2',
  2099 				0x2C => 'High Efficiency AAC Profile @ Level 2',
  1801 			    0x2D => 'High Efficiency AAC Profile @ Level 3',
  2100 				0x2D => 'High Efficiency AAC Profile @ Level 3',
  1802 			    0x2E => 'High Efficiency AAC Profile @ Level 4',
  2101 				0x2E => 'High Efficiency AAC Profile @ Level 4',
  1803 			    0x2F => 'High Efficiency AAC Profile @ Level 5',
  2102 				0x2F => 'High Efficiency AAC Profile @ Level 5',
  1804 			    0xFE => 'Not part of MPEG-4 audio profiles',
  2103 				0xFE => 'Not part of MPEG-4 audio profiles',
  1805 			    0xFF => 'No audio capability required',
  2104 				0xFF => 'No audio capability required',
  1806 			);
  2105 			);
  1807 		}
  2106 		}
  1808 		return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private');
  2107 		return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private');
  1809 	}
  2108 	}
  1810 
  2109 
  2109 
  2408 
  2110 
  2409 
  2111 	public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') {
  2410 	public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') {
  2112 		static $handyatomtranslatorarray = array();
  2411 		static $handyatomtranslatorarray = array();
  2113 		if (empty($handyatomtranslatorarray)) {
  2412 		if (empty($handyatomtranslatorarray)) {
       
  2413 			// http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
       
  2414 			// http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
       
  2415 			// http://atomicparsley.sourceforge.net/mpeg-4files.html
       
  2416 			// https://code.google.com/p/mp4v2/wiki/iTunesMetadata
       
  2417 			$handyatomtranslatorarray["\xA9".'alb'] = 'album';               // iTunes 4.0
       
  2418 			$handyatomtranslatorarray["\xA9".'ART'] = 'artist';
       
  2419 			$handyatomtranslatorarray["\xA9".'art'] = 'artist';              // iTunes 4.0
       
  2420 			$handyatomtranslatorarray["\xA9".'aut'] = 'author';
       
  2421 			$handyatomtranslatorarray["\xA9".'cmt'] = 'comment';             // iTunes 4.0
       
  2422 			$handyatomtranslatorarray["\xA9".'com'] = 'comment';
  2114 			$handyatomtranslatorarray["\xA9".'cpy'] = 'copyright';
  2423 			$handyatomtranslatorarray["\xA9".'cpy'] = 'copyright';
  2115 			$handyatomtranslatorarray["\xA9".'day'] = 'creation_date';    // iTunes 4.0
  2424 			$handyatomtranslatorarray["\xA9".'day'] = 'creation_date';       // iTunes 4.0
  2116 			$handyatomtranslatorarray["\xA9".'dir'] = 'director';
  2425 			$handyatomtranslatorarray["\xA9".'dir'] = 'director';
  2117 			$handyatomtranslatorarray["\xA9".'ed1'] = 'edit1';
  2426 			$handyatomtranslatorarray["\xA9".'ed1'] = 'edit1';
  2118 			$handyatomtranslatorarray["\xA9".'ed2'] = 'edit2';
  2427 			$handyatomtranslatorarray["\xA9".'ed2'] = 'edit2';
  2119 			$handyatomtranslatorarray["\xA9".'ed3'] = 'edit3';
  2428 			$handyatomtranslatorarray["\xA9".'ed3'] = 'edit3';
  2120 			$handyatomtranslatorarray["\xA9".'ed4'] = 'edit4';
  2429 			$handyatomtranslatorarray["\xA9".'ed4'] = 'edit4';
  2121 			$handyatomtranslatorarray["\xA9".'ed5'] = 'edit5';
  2430 			$handyatomtranslatorarray["\xA9".'ed5'] = 'edit5';
  2122 			$handyatomtranslatorarray["\xA9".'ed6'] = 'edit6';
  2431 			$handyatomtranslatorarray["\xA9".'ed6'] = 'edit6';
  2123 			$handyatomtranslatorarray["\xA9".'ed7'] = 'edit7';
  2432 			$handyatomtranslatorarray["\xA9".'ed7'] = 'edit7';
  2124 			$handyatomtranslatorarray["\xA9".'ed8'] = 'edit8';
  2433 			$handyatomtranslatorarray["\xA9".'ed8'] = 'edit8';
  2125 			$handyatomtranslatorarray["\xA9".'ed9'] = 'edit9';
  2434 			$handyatomtranslatorarray["\xA9".'ed9'] = 'edit9';
       
  2435 			$handyatomtranslatorarray["\xA9".'enc'] = 'encoded_by';
  2126 			$handyatomtranslatorarray["\xA9".'fmt'] = 'format';
  2436 			$handyatomtranslatorarray["\xA9".'fmt'] = 'format';
       
  2437 			$handyatomtranslatorarray["\xA9".'gen'] = 'genre';               // iTunes 4.0
       
  2438 			$handyatomtranslatorarray["\xA9".'grp'] = 'grouping';            // iTunes 4.2
       
  2439 			$handyatomtranslatorarray["\xA9".'hst'] = 'host_computer';
  2127 			$handyatomtranslatorarray["\xA9".'inf'] = 'information';
  2440 			$handyatomtranslatorarray["\xA9".'inf'] = 'information';
       
  2441 			$handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics';              // iTunes 5.0
       
  2442 			$handyatomtranslatorarray["\xA9".'mak'] = 'make';
       
  2443 			$handyatomtranslatorarray["\xA9".'mod'] = 'model';
       
  2444 			$handyatomtranslatorarray["\xA9".'nam'] = 'title';               // iTunes 4.0
       
  2445 			$handyatomtranslatorarray["\xA9".'ope'] = 'composer';
  2128 			$handyatomtranslatorarray["\xA9".'prd'] = 'producer';
  2446 			$handyatomtranslatorarray["\xA9".'prd'] = 'producer';
       
  2447 			$handyatomtranslatorarray["\xA9".'PRD'] = 'product';
  2129 			$handyatomtranslatorarray["\xA9".'prf'] = 'performers';
  2448 			$handyatomtranslatorarray["\xA9".'prf'] = 'performers';
  2130 			$handyatomtranslatorarray["\xA9".'req'] = 'system_requirements';
  2449 			$handyatomtranslatorarray["\xA9".'req'] = 'system_requirements';
  2131 			$handyatomtranslatorarray["\xA9".'src'] = 'source_credit';
  2450 			$handyatomtranslatorarray["\xA9".'src'] = 'source_credit';
  2132 			$handyatomtranslatorarray["\xA9".'wrt'] = 'writer';
  2451 			$handyatomtranslatorarray["\xA9".'swr'] = 'software';
  2133 
  2452 			$handyatomtranslatorarray["\xA9".'too'] = 'encoding_tool';       // iTunes 4.0
  2134 			// http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
  2453 			$handyatomtranslatorarray["\xA9".'trk'] = 'track';
  2135 			$handyatomtranslatorarray["\xA9".'nam'] = 'title';           // iTunes 4.0
  2454 			$handyatomtranslatorarray["\xA9".'url'] = 'url';
  2136 			$handyatomtranslatorarray["\xA9".'cmt'] = 'comment';         // iTunes 4.0
       
  2137 			$handyatomtranslatorarray["\xA9".'wrn'] = 'warning';
  2455 			$handyatomtranslatorarray["\xA9".'wrn'] = 'warning';
  2138 			$handyatomtranslatorarray["\xA9".'hst'] = 'host_computer';
  2456 			$handyatomtranslatorarray["\xA9".'wrt'] = 'composer';
  2139 			$handyatomtranslatorarray["\xA9".'mak'] = 'make';
       
  2140 			$handyatomtranslatorarray["\xA9".'mod'] = 'model';
       
  2141 			$handyatomtranslatorarray["\xA9".'PRD'] = 'product';
       
  2142 			$handyatomtranslatorarray["\xA9".'swr'] = 'software';
       
  2143 			$handyatomtranslatorarray["\xA9".'aut'] = 'author';
       
  2144 			$handyatomtranslatorarray["\xA9".'ART'] = 'artist';
       
  2145 			$handyatomtranslatorarray["\xA9".'trk'] = 'track';
       
  2146 			$handyatomtranslatorarray["\xA9".'alb'] = 'album';           // iTunes 4.0
       
  2147 			$handyatomtranslatorarray["\xA9".'com'] = 'comment';
       
  2148 			$handyatomtranslatorarray["\xA9".'gen'] = 'genre';           // iTunes 4.0
       
  2149 			$handyatomtranslatorarray["\xA9".'ope'] = 'composer';
       
  2150 			$handyatomtranslatorarray["\xA9".'url'] = 'url';
       
  2151 			$handyatomtranslatorarray["\xA9".'enc'] = 'encoder';
       
  2152 
       
  2153 			// http://atomicparsley.sourceforge.net/mpeg-4files.html
       
  2154 			$handyatomtranslatorarray["\xA9".'art'] = 'artist';           // iTunes 4.0
       
  2155 			$handyatomtranslatorarray['aART'] = 'album_artist';
  2457 			$handyatomtranslatorarray['aART'] = 'album_artist';
  2156 			$handyatomtranslatorarray['trkn'] = 'track_number';     // iTunes 4.0
  2458 			$handyatomtranslatorarray['apID'] = 'purchase_account';
  2157 			$handyatomtranslatorarray['disk'] = 'disc_number';      // iTunes 4.0
  2459 			$handyatomtranslatorarray['catg'] = 'category';            // iTunes 4.9
  2158 			$handyatomtranslatorarray['gnre'] = 'genre';            // iTunes 4.0
  2460 			$handyatomtranslatorarray['covr'] = 'picture';             // iTunes 4.0
  2159 			$handyatomtranslatorarray["\xA9".'too'] = 'encoder';          // iTunes 4.0
  2461 			$handyatomtranslatorarray['cpil'] = 'compilation';         // iTunes 4.0
  2160 			$handyatomtranslatorarray['tmpo'] = 'bpm';              // iTunes 4.0
  2462 			$handyatomtranslatorarray['cprt'] = 'copyright';           // iTunes 4.0?
  2161 			$handyatomtranslatorarray['cprt'] = 'copyright';        // iTunes 4.0?
  2463 			$handyatomtranslatorarray['desc'] = 'description';         // iTunes 5.0
  2162 			$handyatomtranslatorarray['cpil'] = 'compilation';      // iTunes 4.0
  2464 			$handyatomtranslatorarray['disk'] = 'disc_number';         // iTunes 4.0
  2163 			$handyatomtranslatorarray['covr'] = 'picture';          // iTunes 4.0
  2465 			$handyatomtranslatorarray['egid'] = 'episode_guid';        // iTunes 4.9
  2164 			$handyatomtranslatorarray['rtng'] = 'rating';           // iTunes 4.0
  2466 			$handyatomtranslatorarray['gnre'] = 'genre';               // iTunes 4.0
  2165 			$handyatomtranslatorarray["\xA9".'grp'] = 'grouping';         // iTunes 4.2
  2467 			$handyatomtranslatorarray['hdvd'] = 'hd_video';            // iTunes 4.0
  2166 			$handyatomtranslatorarray['stik'] = 'stik';             // iTunes 4.9
  2468 			$handyatomtranslatorarray['ldes'] = 'description_long';    //
  2167 			$handyatomtranslatorarray['pcst'] = 'podcast';          // iTunes 4.9
  2469 			$handyatomtranslatorarray['keyw'] = 'keyword';             // iTunes 4.9
  2168 			$handyatomtranslatorarray['catg'] = 'category';         // iTunes 4.9
  2470 			$handyatomtranslatorarray['pcst'] = 'podcast';             // iTunes 4.9
  2169 			$handyatomtranslatorarray['keyw'] = 'keyword';          // iTunes 4.9
  2471 			$handyatomtranslatorarray['pgap'] = 'gapless_playback';    // iTunes 7.0
  2170 			$handyatomtranslatorarray['purl'] = 'podcast_url';      // iTunes 4.9
  2472 			$handyatomtranslatorarray['purd'] = 'purchase_date';       // iTunes 6.0.2
  2171 			$handyatomtranslatorarray['egid'] = 'episode_guid';     // iTunes 4.9
  2473 			$handyatomtranslatorarray['purl'] = 'podcast_url';         // iTunes 4.9
  2172 			$handyatomtranslatorarray['desc'] = 'description';      // iTunes 5.0
  2474 			$handyatomtranslatorarray['rtng'] = 'rating';              // iTunes 4.0
  2173 			$handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics';           // iTunes 5.0
  2475 			$handyatomtranslatorarray['soaa'] = 'sort_album_artist';   //
  2174 			$handyatomtranslatorarray['tvnn'] = 'tv_network_name';  // iTunes 6.0
  2476 			$handyatomtranslatorarray['soal'] = 'sort_album';          //
  2175 			$handyatomtranslatorarray['tvsh'] = 'tv_show_name';     // iTunes 6.0
  2477 			$handyatomtranslatorarray['soar'] = 'sort_artist';         //
  2176 			$handyatomtranslatorarray['tvsn'] = 'tv_season';        // iTunes 6.0
  2478 			$handyatomtranslatorarray['soco'] = 'sort_composer';       //
  2177 			$handyatomtranslatorarray['tves'] = 'tv_episode';       // iTunes 6.0
  2479 			$handyatomtranslatorarray['sonm'] = 'sort_title';          //
  2178 			$handyatomtranslatorarray['purd'] = 'purchase_date';    // iTunes 6.0.2
  2480 			$handyatomtranslatorarray['sosn'] = 'sort_show';           //
  2179 			$handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0
  2481 			$handyatomtranslatorarray['stik'] = 'stik';                // iTunes 4.9
  2180 
  2482 			$handyatomtranslatorarray['tmpo'] = 'bpm';                 // iTunes 4.0
  2181 			// http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
  2483 			$handyatomtranslatorarray['trkn'] = 'track_number';        // iTunes 4.0
  2182 
  2484 			$handyatomtranslatorarray['tven'] = 'tv_episode_id';       //
  2183 
  2485 			$handyatomtranslatorarray['tves'] = 'tv_episode';          // iTunes 6.0
       
  2486 			$handyatomtranslatorarray['tvnn'] = 'tv_network_name';     // iTunes 6.0
       
  2487 			$handyatomtranslatorarray['tvsh'] = 'tv_show_name';        // iTunes 6.0
       
  2488 			$handyatomtranslatorarray['tvsn'] = 'tv_season';           // iTunes 6.0
  2184 
  2489 
  2185 			// boxnames:
  2490 			// boxnames:
  2186 			/*
  2491 			/*
  2187 			$handyatomtranslatorarray['iTunSMPB']                    = 'iTunSMPB';
  2492 			$handyatomtranslatorarray['iTunSMPB']                    = 'iTunSMPB';
  2188 			$handyatomtranslatorarray['iTunNORM']                    = 'iTunNORM';
  2493 			$handyatomtranslatorarray['iTunNORM']                    = 'iTunNORM';
  2223 						$image_mime = 'image/bmp';
  2528 						$image_mime = 'image/bmp';
  2224 					}
  2529 					}
  2225 					$data = array('data'=>$data, 'image_mime'=>$image_mime);
  2530 					$data = array('data'=>$data, 'image_mime'=>$image_mime);
  2226 				}
  2531 				}
  2227 			}
  2532 			}
  2228 			$info['quicktime']['comments'][$comment_key][] = $data;
  2533 			$gooddata = array($data);
       
  2534 			if ($comment_key == 'genre') {
       
  2535 				// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
       
  2536 				$gooddata = explode(';', $data);
       
  2537 			}
       
  2538 			foreach ($gooddata as $data) {
       
  2539 				$info['quicktime']['comments'][$comment_key][] = $data;
       
  2540 			}
  2229 		}
  2541 		}
  2230 		return true;
  2542 		return true;
  2231 	}
  2543 	}
       
  2544 
       
  2545     public function LociString($lstring, &$count) {
       
  2546             // Loci strings are UTF-8 or UTF-16 and null (x00/x0000) terminated. UTF-16 has a BOM
       
  2547             // Also need to return the number of bytes the string occupied so additional fields can be extracted
       
  2548             $len = strlen($lstring);
       
  2549             if ($len == 0) {
       
  2550                 $count = 0;
       
  2551                 return '';
       
  2552             }
       
  2553             if ($lstring[0] == "\x00") {
       
  2554                 $count = 1;
       
  2555                 return '';
       
  2556             }
       
  2557             //check for BOM
       
  2558             if ($len > 2 && (($lstring[0] == "\xFE" && $lstring[1] == "\xFF") || ($lstring[0] == "\xFF" && $lstring[1] == "\xFE"))) {
       
  2559                 //UTF-16
       
  2560                 if (preg_match('/(.*)\x00/', $lstring, $lmatches)){
       
  2561                      $count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000
       
  2562                     return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]);
       
  2563                 } else {
       
  2564                     return '';
       
  2565                 }
       
  2566             } else {
       
  2567                 //UTF-8
       
  2568                 if (preg_match('/(.*)\x00/', $lstring, $lmatches)){
       
  2569                     $count = strlen($lmatches[1]) + 1; //account for trailing \x00
       
  2570                     return $lmatches[1];
       
  2571                 }else {
       
  2572                     return '';
       
  2573                 }
       
  2574 
       
  2575             }
       
  2576         }
  2232 
  2577 
  2233 	public function NoNullString($nullterminatedstring) {
  2578 	public function NoNullString($nullterminatedstring) {
  2234 		// remove the single null terminator on null terminated strings
  2579 		// remove the single null terminator on null terminated strings
  2235 		if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") {
  2580 		if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") {
  2236 			return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1);
  2581 			return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1);
  2241 	public function Pascal2String($pascalstring) {
  2586 	public function Pascal2String($pascalstring) {
  2242 		// Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string
  2587 		// Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string
  2243 		return substr($pascalstring, 1);
  2588 		return substr($pascalstring, 1);
  2244 	}
  2589 	}
  2245 
  2590 
       
  2591 
       
  2592 	/*
       
  2593 	// helper functions for m4b audiobook chapters
       
  2594 	// code by Steffen Hartmann 2015-Nov-08
       
  2595 	*/
       
  2596 	public function search_tag_by_key($info, $tag, $history, &$result) {
       
  2597 		foreach ($info as $key => $value) {
       
  2598 			$key_history = $history.'/'.$key;
       
  2599 			if ($key === $tag) {
       
  2600 				$result[] = array($key_history, $info);
       
  2601 			} else {
       
  2602 				if (is_array($value)) {
       
  2603 					$this->search_tag_by_key($value, $tag, $key_history, $result);
       
  2604 				}
       
  2605 			}
       
  2606 		}
       
  2607 	}
       
  2608 
       
  2609 	public function search_tag_by_pair($info, $k, $v, $history, &$result) {
       
  2610 		foreach ($info as $key => $value) {
       
  2611 			$key_history = $history.'/'.$key;
       
  2612 			if (($key === $k) && ($value === $v)) {
       
  2613 				$result[] = array($key_history, $info);
       
  2614 			} else {
       
  2615 				if (is_array($value)) {
       
  2616 					$this->search_tag_by_pair($value, $k, $v, $key_history, $result);
       
  2617 				}
       
  2618 			}
       
  2619 		}
       
  2620 	}
       
  2621 
       
  2622 	public function quicktime_time_to_sample_table($info) {
       
  2623 		$res = array();
       
  2624 		$this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res);
       
  2625 		foreach ($res as $value) {
       
  2626 			$stbl_res = array();
       
  2627 			$this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res);
       
  2628 			if (count($stbl_res) > 0) {
       
  2629 				$stts_res = array();
       
  2630 				$this->search_tag_by_key($value[1], 'time_to_sample_table', $value[0], $stts_res);
       
  2631 				if (count($stts_res) > 0) {
       
  2632 					return $stts_res[0][1]['time_to_sample_table'];
       
  2633 				}
       
  2634 			}
       
  2635 		}
       
  2636 		return array();
       
  2637 	}
       
  2638 
       
  2639 	function quicktime_bookmark_time_scale($info) {
       
  2640 		$time_scale = '';
       
  2641 		$ts_prefix_len = 0;
       
  2642 		$res = array();
       
  2643 		$this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res);
       
  2644 		foreach ($res as $value) {
       
  2645 			$stbl_res = array();
       
  2646 			$this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res);
       
  2647 			if (count($stbl_res) > 0) {
       
  2648 				$ts_res = array();
       
  2649 				$this->search_tag_by_key($info['quicktime']['moov'], 'time_scale', 'quicktime/moov', $ts_res);
       
  2650 				foreach ($ts_res as $value) {
       
  2651 					$prefix = substr($value[0], 0, -12);
       
  2652 					if ((substr($stbl_res[0][0], 0, strlen($prefix)) === $prefix) && ($ts_prefix_len < strlen($prefix))) {
       
  2653 						$time_scale = $value[1]['time_scale'];
       
  2654 						$ts_prefix_len = strlen($prefix);
       
  2655 					}
       
  2656 				}
       
  2657 			}
       
  2658 		}
       
  2659 		return $time_scale;
       
  2660 	}
       
  2661 	/*
       
  2662 	// END helper functions for m4b audiobook chapters
       
  2663 	*/
       
  2664 
       
  2665 
  2246 }
  2666 }