run:R W Run
54.21 KB
2026-03-11 16:18:51
R W Run
79.05 KB
2026-03-11 16:18:51
R W Run
1.36 KB
2026-03-11 16:18:51
R W Run
133.61 KB
2026-03-11 16:18:51
R W Run
26.5 KB
2026-03-11 16:18:51
R W Run
104.64 KB
2026-03-11 16:18:51
R W Run
164.97 KB
2026-03-11 16:18:51
R W Run
136.53 KB
2026-03-11 16:18:51
R W Run
38.46 KB
2026-03-11 16:18:51
R W Run
10.63 KB
2026-03-11 16:18:51
R W Run
19.23 KB
2026-03-11 16:18:51
R W Run
104.5 KB
2026-03-11 16:18:51
R W Run
42.74 KB
2026-03-11 16:18:51
R W Run
18.63 KB
2026-03-11 16:18:51
R W Run
14.7 KB
2026-03-11 16:18:51
R W Run
151.2 KB
2026-03-11 16:18:51
R W Run
11.75 KB
2026-03-11 16:18:51
R W Run
25.71 KB
2026-03-11 16:18:51
R W Run
error_log
📄module.audio-video.quicktime.php
1<?php
2
3/////////////////////////////////////////////////////////////////
4/// getID3() by James Heinrich <info@getid3.org> //
5// available at https://github.com/JamesHeinrich/getID3 //
6// or https://www.getid3.org //
7// or http://getid3.sourceforge.net //
8// see readme.txt for more details //
9/////////////////////////////////////////////////////////////////
10// //
11// module.audio-video.quicktime.php //
12// module for analyzing Quicktime and MP3-in-MP4 files //
13// dependencies: module.audio.mp3.php //
14// dependencies: module.tag.id3v2.php //
15// ///
16/////////////////////////////////////////////////////////////////
17
18if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
19 exit;
20}
21getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);
22getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); // needed for ISO 639-2 language code lookup
23
24class getid3_quicktime extends getid3_handler
25{
26
27 /** audio-video.quicktime
28 * return all parsed data from all atoms if true, otherwise just returned parsed metadata
29 *
30 * @var bool
31 */
32 public $ReturnAtomData = false;
33
34 /** audio-video.quicktime
35 * return all parsed data from all atoms if true, otherwise just returned parsed metadata
36 *
37 * @var bool
38 */
39 public $ParseAllPossibleAtoms = false;
40
41 /**
42 * real ugly, but so is the QuickTime structure that stores keys and values in different multi-nested locations that are hard to relate to each other
43 * https://github.com/JamesHeinrich/getID3/issues/214
44 *
45 * @var int
46 */
47 private $metaDATAkey = 1;
48
49 /**
50 * @return bool
51 */
52 public function Analyze() {
53 $info = &$this->getid3->info;
54
55 $this->metaDATAkey = 1;
56 $info['fileformat'] = 'quicktime';
57 $info['quicktime']['hinting'] = false;
58 $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present
59
60 $this->fseek($info['avdataoffset']);
61
62 $offset = 0;
63 $atomcounter = 0;
64 $atom_data_read_buffer_size = $info['php_memory_limit'] ? round($info['php_memory_limit'] / 4) : $this->getid3->option_fread_buffer_size * 1024; // set read buffer to 25% of PHP memory limit (if one is specified), otherwise use option_fread_buffer_size [default: 32MB]
65 while ($offset < $info['avdataend']) {
66 if (!getid3_lib::intValueSupported($offset)) {
67 $this->error('Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions');
68 break;
69 }
70 $this->fseek($offset);
71 $AtomHeader = $this->fread(8);
72
73 // https://github.com/JamesHeinrich/getID3/issues/382
74 // Atom sizes are stored as 32-bit number in most cases, but sometimes (notably for "mdat")
75 // a 64-bit value is required, in which case the normal 32-bit size field is set to 0x00000001
76 // and the 64-bit "real" size value is the next 8 bytes.
77 $atom_size_extended_bytes = 0;
78 $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4));
79 $atomname = substr($AtomHeader, 4, 4);
80 if ($atomsize == 1) {
81 $atom_size_extended_bytes = 8;
82 $atomsize = getid3_lib::BigEndian2Int($this->fread($atom_size_extended_bytes));
83 }
84
85 if (($offset + $atomsize) > $info['avdataend']) {
86 $info['quicktime'][$atomname]['name'] = $atomname;
87 $info['quicktime'][$atomname]['size'] = $atomsize;
88 $info['quicktime'][$atomname]['offset'] = $offset;
89 $this->error('Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)');
90 return false;
91 }
92 if ($atomsize == 0) {
93 // Furthermore, for historical reasons the list of atoms is optionally
94 // terminated by a 32-bit integer set to 0. If you are writing a program
95 // to read user data atoms, you should allow for the terminating 0.
96 $info['quicktime'][$atomname]['name'] = $atomname;
97 $info['quicktime'][$atomname]['size'] = $atomsize;
98 $info['quicktime'][$atomname]['offset'] = $offset;
99 break;
100 }
101 $atomHierarchy = array();
102 $parsedAtomData = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize - $atom_size_extended_bytes, $atom_data_read_buffer_size)), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms);
103 $parsedAtomData['name'] = $atomname;
104 $parsedAtomData['size'] = $atomsize;
105 $parsedAtomData['offset'] = $offset;
106 if ($atom_size_extended_bytes) {
107 $parsedAtomData['xsize_bytes'] = $atom_size_extended_bytes;
108 }
109 if (in_array($atomname, array('uuid'))) {
110 @$info['quicktime'][$atomname][] = $parsedAtomData;
111 } else {
112 $info['quicktime'][$atomname] = $parsedAtomData;
113 }
114
115 $offset += $atomsize;
116 $atomcounter++;
117 }
118
119 if (!empty($info['avdataend_tmp'])) {
120 // this value is assigned to a temp value and then erased because
121 // otherwise any atoms beyond the 'mdat' atom would not get parsed
122 $info['avdataend'] = $info['avdataend_tmp'];
123 unset($info['avdataend_tmp']);
124 }
125
126 if (isset($info['quicktime']['comments']['chapters']) && is_array($info['quicktime']['comments']['chapters']) && (count($info['quicktime']['comments']['chapters']) > 0)) {
127 $durations = $this->quicktime_time_to_sample_table($info);
128 for ($i = 0; $i < count($info['quicktime']['comments']['chapters']); $i++) {
129 $bookmark = array();
130 $bookmark['title'] = $info['quicktime']['comments']['chapters'][$i];
131 if (isset($durations[$i])) {
132 $bookmark['duration_sample'] = $durations[$i]['sample_duration'];
133 if ($i > 0) {
134 $bookmark['start_sample'] = $info['quicktime']['bookmarks'][($i - 1)]['start_sample'] + $info['quicktime']['bookmarks'][($i - 1)]['duration_sample'];
135 } else {
136 $bookmark['start_sample'] = 0;
137 }
138 if ($time_scale = $this->quicktime_bookmark_time_scale($info)) {
139 $bookmark['duration_seconds'] = $bookmark['duration_sample'] / $time_scale;
140 $bookmark['start_seconds'] = $bookmark['start_sample'] / $time_scale;
141 }
142 }
143 $info['quicktime']['bookmarks'][] = $bookmark;
144 }
145 }
146
147 if (isset($info['quicktime']['temp_meta_key_names'])) {
148 unset($info['quicktime']['temp_meta_key_names']);
149 }
150
151 if (!empty($info['quicktime']['comments']['location.ISO6709'])) {
152 // https://en.wikipedia.org/wiki/ISO_6709
153 foreach ($info['quicktime']['comments']['location.ISO6709'] as $ISO6709string) {
154 $ISO6709parsed = array('latitude'=>false, 'longitude'=>false, 'altitude'=>false);
155 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)) {
156 @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;
157
158 if (strlen($lat_deg) == 2) { // [+-]DD.D
159 $ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * (float) (ltrim($lat_deg, '0').$lat_deg_dec);
160 } elseif (strlen($lat_deg) == 4) { // [+-]DDMM.M
161 $ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * (int) ltrim(substr($lat_deg, 0, 2), '0') + ((float) (ltrim(substr($lat_deg, 2, 2), '0').$lat_deg_dec) / 60);
162 } elseif (strlen($lat_deg) == 6) { // [+-]DDMMSS.S
163 $ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * (int) ltrim(substr($lat_deg, 0, 2), '0') + ((int) ltrim(substr($lat_deg, 2, 2), '0') / 60) + ((float) (ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec) / 3600);
164 }
165
166 if (strlen($lon_deg) == 3) { // [+-]DDD.D
167 $ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * (float) (ltrim($lon_deg, '0').$lon_deg_dec);
168 } elseif (strlen($lon_deg) == 5) { // [+-]DDDMM.M
169 $ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * (int) ltrim(substr($lon_deg, 0, 2), '0') + ((float) (ltrim(substr($lon_deg, 2, 2), '0').$lon_deg_dec) / 60);
170 } elseif (strlen($lon_deg) == 7) { // [+-]DDDMMSS.S
171 $ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * (int) ltrim(substr($lon_deg, 0, 2), '0') + ((int) ltrim(substr($lon_deg, 2, 2), '0') / 60) + ((float) (ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec) / 3600);
172 }
173
174 if (strlen($alt_deg) == 3) { // [+-]DDD.D
175 $ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * (float) (ltrim($alt_deg, '0').$alt_deg_dec);
176 } elseif (strlen($alt_deg) == 5) { // [+-]DDDMM.M
177 $ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * (int) ltrim(substr($alt_deg, 0, 2), '0') + ((float) (ltrim(substr($alt_deg, 2, 2), '0').$alt_deg_dec) / 60);
178 } elseif (strlen($alt_deg) == 7) { // [+-]DDDMMSS.S
179 $ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * (int) ltrim(substr($alt_deg, 0, 2), '0') + ((int) ltrim(substr($alt_deg, 2, 2), '0') / 60) + ((float) (ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec) / 3600);
180 }
181
182 foreach (array('latitude', 'longitude', 'altitude') as $key) {
183 if ($ISO6709parsed[$key] !== false) {
184 $value = (($lat_sign == '-') ? -1 : 1) * floatval($ISO6709parsed[$key]);
185 if (!isset($info['quicktime']['comments']['gps_'.$key]) || !in_array($value, $info['quicktime']['comments']['gps_'.$key])) {
186 @$info['quicktime']['comments']['gps_'.$key][] = (($lat_sign == '-') ? -1 : 1) * floatval($ISO6709parsed[$key]);
187 }
188 }
189 }
190 }
191 if ($ISO6709parsed['latitude'] === false) {
192 $this->warning('location.ISO6709 string not parsed correctly: "'.$ISO6709string.'", please submit as a bug');
193 }
194 break;
195 }
196 }
197
198 if (!isset($info['bitrate']) && !empty($info['playtime_seconds'])) {
199 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
200 }
201 if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) {
202 $info['audio']['bitrate'] = $info['bitrate'];
203 }
204 if (!empty($info['bitrate']) && !empty($info['audio']['bitrate']) && empty($info['video']['bitrate']) && !empty($info['video']['frame_rate']) && !empty($info['video']['resolution_x']) && ($info['bitrate'] > $info['audio']['bitrate'])) {
205 $info['video']['bitrate'] = $info['bitrate'] - $info['audio']['bitrate'];
206 }
207 if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) {
208 foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) {
209 $samples_per_second = $samples_count / $info['playtime_seconds'];
210 if ($samples_per_second > 240) {
211 // has to be audio samples
212 } else {
213 $info['video']['frame_rate'] = $samples_per_second;
214 break;
215 }
216 }
217 }
218 if ($info['audio']['dataformat'] == 'mp4') {
219 $info['fileformat'] = 'mp4';
220 if (empty($info['video']['resolution_x'])) {
221 $info['mime_type'] = 'audio/mp4';
222 unset($info['video']['dataformat']);
223 } else {
224 $info['mime_type'] = 'video/mp4';
225 }
226 }
227 if (!empty($info['quicktime']['ftyp']['signature']) && in_array($info['quicktime']['ftyp']['signature'], array('heic','heix','hevc','hevx','heim','heis','hevm','hevs'))) {
228 if ($info['mime_type'] == 'video/quicktime') { // default value, as we
229 // https://en.wikipedia.org/wiki/High_Efficiency_Image_File_Format
230$this->error('HEIF files not currently supported');
231 switch ($info['quicktime']['ftyp']['signature']) {
232 // https://github.com/strukturag/libheif/issues/83 (comment by Dirk Farin 2018-09-14)
233 case 'heic': // the usual HEIF images
234 case 'heix': // 10bit images, or anything that uses h265 with range extension
235 case 'hevc': // brands for image sequences
236 case 'hevx': // brands for image sequences
237 case 'heim': // multiview
238 case 'heis': // scalable
239 case 'hevm': // multiview sequence
240 case 'hevs': // scalable sequence
241 $info['fileformat'] = 'heif';
242 $info['mime_type'] = 'image/heif';
243 break;
244 }
245 }
246 }
247
248 if (!$this->ReturnAtomData) {
249 unset($info['quicktime']['moov']);
250 }
251
252 if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) {
253 $info['audio']['dataformat'] = 'quicktime';
254 }
255 if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) {
256 $info['video']['dataformat'] = 'quicktime';
257 }
258 if (isset($info['video']) && ($info['mime_type'] == 'audio/mp4') && empty($info['video']['resolution_x']) && empty($info['video']['resolution_y'])) {
259 unset($info['video']);
260 }
261
262 return true;
263 }
264
265 /**
266 * @param string $atomname
267 * @param int $atomsize
268 * @param string $atom_data
269 * @param int $baseoffset
270 * @param array $atomHierarchy
271 * @param bool $ParseAllPossibleAtoms
272 *
273 * @return array|false
274 */
275 public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
276 // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm
277 // https://code.google.com/p/mp4v2/wiki/iTunesMetadata
278
279 $info = &$this->getid3->info;
280
281 $atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see https://www.getid3.org/phpBB3/viewtopic.php?t=1717
282 array_push($atomHierarchy, $atomname);
283 $atom_structure = array();
284 $atom_structure['hierarchy'] = implode(' ', $atomHierarchy);
285 $atom_structure['name'] = $atomname;
286 $atom_structure['size'] = $atomsize;
287 $atom_structure['offset'] = $baseoffset;
288 if (substr($atomname, 0, 3) == "\x00\x00\x00") {
289 // https://github.com/JamesHeinrich/getID3/issues/139
290 $atomname = getid3_lib::BigEndian2Int($atomname);
291 $atom_structure['name'] = $atomname;
292 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
293 } else {
294 switch ($atomname) {
295 case 'moov': // MOVie container atom
296 case 'moof': // MOvie Fragment box
297 case 'trak': // TRAcK container atom
298 case 'traf': // TRAck Fragment box
299 case 'clip': // CLIPping container atom
300 case 'matt': // track MATTe container atom
301 case 'edts': // EDiTS container atom
302 case 'tref': // Track REFerence container atom
303 case 'mdia': // MeDIA container atom
304 case 'minf': // Media INFormation container atom
305 case 'dinf': // Data INFormation container atom
306 case 'nmhd': // Null Media HeaDer container atom
307 case 'udta': // User DaTA container atom
308 case 'cmov': // Compressed MOVie container atom
309 case 'rmra': // Reference Movie Record Atom
310 case 'rmda': // Reference Movie Descriptor Atom
311 case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR)
312 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
313 break;
314
315 case 'ilst': // Item LiST container atom
316 if ($atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms)) {
317 // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted
318 $allnumericnames = true;
319 foreach ($atom_structure['subatoms'] as $subatomarray) {
320 if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) {
321 $allnumericnames = false;
322 break;
323 }
324 }
325 if ($allnumericnames) {
326 $newData = array();
327 foreach ($atom_structure['subatoms'] as $subatomarray) {
328 foreach ($subatomarray['subatoms'] as $newData_subatomarray) {
329 unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']);
330 $newData[$subatomarray['name']] = $newData_subatomarray;
331 break;
332 }
333 }
334 $atom_structure['data'] = $newData;
335 unset($atom_structure['subatoms']);
336 }
337 }
338 break;
339
340 case 'stbl': // Sample TaBLe container atom
341 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
342 $isVideo = false;
343 $framerate = 0;
344 $framecount = 0;
345 foreach ($atom_structure['subatoms'] as $key => $value_array) {
346 if (isset($value_array['sample_description_table'])) {
347 foreach ($value_array['sample_description_table'] as $key2 => $value_array2) {
348 if (isset($value_array2['data_format'])) {
349 switch ($value_array2['data_format']) {
350 case 'avc1':
351 case 'mp4v':
352 // video data
353 $isVideo = true;
354 break;
355 case 'mp4a':
356 // audio data
357 break;
358 }
359 }
360 }
361 } elseif (isset($value_array['time_to_sample_table'])) {
362 foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) {
363 if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0) && !empty($info['quicktime']['time_scale'])) {
364 $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3);
365 $framecount = $value_array2['sample_count'];
366 }
367 }
368 }
369 }
370 if ($isVideo && $framerate) {
371 $info['quicktime']['video']['frame_rate'] = $framerate;
372 $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate'];
373 }
374 if ($isVideo && $framecount) {
375 $info['quicktime']['video']['frame_count'] = $framecount;
376 }
377 break;
378
379
380 case "\xA9".'alb': // ALBum
381 case "\xA9".'ART': //
382 case "\xA9".'art': // ARTist
383 case "\xA9".'aut': //
384 case "\xA9".'cmt': // CoMmenT
385 case "\xA9".'com': // COMposer
386 case "\xA9".'cpy': //
387 case "\xA9".'day': // content created year
388 case "\xA9".'dir': //
389 case "\xA9".'ed1': //
390 case "\xA9".'ed2': //
391 case "\xA9".'ed3': //
392 case "\xA9".'ed4': //
393 case "\xA9".'ed5': //
394 case "\xA9".'ed6': //
395 case "\xA9".'ed7': //
396 case "\xA9".'ed8': //
397 case "\xA9".'ed9': //
398 case "\xA9".'enc': //
399 case "\xA9".'fmt': //
400 case "\xA9".'gen': // GENre
401 case "\xA9".'grp': // GRouPing
402 case "\xA9".'hst': //
403 case "\xA9".'inf': //
404 case "\xA9".'lyr': // LYRics
405 case "\xA9".'mak': //
406 case "\xA9".'mod': //
407 case "\xA9".'nam': // full NAMe
408 case "\xA9".'ope': //
409 case "\xA9".'PRD': //
410 case "\xA9".'prf': //
411 case "\xA9".'req': //
412 case "\xA9".'src': //
413 case "\xA9".'swr': //
414 case "\xA9".'too': // encoder
415 case "\xA9".'trk': // TRacK
416 case "\xA9".'url': //
417 case "\xA9".'wrn': //
418 case "\xA9".'wrt': // WRiTer
419 case '----': // itunes specific
420 case 'aART': // Album ARTist
421 case 'akID': // iTunes store account type
422 case 'apID': // Purchase Account
423 case 'atID': //
424 case 'catg': // CaTeGory
425 case 'cmID': //
426 case 'cnID': //
427 case 'covr': // COVeR artwork
428 case 'cpil': // ComPILation
429 case 'cprt': // CoPyRighT
430 case 'desc': // DESCription
431 case 'disk': // DISK number
432 case 'egid': // Episode Global ID
433 case 'geID': //
434 case 'gnre': // GeNRE
435 case 'hdvd': // HD ViDeo
436 case 'keyw': // KEYWord
437 case 'ldes': // Long DEScription
438 case 'pcst': // PodCaST
439 case 'pgap': // GAPless Playback
440 case 'plID': //
441 case 'purd': // PURchase Date
442 case 'purl': // Podcast URL
443 case 'rati': //
444 case 'rndu': //
445 case 'rpdu': //
446 case 'rtng': // RaTiNG
447 case 'sfID': // iTunes store country
448 case 'soaa': // SOrt Album Artist
449 case 'soal': // SOrt ALbum
450 case 'soar': // SOrt ARtist
451 case 'soco': // SOrt COmposer
452 case 'sonm': // SOrt NaMe
453 case 'sosn': // SOrt Show Name
454 case 'stik': //
455 case 'tmpo': // TeMPO (BPM)
456 case 'trkn': // TRacK Number
457 case 'tven': // tvEpisodeID
458 case 'tves': // TV EpiSode
459 case 'tvnn': // TV Network Name
460 case 'tvsh': // TV SHow Name
461 case 'tvsn': // TV SeasoN
462 if ($atom_parent == 'udta') {
463 // User data atom handler
464 $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
465 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2));
466 $atom_structure['data'] = substr($atom_data, 4);
467
468 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
469 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
470 $info['comments']['language'][] = $atom_structure['language'];
471 }
472 } else {
473 // Apple item list box atom handler
474 $atomoffset = 0;
475 if (substr($atom_data, 2, 2) == "\x10\xB5") {
476 // not sure what it means, but observed on iPhone4 data.
477 // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data
478 while ($atomoffset < strlen($atom_data)) {
479 $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2));
480 $boxsmalltype = substr($atom_data, $atomoffset + 2, 2);
481 $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize);
482 if ($boxsmallsize <= 1) {
483 $this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
484 $atom_structure['data'] = null;
485 $atomoffset = strlen($atom_data);
486 break;
487 }
488 switch ($boxsmalltype) {
489 case "\x10\xB5":
490 $atom_structure['data'] = $boxsmalldata;
491 break;
492 default:
493 $this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset);
494 $atom_structure['data'] = $atom_data;
495 break;
496 }
497 $atomoffset += (4 + $boxsmallsize);
498 }
499 } else {
500 while ($atomoffset < strlen($atom_data)) {
501 $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4));
502 $boxtype = substr($atom_data, $atomoffset + 4, 4);
503 $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8);
504 if ($boxsize <= 1) {
505 $this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
506 $atom_structure['data'] = null;
507 $atomoffset = strlen($atom_data);
508 break;
509 }
510 $atomoffset += $boxsize;
511
512 switch ($boxtype) {
513 case 'mean':
514 case 'name':
515 $atom_structure[$boxtype] = substr($boxdata, 4);
516 break;
517
518 case 'data':
519 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1));
520 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3));
521 switch ($atom_structure['flags_raw']) {
522 case 0: // data flag
523 case 21: // tmpo/cpil flag
524 switch ($atomname) {
525 case 'cpil':
526 case 'hdvd':
527 case 'pcst':
528 case 'pgap':
529 // 8-bit integer (boolean)
530 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
531 break;
532
533 case 'tmpo':
534 // 16-bit integer
535 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2));
536 break;
537
538 case 'disk':
539 case 'trkn':
540 // binary
541 $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2));
542 $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2));
543 $atom_structure['data'] = empty($num) ? '' : $num;
544 $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total;
545 break;
546
547 case 'gnre':
548 // enum
549 $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
550 $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1);
551 break;
552
553 case 'rtng':
554 // 8-bit integer
555 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
556 $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]);
557 break;
558
559 case 'stik':
560 // 8-bit integer (enum)
561 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
562 $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]);
563 break;
564
565 case 'sfID':
566 // 32-bit integer
567 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
568 $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]);
569 break;
570
571 case 'egid':
572 case 'purl':
573 $atom_structure['data'] = substr($boxdata, 8);
574 break;
575
576 case 'plID':
577 // 64-bit integer
578 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8));
579 break;
580
581 case 'covr':
582 $atom_structure['data'] = substr($boxdata, 8);
583 // not a foolproof check, but better than nothing
584 if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
585 $atom_structure['image_mime'] = 'image/jpeg';
586 } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
587 $atom_structure['image_mime'] = 'image/png';
588 } elseif (preg_match('#^GIF#', $atom_structure['data'])) {
589 $atom_structure['image_mime'] = 'image/gif';
590 }
591 $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_structure['data'], 'description'=>'cover');
592 break;
593
594 case 'atID':
595 case 'cnID':
596 case 'geID':
597 case 'tves':
598 case 'tvsn':
599 default:
600 // 32-bit integer
601 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
602 }
603 break;
604
605 case 1: // text flag
606 case 13: // image flag
607 default:
608 $atom_structure['data'] = substr($boxdata, 8);
609 if ($atomname == 'covr') {
610 if (!empty($atom_structure['data'])) {
611 $atom_structure['image_mime'] = 'image/unknown'; // provide default MIME type to ensure array keys exist
612 if (function_exists('getimagesizefromstring') && ($getimagesize = getimagesizefromstring($atom_structure['data'])) && !empty($getimagesize['mime'])) {
613 $atom_structure['image_mime'] = $getimagesize['mime'];
614 } else {
615 // if getimagesizefromstring is not available, or fails for some reason, fall back to simple detection of common image formats
616 $ImageFormatSignatures = array(
617 'image/jpeg' => "\xFF\xD8\xFF",
618 'image/png' => "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A",
619 'image/gif' => 'GIF',
620 );
621 foreach ($ImageFormatSignatures as $mime => $image_format_signature) {
622 if (substr($atom_structure['data'], 0, strlen($image_format_signature)) == $image_format_signature) {
623 $atom_structure['image_mime'] = $mime;
624 break;
625 }
626 }
627 }
628 $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_structure['data'], 'description'=>'cover');
629 } else {
630 $this->warning('Unknown empty "covr" image at offset '.$baseoffset);
631 }
632 }
633 break;
634
635 }
636 break;
637
638 default:
639 $this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset);
640 $atom_structure['data'] = $atom_data;
641
642 }
643 }
644 }
645 }
646 $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']);
647 break;
648
649
650 case 'play': // auto-PLAY atom
651 $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
652
653 $info['quicktime']['autoplay'] = $atom_structure['autoplay'];
654 break;
655
656
657 case 'WLOC': // Window LOCation atom
658 $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
659 $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2));
660 break;
661
662
663 case 'LOOP': // LOOPing atom
664 case 'SelO': // play SELection Only atom
665 case 'AllF': // play ALL Frames atom
666 $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data);
667 break;
668
669
670 case 'name': //
671 case 'MCPS': // Media Cleaner PRo
672 case '@PRM': // adobe PReMiere version
673 case '@PRQ': // adobe PRemiere Quicktime version
674 $atom_structure['data'] = $atom_data;
675 break;
676
677
678 case 'cmvd': // Compressed MooV Data atom
679 // Code by ubergeekØubergeek*tv based on information from
680 // http://developer.apple.com/quicktime/icefloe/dispatch012.html
681 $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
682
683 $CompressedFileData = substr($atom_data, 4);
684 if ($UncompressedHeader = @gzuncompress($CompressedFileData)) {
685 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms);
686 } else {
687 $this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']);
688 }
689 break;
690
691
692 case 'dcom': // Data COMpression atom
693 $atom_structure['compression_id'] = $atom_data;
694 $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data);
695 break;
696
697
698 case 'rdrf': // Reference movie Data ReFerence atom
699 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
700 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
701 $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001);
702
703 $atom_structure['reference_type_name'] = substr($atom_data, 4, 4);
704 $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
705 switch ($atom_structure['reference_type_name']) {
706 case 'url ':
707 $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12));
708 break;
709
710 case 'alis':
711 $atom_structure['file_alias'] = substr($atom_data, 12);
712 break;
713
714 case 'rsrc':
715 $atom_structure['resource_alias'] = substr($atom_data, 12);
716 break;
717
718 default:
719 $atom_structure['data'] = substr($atom_data, 12);
720 break;
721 }
722 break;
723
724
725 case 'rmqu': // Reference Movie QUality atom
726 $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data);
727 break;
728
729
730 case 'rmcs': // Reference Movie Cpu Speed atom
731 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
732 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
733 $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
734 break;
735
736
737 case 'rmvc': // Reference Movie Version Check atom
738 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
739 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
740 $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4);
741 $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
742 $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
743 $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2));
744 break;
745
746
747 case 'rmcd': // Reference Movie Component check atom
748 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
749 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
750 $atom_structure['component_type'] = substr($atom_data, 4, 4);
751 $atom_structure['component_subtype'] = substr($atom_data, 8, 4);
752 $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4);
753 $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
754 $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
755 $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4));
756 break;
757
758
759 case 'rmdr': // Reference Movie Data Rate atom
760 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
761 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
762 $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
763
764 $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10;
765 break;
766
767
768 case 'rmla': // Reference Movie Language Atom
769 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
770 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
771 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
772
773 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
774 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
775 $info['comments']['language'][] = $atom_structure['language'];
776 }
777 break;
778
779
780 case 'ptv ': // Print To Video - defines a movie's full screen mode
781 // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm
782 $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
783 $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000
784 $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000
785 $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1));
786 $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1));
787
788 $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag'];
789 $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag'];
790
791 $ptv_lookup = array(
792 0 => 'normal',
793 1 => 'double',
794 2 => 'half',
795 3 => 'full',
796 4 => 'current'
797 );
798 if (isset($ptv_lookup[$atom_structure['display_size_raw']])) {
799 $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
800 } else {
801 $this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')');
802 }
803 break;
804
805
806 case 'stsd': // Sample Table Sample Description atom
807 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); // hardcoded: 0x00
808 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x000000
809 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
810
811 // see: https://github.com/JamesHeinrich/getID3/issues/111
812 // Some corrupt files have been known to have high bits set in the number_entries field
813 // This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000
814 // Workaround: mask off the upper byte and throw a warning if it's nonzero
815 if ($atom_structure['number_entries'] > 0x000FFFFF) {
816 if ($atom_structure['number_entries'] > 0x00FFFFFF) {
817 $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));
818 $atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF);
819 } else {
820 $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');
821 }
822 }
823
824 $stsdEntriesDataOffset = 8;
825 for ($i = 0; $i < (int) $atom_structure['number_entries']; $i++) {
826 $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4));
827 $stsdEntriesDataOffset += 4;
828 $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4);
829 $stsdEntriesDataOffset += 4;
830 $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6));
831 $stsdEntriesDataOffset += 6;
832 $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2));
833 $stsdEntriesDataOffset += 2;
834 $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2));
835 $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2);
836 if (substr($atom_structure['sample_description_table'][$i]['data'], 1, 54) == 'application/octet-stream;type=com.parrot.videometadata') {
837 // special handling for apparently-malformed (TextMetaDataSampleEntry?) data for some version of Parrot drones
838 $atom_structure['sample_description_table'][$i]['parrot_frame_metadata']['mime_type'] = substr($atom_structure['sample_description_table'][$i]['data'], 1, 55);
839 $atom_structure['sample_description_table'][$i]['parrot_frame_metadata']['metadata_version'] = (int) substr($atom_structure['sample_description_table'][$i]['data'], 55, 1);
840 unset($atom_structure['sample_description_table'][$i]['data']);
841$this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in this version of getID3() ['.$this->getid3->version().']');
842 continue;
843 }
844
845 $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2));
846 $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2));
847 $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4);
848
849 switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) {
850
851 case "\x00\x00\x00\x00":
852 // audio tracks
853 $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2));
854 $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2));
855 $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2));
856 $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2));
857 $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4));
858
859 // video tracks
860 // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html
861 // https://developer.apple.com/documentation/quicktime-file-format
862 $STSDvOffset = 8;
863 $atom_structure['sample_description_table'][$i]['temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 4)); $STSDvOffset += 4;
864 $atom_structure['sample_description_table'][$i]['spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 4)); $STSDvOffset += 4;
865 $atom_structure['sample_description_table'][$i]['width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 2)); $STSDvOffset += 2;
866 $atom_structure['sample_description_table'][$i]['height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 2)); $STSDvOffset += 2;
867 $atom_structure['sample_description_table'][$i]['resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 4)); $STSDvOffset += 4;
868 $atom_structure['sample_description_table'][$i]['resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 4)); $STSDvOffset += 4;
869 $atom_structure['sample_description_table'][$i]['data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 4)); $STSDvOffset += 4;
870 $atom_structure['sample_description_table'][$i]['frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 2)); $STSDvOffset += 2;
871 $atom_structure['sample_description_table'][$i]['compressor_name'] = substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 32) ; $STSDvOffset += 32;
872 $atom_structure['sample_description_table'][$i]['compressor_name'] = $this->MaybePascal2String(rtrim($atom_structure['sample_description_table'][$i]['compressor_name'], "\x00")); // https://github.com/JamesHeinrich/getID3/issues/452
873 $atom_structure['sample_description_table'][$i]['pixel_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 2)); $STSDvOffset += 2;
874 $atom_structure['sample_description_table'][$i]['color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 2)); $STSDvOffset += 2;
875
876 switch ($atom_structure['sample_description_table'][$i]['data_format']) {
877 case '2vuY':
878 case 'avc1':
879 case 'cvid':
880 case 'dvc ':
881 case 'dvcp':
882 case 'gif ':
883 case 'h263':
884 case 'hvc1':
885 case 'jpeg':
886 case 'kpcd':
887 case 'mjpa':
888 case 'mjpb':
889 case 'mp4v':
890 case 'png ':
891 case 'raw ':
892 case 'rle ':
893 case 'rpza':
894 case 'smc ':
895 case 'SVQ1':
896 case 'SVQ3':
897 case 'tiff':
898 case 'v210':
899 case 'v216':
900 case 'v308':
901 case 'v408':
902 case 'v410':
903 case 'yuv2':
904 $info['fileformat'] = 'mp4';
905 $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format'];
906 if ($this->QuicktimeVideoCodecLookup($info['video']['fourcc'])) {
907 $info['video']['fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($info['video']['fourcc']);
908 }
909
910 // https://www.getid3.org/phpBB3/viewtopic.php?t=1550
911 //if ((!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['width'])) && (empty($info['video']['resolution_x']) || empty($info['video']['resolution_y']) || (number_format($info['video']['resolution_x'], 6) != number_format(round($info['video']['resolution_x']), 6)) || (number_format($info['video']['resolution_y'], 6) != number_format(round($info['video']['resolution_y']), 6)))) { // ugly check for floating point numbers
912 if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) {
913 // assume that values stored here are more important than values stored in [tkhd] atom
914 $info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width'];
915 $info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height'];
916 $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x'];
917 $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y'];
918 }
919 break;
920
921 case 'qtvr':
922 $info['video']['dataformat'] = 'quicktimevr';
923 break;
924
925 case 'mp4a':
926 $atom_structure['sample_description_table'][$i]['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_structure['sample_description_table'][$i]['data'], 20), $baseoffset + $stsdEntriesDataOffset - 20 - 16, $atomHierarchy, $ParseAllPossibleAtoms);
927
928 $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
929 $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate'];
930 $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels'];
931 $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth'];
932 $info['audio']['codec'] = $info['quicktime']['audio']['codec'];
933 $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate'];
934 $info['audio']['channels'] = $info['quicktime']['audio']['channels'];
935 $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth'];
936 switch ($atom_structure['sample_description_table'][$i]['data_format']) {
937 case 'raw ': // PCM
938 case 'alac': // Apple Lossless Audio Codec
939 case 'sowt': // signed/two's complement (Little Endian)
940 case 'twos': // signed/two's complement (Big Endian)
941 case 'in24': // 24-bit Integer
942 case 'in32': // 32-bit Integer
943 case 'fl32': // 32-bit Floating Point
944 case 'fl64': // 64-bit Floating Point
945 $info['audio']['lossless'] = $info['quicktime']['audio']['lossless'] = true;
946 $info['audio']['bitrate'] = $info['quicktime']['audio']['bitrate'] = $info['audio']['channels'] * $info['audio']['bits_per_sample'] * $info['audio']['sample_rate'];
947 break;
948 default:
949 $info['audio']['lossless'] = false;
950 break;
951 }
952 break;
953
954 default:
955 break;
956 }
957 break;
958
959 default:
960 switch ($atom_structure['sample_description_table'][$i]['data_format']) {
961 case 'mp4s':
962 $info['fileformat'] = 'mp4';
963 break;
964
965 default:
966 // video atom
967 $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4));
968 $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4));
969 $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2));
970 $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2));
971 $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4));
972 $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4));
973 $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4));
974 $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2));
975 $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1));
976 $atom_structure['sample_description_table'][$i]['video_encoder_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 35, $atom_structure['sample_description_table'][$i]['video_encoder_name_len']);
977 $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2));
978 $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2));
979
980 $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (((int) $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color');
981 $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']);
982
983 if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') {
984 $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format'];
985 $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
986 $info['quicktime']['video']['codec'] = (((int) $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']);
987 $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'];
988 $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name'];
989
990 $info['video']['codec'] = $info['quicktime']['video']['codec'];
991 $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth'];
992 }
993 $info['video']['lossless'] = false;
994 $info['video']['pixel_aspect_ratio'] = (float) 1;
995 break;
996 }
997 break;
998 }
999 switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) {
1000 case 'mp4a':
1001 $info['audio']['dataformat'] = 'mp4';
1002 $info['quicktime']['audio']['codec'] = 'mp4';
1003 break;
1004
1005 case '3ivx':
1006 case '3iv1':
1007 case '3iv2':
1008 $info['video']['dataformat'] = '3ivx';
1009 break;
1010
1011 case 'xvid':
1012 $info['video']['dataformat'] = 'xvid';
1013 break;
1014
1015 case 'mp4v':
1016 $info['video']['dataformat'] = 'mpeg4';
1017 break;
1018
1019 case 'divx':
1020 case 'div1':
1021 case 'div2':
1022 case 'div3':
1023 case 'div4':
1024 case 'div5':
1025 case 'div6':
1026 $info['video']['dataformat'] = 'divx';
1027 break;
1028
1029 default:
1030 // do nothing
1031 break;
1032 }
1033 unset($atom_structure['sample_description_table'][$i]['data']);
1034 }
1035 break;
1036
1037
1038 case 'stts': // Sample Table Time-to-Sample atom
1039 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1040 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1041 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1042 $sttsEntriesDataOffset = 8;
1043 //$FrameRateCalculatorArray = array();
1044 $frames_count = 0;
1045
1046 $max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']);
1047 if ($max_stts_entries_to_scan < $atom_structure['number_entries']) {
1048 $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($this->getid3->memory_limit / 1048576).'MB).');
1049 }
1050 for ($i = 0; $i < $max_stts_entries_to_scan; $i++) {
1051 $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
1052 $sttsEntriesDataOffset += 4;
1053 $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
1054 $sttsEntriesDataOffset += 4;
1055
1056 $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count'];
1057
1058 // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM
1059 //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) {
1060 // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'];
1061 // if ($stts_new_framerate <= 60) {
1062 // // some atoms have durations of "1" giving a very large framerate, which probably is not right
1063 // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate);
1064 // }
1065 //}
1066 //
1067 //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count'];
1068 }
1069 $info['quicktime']['stts_framecount'][] = $frames_count;
1070 //$sttsFramesTotal = 0;
1071 //$sttsSecondsTotal = 0;
1072 //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) {
1073 // if (($frames_per_second > 60) || ($frames_per_second < 1)) {
1074 // // not video FPS information, probably audio information
1075 // $sttsFramesTotal = 0;
1076 // $sttsSecondsTotal = 0;
1077 // break;
1078 // }
1079 // $sttsFramesTotal += $frame_count;
1080 // $sttsSecondsTotal += $frame_count / $frames_per_second;
1081 //}
1082 //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) {
1083 // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) {
1084 // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal;
1085 // }
1086 //}
1087 break;
1088
1089
1090 case 'stss': // Sample Table Sync Sample (key frames) atom
1091 if ($ParseAllPossibleAtoms) {
1092 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1093 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1094 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1095 $stssEntriesDataOffset = 8;
1096 for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
1097 $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4));
1098 $stssEntriesDataOffset += 4;
1099 }
1100 }
1101 break;
1102
1103
1104 case 'stsc': // Sample Table Sample-to-Chunk atom
1105 if ($ParseAllPossibleAtoms) {
1106 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1107 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1108 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1109 $stscEntriesDataOffset = 8;
1110 for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
1111 $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
1112 $stscEntriesDataOffset += 4;
1113 $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
1114 $stscEntriesDataOffset += 4;
1115 $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
1116 $stscEntriesDataOffset += 4;
1117 }
1118 }
1119 break;
1120
1121
1122 case 'stsz': // Sample Table SiZe atom
1123 if ($ParseAllPossibleAtoms) {
1124 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1125 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1126 $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1127 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
1128 $stszEntriesDataOffset = 12;
1129 if ($atom_structure['sample_size'] == 0) {
1130 for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
1131 $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4));
1132 $stszEntriesDataOffset += 4;
1133 }
1134 }
1135 }
1136 break;
1137
1138
1139 case 'stco': // Sample Table Chunk Offset atom
1140// if (true) {
1141 if ($ParseAllPossibleAtoms) {
1142 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1143 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1144 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1145 $stcoEntriesDataOffset = 8;
1146 for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
1147 $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4));
1148 $stcoEntriesDataOffset += 4;
1149 }
1150 }
1151 break;
1152
1153
1154 case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files)
1155 if ($ParseAllPossibleAtoms) {
1156 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1157 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1158 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1159 $stcoEntriesDataOffset = 8;
1160 for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
1161 $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8));
1162 $stcoEntriesDataOffset += 8;
1163 }
1164 }
1165 break;
1166
1167
1168 case 'dref': // Data REFerence atom
1169 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1170 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1171 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1172 $drefDataOffset = 8;
1173 for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
1174 $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4));
1175 $drefDataOffset += 4;
1176 $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4);
1177 $drefDataOffset += 4;
1178 $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1));
1179 $drefDataOffset += 1;
1180 $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000
1181 $drefDataOffset += 3;
1182 $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));
1183 $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3);
1184
1185 $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001);
1186 }
1187 break;
1188
1189
1190 case 'gmin': // base Media INformation atom
1191 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1192 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1193 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
1194 $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2));
1195 $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2));
1196 $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2));
1197 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2));
1198 $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2));
1199 break;
1200
1201
1202 case 'smhd': // Sound Media information HeaDer atom
1203 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1204 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1205 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
1206 $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2));
1207 break;
1208
1209
1210 case 'vmhd': // Video Media information HeaDer atom
1211 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1212 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
1213 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
1214 $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2));
1215 $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2));
1216 $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2));
1217
1218 $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001);
1219 break;
1220
1221
1222 case 'hdlr': // HanDLeR reference atom
1223 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1224 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1225 $atom_structure['component_type'] = substr($atom_data, 4, 4);
1226 $atom_structure['component_subtype'] = substr($atom_data, 8, 4);
1227 $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4);
1228 $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
1229 $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
1230 $atom_structure['component_name'] = $this->MaybePascal2String(substr($atom_data, 24));
1231
1232 if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) {
1233 $info['video']['dataformat'] = 'quicktimevr';
1234 }
1235 break;
1236
1237
1238 case 'mdhd': // MeDia HeaDer atom
1239 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1240 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1241 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1242 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
1243 $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
1244 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
1245 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2));
1246 $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2));
1247
1248 if ($atom_structure['time_scale'] == 0) {
1249 $this->error('Corrupt Quicktime file: mdhd.time_scale == zero');
1250 return false;
1251 }
1252 $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']);
1253
1254 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
1255 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
1256 $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale'];
1257 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
1258 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
1259 $info['comments']['language'][] = $atom_structure['language'];
1260 }
1261 $info['quicktime']['timestamps_unix']['create'][$atom_structure['hierarchy']] = $atom_structure['creation_time_unix'];
1262 $info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modify_time_unix'];
1263 break;
1264
1265
1266 case 'pnot': // Preview atom
1267 $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format"
1268 $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00
1269 $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT'
1270 $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01
1271
1272 $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']);
1273 $info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modification_date_unix'];
1274 break;
1275
1276
1277 case 'crgn': // Clipping ReGioN atom
1278 $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box,
1279 $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields
1280 $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region.
1281 break;
1282
1283
1284 case 'load': // track LOAD settings atom
1285 $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
1286 $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1287 $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
1288 $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
1289
1290 $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020);
1291 $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100);
1292 break;
1293
1294
1295 case 'tmcd': // TiMe CoDe atom
1296 case 'chap': // CHAPter list atom
1297 case 'sync': // SYNChronization atom
1298 case 'scpt': // tranSCriPT atom
1299 case 'ssrc': // non-primary SouRCe atom
1300 for ($i = 0; $i < strlen($atom_data); $i += 4) {
1301 @$atom_structure['track_id'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4));
1302 }
1303 break;
1304
1305
1306 case 'elst': // Edit LiST atom
1307 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1308 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1309 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1310 for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) {
1311 $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4));
1312 $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4));
1313 $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4));
1314 }
1315 break;
1316
1317
1318 case 'kmat': // compressed MATte atom
1319 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1320 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1321 $atom_structure['matte_data_raw'] = substr($atom_data, 4);
1322 break;
1323
1324
1325 case 'ctab': // Color TABle atom
1326 $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000
1327 $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000
1328 $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1;
1329 for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) {
1330 $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2));
1331 $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2));
1332 $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2));
1333 $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2));
1334 }
1335 break;
1336
1337
1338 case 'mvhd': // MoVie HeaDer atom
1339 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1340 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
1341 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1342 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
1343 $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
1344 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
1345 $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4));
1346 $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2));
1347 $atom_structure['reserved'] = substr($atom_data, 26, 10);
1348 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4));
1349 $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4));
1350 $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4));
1351 $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4));
1352 $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4));
1353 $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4));
1354 $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4));
1355 $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4));
1356 $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4));
1357 $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4));
1358 $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4));
1359 $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4));
1360 $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4));
1361 $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4));
1362 $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4));
1363 $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4));
1364
1365 if ($atom_structure['time_scale'] == 0) {
1366 $this->error('Corrupt Quicktime file: mvhd.time_scale == zero');
1367 return false;
1368 }
1369 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
1370 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
1371 $info['quicktime']['timestamps_unix']['create'][$atom_structure['hierarchy']] = $atom_structure['creation_time_unix'];
1372 $info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modify_time_unix'];
1373 $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']);
1374 $info['quicktime']['display_scale'] = $atom_structure['matrix_a'];
1375 $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale'];
1376 break;
1377
1378
1379 case 'tkhd': // TracK HeaDer atom
1380 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1381 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
1382 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1383 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
1384 $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
1385 $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
1386 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
1387 $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8));
1388 $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2));
1389 $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2));
1390 $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2));
1391 $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2));
1392 // http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html
1393 // http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737
1394 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4));
1395 $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4));
1396 $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4));
1397 $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4));
1398 $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4));
1399 $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4));
1400 $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4));
1401 $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4));
1402 $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4));
1403 $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4));
1404 $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4));
1405 $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001);
1406 $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002);
1407 $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004);
1408 $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008);
1409 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
1410 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
1411 $info['quicktime']['timestamps_unix']['create'][$atom_structure['hierarchy']] = $atom_structure['creation_time_unix'];
1412 $info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modify_time_unix'];
1413
1414 // https://www.getid3.org/phpBB3/viewtopic.php?t=1908
1415 // attempt to compute rotation from matrix values
1416 // 2017-Dec-28: uncertain if 90/270 are correctly oriented; values returned by FixedPoint16_16 should perhaps be -1 instead of 65535(?)
1417 $matrixRotation = 0;
1418 switch ($atom_structure['matrix_a'].':'.$atom_structure['matrix_b'].':'.$atom_structure['matrix_c'].':'.$atom_structure['matrix_d']) {
1419 case '1:0:0:1': $matrixRotation = 0; break;
1420 case '0:1:65535:0': $matrixRotation = 90; break;
1421 case '65535:0:0:65535': $matrixRotation = 180; break;
1422 case '0:65535:1:0': $matrixRotation = 270; break;
1423 default: break;
1424 }
1425
1426 // https://www.getid3.org/phpBB3/viewtopic.php?t=2468
1427 // The rotation matrix can appear in the Quicktime file multiple times, at least once for each track,
1428 // and it's possible that only the video track (or, in theory, one of the video tracks) is flagged as
1429 // rotated while the other tracks (e.g. audio) is tagged as rotation=0 (behavior noted on iPhone 8 Plus)
1430 // The correct solution would be to check if the TrackID associated with the rotation matrix is indeed
1431 // a video track (or the main video track) and only set the rotation then, but since information about
1432 // what track is what is not trivially there to be examined, the lazy solution is to set the rotation
1433 // if it is found to be nonzero, on the assumption that tracks that don't need it will have rotation set
1434 // to zero (and be effectively ignored) and the video track will have rotation set correctly, which will
1435 // either be zero and automatically correct, or nonzero and be set correctly.
1436 if (!isset($info['video']['rotate']) || (($info['video']['rotate'] == 0) && ($matrixRotation > 0))) {
1437 $info['quicktime']['video']['rotate'] = $info['video']['rotate'] = $matrixRotation;
1438 }
1439
1440 if ($atom_structure['flags']['enabled'] == 1) {
1441 if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) {
1442 $info['video']['resolution_x'] = $atom_structure['width'];
1443 $info['video']['resolution_y'] = $atom_structure['height'];
1444 }
1445 $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']);
1446 $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']);
1447 $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x'];
1448 $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y'];
1449 } else {
1450 // see: https://www.getid3.org/phpBB3/viewtopic.php?t=1295
1451 //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); }
1452 //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); }
1453 //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); }
1454 }
1455 break;
1456
1457
1458 case 'iods': // Initial Object DeScriptor atom
1459 // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h
1460 // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html
1461 $offset = 0;
1462 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1463 $offset += 1;
1464 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3));
1465 $offset += 3;
1466 $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1467 $offset += 1;
1468 $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset);
1469 //$offset already adjusted by quicktime_read_mp4_descr_length()
1470 $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2));
1471 $offset += 2;
1472 $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1473 $offset += 1;
1474 $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1475 $offset += 1;
1476 $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1477 $offset += 1;
1478 $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1479 $offset += 1;
1480 $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1481 $offset += 1;
1482
1483 $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields
1484 for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) {
1485 $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1486 $offset += 1;
1487 $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset);
1488 //$offset already adjusted by quicktime_read_mp4_descr_length()
1489 $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4));
1490 $offset += 4;
1491 }
1492
1493 $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']);
1494 $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']);
1495 break;
1496
1497 case 'ftyp': // FileTYPe (?) atom (for MP4 it seems)
1498 $atom_structure['signature'] = substr($atom_data, 0, 4);
1499 $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1500 $atom_structure['fourcc'] = substr($atom_data, 8, 4);
1501 break;
1502
1503 case 'mdat': // Media DATa atom
1504 // 'mdat' contains the actual data for the audio/video, possibly also subtitles
1505
1506 /* due to lack of known documentation, this is a kludge implementation. If you know of documentation on how mdat is properly structed, please send it to info@getid3.org */
1507
1508 // first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?)
1509 $mdat_offset = 0;
1510 while (true) {
1511 if (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x08".'wide') {
1512 $mdat_offset += 8;
1513 } elseif (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x00".'mdat') {
1514 $mdat_offset += 8;
1515 } else {
1516 break;
1517 }
1518 }
1519 if (substr($atom_data, $mdat_offset, 4) == 'GPRO') {
1520 $GOPRO_chunk_length = getid3_lib::LittleEndian2Int(substr($atom_data, $mdat_offset + 4, 4));
1521 $GOPRO_offset = 8;
1522 $atom_structure['GPRO']['raw'] = substr($atom_data, $mdat_offset + 8, $GOPRO_chunk_length - 8);
1523 $atom_structure['GPRO']['firmware'] = substr($atom_structure['GPRO']['raw'], 0, 15);
1524 $atom_structure['GPRO']['unknown1'] = substr($atom_structure['GPRO']['raw'], 15, 16);
1525 $atom_structure['GPRO']['unknown2'] = substr($atom_structure['GPRO']['raw'], 31, 32);
1526 $atom_structure['GPRO']['unknown3'] = substr($atom_structure['GPRO']['raw'], 63, 16);
1527 $atom_structure['GPRO']['camera'] = substr($atom_structure['GPRO']['raw'], 79, 32);
1528 $info['quicktime']['camera']['model'] = rtrim($atom_structure['GPRO']['camera'], "\x00");
1529 }
1530
1531 // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field
1532 while (($mdat_offset < (strlen($atom_data) - 8))
1533 && ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2)))
1534 && ($chapter_string_length < 1000)
1535 && ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2))
1536 && preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) {
1537 list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches;
1538 $mdat_offset += (2 + $chapter_string_length);
1539 @$info['quicktime']['comments']['chapters'][] = $chapter_string;
1540
1541 // "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled)
1542 if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8
1543 $mdat_offset += 12;
1544 }
1545 }
1546
1547 if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) {
1548
1549 $info['avdataoffset'] = $atom_structure['offset'] + 8; // $info['quicktime'][$atomname]['offset'] + 8;
1550 $OldAVDataEnd = $info['avdataend'];
1551 $info['avdataend'] = $atom_structure['offset'] + $atom_structure['size']; // $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size'];
1552
1553 $getid3_temp = new getID3();
1554 $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
1555 $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
1556 $getid3_temp->info['avdataend'] = $info['avdataend'];
1557 $getid3_mp3 = new getid3_mp3($getid3_temp);
1558 if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) {
1559 $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false);
1560 if (!empty($getid3_temp->info['warning'])) {
1561 foreach ($getid3_temp->info['warning'] as $value) {
1562 $this->warning($value);
1563 }
1564 }
1565 if (!empty($getid3_temp->info['mpeg'])) {
1566 $info['mpeg'] = $getid3_temp->info['mpeg'];
1567 if (isset($info['mpeg']['audio'])) {
1568 $info['audio']['dataformat'] = 'mp3';
1569 $info['audio']['codec'] = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3')));
1570 $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate'];
1571 $info['audio']['channels'] = $info['mpeg']['audio']['channels'];
1572 $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];
1573 $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
1574 $info['bitrate'] = $info['audio']['bitrate'];
1575 }
1576 }
1577 }
1578 unset($getid3_mp3, $getid3_temp);
1579 $info['avdataend'] = $OldAVDataEnd;
1580 unset($OldAVDataEnd);
1581
1582 }
1583
1584 unset($mdat_offset, $chapter_string_length, $chapter_matches);
1585 break;
1586
1587 case 'ID32': // ID3v2
1588 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
1589
1590 $getid3_temp = new getID3();
1591 $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
1592 $getid3_id3v2 = new getid3_id3v2($getid3_temp);
1593 $getid3_id3v2->StartingOffset = $atom_structure['offset'] + 14; // framelength(4)+framename(4)+flags(4)+??(2)
1594 if ($atom_structure['valid'] = $getid3_id3v2->Analyze()) {
1595 $atom_structure['id3v2'] = $getid3_temp->info['id3v2'];
1596 } else {
1597 $this->warning('ID32 frame at offset '.$atom_structure['offset'].' did not parse');
1598 }
1599 unset($getid3_temp, $getid3_id3v2);
1600 break;
1601
1602 case 'free': // FREE space atom
1603 case 'skip': // SKIP atom
1604 case 'wide': // 64-bit expansion placeholder atom
1605 // 'free', 'skip' and 'wide' are just padding, contains no useful data at all
1606
1607 // When writing QuickTime files, it is sometimes necessary to update an atom's size.
1608 // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom
1609 // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime
1610 // puts an 8-byte placeholder atom before any atoms it may have to update the size of.
1611 // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the
1612 // placeholder atom can be overwritten to obtain the necessary 8 extra bytes.
1613 // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ).
1614 break;
1615
1616
1617 case 'nsav': // NoSAVe atom
1618 // http://developer.apple.com/technotes/tn/tn2038.html
1619 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
1620 break;
1621
1622 case 'ctyp': // Controller TYPe atom (seen on QTVR)
1623 // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt
1624 // some controller names are:
1625 // 0x00 + 'std' for linear movie
1626 // 'none' for no controls
1627 $atom_structure['ctyp'] = substr($atom_data, 0, 4);
1628 $info['quicktime']['controller'] = $atom_structure['ctyp'];
1629 switch ($atom_structure['ctyp']) {
1630 case 'qtvr':
1631 $info['video']['dataformat'] = 'quicktimevr';
1632 break;
1633 }
1634 break;
1635
1636 case 'pano': // PANOrama track (seen on QTVR)
1637 $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
1638 break;
1639
1640 case 'hint': // HINT track
1641 case 'hinf': //
1642 case 'hinv': //
1643 case 'hnti': //
1644 $info['quicktime']['hinting'] = true;
1645 break;
1646
1647 case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR)
1648 for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) {
1649 $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4));
1650 }
1651 break;
1652
1653
1654 // Observed-but-not-handled atom types are just listed here to prevent warnings being generated
1655 case 'FXTC': // Something to do with Adobe After Effects (?)
1656 case 'PrmA':
1657 case 'code':
1658 case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html
1659 case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html
1660 // tapt seems to be used to compute the video size [https://www.getid3.org/phpBB3/viewtopic.php?t=838]
1661 // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html
1662 // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html
1663 case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
1664 case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
1665 case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
1666 case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
1667 //$atom_structure['data'] = $atom_data;
1668 break;
1669
1670 case "\xA9".'xyz': // GPS latitude+longitude+altitude
1671 $atom_structure['data'] = $atom_data;
1672 if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) {
1673 @list($all, $latitude, $longitude, $altitude) = $matches;
1674 $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude);
1675 $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude);
1676 if (!empty($altitude)) { // @phpstan-ignore-line
1677 $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude);
1678 }
1679 } else {
1680 $this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.');
1681 }
1682 break;
1683
1684 case 'NCDT':
1685 // https://exiftool.org/TagNames/Nikon.html
1686 // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
1687 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms);
1688 break;
1689 case 'NCTH': // Nikon Camera THumbnail image
1690 case 'NCVW': // Nikon Camera preVieW image
1691 case 'NCM1': // Nikon Camera preview iMage 1
1692 case 'NCM2': // Nikon Camera preview iMage 2
1693 // https://exiftool.org/TagNames/Nikon.html
1694 if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) {
1695 $descriptions = array(
1696 'NCTH' => 'Nikon Camera Thumbnail Image',
1697 'NCVW' => 'Nikon Camera Preview Image',
1698 'NCM1' => 'Nikon Camera Preview Image 1',
1699 'NCM2' => 'Nikon Camera Preview Image 2',
1700 );
1701 $atom_structure['data'] = $atom_data;
1702 $atom_structure['image_mime'] = 'image/jpeg';
1703 $atom_structure['description'] = $descriptions[$atomname];
1704 $info['quicktime']['comments']['picture'][] = array(
1705 'image_mime' => $atom_structure['image_mime'],
1706 'data' => $atom_data,
1707 'description' => $atom_structure['description']
1708 );
1709 }
1710 break;
1711 case 'NCTG': // Nikon - https://exiftool.org/TagNames/Nikon.html#NCTG
1712 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.nikon-nctg.php', __FILE__, true);
1713 $nikonNCTG = new getid3_tag_nikon_nctg($this->getid3);
1714
1715 $atom_structure['data'] = $nikonNCTG->parse($atom_data);
1716 break;
1717 case 'NCHD': // Nikon:MakerNoteVersion - https://exiftool.org/TagNames/Nikon.html
1718 $makerNoteVersion = '';
1719 for ($i = 0, $iMax = strlen($atom_data); $i < $iMax; ++$i) {
1720 if (ord($atom_data[$i]) <= 0x1F) {
1721 $makerNoteVersion .= ' '.ord($atom_data[$i]);
1722 } else {
1723 $makerNoteVersion .= $atom_data[$i];
1724 }
1725 }
1726 $makerNoteVersion = rtrim($makerNoteVersion, "\x00");
1727 $atom_structure['data'] = array(
1728 'MakerNoteVersion' => $makerNoteVersion
1729 );
1730 break;
1731 case 'NCDB': // Nikon - https://exiftool.org/TagNames/Nikon.html
1732 case 'CNCV': // Canon:CompressorVersion - https://exiftool.org/TagNames/Canon.html
1733 $atom_structure['data'] = $atom_data;
1734 break;
1735
1736 case "\x00\x00\x00\x00":
1737 // some kind of metacontainer, may contain a big data dump such as:
1738 // 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
1739 // https://xhelmboyx.tripod.com/formats/qti-layout.txt
1740
1741 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1742 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
1743 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
1744 //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
1745 break;
1746
1747 case 'meta': // METAdata atom
1748 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html
1749
1750 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1751 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
1752 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
1753 break;
1754
1755 case 'data': // metaDATA atom
1756 // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data
1757 $atom_structure['language'] = substr($atom_data, 4 + 0, 2);
1758 $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2));
1759 $atom_structure['data'] = substr($atom_data, 4 + 4);
1760 $atom_structure['key_name'] = (isset($info['quicktime']['temp_meta_key_names'][$this->metaDATAkey]) ? $info['quicktime']['temp_meta_key_names'][$this->metaDATAkey] : '');
1761 $this->metaDATAkey++;
1762
1763 switch ($atom_structure['key_name']) {
1764 case 'com.android.capture.fps':
1765 $atom_structure['data'] = getid3_lib::BigEndian2Float($atom_structure['data']);
1766 break;
1767 }
1768
1769 if ($atom_structure['key_name'] && $atom_structure['data']) {
1770 @$info['quicktime']['comments'][str_replace('com.android.', '', str_replace('com.apple.quicktime.', '', $atom_structure['key_name']))][] = $atom_structure['data'];
1771 }
1772 break;
1773
1774 case 'keys': // KEYS that may be present in the metadata atom.
1775 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21
1776 // The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom.
1777 // 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".
1778 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1779 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
1780 $atom_structure['entry_count'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1781 $keys_atom_offset = 8;
1782 for ($i = 1; $i <= $atom_structure['entry_count']; $i++) {
1783 $atom_structure['keys'][$i]['key_size'] = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4));
1784 $atom_structure['keys'][$i]['key_namespace'] = substr($atom_data, $keys_atom_offset + 4, 4);
1785 $atom_structure['keys'][$i]['key_value'] = substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8);
1786 $keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace
1787
1788 $info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value'];
1789 }
1790 break;
1791
1792 case 'uuid': // user-defined atom often seen containing XML data, also used for potentially many other purposes, only a few specifically handled by getID3 (e.g. 360fly spatial data)
1793 //Get the UUID ID in first 16 bytes
1794 $uuid_bytes_read = unpack('H8time_low/H4time_mid/H4time_hi/H4clock_seq_hi/H12clock_seq_low', substr($atom_data, 0, 16));
1795 $atom_structure['uuid_field_id'] = implode('-', $uuid_bytes_read);
1796
1797 switch ($atom_structure['uuid_field_id']) { // http://fileformats.archiveteam.org/wiki/Boxes/atoms_format#UUID_boxes
1798
1799 case '0537cdab-9d0c-4431-a72a-fa561f2a113e': // Exif - http://fileformats.archiveteam.org/wiki/Exif
1800 case '2c4c0100-8504-40b9-a03e-562148d6dfeb': // Photoshop Image Resources - http://fileformats.archiveteam.org/wiki/Photoshop_Image_Resources
1801 case '33c7a4d2-b81d-4723-a0ba-f1a3e097ad38': // IPTC-IIM - http://fileformats.archiveteam.org/wiki/IPTC-IIM
1802 case '8974dbce-7be7-4c51-84f9-7148f9882554': // PIFF Track Encryption Box - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format
1803 case '96a9f1f1-dc98-402d-a7ae-d68e34451809': // GeoJP2 World File Box - http://fileformats.archiveteam.org/wiki/GeoJP2
1804 case 'a2394f52-5a9b-4f14-a244-6c427c648df4': // PIFF Sample Encryption Box - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format
1805 case 'b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03': // GeoJP2 GeoTIFF Box - http://fileformats.archiveteam.org/wiki/GeoJP2
1806 case 'd08a4f18-10f3-4a82-b6c8-32d8aba183d3': // PIFF Protection System Specific Header Box - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format
1807 $this->warning('Unhandled (but recognized) "uuid" atom identified by "'.$atom_structure['uuid_field_id'].'" at offset '.$atom_structure['offset'].' ('.strlen($atom_data).' bytes)');
1808 break;
1809
1810 case 'be7acfcb-97a9-42e8-9c71-999491e3afac': // XMP data (in XML format)
1811 $atom_structure['xml'] = substr($atom_data, 16, strlen($atom_data) - 16 - 8); // 16 bytes for UUID, 8 bytes header(?)
1812 break;
1813
1814 case 'efe1589a-bb77-49ef-8095-27759eb1dc6f': // 360fly data
1815 /* 360fly code in this block by Paul Lewis 2019-Oct-31 */
1816 /* Sensor Timestamps need to be calculated using the recordings base time at ['quicktime']['moov']['subatoms'][0]['creation_time_unix']. */
1817 $atom_structure['title'] = '360Fly Sensor Data';
1818
1819 //Get the UUID HEADER data
1820 $uuid_bytes_read = unpack('vheader_size/vheader_version/vtimescale/vhardware_version/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/', substr($atom_data, 16, 32));
1821 $atom_structure['uuid_header'] = $uuid_bytes_read;
1822
1823 $start_byte = 48;
1824 $atom_SENSOR_data = substr($atom_data, $start_byte);
1825 $atom_structure['sensor_data']['data_type'] = array(
1826 'fusion_count' => 0, // ID 250
1827 'fusion_data' => array(),
1828 'accel_count' => 0, // ID 1
1829 'accel_data' => array(),
1830 'gyro_count' => 0, // ID 2
1831 'gyro_data' => array(),
1832 'magno_count' => 0, // ID 3
1833 'magno_data' => array(),
1834 'gps_count' => 0, // ID 5
1835 'gps_data' => array(),
1836 'rotation_count' => 0, // ID 6
1837 'rotation_data' => array(),
1838 'unknown_count' => 0, // ID ??
1839 'unknown_data' => array(),
1840 'debug_list' => '', // Used to debug variables stored as comma delimited strings
1841 );
1842 $debug_structure = array();
1843 $debug_structure['debug_items'] = array();
1844 // Can start loop here to decode all sensor data in 32 Byte chunks:
1845 foreach (str_split($atom_SENSOR_data, 32) as $sensor_key => $sensor_data) {
1846 // This gets me a data_type code to work out what data is in the next 31 bytes.
1847 $sensor_data_type = substr($sensor_data, 0, 1);
1848 $sensor_data_content = substr($sensor_data, 1);
1849 $uuid_bytes_read = unpack('C*', $sensor_data_type);
1850 $sensor_data_array = array();
1851 switch ($uuid_bytes_read[1]) {
1852 case 250:
1853 $atom_structure['sensor_data']['data_type']['fusion_count']++;
1854 $uuid_bytes_read = unpack('cmode/Jtimestamp/Gyaw/Gpitch/Groll/x*', $sensor_data_content);
1855 $sensor_data_array['mode'] = $uuid_bytes_read['mode'];
1856 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
1857 $sensor_data_array['yaw'] = $uuid_bytes_read['yaw'];
1858 $sensor_data_array['pitch'] = $uuid_bytes_read['pitch'];
1859 $sensor_data_array['roll'] = $uuid_bytes_read['roll'];
1860 array_push($atom_structure['sensor_data']['data_type']['fusion_data'], $sensor_data_array);
1861 break;
1862 case 1:
1863 $atom_structure['sensor_data']['data_type']['accel_count']++;
1864 $uuid_bytes_read = unpack('cmode/Jtimestamp/Gyaw/Gpitch/Groll/x*', $sensor_data_content);
1865 $sensor_data_array['mode'] = $uuid_bytes_read['mode'];
1866 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
1867 $sensor_data_array['yaw'] = $uuid_bytes_read['yaw'];
1868 $sensor_data_array['pitch'] = $uuid_bytes_read['pitch'];
1869 $sensor_data_array['roll'] = $uuid_bytes_read['roll'];
1870 array_push($atom_structure['sensor_data']['data_type']['accel_data'], $sensor_data_array);
1871 break;
1872 case 2:
1873 $atom_structure['sensor_data']['data_type']['gyro_count']++;
1874 $uuid_bytes_read = unpack('cmode/Jtimestamp/Gyaw/Gpitch/Groll/x*', $sensor_data_content);
1875 $sensor_data_array['mode'] = $uuid_bytes_read['mode'];
1876 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
1877 $sensor_data_array['yaw'] = $uuid_bytes_read['yaw'];
1878 $sensor_data_array['pitch'] = $uuid_bytes_read['pitch'];
1879 $sensor_data_array['roll'] = $uuid_bytes_read['roll'];
1880 array_push($atom_structure['sensor_data']['data_type']['gyro_data'], $sensor_data_array);
1881 break;
1882 case 3:
1883 $atom_structure['sensor_data']['data_type']['magno_count']++;
1884 $uuid_bytes_read = unpack('cmode/Jtimestamp/Gmagx/Gmagy/Gmagz/x*', $sensor_data_content);
1885 $sensor_data_array['mode'] = $uuid_bytes_read['mode'];
1886 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
1887 $sensor_data_array['magx'] = $uuid_bytes_read['magx'];
1888 $sensor_data_array['magy'] = $uuid_bytes_read['magy'];
1889 $sensor_data_array['magz'] = $uuid_bytes_read['magz'];
1890 array_push($atom_structure['sensor_data']['data_type']['magno_data'], $sensor_data_array);
1891 break;
1892 case 5:
1893 $atom_structure['sensor_data']['data_type']['gps_count']++;
1894 $uuid_bytes_read = unpack('cmode/Jtimestamp/Glat/Glon/Galt/Gspeed/nbearing/nacc/x*', $sensor_data_content);
1895 $sensor_data_array['mode'] = $uuid_bytes_read['mode'];
1896 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
1897 $sensor_data_array['lat'] = $uuid_bytes_read['lat'];
1898 $sensor_data_array['lon'] = $uuid_bytes_read['lon'];
1899 $sensor_data_array['alt'] = $uuid_bytes_read['alt'];
1900 $sensor_data_array['speed'] = $uuid_bytes_read['speed'];
1901 $sensor_data_array['bearing'] = $uuid_bytes_read['bearing'];
1902 $sensor_data_array['acc'] = $uuid_bytes_read['acc'];
1903 array_push($atom_structure['sensor_data']['data_type']['gps_data'], $sensor_data_array);
1904 //array_push($debug_structure['debug_items'], $uuid_bytes_read['timestamp']);
1905 break;
1906 case 6:
1907 $atom_structure['sensor_data']['data_type']['rotation_count']++;
1908 $uuid_bytes_read = unpack('cmode/Jtimestamp/Grotx/Groty/Grotz/x*', $sensor_data_content);
1909 $sensor_data_array['mode'] = $uuid_bytes_read['mode'];
1910 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
1911 $sensor_data_array['rotx'] = $uuid_bytes_read['rotx'];
1912 $sensor_data_array['roty'] = $uuid_bytes_read['roty'];
1913 $sensor_data_array['rotz'] = $uuid_bytes_read['rotz'];
1914 array_push($atom_structure['sensor_data']['data_type']['rotation_data'], $sensor_data_array);
1915 break;
1916 default:
1917 $atom_structure['sensor_data']['data_type']['unknown_count']++;
1918 break;
1919 }
1920 }
1921 //if (isset($debug_structure['debug_items']) && count($debug_structure['debug_items']) > 0) {
1922 // $atom_structure['sensor_data']['data_type']['debug_list'] = implode(',', $debug_structure['debug_items']);
1923 //} else {
1924 $atom_structure['sensor_data']['data_type']['debug_list'] = 'No debug items in list!';
1925 //}
1926 break;
1927
1928 default:
1929 $this->warning('Unhandled "uuid" atom identified by "'.$atom_structure['uuid_field_id'].'" at offset '.$atom_structure['offset'].' ('.strlen($atom_data).' bytes)');
1930 }
1931 break;
1932
1933 case 'gps ':
1934 // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730
1935 // The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data.
1936 // The first row is version/metadata/notsure, I skip that.
1937 // The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file.
1938
1939 $GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size
1940 if (strlen($atom_data) > 0) {
1941 if ((strlen($atom_data) % $GPS_rowsize) == 0) {
1942 $atom_structure['gps_toc'] = array();
1943 foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) {
1944 $atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize));
1945 }
1946
1947 $atom_structure['gps_entries'] = array();
1948 $previous_offset = $this->ftell();
1949 foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) {
1950 if ($key == 0) {
1951 // "The first row is version/metadata/notsure, I skip that."
1952 continue;
1953 }
1954 $this->fseek($gps_pointer['offset']);
1955 $GPS_free_data = $this->fread($gps_pointer['size']);
1956
1957 /*
1958 // 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
1959
1960 // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730
1961 // The structure of the GPS data atom (the 'free' atoms mentioned above) is following:
1962 // hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from('<IIIIIIssssfff',data, 48)
1963 // For those unfamiliar with python struct:
1964 // I = int
1965 // s = is string (size 1, in this case)
1966 // f = float
1967
1968 //$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));
1969 */
1970
1971 // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
1972 // $GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67
1973 // $GPRMC,002454,A,3553.5295,N,13938.6570,E,0.0,43.1,180700,7.1,W,A*3F
1974 // $GPRMC,094347.000,A,5342.0061,N,00737.9908,W,0.01,156.75,140217,,,A*7D
1975 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)) {
1976 $GPS_this_GPRMC = array();
1977 $GPS_this_GPRMC_raw = array();
1978 list(
1979 $GPS_this_GPRMC_raw['gprmc'],
1980 $GPS_this_GPRMC_raw['timestamp'],
1981 $GPS_this_GPRMC_raw['status'],
1982 $GPS_this_GPRMC_raw['latitude'],
1983 $GPS_this_GPRMC_raw['latitude_direction'],
1984 $GPS_this_GPRMC_raw['longitude'],
1985 $GPS_this_GPRMC_raw['longitude_direction'],
1986 $GPS_this_GPRMC_raw['knots'],
1987 $GPS_this_GPRMC_raw['angle'],
1988 $GPS_this_GPRMC_raw['datestamp'],
1989 $GPS_this_GPRMC_raw['variation'],
1990 $GPS_this_GPRMC_raw['variation_direction'],
1991 $dummy,
1992 $GPS_this_GPRMC_raw['checksum'],
1993 ) = $matches;
1994 $GPS_this_GPRMC['raw'] = $GPS_this_GPRMC_raw;
1995
1996 $hour = substr($GPS_this_GPRMC['raw']['timestamp'], 0, 2);
1997 $minute = substr($GPS_this_GPRMC['raw']['timestamp'], 2, 2);
1998 $second = substr($GPS_this_GPRMC['raw']['timestamp'], 4, 2);
1999 $ms = substr($GPS_this_GPRMC['raw']['timestamp'], 6); // may contain decimal seconds
2000 $day = substr($GPS_this_GPRMC['raw']['datestamp'], 0, 2);
2001 $month = substr($GPS_this_GPRMC['raw']['datestamp'], 2, 2);
2002 $year = (int) substr($GPS_this_GPRMC['raw']['datestamp'], 4, 2);
2003 $year += (($year > 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess
2004 $GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms;
2005
2006 $GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void
2007
2008 foreach (array('latitude','longitude') as $latlon) {
2009 preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches);
2010 list($dummy, $deg, $min) = $matches;
2011 $GPS_this_GPRMC[$latlon] = (int) $deg + ((float) $min / 60);
2012 }
2013 $GPS_this_GPRMC['latitude'] *= (($GPS_this_GPRMC['raw']['latitude_direction'] == 'S') ? -1 : 1);
2014 $GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1);
2015
2016 $GPS_this_GPRMC['heading'] = $GPS_this_GPRMC['raw']['angle'];
2017 $GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots'];
2018 $GPS_this_GPRMC['speed_kmh'] = (float) $GPS_this_GPRMC['raw']['knots'] * 1.852;
2019 if ($GPS_this_GPRMC['raw']['variation']) {
2020 $GPS_this_GPRMC['variation'] = (float) $GPS_this_GPRMC['raw']['variation'];
2021 $GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1);
2022 }
2023
2024 $atom_structure['gps_entries'][$key] = $GPS_this_GPRMC;
2025
2026 @$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array(
2027 'latitude' => (float) $GPS_this_GPRMC['latitude'],
2028 'longitude' => (float) $GPS_this_GPRMC['longitude'],
2029 'speed_kmh' => (float) $GPS_this_GPRMC['speed_kmh'],
2030 'heading' => (float) $GPS_this_GPRMC['heading'],
2031 );
2032
2033 } else {
2034 $this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']);
2035 }
2036 }
2037 $this->fseek($previous_offset);
2038
2039 } else {
2040 $this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset);
2041 }
2042 } else {
2043 $this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset);
2044 }
2045 break;
2046
2047 case 'loci':// 3GP location (El Loco)
2048 $loffset = 0;
2049 $info['quicktime']['comments']['gps_flags'] = array( getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)));
2050 $info['quicktime']['comments']['gps_lang'] = array( getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)));
2051 $info['quicktime']['comments']['gps_location'] = array( $this->LociString(substr($atom_data, 6), $loffset));
2052 $loci_data = substr($atom_data, 6 + $loffset);
2053 $info['quicktime']['comments']['gps_role'] = array( getid3_lib::BigEndian2Int(substr($loci_data, 0, 1)));
2054 $info['quicktime']['comments']['gps_longitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4)));
2055 $info['quicktime']['comments']['gps_latitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4)));
2056 $info['quicktime']['comments']['gps_altitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4)));
2057 $info['quicktime']['comments']['gps_body'] = array( $this->LociString(substr($loci_data, 13 ), $loffset));
2058 $info['quicktime']['comments']['gps_notes'] = array( $this->LociString(substr($loci_data, 13 + $loffset), $loffset));
2059 break;
2060
2061 case 'chpl': // CHaPter List
2062 // https://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf
2063 $chpl_version = getid3_lib::BigEndian2Int(substr($atom_data, 4, 1)); // Expected to be 0
2064 $chpl_flags = getid3_lib::BigEndian2Int(substr($atom_data, 5, 3)); // Reserved, set to 0
2065 $chpl_count = getid3_lib::BigEndian2Int(substr($atom_data, 8, 1));
2066 $chpl_offset = 9;
2067 for ($i = 0; $i < $chpl_count; $i++) {
2068 if (($chpl_offset + 9) >= strlen($atom_data)) {
2069 $this->warning('QuickTime chapter '.$i.' extends beyond end of "chpl" atom');
2070 break;
2071 }
2072 $info['quicktime']['chapters'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 8)) / 10000000; // timestamps are stored as 100-nanosecond units
2073 $chpl_offset += 8;
2074 $chpl_title_size = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 1));
2075 $chpl_offset += 1;
2076 $info['quicktime']['chapters'][$i]['title'] = substr($atom_data, $chpl_offset, $chpl_title_size);
2077 $chpl_offset += $chpl_title_size;
2078 }
2079 break;
2080
2081 case 'FIRM': // FIRMware version(?), seen on GoPro Hero4
2082 $info['quicktime']['camera']['firmware'] = $atom_data;
2083 break;
2084
2085 case 'CAME': // FIRMware version(?), seen on GoPro Hero4
2086 $info['quicktime']['camera']['serial_hash'] = unpack('H*', $atom_data);
2087 break;
2088
2089 case 'dscp':
2090 case 'rcif':
2091 // https://www.getid3.org/phpBB3/viewtopic.php?t=1908
2092 if (substr($atom_data, 0, 7) == "\x00\x00\x00\x00\x55\xC4".'{') {
2093 if ($json_decoded = @json_decode(rtrim(substr($atom_data, 6), "\x00"), true)) {
2094 $info['quicktime']['camera'][$atomname] = $json_decoded;
2095 if (($atomname == 'rcif') && isset($info['quicktime']['camera']['rcif']['wxcamera']['rotate'])) {
2096 $info['video']['rotate'] = $info['quicktime']['video']['rotate'] = $info['quicktime']['camera']['rcif']['wxcamera']['rotate'];
2097 }
2098 } else {
2099 $this->warning('Failed to JSON decode atom "'.$atomname.'"');
2100 $atom_structure['data'] = $atom_data;
2101 }
2102 unset($json_decoded);
2103 } else {
2104 $this->warning('Expecting 55 C4 7B at start of atom "'.$atomname.'", found '.getid3_lib::PrintHexBytes(substr($atom_data, 4, 3)).' instead');
2105 $atom_structure['data'] = $atom_data;
2106 }
2107 break;
2108
2109 case 'frea':
2110 // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea
2111 // may contain "scra" (PreviewImage) and/or "thma" (ThumbnailImage)
2112 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms);
2113 break;
2114 case 'tima': // subatom to "frea"
2115 // no idea what this does, the one sample file I've seen has a value of 0x00000027
2116 $atom_structure['data'] = $atom_data;
2117 break;
2118 case 'ver ': // subatom to "frea"
2119 // some kind of version number, the one sample file I've seen has a value of "3.00.073"
2120 $atom_structure['data'] = $atom_data;
2121 break;
2122 case 'thma': // subatom to "frea" -- "ThumbnailImage"
2123 // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea
2124 if (strlen($atom_data) > 0) {
2125 $info['quicktime']['comments']['picture'][] = array('data'=>$atom_data, 'image_mime'=>'image/jpeg', 'description'=>'ThumbnailImage');
2126 }
2127 break;
2128 case 'scra': // subatom to "frea" -- "PreviewImage"
2129 // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea
2130 // but the only sample file I've seen has no useful data here
2131 if (strlen($atom_data) > 0) {
2132 $info['quicktime']['comments']['picture'][] = array('data'=>$atom_data, 'image_mime'=>'image/jpeg', 'description'=>'PreviewImage');
2133 }
2134 break;
2135
2136 case 'cdsc': // timed metadata reference
2137 // A QuickTime movie can contain none, one, or several timed metadata tracks. Timed metadata tracks can refer to multiple tracks.
2138 // Metadata tracks are linked to the tracks they describe using a track-reference of type 'cdsc'. The metadata track holds the 'cdsc' track reference.
2139 $atom_structure['track_number'] = getid3_lib::BigEndian2Int($atom_data);
2140 break;
2141
2142
2143 case 'esds': // Elementary Stream DeScriptor
2144 // https://github.com/JamesHeinrich/getID3/issues/414
2145 // https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/formats/mp4/es_descriptor.cc
2146 // https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/formats/mp4/es_descriptor.h
2147 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); // hardcoded: 0x00
2148 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x000000
2149 $esds_offset = 4;
2150
2151 $atom_structure['ES_DescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
2152 $esds_offset += 1;
2153 if ($atom_structure['ES_DescrTag'] != 0x03) {
2154 $this->warning('expecting esds.ES_DescrTag = 0x03, found 0x'.sprintf('%02X', $atom_structure['ES_DescrTag']).', at offset '.$atom_structure['offset']);
2155 break;
2156 }
2157 $atom_structure['ES_DescrSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
2158
2159 $atom_structure['ES_ID'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2));
2160 $esds_offset += 2;
2161 $atom_structure['ES_flagsraw'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
2162 $esds_offset += 1;
2163 $atom_structure['ES_flags']['stream_dependency'] = (bool) ($atom_structure['ES_flagsraw'] & 0x80);
2164 $atom_structure['ES_flags']['url_flag'] = (bool) ($atom_structure['ES_flagsraw'] & 0x40);
2165 $atom_structure['ES_flags']['ocr_stream'] = (bool) ($atom_structure['ES_flagsraw'] & 0x20);
2166 $atom_structure['ES_stream_priority'] = ($atom_structure['ES_flagsraw'] & 0x1F);
2167 if ($atom_structure['ES_flags']['url_flag']) {
2168 $this->warning('Unsupported esds.url_flag enabled at offset '.$atom_structure['offset']);
2169 break;
2170 }
2171 if ($atom_structure['ES_flags']['stream_dependency']) {
2172 $atom_structure['ES_dependsOn_ES_ID'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2));
2173 $esds_offset += 2;
2174 }
2175 if ($atom_structure['ES_flags']['ocr_stream']) {
2176 $atom_structure['ES_OCR_ES_Id'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2));
2177 $esds_offset += 2;
2178 }
2179
2180 $atom_structure['ES_DecoderConfigDescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
2181 $esds_offset += 1;
2182 if ($atom_structure['ES_DecoderConfigDescrTag'] != 0x04) {
2183 $this->warning('expecting esds.ES_DecoderConfigDescrTag = 0x04, found 0x'.sprintf('%02X', $atom_structure['ES_DecoderConfigDescrTag']).', at offset '.$atom_structure['offset']);
2184 break;
2185 }
2186 $atom_structure['ES_DecoderConfigDescrTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
2187
2188 $atom_structure['ES_objectTypeIndication'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
2189 $esds_offset += 1;
2190 // https://stackoverflow.com/questions/3987850
2191 // 0x40 = "Audio ISO/IEC 14496-3" = MPEG-4 Audio
2192 // 0x67 = "Audio ISO/IEC 13818-7 LowComplexity Profile" = MPEG-2 AAC LC
2193 // 0x69 = "Audio ISO/IEC 13818-3" = MPEG-2 Backward Compatible Audio (MPEG-2 Layers 1, 2, and 3)
2194 // 0x6B = "Audio ISO/IEC 11172-3" = MPEG-1 Audio (MPEG-1 Layers 1, 2, and 3)
2195
2196 $streamTypePlusFlags = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
2197 $esds_offset += 1;
2198 $atom_structure['ES_streamType'] = ($streamTypePlusFlags & 0xFC) >> 2;
2199 $atom_structure['ES_upStream'] = (bool) ($streamTypePlusFlags & 0x02) >> 1;
2200 $atom_structure['ES_bufferSizeDB'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 3));
2201 $esds_offset += 3;
2202 $atom_structure['ES_maxBitrate'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 4));
2203 $esds_offset += 4;
2204 $atom_structure['ES_avgBitrate'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 4));
2205 $esds_offset += 4;
2206 if ($atom_structure['ES_avgBitrate']) {
2207 $info['quicktime']['audio']['bitrate'] = $atom_structure['ES_avgBitrate'];
2208 $info['audio']['bitrate'] = $atom_structure['ES_avgBitrate'];
2209 }
2210
2211 $atom_structure['ES_DecSpecificInfoTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
2212 $esds_offset += 1;
2213 if ($atom_structure['ES_DecSpecificInfoTag'] != 0x05) {
2214 $this->warning('expecting esds.ES_DecSpecificInfoTag = 0x05, found 0x'.sprintf('%02X', $atom_structure['ES_DecSpecificInfoTag']).', at offset '.$atom_structure['offset']);
2215 break;
2216 }
2217 $atom_structure['ES_DecSpecificInfoTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
2218
2219 $atom_structure['ES_DecSpecificInfo'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, $atom_structure['ES_DecSpecificInfoTagSize']));
2220 $esds_offset += $atom_structure['ES_DecSpecificInfoTagSize'];
2221
2222 $atom_structure['ES_SLConfigDescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
2223 $esds_offset += 1;
2224 if ($atom_structure['ES_SLConfigDescrTag'] != 0x06) {
2225 $this->warning('expecting esds.ES_SLConfigDescrTag = 0x05, found 0x'.sprintf('%02X', $atom_structure['ES_SLConfigDescrTag']).', at offset '.$atom_structure['offset']);
2226 break;
2227 }
2228 $atom_structure['ES_SLConfigDescrTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
2229
2230 $atom_structure['ES_SLConfigDescr'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, $atom_structure['ES_SLConfigDescrTagSize']));
2231 $esds_offset += $atom_structure['ES_SLConfigDescrTagSize'];
2232 break;
2233
2234// AVIF-related - https://docs.rs/avif-parse/0.13.2/src/avif_parse/boxes.rs.html
2235 case 'pitm': // Primary ITeM
2236 case 'iloc': // Item LOCation
2237 case 'iinf': // Item INFo
2238 case 'iref': // Image REFerence
2239 case 'iprp': // Image PRoPerties
2240$this->error('AVIF files not currently supported');
2241 $atom_structure['data'] = $atom_data;
2242 break;
2243
2244 case 'tfdt': // Track Fragment base media Decode Time box
2245 case 'tfhd': // Track Fragment HeaDer box
2246 case 'mfhd': // Movie Fragment HeaDer box
2247 case 'trun': // Track fragment RUN box
2248$this->error('fragmented mp4 files not currently supported');
2249 $atom_structure['data'] = $atom_data;
2250 break;
2251
2252 case 'mvex': // MoVie EXtends box
2253 case 'pssh': // Protection System Specific Header box
2254 case 'sidx': // Segment InDeX box
2255 default:
2256 $this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).'), '.$atomsize.' bytes at offset '.$baseoffset);
2257 $atom_structure['data'] = $atom_data;
2258 break;
2259 }
2260 }
2261 array_pop($atomHierarchy);
2262 return $atom_structure;
2263 }
2264
2265 /**
2266 * @param string $atom_data
2267 * @param int $baseoffset
2268 * @param array $atomHierarchy
2269 * @param bool $ParseAllPossibleAtoms
2270 *
2271 * @return array|false
2272 */
2273 public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
2274 $atom_structure = array();
2275 $subatomoffset = 0;
2276 $subatomcounter = 0;
2277 if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) {
2278 return false;
2279 }
2280 while ($subatomoffset < strlen($atom_data)) {
2281 $subatomsize = getid3_lib::BigEndian2Int(substr($atom_data, $subatomoffset + 0, 4));
2282 $subatomname = substr($atom_data, $subatomoffset + 4, 4);
2283 $subatomdata = substr($atom_data, $subatomoffset + 8, $subatomsize - 8);
2284 if ($subatomsize == 0) {
2285 // Furthermore, for historical reasons the list of atoms is optionally
2286 // terminated by a 32-bit integer set to 0. If you are writing a program
2287 // to read user data atoms, you should allow for the terminating 0.
2288 if (strlen($atom_data) > 12) {
2289 $subatomoffset += 4;
2290 continue;
2291 }
2292 break;
2293 }
2294 if (strlen($subatomdata) < ($subatomsize - 8)) {
2295 // we don't have enough data to decode the subatom.
2296 // this may be because we are refusing to parse large subatoms, or it may be because this atom had its size set too large
2297 // so we passed in the start of a following atom incorrectly?
2298 break;
2299 }
2300 $atom_structure[$subatomcounter++] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms);
2301 $subatomoffset += $subatomsize;
2302 }
2303
2304 if (empty($atom_structure)) {
2305 return false;
2306 }
2307
2308 return $atom_structure;
2309 }
2310
2311 /**
2312 * @param string $data
2313 * @param int $offset
2314 *
2315 * @return int
2316 */
2317 public function quicktime_read_mp4_descr_length($data, &$offset) {
2318 // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html
2319 $num_bytes = 0;
2320 $length = 0;
2321 do {
2322 $b = ord(substr($data, $offset++, 1));
2323 $length = ($length << 7) | ($b & 0x7F);
2324 } while (($b & 0x80) && ($num_bytes++ < 4));
2325 return $length;
2326 }
2327
2328 /**
2329 * @param int $languageid
2330 *
2331 * @return string
2332 */
2333 public function QuicktimeLanguageLookup($languageid) {
2334 // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-34353
2335 static $QuicktimeLanguageLookup = array();
2336 if (empty($QuicktimeLanguageLookup)) {
2337 $QuicktimeLanguageLookup[0] = 'English';
2338 $QuicktimeLanguageLookup[1] = 'French';
2339 $QuicktimeLanguageLookup[2] = 'German';
2340 $QuicktimeLanguageLookup[3] = 'Italian';
2341 $QuicktimeLanguageLookup[4] = 'Dutch';
2342 $QuicktimeLanguageLookup[5] = 'Swedish';
2343 $QuicktimeLanguageLookup[6] = 'Spanish';
2344 $QuicktimeLanguageLookup[7] = 'Danish';
2345 $QuicktimeLanguageLookup[8] = 'Portuguese';
2346 $QuicktimeLanguageLookup[9] = 'Norwegian';
2347 $QuicktimeLanguageLookup[10] = 'Hebrew';
2348 $QuicktimeLanguageLookup[11] = 'Japanese';
2349 $QuicktimeLanguageLookup[12] = 'Arabic';
2350 $QuicktimeLanguageLookup[13] = 'Finnish';
2351 $QuicktimeLanguageLookup[14] = 'Greek';
2352 $QuicktimeLanguageLookup[15] = 'Icelandic';
2353 $QuicktimeLanguageLookup[16] = 'Maltese';
2354 $QuicktimeLanguageLookup[17] = 'Turkish';
2355 $QuicktimeLanguageLookup[18] = 'Croatian';
2356 $QuicktimeLanguageLookup[19] = 'Chinese (Traditional)';
2357 $QuicktimeLanguageLookup[20] = 'Urdu';
2358 $QuicktimeLanguageLookup[21] = 'Hindi';
2359 $QuicktimeLanguageLookup[22] = 'Thai';
2360 $QuicktimeLanguageLookup[23] = 'Korean';
2361 $QuicktimeLanguageLookup[24] = 'Lithuanian';
2362 $QuicktimeLanguageLookup[25] = 'Polish';
2363 $QuicktimeLanguageLookup[26] = 'Hungarian';
2364 $QuicktimeLanguageLookup[27] = 'Estonian';
2365 $QuicktimeLanguageLookup[28] = 'Lettish';
2366 $QuicktimeLanguageLookup[28] = 'Latvian';
2367 $QuicktimeLanguageLookup[29] = 'Saamisk';
2368 $QuicktimeLanguageLookup[29] = 'Lappish';
2369 $QuicktimeLanguageLookup[30] = 'Faeroese';
2370 $QuicktimeLanguageLookup[31] = 'Farsi';
2371 $QuicktimeLanguageLookup[31] = 'Persian';
2372 $QuicktimeLanguageLookup[32] = 'Russian';
2373 $QuicktimeLanguageLookup[33] = 'Chinese (Simplified)';
2374 $QuicktimeLanguageLookup[34] = 'Flemish';
2375 $QuicktimeLanguageLookup[35] = 'Irish';
2376 $QuicktimeLanguageLookup[36] = 'Albanian';
2377 $QuicktimeLanguageLookup[37] = 'Romanian';
2378 $QuicktimeLanguageLookup[38] = 'Czech';
2379 $QuicktimeLanguageLookup[39] = 'Slovak';
2380 $QuicktimeLanguageLookup[40] = 'Slovenian';
2381 $QuicktimeLanguageLookup[41] = 'Yiddish';
2382 $QuicktimeLanguageLookup[42] = 'Serbian';
2383 $QuicktimeLanguageLookup[43] = 'Macedonian';
2384 $QuicktimeLanguageLookup[44] = 'Bulgarian';
2385 $QuicktimeLanguageLookup[45] = 'Ukrainian';
2386 $QuicktimeLanguageLookup[46] = 'Byelorussian';
2387 $QuicktimeLanguageLookup[47] = 'Uzbek';
2388 $QuicktimeLanguageLookup[48] = 'Kazakh';
2389 $QuicktimeLanguageLookup[49] = 'Azerbaijani';
2390 $QuicktimeLanguageLookup[50] = 'AzerbaijanAr';
2391 $QuicktimeLanguageLookup[51] = 'Armenian';
2392 $QuicktimeLanguageLookup[52] = 'Georgian';
2393 $QuicktimeLanguageLookup[53] = 'Moldavian';
2394 $QuicktimeLanguageLookup[54] = 'Kirghiz';
2395 $QuicktimeLanguageLookup[55] = 'Tajiki';
2396 $QuicktimeLanguageLookup[56] = 'Turkmen';
2397 $QuicktimeLanguageLookup[57] = 'Mongolian';
2398 $QuicktimeLanguageLookup[58] = 'MongolianCyr';
2399 $QuicktimeLanguageLookup[59] = 'Pashto';
2400 $QuicktimeLanguageLookup[60] = 'Kurdish';
2401 $QuicktimeLanguageLookup[61] = 'Kashmiri';
2402 $QuicktimeLanguageLookup[62] = 'Sindhi';
2403 $QuicktimeLanguageLookup[63] = 'Tibetan';
2404 $QuicktimeLanguageLookup[64] = 'Nepali';
2405 $QuicktimeLanguageLookup[65] = 'Sanskrit';
2406 $QuicktimeLanguageLookup[66] = 'Marathi';
2407 $QuicktimeLanguageLookup[67] = 'Bengali';
2408 $QuicktimeLanguageLookup[68] = 'Assamese';
2409 $QuicktimeLanguageLookup[69] = 'Gujarati';
2410 $QuicktimeLanguageLookup[70] = 'Punjabi';
2411 $QuicktimeLanguageLookup[71] = 'Oriya';
2412 $QuicktimeLanguageLookup[72] = 'Malayalam';
2413 $QuicktimeLanguageLookup[73] = 'Kannada';
2414 $QuicktimeLanguageLookup[74] = 'Tamil';
2415 $QuicktimeLanguageLookup[75] = 'Telugu';
2416 $QuicktimeLanguageLookup[76] = 'Sinhalese';
2417 $QuicktimeLanguageLookup[77] = 'Burmese';
2418 $QuicktimeLanguageLookup[78] = 'Khmer';
2419 $QuicktimeLanguageLookup[79] = 'Lao';
2420 $QuicktimeLanguageLookup[80] = 'Vietnamese';
2421 $QuicktimeLanguageLookup[81] = 'Indonesian';
2422 $QuicktimeLanguageLookup[82] = 'Tagalog';
2423 $QuicktimeLanguageLookup[83] = 'MalayRoman';
2424 $QuicktimeLanguageLookup[84] = 'MalayArabic';
2425 $QuicktimeLanguageLookup[85] = 'Amharic';
2426 $QuicktimeLanguageLookup[86] = 'Tigrinya';
2427 $QuicktimeLanguageLookup[87] = 'Galla';
2428 $QuicktimeLanguageLookup[87] = 'Oromo';
2429 $QuicktimeLanguageLookup[88] = 'Somali';
2430 $QuicktimeLanguageLookup[89] = 'Swahili';
2431 $QuicktimeLanguageLookup[90] = 'Ruanda';
2432 $QuicktimeLanguageLookup[91] = 'Rundi';
2433 $QuicktimeLanguageLookup[92] = 'Chewa';
2434 $QuicktimeLanguageLookup[93] = 'Malagasy';
2435 $QuicktimeLanguageLookup[94] = 'Esperanto';
2436 $QuicktimeLanguageLookup[128] = 'Welsh';
2437 $QuicktimeLanguageLookup[129] = 'Basque';
2438 $QuicktimeLanguageLookup[130] = 'Catalan';
2439 $QuicktimeLanguageLookup[131] = 'Latin';
2440 $QuicktimeLanguageLookup[132] = 'Quechua';
2441 $QuicktimeLanguageLookup[133] = 'Guarani';
2442 $QuicktimeLanguageLookup[134] = 'Aymara';
2443 $QuicktimeLanguageLookup[135] = 'Tatar';
2444 $QuicktimeLanguageLookup[136] = 'Uighur';
2445 $QuicktimeLanguageLookup[137] = 'Dzongkha';
2446 $QuicktimeLanguageLookup[138] = 'JavaneseRom';
2447 $QuicktimeLanguageLookup[32767] = 'Unspecified';
2448 }
2449 if (($languageid > 138) && ($languageid < 32767)) {
2450 /*
2451 ISO Language Codes - http://www.loc.gov/standards/iso639-2/php/code_list.php
2452 Because the language codes specified by ISO 639-2/T are three characters long, they must be packed to fit into a 16-bit field.
2453 The packing algorithm must map each of the three characters, which are always lowercase, into a 5-bit integer and then concatenate
2454 these integers into the least significant 15 bits of a 16-bit integer, leaving the 16-bit integer's most significant bit set to zero.
2455
2456 One algorithm for performing this packing is to treat each ISO character as a 16-bit integer. Subtract 0x60 from the first character
2457 and multiply by 2^10 (0x400), subtract 0x60 from the second character and multiply by 2^5 (0x20), subtract 0x60 from the third character,
2458 and add the three 16-bit values. This will result in a single 16-bit value with the three codes correctly packed into the 15 least
2459 significant bits and the most significant bit set to zero.
2460 */
2461 $iso_language_id = '';
2462 $iso_language_id .= chr((($languageid & 0x7C00) >> 10) + 0x60);
2463 $iso_language_id .= chr((($languageid & 0x03E0) >> 5) + 0x60);
2464 $iso_language_id .= chr((($languageid & 0x001F) >> 0) + 0x60);
2465 $QuicktimeLanguageLookup[$languageid] = getid3_id3v2::LanguageLookup($iso_language_id);
2466 }
2467 return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid');
2468 }
2469
2470 /**
2471 * @param string $codecid
2472 *
2473 * @return string
2474 */
2475 public function QuicktimeVideoCodecLookup($codecid) {
2476 static $QuicktimeVideoCodecLookup = array();
2477 if (empty($QuicktimeVideoCodecLookup)) {
2478 $QuicktimeVideoCodecLookup['.SGI'] = 'SGI';
2479 $QuicktimeVideoCodecLookup['3IV1'] = '3ivx MPEG-4 v1';
2480 $QuicktimeVideoCodecLookup['3IV2'] = '3ivx MPEG-4 v2';
2481 $QuicktimeVideoCodecLookup['3IVX'] = '3ivx MPEG-4';
2482 $QuicktimeVideoCodecLookup['8BPS'] = 'Planar RGB';
2483 $QuicktimeVideoCodecLookup['avc1'] = 'H.264/MPEG-4 AVC';
2484 $QuicktimeVideoCodecLookup['avr '] = 'AVR-JPEG';
2485 $QuicktimeVideoCodecLookup['b16g'] = '16Gray';
2486 $QuicktimeVideoCodecLookup['b32a'] = '32AlphaGray';
2487 $QuicktimeVideoCodecLookup['b48r'] = '48RGB';
2488 $QuicktimeVideoCodecLookup['b64a'] = '64ARGB';
2489 $QuicktimeVideoCodecLookup['base'] = 'Base';
2490 $QuicktimeVideoCodecLookup['clou'] = 'Cloud';
2491 $QuicktimeVideoCodecLookup['cmyk'] = 'CMYK';
2492 $QuicktimeVideoCodecLookup['cvid'] = 'Cinepak';
2493 $QuicktimeVideoCodecLookup['dmb1'] = 'OpenDML JPEG';
2494 $QuicktimeVideoCodecLookup['dvc '] = 'DVC-NTSC';
2495 $QuicktimeVideoCodecLookup['dvcp'] = 'DVC-PAL';
2496 $QuicktimeVideoCodecLookup['dvpn'] = 'DVCPro-NTSC';
2497 $QuicktimeVideoCodecLookup['dvpp'] = 'DVCPro-PAL';
2498 $QuicktimeVideoCodecLookup['fire'] = 'Fire';
2499 $QuicktimeVideoCodecLookup['flic'] = 'FLC';
2500 $QuicktimeVideoCodecLookup['gif '] = 'GIF';
2501 $QuicktimeVideoCodecLookup['h261'] = 'H261';
2502 $QuicktimeVideoCodecLookup['h263'] = 'H263';
2503 $QuicktimeVideoCodecLookup['hvc1'] = 'H.265/HEVC';
2504 $QuicktimeVideoCodecLookup['IV41'] = 'Indeo4';
2505 $QuicktimeVideoCodecLookup['jpeg'] = 'JPEG';
2506 $QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD';
2507 $QuicktimeVideoCodecLookup['mjpa'] = 'Motion JPEG-A';
2508 $QuicktimeVideoCodecLookup['mjpb'] = 'Motion JPEG-B';
2509 $QuicktimeVideoCodecLookup['msvc'] = 'Microsoft Video1';
2510 $QuicktimeVideoCodecLookup['myuv'] = 'MPEG YUV420';
2511 $QuicktimeVideoCodecLookup['path'] = 'Vector';
2512 $QuicktimeVideoCodecLookup['png '] = 'PNG';
2513 $QuicktimeVideoCodecLookup['PNTG'] = 'MacPaint';
2514 $QuicktimeVideoCodecLookup['qdgx'] = 'QuickDrawGX';
2515 $QuicktimeVideoCodecLookup['qdrw'] = 'QuickDraw';
2516 $QuicktimeVideoCodecLookup['raw '] = 'RAW';
2517 $QuicktimeVideoCodecLookup['ripl'] = 'WaterRipple';
2518 $QuicktimeVideoCodecLookup['rpza'] = 'Video';
2519 $QuicktimeVideoCodecLookup['smc '] = 'Graphics';
2520 $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 1';
2521 $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 3';
2522 $QuicktimeVideoCodecLookup['syv9'] = 'Sorenson YUV9';
2523 $QuicktimeVideoCodecLookup['tga '] = 'Targa';
2524 $QuicktimeVideoCodecLookup['tiff'] = 'TIFF';
2525 $QuicktimeVideoCodecLookup['WRAW'] = 'Windows RAW';
2526 $QuicktimeVideoCodecLookup['WRLE'] = 'BMP';
2527 $QuicktimeVideoCodecLookup['y420'] = 'YUV420';
2528 $QuicktimeVideoCodecLookup['yuv2'] = 'ComponentVideo';
2529 $QuicktimeVideoCodecLookup['yuvs'] = 'ComponentVideoUnsigned';
2530 $QuicktimeVideoCodecLookup['yuvu'] = 'ComponentVideoSigned';
2531 }
2532 return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : '');
2533 }
2534
2535 /**
2536 * @param string $codecid
2537 *
2538 * @return mixed|string
2539 */
2540 public function QuicktimeAudioCodecLookup($codecid) {
2541 static $QuicktimeAudioCodecLookup = array();
2542 if (empty($QuicktimeAudioCodecLookup)) {
2543 $QuicktimeAudioCodecLookup['.mp3'] = 'Fraunhofer MPEG Layer-III alias';
2544 $QuicktimeAudioCodecLookup['aac '] = 'ISO/IEC 14496-3 AAC';
2545 $QuicktimeAudioCodecLookup['agsm'] = 'Apple GSM 10:1';
2546 $QuicktimeAudioCodecLookup['alac'] = 'Apple Lossless Audio Codec';
2547 $QuicktimeAudioCodecLookup['alaw'] = 'A-law 2:1';
2548 $QuicktimeAudioCodecLookup['conv'] = 'Sample Format';
2549 $QuicktimeAudioCodecLookup['dvca'] = 'DV';
2550 $QuicktimeAudioCodecLookup['dvi '] = 'DV 4:1';
2551 $QuicktimeAudioCodecLookup['eqal'] = 'Frequency Equalizer';
2552 $QuicktimeAudioCodecLookup['fl32'] = '32-bit Floating Point';
2553 $QuicktimeAudioCodecLookup['fl64'] = '64-bit Floating Point';
2554 $QuicktimeAudioCodecLookup['ima4'] = 'Interactive Multimedia Association 4:1';
2555 $QuicktimeAudioCodecLookup['in24'] = '24-bit Integer';
2556 $QuicktimeAudioCodecLookup['in32'] = '32-bit Integer';
2557 $QuicktimeAudioCodecLookup['lpc '] = 'LPC 23:1';
2558 $QuicktimeAudioCodecLookup['MAC3'] = 'Macintosh Audio Compression/Expansion (MACE) 3:1';
2559 $QuicktimeAudioCodecLookup['MAC6'] = 'Macintosh Audio Compression/Expansion (MACE) 6:1';
2560 $QuicktimeAudioCodecLookup['mixb'] = '8-bit Mixer';
2561 $QuicktimeAudioCodecLookup['mixw'] = '16-bit Mixer';
2562 $QuicktimeAudioCodecLookup['mp4a'] = 'ISO/IEC 14496-3 AAC';
2563 $QuicktimeAudioCodecLookup['MS'."\x00\x02"] = 'Microsoft ADPCM';
2564 $QuicktimeAudioCodecLookup['MS'."\x00\x11"] = 'DV IMA';
2565 $QuicktimeAudioCodecLookup['MS'."\x00\x55"] = 'Fraunhofer MPEG Layer III';
2566 $QuicktimeAudioCodecLookup['NONE'] = 'No Encoding';
2567 $QuicktimeAudioCodecLookup['Qclp'] = 'Qualcomm PureVoice';
2568 $QuicktimeAudioCodecLookup['QDM2'] = 'QDesign Music 2';
2569 $QuicktimeAudioCodecLookup['QDMC'] = 'QDesign Music 1';
2570 $QuicktimeAudioCodecLookup['ratb'] = '8-bit Rate';
2571 $QuicktimeAudioCodecLookup['ratw'] = '16-bit Rate';
2572 $QuicktimeAudioCodecLookup['raw '] = 'raw PCM';
2573 $QuicktimeAudioCodecLookup['sour'] = 'Sound Source';
2574 $QuicktimeAudioCodecLookup['sowt'] = 'signed/two\'s complement (Little Endian)';
2575 $QuicktimeAudioCodecLookup['str1'] = 'Iomega MPEG layer II';
2576 $QuicktimeAudioCodecLookup['str2'] = 'Iomega MPEG *layer II';
2577 $QuicktimeAudioCodecLookup['str3'] = 'Iomega MPEG **layer II';
2578 $QuicktimeAudioCodecLookup['str4'] = 'Iomega MPEG ***layer II';
2579 $QuicktimeAudioCodecLookup['twos'] = 'signed/two\'s complement (Big Endian)';
2580 $QuicktimeAudioCodecLookup['ulaw'] = 'mu-law 2:1';
2581 }
2582 return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : '');
2583 }
2584
2585 /**
2586 * @param string $compressionid
2587 *
2588 * @return string
2589 */
2590 public function QuicktimeDCOMLookup($compressionid) {
2591 static $QuicktimeDCOMLookup = array();
2592 if (empty($QuicktimeDCOMLookup)) {
2593 $QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate';
2594 $QuicktimeDCOMLookup['adec'] = 'Apple Compression';
2595 }
2596 return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : '');
2597 }
2598
2599 /**
2600 * @param int $colordepthid
2601 *
2602 * @return string
2603 */
2604 public function QuicktimeColorNameLookup($colordepthid) {
2605 static $QuicktimeColorNameLookup = array();
2606 if (empty($QuicktimeColorNameLookup)) {
2607 $QuicktimeColorNameLookup[1] = '2-color (monochrome)';
2608 $QuicktimeColorNameLookup[2] = '4-color';
2609 $QuicktimeColorNameLookup[4] = '16-color';
2610 $QuicktimeColorNameLookup[8] = '256-color';
2611 $QuicktimeColorNameLookup[16] = 'thousands (16-bit color)';
2612 $QuicktimeColorNameLookup[24] = 'millions (24-bit color)';
2613 $QuicktimeColorNameLookup[32] = 'millions+ (32-bit color)';
2614 $QuicktimeColorNameLookup[33] = 'black & white';
2615 $QuicktimeColorNameLookup[34] = '4-gray';
2616 $QuicktimeColorNameLookup[36] = '16-gray';
2617 $QuicktimeColorNameLookup[40] = '256-gray';
2618 }
2619 return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid');
2620 }
2621
2622 /**
2623 * @param int $stik
2624 *
2625 * @return string
2626 */
2627 public function QuicktimeSTIKLookup($stik) {
2628 static $QuicktimeSTIKLookup = array();
2629 if (empty($QuicktimeSTIKLookup)) {
2630 $QuicktimeSTIKLookup[0] = 'Movie';
2631 $QuicktimeSTIKLookup[1] = 'Normal';
2632 $QuicktimeSTIKLookup[2] = 'Audiobook';
2633 $QuicktimeSTIKLookup[5] = 'Whacked Bookmark';
2634 $QuicktimeSTIKLookup[6] = 'Music Video';
2635 $QuicktimeSTIKLookup[9] = 'Short Film';
2636 $QuicktimeSTIKLookup[10] = 'TV Show';
2637 $QuicktimeSTIKLookup[11] = 'Booklet';
2638 $QuicktimeSTIKLookup[14] = 'Ringtone';
2639 $QuicktimeSTIKLookup[21] = 'Podcast';
2640 }
2641 return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid');
2642 }
2643
2644 /**
2645 * @param int $audio_profile_id
2646 *
2647 * @return string
2648 */
2649 public function QuicktimeIODSaudioProfileName($audio_profile_id) {
2650 static $QuicktimeIODSaudioProfileNameLookup = array();
2651 if (empty($QuicktimeIODSaudioProfileNameLookup)) {
2652 $QuicktimeIODSaudioProfileNameLookup = array(
2653 0x00 => 'ISO Reserved (0x00)',
2654 0x01 => 'Main Audio Profile @ Level 1',
2655 0x02 => 'Main Audio Profile @ Level 2',
2656 0x03 => 'Main Audio Profile @ Level 3',
2657 0x04 => 'Main Audio Profile @ Level 4',
2658 0x05 => 'Scalable Audio Profile @ Level 1',
2659 0x06 => 'Scalable Audio Profile @ Level 2',
2660 0x07 => 'Scalable Audio Profile @ Level 3',
2661 0x08 => 'Scalable Audio Profile @ Level 4',
2662 0x09 => 'Speech Audio Profile @ Level 1',
2663 0x0A => 'Speech Audio Profile @ Level 2',
2664 0x0B => 'Synthetic Audio Profile @ Level 1',
2665 0x0C => 'Synthetic Audio Profile @ Level 2',
2666 0x0D => 'Synthetic Audio Profile @ Level 3',
2667 0x0E => 'High Quality Audio Profile @ Level 1',
2668 0x0F => 'High Quality Audio Profile @ Level 2',
2669 0x10 => 'High Quality Audio Profile @ Level 3',
2670 0x11 => 'High Quality Audio Profile @ Level 4',
2671 0x12 => 'High Quality Audio Profile @ Level 5',
2672 0x13 => 'High Quality Audio Profile @ Level 6',
2673 0x14 => 'High Quality Audio Profile @ Level 7',
2674 0x15 => 'High Quality Audio Profile @ Level 8',
2675 0x16 => 'Low Delay Audio Profile @ Level 1',
2676 0x17 => 'Low Delay Audio Profile @ Level 2',
2677 0x18 => 'Low Delay Audio Profile @ Level 3',
2678 0x19 => 'Low Delay Audio Profile @ Level 4',
2679 0x1A => 'Low Delay Audio Profile @ Level 5',
2680 0x1B => 'Low Delay Audio Profile @ Level 6',
2681 0x1C => 'Low Delay Audio Profile @ Level 7',
2682 0x1D => 'Low Delay Audio Profile @ Level 8',
2683 0x1E => 'Natural Audio Profile @ Level 1',
2684 0x1F => 'Natural Audio Profile @ Level 2',
2685 0x20 => 'Natural Audio Profile @ Level 3',
2686 0x21 => 'Natural Audio Profile @ Level 4',
2687 0x22 => 'Mobile Audio Internetworking Profile @ Level 1',
2688 0x23 => 'Mobile Audio Internetworking Profile @ Level 2',
2689 0x24 => 'Mobile Audio Internetworking Profile @ Level 3',
2690 0x25 => 'Mobile Audio Internetworking Profile @ Level 4',
2691 0x26 => 'Mobile Audio Internetworking Profile @ Level 5',
2692 0x27 => 'Mobile Audio Internetworking Profile @ Level 6',
2693 0x28 => 'AAC Profile @ Level 1',
2694 0x29 => 'AAC Profile @ Level 2',
2695 0x2A => 'AAC Profile @ Level 4',
2696 0x2B => 'AAC Profile @ Level 5',
2697 0x2C => 'High Efficiency AAC Profile @ Level 2',
2698 0x2D => 'High Efficiency AAC Profile @ Level 3',
2699 0x2E => 'High Efficiency AAC Profile @ Level 4',
2700 0x2F => 'High Efficiency AAC Profile @ Level 5',
2701 0xFE => 'Not part of MPEG-4 audio profiles',
2702 0xFF => 'No audio capability required',
2703 );
2704 }
2705 return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private');
2706 }
2707
2708 /**
2709 * @param int $video_profile_id
2710 *
2711 * @return string
2712 */
2713 public function QuicktimeIODSvideoProfileName($video_profile_id) {
2714 static $QuicktimeIODSvideoProfileNameLookup = array();
2715 if (empty($QuicktimeIODSvideoProfileNameLookup)) {
2716 $QuicktimeIODSvideoProfileNameLookup = array(
2717 0x00 => 'Reserved (0x00) Profile',
2718 0x01 => 'Simple Profile @ Level 1',
2719 0x02 => 'Simple Profile @ Level 2',
2720 0x03 => 'Simple Profile @ Level 3',
2721 0x08 => 'Simple Profile @ Level 0',
2722 0x10 => 'Simple Scalable Profile @ Level 0',
2723 0x11 => 'Simple Scalable Profile @ Level 1',
2724 0x12 => 'Simple Scalable Profile @ Level 2',
2725 0x15 => 'AVC/H264 Profile',
2726 0x21 => 'Core Profile @ Level 1',
2727 0x22 => 'Core Profile @ Level 2',
2728 0x32 => 'Main Profile @ Level 2',
2729 0x33 => 'Main Profile @ Level 3',
2730 0x34 => 'Main Profile @ Level 4',
2731 0x42 => 'N-bit Profile @ Level 2',
2732 0x51 => 'Scalable Texture Profile @ Level 1',
2733 0x61 => 'Simple Face Animation Profile @ Level 1',
2734 0x62 => 'Simple Face Animation Profile @ Level 2',
2735 0x63 => 'Simple FBA Profile @ Level 1',
2736 0x64 => 'Simple FBA Profile @ Level 2',
2737 0x71 => 'Basic Animated Texture Profile @ Level 1',
2738 0x72 => 'Basic Animated Texture Profile @ Level 2',
2739 0x81 => 'Hybrid Profile @ Level 1',
2740 0x82 => 'Hybrid Profile @ Level 2',
2741 0x91 => 'Advanced Real Time Simple Profile @ Level 1',
2742 0x92 => 'Advanced Real Time Simple Profile @ Level 2',
2743 0x93 => 'Advanced Real Time Simple Profile @ Level 3',
2744 0x94 => 'Advanced Real Time Simple Profile @ Level 4',
2745 0xA1 => 'Core Scalable Profile @ Level1',
2746 0xA2 => 'Core Scalable Profile @ Level2',
2747 0xA3 => 'Core Scalable Profile @ Level3',
2748 0xB1 => 'Advanced Coding Efficiency Profile @ Level 1',
2749 0xB2 => 'Advanced Coding Efficiency Profile @ Level 2',
2750 0xB3 => 'Advanced Coding Efficiency Profile @ Level 3',
2751 0xB4 => 'Advanced Coding Efficiency Profile @ Level 4',
2752 0xC1 => 'Advanced Core Profile @ Level 1',
2753 0xC2 => 'Advanced Core Profile @ Level 2',
2754 0xD1 => 'Advanced Scalable Texture @ Level1',
2755 0xD2 => 'Advanced Scalable Texture @ Level2',
2756 0xE1 => 'Simple Studio Profile @ Level 1',
2757 0xE2 => 'Simple Studio Profile @ Level 2',
2758 0xE3 => 'Simple Studio Profile @ Level 3',
2759 0xE4 => 'Simple Studio Profile @ Level 4',
2760 0xE5 => 'Core Studio Profile @ Level 1',
2761 0xE6 => 'Core Studio Profile @ Level 2',
2762 0xE7 => 'Core Studio Profile @ Level 3',
2763 0xE8 => 'Core Studio Profile @ Level 4',
2764 0xF0 => 'Advanced Simple Profile @ Level 0',
2765 0xF1 => 'Advanced Simple Profile @ Level 1',
2766 0xF2 => 'Advanced Simple Profile @ Level 2',
2767 0xF3 => 'Advanced Simple Profile @ Level 3',
2768 0xF4 => 'Advanced Simple Profile @ Level 4',
2769 0xF5 => 'Advanced Simple Profile @ Level 5',
2770 0xF7 => 'Advanced Simple Profile @ Level 3b',
2771 0xF8 => 'Fine Granularity Scalable Profile @ Level 0',
2772 0xF9 => 'Fine Granularity Scalable Profile @ Level 1',
2773 0xFA => 'Fine Granularity Scalable Profile @ Level 2',
2774 0xFB => 'Fine Granularity Scalable Profile @ Level 3',
2775 0xFC => 'Fine Granularity Scalable Profile @ Level 4',
2776 0xFD => 'Fine Granularity Scalable Profile @ Level 5',
2777 0xFE => 'Not part of MPEG-4 Visual profiles',
2778 0xFF => 'No visual capability required',
2779 );
2780 }
2781 return (isset($QuicktimeIODSvideoProfileNameLookup[$video_profile_id]) ? $QuicktimeIODSvideoProfileNameLookup[$video_profile_id] : 'ISO Reserved Profile');
2782 }
2783
2784 /**
2785 * @param int $rtng
2786 *
2787 * @return string
2788 */
2789 public function QuicktimeContentRatingLookup($rtng) {
2790 static $QuicktimeContentRatingLookup = array();
2791 if (empty($QuicktimeContentRatingLookup)) {
2792 $QuicktimeContentRatingLookup[0] = 'None';
2793 $QuicktimeContentRatingLookup[1] = 'Explicit';
2794 $QuicktimeContentRatingLookup[2] = 'Clean';
2795 $QuicktimeContentRatingLookup[4] = 'Explicit (old)';
2796 }
2797 return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid');
2798 }
2799
2800 /**
2801 * @param int $akid
2802 *
2803 * @return string
2804 */
2805 public function QuicktimeStoreAccountTypeLookup($akid) {
2806 static $QuicktimeStoreAccountTypeLookup = array();
2807 if (empty($QuicktimeStoreAccountTypeLookup)) {
2808 $QuicktimeStoreAccountTypeLookup[0] = 'iTunes';
2809 $QuicktimeStoreAccountTypeLookup[1] = 'AOL';
2810 }
2811 return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid');
2812 }
2813
2814 /**
2815 * @param int $sfid
2816 *
2817 * @return string
2818 */
2819 public function QuicktimeStoreFrontCodeLookup($sfid) {
2820 static $QuicktimeStoreFrontCodeLookup = array();
2821 if (empty($QuicktimeStoreFrontCodeLookup)) {
2822 $QuicktimeStoreFrontCodeLookup[143460] = 'Australia';
2823 $QuicktimeStoreFrontCodeLookup[143445] = 'Austria';
2824 $QuicktimeStoreFrontCodeLookup[143446] = 'Belgium';
2825 $QuicktimeStoreFrontCodeLookup[143455] = 'Canada';
2826 $QuicktimeStoreFrontCodeLookup[143458] = 'Denmark';
2827 $QuicktimeStoreFrontCodeLookup[143447] = 'Finland';
2828 $QuicktimeStoreFrontCodeLookup[143442] = 'France';
2829 $QuicktimeStoreFrontCodeLookup[143443] = 'Germany';
2830 $QuicktimeStoreFrontCodeLookup[143448] = 'Greece';
2831 $QuicktimeStoreFrontCodeLookup[143449] = 'Ireland';
2832 $QuicktimeStoreFrontCodeLookup[143450] = 'Italy';
2833 $QuicktimeStoreFrontCodeLookup[143462] = 'Japan';
2834 $QuicktimeStoreFrontCodeLookup[143451] = 'Luxembourg';
2835 $QuicktimeStoreFrontCodeLookup[143452] = 'Netherlands';
2836 $QuicktimeStoreFrontCodeLookup[143461] = 'New Zealand';
2837 $QuicktimeStoreFrontCodeLookup[143457] = 'Norway';
2838 $QuicktimeStoreFrontCodeLookup[143453] = 'Portugal';
2839 $QuicktimeStoreFrontCodeLookup[143454] = 'Spain';
2840 $QuicktimeStoreFrontCodeLookup[143456] = 'Sweden';
2841 $QuicktimeStoreFrontCodeLookup[143459] = 'Switzerland';
2842 $QuicktimeStoreFrontCodeLookup[143444] = 'United Kingdom';
2843 $QuicktimeStoreFrontCodeLookup[143441] = 'United States';
2844 }
2845 return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid');
2846 }
2847
2848 /**
2849 * @param string $keyname
2850 * @param string|array $data
2851 * @param string $boxname
2852 *
2853 * @return bool
2854 */
2855 public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') {
2856 static $handyatomtranslatorarray = array();
2857 if (empty($handyatomtranslatorarray)) {
2858 // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
2859 // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
2860 // http://atomicparsley.sourceforge.net/mpeg-4files.html
2861 // https://code.google.com/p/mp4v2/wiki/iTunesMetadata
2862 $handyatomtranslatorarray["\xA9".'alb'] = 'album'; // iTunes 4.0
2863 $handyatomtranslatorarray["\xA9".'ART'] = 'artist';
2864 $handyatomtranslatorarray["\xA9".'art'] = 'artist'; // iTunes 4.0
2865 $handyatomtranslatorarray["\xA9".'aut'] = 'author';
2866 $handyatomtranslatorarray["\xA9".'cmt'] = 'comment'; // iTunes 4.0
2867 $handyatomtranslatorarray["\xA9".'com'] = 'comment';
2868 $handyatomtranslatorarray["\xA9".'cpy'] = 'copyright';
2869 $handyatomtranslatorarray["\xA9".'day'] = 'creation_date'; // iTunes 4.0
2870 $handyatomtranslatorarray["\xA9".'dir'] = 'director';
2871 $handyatomtranslatorarray["\xA9".'ed1'] = 'edit1';
2872 $handyatomtranslatorarray["\xA9".'ed2'] = 'edit2';
2873 $handyatomtranslatorarray["\xA9".'ed3'] = 'edit3';
2874 $handyatomtranslatorarray["\xA9".'ed4'] = 'edit4';
2875 $handyatomtranslatorarray["\xA9".'ed5'] = 'edit5';
2876 $handyatomtranslatorarray["\xA9".'ed6'] = 'edit6';
2877 $handyatomtranslatorarray["\xA9".'ed7'] = 'edit7';
2878 $handyatomtranslatorarray["\xA9".'ed8'] = 'edit8';
2879 $handyatomtranslatorarray["\xA9".'ed9'] = 'edit9';
2880 $handyatomtranslatorarray["\xA9".'enc'] = 'encoded_by';
2881 $handyatomtranslatorarray["\xA9".'fmt'] = 'format';
2882 $handyatomtranslatorarray["\xA9".'gen'] = 'genre'; // iTunes 4.0
2883 $handyatomtranslatorarray["\xA9".'grp'] = 'grouping'; // iTunes 4.2
2884 $handyatomtranslatorarray["\xA9".'hst'] = 'host_computer';
2885 $handyatomtranslatorarray["\xA9".'inf'] = 'information';
2886 $handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics'; // iTunes 5.0
2887 $handyatomtranslatorarray["\xA9".'mak'] = 'make';
2888 $handyatomtranslatorarray["\xA9".'mod'] = 'model';
2889 $handyatomtranslatorarray["\xA9".'nam'] = 'title'; // iTunes 4.0
2890 $handyatomtranslatorarray["\xA9".'ope'] = 'composer';
2891 $handyatomtranslatorarray["\xA9".'prd'] = 'producer';
2892 $handyatomtranslatorarray["\xA9".'PRD'] = 'product';
2893 $handyatomtranslatorarray["\xA9".'prf'] = 'performers';
2894 $handyatomtranslatorarray["\xA9".'req'] = 'system_requirements';
2895 $handyatomtranslatorarray["\xA9".'src'] = 'source_credit';
2896 $handyatomtranslatorarray["\xA9".'swr'] = 'software';
2897 $handyatomtranslatorarray["\xA9".'too'] = 'encoding_tool'; // iTunes 4.0
2898 $handyatomtranslatorarray["\xA9".'trk'] = 'track_number';
2899 $handyatomtranslatorarray["\xA9".'url'] = 'url';
2900 $handyatomtranslatorarray["\xA9".'wrn'] = 'warning';
2901 $handyatomtranslatorarray["\xA9".'wrt'] = 'composer';
2902 $handyatomtranslatorarray['aART'] = 'album_artist';
2903 $handyatomtranslatorarray['apID'] = 'purchase_account';
2904 $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9
2905 $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0
2906 $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0
2907 $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0?
2908 $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0
2909 $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0
2910 $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9
2911 $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0
2912 $handyatomtranslatorarray['hdvd'] = 'hd_video'; // iTunes 4.0
2913 $handyatomtranslatorarray['ldes'] = 'description_long'; //
2914 $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9
2915 $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9
2916 $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0
2917 $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2
2918 $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9
2919 $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0
2920 $handyatomtranslatorarray['soaa'] = 'sort_album_artist'; //
2921 $handyatomtranslatorarray['soal'] = 'sort_album'; //
2922 $handyatomtranslatorarray['soar'] = 'sort_artist'; //
2923 $handyatomtranslatorarray['soco'] = 'sort_composer'; //
2924 $handyatomtranslatorarray['sonm'] = 'sort_title'; //
2925 $handyatomtranslatorarray['sosn'] = 'sort_show'; //
2926 $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9
2927 $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0
2928 $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0
2929 $handyatomtranslatorarray['tven'] = 'tv_episode_id'; //
2930 $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0
2931 $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0
2932 $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0
2933 $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0
2934
2935 // boxnames:
2936 /*
2937 $handyatomtranslatorarray['iTunSMPB'] = 'iTunSMPB';
2938 $handyatomtranslatorarray['iTunNORM'] = 'iTunNORM';
2939 $handyatomtranslatorarray['Encoding Params'] = 'Encoding Params';
2940 $handyatomtranslatorarray['replaygain_track_gain'] = 'replaygain_track_gain';
2941 $handyatomtranslatorarray['replaygain_track_peak'] = 'replaygain_track_peak';
2942 $handyatomtranslatorarray['replaygain_track_minmax'] = 'replaygain_track_minmax';
2943 $handyatomtranslatorarray['MusicIP PUID'] = 'MusicIP PUID';
2944 $handyatomtranslatorarray['MusicBrainz Artist Id'] = 'MusicBrainz Artist Id';
2945 $handyatomtranslatorarray['MusicBrainz Album Id'] = 'MusicBrainz Album Id';
2946 $handyatomtranslatorarray['MusicBrainz Album Artist Id'] = 'MusicBrainz Album Artist Id';
2947 $handyatomtranslatorarray['MusicBrainz Track Id'] = 'MusicBrainz Track Id';
2948 $handyatomtranslatorarray['MusicBrainz Disc Id'] = 'MusicBrainz Disc Id';
2949
2950 // http://age.hobba.nl/audio/tag_frame_reference.html
2951 $handyatomtranslatorarray['PLAY_COUNTER'] = 'play_counter'; // Foobar2000 - https://www.getid3.org/phpBB3/viewtopic.php?t=1355
2952 $handyatomtranslatorarray['MEDIATYPE'] = 'mediatype'; // Foobar2000 - https://www.getid3.org/phpBB3/viewtopic.php?t=1355
2953 */
2954 }
2955 $info = &$this->getid3->info;
2956 $comment_key = '';
2957 if ($boxname && ($boxname != $keyname)) {
2958 $comment_key = (isset($handyatomtranslatorarray[$boxname]) ? $handyatomtranslatorarray[$boxname] : $boxname);
2959 } elseif (isset($handyatomtranslatorarray[$keyname])) {
2960 $comment_key = $handyatomtranslatorarray[$keyname];
2961 }
2962 if ($comment_key) {
2963 if ($comment_key == 'picture') {
2964 // already copied directly into [comments][picture] elsewhere, do not re-copy here
2965 return true;
2966 }
2967 $gooddata = array($data);
2968 if ($comment_key == 'genre') {
2969 // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
2970 $gooddata = explode(';', $data);
2971 }
2972 foreach ($gooddata as $data) {
2973 if (!empty($info['quicktime']['comments'][$comment_key]) && in_array($data, $info['quicktime']['comments'][$comment_key], true)) {
2974 // avoid duplicate copies of identical data
2975 continue;
2976 }
2977 $info['quicktime']['comments'][$comment_key][] = $data;
2978 }
2979 }
2980 return true;
2981 }
2982
2983 /**
2984 * @param string $lstring
2985 * @param int $count
2986 *
2987 * @return string
2988 */
2989 public function LociString($lstring, &$count) {
2990 // Loci strings are UTF-8 or UTF-16 and null (x00/x0000) terminated. UTF-16 has a BOM
2991 // Also need to return the number of bytes the string occupied so additional fields can be extracted
2992 $len = strlen($lstring);
2993 if ($len == 0) {
2994 $count = 0;
2995 return '';
2996 }
2997 if ($lstring[0] == "\x00") {
2998 $count = 1;
2999 return '';
3000 }
3001 // check for BOM
3002 if (($len > 2) && ((($lstring[0] == "\xFE") && ($lstring[1] == "\xFF")) || (($lstring[0] == "\xFF") && ($lstring[1] == "\xFE")))) {
3003 // UTF-16
3004 if (preg_match('/(.*)\x00/', $lstring, $lmatches)) {
3005 $count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000
3006 return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]);
3007 } else {
3008 return '';
3009 }
3010 }
3011 // UTF-8
3012 if (preg_match('/(.*)\x00/', $lstring, $lmatches)) {
3013 $count = strlen($lmatches[1]) + 1; //account for trailing \x00
3014 return $lmatches[1];
3015 }
3016 return '';
3017 }
3018
3019 /**
3020 * @param string $nullterminatedstring
3021 *
3022 * @return string
3023 */
3024 public function NoNullString($nullterminatedstring) {
3025 // remove the single null terminator on null terminated strings
3026 if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") {
3027 return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1);
3028 }
3029 return $nullterminatedstring;
3030 }
3031
3032 /**
3033 * @param string $pascalstring
3034 *
3035 * @return string
3036 */
3037 public function Pascal2String($pascalstring) {
3038 // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string
3039 return substr($pascalstring, 1);
3040 }
3041
3042 /**
3043 * @param string $pascalstring
3044 *
3045 * @return string
3046 */
3047 public function MaybePascal2String($pascalstring) {
3048 // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string
3049 // Check if string actually is in this format or written incorrectly, straight string, or null-terminated string
3050 if (ord(substr($pascalstring, 0, 1)) == (strlen($pascalstring) - 1)) {
3051 return substr($pascalstring, 1);
3052 } elseif (substr($pascalstring, -1, 1) == "\x00") {
3053 // appears to be null-terminated instead of Pascal-style
3054 return substr($pascalstring, 0, -1);
3055 }
3056 return $pascalstring;
3057 }
3058
3059
3060 /**
3061 * Helper functions for m4b audiobook chapters
3062 * code by Steffen Hartmann 2015-Nov-08.
3063 *
3064 * @param array $info
3065 * @param string $tag
3066 * @param string $history
3067 * @param array $result
3068 */
3069 public function search_tag_by_key($info, $tag, $history, &$result) {
3070 foreach ($info as $key => $value) {
3071 $key_history = $history.'/'.$key;
3072 if ($key === $tag) {
3073 $result[] = array($key_history, $info);
3074 } else {
3075 if (is_array($value)) {
3076 $this->search_tag_by_key($value, $tag, $key_history, $result);
3077 }
3078 }
3079 }
3080 }
3081
3082 /**
3083 * @param array $info
3084 * @param string $k
3085 * @param string $v
3086 * @param string $history
3087 * @param array $result
3088 */
3089 public function search_tag_by_pair($info, $k, $v, $history, &$result) {
3090 foreach ($info as $key => $value) {
3091 $key_history = $history.'/'.$key;
3092 if (($key === $k) && ($value === $v)) {
3093 $result[] = array($key_history, $info);
3094 } else {
3095 if (is_array($value)) {
3096 $this->search_tag_by_pair($value, $k, $v, $key_history, $result);
3097 }
3098 }
3099 }
3100 }
3101
3102 /**
3103 * @param array $info
3104 *
3105 * @return array
3106 */
3107 public function quicktime_time_to_sample_table($info) {
3108 $res = array();
3109 $this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res);
3110 foreach ($res as $value) {
3111 $stbl_res = array();
3112 $this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res);
3113 if (count($stbl_res) > 0) {
3114 $stts_res = array();
3115 $this->search_tag_by_key($value[1], 'time_to_sample_table', $value[0], $stts_res);
3116 if (count($stts_res) > 0) {
3117 return $stts_res[0][1]['time_to_sample_table'];
3118 }
3119 }
3120 }
3121 return array();
3122 }
3123
3124
3125 /**
3126 * @param array $info
3127 *
3128 * @return int
3129 */
3130 public function quicktime_bookmark_time_scale($info) {
3131 $time_scale = '';
3132 $ts_prefix_len = 0;
3133 $res = array();
3134 $this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res);
3135 foreach ($res as $value) {
3136 $stbl_res = array();
3137 $this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res);
3138 if (count($stbl_res) > 0) {
3139 $ts_res = array();
3140 $this->search_tag_by_key($info['quicktime']['moov'], 'time_scale', 'quicktime/moov', $ts_res);
3141 foreach ($ts_res as $sub_value) {
3142 $prefix = substr($sub_value[0], 0, -12);
3143 if ((substr($stbl_res[0][0], 0, strlen($prefix)) === $prefix) && ($ts_prefix_len < strlen($prefix))) {
3144 $time_scale = $sub_value[1]['time_scale'];
3145 $ts_prefix_len = strlen($prefix);
3146 }
3147 }
3148 }
3149 }
3150 return $time_scale;
3151 }
3152 /*
3153 // END helper functions for m4b audiobook chapters
3154 */
3155
3156
3157}
3158
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1