wp/wp-includes/ID3/module.audio-video.quicktime.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
--- a/wp/wp-includes/ID3/module.audio-video.quicktime.php	Thu Sep 29 08:06:27 2022 +0200
+++ b/wp/wp-includes/ID3/module.audio-video.quicktime.php	Fri Sep 05 18:40:08 2025 +0200
@@ -61,12 +61,16 @@
 			$this->fseek($offset);
 			$AtomHeader = $this->fread(8);
 
+			// https://github.com/JamesHeinrich/getID3/issues/382
+			// Atom sizes are stored as 32-bit number in most cases, but sometimes (notably for "mdat")
+			// a 64-bit value is required, in which case the normal 32-bit size field is set to 0x00000001
+			// and the 64-bit "real" size value is the next 8 bytes.
+			$atom_size_extended_bytes = 0;
 			$atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4));
 			$atomname = substr($AtomHeader, 4, 4);
-
-			// 64-bit MOV patch by jlegateØktnc*com
 			if ($atomsize == 1) {
-				$atomsize = getid3_lib::BigEndian2Int($this->fread(8));
+				$atom_size_extended_bytes = 8;
+				$atomsize = getid3_lib::BigEndian2Int($this->fread($atom_size_extended_bytes));
 			}
 
 			if (($offset + $atomsize) > $info['avdataend']) {
@@ -85,12 +89,14 @@
 				$info['quicktime'][$atomname]['offset'] = $offset;
 				break;
 			}
-
 			$atomHierarchy = array();
-			$parsedAtomData = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize, $atom_data_read_buffer_size)), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms);
+			$parsedAtomData = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize - $atom_size_extended_bytes, $atom_data_read_buffer_size)), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms);
 			$parsedAtomData['name']   = $atomname;
 			$parsedAtomData['size']   = $atomsize;
 			$parsedAtomData['offset'] = $offset;
+			if ($atom_size_extended_bytes) {
+				$parsedAtomData['xsize_bytes'] = $atom_size_extended_bytes;
+			}
 			if (in_array($atomname, array('uuid'))) {
 				@$info['quicktime'][$atomname][] = $parsedAtomData;
 			} else {
@@ -108,7 +114,7 @@
 			unset($info['avdataend_tmp']);
 		}
 
-		if (!empty($info['quicktime']['comments']['chapters']) && is_array($info['quicktime']['comments']['chapters']) && (count($info['quicktime']['comments']['chapters']) > 0)) {
+		if (isset($info['quicktime']['comments']['chapters']) && is_array($info['quicktime']['comments']['chapters']) && (count($info['quicktime']['comments']['chapters']) > 0)) {
 			$durations = $this->quicktime_time_to_sample_table($info);
 			for ($i = 0; $i < count($info['quicktime']['comments']['chapters']); $i++) {
 				$bookmark = array();
@@ -146,7 +152,7 @@
 					} elseif (strlen($lat_deg) == 4) {  // [+-]DDMM.M
 						$ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0').$lat_deg_dec / 60);
 					} elseif (strlen($lat_deg) == 6) {  // [+-]DDMMSS.S
-						$ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec / 3600);
+						$ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval((int) ltrim(substr($lat_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec / 3600);
 					}
 
 					if (strlen($lon_deg) == 3) {        // [+-]DDD.D
@@ -154,7 +160,7 @@
 					} elseif (strlen($lon_deg) == 5) {  // [+-]DDDMM.M
 						$ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0').$lon_deg_dec / 60);
 					} elseif (strlen($lon_deg) == 7) {  // [+-]DDDMMSS.S
-						$ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec / 3600);
+						$ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval((int) ltrim(substr($lon_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec / 3600);
 					}
 
 					if (strlen($alt_deg) == 3) {        // [+-]DDD.D
@@ -162,7 +168,7 @@
 					} elseif (strlen($alt_deg) == 5) {  // [+-]DDDMM.M
 						$ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0').$alt_deg_dec / 60);
 					} elseif (strlen($alt_deg) == 7) {  // [+-]DDDMMSS.S
-						$ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec / 3600);
+						$ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval((int) ltrim(substr($alt_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec / 3600);
 					}
 
 					foreach (array('latitude', 'longitude', 'altitude') as $key) {
@@ -259,7 +265,9 @@
 		} else {
 			switch ($atomname) {
 				case 'moov': // MOVie container atom
+				case 'moof': // MOvie Fragment box
 				case 'trak': // TRAcK container atom
+				case 'traf': // TRAck Fragment box
 				case 'clip': // CLIPping container atom
 				case 'matt': // track MATTe container atom
 				case 'edts': // EDiTS container atom
@@ -324,7 +332,7 @@
 							}
 						} elseif (isset($value_array['time_to_sample_table'])) {
 							foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) {
-								if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) {
+								if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0) && !empty($info['quicktime']['time_scale'])) {
 									$framerate  = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3);
 									$framecount = $value_array2['sample_count'];
 								}
@@ -768,8 +776,8 @@
 
 
 				case 'stsd': // Sample Table Sample Description atom
-					$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
-					$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
+					$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1)); // hardcoded: 0x00
+					$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x000000
 					$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
 
 					// see: https://github.com/JamesHeinrich/getID3/issues/111
@@ -797,7 +805,6 @@
 						$stsdEntriesDataOffset += 2;
 						$atom_structure['sample_description_table'][$i]['data']             =                           substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2));
 						$stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2);
-
 						if (substr($atom_structure['sample_description_table'][$i]['data'],  1, 54) == 'application/octet-stream;type=com.parrot.videometadata') {
 							// special handling for apparently-malformed (TextMetaDataSampleEntry?) data for some version of Parrot drones
 							$atom_structure['sample_description_table'][$i]['parrot_frame_metadata']['mime_type']        =       substr($atom_structure['sample_description_table'][$i]['data'],  1, 55);
@@ -843,6 +850,7 @@
 									case 'dvcp':
 									case 'gif ':
 									case 'h263':
+									case 'hvc1':
 									case 'jpeg':
 									case 'kpcd':
 									case 'mjpa':
@@ -884,7 +892,8 @@
 										break;
 
 									case 'mp4a':
-									default:
+										$atom_structure['sample_description_table'][$i]['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_structure['sample_description_table'][$i]['data'], 20), $baseoffset + $stsdEntriesDataOffset - 20 - 16, $atomHierarchy, $ParseAllPossibleAtoms);
+
 										$info['quicktime']['audio']['codec']       = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
 										$info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate'];
 										$info['quicktime']['audio']['channels']    = $atom_structure['sample_description_table'][$i]['audio_channels'];
@@ -910,6 +919,9 @@
 												break;
 										}
 										break;
+
+									default:
+										break;
 								}
 								break;
 
@@ -1541,6 +1553,21 @@
 					unset($mdat_offset, $chapter_string_length, $chapter_matches);
 					break;
 
+				case 'ID32': // ID3v2
+					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
+
+					$getid3_temp = new getID3();
+					$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
+					$getid3_id3v2 = new getid3_id3v2($getid3_temp);
+					$getid3_id3v2->StartingOffset = $atom_structure['offset'] + 14; // framelength(4)+framename(4)+flags(4)+??(2)
+					if ($atom_structure['valid'] = $getid3_id3v2->Analyze()) {
+						$atom_structure['id3v2'] = $getid3_temp->info['id3v2'];
+					} else {
+						$this->warning('ID32 frame at offset '.$atom_structure['offset'].' did not parse');
+					}
+					unset($getid3_temp, $getid3_id3v2);
+					break;
+
 				case 'free': // FREE space atom
 				case 'skip': // SKIP atom
 				case 'wide': // 64-bit expansion placeholder atom
@@ -1642,7 +1669,7 @@
 						);
 						$atom_structure['data'] = $atom_data;
 						$atom_structure['image_mime'] = 'image/jpeg';
-						$atom_structure['description'] = isset($descriptions[$atomname]) ? $descriptions[$atomname] : 'Nikon preview image';
+						$atom_structure['description'] = $descriptions[$atomname];
 						$info['quicktime']['comments']['picture'][] = array(
 							'image_mime' => $atom_structure['image_mime'],
 							'data' => $atom_data,
@@ -1659,7 +1686,7 @@
 				case 'NCHD': // Nikon:MakerNoteVersion  - https://exiftool.org/TagNames/Nikon.html
 					$makerNoteVersion = '';
 					for ($i = 0, $iMax = strlen($atom_data); $i < $iMax; ++$i) {
-						if (ord($atom_data[$i]) >= 0x00 && ord($atom_data[$i]) <= 0x1F) {
+						if (ord($atom_data[$i]) <= 0x1F) {
 							$makerNoteVersion .= ' '.ord($atom_data[$i]);
 						} else {
 							$makerNoteVersion .= $atom_data[$i];
@@ -1700,7 +1727,8 @@
 					$atom_structure['language'] =                           substr($atom_data, 4 + 0, 2);
 					$atom_structure['unknown']  = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2));
 					$atom_structure['data']     =                           substr($atom_data, 4 + 4);
-					$atom_structure['key_name'] = @$info['quicktime']['temp_meta_key_names'][$metaDATAkey++];
+					$atom_structure['key_name'] = (isset($info['quicktime']['temp_meta_key_names'][$metaDATAkey]) ? $info['quicktime']['temp_meta_key_names'][$metaDATAkey] : '');
+					$metaDATAkey++;
 
 					if ($atom_structure['key_name'] && $atom_structure['data']) {
 						@$info['quicktime']['comments'][str_replace('com.apple.quicktime.', '', $atom_structure['key_name'])][] = $atom_structure['data'];
@@ -2075,6 +2103,119 @@
 					$atom_structure['track_number'] = getid3_lib::BigEndian2Int($atom_data);
 					break;
 
+
+				case 'esds': // Elementary Stream DeScriptor
+					// https://github.com/JamesHeinrich/getID3/issues/414
+					// https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/formats/mp4/es_descriptor.cc
+					// https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/formats/mp4/es_descriptor.h
+					$atom_structure['version']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1)); // hardcoded: 0x00
+					$atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x000000
+					$esds_offset = 4;
+
+					$atom_structure['ES_DescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+					$esds_offset += 1;
+					if ($atom_structure['ES_DescrTag'] != 0x03) {
+						$this->warning('expecting esds.ES_DescrTag = 0x03, found 0x'.getid3_lib::PrintHexBytes($atom_structure['ES_DescrTag']).'), at offset '.$atom_structure['offset']);
+						break;
+					}
+					$atom_structure['ES_DescrSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
+
+					$atom_structure['ES_ID'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2));
+					$esds_offset += 2;
+					$atom_structure['ES_flagsraw'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+					$esds_offset += 1;
+					$atom_structure['ES_flags']['stream_dependency'] = (bool) ($atom_structure['ES_flagsraw'] & 0x80);
+					$atom_structure['ES_flags']['url_flag']          = (bool) ($atom_structure['ES_flagsraw'] & 0x40);
+					$atom_structure['ES_flags']['ocr_stream']        = (bool) ($atom_structure['ES_flagsraw'] & 0x20);
+					$atom_structure['ES_stream_priority']            =        ($atom_structure['ES_flagsraw'] & 0x1F);
+					if ($atom_structure['ES_flags']['url_flag']) {
+						$this->warning('Unsupported esds.url_flag enabled at offset '.$atom_structure['offset']);
+						break;
+					}
+					if ($atom_structure['ES_flags']['stream_dependency']) {
+						$atom_structure['ES_dependsOn_ES_ID'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2));
+						$esds_offset += 2;
+					}
+					if ($atom_structure['ES_flags']['ocr_stream']) {
+						$atom_structure['ES_OCR_ES_Id'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2));
+						$esds_offset += 2;
+					}
+
+					$atom_structure['ES_DecoderConfigDescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+					$esds_offset += 1;
+					if ($atom_structure['ES_DecoderConfigDescrTag'] != 0x04) {
+						$this->warning('expecting esds.ES_DecoderConfigDescrTag = 0x04, found 0x'.getid3_lib::PrintHexBytes($atom_structure['ES_DecoderConfigDescrTag']).'), at offset '.$atom_structure['offset']);
+						break;
+					}
+					$atom_structure['ES_DecoderConfigDescrTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
+
+					$atom_structure['ES_objectTypeIndication'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+					$esds_offset += 1;
+					// https://stackoverflow.com/questions/3987850
+					// 0x40 = "Audio ISO/IEC 14496-3"                       = MPEG-4 Audio
+					// 0x67 = "Audio ISO/IEC 13818-7 LowComplexity Profile" = MPEG-2 AAC LC
+					// 0x69 = "Audio ISO/IEC 13818-3"                       = MPEG-2 Backward Compatible Audio (MPEG-2 Layers 1, 2, and 3)
+					// 0x6B = "Audio ISO/IEC 11172-3"                       = MPEG-1 Audio (MPEG-1 Layers 1, 2, and 3)
+
+					$streamTypePlusFlags = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+					$esds_offset += 1;
+					$atom_structure['ES_streamType'] =        ($streamTypePlusFlags & 0xFC) >> 2;
+					$atom_structure['ES_upStream']   = (bool) ($streamTypePlusFlags & 0x02) >> 1;
+					$atom_structure['ES_bufferSizeDB'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 3));
+					$esds_offset += 3;
+					$atom_structure['ES_maxBitrate'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 4));
+					$esds_offset += 4;
+					$atom_structure['ES_avgBitrate'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 4));
+					$esds_offset += 4;
+					if ($atom_structure['ES_avgBitrate']) {
+						$info['quicktime']['audio']['bitrate'] = $atom_structure['ES_avgBitrate'];
+						$info['audio']['bitrate']              = $atom_structure['ES_avgBitrate'];
+					}
+
+					$atom_structure['ES_DecSpecificInfoTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+					$esds_offset += 1;
+					if ($atom_structure['ES_DecSpecificInfoTag'] != 0x05) {
+						$this->warning('expecting esds.ES_DecSpecificInfoTag = 0x05, found 0x'.getid3_lib::PrintHexBytes($atom_structure['ES_DecSpecificInfoTag']).'), at offset '.$atom_structure['offset']);
+						break;
+					}
+					$atom_structure['ES_DecSpecificInfoTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
+
+					$atom_structure['ES_DecSpecificInfo'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, $atom_structure['ES_DecSpecificInfoTagSize']));
+					$esds_offset += $atom_structure['ES_DecSpecificInfoTagSize'];
+
+					$atom_structure['ES_SLConfigDescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
+					$esds_offset += 1;
+					if ($atom_structure['ES_SLConfigDescrTag'] != 0x06) {
+						$this->warning('expecting esds.ES_SLConfigDescrTag = 0x05, found 0x'.getid3_lib::PrintHexBytes($atom_structure['ES_SLConfigDescrTag']).'), at offset '.$atom_structure['offset']);
+						break;
+					}
+					$atom_structure['ES_SLConfigDescrTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);
+
+					$atom_structure['ES_SLConfigDescr'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, $atom_structure['ES_SLConfigDescrTagSize']));
+					$esds_offset += $atom_structure['ES_SLConfigDescrTagSize'];
+					break;
+
+// AVIF-related - https://docs.rs/avif-parse/0.13.2/src/avif_parse/boxes.rs.html
+				case 'pitm': // Primary ITeM
+				case 'iloc': // Item LOCation
+				case 'iinf': // Item INFo
+				case 'iref': // Image REFerence
+				case 'iprp': // Image PRoPerties
+$this->error('AVIF files not currently supported');
+					$atom_structure['data'] = $atom_data;
+					break;
+
+				case 'tfdt': // Track Fragment base media Decode Time box
+				case 'tfhd': // Track Fragment HeaDer box
+				case 'mfhd': // Movie Fragment HeaDer box
+				case 'trun': // Track fragment RUN box
+$this->error('fragmented mp4 files not currently supported');
+					$atom_structure['data'] = $atom_data;
+					break;
+
+				case 'mvex': // MoVie EXtends box
+				case 'pssh': // Protection System Specific Header box
+				case 'sidx': // Segment InDeX box
 				default:
 					$this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).'), '.$atomsize.' bytes at offset '.$baseoffset);
 					$atom_structure['data'] = $atom_data;
@@ -2323,6 +2464,7 @@
 			$QuicktimeVideoCodecLookup['gif '] = 'GIF';
 			$QuicktimeVideoCodecLookup['h261'] = 'H261';
 			$QuicktimeVideoCodecLookup['h263'] = 'H263';
+			$QuicktimeVideoCodecLookup['hvc1'] = 'H.265/HEVC';
 			$QuicktimeVideoCodecLookup['IV41'] = 'Indeo4';
 			$QuicktimeVideoCodecLookup['jpeg'] = 'JPEG';
 			$QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD';
@@ -2943,6 +3085,7 @@
 		return array();
 	}
 
+
 	/**
 	 * @param array $info
 	 *