/** * Open file * * @param \SplFileInfo $file * * @return \GravityMedia\Metadata\GetId3\Factory */ public function open(\SplFileInfo $file) { if (!$this->getId3->openfile($file->getRealPath())) { $this->close(); throw new \RuntimeException(sprintf('Error while opening file "%s": %s.', $this->filename, implode(PHP_EOL, $this->info['error']))); } return new Factory($this); }
function GetMIMEtype($filename) { $filename = realpath($filename); if (!file_exists($filename)) { echo 'File does not exist: "' . htmlentities($filename) . '"<br>'; return ''; } elseif (!is_readable($filename)) { echo 'File is not readable: "' . htmlentities($filename) . '"<br>'; return ''; } // include getID3() library (can be in a different directory if full path is specified) require_once '../getid3/getid3.php'; // Initialize getID3 engine $getID3 = new getID3(); $DeterminedMIMEtype = ''; if ($fp = fopen($filename, 'rb')) { $getID3->openfile($filename); if (empty($getID3->info['error'])) { // ID3v2 is the only tag format that might be prepended in front of files, and it's non-trivial to skip, easier just to parse it and know where to skip to getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.tag.id3v2.php', __FILE__, true); $getid3_id3v2 = new getid3_id3v2($getID3); $getid3_id3v2->Analyze(); fseek($fp, $getID3->info['avdataoffset'], SEEK_SET); $formattest = fread($fp, 16); // 16 bytes is sufficient for any format except ISO CD-image fclose($fp); $DeterminedFormatInfo = $getID3->GetFileFormat($formattest); $DeterminedMIMEtype = $DeterminedFormatInfo['mime_type']; } else { echo 'Failed to getID3->openfile "' . htmlentities($filename) . '"<br>'; } } else { echo 'Failed to fopen "' . htmlentities($filename) . '"<br>'; } return $DeterminedMIMEtype; }
function Analyze() { $info =& $this->getid3->info; // http://www.matroska.org/technical/specs/index.html#EBMLBasics $offset = $info['avdataoffset']; $EBMLdata = ''; $EBMLdata_offset = $offset; if (!getid3_lib::intValueSupported($info['avdataend'])) { $this->warnings[] = 'This version of getID3() [' . $this->getid3->version() . '] may or may not correctly handle Matroska files larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB'; } while ($offset < $info['avdataend']) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $top_element_offset = $offset; $top_element_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $top_element_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); if ($top_element_length === false) { $this->warnings[] = 'invalid chunk length at ' . $top_element_offset; $offset = PHP_INT_MAX + 1; break; } $top_element_endoffset = $offset + $top_element_length; switch ($top_element_id) { case EBML_ID_EBML: $info['fileformat'] = 'matroska'; $info['matroska']['header']['offset'] = $top_element_offset; $info['matroska']['header']['length'] = $top_element_length; while ($offset < $top_element_endoffset) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $element_data = array(); $element_data_offset = $offset; $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $element_data['id_name'] = $this->EBMLidName($element_data['id']); $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $end_offset = $offset + $element_data['length']; switch ($element_data['id']) { case EBML_ID_VOID: // padding, ignore break; case EBML_ID_EBMLVERSION: case EBML_ID_EBMLREADVERSION: case EBML_ID_EBMLMAXIDLENGTH: case EBML_ID_EBMLMAXSIZELENGTH: case EBML_ID_DOCTYPEVERSION: case EBML_ID_DOCTYPEREADVERSION: $element_data['data'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length'])); break; case EBML_ID_DOCTYPE: $element_data['data'] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length']), ""); break; case EBML_ID_CRC32: // probably not useful, ignore unset($element_data); break; default: $this->warnings[] = 'Unhandled track.video element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $element_data['id'] . '::' . $element_data['id_name'] . ') at ' . $element_data_offset; break; } $offset = $end_offset; if (!empty($element_data)) { $info['matroska']['header']['elements'][] = $element_data; } } break; case EBML_ID_SEGMENT: $info['matroska']['segment'][0]['offset'] = $top_element_offset; $info['matroska']['segment'][0]['length'] = $top_element_length; $segment_key = -1; while ($offset < $info['avdataend']) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $element_data = array(); $element_data['offset'] = $offset; $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $element_data['id_name'] = $this->EBMLidName($element_data['id']); $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); if ($element_data['length'] === false) { $this->warnings[] = 'invalid chunk length at ' . $element_data['offset']; //$offset = PHP_INT_MAX + 1; $offset = $info['avdataend']; break; } $element_end = $offset + $element_data['length']; switch ($element_data['id']) { //case EBML_ID_CLUSTER: // // too many cluster entries, probably not useful // break; case false: $this->warnings[] = 'invalid ID at ' . $element_data['offset']; $offset = $element_end; continue 3; default: $info['matroska']['segments'][] = $element_data; break; } $segment_key++; switch ($element_data['id']) { case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $seek_entry = array(); $seek_entry['offset'] = $offset; $seek_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $seek_entry['id_name'] = $this->EBMLidName($seek_entry['id']); $seek_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $seek_end_offset = $offset + $seek_entry['length']; switch ($seek_entry['id']) { case EBML_ID_SEEK: // Contains a single seek entry to an EBML element while ($offset < $seek_end_offset) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $value = substr($EBMLdata, $offset - $EBMLdata_offset, $length); $offset += $length; switch ($id) { case EBML_ID_SEEKID: $dummy = 0; $seek_entry['target_id'] = $this->readEBMLint($value, $dummy); $seek_entry['target_name'] = $this->EBMLidName($seek_entry['target_id']); break; case EBML_ID_SEEKPOSITION: $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($value); break; case EBML_ID_CRC32: // probably not useful, ignore //$seek_entry['crc32'] = getid3_lib::PrintHexBytes($value, true, false, false); unset($seek_entry); break; default: $info['error'][] = 'Unhandled segment [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $id . ') at ' . $offset; break; } } if (!empty($seek_entry)) { $info['matroska']['seek'][] = $seek_entry; } //switch ($seek_entry['target_id']) { // case EBML_ID_CLUSTER: // // too many cluster seek points, probably not useful // break; // default: // $info['matroska']['seek'][] = $seek_entry; // break; //} break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled seekhead element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $seek_entry['id'] . '::' . $seek_entry['id_name'] . ') at ' . $offset; break; } $offset = $seek_end_offset; } break; case EBML_ID_TRACKS: // information about all tracks in segment $info['matroska']['tracks'] = $element_data; while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $track_entry = array(); $track_entry['offset'] = $offset; $track_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $track_entry['id_name'] = $this->EBMLidName($track_entry['id']); $track_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $track_entry_endoffset = $offset + $track_entry['length']; // $track_entry['offset'] is not the same as $offset, even though they were set equal a few lines up: $offset has been automagically incremented by readEMLint() switch ($track_entry['id']) { case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. while ($offset < $track_entry_endoffset) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_TRACKNUMBER: case EBML_ID_TRACKUID: case EBML_ID_TRACKTYPE: case EBML_ID_MINCACHE: case EBML_ID_MAXCACHE: case EBML_ID_MAXBLOCKADDITIONID: case EBML_ID_DEFAULTDURATION: // nanoseconds per frame $track_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_TRACKTIMECODESCALE: $track_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_CODECID: case EBML_ID_LANGUAGE: case EBML_ID_NAME: case EBML_ID_CODECNAME: case EBML_ID_CODECPRIVATE: $track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); break; // thought maybe it was a nice wFormatTag entry, but it's not :( //case EBML_ID_CODECPRIVATE: //$track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); //if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { // $track_entry[$subelement_idname.'_decoded'] = getid3_riff::RIFFparseWAVEFORMATex($track_entry[$subelement_idname]); // if (isset($track_entry[$subelement_idname.'_decoded']['raw']['wFormatTag'])) { // } //} else { // $this->warnings[] = 'failed to include "module.audio-video.riff.php" for parsing codec private data'; //} //break; // thought maybe it was a nice wFormatTag entry, but it's not :( //case EBML_ID_CODECPRIVATE: //$track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); //if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { // $track_entry[$subelement_idname.'_decoded'] = getid3_riff::RIFFparseWAVEFORMATex($track_entry[$subelement_idname]); // if (isset($track_entry[$subelement_idname.'_decoded']['raw']['wFormatTag'])) { // } //} else { // $this->warnings[] = 'failed to include "module.audio-video.riff.php" for parsing codec private data'; //} //break; case EBML_ID_FLAGENABLED: case EBML_ID_FLAGDEFAULT: case EBML_ID_FLAGFORCED: case EBML_ID_FLAGLACING: case EBML_ID_CODECDECODEALL: $track_entry[$subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_VIDEO: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_PIXELWIDTH: case EBML_ID_PIXELHEIGHT: case EBML_ID_STEREOMODE: case EBML_ID_PIXELCROPBOTTOM: case EBML_ID_PIXELCROPTOP: case EBML_ID_PIXELCROPLEFT: case EBML_ID_PIXELCROPRIGHT: case EBML_ID_DISPLAYWIDTH: case EBML_ID_DISPLAYHEIGHT: case EBML_ID_DISPLAYUNIT: case EBML_ID_ASPECTRATIOTYPE: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_FLAGINTERLACED: $track_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_GAMMAVALUE: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_COLOURSPACE: $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), ""); break; default: $this->warnings[] = 'Unhandled track.video element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } if (isset($track_entry[$this->EBMLidName(EBML_ID_CODECID)]) && $track_entry[$this->EBMLidName(EBML_ID_CODECID)] == 'V_MS/VFW/FOURCC' && isset($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)])) { if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, false)) { $track_entry['codec_private_parsed'] = getid3_riff::ParseBITMAPINFOHEADER($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)]); } else { $this->warnings[] = 'Unable to parse codec private data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio-video.riff.php"'; } } break; case EBML_ID_AUDIO: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CHANNELS: case EBML_ID_BITDEPTH: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_SAMPLINGFREQUENCY: case EBML_ID_OUTPUTSAMPLINGFREQUENCY: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CHANNELPOSITIONS: $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), ""); break; default: $this->warnings[] = 'Unhandled track.audio element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CONTENTENCODINGS: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CONTENTENCODING: while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CONTENTENCODINGORDER: case EBML_ID_CONTENTENCODINGSCOPE: case EBML_ID_CONTENTENCODINGTYPE: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CONTENTCOMPRESSION: while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CONTENTCOMPALGO: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; case EBML_ID_CONTENTCOMPSETTINGS: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } break; case EBML_ID_CONTENTENCRYPTION: while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CONTENTENCALGO: case EBML_ID_CONTENTSIGALGO: case EBML_ID_CONTENTSIGHASHALGO: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; case EBML_ID_CONTENTENCKEYID: case EBML_ID_CONTENTSIGNATURE: case EBML_ID_CONTENTSIGKEYID: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_sub_subelement_end; } break; default: $this->warnings[] = 'Unhandled track.contentencodings element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled track element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_CRC32: // probably not useful, ignore $offset = $track_entry_endoffset; break; default: $this->warnings[] = 'Unhandled track element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $track_entry['id'] . '::' . $track_entry['id_name'] . ') at ' . $track_entry['offset']; $offset = $track_entry_endoffset; break; } $info['matroska']['tracks']['tracks'][] = $track_entry; } break; case EBML_ID_INFO: // Contains the position of other level 1 elements $info_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CHAPTERTRANSLATEEDITIONUID: case EBML_ID_CHAPTERTRANSLATECODEC: case EBML_ID_TIMECODESCALE: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_DURATION: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_DATEUTC: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); $info_entry[$subelement_idname . '_unix'] = $this->EBMLdate2unix($info_entry[$subelement_idname]); break; case EBML_ID_SEGMENTUID: case EBML_ID_PREVUID: case EBML_ID_NEXTUID: case EBML_ID_SEGMENTFAMILY: case EBML_ID_CHAPTERTRANSLATEID: $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); break; case EBML_ID_SEGMENTFILENAME: case EBML_ID_PREVFILENAME: case EBML_ID_NEXTFILENAME: case EBML_ID_TITLE: case EBML_ID_MUXINGAPP: case EBML_ID_WRITINGAPP: $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); $info['matroska']['comments'][strtolower($subelement_idname)][] = $info_entry[$subelement_idname]; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled info element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } $info['matroska']['info'][] = $info_entry; break; case EBML_ID_CUES: $cues_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CUEPOINT: $cuepoint_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CUETRACKPOSITIONS: while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CUETRACK: $cuepoint_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled cues.cuepoint.cuetrackpositions element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CUETIME: $cuepoint_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled cues.cuepoint element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cues_entry[] = $cuepoint_entry; $offset = $sub_subelement_end; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled cues element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } $info['matroska']['cues'] = $cues_entry; break; case EBML_ID_TAGS: $tags_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; $tag_entry = array(); switch ($subelement_id) { case EBML_ID_WRITINGAPP: $tag_entry[$subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length); break; case EBML_ID_TAG: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_TARGETS: $targets_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_TARGETTYPEVALUE: $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); $targets_entry[strtolower($sub_sub_subelement_idname) . '_long'] = $this->MatroskaTargetTypeValue($targets_entry[$sub_sub_subelement_idname]); break; case EBML_ID_EDITIONUID: case EBML_ID_CHAPTERUID: case EBML_ID_ATTACHMENTUID: case EBML_ID_TAGTRACKUID: case EBML_ID_TAGCHAPTERUID: $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tag.targets element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $tag_entry[$sub_subelement_idname][] = $targets_entry; break; case EBML_ID_SIMPLETAG: //$tag_entry[$sub_subelement_idname][] = $simpletag_entry; $tag_entry[$sub_subelement_idname][] = $this->Handle_EMBL_ID_SIMPLETAG($offset, $sub_subelement_end); break; case EBML_ID_TARGETTYPE: $tag_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); break; case EBML_ID_TRACKUID: $tag_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tags.tag element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $offset = $sub_subelement_end; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled tags element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $tags_entry['tags'][] = $tag_entry; $offset = $subelement_end; } $info['matroska']['tags'] = $tags_entry['tags']; break; case EBML_ID_ATTACHMENTS: while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_ATTACHEDFILE: $attachedfile_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_FILEDESCRIPTION: case EBML_ID_FILENAME: case EBML_ID_FILEMIMETYPE: $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); break; case EBML_ID_FILEDATA: $attachedfile_entry['data_offset'] = $offset; $attachedfile_entry['data_length'] = $sub_subelement_length; do { if ($this->inline_attachments === false) { // skip entirely break; } if ($this->inline_attachments === true) { // great } elseif (is_int($this->inline_attachments)) { if ($this->inline_attachments < $sub_subelement_length) { // too big, skip $this->warnings[] = 'attachment at ' . $sub_subelement_offset . ' is too large to process inline (' . number_format($sub_subelement_length) . ' bytes)'; break; } } elseif (is_string($this->inline_attachments)) { $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { // cannot write, skip $this->warnings[] = 'attachment at ' . $sub_subelement_offset . ' cannot be saved to "' . $this->inline_attachments . '" (not writable)'; break; } } // if we get this far, must be OK $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset, $sub_subelement_length); $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); if (is_string($this->inline_attachments)) { $destination_filename = $this->inline_attachments . DIRECTORY_SEPARATOR . md5($info['filenamepath']) . '_' . $attachedfile_entry['data_offset']; if (!file_exists($destination_filename) || is_writable($destination_filename)) { file_put_contents($destination_filename, $attachedfile_entry[$sub_subelement_idname]); } else { $this->warnings[] = 'attachment at ' . $sub_subelement_offset . ' cannot be saved to "' . $destination_filename . '" (not writable)'; } $attachedfile_entry[$sub_subelement_idname . '_filename'] = $destination_filename; unset($attachedfile_entry[$sub_subelement_idname]); } } while (false); break; case EBML_ID_FILEUID: $attachedfile_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled attachment.attachedfile element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } if (!empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)]) && !empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]) && preg_match('#^image/#i', $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)])) { if ($this->inline_attachments === true || is_int($this->inline_attachments) && $this->inline_attachments >= strlen($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)])) { $attachedfile_entry['data'] = $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)]; $attachedfile_entry['image_mime'] = $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]; $info['matroska']['comments']['picture'][] = array('data' => $attachedfile_entry['data'], 'image_mime' => $attachedfile_entry['image_mime'], 'filename' => !empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILENAME)]) ? $attachedfile_entry[$this->EBMLidName(EBML_ID_FILENAME)] : ''); unset($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)], $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]); } } if (!empty($attachedfile_entry['image_mime']) && preg_match('#^image/#i', $attachedfile_entry['image_mime'])) { // don't add a second copy of attached images, which are grouped under the standard location [comments][picture] } else { $info['matroska']['attachments'][] = $attachedfile_entry; } $offset = $sub_subelement_end; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled tags element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_CHAPTERS: // not important to us, contains mostly actual audio/video data, ignore while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_EDITIONENTRY: $editionentry_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_EDITIONUID: $editionentry_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_EDITIONFLAGHIDDEN: case EBML_ID_EDITIONFLAGDEFAULT: case EBML_ID_EDITIONFLAGORDERED: $editionentry_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CHAPTERATOM: $chapteratom_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CHAPTERSEGMENTUID: case EBML_ID_CHAPTERSEGMENTEDITIONUID: $chapteratom_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); break; case EBML_ID_CHAPTERFLAGENABLED: case EBML_ID_CHAPTERFLAGHIDDEN: $chapteratom_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CHAPTERUID: case EBML_ID_CHAPTERTIMESTART: case EBML_ID_CHAPTERTIMEEND: $chapteratom_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CHAPTERTRACK: $chaptertrack_entry = array(); while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CHAPTERTRACKNUMBER: $chaptertrack_entry[$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chaptertrack element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_sub_subelement_id . '::' . $sub_sub_sub_subelement_idname . ') at ' . $sub_sub_sub_subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } $chapteratom_entry[$sub_sub_subelement_idname][] = $chaptertrack_entry; break; case EBML_ID_CHAPTERDISPLAY: $chapterdisplay_entry = array(); while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CHAPSTRING: case EBML_ID_CHAPLANGUAGE: case EBML_ID_CHAPCOUNTRY: $chapterdisplay_entry[$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chapterdisplay element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_sub_subelement_id . '::' . $sub_sub_sub_subelement_idname . ') at ' . $sub_sub_sub_subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } $chapteratom_entry[$sub_sub_subelement_idname][] = $chapterdisplay_entry; break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $editionentry_entry[$sub_subelement_idname][] = $chapteratom_entry; break; default: $this->warnings[] = 'Unhandled chapters.editionentry element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $info['matroska']['chapters'][] = $editionentry_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled chapters element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_VOID: // padding, ignore $void_entry = array(); $void_entry['offset'] = $offset; $info['matroska']['void'][] = $void_entry; break; case EBML_ID_CLUSTER: // not important to us, contains mostly actual audio/video data, ignore $cluster_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CLUSTERTIMECODE: case EBML_ID_CLUSTERPOSITION: case EBML_ID_CLUSTERPREVSIZE: $cluster_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_CLUSTERSILENTTRACKS: $cluster_silent_tracks = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CLUSTERSILENTTRACKNUMBER: $cluster_silent_tracks[] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled clusters.silenttracks element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cluster_entry[$subelement_idname][] = $cluster_silent_tracks; $offset = $sub_subelement_end; break; case EBML_ID_CLUSTERBLOCKGROUP: $cluster_block_group = array('offset' => $offset); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CLUSTERBLOCK: $cluster_block_data = array(); $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); $offset += 2; // unsure whether this is 1 octect or 2 octets? (http://matroska.org/technical/specs/index.html#block_structure) $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0xF0) >> 4); $cluster_block_data['flags']['invisible'] = (bool) (($cluster_block_data['flags_raw'] & 0x8) >> 3); $cluster_block_data['flags']['lacing'] = ($cluster_block_data['flags_raw'] & 0x6) >> 1; //$cluster_block_data['flags']['reserved2'] = (($cluster_block_data['flags_raw'] & 0x01) >> 0); $cluster_block_data['flags']['lacing_type'] = $this->MatroskaBlockLacingType($cluster_block_data['flags']['lacing']); if ($cluster_block_data['flags']['lacing'] != 0) { $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Number of frames in the lace-1 (uint8) $offset += 1; if ($cluster_block_data['flags']['lacing'] != 2) { $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). $offset += 1; } } if (!isset($info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['offset'] = $offset; $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['length'] = $subelement_length; } $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; break; case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int // unsigned-int case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), false, true); break; default: $this->warnings[] = 'Unhandled clusters.blockgroup element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cluster_entry[$subelement_idname][] = $cluster_block_group; $offset = $sub_subelement_end; break; case EBML_ID_CLUSTERSIMPLEBLOCK: // http://www.matroska.org/technical/specs/index.html#simpleblock_structure $cluster_block_data = array(); $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); $offset += 2; $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; $cluster_block_data['flags']['keyframe'] = ($cluster_block_data['flags_raw'] & 0x80) >> 7; $cluster_block_data['flags']['reserved1'] = ($cluster_block_data['flags_raw'] & 0x70) >> 4; $cluster_block_data['flags']['invisible'] = ($cluster_block_data['flags_raw'] & 0x8) >> 3; $cluster_block_data['flags']['lacing'] = ($cluster_block_data['flags_raw'] & 0x6) >> 1; // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing $cluster_block_data['flags']['discardable'] = $cluster_block_data['flags_raw'] & 0x1; if ($cluster_block_data['flags']['lacing'] > 0) { $cluster_block_data['lace_frames'] = 1 + getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; if ($cluster_block_data['flags']['lacing'] != 0x2) { // *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). $cluster_block_data['lace_frame_size'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; } } if (!isset($info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['offset'] = $offset; $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['length'] = $subelement_length; } $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; break; default: $this->warnings[] = 'Unhandled cluster element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } $info['matroska']['cluster'][] = $cluster_entry; // check to see if all the data we need exists already, if so, break out of the loop if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { break 2; } } break; default: if ($element_data['id_name'] == dechex($element_data['id'])) { $info['error'][] = 'Unhandled segment [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $element_data['id'] . ') at ' . $element_data_offset; } else { $this->warnings[] = 'Unhandled segment [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $element_data['id'] . '::' . $element_data['id_name'] . ') at ' . $element_data['offset']; } break; } $offset = $element_end; } break; default: $info['error'][] = 'Unhandled chunk [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $top_element_id . ') at ' . $offset; break; } $offset = $top_element_endoffset; } if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { foreach ($info['matroska']['info'] as $key => $infoarray) { if (isset($infoarray['Duration'])) { // TimecodeScale is how many nanoseconds each Duration unit is $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); break; } } } if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { foreach ($info['matroska']['tags'] as $key => $infoarray) { $this->ExtractCommentsSimpleTag($infoarray); } } if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { $track_info = array(); if (isset($trackarray['FlagDefault'])) { $track_info['default'] = $trackarray['FlagDefault']; } switch (isset($trackarray['TrackType']) ? $trackarray['TrackType'] : '') { case 1: // Video if (!empty($trackarray['PixelWidth'])) { $track_info['resolution_x'] = $trackarray['PixelWidth']; } if (!empty($trackarray['PixelHeight'])) { $track_info['resolution_y'] = $trackarray['PixelHeight']; } if (!empty($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } if (!empty($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } if (!empty($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } if (!empty($trackarray['CodecID'])) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } if (!empty($trackarray['codec_private_parsed']['fourcc'])) { $track_info['fourcc'] = $trackarray['codec_private_parsed']['fourcc']; } $info['video']['streams'][] = $track_info; if (isset($track_info['resolution_x']) && empty($info['video']['resolution_x'])) { foreach ($track_info as $key => $value) { $info['video'][$key] = $value; } } break; case 2: // Audio if (!empty($trackarray['CodecID'])) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } if (!empty($trackarray['SamplingFrequency'])) { $track_info['sample_rate'] = $trackarray['SamplingFrequency']; } if (!empty($trackarray['Channels'])) { $track_info['channels'] = $trackarray['Channels']; } if (!empty($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } if (!empty($trackarray['Language'])) { $track_info['language'] = $trackarray['Language']; } switch (isset($trackarray[$this->EBMLidName(EBML_ID_CODECID)]) ? $trackarray[$this->EBMLidName(EBML_ID_CODECID)] : '') { case 'A_PCM/INT/LIT': case 'A_PCM/INT/BIG': $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; break; case 'A_AC3': if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { if (isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'])) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; $getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3->Analyze(); unset($getid3_temp->info['ac3']['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ac3']; if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warnings[] = 'getid3_ac3() says: [' . $newerror . ']'; } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warnings[] = 'getid3_ac3() says: [' . $newerror . ']'; } } if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } unset($getid3_temp, $getid3_ac3); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because $info[matroska][track_data_offsets][' . $trackarray['TrackNumber'] . '][offset] not set'; } } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.ac3.php"'; } break; case 'A_DTS': $dts_offset = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; // this is a NASTY hack, but sometimes audio data is off by a byte or two and not sure why, email info@getid3.org if you can explain better fseek($this->getid3->fp, $dts_offset, SEEK_SET); $magic_test = fread($this->getid3->fp, 8); for ($i = 0; $i < 4; $i++) { // look to see if DTS "magic" is here, if so adjust offset by that many bytes if (substr($magic_test, $i, 4) == "þ€") { $dts_offset += $i; break; } } if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.dts.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $dts_offset; $getid3_dts = new getid3_dts($getid3_temp); $getid3_dts->Analyze(); unset($getid3_temp->info['dts']['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['dts']; if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warnings[] = 'getid3_dts() says: [' . $newerror . ']'; } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warnings[] = 'getid3_dts() says: [' . $newerror . ']'; } } if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } unset($getid3_temp, $getid3_dts); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.dts.php"'; } break; case 'A_AAC': $this->warnings[] = 'This version of getID3() [v' . $this->getid3->version() . '] has problems parsing AAC audio in Matroska containers [' . basename(__FILE__) . ':' . __LINE__ . ']'; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.aac.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; $getid3_aac = new getid3_aac($getid3_temp); $getid3_aac->Analyze(); unset($getid3_temp->info['aac']['GETID3_VERSION']); if (!empty($getid3_temp->info['audio']['dataformat'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['aac']; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } else { $this->warnings[] = 'Failed to parse ' . $trackarray[$this->EBMLidName(EBML_ID_CODECID)] . ' audio data [' . basename(__FILE__) . ':' . __LINE__ . ']'; } unset($getid3_temp, $getid3_aac); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.aac.php"'; } break; case 'A_MPEG/L3': if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.mp3.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; $getid3_mp3 = new getid3_mp3($getid3_temp); $getid3_mp3->allow_bruteforce = true; $getid3_mp3->Analyze(); if (!empty($getid3_temp->info['mpeg'])) { unset($getid3_temp->info['mpeg']['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['mpeg']; if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warnings[] = 'getid3_mp3() says: [' . $newerror . ']'; } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warnings[] = 'getid3_mp3() says: [' . $newerror . ']'; } } if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because getid3_mp3::Analyze failed at offset ' . $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; } unset($getid3_temp, $getid3_mp3); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.mp3.php"'; } break; case 'A_VORBIS': if (isset($trackarray['CodecPrivate'])) { // this is a NASTY hack, email info@getid3.org if you have a better idea how to get this info out $found_vorbis = false; for ($vorbis_offset = 1; $vorbis_offset < 16; $vorbis_offset++) { if (substr($trackarray['CodecPrivate'], $vorbis_offset, 6) == 'vorbis') { $vorbis_offset--; $found_vorbis = true; break; } } if ($found_vorbis) { if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ogg.php', __FILE__, false)) { $oggpageinfo['page_seqno'] = 0; $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_ogg = new getid3_ogg($getid3_temp); $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); $vorbis_fileinfo = $getid3_temp->info; unset($getid3_temp, $getid3_ogg); if (isset($vorbis_fileinfo['audio'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']]['audio'] = $vorbis_fileinfo['audio']; } if (isset($vorbis_fileinfo['ogg'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']]['ogg'] = $vorbis_fileinfo['ogg']; } if (!empty($vorbis_fileinfo['error'])) { foreach ($vorbis_fileinfo['error'] as $newerror) { $this->warnings[] = 'getid3_ogg() says: [' . $newerror . ']'; } } if (!empty($vorbis_fileinfo['warning'])) { foreach ($vorbis_fileinfo['warning'] as $newerror) { $this->warnings[] = 'getid3_ogg() says: [' . $newerror . ']'; } } if (isset($vorbis_fileinfo['audio']) && is_array($vorbis_fileinfo['audio'])) { foreach ($vorbis_fileinfo['audio'] as $key => $value) { $track_info[$key] = $value; } } if (!empty($vorbis_fileinfo['ogg']['bitrate_average'])) { $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_average']; } elseif (!empty($vorbis_fileinfo['ogg']['bitrate_nominal'])) { $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_nominal']; } unset($vorbis_fileinfo); unset($oggpageinfo); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.ogg.php"'; } } else { } } else { } break; default: $this->warnings[] = 'Unhandled audio type "' . (isset($trackarray[$this->EBMLidName(EBML_ID_CODECID)]) ? $trackarray[$this->EBMLidName(EBML_ID_CODECID)] : '') . '"'; break; } $info['audio']['streams'][] = $track_info; if (isset($track_info['dataformat']) && empty($info['audio']['dataformat'])) { foreach ($track_info as $key => $value) { $info['audio'][$key] = $value; } } break; default: // ignore, do nothing break; } } } if ($this->hide_clusters) { // too much data returned that is usually not useful if (isset($info['matroska']['segments']) && is_array($info['matroska']['segments'])) { foreach ($info['matroska']['segments'] as $key => $segmentsarray) { if ($segmentsarray['id'] == EBML_ID_CLUSTER) { unset($info['matroska']['segments'][$key]); } } } if (isset($info['matroska']['seek']) && is_array($info['matroska']['seek'])) { foreach ($info['matroska']['seek'] as $key => $seekarray) { if ($seekarray['target_id'] == EBML_ID_CLUSTER) { unset($info['matroska']['seek'][$key]); } } } //unset($info['matroska']['cluster']); //unset($info['matroska']['track_data_offsets']); } if (!empty($info['video']['streams'])) { $info['mime_type'] = 'video/x-matroska'; } elseif (!empty($info['audio']['streams'])) { $info['mime_type'] = 'audio/x-matroska'; } elseif (isset($info['mime_type'])) { unset($info['mime_type']); } foreach ($this->warnings as $key => $value) { $info['warning'][] = $value; } return true; }
function Analyze() { $info =& $this->getid3->info; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $LPACheader = fread($this->getid3->fp, 14); if (substr($LPACheader, 0, 4) != 'LPAC') { $info['error'][] = 'Expected "LPAC" at offset ' . $info['avdataoffset'] . ', found "' . $StreamMarker . '"'; return false; } $info['avdataoffset'] += 14; $info['fileformat'] = 'lpac'; $info['audio']['dataformat'] = 'lpac'; $info['audio']['lossless'] = true; $info['audio']['bitrate_mode'] = 'vbr'; $info['lpac']['file_version'] = getid3_lib::BigEndian2Int(substr($LPACheader, 4, 1)); $flags['audio_type'] = getid3_lib::BigEndian2Int(substr($LPACheader, 5, 1)); $info['lpac']['total_samples'] = getid3_lib::BigEndian2Int(substr($LPACheader, 6, 4)); $flags['parameters'] = getid3_lib::BigEndian2Int(substr($LPACheader, 10, 4)); $info['lpac']['flags']['is_wave'] = (bool) ($flags['audio_type'] & 0x40); $info['lpac']['flags']['stereo'] = (bool) ($flags['audio_type'] & 0x4); $info['lpac']['flags']['24_bit'] = (bool) ($flags['audio_type'] & 0x2); $info['lpac']['flags']['16_bit'] = (bool) ($flags['audio_type'] & 0x1); if ($info['lpac']['flags']['24_bit'] && $info['lpac']['flags']['16_bit']) { $info['warning'][] = '24-bit and 16-bit flags cannot both be set'; } $info['lpac']['flags']['fast_compress'] = (bool) ($flags['parameters'] & 0x40000000); $info['lpac']['flags']['random_access'] = (bool) ($flags['parameters'] & 0x8000000); $info['lpac']['block_length'] = pow(2, ($flags['parameters'] & 0x7000000) >> 24) * 256; $info['lpac']['flags']['adaptive_prediction_order'] = (bool) ($flags['parameters'] & 0x800000); $info['lpac']['flags']['adaptive_quantization'] = (bool) ($flags['parameters'] & 0x400000); $info['lpac']['flags']['joint_stereo'] = (bool) ($flags['parameters'] & 0x40000); $info['lpac']['quantization'] = ($flags['parameters'] & 0x1f00) >> 8; $info['lpac']['max_prediction_order'] = $flags['parameters'] & 0x3f; if ($info['lpac']['flags']['fast_compress'] && $info['lpac']['max_prediction_order'] != 3) { $info['warning'][] = 'max_prediction_order expected to be "3" if fast_compress is true, actual value is "' . $info['lpac']['max_prediction_order'] . '"'; } switch ($info['lpac']['file_version']) { case 6: if ($info['lpac']['flags']['adaptive_quantization']) { $info['warning'][] = 'adaptive_quantization expected to be false in LPAC file stucture v6, actually true'; } if ($info['lpac']['quantization'] != 20) { $info['warning'][] = 'Quantization expected to be 20 in LPAC file stucture v6, actually ' . $info['lpac']['flags']['Q']; } break; default: //$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] only supports LPAC file format version 6, this file is version '.$info['lpac']['file_version'].' - please report to info@getid3.org'; break; } $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info = $info; $getid3_riff = new getid3_riff($getid3_temp); $getid3_riff->Analyze(); $info['avdataoffset'] = $getid3_temp->info['avdataoffset']; $info['riff'] = $getid3_temp->info['riff']; $info['error'] = $getid3_temp->info['error']; $info['warning'] = $getid3_temp->info['warning']; $info['lpac']['comments']['comment'] = $getid3_temp->info['comments']; $info['audio']['sample_rate'] = $getid3_temp->info['audio']['sample_rate']; unset($getid3_temp, $getid3_riff); $info['audio']['channels'] = $info['lpac']['flags']['stereo'] ? 2 : 1; if ($info['lpac']['flags']['24_bit']) { $info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample']; } elseif ($info['lpac']['flags']['16_bit']) { $info['audio']['bits_per_sample'] = 16; } else { $info['audio']['bits_per_sample'] = 8; } if ($info['lpac']['flags']['fast_compress']) { // fast $info['audio']['encoder_options'] = '-1'; } else { switch ($info['lpac']['max_prediction_order']) { case 20: // simple $info['audio']['encoder_options'] = '-2'; break; case 30: // medium $info['audio']['encoder_options'] = '-3'; break; case 40: // high $info['audio']['encoder_options'] = '-4'; break; case 60: // extrahigh $info['audio']['encoder_options'] = '-5'; break; } } $info['playtime_seconds'] = $info['lpac']['total_samples'] / $info['audio']['sample_rate']; $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; return true; }
function HandleBonkTags($BonkTagName) { $info =& $this->getid3->info; switch ($BonkTagName) { case 'BONK': // shortcut $thisfile_bonk_BONK =& $info['bonk']['BONK']; $BonkData = "" . 'BONK' . fread($this->getid3->fp, 17); $thisfile_bonk_BONK['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); $thisfile_bonk_BONK['number_samples'] = getid3_lib::LittleEndian2Int(substr($BonkData, 6, 4)); $thisfile_bonk_BONK['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BonkData, 10, 4)); $thisfile_bonk_BONK['channels'] = getid3_lib::LittleEndian2Int(substr($BonkData, 14, 1)); $thisfile_bonk_BONK['lossless'] = (bool) getid3_lib::LittleEndian2Int(substr($BonkData, 15, 1)); $thisfile_bonk_BONK['joint_stereo'] = (bool) getid3_lib::LittleEndian2Int(substr($BonkData, 16, 1)); $thisfile_bonk_BONK['number_taps'] = getid3_lib::LittleEndian2Int(substr($BonkData, 17, 2)); $thisfile_bonk_BONK['downsampling_ratio'] = getid3_lib::LittleEndian2Int(substr($BonkData, 19, 1)); $thisfile_bonk_BONK['samples_per_packet'] = getid3_lib::LittleEndian2Int(substr($BonkData, 20, 2)); $info['avdataoffset'] = $thisfile_bonk_BONK['offset'] + 5 + 17; $info['avdataend'] = $thisfile_bonk_BONK['offset'] + $thisfile_bonk_BONK['size']; $info['fileformat'] = 'bonk'; $info['audio']['dataformat'] = 'bonk'; $info['audio']['bitrate_mode'] = 'vbr'; // assumed $info['audio']['channels'] = $thisfile_bonk_BONK['channels']; $info['audio']['sample_rate'] = $thisfile_bonk_BONK['sample_rate']; $info['audio']['channelmode'] = $thisfile_bonk_BONK['joint_stereo'] ? 'joint stereo' : 'stereo'; $info['audio']['lossless'] = $thisfile_bonk_BONK['lossless']; $info['audio']['codec'] = 'bonk'; $info['playtime_seconds'] = $thisfile_bonk_BONK['number_samples'] / ($thisfile_bonk_BONK['sample_rate'] * $thisfile_bonk_BONK['channels']); if ($info['playtime_seconds'] > 0) { $info['audio']['bitrate'] = ($info['bonk']['dataend'] - $info['bonk']['dataoffset']) * 8 / $info['playtime_seconds']; } break; case 'INFO': // shortcut $thisfile_bonk_INFO =& $info['bonk']['INFO']; $thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1)); $thisfile_bonk_INFO['entries_count'] = 0; $NextInfoDataPair = fread($this->getid3->fp, 5); if (!$this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { while (!feof($this->getid3->fp)) { //$CurrentSeekInfo['offset'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 0, 4)); //$CurrentSeekInfo['nextbit'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 4, 1)); //$thisfile_bonk_INFO[] = $CurrentSeekInfo; $NextInfoDataPair = fread($this->getid3->fp, 5); if ($this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { fseek($this->getid3->fp, -5, SEEK_CUR); break; } $thisfile_bonk_INFO['entries_count']++; } } break; case 'META': $BonkData = "" . 'META' . fread($this->getid3->fp, $info['bonk']['META']['size'] - 5); $info['bonk']['META']['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); $MetaTagEntries = floor((strlen($BonkData) - 8 - 6) / 8); // BonkData - xxxxmeta - ØMETA $offset = 6; for ($i = 0; $i < $MetaTagEntries; $i++) { $MetaEntryTagName = substr($BonkData, $offset, 4); $offset += 4; $MetaEntryTagOffset = getid3_lib::LittleEndian2Int(substr($BonkData, $offset, 4)); $offset += 4; $info['bonk']['META']['tags'][$MetaEntryTagName] = $MetaEntryTagOffset; } break; case ' ID3': $info['audio']['encoder'] = 'Extended BONK v0.9+'; // ID3v2 checking is optional if (class_exists('getid3_id3v2')) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_id3v2 = new getid3_id3v2($getid3_temp); $getid3_id3v2->StartingOffset = $info['bonk'][' ID3']['offset'] + 2; $info['bonk'][' ID3']['valid'] = $getid3_id3v2->Analyze(); if ($info['bonk'][' ID3']['valid']) { $info['id3v2'] = $getid3_temp->info['id3v2']; } unset($getid3_temp, $getid3_id3v2); } break; default: $info['warning'][] = 'Unexpected Bonk tag "' . $BonkTagName . '" at offset ' . $info['bonk'][$BonkTagName]['offset']; break; } }
public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm $info =& $this->getid3->info; $atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see http://www.getid3.org/phpBB3/viewtopic.php?t=1717 array_push($atomHierarchy, $atomname); $atom_structure['hierarchy'] = implode(' ', $atomHierarchy); $atom_structure['name'] = $atomname; $atom_structure['size'] = $atomsize; $atom_structure['offset'] = $baseoffset; switch ($atomname) { case 'moov': // MOVie container atom // MOVie container atom case 'trak': // TRAcK container atom // TRAcK container atom case 'clip': // CLIPping container atom // CLIPping container atom case 'matt': // track MATTe container atom // track MATTe container atom case 'edts': // EDiTS container atom // EDiTS container atom case 'tref': // Track REFerence container atom // Track REFerence container atom case 'mdia': // MeDIA container atom // MeDIA container atom case 'minf': // Media INFormation container atom // Media INFormation container atom case 'dinf': // Data INFormation container atom // Data INFormation container atom case 'udta': // User DaTA container atom // User DaTA container atom case 'cmov': // Compressed MOVie container atom // Compressed MOVie container atom case 'rmra': // Reference Movie Record Atom // Reference Movie Record Atom case 'rmda': // Reference Movie Descriptor Atom // Reference Movie Descriptor Atom case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); break; case 'ilst': // Item LiST container atom if ($atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms)) { // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted $allnumericnames = true; foreach ($atom_structure['subatoms'] as $subatomarray) { if (!is_integer($subatomarray['name']) || count($subatomarray['subatoms']) != 1) { $allnumericnames = false; break; } } if ($allnumericnames) { $newData = array(); foreach ($atom_structure['subatoms'] as $subatomarray) { foreach ($subatomarray['subatoms'] as $newData_subatomarray) { unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); $newData[$subatomarray['name']] = $newData_subatomarray; break; } } $atom_structure['data'] = $newData; unset($atom_structure['subatoms']); } } break; case "": case "": case "": case "": case "": $atomname = getid3_lib::BigEndian2Int($atomname); $atom_structure['name'] = $atomname; $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); break; case 'stbl': // Sample TaBLe container atom $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); $isVideo = false; $framerate = 0; $framecount = 0; foreach ($atom_structure['subatoms'] as $key => $value_array) { if (isset($value_array['sample_description_table'])) { foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { if (isset($value_array2['data_format'])) { switch ($value_array2['data_format']) { case 'avc1': case 'mp4v': // video data $isVideo = true; break; case 'mp4a': // audio data break; } } } } 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) { $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); $framecount = $value_array2['sample_count']; } } } } if ($isVideo && $framerate) { $info['quicktime']['video']['frame_rate'] = $framerate; $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; } if ($isVideo && $framecount) { $info['quicktime']['video']['frame_count'] = $framecount; } break; case 'aART': // Album ARTist // Album ARTist case 'catg': // CaTeGory // CaTeGory case 'covr': // COVeR artwork // COVeR artwork case 'cpil': // ComPILation // ComPILation case 'cprt': // CoPyRighT // CoPyRighT case 'desc': // DESCription // DESCription case 'disk': // DISK number // DISK number case 'egid': // Episode Global ID // Episode Global ID case 'gnre': // GeNRE // GeNRE case 'keyw': // KEYWord // KEYWord case 'ldes': case 'pcst': // PodCaST // PodCaST case 'pgap': // GAPless Playback // GAPless Playback case 'purd': // PURchase Date // PURchase Date case 'purl': // Podcast URL // Podcast URL case 'rati': case 'rndu': case 'rpdu': case 'rtng': // RaTiNG // RaTiNG case 'stik': case 'tmpo': // TeMPO (BPM) // TeMPO (BPM) case 'trkn': // TRacK Number // TRacK Number case 'tves': // TV EpiSode // TV EpiSode case 'tvnn': // TV Network Name // TV Network Name case 'tvsh': // TV SHow Name // TV SHow Name case 'tvsn': // TV SeasoN // TV SeasoN case 'akID': // iTunes store account type // iTunes store account type case 'apID': case 'atID': case 'cmID': case 'cnID': case 'geID': case 'plID': case 'sfID': // iTunes store country // iTunes store country case "©" . 'alb': // ALBum // ALBum case "©" . 'art': // ARTist // ARTist case "©" . 'ART': case "©" . 'aut': case "©" . 'cmt': // CoMmenT // CoMmenT case "©" . 'com': // COMposer // COMposer case "©" . 'cpy': case "©" . 'day': // content created year // content created year case "©" . 'dir': case "©" . 'ed1': case "©" . 'ed2': case "©" . 'ed3': case "©" . 'ed4': case "©" . 'ed5': case "©" . 'ed6': case "©" . 'ed7': case "©" . 'ed8': case "©" . 'ed9': case "©" . 'enc': case "©" . 'fmt': case "©" . 'gen': // GENre // GENre case "©" . 'grp': // GRouPing // GRouPing case "©" . 'hst': case "©" . 'inf': case "©" . 'lyr': // LYRics // LYRics case "©" . 'mak': case "©" . 'mod': case "©" . 'nam': // full NAMe // full NAMe case "©" . 'ope': case "©" . 'PRD': case "©" . 'prd': case "©" . 'prf': case "©" . 'req': case "©" . 'src': case "©" . 'swr': case "©" . 'too': // encoder // encoder case "©" . 'trk': // TRacK // TRacK case "©" . 'url': case "©" . 'wrn': case "©" . 'wrt': // WRiTer // WRiTer case '----': // itunes specific if ($atom_parent == 'udta') { // User data atom handler $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); $atom_structure['data'] = substr($atom_data, 4); $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); if (empty($info['comments']['language']) || !in_array($atom_structure['language'], $info['comments']['language'])) { $info['comments']['language'][] = $atom_structure['language']; } } else { // Apple item list box atom handler $atomoffset = 0; if (substr($atom_data, 2, 2) == "µ") { // not sure what it means, but observed on iPhone4 data. // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data while ($atomoffset < strlen($atom_data)) { $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); if ($boxsmallsize <= 1) { $info['warning'][] = 'Invalid QuickTime atom smallbox size "' . $boxsmallsize . '" in atom "' . $atomname . '" at offset: ' . ($atom_structure['offset'] + $atomoffset); $atom_structure['data'] = null; $atomoffset = strlen($atom_data); break; } switch ($boxsmalltype) { case "µ": $atom_structure['data'] = $boxsmalldata; break; default: $info['warning'][] = 'Unknown QuickTime smallbox type: "' . getid3_lib::PrintHexBytes($boxsmalltype) . '" at offset ' . $baseoffset; $atom_structure['data'] = $atom_data; break; } $atomoffset += 4 + $boxsmallsize; } } else { while ($atomoffset < strlen($atom_data)) { $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); $boxtype = substr($atom_data, $atomoffset + 4, 4); $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); if ($boxsize <= 1) { $info['warning'][] = 'Invalid QuickTime atom box size "' . $boxsize . '" in atom "' . $atomname . '" at offset: ' . ($atom_structure['offset'] + $atomoffset); $atom_structure['data'] = null; $atomoffset = strlen($atom_data); break; } $atomoffset += $boxsize; switch ($boxtype) { case 'mean': case 'name': $atom_structure[$boxtype] = substr($boxdata, 4); break; case 'data': $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); switch ($atom_structure['flags_raw']) { case 0: // data flag // data flag case 21: // tmpo/cpil flag switch ($atomname) { case 'cpil': case 'pcst': case 'pgap': $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); break; case 'tmpo': $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); break; case 'disk': case 'trkn': $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); $atom_structure['data'] = empty($num) ? '' : $num; $atom_structure['data'] .= empty($num_total) ? '' : '/' . $num_total; break; case 'gnre': $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); break; case 'rtng': $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); break; case 'stik': $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); break; case 'sfID': $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); break; case 'egid': case 'purl': $atom_structure['data'] = substr($boxdata, 8); break; default: $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); } break; case 1: // text flag // text flag case 13: // image flag // image flag default: $atom_structure['data'] = substr($boxdata, 8); if ($atomname == 'covr') { // not a foolproof check, but better than nothing if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { $atom_structure['image_mime'] = 'image/jpeg'; } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) { $atom_structure['image_mime'] = 'image/png'; } elseif (preg_match('#^GIF#', $atom_structure['data'])) { $atom_structure['image_mime'] = 'image/gif'; } } break; } break; default: $info['warning'][] = 'Unknown QuickTime box type: "' . getid3_lib::PrintHexBytes($boxtype) . '" at offset ' . $baseoffset; $atom_structure['data'] = $atom_data; } } } } $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); break; case 'play': // auto-PLAY atom $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $info['quicktime']['autoplay'] = $atom_structure['autoplay']; break; case 'WLOC': // Window LOCation atom $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); break; case 'LOOP': // LOOPing atom // LOOPing atom case 'SelO': // play SELection Only atom // play SELection Only atom case 'AllF': // play ALL Frames atom $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); break; case 'name': // // case 'MCPS': // Media Cleaner PRo // Media Cleaner PRo case '@PRM': // adobe PReMiere version // adobe PReMiere version case '@PRQ': // adobe PRemiere Quicktime version $atom_structure['data'] = $atom_data; break; case 'cmvd': // Compressed MooV Data atom // Code by ubergeekØubergeek*tv based on information from // http://developer.apple.com/quicktime/icefloe/dispatch012.html $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); $CompressedFileData = substr($atom_data, 4); if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); } else { $info['warning'][] = 'Error decompressing compressed MOV atom at offset ' . $atom_structure['offset']; } break; case 'dcom': // Data COMpression atom $atom_structure['compression_id'] = $atom_data; $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); break; case 'rdrf': // Reference movie Data ReFerence atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x1); $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); switch ($atom_structure['reference_type_name']) { case 'url ': $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); break; case 'alis': $atom_structure['file_alias'] = substr($atom_data, 12); break; case 'rsrc': $atom_structure['resource_alias'] = substr($atom_data, 12); break; default: $atom_structure['data'] = substr($atom_data, 12); break; } break; case 'rmqu': // Reference Movie QUality atom $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); break; case 'rmcs': // Reference Movie Cpu Speed 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['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); break; case 'rmvc': // Reference Movie Version Check 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['gestalt_selector'] = substr($atom_data, 4, 4); $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); break; case 'rmcd': // Reference Movie Component check 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['component_type'] = substr($atom_data, 4, 4); $atom_structure['component_subtype'] = substr($atom_data, 8, 4); $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); break; case 'rmdr': // Reference Movie Data Rate 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['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; break; case 'rmla': // Reference Movie Language 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['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); if (empty($info['comments']['language']) || !in_array($atom_structure['language'], $info['comments']['language'])) { $info['comments']['language'][] = $atom_structure['language']; } break; case 'rmla': // Reference Movie Language 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['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); break; case 'ptv ': // Print To Video - defines a movie's full screen mode // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; $ptv_lookup[0] = 'normal'; $ptv_lookup[1] = 'double'; $ptv_lookup[2] = 'half'; $ptv_lookup[3] = 'full'; $ptv_lookup[4] = 'current'; if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; } else { $info['warning'][] = 'unknown "ptv " display constant (' . $atom_structure['display_size_raw'] . ')'; } break; 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['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $stsdEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); $stsdEntriesDataOffset += 4; $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); $stsdEntriesDataOffset += 4; $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); $stsdEntriesDataOffset += 6; $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); $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; $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { case "": // audio tracks $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); // video tracks // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html $atom_structure['sample_description_table'][$i]['temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); $atom_structure['sample_description_table'][$i]['spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); $atom_structure['sample_description_table'][$i]['width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); $atom_structure['sample_description_table'][$i]['height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); $atom_structure['sample_description_table'][$i]['resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); $atom_structure['sample_description_table'][$i]['resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); $atom_structure['sample_description_table'][$i]['data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 4)); $atom_structure['sample_description_table'][$i]['frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 36, 2)); $atom_structure['sample_description_table'][$i]['compressor_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 38, 4); $atom_structure['sample_description_table'][$i]['pixel_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 42, 2)); $atom_structure['sample_description_table'][$i]['color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 44, 2)); switch ($atom_structure['sample_description_table'][$i]['data_format']) { case '2vuY': case 'avc1': case 'cvid': case 'dvc ': case 'dvcp': case 'gif ': case 'h263': case 'jpeg': case 'kpcd': case 'mjpa': case 'mjpb': case 'mp4v': case 'png ': case 'raw ': case 'rle ': case 'rpza': case 'smc ': case 'SVQ1': case 'SVQ3': case 'tiff': case 'v210': case 'v216': case 'v308': case 'v408': case 'v410': case 'yuv2': $info['fileformat'] = 'mp4'; $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; // http://www.getid3.org/phpBB3/viewtopic.php?t=1550 //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 if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) { // assume that values stored here are more important than values stored in [tkhd] atom $info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width']; $info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height']; $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; } break; case 'qtvr': $info['video']['dataformat'] = 'quicktimevr'; break; case 'mp4a': default: $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']; $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; $info['audio']['codec'] = $info['quicktime']['audio']['codec']; $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; $info['audio']['channels'] = $info['quicktime']['audio']['channels']; $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; switch ($atom_structure['sample_description_table'][$i]['data_format']) { case 'raw ': // PCM // PCM case 'alac': // Apple Lossless Audio Codec $info['audio']['lossless'] = true; break; default: $info['audio']['lossless'] = false; break; } break; } break; default: switch ($atom_structure['sample_description_table'][$i]['data_format']) { case 'mp4s': $info['fileformat'] = 'mp4'; break; default: // video atom $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); $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']); $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32 ? 'grayscale' : 'color'; $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); $info['quicktime']['video']['codec'] = $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']; $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; $info['video']['codec'] = $info['quicktime']['video']['codec']; $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; } $info['video']['lossless'] = false; $info['video']['pixel_aspect_ratio'] = (double) 1; break; } break; } switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { case 'mp4a': $info['audio']['dataformat'] = 'mp4'; $info['quicktime']['audio']['codec'] = 'mp4'; break; case '3ivx': case '3iv1': case '3iv2': $info['video']['dataformat'] = '3ivx'; break; case 'xvid': $info['video']['dataformat'] = 'xvid'; break; case 'mp4v': $info['video']['dataformat'] = 'mpeg4'; break; case 'divx': case 'div1': case 'div2': case 'div3': case 'div4': case 'div5': case 'div6': $info['video']['dataformat'] = 'divx'; break; default: // do nothing break; } unset($atom_structure['sample_description_table'][$i]['data']); } break; case 'stts': // Sample Table Time-to-Sample 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['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $sttsEntriesDataOffset = 8; //$FrameRateCalculatorArray = array(); $frames_count = 0; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); $sttsEntriesDataOffset += 4; $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); $sttsEntriesDataOffset += 4; $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; // if ($stts_new_framerate <= 60) { // // some atoms have durations of "1" giving a very large framerate, which probably is not right // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); // } //} // //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; } $info['quicktime']['stts_framecount'][] = $frames_count; //$sttsFramesTotal = 0; //$sttsSecondsTotal = 0; //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { // if (($frames_per_second > 60) || ($frames_per_second < 1)) { // // not video FPS information, probably audio information // $sttsFramesTotal = 0; // $sttsSecondsTotal = 0; // break; // } // $sttsFramesTotal += $frame_count; // $sttsSecondsTotal += $frame_count / $frames_per_second; //} //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; // } //} break; case 'stss': // Sample Table Sync Sample (key frames) atom if ($ParseAllPossibleAtoms) { $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['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $stssEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); $stssEntriesDataOffset += 4; } } break; case 'stsc': // Sample Table Sample-to-Chunk atom if ($ParseAllPossibleAtoms) { $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['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $stscEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); $stscEntriesDataOffset += 4; $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); $stscEntriesDataOffset += 4; $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); $stscEntriesDataOffset += 4; } } break; case 'stsz': // Sample Table SiZe atom if ($ParseAllPossibleAtoms) { $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['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $stszEntriesDataOffset = 12; if ($atom_structure['sample_size'] == 0) { for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4)); $stszEntriesDataOffset += 4; } } } break; case 'stco': // Sample Table Chunk Offset atom if ($ParseAllPossibleAtoms) { $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['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $stcoEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); $stcoEntriesDataOffset += 4; } } break; case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) if ($ParseAllPossibleAtoms) { $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['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $stcoEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); $stcoEntriesDataOffset += 8; } } break; case 'dref': // Data REFerence 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['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $drefDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); $drefDataOffset += 4; $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); $drefDataOffset += 4; $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); $drefDataOffset += 1; $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 $drefDataOffset += 3; $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, $atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); $drefDataOffset += $atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3; $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x1); } break; case 'gmin': // base Media INformation 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['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); break; case 'smhd': // Sound Media information HeaDer 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['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); break; case 'vmhd': // Video Media information HeaDer atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x1); break; case 'hdlr': // HanDLeR reference 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['component_type'] = substr($atom_data, 4, 4); $atom_structure['component_subtype'] = substr($atom_data, 8, 4); $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24)); if ($atom_structure['component_subtype'] == 'STpn' && $atom_structure['component_manufacturer'] == 'zzzz') { $info['video']['dataformat'] = 'quicktimevr'; } break; case 'mdhd': // MeDia HeaDer 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['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); if ($atom_structure['time_scale'] == 0) { $info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero'; return false; } $info['quicktime']['time_scale'] = isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']; $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); if (empty($info['comments']['language']) || !in_array($atom_structure['language'], $info['comments']['language'])) { $info['comments']['language'][] = $atom_structure['language']; } break; case 'pnot': // Preview atom $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); break; case 'crgn': // Clipping ReGioN atom $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. break; case 'load': // track LOAD settings atom $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x20); $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x100); break; case 'tmcd': // TiMe CoDe atom // TiMe CoDe atom case 'chap': // CHAPter list atom // CHAPter list atom case 'sync': // SYNChronization atom // SYNChronization atom case 'scpt': // tranSCriPT atom // tranSCriPT atom case 'ssrc': // non-primary SouRCe atom for ($i = 0; $i < strlen($atom_data); $i += 4) { @($atom_structure['track_id'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4))); } break; case 'elst': // Edit LiST 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['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $i * 12 + 0, 4)); $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $i * 12 + 4, 4)); $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + $i * 12 + 8, 4)); } break; case 'kmat': // compressed MATte 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['matte_data_raw'] = substr($atom_data, 4); break; case 'ctab': // Color TABle atom $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $colortableentry * 8 + 0, 2)); $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $colortableentry * 8 + 2, 2)); $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $colortableentry * 8 + 4, 2)); $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $colortableentry * 8 + 6, 2)); } break; case 'mvhd': // MoVie HeaDer atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); $atom_structure['reserved'] = substr($atom_data, 26, 10); $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); if ($atom_structure['time_scale'] == 0) { $info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero'; return false; } $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); $info['quicktime']['time_scale'] = isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']; $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; break; case 'tkhd': // TracK HeaDer atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); // http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html // http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4)); $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4)); $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4)); $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x1); $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x2); $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x4); $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x8); $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); if ($atom_structure['flags']['enabled'] == 1) { if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { $info['video']['resolution_x'] = $atom_structure['width']; $info['video']['resolution_y'] = $atom_structure['height']; } $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; } else { // see: http://www.getid3.org/phpBB3/viewtopic.php?t=1295 //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } } break; case 'iods': // Initial Object DeScriptor atom // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html $offset = 0; $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); $offset += 3; $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); //$offset already adjusted by quicktime_read_mp4_descr_length() $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); //$offset already adjusted by quicktime_read_mp4_descr_length() $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4; } $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); break; case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) $atom_structure['signature'] = substr($atom_data, 0, 4); $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['fourcc'] = substr($atom_data, 8, 4); break; case 'mdat': // Media DATa atom // 'mdat' contains the actual data for the audio/video, possibly also subtitles /* 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 */ // first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?) $mdat_offset = 0; while (true) { if (substr($atom_data, $mdat_offset, 8) == "" . 'wide') { $mdat_offset += 8; } elseif (substr($atom_data, $mdat_offset, 8) == "" . 'mdat') { $mdat_offset += 8; } else { break; } } // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field while (($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2))) && $chapter_string_length < 1000 && $chapter_string_length <= strlen($atom_data) - $mdat_offset - 2 && preg_match('#^[\\x20-\\xFF]+$#', substr($atom_data, $mdat_offset + 2, $chapter_string_length), $chapter_matches)) { $mdat_offset += 2 + $chapter_string_length; @($info['quicktime']['comments']['chapters'][] = $chapter_matches[0]); } if ($atomsize > 8 && (!isset($info['avdataend_tmp']) || $info['quicktime'][$atomname]['size'] > $info['avdataend_tmp'] - $info['avdataoffset'])) { $info['avdataoffset'] = $atom_structure['offset'] + 8; // $info['quicktime'][$atomname]['offset'] + 8; $OldAVDataEnd = $info['avdataend']; $info['avdataend'] = $atom_structure['offset'] + $atom_structure['size']; // $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_mp3 = new getid3_mp3($getid3_temp); if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) { $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $value) { $info['warning'][] = $value; } } if (!empty($getid3_temp->info['mpeg'])) { $info['mpeg'] = $getid3_temp->info['mpeg']; if (isset($info['mpeg']['audio'])) { $info['audio']['dataformat'] = 'mp3'; $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')); $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); $info['bitrate'] = $info['audio']['bitrate']; } } } unset($getid3_mp3, $getid3_temp); $info['avdataend'] = $OldAVDataEnd; unset($OldAVDataEnd); } unset($mdat_offset, $chapter_string_length, $chapter_matches); break; case 'free': // FREE space atom // FREE space atom case 'skip': // SKIP atom // SKIP atom case 'wide': // 64-bit expansion placeholder atom // 'free', 'skip' and 'wide' are just padding, contains no useful data at all // When writing QuickTime files, it is sometimes necessary to update an atom's size. // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime // puts an 8-byte placeholder atom before any atoms it may have to update the size of. // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). break; case 'nsav': // NoSAVe atom // http://developer.apple.com/technotes/tn/tn2038.html $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); break; case 'ctyp': // Controller TYPe atom (seen on QTVR) // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt // some controller names are: // 0x00 + 'std' for linear movie // 'none' for no controls $atom_structure['ctyp'] = substr($atom_data, 0, 4); $info['quicktime']['controller'] = $atom_structure['ctyp']; switch ($atom_structure['ctyp']) { case 'qtvr': $info['video']['dataformat'] = 'quicktimevr'; break; } break; case 'pano': // PANOrama track (seen on QTVR) $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); break; case 'hint': // HINT track // HINT track case 'hinf': // // case 'hinv': // // case 'hnti': // $info['quicktime']['hinting'] = true; break; case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) for ($i = 0; $i < $atom_structure['size'] - 8; $i += 4) { $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); } break; // Observed-but-not-handled atom types are just listed here to prevent warnings being generated // Observed-but-not-handled atom types are just listed here to prevent warnings being generated case 'FXTC': // Something to do with Adobe After Effects (?) // Something to do with Adobe After Effects (?) case 'PrmA': case 'code': case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838] // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838] // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html case 'ctts': // STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html // STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html case 'cslg': // STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html // STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html case 'sdtp': // STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html // STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html case 'stps': // STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html //$atom_structure['data'] = $atom_data; break; case "©" . 'xyz': // GPS latitude+longitude+altitude $atom_structure['data'] = $atom_data; if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { @(list($all, $latitude, $longitude, $altitude) = $matches); $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); if (!empty($altitude)) { $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); } } else { $info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset ' . $baseoffset . '. Please report as getID3() bug.'; } break; case 'NCDT': // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); break; case 'NCTH': // Nikon Camera THumbnail image // Nikon Camera THumbnail image case 'NCVW': // Nikon Camera preVieW image // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html if (preg_match('/^\\xFF\\xD8\\xFF/', $atom_data)) { $atom_structure['data'] = $atom_data; $atom_structure['image_mime'] = 'image/jpeg'; $atom_structure['description'] = $atomname == 'NCTH' ? 'Nikon Camera Thumbnail Image' : ($atomname == 'NCVW' ? 'Nikon Camera Preview Image' : 'Nikon preview image'); $info['quicktime']['comments']['picture'][] = array('image_mime' => $atom_structure['image_mime'], 'data' => $atom_data, 'description' => $atom_structure['description']); } break; case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); break; case 'NCHD': // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html case 'NCDB': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html $atom_structure['data'] = $atom_data; break; case "": case 'meta': // METAdata atom // some kind of metacontainer, may contain a big data dump such as: // mdta keys mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst data DEApple 0 (data DE2011-05-11T17:54:04+0200 2 *data DE+52.4936+013.3897+040.247/ data DE4.3.1 data DEiPhone 4 // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); break; case 'data': // metaDATA atom // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data $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); break; default: $info['warning'][] = 'Unknown QuickTime atom type: "' . $atomname . '" (' . trim(getid3_lib::PrintHexBytes($atomname)) . ') at offset ' . $baseoffset; $atom_structure['data'] = $atom_data; break; } array_pop($atomHierarchy); return $atom_structure; }
public function Analyze() { $info =& $this->getid3->info; // parse container try { $this->parseEBML($info); } catch (Exception $e) { $info['error'][] = 'EBML parser: ' . $e->getMessage(); } // calculate playtime if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { foreach ($info['matroska']['info'] as $key => $infoarray) { if (isset($infoarray['Duration'])) { // TimecodeScale is how many nanoseconds each Duration unit is $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); break; } } } // extract tags if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { foreach ($info['matroska']['tags'] as $key => $infoarray) { $this->ExtractCommentsSimpleTag($infoarray); } } // process tracks if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { $track_info = array(); $track_info['dataformat'] = self::MatroskaCodecIDtoCommonName($trackarray['CodecID']); $track_info['default'] = isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true; if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; } switch ($trackarray['TrackType']) { case 1: // Video $track_info['resolution_x'] = $trackarray['PixelWidth']; $track_info['resolution_y'] = $trackarray['PixelHeight']; if (isset($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } if (isset($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } //if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } switch ($trackarray['CodecID']) { case 'V_MS/VFW/FOURCC': if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, false)) { $this->getid3->warning('Unable to parse codec private data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio-video.riff.php"'); break; } $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']); $track_info['codec'] = getid3_riff::RIFFfourccLookup($parsed['fourcc']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; break; } $info['video']['streams'][] = $track_info; break; case 2: // Audio $track_info['sample_rate'] = isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0; $track_info['channels'] = isset($trackarray['Channels']) ? $trackarray['Channels'] : 1; $track_info['language'] = isset($trackarray['Language']) ? $trackarray['Language'] : 'eng'; if (isset($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } //if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } switch ($trackarray['CodecID']) { case 'A_PCM/INT/LIT': case 'A_PCM/INT/BIG': $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; break; case 'A_AC3': case 'A_DTS': case 'A_MPEG/L3': //case 'A_FLAC': if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.' . $track_info['dataformat'] . '.php', __FILE__, false)) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.' . $track_info['dataformat'] . '.php"'); break; } if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because $info[matroska][track_data_offsets][' . $trackarray['TrackNumber'] . '] not set'); break; } // create temp instance $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; if ($track_info['dataformat'] == 'mp3' || $track_info['dataformat'] == 'flac') { $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; } // analyze $class = 'getid3_' . $track_info['dataformat']; $header_data_key = $track_info['dataformat'] == 'mp3' ? 'mpeg' : $track_info['dataformat']; $getid3_audio = new $class($getid3_temp); if ($track_info['dataformat'] == 'mp3') { $getid3_audio->allow_bruteforce = true; } if ($track_info['dataformat'] == 'flac') { $getid3_audio->AnalyzeString($trackarray['CodecPrivate']); } else { $getid3_audio->Analyze(); } if (!empty($getid3_temp->info[$header_data_key])) { unset($getid3_temp->info[$header_data_key]['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } else { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because ' . $class . '::Analyze() failed at offset ' . $getid3_temp->info['avdataoffset']); } // copy errors and warnings if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->getid3->warning($class . '() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { if ($track_info['dataformat'] == 'mp3' && preg_match('/^Probable truncated file: expecting \\d+ bytes of audio data, only found \\d+ \\(short by \\d+ bytes\\)$/', $newerror)) { // LAME/Xing header is probably set, but audio data is chunked into Matroska file and near-impossible to verify if audio stream is complete, so ignore useless warning continue; } $this->getid3->warning($class . '() says: [' . $newerror . ']'); } } unset($getid3_temp, $getid3_audio); break; case 'A_AAC': case 'A_AAC/MPEG2/LC': case 'A_AAC/MPEG4/LC': case 'A_AAC/MPEG4/LC/SBR': $this->getid3->warning($trackarray['CodecID'] . ' audio data contains no header, audio/video bitrates can\'t be calculated'); break; case 'A_VORBIS': if (!isset($trackarray['CodecPrivate'])) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because CodecPrivate data not set'); break; } $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1); if ($vorbis_offset === false) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because CodecPrivate data does not contain "vorbis" keyword'); break; } $vorbis_offset -= 1; if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ogg.php', __FILE__, false)) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.ogg.php"'); } // create temp instance $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); // analyze $getid3_ogg = new getid3_ogg($getid3_temp); $oggpageinfo['page_seqno'] = 0; $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); if (!empty($getid3_temp->info['ogg'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg']; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } // copy errors and warnings if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->getid3->warning('getid3_ogg() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->getid3->warning('getid3_ogg() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) { $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal']; } unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset); break; case 'A_MS/ACM': if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, false)) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio-video.riff.php"'); break; } $parsed = getid3_riff::RIFFparseWAVEFORMATex($trackarray['CodecPrivate']); foreach ($parsed as $key => $value) { if ($key != 'raw') { $track_info[$key] = $value; } } $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; break; default: $this->getid3->warning('Unhandled audio type "' . (isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '') . '"'); } $info['audio']['streams'][] = $track_info; break; } } if (!empty($info['video']['streams'])) { $info['video'] = self::getDefaultStreamInfo($info['video']['streams']); } if (!empty($info['audio']['streams'])) { $info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']); } } // determine mime type if (!empty($info['video']['streams'])) { $info['mime_type'] = $info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska'; } elseif (!empty($info['audio']['streams'])) { $info['mime_type'] = $info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska'; } elseif (isset($info['mime_type'])) { unset($info['mime_type']); } return true; }
public function getinfo($filename) { $realfile = litepublisher::$paths->files . str_replace('/', DIRECTORY_SEPARATOR, $filename); $result = $this->getdefaultvalues($filename); if (preg_match("/\\.({$this->videoext})\$/", $filename, $m)) { $ext = $m[1]; $mime = array('mp4' => 'video/mp4', 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'avi' => 'video/x-msvideo', 'mov' => 'video/quicktime', 'ogv' => 'video/ogg', 'webm' => 'video/webm', 'flv' => 'video/x-flv', 'f4v' => 'video/mp4', 'f4p' => 'video/mp4'); if (isset($mime[$ext])) { $result['mime'] = $mime[$ext]; } $result['media'] = 'video'; return $result; } if ($info = @getimagesize($realfile)) { $result['mime'] = $info['mime']; $result['media'] = 'application/x-shockwave-flash' == $info['mime'] ? 'flash' : 'image'; $result['width'] = $info[0]; $result['height'] = $info[1]; return $result; } if (preg_match("/\\.({$this->audioext})\$/", $filename)) { $mime = array('mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav', 'flac' => 'audio/ogg', 'f4a' => 'audio/mp4', 'f4b' => 'audio/mp4'); $result['mime'] = $mime[strtolower(substr($filename, -3))]; $result['media'] = 'audio'; return $result; } if (strend($filename, '.txt')) { $result['mime'] = 'text/plain'; $result['media'] = 'text'; return $result; } if (strend($filename, '.swf')) { $result['media'] = 'flash'; $result['mime'] = 'application/x-shockwave-flash'; require_once litepublisher::$paths->libinclude . 'getid3.php'; require_once litepublisher::$paths->libinclude . 'getid3.lib.php'; require_once litepublisher::$paths->libinclude . 'module.audio-video.swf.php'; $getID3 = new getID3(); $getID3->option_md5_data = true; $getID3->option_md5_data_source = true; $getID3->encoding = 'UTF-8'; //$info = $getID3->analyze($realfile); $getID3->openfile($realfile); $swf = new getid3_swf($getID3); $swf->analyze(); fclose($getID3->fp); $info = $getID3->info; if (!isset($info['error'])) { $result['width'] = (int) round($info['swf']['header']['frame_width'] / 20); $result['height'] = (int) round($info['swf']['header']['frame_height'] / 20); return $result; } } return $result; }
function Analyze() { $info =& $this->getid3->info; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); while (true) { $wavpackheader = fread($this->getid3->fp, 32); if (ftell($this->getid3->fp) >= $info['avdataend']) { break; } elseif (feof($this->getid3->fp)) { break; } elseif (isset($info['wavpack']['blockheader']['total_samples']) && isset($info['wavpack']['blockheader']['block_samples']) && $info['wavpack']['blockheader']['total_samples'] > 0 && $info['wavpack']['blockheader']['block_samples'] > 0 && (!isset($info['wavpack']['riff_trailer_size']) || $info['wavpack']['riff_trailer_size'] <= 0) && (isset($info['wavpack']['config_flags']['md5_checksum']) && $info['wavpack']['config_flags']['md5_checksum'] === false || !empty($info['md5_data_source']))) { break; } $blockheader_offset = ftell($this->getid3->fp) - 32; $blockheader_magic = substr($wavpackheader, 0, 4); $blockheader_size = getid3_lib::LittleEndian2Int(substr($wavpackheader, 4, 4)); $magic = 'wvpk'; if ($blockheader_magic != $magic) { $info['error'][] = 'Expecting "' . getid3_lib::PrintHexBytes($magic) . '" at offset ' . $blockheader_offset . ', found "' . getid3_lib::PrintHexBytes($blockheader_magic) . '"'; switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') { case 'wavpack': case 'wvc': break; default: unset($info['fileformat']); unset($info['audio']); unset($info['wavpack']); break; } return false; } if (empty($info['wavpack']['blockheader']['block_samples']) || empty($info['wavpack']['blockheader']['total_samples']) || $info['wavpack']['blockheader']['block_samples'] <= 0 || $info['wavpack']['blockheader']['total_samples'] <= 0) { // Also, it is possible that the first block might not have // any samples (block_samples == 0) and in this case you should skip blocks // until you find one with samples because the other information (like // total_samples) are not guaranteed to be correct until (block_samples > 0) // Finally, I have defined a format for files in which the length is not known // (for example when raw files are created using pipes). In these cases // total_samples will be -1 and you must seek to the final block to determine // the total number of samples. $info['audio']['dataformat'] = 'wavpack'; $info['fileformat'] = 'wavpack'; $info['audio']['lossless'] = true; $info['audio']['bitrate_mode'] = 'vbr'; $info['wavpack']['blockheader']['offset'] = $blockheader_offset; $info['wavpack']['blockheader']['magic'] = $blockheader_magic; $info['wavpack']['blockheader']['size'] = $blockheader_size; if ($info['wavpack']['blockheader']['size'] >= 0x100000) { $info['error'][] = 'Expecting WavPack block size less than "0x100000", found "' . $info['wavpack']['blockheader']['size'] . '" at offset ' . $info['wavpack']['blockheader']['offset']; switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') { case 'wavpack': case 'wvc': break; default: unset($info['fileformat']); unset($info['audio']); unset($info['wavpack']); break; } return false; } $info['wavpack']['blockheader']['minor_version'] = ord($wavpackheader[8]); $info['wavpack']['blockheader']['major_version'] = ord($wavpackheader[9]); if ($info['wavpack']['blockheader']['major_version'] != 4 || $info['wavpack']['blockheader']['minor_version'] < 4 && $info['wavpack']['blockheader']['minor_version'] > 16) { $info['error'][] = 'Expecting WavPack version between "4.2" and "4.16", found version "' . $info['wavpack']['blockheader']['major_version'] . '.' . $info['wavpack']['blockheader']['minor_version'] . '" at offset ' . $info['wavpack']['blockheader']['offset']; switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') { case 'wavpack': case 'wvc': break; default: unset($info['fileformat']); unset($info['audio']); unset($info['wavpack']); break; } return false; } $info['wavpack']['blockheader']['track_number'] = ord($wavpackheader[10]); // unused $info['wavpack']['blockheader']['index_number'] = ord($wavpackheader[11]); // unused $info['wavpack']['blockheader']['total_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 12, 4)); $info['wavpack']['blockheader']['block_index'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 16, 4)); $info['wavpack']['blockheader']['block_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 20, 4)); $info['wavpack']['blockheader']['flags_raw'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 24, 4)); $info['wavpack']['blockheader']['crc'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 28, 4)); $info['wavpack']['blockheader']['flags']['bytes_per_sample'] = 1 + ($info['wavpack']['blockheader']['flags_raw'] & 0x3); $info['wavpack']['blockheader']['flags']['mono'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x4); $info['wavpack']['blockheader']['flags']['hybrid'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x8); $info['wavpack']['blockheader']['flags']['joint_stereo'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x10); $info['wavpack']['blockheader']['flags']['cross_decorrelation'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x20); $info['wavpack']['blockheader']['flags']['hybrid_noiseshape'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x40); $info['wavpack']['blockheader']['flags']['ieee_32bit_float'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x80); $info['wavpack']['blockheader']['flags']['int_32bit'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x100); $info['wavpack']['blockheader']['flags']['hybrid_bitrate_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x200); $info['wavpack']['blockheader']['flags']['hybrid_balance_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x400); $info['wavpack']['blockheader']['flags']['multichannel_initial'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x800); $info['wavpack']['blockheader']['flags']['multichannel_final'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x1000); $info['audio']['lossless'] = !$info['wavpack']['blockheader']['flags']['hybrid']; } while (!feof($this->getid3->fp) && ftell($this->getid3->fp) < $blockheader_offset + $blockheader_size + 8) { $metablock = array('offset' => ftell($this->getid3->fp)); $metablockheader = fread($this->getid3->fp, 2); if (feof($this->getid3->fp)) { break; } $metablock['id'] = ord($metablockheader[0]); $metablock['function_id'] = $metablock['id'] & 0x3f; $metablock['function_name'] = $this->WavPackMetablockNameLookup($metablock['function_id']); // The 0x20 bit in the id of the meta subblocks (which is defined as // ID_OPTIONAL_DATA) is a permanent part of the id. The idea is that // if a decoder encounters an id that it does not know about, it uses // that "ID_OPTIONAL_DATA" flag to determine what to do. If it is set // then the decoder simply ignores the metadata, but if it is zero // then the decoder should quit because it means that an understanding // of the metadata is required to correctly decode the audio. $metablock['non_decoder'] = (bool) ($metablock['id'] & 0x20); $metablock['padded_data'] = (bool) ($metablock['id'] & 0x40); $metablock['large_block'] = (bool) ($metablock['id'] & 0x80); if ($metablock['large_block']) { $metablockheader .= fread($this->getid3->fp, 2); } $metablock['size'] = getid3_lib::LittleEndian2Int(substr($metablockheader, 1)) * 2; // size is stored in words $metablock['data'] = null; if ($metablock['size'] > 0) { switch ($metablock['function_id']) { case 0x21: // ID_RIFF_HEADER // ID_RIFF_HEADER case 0x22: // ID_RIFF_TRAILER // ID_RIFF_TRAILER case 0x23: // ID_REPLAY_GAIN // ID_REPLAY_GAIN case 0x24: // ID_CUESHEET // ID_CUESHEET case 0x25: // ID_CONFIG_BLOCK // ID_CONFIG_BLOCK case 0x26: // ID_MD5_CHECKSUM $metablock['data'] = fread($this->getid3->fp, $metablock['size']); if ($metablock['padded_data']) { // padded to the nearest even byte $metablock['size']--; $metablock['data'] = substr($metablock['data'], 0, -1); } break; case 0x0: // ID_DUMMY // ID_DUMMY case 0x1: // ID_ENCODER_INFO // ID_ENCODER_INFO case 0x2: // ID_DECORR_TERMS // ID_DECORR_TERMS case 0x3: // ID_DECORR_WEIGHTS // ID_DECORR_WEIGHTS case 0x4: // ID_DECORR_SAMPLES // ID_DECORR_SAMPLES case 0x5: // ID_ENTROPY_VARS // ID_ENTROPY_VARS case 0x6: // ID_HYBRID_PROFILE // ID_HYBRID_PROFILE case 0x7: // ID_SHAPING_WEIGHTS // ID_SHAPING_WEIGHTS case 0x8: // ID_FLOAT_INFO // ID_FLOAT_INFO case 0x9: // ID_INT32_INFO // ID_INT32_INFO case 0xa: // ID_WV_BITSTREAM // ID_WV_BITSTREAM case 0xb: // ID_WVC_BITSTREAM // ID_WVC_BITSTREAM case 0xc: // ID_WVX_BITSTREAM // ID_WVX_BITSTREAM case 0xd: // ID_CHANNEL_INFO fseek($this->getid3->fp, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET); break; default: $info['warning'][] = 'Unexpected metablock type "0x' . str_pad(dechex($metablock['function_id']), 2, '0', STR_PAD_LEFT) . '" at offset ' . $metablock['offset']; fseek($this->getid3->fp, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET); break; } switch ($metablock['function_id']) { case 0x21: // ID_RIFF_HEADER getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, true); $original_wav_filesize = getid3_lib::LittleEndian2Int(substr($metablock['data'], 4, 4)); $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_riff = new getid3_riff($getid3_temp); $getid3_riff->ParseRIFFdata($metablock['data']); $metablock['riff'] = $getid3_temp->info['riff']; $info['audio']['sample_rate'] = $getid3_temp->info['riff']['raw']['fmt ']['nSamplesPerSec']; unset($getid3_riff, $getid3_temp); $metablock['riff']['original_filesize'] = $original_wav_filesize; $info['wavpack']['riff_trailer_size'] = $original_wav_filesize - $metablock['riff']['WAVE']['data'][0]['size'] - $metablock['riff']['header_size']; $info['playtime_seconds'] = $info['wavpack']['blockheader']['total_samples'] / $info['audio']['sample_rate']; // Safe RIFF header in case there's a RIFF footer later $metablockRIFFheader = $metablock['data']; break; case 0x22: // ID_RIFF_TRAILER $metablockRIFFfooter = $metablockRIFFheader . $metablock['data']; getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, true); $startoffset = $metablock['offset'] + ($metablock['large_block'] ? 4 : 2); $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_temp->info['fileformat'] = 'riff'; $getid3_riff = new getid3_riff($getid3_temp); $metablock['riff'] = $getid3_riff->ParseRIFF($startoffset, $startoffset + $metablock['size']); if (!empty($metablock['riff']['INFO'])) { $getid3_riff->RIFFcommentsParse($metablock['riff']['INFO'], $metablock['comments']); $info['tags']['riff'] = $metablock['comments']; } unset($getid3_temp, $getid3_riff); break; case 0x23: // ID_REPLAY_GAIN $info['warning'][] = 'WavPack "Replay Gain" contents not yet handled by getID3() in metablock at offset ' . $metablock['offset']; break; case 0x24: // ID_CUESHEET $info['warning'][] = 'WavPack "Cuesheet" contents not yet handled by getID3() in metablock at offset ' . $metablock['offset']; break; case 0x25: // ID_CONFIG_BLOCK $metablock['flags_raw'] = getid3_lib::LittleEndian2Int(substr($metablock['data'], 0, 3)); $metablock['flags']['adobe_mode'] = (bool) ($metablock['flags_raw'] & 0x1); // "adobe" mode for 32-bit floats $metablock['flags']['fast_flag'] = (bool) ($metablock['flags_raw'] & 0x2); // fast mode $metablock['flags']['very_fast_flag'] = (bool) ($metablock['flags_raw'] & 0x4); // double fast $metablock['flags']['high_flag'] = (bool) ($metablock['flags_raw'] & 0x8); // high quality mode $metablock['flags']['very_high_flag'] = (bool) ($metablock['flags_raw'] & 0x10); // double high (not used yet) $metablock['flags']['bitrate_kbps'] = (bool) ($metablock['flags_raw'] & 0x20); // bitrate is kbps, not bits / sample $metablock['flags']['auto_shaping'] = (bool) ($metablock['flags_raw'] & 0x40); // automatic noise shaping $metablock['flags']['shape_override'] = (bool) ($metablock['flags_raw'] & 0x80); // shaping mode specified $metablock['flags']['joint_override'] = (bool) ($metablock['flags_raw'] & 0x100); // joint-stereo mode specified $metablock['flags']['copy_time'] = (bool) ($metablock['flags_raw'] & 0x200); // copy file-time from source $metablock['flags']['create_exe'] = (bool) ($metablock['flags_raw'] & 0x400); // create executable $metablock['flags']['create_wvc'] = (bool) ($metablock['flags_raw'] & 0x800); // create correction file $metablock['flags']['optimize_wvc'] = (bool) ($metablock['flags_raw'] & 0x1000); // maximize bybrid compression $metablock['flags']['quality_mode'] = (bool) ($metablock['flags_raw'] & 0x2000); // psychoacoustic quality mode $metablock['flags']['raw_flag'] = (bool) ($metablock['flags_raw'] & 0x4000); // raw mode (not implemented yet) $metablock['flags']['calc_noise'] = (bool) ($metablock['flags_raw'] & 0x8000); // calc noise in hybrid mode $metablock['flags']['lossy_mode'] = (bool) ($metablock['flags_raw'] & 0x10000); // obsolete (for information) $metablock['flags']['extra_mode'] = (bool) ($metablock['flags_raw'] & 0x20000); // extra processing mode $metablock['flags']['skip_wvx'] = (bool) ($metablock['flags_raw'] & 0x40000); // no wvx stream w/ floats & big ints $metablock['flags']['md5_checksum'] = (bool) ($metablock['flags_raw'] & 0x80000); // compute & store MD5 signature $metablock['flags']['quiet_mode'] = (bool) ($metablock['flags_raw'] & 0x100000); // don't report progress % $info['wavpack']['config_flags'] = $metablock['flags']; $info['audio']['encoder_options'] = ''; if ($info['wavpack']['blockheader']['flags']['hybrid']) { $info['audio']['encoder_options'] .= ' -b???'; } $info['audio']['encoder_options'] .= $metablock['flags']['adobe_mode'] ? ' -a' : ''; $info['audio']['encoder_options'] .= $metablock['flags']['optimize_wvc'] ? ' -cc' : ''; $info['audio']['encoder_options'] .= $metablock['flags']['create_exe'] ? ' -e' : ''; $info['audio']['encoder_options'] .= $metablock['flags']['fast_flag'] ? ' -f' : ''; $info['audio']['encoder_options'] .= $metablock['flags']['joint_override'] ? ' -j?' : ''; $info['audio']['encoder_options'] .= $metablock['flags']['high_flag'] ? ' -h' : ''; $info['audio']['encoder_options'] .= $metablock['flags']['md5_checksum'] ? ' -m' : ''; $info['audio']['encoder_options'] .= $metablock['flags']['calc_noise'] ? ' -n' : ''; $info['audio']['encoder_options'] .= $metablock['flags']['shape_override'] ? ' -s?' : ''; $info['audio']['encoder_options'] .= $metablock['flags']['extra_mode'] ? ' -x?' : ''; if (!empty($info['audio']['encoder_options'])) { $info['audio']['encoder_options'] = trim($info['audio']['encoder_options']); } elseif (isset($info['audio']['encoder_options'])) { unset($info['audio']['encoder_options']); } break; case 0x26: // ID_MD5_CHECKSUM if (strlen($metablock['data']) == 16) { $info['md5_data_source'] = strtolower(getid3_lib::PrintHexBytes($metablock['data'], true, false, false)); } else { $info['warning'][] = 'Expecting 16 bytes of WavPack "MD5 Checksum" in metablock at offset ' . $metablock['offset'] . ', but found ' . strlen($metablock['data']) . ' bytes'; } break; case 0x0: // ID_DUMMY // ID_DUMMY case 0x1: // ID_ENCODER_INFO // ID_ENCODER_INFO case 0x2: // ID_DECORR_TERMS // ID_DECORR_TERMS case 0x3: // ID_DECORR_WEIGHTS // ID_DECORR_WEIGHTS case 0x4: // ID_DECORR_SAMPLES // ID_DECORR_SAMPLES case 0x5: // ID_ENTROPY_VARS // ID_ENTROPY_VARS case 0x6: // ID_HYBRID_PROFILE // ID_HYBRID_PROFILE case 0x7: // ID_SHAPING_WEIGHTS // ID_SHAPING_WEIGHTS case 0x8: // ID_FLOAT_INFO // ID_FLOAT_INFO case 0x9: // ID_INT32_INFO // ID_INT32_INFO case 0xa: // ID_WV_BITSTREAM // ID_WV_BITSTREAM case 0xb: // ID_WVC_BITSTREAM // ID_WVC_BITSTREAM case 0xc: // ID_WVX_BITSTREAM // ID_WVX_BITSTREAM case 0xd: // ID_CHANNEL_INFO unset($metablock); break; } } if (!empty($metablock)) { $info['wavpack']['metablocks'][] = $metablock; } } } $info['audio']['encoder'] = 'WavPack v' . $info['wavpack']['blockheader']['major_version'] . '.' . str_pad($info['wavpack']['blockheader']['minor_version'], 2, '0', STR_PAD_LEFT); $info['audio']['bits_per_sample'] = $info['wavpack']['blockheader']['flags']['bytes_per_sample'] * 8; $info['audio']['channels'] = $info['wavpack']['blockheader']['flags']['mono'] ? 1 : 2; if (!empty($info['playtime_seconds'])) { $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; } else { $info['audio']['dataformat'] = 'wvc'; } return true; }
function Analyze() { $info =& $this->getid3->info; $offset = 0; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $rawdata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); switch (substr($rawdata, $offset, 4)) { case 'LA02': case 'LA03': case 'LA04': $info['fileformat'] = 'la'; $info['audio']['dataformat'] = 'la'; $info['audio']['lossless'] = true; $info['la']['version_major'] = (int) substr($rawdata, $offset + 2, 1); $info['la']['version_minor'] = (int) substr($rawdata, $offset + 3, 1); $info['la']['version'] = (double) $info['la']['version_major'] + $info['la']['version_minor'] / 10; $offset += 4; $info['la']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); $offset += 4; if ($info['la']['uncompressed_size'] == 0) { $info['error'][] = 'Corrupt LA file: uncompressed_size == zero'; return false; } $WAVEchunk = substr($rawdata, $offset, 4); if ($WAVEchunk !== 'WAVE') { $info['error'][] = 'Expected "WAVE" (' . getid3_lib::PrintHexBytes('WAVE') . ') at offset ' . $offset . ', found "' . $WAVEchunk . '" (' . getid3_lib::PrintHexBytes($WAVEchunk) . ') instead.'; return false; } $offset += 4; $info['la']['fmt_size'] = 24; if ($info['la']['version'] >= 0.3) { $info['la']['fmt_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); $info['la']['header_size'] = 49 + $info['la']['fmt_size'] - 24; $offset += 4; } else { // version 0.2 didn't support additional data blocks $info['la']['header_size'] = 41; } $fmt_chunk = substr($rawdata, $offset, 4); if ($fmt_chunk !== 'fmt ') { $info['error'][] = 'Expected "fmt " (' . getid3_lib::PrintHexBytes('fmt ') . ') at offset ' . $offset . ', found "' . $fmt_chunk . '" (' . getid3_lib::PrintHexBytes($fmt_chunk) . ') instead.'; return false; } $offset += 4; $fmt_size = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); $offset += 4; $info['la']['raw']['format'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); $offset += 2; $info['la']['channels'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); $offset += 2; if ($info['la']['channels'] == 0) { $info['error'][] = 'Corrupt LA file: channels == zero'; return false; } $info['la']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); $offset += 4; if ($info['la']['sample_rate'] == 0) { $info['error'][] = 'Corrupt LA file: sample_rate == zero'; return false; } $info['la']['bytes_per_second'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); $offset += 4; $info['la']['bytes_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); $offset += 2; $info['la']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); $offset += 2; $info['la']['samples'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); $offset += 4; $info['la']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 1)); $offset += 1; $info['la']['flags']['seekable'] = (bool) ($info['la']['raw']['flags'] & 0x1); if ($info['la']['version'] >= 0.4) { $info['la']['flags']['high_compression'] = (bool) ($info['la']['raw']['flags'] & 0x2); } $info['la']['original_crc'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); $offset += 4; // mikeØbevin*de // Basically, the blocksize/seekevery are 61440/19 in La0.4 and 73728/16 // in earlier versions. A seekpoint is added every blocksize * seekevery // samples, so 4 * int(totalSamples / (blockSize * seekEvery)) should // give the number of bytes used for the seekpoints. Of course, if seeking // is disabled, there are no seekpoints stored. if ($info['la']['version'] >= 0.4) { $info['la']['blocksize'] = 61440; $info['la']['seekevery'] = 19; } else { $info['la']['blocksize'] = 73728; $info['la']['seekevery'] = 16; } $info['la']['seekpoint_count'] = 0; if ($info['la']['flags']['seekable']) { $info['la']['seekpoint_count'] = floor($info['la']['samples'] / ($info['la']['blocksize'] * $info['la']['seekevery'])); for ($i = 0; $i < $info['la']['seekpoint_count']; $i++) { $info['la']['seekpoints'][] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); $offset += 4; } } if ($info['la']['version'] >= 0.3) { // Following the main header information, the program outputs all of the // seekpoints. Following these is what I called the 'footer start', // i.e. the position immediately after the La audio data is finished. $info['la']['footerstart'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); $offset += 4; if ($info['la']['footerstart'] > $info['filesize']) { $info['warning'][] = 'FooterStart value points to offset ' . $info['la']['footerstart'] . ' which is beyond end-of-file (' . $info['filesize'] . ')'; $info['la']['footerstart'] = $info['filesize']; } } else { // La v0.2 didn't have FooterStart value $info['la']['footerstart'] = $info['avdataend']; } if ($info['la']['footerstart'] < $info['avdataend']) { if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) { if ($RIFF_fp = fopen($RIFFtempfilename, 'w+b')) { $RIFFdata = 'WAVE'; if ($info['la']['version'] == 0.2) { $RIFFdata .= substr($rawdata, 12, 24); } else { $RIFFdata .= substr($rawdata, 16, 24); } if ($info['la']['footerstart'] < $info['avdataend']) { fseek($this->getid3->fp, $info['la']['footerstart'], SEEK_SET); $RIFFdata .= fread($this->getid3->fp, $info['avdataend'] - $info['la']['footerstart']); } $RIFFdata = 'RIFF' . getid3_lib::LittleEndian2String(strlen($RIFFdata), 4, false) . $RIFFdata; fwrite($RIFF_fp, $RIFFdata, strlen($RIFFdata)); fclose($RIFF_fp); $getid3_temp = new getID3(); $getid3_temp->openfile($RIFFtempfilename); $getid3_riff = new getid3_riff($getid3_temp); $getid3_riff->Analyze(); if (empty($getid3_temp->info['error'])) { $info['riff'] = $getid3_temp->info['riff']; } else { $info['warning'][] = 'Error parsing RIFF portion of La file: ' . implode($getid3_temp->info['error']); } unset($getid3_temp, $getid3_riff); } unlink($RIFFtempfilename); } } // $info['avdataoffset'] should be zero to begin with, but just in case it's not, include the addition anyway $info['avdataend'] = $info['avdataoffset'] + $info['la']['footerstart']; $info['avdataoffset'] = $info['avdataoffset'] + $offset; //$info['la']['codec'] = RIFFwFormatTagLookup($info['la']['raw']['format']); $info['la']['compression_ratio'] = (double) (($info['avdataend'] - $info['avdataoffset']) / $info['la']['uncompressed_size']); $info['playtime_seconds'] = (double) ($info['la']['samples'] / $info['la']['sample_rate']) / $info['la']['channels']; if ($info['playtime_seconds'] == 0) { $info['error'][] = 'Corrupt LA file: playtime_seconds == zero'; return false; } $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; //$info['audio']['codec'] = $info['la']['codec']; $info['audio']['bits_per_sample'] = $info['la']['bits_per_sample']; break; default: if (substr($rawdata, $offset, 2) == 'LA') { $info['error'][] = 'This version of getID3() [' . $this->getid3->version() . '] does not support LA version ' . substr($rawdata, $offset + 2, 1) . '.' . substr($rawdata, $offset + 3, 1) . ' which this appears to be - check http://getid3.sourceforge.net for updates.'; } else { $info['error'][] = 'Not a LA (Lossless-Audio) file'; } return false; break; } $info['audio']['channels'] = $info['la']['channels']; $info['audio']['sample_rate'] = (int) $info['la']['sample_rate']; $info['audio']['encoder'] = 'LA v' . $info['la']['version']; return true; }
public function ParseOptimFROGheader45() { // for fileformat of v4.50a and higher $info =& $this->getid3->info; $RIFFdata = ''; $this->fseek($info['avdataoffset']); while (!feof($this->getid3->fp) && $this->ftell() < $info['avdataend']) { $BlockOffset = $this->ftell(); $BlockData = $this->fread(8); $offset = 8; $BlockName = substr($BlockData, 0, 4); $BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4)); if ($BlockName == 'OFRX') { $BlockName = 'OFR '; } if (!isset($info['ofr'][$BlockName])) { $info['ofr'][$BlockName] = array(); } $thisfile_ofr_thisblock =& $info['ofr'][$BlockName]; switch ($BlockName) { case 'OFR ': // shortcut $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; $info['audio']['encoder'] = 'OptimFROG 4.50 alpha'; switch ($BlockSize) { case 12: case 15: // good break; default: $info['warning'][] = '"' . $BlockName . '" contains more data than expected (expected 12 or 15 bytes, found ' . $BlockSize . ' bytes)'; break; } $BlockData .= $this->fread($BlockSize); $thisfile_ofr_thisblock['total_samples'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 6)); $offset += 6; $thisfile_ofr_thisblock['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); $thisfile_ofr_thisblock['sample_type'] = $this->OptimFROGsampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); $offset += 1; $thisfile_ofr_thisblock['channel_config'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); $thisfile_ofr_thisblock['channels'] = $thisfile_ofr_thisblock['channel_config']; $offset += 1; $thisfile_ofr_thisblock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); $offset += 4; if ($BlockSize > 12) { // OFR 4.504b or higher $thisfile_ofr_thisblock['channels'] = $this->OptimFROGchannelConfigNumChannelsLookup($thisfile_ofr_thisblock['channel_config']); $thisfile_ofr_thisblock['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); $thisfile_ofr_thisblock['encoder'] = $this->OptimFROGencoderNameLookup($thisfile_ofr_thisblock['raw']['encoder_id']); $offset += 2; $thisfile_ofr_thisblock['raw']['compression'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); $thisfile_ofr_thisblock['compression'] = $this->OptimFROGcompressionLookup($thisfile_ofr_thisblock['raw']['compression']); $thisfile_ofr_thisblock['speedup'] = $this->OptimFROGspeedupLookup($thisfile_ofr_thisblock['raw']['compression']); $offset += 1; $info['audio']['encoder'] = 'OptimFROG ' . $thisfile_ofr_thisblock['encoder']; $info['audio']['encoder_options'] = '--mode ' . $thisfile_ofr_thisblock['compression']; if (($thisfile_ofr_thisblock['raw']['encoder_id'] & 0xf0) >> 4 == 7) { // v4.507 if (strtolower(getid3_lib::fileextension($info['filename'])) == 'ofs') { // OptimFROG DualStream format is lossy, but as of v4.507 there is no way to tell the difference // between lossless and lossy other than the file extension. $info['audio']['dataformat'] = 'ofs'; $info['audio']['lossless'] = true; } } } $info['audio']['channels'] = $thisfile_ofr_thisblock['channels']; $info['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate']; $info['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); break; case 'COMP': // unlike other block types, there CAN be multiple COMP blocks $COMPdata['offset'] = $BlockOffset; $COMPdata['size'] = $BlockSize; if ($info['avdataoffset'] == 0) { $info['avdataoffset'] = $BlockOffset; } // Only interested in first 14 bytes (only first 12 needed for v4.50 alpha), not actual audio data $BlockData .= $this->fread(14); $this->fseek($BlockSize - 14, SEEK_CUR); $COMPdata['crc_32'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); $offset += 4; $COMPdata['sample_count'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); $offset += 4; $COMPdata['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); $COMPdata['sample_type'] = $this->OptimFROGsampleTypeLookup($COMPdata['raw']['sample_type']); $offset += 1; $COMPdata['raw']['channel_configuration'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); $COMPdata['channel_configuration'] = $this->OptimFROGchannelConfigurationLookup($COMPdata['raw']['channel_configuration']); $offset += 1; $COMPdata['raw']['algorithm_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); //$COMPdata['algorithm'] = OptimFROGalgorithmNameLookup($COMPdata['raw']['algorithm_id']); $offset += 2; if ($info['ofr']['OFR ']['size'] > 12) { // OFR 4.504b or higher $COMPdata['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); $COMPdata['encoder'] = $this->OptimFROGencoderNameLookup($COMPdata['raw']['encoder_id']); $offset += 2; } if ($COMPdata['crc_32'] == 0x454e4f4e) { // ASCII value of 'NONE' - placeholder value in v4.50a $COMPdata['crc_32'] = false; } $thisfile_ofr_thisblock[] = $COMPdata; break; case 'HEAD': $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; $RIFFdata .= $this->fread($BlockSize); break; case 'TAIL': $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; if ($BlockSize > 0) { $RIFFdata .= $this->fread($BlockSize); } break; case 'RECV': // block contains no useful meta data - simply note and skip $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; $this->fseek($BlockSize, SEEK_CUR); break; case 'APET': // APEtag v2 $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; $info['warning'][] = 'APEtag processing inside OptimFROG not supported in this version (' . $this->getid3->version() . ') of getID3()'; $this->fseek($BlockSize, SEEK_CUR); break; case 'MD5 ': // APEtag v2 $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; if ($BlockSize == 16) { $thisfile_ofr_thisblock['md5_binary'] = $this->fread($BlockSize); $thisfile_ofr_thisblock['md5_string'] = getid3_lib::PrintHexBytes($thisfile_ofr_thisblock['md5_binary'], true, false, false); $info['md5_data_source'] = $thisfile_ofr_thisblock['md5_string']; } else { $info['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found ' . $BlockSize . ' instead'; $this->fseek($BlockSize, SEEK_CUR); } break; default: $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; $info['warning'][] = 'Unhandled OptimFROG block type "' . $BlockName . '" at offset ' . $thisfile_ofr_thisblock['offset']; $this->fseek($BlockSize, SEEK_CUR); break; } } if (isset($info['ofr']['TAIL']['offset'])) { $info['avdataend'] = $info['ofr']['TAIL']['offset']; } $info['playtime_seconds'] = (double) $info['ofr']['OFR ']['total_samples'] / ($info['audio']['channels'] * $info['audio']['sample_rate']); $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; // move the data chunk after all other chunks (if any) // so that the RIFF parser doesn't see EOF when trying // to skip over the data chunk $RIFFdata = substr($RIFFdata, 0, 36) . substr($RIFFdata, 44) . substr($RIFFdata, 36, 8); $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_riff = new getid3_riff($getid3_temp); $getid3_riff->ParseRIFFdata($RIFFdata); $info['riff'] = $getid3_temp->info['riff']; unset($getid3_riff, $getid3_temp, $RIFFdata); return true; }
public function Analyze() { $info =& $this->getid3->info; $info['fileformat'] = 'mpeg'; $this->fseek($info['avdataoffset']); $MPEGstreamData = $this->fread($this->getid3->option_fread_buffer_size); $MPEGstreamBaseOffset = 0; // how far are we from the beginning of the file data ($info['avdataoffset']) $MPEGstreamDataOffset = 0; // how far are we from the beginning of the buffer data (~32kB) $StartCodeValue = false; $prevStartCodeValue = false; $GOPcounter = -1; $FramesByGOP = array(); $ParsedAVchannels = array(); do { //echo $MPEGstreamDataOffset.' vs '.(strlen($MPEGstreamData) - 1024).'<Br>'; if ($MPEGstreamDataOffset > strlen($MPEGstreamData) - 16384) { // buffer running low, get more data //echo 'reading more data<br>'; $MPEGstreamData .= $this->fread($this->getid3->option_fread_buffer_size); if (strlen($MPEGstreamData) > $this->getid3->option_fread_buffer_size) { $MPEGstreamData = substr($MPEGstreamData, $MPEGstreamDataOffset); $MPEGstreamBaseOffset += $MPEGstreamDataOffset; $MPEGstreamDataOffset = 0; } } if (($StartCodeOffset = strpos($MPEGstreamData, self::START_CODE_BASE, $MPEGstreamDataOffset)) === false) { //echo 'no more start codes found.<br>'; break; } else { $MPEGstreamDataOffset = $StartCodeOffset; $prevStartCodeValue = $StartCodeValue; $StartCodeValue = ord(substr($MPEGstreamData, $StartCodeOffset + 3, 1)); //echo 'Found "'.strtoupper(dechex($StartCodeValue)).'" at offset '.($MPEGstreamBaseOffset + $StartCodeOffset).' ($MPEGstreamDataOffset = '.$MPEGstreamDataOffset.')<br>'; } $MPEGstreamDataOffset += 4; switch ($StartCodeValue) { case 0x0: // picture_start_code if ($info['mpeg']['video']['bitrate_mode'] == 'vbr') { $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 4)); $bitstreamoffset = 0; $PictureHeader = array(); $PictureHeader['temporal_reference'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 10); // 10-bit unsigned integer associated with each input picture. It is incremented by one, modulo 1024, for each input frame. When a frame is coded as two fields the temporal reference in the picture header of both fields is the same. Following a group start header the temporal reference of the earliest picture (in display order) shall be reset to zero. $PictureHeader['picture_coding_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_coding_type $PictureHeader['vbv_delay'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 16); // 16 bits for vbv_delay //... etc $FramesByGOP[$GOPcounter][] = $PictureHeader; } break; case 0xb3: // sequence_header_code /* Note: purposely doing the less-pretty (and probably a bit slower) method of using string of bits rather than bitwise operations. Mostly because PHP 32-bit doesn't handle unsigned integers well for bitwise operation. Also the MPEG stream is designed as a bitstream and often doesn't align nicely with byte boundaries. */ $info['video']['codec'] = 'MPEG-1'; // will be updated if extension_start_code found $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 8)); $bitstreamoffset = 0; $info['mpeg']['video']['raw']['horizontal_size_value'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for horizontal frame size. Note: horizontal_size_extension, if present, will add 2 most-significant bits to this value $info['mpeg']['video']['raw']['vertical_size_value'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for vertical frame size. Note: vertical_size_extension, if present, will add 2 most-significant bits to this value $info['mpeg']['video']['raw']['aspect_ratio_information'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for aspect_ratio_information $info['mpeg']['video']['raw']['frame_rate_code'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for Frame Rate id code $info['mpeg']['video']['raw']['bitrate'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 18); // 18 bits for bit_rate_value (18 set bits = VBR, otherwise bitrate = this value * 400) $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. $info['mpeg']['video']['raw']['vbv_buffer_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 10); // 10 bits vbv_buffer_size_value $info['mpeg']['video']['raw']['constrained_param_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: constrained_param_flag $info['mpeg']['video']['raw']['load_intra_quantiser_matrix'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: load_intra_quantiser_matrix if ($info['mpeg']['video']['raw']['load_intra_quantiser_matrix']) { $bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 12, 64)); for ($i = 0; $i < 64; $i++) { $info['mpeg']['video']['raw']['intra_quantiser_matrix'][$i] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); } } $info['mpeg']['video']['raw']['load_non_intra_quantiser_matrix'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); if ($info['mpeg']['video']['raw']['load_non_intra_quantiser_matrix']) { $bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 12 + ($info['mpeg']['video']['raw']['load_intra_quantiser_matrix'] ? 64 : 0), 64)); for ($i = 0; $i < 64; $i++) { $info['mpeg']['video']['raw']['non_intra_quantiser_matrix'][$i] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); } } $info['mpeg']['video']['pixel_aspect_ratio'] = self::videoAspectRatioLookup($info['mpeg']['video']['raw']['aspect_ratio_information']); $info['mpeg']['video']['pixel_aspect_ratio_text'] = self::videoAspectRatioTextLookup($info['mpeg']['video']['raw']['aspect_ratio_information']); $info['mpeg']['video']['frame_rate'] = self::videoFramerateLookup($info['mpeg']['video']['raw']['frame_rate_code']); if ($info['mpeg']['video']['raw']['bitrate'] == 0x3ffff) { // 18 set bits = VBR //$this->warning('This version of getID3() ['.$this->getid3->version().'] cannot determine average bitrate of VBR MPEG video files'); $info['mpeg']['video']['bitrate_mode'] = 'vbr'; } else { $info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400; $info['mpeg']['video']['bitrate_mode'] = 'cbr'; $info['video']['bitrate'] = $info['mpeg']['video']['bitrate']; } $info['video']['resolution_x'] = $info['mpeg']['video']['raw']['horizontal_size_value']; $info['video']['resolution_y'] = $info['mpeg']['video']['raw']['vertical_size_value']; $info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate']; $info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode']; $info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio']; $info['video']['lossless'] = false; $info['video']['bits_per_sample'] = 24; break; case 0xb5: // extension_start_code $info['video']['codec'] = 'MPEG-2'; $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 8)); // 48 bits for Sequence Extension ID; 61 bits for Sequence Display Extension ID; 59 bits for Sequence Scalable Extension ID $bitstreamoffset = 0; $info['mpeg']['video']['raw']['extension_start_code_identifier'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for extension_start_code_identifier //echo $info['mpeg']['video']['raw']['extension_start_code_identifier'].'<br>'; switch ($info['mpeg']['video']['raw']['extension_start_code_identifier']) { case 1: // 0001 Sequence Extension ID $info['mpeg']['video']['raw']['profile_and_level_indication'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for profile_and_level_indication $info['mpeg']['video']['raw']['progressive_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: progressive_sequence $info['mpeg']['video']['raw']['chroma_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for chroma_format $info['mpeg']['video']['raw']['horizontal_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for horizontal_size_extension $info['mpeg']['video']['raw']['vertical_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for vertical_size_extension $info['mpeg']['video']['raw']['bit_rate_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for bit_rate_extension $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. $info['mpeg']['video']['raw']['vbv_buffer_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for vbv_buffer_size_extension $info['mpeg']['video']['raw']['low_delay'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: low_delay $info['mpeg']['video']['raw']['frame_rate_extension_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for frame_rate_extension_n $info['mpeg']['video']['raw']['frame_rate_extension_d'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for frame_rate_extension_d $info['video']['resolution_x'] = $info['mpeg']['video']['raw']['horizontal_size_extension'] << 12 | $info['mpeg']['video']['raw']['horizontal_size_value']; $info['video']['resolution_y'] = $info['mpeg']['video']['raw']['vertical_size_extension'] << 12 | $info['mpeg']['video']['raw']['vertical_size_value']; $info['video']['interlaced'] = !$info['mpeg']['video']['raw']['progressive_sequence']; $info['mpeg']['video']['interlaced'] = !$info['mpeg']['video']['raw']['progressive_sequence']; $info['mpeg']['video']['chroma_format'] = self::chromaFormatTextLookup($info['mpeg']['video']['raw']['chroma_format']); break; case 2: // 0010 Sequence Display Extension ID $info['mpeg']['video']['raw']['video_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for video_format $info['mpeg']['video']['raw']['colour_description'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: colour_description if ($info['mpeg']['video']['raw']['colour_description']) { $info['mpeg']['video']['raw']['colour_primaries'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for colour_primaries $info['mpeg']['video']['raw']['transfer_characteristics'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for transfer_characteristics $info['mpeg']['video']['raw']['matrix_coefficients'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for matrix_coefficients } $info['mpeg']['video']['raw']['display_horizontal_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for display_horizontal_size $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. $info['mpeg']['video']['raw']['display_vertical_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for display_vertical_size $info['mpeg']['video']['video_format'] = self::videoFormatTextLookup($info['mpeg']['video']['raw']['video_format']); break; case 3: // 0011 Quant Matrix Extension ID break; case 5: // 0101 Sequence Scalable Extension ID $info['mpeg']['video']['raw']['scalable_mode'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for scalable_mode $info['mpeg']['video']['raw']['layer_id'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for layer_id if ($info['mpeg']['video']['raw']['scalable_mode'] == 1) { // "spatial scalability" $info['mpeg']['video']['raw']['lower_layer_prediction_horizontal_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for lower_layer_prediction_horizontal_size $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. $info['mpeg']['video']['raw']['lower_layer_prediction_vertical_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for lower_layer_prediction_vertical_size $info['mpeg']['video']['raw']['horizontal_subsampling_factor_m'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for horizontal_subsampling_factor_m $info['mpeg']['video']['raw']['horizontal_subsampling_factor_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for horizontal_subsampling_factor_n $info['mpeg']['video']['raw']['vertical_subsampling_factor_m'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for vertical_subsampling_factor_m $info['mpeg']['video']['raw']['vertical_subsampling_factor_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for vertical_subsampling_factor_n } elseif ($info['mpeg']['video']['raw']['scalable_mode'] == 3) { // "temporal scalability" $info['mpeg']['video']['raw']['picture_mux_enable'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: picture_mux_enable if ($info['mpeg']['video']['raw']['picture_mux_enable']) { $info['mpeg']['video']['raw']['mux_to_progressive_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: mux_to_progressive_sequence } $info['mpeg']['video']['raw']['picture_mux_order'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_mux_order $info['mpeg']['video']['raw']['picture_mux_factor'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_mux_factor } $info['mpeg']['video']['scalable_mode'] = self::scalableModeTextLookup($info['mpeg']['video']['raw']['scalable_mode']); break; case 7: // 0111 Picture Display Extension ID break; case 8: // 1000 Picture Coding Extension ID $info['mpeg']['video']['raw']['f_code_00'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[0][0] (forward horizontal) $info['mpeg']['video']['raw']['f_code_01'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[0][1] (forward vertical) $info['mpeg']['video']['raw']['f_code_10'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[1][0] (backward horizontal) $info['mpeg']['video']['raw']['f_code_11'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[1][1] (backward vertical) $info['mpeg']['video']['raw']['intra_dc_precision'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for intra_dc_precision $info['mpeg']['video']['raw']['picture_structure'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for picture_structure $info['mpeg']['video']['raw']['top_field_first'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: top_field_first $info['mpeg']['video']['raw']['frame_pred_frame_dct'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: frame_pred_frame_dct $info['mpeg']['video']['raw']['concealment_motion_vectors'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: concealment_motion_vectors $info['mpeg']['video']['raw']['q_scale_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: q_scale_type $info['mpeg']['video']['raw']['intra_vlc_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: intra_vlc_format $info['mpeg']['video']['raw']['alternate_scan'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: alternate_scan $info['mpeg']['video']['raw']['repeat_first_field'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: repeat_first_field $info['mpeg']['video']['raw']['chroma_420_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: chroma_420_type $info['mpeg']['video']['raw']['progressive_frame'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: progressive_frame $info['mpeg']['video']['raw']['composite_display_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: composite_display_flag if ($info['mpeg']['video']['raw']['composite_display_flag']) { $info['mpeg']['video']['raw']['v_axis'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: v_axis $info['mpeg']['video']['raw']['field_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for field_sequence $info['mpeg']['video']['raw']['sub_carrier'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: sub_carrier $info['mpeg']['video']['raw']['burst_amplitude'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 7); // 7 bits for burst_amplitude $info['mpeg']['video']['raw']['sub_carrier_phase'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for sub_carrier_phase } $info['mpeg']['video']['intra_dc_precision_bits'] = $info['mpeg']['video']['raw']['intra_dc_precision'] + 8; $info['mpeg']['video']['picture_structure'] = self::pictureStructureTextLookup($info['mpeg']['video']['raw']['picture_structure']); break; case 9: // 1001 Picture Spatial Scalable Extension ID break; case 10: // 1010 Picture Temporal Scalable Extension ID break; default: $this->warning('Unexpected $info[mpeg][video][raw][extension_start_code_identifier] value of ' . $info['mpeg']['video']['raw']['extension_start_code_identifier']); break; } break; case 0xb8: // group_of_pictures_header $GOPcounter++; if ($info['mpeg']['video']['bitrate_mode'] == 'vbr') { $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 4)); // 27 bits needed for group_of_pictures_header $bitstreamoffset = 0; $GOPheader = array(); $GOPheader['byte_offset'] = $MPEGstreamBaseOffset + $StartCodeOffset; $GOPheader['drop_frame_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: drop_frame_flag $GOPheader['time_code_hours'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for time_code_hours $GOPheader['time_code_minutes'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_minutes $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. $GOPheader['time_code_seconds'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_seconds $GOPheader['time_code_pictures'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_pictures $GOPheader['closed_gop'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: closed_gop $GOPheader['broken_link'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: broken_link $time_code_separator = $GOPheader['drop_frame_flag'] ? ';' : ':'; // While non-drop time code is displayed with colons separating the digit pairs—"HH:MM:SS:FF"—drop frame is usually represented with a semi-colon (;) or period (.) as the divider between all the digit pairs—"HH;MM;SS;FF", "HH.MM.SS.FF" $GOPheader['time_code'] = sprintf('%02d' . $time_code_separator . '%02d' . $time_code_separator . '%02d' . $time_code_separator . '%02d', $GOPheader['time_code_hours'], $GOPheader['time_code_minutes'], $GOPheader['time_code_seconds'], $GOPheader['time_code_pictures']); $info['mpeg']['group_of_pictures'][] = $GOPheader; } break; case 0xc0: // audio stream // audio stream case 0xc1: // audio stream // audio stream case 0xc2: // audio stream // audio stream case 0xc3: // audio stream // audio stream case 0xc4: // audio stream // audio stream case 0xc5: // audio stream // audio stream case 0xc6: // audio stream // audio stream case 0xc7: // audio stream // audio stream case 0xc8: // audio stream // audio stream case 0xc9: // audio stream // audio stream case 0xca: // audio stream // audio stream case 0xcb: // audio stream // audio stream case 0xcc: // audio stream // audio stream case 0xcd: // audio stream // audio stream case 0xce: // audio stream // audio stream case 0xcf: // audio stream // audio stream case 0xd0: // audio stream // audio stream case 0xd1: // audio stream // audio stream case 0xd2: // audio stream // audio stream case 0xd3: // audio stream // audio stream case 0xd4: // audio stream // audio stream case 0xd5: // audio stream // audio stream case 0xd6: // audio stream // audio stream case 0xd7: // audio stream // audio stream case 0xd8: // audio stream // audio stream case 0xd9: // audio stream // audio stream case 0xda: // audio stream // audio stream case 0xdb: // audio stream // audio stream case 0xdc: // audio stream // audio stream case 0xdd: // audio stream // audio stream case 0xde: // audio stream // audio stream case 0xdf: // audio stream //case 0xE0: // video stream //case 0xE1: // video stream //case 0xE2: // video stream //case 0xE3: // video stream //case 0xE4: // video stream //case 0xE5: // video stream //case 0xE6: // video stream //case 0xE7: // video stream //case 0xE8: // video stream //case 0xE9: // video stream //case 0xEA: // video stream //case 0xEB: // video stream //case 0xEC: // video stream //case 0xED: // video stream //case 0xEE: // video stream //case 0xEF: // video stream if (isset($ParsedAVchannels[$StartCodeValue])) { break; } $ParsedAVchannels[$StartCodeValue] = $StartCodeValue; // http://en.wikipedia.org/wiki/Packetized_elementary_stream // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html /* $PackedElementaryStream = array(); if ($StartCodeValue >= 0xE0) { $PackedElementaryStream['stream_type'] = 'video'; $PackedElementaryStream['stream_id'] = $StartCodeValue - 0xE0; } else { $PackedElementaryStream['stream_type'] = 'audio'; $PackedElementaryStream['stream_id'] = $StartCodeValue - 0xC0; } $PackedElementaryStream['packet_length'] = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $StartCodeOffset + 4, 2)); $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 6, 3)); // more may be needed below $bitstreamoffset = 0; $PackedElementaryStream['marker_bits'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for marker_bits -- should be "10" = 2 echo 'marker_bits = '.$PackedElementaryStream['marker_bits'].'<br>'; $PackedElementaryStream['scrambling_control'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for scrambling_control -- 00 implies not scrambled $PackedElementaryStream['priority'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: priority $PackedElementaryStream['data_alignment_indicator'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: data_alignment_indicator -- 1 indicates that the PES packet header is immediately followed by the video start code or audio syncword $PackedElementaryStream['copyright'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: copyright -- 1 implies copyrighted $PackedElementaryStream['original_or_copy'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: original_or_copy -- 1 implies original $PackedElementaryStream['pts_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: pts_flag -- Presentation Time Stamp $PackedElementaryStream['dts_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: dts_flag -- Decode Time Stamp $PackedElementaryStream['escr_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: escr_flag -- Elementary Stream Clock Reference $PackedElementaryStream['es_rate_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: es_rate_flag -- Elementary Stream [data] Rate $PackedElementaryStream['dsm_trick_mode_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: dsm_trick_mode_flag -- DSM trick mode - not used by DVD $PackedElementaryStream['additional_copy_info_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: additional_copy_info_flag $PackedElementaryStream['crc_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: crc_flag $PackedElementaryStream['extension_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: extension_flag $PackedElementaryStream['pes_remain_header_length'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 1 bit flag: priority $additional_header_bytes = 0; $additional_header_bytes += ($PackedElementaryStream['pts_flag'] ? 5 : 0); $additional_header_bytes += ($PackedElementaryStream['dts_flag'] ? 5 : 0); $additional_header_bytes += ($PackedElementaryStream['escr_flag'] ? 6 : 0); $additional_header_bytes += ($PackedElementaryStream['es_rate_flag'] ? 3 : 0); $additional_header_bytes += ($PackedElementaryStream['additional_copy_info_flag'] ? 1 : 0); $additional_header_bytes += ($PackedElementaryStream['crc_flag'] ? 2 : 0); $additional_header_bytes += ($PackedElementaryStream['extension_flag'] ? 1 : 0); $PackedElementaryStream['additional_header_bytes'] = $additional_header_bytes; $bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 9, $additional_header_bytes)); $info['mpeg']['packed_elementary_streams'][$PackedElementaryStream['stream_type']][$PackedElementaryStream['stream_id']][] = $PackedElementaryStream; */ $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info = $info; $getid3_mp3 = new getid3_mp3($getid3_temp); for ($i = 0; $i <= 7; $i++) { // some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after // I have no idea why or what the difference is, so this is a stupid hack. // If anybody has any better idea of what's going on, please let me know - info@getid3.org $getid3_temp->info = $info; // only overwrite real data if valid header found //echo 'audio at? '.($MPEGstreamBaseOffset + $StartCodeOffset + 4 + 8 + $i).'<br>'; if ($getid3_mp3->decodeMPEGaudioHeader($MPEGstreamBaseOffset + $StartCodeOffset + 4 + 8 + $i, $getid3_temp->info, false)) { //echo 'yes!<br>'; $info = $getid3_temp->info; $info['audio']['bitrate_mode'] = 'cbr'; $info['audio']['lossless'] = false; break; } } unset($getid3_temp, $getid3_mp3); break; case 0xbc: // Program Stream Map // Program Stream Map case 0xbd: // Private stream 1 (non MPEG audio, subpictures) // Private stream 1 (non MPEG audio, subpictures) case 0xbe: // Padding stream // Padding stream case 0xbf: // Private stream 2 (navigation data) // Private stream 2 (navigation data) case 0xf0: // ECM stream // ECM stream case 0xf1: // EMM stream // EMM stream case 0xf2: // DSM-CC stream // DSM-CC stream case 0xf3: // ISO/IEC_13522_stream // ISO/IEC_13522_stream case 0xf4: // ITU-I Rec. H.222.1 type A // ITU-I Rec. H.222.1 type A case 0xf5: // ITU-I Rec. H.222.1 type B // ITU-I Rec. H.222.1 type B case 0xf6: // ITU-I Rec. H.222.1 type C // ITU-I Rec. H.222.1 type C case 0xf7: // ITU-I Rec. H.222.1 type D // ITU-I Rec. H.222.1 type D case 0xf8: // ITU-I Rec. H.222.1 type E // ITU-I Rec. H.222.1 type E case 0xf9: // ancilliary stream // ancilliary stream case 0xfa: // ISO/IEC 14496-1 SL-packtized stream // ISO/IEC 14496-1 SL-packtized stream case 0xfb: // ISO/IEC 14496-1 FlexMux stream // ISO/IEC 14496-1 FlexMux stream case 0xfc: // metadata stream // metadata stream case 0xfd: // extended stream ID // extended stream ID case 0xfe: // reserved data stream // reserved data stream case 0xff: // program stream directory // ignore break; default: // ignore break; } } while (true); // // Temporary hack to account for interleaving overhead: // if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) { // $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['video']['bitrate'] + $info['audio']['bitrate']); // // // Interleaved MPEG audio/video files have a certain amount of overhead that varies // // by both video and audio bitrates, and not in any sensible, linear/logarithmic pattern // // Use interpolated lookup tables to approximately guess how much is overhead, because // // playtime is calculated as filesize / total-bitrate // $info['playtime_seconds'] *= self::systemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']); // // //switch ($info['video']['bitrate']) { // // case('5000000'): // // $multiplier = 0.93292642112380355828048824319889; // // break; // // case('5500000'): // // $multiplier = 0.93582895375200989965359777343219; // // break; // // case('6000000'): // // $multiplier = 0.93796247714820932532911373859139; // // break; // // case('7000000'): // // $multiplier = 0.9413264083635103463010117778776; // // break; // // default: // // $multiplier = 1; // // break; // //} // //$info['playtime_seconds'] *= $multiplier; // //$info['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.'; // if ($info['video']['bitrate'] < 50000) { // $this->warning('Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.'); // } // } // /* $time_prev = 0; $byte_prev = 0; $vbr_bitrates = array(); foreach ($info['mpeg']['group_of_pictures'] as $gopkey => $gopdata) { $time_this = ($gopdata['time_code_hours'] * 3600) + ($gopdata['time_code_minutes'] * 60) + $gopdata['time_code_seconds'] + ($gopdata['time_code_seconds'] / 30); $byte_this = $gopdata['byte_offset']; if ($gopkey > 0) { if ($time_this > $time_prev) { $bytedelta = $byte_this - $byte_prev; $timedelta = $time_this - $time_prev; $this_bitrate = ($bytedelta * 8) / $timedelta; echo $gopkey.': ('.number_format($time_prev, 2).'-'.number_format($time_this, 2).') '.number_format($bytedelta).' bytes over '.number_format($timedelta, 3).' seconds = '.number_format($this_bitrate / 1000, 2).'kbps<br>'; $time_prev = $time_this; $byte_prev = $byte_this; $vbr_bitrates[] = $this_bitrate; } } } echo 'average_File_bitrate = '.number_format(array_sum($vbr_bitrates) / count($vbr_bitrates), 1).'<br>'; */ //echo '<pre>'.print_r($FramesByGOP, true).'</pre>'; if ($info['mpeg']['video']['bitrate_mode'] == 'vbr') { $last_GOP_id = max(array_keys($FramesByGOP)); $frames_in_last_GOP = count($FramesByGOP[$last_GOP_id]); $gopdata =& $info['mpeg']['group_of_pictures'][$last_GOP_id]; $info['playtime_seconds'] = $gopdata['time_code_hours'] * 3600 + $gopdata['time_code_minutes'] * 60 + $gopdata['time_code_seconds'] + ($gopdata['time_code_pictures'] + $frames_in_last_GOP + 1) / $info['mpeg']['video']['frame_rate']; if (!isset($info['video']['bitrate'])) { $overall_bitrate = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; $info['video']['bitrate'] = $overall_bitrate - (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0); } unset($info['mpeg']['group_of_pictures']); } return true; }
function Analyze() { $info =& $this->getid3->info; $info['fileformat'] = 'quicktime'; $info['quicktime']['hinting'] = false; $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $offset = 0; $atomcounter = 0; while ($offset < $info['avdataend']) { if (!getid3_lib::intValueSupported($offset)) { $info['error'][] = 'Unable to parse atom at offset ' . $offset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break; } fseek($this->getid3->fp, $offset, SEEK_SET); $AtomHeader = fread($this->getid3->fp, 8); $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(fread($this->getid3->fp, 8)); } $info['quicktime'][$atomname]['name'] = $atomname; $info['quicktime'][$atomname]['size'] = $atomsize; $info['quicktime'][$atomname]['offset'] = $offset; if ($offset + $atomsize > $info['avdataend']) { $info['error'][] = 'Atom at offset ' . $offset . ' claims to go beyond end-of-file (length: ' . $atomsize . ' bytes)'; return false; } if ($atomsize == 0) { // Furthermore, for historical reasons the list of atoms is optionally // terminated by a 32-bit integer set to 0. If you are writing a program // to read user data atoms, you should allow for the terminating 0. break; } switch ($atomname) { case 'mdat': // Media DATa atom // 'mdat' contains the actual data for the audio/video if ($atomsize > 8 && (!isset($info['avdataend_tmp']) || $info['quicktime'][$atomname]['size'] > $info['avdataend_tmp'] - $info['avdataoffset'])) { $info['avdataoffset'] = $info['quicktime'][$atomname]['offset'] + 8; $OldAVDataEnd = $info['avdataend']; $info['avdataend'] = $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_mp3 = new getid3_mp3($getid3_temp); if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode(fread($this->getid3->fp, 4)))) { $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $value) { $info['warning'][] = $value; } } if (!empty($getid3_temp->info['mpeg'])) { $info['mpeg'] = $getid3_temp->info['mpeg']; if (isset($info['mpeg']['audio'])) { $info['audio']['dataformat'] = 'mp3'; $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')); $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); $info['bitrate'] = $info['audio']['bitrate']; } } } unset($getid3_mp3, $getid3_temp); $info['avdataend'] = $OldAVDataEnd; unset($OldAVDataEnd); } break; case 'free': // FREE space atom // FREE space atom case 'skip': // SKIP atom // SKIP atom case 'wide': // 64-bit expansion placeholder atom // 'free', 'skip' and 'wide' are just padding, contains no useful data at all break; default: $atomHierarchy = array(); $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($this->getid3->fp, $atomsize), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); break; } $offset += $atomsize; $atomcounter++; } if (!empty($info['avdataend_tmp'])) { // this value is assigned to a temp value and then erased because // otherwise any atoms beyond the 'mdat' atom would not get parsed $info['avdataend'] = $info['avdataend_tmp']; unset($info['avdataend_tmp']); } if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { $info['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; } if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) { $info['audio']['bitrate'] = $info['bitrate']; } if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) { foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) { $samples_per_second = $samples_count / $info['playtime_seconds']; if ($samples_per_second > 240) { // has to be audio samples } else { $info['video']['frame_rate'] = $samples_per_second; break; } } } if ($info['audio']['dataformat'] == 'mp4' && empty($info['video']['resolution_x'])) { $info['fileformat'] = 'mp4'; $info['mime_type'] = 'audio/mp4'; unset($info['video']['dataformat']); } if (!$this->ReturnAtomData) { unset($info['quicktime']['moov']); } if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) { $info['audio']['dataformat'] = 'quicktime'; } if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) { $info['video']['dataformat'] = 'quicktime'; } return true; }
function Analyze() { $info =& $this->getid3->info; $info['fileformat'] = 'gzip'; $start_length = 10; $unpack_header = 'a1id1/a1id2/a1cmethod/a1flags/a4mtime/a1xflags/a1os'; //+---+---+---+---+---+---+---+---+---+---+ //|ID1|ID2|CM |FLG| MTIME |XFL|OS | //+---+---+---+---+---+---+---+---+---+---+ if ($info['filesize'] > $info['php_memory_limit']) { $info['error'][] = 'File is too large (' . number_format($info['filesize']) . ' bytes) to read into memory (limit: ' . number_format($info['php_memory_limit'] / 1048576) . 'MB)'; return false; } fseek($this->getid3->fp, 0); $buffer = fread($this->getid3->fp, $info['filesize']); $arr_members = explode("‹", $buffer); while (true) { $is_wrong_members = false; $num_members = intval(count($arr_members)); for ($i = 0; $i < $num_members; $i++) { if (strlen($arr_members[$i]) == 0) { continue; } $buf = "‹" . $arr_members[$i]; $attr = unpack($unpack_header, substr($buf, 0, $start_length)); if (!$this->get_os_type(ord($attr['os']))) { // Merge member with previous if wrong OS type $arr_members[$i - 1] .= $buf; $arr_members[$i] = ''; $is_wrong_members = true; continue; } } if (!$is_wrong_members) { break; } } $info['gzip']['files'] = array(); $fpointer = 0; $idx = 0; for ($i = 0; $i < $num_members; $i++) { if (strlen($arr_members[$i]) == 0) { continue; } $thisInfo =& $info['gzip']['member_header'][++$idx]; $buff = "‹" . $arr_members[$i]; $attr = unpack($unpack_header, substr($buff, 0, $start_length)); $thisInfo['filemtime'] = getid3_lib::LittleEndian2Int($attr['mtime']); $thisInfo['raw']['id1'] = ord($attr['cmethod']); $thisInfo['raw']['id2'] = ord($attr['cmethod']); $thisInfo['raw']['cmethod'] = ord($attr['cmethod']); $thisInfo['raw']['os'] = ord($attr['os']); $thisInfo['raw']['xflags'] = ord($attr['xflags']); $thisInfo['raw']['flags'] = ord($attr['flags']); $thisInfo['flags']['crc16'] = (bool) ($thisInfo['raw']['flags'] & 0x2); $thisInfo['flags']['extra'] = (bool) ($thisInfo['raw']['flags'] & 0x4); $thisInfo['flags']['filename'] = (bool) ($thisInfo['raw']['flags'] & 0x8); $thisInfo['flags']['comment'] = (bool) ($thisInfo['raw']['flags'] & 0x10); $thisInfo['compression'] = $this->get_xflag_type($thisInfo['raw']['xflags']); $thisInfo['os'] = $this->get_os_type($thisInfo['raw']['os']); if (!$thisInfo['os']) { $info['error'][] = 'Read error on gzip file'; return false; } $fpointer = 10; $arr_xsubfield = array(); // bit 2 - FLG.FEXTRA //+---+---+=================================+ //| XLEN |...XLEN bytes of "extra field"...| //+---+---+=================================+ if ($thisInfo['flags']['extra']) { $w_xlen = substr($buff, $fpointer, 2); $xlen = getid3_lib::LittleEndian2Int($w_xlen); $fpointer += 2; $thisInfo['raw']['xfield'] = substr($buff, $fpointer, $xlen); // Extra SubFields //+---+---+---+---+==================================+ //|SI1|SI2| LEN |... LEN bytes of subfield data ...| //+---+---+---+---+==================================+ $idx = 0; while (true) { if ($idx >= $xlen) { break; } $si1 = ord(substr($buff, $fpointer + $idx++, 1)); $si2 = ord(substr($buff, $fpointer + $idx++, 1)); if ($si1 == 0x41 && $si2 == 0x70) { $w_xsublen = substr($buff, $fpointer + $idx, 2); $xsublen = getid3_lib::LittleEndian2Int($w_xsublen); $idx += 2; $arr_xsubfield[] = substr($buff, $fpointer + $idx, $xsublen); $idx += $xsublen; } else { break; } } $fpointer += $xlen; } // bit 3 - FLG.FNAME //+=========================================+ //|...original file name, zero-terminated...| //+=========================================+ // GZIP files may have only one file, with no filename, so assume original filename is current filename without .gz $thisInfo['filename'] = preg_replace('#\\.gz$#i', '', $info['filename']); if ($thisInfo['flags']['filename']) { while (true) { if (ord($buff[$fpointer]) == 0) { $fpointer++; break; } $thisInfo['filename'] .= $buff[$fpointer]; $fpointer++; } } // bit 4 - FLG.FCOMMENT //+===================================+ //|...file comment, zero-terminated...| //+===================================+ if ($thisInfo['flags']['comment']) { while (true) { if (ord($buff[$fpointer]) == 0) { $fpointer++; break; } $thisInfo['comment'] .= $buff[$fpointer]; $fpointer++; } } // bit 1 - FLG.FHCRC //+---+---+ //| CRC16 | //+---+---+ if ($thisInfo['flags']['crc16']) { $w_crc = substr($buff, $fpointer, 2); $thisInfo['crc16'] = getid3_lib::LittleEndian2Int($w_crc); $fpointer += 2; } // bit 0 - FLG.FTEXT //if ($thisInfo['raw']['flags'] & 0x01) { // Ignored... //} // bits 5, 6, 7 - reserved $thisInfo['crc32'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 8, 4)); $thisInfo['filesize'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 4)); $info['gzip']['files'] = getid3_lib::array_merge_clobber($info['gzip']['files'], getid3_lib::CreateDeepArray($thisInfo['filename'], '/', $thisInfo['filesize'])); if ($this->option_gzip_parse_contents) { // Try to inflate GZip $csize = 0; $inflated = ''; $chkcrc32 = ''; if (function_exists('gzinflate')) { $cdata = substr($buff, $fpointer); $cdata = substr($cdata, 0, strlen($cdata) - 8); $csize = strlen($cdata); $inflated = gzinflate($cdata); // Calculate CRC32 for inflated content $thisInfo['crc32_valid'] = (bool) (sprintf('%u', crc32($inflated)) == $thisInfo['crc32']); // determine format $formattest = substr($inflated, 0, 32774); $getid3_temp = new getID3(); $determined_format = $getid3_temp->GetFileFormat($formattest); unset($getid3_temp); // file format is determined $determined_format['module'] = isset($determined_format['module']) ? $determined_format['module'] : ''; switch ($determined_format['module']) { case 'tar': // view TAR-file info if (file_exists(GETID3_INCLUDEPATH . $determined_format['include']) && (include_once GETID3_INCLUDEPATH . $determined_format['include'])) { if (($temp_tar_filename = tempnam(GETID3_TEMP_DIR, 'getID3')) === false) { // can't find anywhere to create a temp file, abort $info['error'][] = 'Unable to create temp file to parse TAR inside GZIP file'; break; } if ($fp_temp_tar = fopen($temp_tar_filename, 'w+b')) { fwrite($fp_temp_tar, $inflated); fclose($fp_temp_tar); $getid3_temp = new getID3(); $getid3_temp->openfile($temp_tar_filename); $getid3_tar = new getid3_tar($getid3_temp); $getid3_tar->Analyze(); $info['gzip']['member_header'][$idx]['tar'] = $getid3_temp->info['tar']; unset($getid3_temp, $getid3_tar); unlink($temp_tar_filename); } else { $info['error'][] = 'Unable to fopen() temp file to parse TAR inside GZIP file'; break; } } break; case '': default: // unknown or unhandled format break; } } } } return true; }
/** * * @access public * @return **/ public function getID3Mime() { $mime = NULL; $filename = realpath($this->file_src_pathname); if (!file_exists($filename)) { $this->error = 'File does not exist: "' . htmlentities($filename); return false; } elseif (!is_readable($filename)) { $this->error = 'File is not readable: "' . htmlentities($filename); return false; } require_once CAT_PATH . '/modules/lib_getid3/getid3/getid3.php'; $getID3 = new getID3(); if ($fp = fopen($filename, 'rb')) { $getID3->openfile($filename); if (empty($getID3->info['error'])) { // ID3v2 is the only tag format that might be prepended in front of files, and it's non-trivial to skip, easier just to parse it and know where to skip to getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.tag.id3v2.php', __FILE__, true); $getid3_id3v2 = new getid3_id3v2($getID3); $getid3_id3v2->Analyze(); fseek($fp, $getID3->info['avdataoffset'], SEEK_SET); $formattest = fread($fp, 16); // 16 bytes is sufficient for any format except ISO CD-image fclose($fp); $DeterminedFormatInfo = $getID3->GetFileFormat($formattest); $mime = $DeterminedFormatInfo['mime_type']; } else { $this->error = 'Failed to getID3->openfile "' . htmlentities($filename); } } else { $this->error = 'Failed to fopen "' . htmlentities($filename); } $this->log()->logDebug('MIME type detected as [' . $mime . '] by getID3 library'); return $mime; }
public function Analyze() { $info =& $this->getid3->info; // parse container try { $this->parseEBML($info); } catch (Exception $e) { $info['error'][] = 'EBML parser: ' . $e->getMessage(); } // calculate playtime if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { foreach ($info['matroska']['info'] as $key => $infoarray) { if (isset($infoarray['Duration'])) { // TimecodeScale is how many nanoseconds each Duration unit is $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); break; } } } // extract tags if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { foreach ($info['matroska']['tags'] as $key => $infoarray) { $this->ExtractCommentsSimpleTag($infoarray); } } // process tracks if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { $track_info = array(); $track_info['dataformat'] = self::CodecIDtoCommonName($trackarray['CodecID']); $track_info['default'] = isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true; if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; } switch ($trackarray['TrackType']) { case 1: // Video $track_info['resolution_x'] = $trackarray['PixelWidth']; $track_info['resolution_y'] = $trackarray['PixelHeight']; $track_info['display_unit'] = self::displayUnit(isset($trackarray['DisplayUnit']) ? $trackarray['DisplayUnit'] : 0); $track_info['display_x'] = isset($trackarray['DisplayWidth']) ? $trackarray['DisplayWidth'] : $trackarray['PixelWidth']; $track_info['display_y'] = isset($trackarray['DisplayHeight']) ? $trackarray['DisplayHeight'] : $trackarray['PixelHeight']; if (isset($trackarray['PixelCropBottom'])) { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; } if (isset($trackarray['PixelCropTop'])) { $track_info['crop_top'] = $trackarray['PixelCropTop']; } if (isset($trackarray['PixelCropLeft'])) { $track_info['crop_left'] = $trackarray['PixelCropLeft']; } if (isset($trackarray['PixelCropRight'])) { $track_info['crop_right'] = $trackarray['PixelCropRight']; } if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } switch ($trackarray['CodecID']) { case 'V_MS/VFW/FOURCC': getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, true); $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']); $track_info['codec'] = getid3_riff::fourccLookup($parsed['fourcc']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; break; /*case 'V_MPEG4/ISO/AVC': $h264['profile'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 1, 1)); $h264['level'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 3, 1)); $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 4, 1)); $h264['NALUlength'] = ($rn & 3) + 1; $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 5, 1)); $nsps = ($rn & 31); $offset = 6; for ($i = 0; $i < $nsps; $i ++) { $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); $h264['SPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); $offset += 2 + $length; } $npps = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 1)); $offset += 1; for ($i = 0; $i < $npps; $i ++) { $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); $h264['PPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); $offset += 2 + $length; } $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $h264; break;*/ } $info['video']['streams'][] = $track_info; break; case 2: // Audio $track_info['sample_rate'] = isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0; $track_info['channels'] = isset($trackarray['Channels']) ? $trackarray['Channels'] : 1; $track_info['language'] = isset($trackarray['Language']) ? $trackarray['Language'] : 'eng'; if (isset($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } switch ($trackarray['CodecID']) { case 'A_PCM/INT/LIT': case 'A_PCM/INT/BIG': $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; break; case 'A_AC3': case 'A_DTS': case 'A_MPEG/L3': case 'A_MPEG/L2': case 'A_FLAC': getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.' . ($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']) . '.php', __FILE__, true); if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { $this->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because $info[matroska][track_data_offsets][' . $trackarray['TrackNumber'] . '] not set'); break; } // create temp instance $getid3_temp = new getID3(); if ($track_info['dataformat'] != 'flac') { $getid3_temp->openfile($this->getid3->filename); } $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; if ($track_info['dataformat'][0] == 'm' || $track_info['dataformat'] == 'flac') { $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; } // analyze $class = 'getid3_' . ($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']); $header_data_key = $track_info['dataformat'][0] == 'm' ? 'mpeg' : $track_info['dataformat']; $getid3_audio = new $class($getid3_temp, __CLASS__); if ($track_info['dataformat'] == 'flac') { $getid3_audio->AnalyzeString($trackarray['CodecPrivate']); } else { $getid3_audio->Analyze(); } if (!empty($getid3_temp->info[$header_data_key])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } else { $this->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because ' . $class . '::Analyze() failed at offset ' . $getid3_temp->info['avdataoffset']); } // copy errors and warnings if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warning($class . '() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warning($class . '() says: [' . $newerror . ']'); } } unset($getid3_temp, $getid3_audio); break; case 'A_AAC': case 'A_AAC/MPEG2/LC': case 'A_AAC/MPEG2/LC/SBR': case 'A_AAC/MPEG4/LC': case 'A_AAC/MPEG4/LC/SBR': $this->warning($trackarray['CodecID'] . ' audio data contains no header, audio/video bitrates can\'t be calculated'); break; case 'A_VORBIS': if (!isset($trackarray['CodecPrivate'])) { $this->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because CodecPrivate data not set'); break; } $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1); if ($vorbis_offset === false) { $this->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because CodecPrivate data does not contain "vorbis" keyword'); break; } $vorbis_offset -= 1; getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ogg.php', __FILE__, true); // create temp instance $getid3_temp = new getID3(); // analyze $getid3_ogg = new getid3_ogg($getid3_temp); $oggpageinfo['page_seqno'] = 0; $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); if (!empty($getid3_temp->info['ogg'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg']; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } // copy errors and warnings if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warning('getid3_ogg() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warning('getid3_ogg() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) { $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal']; } unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset); break; case 'A_MS/ACM': getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, true); $parsed = getid3_riff::parseWAVEFORMATex($trackarray['CodecPrivate']); foreach ($parsed as $key => $value) { if ($key != 'raw') { $track_info[$key] = $value; } } $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; break; default: $this->warning('Unhandled audio type "' . (isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '') . '"'); break; } $info['audio']['streams'][] = $track_info; break; } } if (!empty($info['video']['streams'])) { $info['video'] = self::getDefaultStreamInfo($info['video']['streams']); } if (!empty($info['audio']['streams'])) { $info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']); } } // process attachments if (isset($info['matroska']['attachments']) && $this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE) { foreach ($info['matroska']['attachments'] as $i => $entry) { if (strpos($entry['FileMimeType'], 'image/') === 0 && !empty($entry['FileData'])) { $info['matroska']['comments']['picture'][] = array('data' => $entry['FileData'], 'image_mime' => $entry['FileMimeType'], 'filename' => $entry['FileName']); } } } // determine mime type if (!empty($info['video']['streams'])) { $info['mime_type'] = $info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska'; } elseif (!empty($info['audio']['streams'])) { $info['mime_type'] = $info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska'; } elseif (isset($info['mime_type'])) { unset($info['mime_type']); } return true; }
public function ParseRIFFdata(&$RIFFdata) { $info =& $this->getid3->info; if ($RIFFdata) { $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); $fp_temp = fopen($tempfile, 'wb'); $RIFFdataLength = strlen($RIFFdata); $NewLengthString = getid3_lib::LittleEndian2String($RIFFdataLength, 4); for ($i = 0; $i < 4; $i++) { $RIFFdata[$i + 4] = $NewLengthString[$i]; } fwrite($fp_temp, $RIFFdata); fclose($fp_temp); $getid3_temp = new getID3(); $getid3_temp->openfile($tempfile); $getid3_temp->info['filesize'] = $RIFFdataLength; $getid3_temp->info['filenamepath'] = $info['filenamepath']; $getid3_temp->info['tags'] = $info['tags']; $getid3_temp->info['warning'] = $info['warning']; $getid3_temp->info['error'] = $info['error']; $getid3_temp->info['comments'] = $info['comments']; $getid3_temp->info['audio'] = isset($info['audio']) ? $info['audio'] : array(); $getid3_temp->info['video'] = isset($info['video']) ? $info['video'] : array(); $getid3_riff = new getid3_riff($getid3_temp); $getid3_riff->Analyze(); $info['riff'] = $getid3_temp->info['riff']; $info['warning'] = $getid3_temp->info['warning']; $info['error'] = $getid3_temp->info['error']; $info['tags'] = $getid3_temp->info['tags']; $info['comments'] = $getid3_temp->info['comments']; unset($getid3_riff, $getid3_temp); unlink($tempfile); } return false; }
public function Analyze() { $info =& $this->getid3->info; // Shortcuts $thisfile_audio =& $info['audio']; $thisfile_video =& $info['video']; $info['asf'] = array(); $thisfile_asf =& $info['asf']; $thisfile_asf['comments'] = array(); $thisfile_asf_comments =& $thisfile_asf['comments']; $thisfile_asf['header_object'] = array(); $thisfile_asf_headerobject =& $thisfile_asf['header_object']; // ASF structure: // * Header Object [required] // * File Properties Object [required] (global file attributes) // * Stream Properties Object [required] (defines media stream & characteristics) // * Header Extension Object [required] (additional functionality) // * Content Description Object (bibliographic information) // * Script Command Object (commands for during playback) // * Marker Object (named jumped points within the file) // * Data Object [required] // * Data Packets // * Index Object // Header Object: (mandatory, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for header object - GETID3_ASF_Header_Object // Object Size QWORD 64 // size of header object, including 30 bytes of Header Object header // Number of Header Objects DWORD 32 // number of objects in header object // Reserved1 BYTE 8 // hardcoded: 0x01 // Reserved2 BYTE 8 // hardcoded: 0x02 $info['fileformat'] = 'asf'; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $HeaderObjectData = fread($this->getid3->fp, 30); $thisfile_asf_headerobject['objectid'] = substr($HeaderObjectData, 0, 16); $thisfile_asf_headerobject['objectid_guid'] = $this->BytestringToGUID($thisfile_asf_headerobject['objectid']); if ($thisfile_asf_headerobject['objectid'] != GETID3_ASF_Header_Object) { $info['warning'][] = 'ASF header GUID {' . $this->BytestringToGUID($thisfile_asf_headerobject['objectid']) . '} does not match expected "GETID3_ASF_Header_Object" GUID {' . $this->BytestringToGUID(GETID3_ASF_Header_Object) . '}'; unset($info['fileformat']); unset($info['asf']); return false; break; } $thisfile_asf_headerobject['objectsize'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 16, 8)); $thisfile_asf_headerobject['headerobjects'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 24, 4)); $thisfile_asf_headerobject['reserved1'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 28, 1)); $thisfile_asf_headerobject['reserved2'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 29, 1)); $NextObjectOffset = ftell($this->getid3->fp); $ASFHeaderData = fread($this->getid3->fp, $thisfile_asf_headerobject['objectsize'] - 30); $offset = 0; for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) { $NextObjectGUID = substr($ASFHeaderData, $offset, 16); $offset += 16; $NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID); $NextObjectSize = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); $offset += 8; switch ($NextObjectGUID) { case GETID3_ASF_File_Properties_Object: // File Properties Object: (mandatory, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for file properties object - GETID3_ASF_File_Properties_Object // Object Size QWORD 64 // size of file properties object, including 104 bytes of File Properties Object header // File ID GUID 128 // unique ID - identical to File ID in Data Object // File Size QWORD 64 // entire file in bytes. Invalid if Broadcast Flag == 1 // Creation Date QWORD 64 // date & time of file creation. Maybe invalid if Broadcast Flag == 1 // Data Packets Count QWORD 64 // number of data packets in Data Object. Invalid if Broadcast Flag == 1 // Play Duration QWORD 64 // playtime, in 100-nanosecond units. Invalid if Broadcast Flag == 1 // Send Duration QWORD 64 // time needed to send file, in 100-nanosecond units. Players can ignore this value. Invalid if Broadcast Flag == 1 // Preroll QWORD 64 // time to buffer data before starting to play file, in 1-millisecond units. If <> 0, PlayDuration and PresentationTime have been offset by this amount // Flags DWORD 32 // // * Broadcast Flag bits 1 (0x01) // file is currently being written, some header values are invalid // * Seekable Flag bits 1 (0x02) // is file seekable // * Reserved bits 30 (0xFFFFFFFC) // reserved - set to zero // Minimum Data Packet Size DWORD 32 // in bytes. should be same as Maximum Data Packet Size. Invalid if Broadcast Flag == 1 // Maximum Data Packet Size DWORD 32 // in bytes. should be same as Minimum Data Packet Size. Invalid if Broadcast Flag == 1 // Maximum Bitrate DWORD 32 // maximum instantaneous bitrate in bits per second for entire file, including all data streams and ASF overhead // shortcut $thisfile_asf['file_properties_object'] = array(); $thisfile_asf_filepropertiesobject =& $thisfile_asf['file_properties_object']; $thisfile_asf_filepropertiesobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_filepropertiesobject['objectid'] = $NextObjectGUID; $thisfile_asf_filepropertiesobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_filepropertiesobject['objectsize'] = $NextObjectSize; $thisfile_asf_filepropertiesobject['fileid'] = substr($ASFHeaderData, $offset, 16); $offset += 16; $thisfile_asf_filepropertiesobject['fileid_guid'] = $this->BytestringToGUID($thisfile_asf_filepropertiesobject['fileid']); $thisfile_asf_filepropertiesobject['filesize'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); $offset += 8; $thisfile_asf_filepropertiesobject['creation_date'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); $thisfile_asf_filepropertiesobject['creation_date_unix'] = $this->FILETIMEtoUNIXtime($thisfile_asf_filepropertiesobject['creation_date']); $offset += 8; $thisfile_asf_filepropertiesobject['data_packets'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); $offset += 8; $thisfile_asf_filepropertiesobject['play_duration'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); $offset += 8; $thisfile_asf_filepropertiesobject['send_duration'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); $offset += 8; $thisfile_asf_filepropertiesobject['preroll'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); $offset += 8; $thisfile_asf_filepropertiesobject['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_filepropertiesobject['flags']['broadcast'] = (bool) ($thisfile_asf_filepropertiesobject['flags_raw'] & 0x1); $thisfile_asf_filepropertiesobject['flags']['seekable'] = (bool) ($thisfile_asf_filepropertiesobject['flags_raw'] & 0x2); $thisfile_asf_filepropertiesobject['min_packet_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_filepropertiesobject['max_packet_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_filepropertiesobject['max_bitrate'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; if ($thisfile_asf_filepropertiesobject['flags']['broadcast']) { // broadcast flag is set, some values invalid unset($thisfile_asf_filepropertiesobject['filesize']); unset($thisfile_asf_filepropertiesobject['data_packets']); unset($thisfile_asf_filepropertiesobject['play_duration']); unset($thisfile_asf_filepropertiesobject['send_duration']); unset($thisfile_asf_filepropertiesobject['min_packet_size']); unset($thisfile_asf_filepropertiesobject['max_packet_size']); } else { // broadcast flag NOT set, perform calculations $info['playtime_seconds'] = $thisfile_asf_filepropertiesobject['play_duration'] / 10000000 - $thisfile_asf_filepropertiesobject['preroll'] / 1000; //$info['bitrate'] = $thisfile_asf_filepropertiesobject['max_bitrate']; $info['bitrate'] = (isset($thisfile_asf_filepropertiesobject['filesize']) ? $thisfile_asf_filepropertiesobject['filesize'] : $info['filesize']) * 8 / $info['playtime_seconds']; } break; case GETID3_ASF_Stream_Properties_Object: // Stream Properties Object: (mandatory, one per media stream) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for stream properties object - GETID3_ASF_Stream_Properties_Object // Object Size QWORD 64 // size of stream properties object, including 78 bytes of Stream Properties Object header // Stream Type GUID 128 // GETID3_ASF_Audio_Media, GETID3_ASF_Video_Media or GETID3_ASF_Command_Media // Error Correction Type GUID 128 // GETID3_ASF_Audio_Spread for audio-only streams, GETID3_ASF_No_Error_Correction for other stream types // Time Offset QWORD 64 // 100-nanosecond units. typically zero. added to all timestamps of samples in the stream // Type-Specific Data Length DWORD 32 // number of bytes for Type-Specific Data field // Error Correction Data Length DWORD 32 // number of bytes for Error Correction Data field // Flags WORD 16 // // * Stream Number bits 7 (0x007F) // number of this stream. 1 <= valid <= 127 // * Reserved bits 8 (0x7F80) // reserved - set to zero // * Encrypted Content Flag bits 1 (0x8000) // stream contents encrypted if set // Reserved DWORD 32 // reserved - set to zero // Type-Specific Data BYTESTREAM variable // type-specific format data, depending on value of Stream Type // Error Correction Data BYTESTREAM variable // error-correction-specific format data, depending on value of Error Correct Type // There is one GETID3_ASF_Stream_Properties_Object for each stream (audio, video) but the // stream number isn't known until halfway through decoding the structure, hence it // it is decoded to a temporary variable and then stuck in the appropriate index later $StreamPropertiesObjectData['offset'] = $NextObjectOffset + $offset; $StreamPropertiesObjectData['objectid'] = $NextObjectGUID; $StreamPropertiesObjectData['objectid_guid'] = $NextObjectGUIDtext; $StreamPropertiesObjectData['objectsize'] = $NextObjectSize; $StreamPropertiesObjectData['stream_type'] = substr($ASFHeaderData, $offset, 16); $offset += 16; $StreamPropertiesObjectData['stream_type_guid'] = $this->BytestringToGUID($StreamPropertiesObjectData['stream_type']); $StreamPropertiesObjectData['error_correct_type'] = substr($ASFHeaderData, $offset, 16); $offset += 16; $StreamPropertiesObjectData['error_correct_guid'] = $this->BytestringToGUID($StreamPropertiesObjectData['error_correct_type']); $StreamPropertiesObjectData['time_offset'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); $offset += 8; $StreamPropertiesObjectData['type_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $StreamPropertiesObjectData['error_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $StreamPropertiesObjectData['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $StreamPropertiesObjectStreamNumber = $StreamPropertiesObjectData['flags_raw'] & 0x7f; $StreamPropertiesObjectData['flags']['encrypted'] = (bool) ($StreamPropertiesObjectData['flags_raw'] & 0x8000); $offset += 4; // reserved - DWORD $StreamPropertiesObjectData['type_specific_data'] = substr($ASFHeaderData, $offset, $StreamPropertiesObjectData['type_data_length']); $offset += $StreamPropertiesObjectData['type_data_length']; $StreamPropertiesObjectData['error_correct_data'] = substr($ASFHeaderData, $offset, $StreamPropertiesObjectData['error_data_length']); $offset += $StreamPropertiesObjectData['error_data_length']; switch ($StreamPropertiesObjectData['stream_type']) { case GETID3_ASF_Audio_Media: $thisfile_audio['dataformat'] = !empty($thisfile_audio['dataformat']) ? $thisfile_audio['dataformat'] : 'asf'; $thisfile_audio['bitrate_mode'] = !empty($thisfile_audio['bitrate_mode']) ? $thisfile_audio['bitrate_mode'] : 'cbr'; $audiodata = getid3_riff::parseWAVEFORMATex(substr($StreamPropertiesObjectData['type_specific_data'], 0, 16)); unset($audiodata['raw']); $thisfile_audio = getid3_lib::array_merge_noclobber($audiodata, $thisfile_audio); break; case GETID3_ASF_Video_Media: $thisfile_video['dataformat'] = !empty($thisfile_video['dataformat']) ? $thisfile_video['dataformat'] : 'asf'; $thisfile_video['bitrate_mode'] = !empty($thisfile_video['bitrate_mode']) ? $thisfile_video['bitrate_mode'] : 'cbr'; break; case GETID3_ASF_Command_Media: default: // do nothing break; } $thisfile_asf['stream_properties_object'][$StreamPropertiesObjectStreamNumber] = $StreamPropertiesObjectData; unset($StreamPropertiesObjectData); // clear for next stream, if any break; case GETID3_ASF_Header_Extension_Object: // Header Extension Object: (mandatory, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Header Extension object - GETID3_ASF_Header_Extension_Object // Object Size QWORD 64 // size of Header Extension object, including 46 bytes of Header Extension Object header // Reserved Field 1 GUID 128 // hardcoded: GETID3_ASF_Reserved_1 // Reserved Field 2 WORD 16 // hardcoded: 0x00000006 // Header Extension Data Size DWORD 32 // in bytes. valid: 0, or > 24. equals object size minus 46 // Header Extension Data BYTESTREAM variable // array of zero or more extended header objects // shortcut $thisfile_asf['header_extension_object'] = array(); $thisfile_asf_headerextensionobject =& $thisfile_asf['header_extension_object']; $thisfile_asf_headerextensionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_headerextensionobject['objectid'] = $NextObjectGUID; $thisfile_asf_headerextensionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_headerextensionobject['objectsize'] = $NextObjectSize; $thisfile_asf_headerextensionobject['reserved_1'] = substr($ASFHeaderData, $offset, 16); $offset += 16; $thisfile_asf_headerextensionobject['reserved_1_guid'] = $this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']); if ($thisfile_asf_headerextensionobject['reserved_1'] != GETID3_ASF_Reserved_1) { $info['warning'][] = 'header_extension_object.reserved_1 GUID (' . $this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']) . ') does not match expected "GETID3_ASF_Reserved_1" GUID (' . $this->BytestringToGUID(GETID3_ASF_Reserved_1) . ')'; //return false; break; } $thisfile_asf_headerextensionobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) { $info['warning'][] = 'header_extension_object.reserved_2 (' . getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']) . ') does not match expected value of "6"'; //return false; break; } $thisfile_asf_headerextensionobject['extension_data_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_headerextensionobject['extension_data'] = substr($ASFHeaderData, $offset, $thisfile_asf_headerextensionobject['extension_data_size']); $unhandled_sections = 0; $thisfile_asf_headerextensionobject['extension_data_parsed'] = $this->ASF_HeaderExtensionObjectDataParse($thisfile_asf_headerextensionobject['extension_data'], $unhandled_sections); if ($unhandled_sections === 0) { unset($thisfile_asf_headerextensionobject['extension_data']); } $offset += $thisfile_asf_headerextensionobject['extension_data_size']; break; case GETID3_ASF_Codec_List_Object: // Codec List Object: (optional, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Codec List object - GETID3_ASF_Codec_List_Object // Object Size QWORD 64 // size of Codec List object, including 44 bytes of Codec List Object header // Reserved GUID 128 // hardcoded: 86D15241-311D-11D0-A3A4-00A0C90348F6 // Codec Entries Count DWORD 32 // number of entries in Codec Entries array // Codec Entries array of: variable // // * Type WORD 16 // 0x0001 = Video Codec, 0x0002 = Audio Codec, 0xFFFF = Unknown Codec // * Codec Name Length WORD 16 // number of Unicode characters stored in the Codec Name field // * Codec Name WCHAR variable // array of Unicode characters - name of codec used to create the content // * Codec Description Length WORD 16 // number of Unicode characters stored in the Codec Description field // * Codec Description WCHAR variable // array of Unicode characters - description of format used to create the content // * Codec Information Length WORD 16 // number of Unicode characters stored in the Codec Information field // * Codec Information BYTESTREAM variable // opaque array of information bytes about the codec used to create the content // shortcut $thisfile_asf['codec_list_object'] = array(); $thisfile_asf_codeclistobject =& $thisfile_asf['codec_list_object']; $thisfile_asf_codeclistobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_codeclistobject['objectid'] = $NextObjectGUID; $thisfile_asf_codeclistobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_codeclistobject['objectsize'] = $NextObjectSize; $thisfile_asf_codeclistobject['reserved'] = substr($ASFHeaderData, $offset, 16); $offset += 16; $thisfile_asf_codeclistobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']); if ($thisfile_asf_codeclistobject['reserved'] != $this->GUIDtoBytestring('86D15241-311D-11D0-A3A4-00A0C90348F6')) { $info['warning'][] = 'codec_list_object.reserved GUID {' . $this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']) . '} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}'; //return false; break; } $thisfile_asf_codeclistobject['codec_entries_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; for ($CodecEntryCounter = 0; $CodecEntryCounter < $thisfile_asf_codeclistobject['codec_entries_count']; $CodecEntryCounter++) { // shortcut $thisfile_asf_codeclistobject['codec_entries'][$CodecEntryCounter] = array(); $thisfile_asf_codeclistobject_codecentries_current =& $thisfile_asf_codeclistobject['codec_entries'][$CodecEntryCounter]; $thisfile_asf_codeclistobject_codecentries_current['type_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_codeclistobject_codecentries_current['type'] = $this->ASFCodecListObjectTypeLookup($thisfile_asf_codeclistobject_codecentries_current['type_raw']); $CodecNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character $offset += 2; $thisfile_asf_codeclistobject_codecentries_current['name'] = substr($ASFHeaderData, $offset, $CodecNameLength); $offset += $CodecNameLength; $CodecDescriptionLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character $offset += 2; $thisfile_asf_codeclistobject_codecentries_current['description'] = substr($ASFHeaderData, $offset, $CodecDescriptionLength); $offset += $CodecDescriptionLength; $CodecInformationLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_codeclistobject_codecentries_current['information'] = substr($ASFHeaderData, $offset, $CodecInformationLength); $offset += $CodecInformationLength; if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { // audio codec if (strpos($thisfile_asf_codeclistobject_codecentries_current['description'], ',') === false) { $info['warning'][] = '[asf][codec_list_object][codec_entries][' . $CodecEntryCounter . '][description] expected to contain comma-seperated list of parameters: "' . $thisfile_asf_codeclistobject_codecentries_current['description'] . '"'; } else { list($AudioCodecBitrate, $AudioCodecFrequency, $AudioCodecChannels) = explode(',', $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description'])); $thisfile_audio['codec'] = $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['name']); if (!isset($thisfile_audio['bitrate']) && strstr($AudioCodecBitrate, 'kbps')) { $thisfile_audio['bitrate'] = (int) (trim(str_replace('kbps', '', $AudioCodecBitrate)) * 1000); } //if (!isset($thisfile_video['bitrate']) && isset($thisfile_audio['bitrate']) && isset($thisfile_asf['file_properties_object']['max_bitrate']) && ($thisfile_asf_codeclistobject['codec_entries_count'] > 1)) { if (empty($thisfile_video['bitrate']) && !empty($thisfile_audio['bitrate']) && !empty($info['bitrate'])) { //$thisfile_video['bitrate'] = $thisfile_asf['file_properties_object']['max_bitrate'] - $thisfile_audio['bitrate']; $thisfile_video['bitrate'] = $info['bitrate'] - $thisfile_audio['bitrate']; } $AudioCodecFrequency = (int) trim(str_replace('kHz', '', $AudioCodecFrequency)); switch ($AudioCodecFrequency) { case 8: case 8000: $thisfile_audio['sample_rate'] = 8000; break; case 11: case 11025: $thisfile_audio['sample_rate'] = 11025; break; case 12: case 12000: $thisfile_audio['sample_rate'] = 12000; break; case 16: case 16000: $thisfile_audio['sample_rate'] = 16000; break; case 22: case 22050: $thisfile_audio['sample_rate'] = 22050; break; case 24: case 24000: $thisfile_audio['sample_rate'] = 24000; break; case 32: case 32000: $thisfile_audio['sample_rate'] = 32000; break; case 44: case 441000: $thisfile_audio['sample_rate'] = 44100; break; case 48: case 48000: $thisfile_audio['sample_rate'] = 48000; break; default: $info['warning'][] = 'unknown frequency: "' . $AudioCodecFrequency . '" (' . $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']) . ')'; break; } if (!isset($thisfile_audio['channels'])) { if (strstr($AudioCodecChannels, 'stereo')) { $thisfile_audio['channels'] = 2; } elseif (strstr($AudioCodecChannels, 'mono')) { $thisfile_audio['channels'] = 1; } } } } } break; case GETID3_ASF_Script_Command_Object: // Script Command Object: (optional, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Script Command object - GETID3_ASF_Script_Command_Object // Object Size QWORD 64 // size of Script Command object, including 44 bytes of Script Command Object header // Reserved GUID 128 // hardcoded: 4B1ACBE3-100B-11D0-A39B-00A0C90348F6 // Commands Count WORD 16 // number of Commands structures in the Script Commands Objects // Command Types Count WORD 16 // number of Command Types structures in the Script Commands Objects // Command Types array of: variable // // * Command Type Name Length WORD 16 // number of Unicode characters for Command Type Name // * Command Type Name WCHAR variable // array of Unicode characters - name of a type of command // Commands array of: variable // // * Presentation Time DWORD 32 // presentation time of that command, in milliseconds // * Type Index WORD 16 // type of this command, as a zero-based index into the array of Command Types of this object // * Command Name Length WORD 16 // number of Unicode characters for Command Name // * Command Name WCHAR variable // array of Unicode characters - name of this command // shortcut $thisfile_asf['script_command_object'] = array(); $thisfile_asf_scriptcommandobject =& $thisfile_asf['script_command_object']; $thisfile_asf_scriptcommandobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_scriptcommandobject['objectid'] = $NextObjectGUID; $thisfile_asf_scriptcommandobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_scriptcommandobject['objectsize'] = $NextObjectSize; $thisfile_asf_scriptcommandobject['reserved'] = substr($ASFHeaderData, $offset, 16); $offset += 16; $thisfile_asf_scriptcommandobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']); if ($thisfile_asf_scriptcommandobject['reserved'] != $this->GUIDtoBytestring('4B1ACBE3-100B-11D0-A39B-00A0C90348F6')) { $info['warning'][] = 'script_command_object.reserved GUID {' . $this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']) . '} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}'; //return false; break; } $thisfile_asf_scriptcommandobject['commands_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_scriptcommandobject['command_types_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; for ($CommandTypesCounter = 0; $CommandTypesCounter < $thisfile_asf_scriptcommandobject['command_types_count']; $CommandTypesCounter++) { $CommandTypeNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character $offset += 2; $thisfile_asf_scriptcommandobject['command_types'][$CommandTypesCounter]['name'] = substr($ASFHeaderData, $offset, $CommandTypeNameLength); $offset += $CommandTypeNameLength; } for ($CommandsCounter = 0; $CommandsCounter < $thisfile_asf_scriptcommandobject['commands_count']; $CommandsCounter++) { $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['presentation_time'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['type_index'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $CommandTypeNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character $offset += 2; $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['name'] = substr($ASFHeaderData, $offset, $CommandTypeNameLength); $offset += $CommandTypeNameLength; } break; case GETID3_ASF_Marker_Object: // Marker Object: (optional, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Marker object - GETID3_ASF_Marker_Object // Object Size QWORD 64 // size of Marker object, including 48 bytes of Marker Object header // Reserved GUID 128 // hardcoded: 4CFEDB20-75F6-11CF-9C0F-00A0C90349CB // Markers Count DWORD 32 // number of Marker structures in Marker Object // Reserved WORD 16 // hardcoded: 0x0000 // Name Length WORD 16 // number of bytes in the Name field // Name WCHAR variable // name of the Marker Object // Markers array of: variable // // * Offset QWORD 64 // byte offset into Data Object // * Presentation Time QWORD 64 // in 100-nanosecond units // * Entry Length WORD 16 // length in bytes of (Send Time + Flags + Marker Description Length + Marker Description + Padding) // * Send Time DWORD 32 // in milliseconds // * Flags DWORD 32 // hardcoded: 0x00000000 // * Marker Description Length DWORD 32 // number of bytes in Marker Description field // * Marker Description WCHAR variable // array of Unicode characters - description of marker entry // * Padding BYTESTREAM variable // optional padding bytes // shortcut $thisfile_asf['marker_object'] = array(); $thisfile_asf_markerobject =& $thisfile_asf['marker_object']; $thisfile_asf_markerobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_markerobject['objectid'] = $NextObjectGUID; $thisfile_asf_markerobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_markerobject['objectsize'] = $NextObjectSize; $thisfile_asf_markerobject['reserved'] = substr($ASFHeaderData, $offset, 16); $offset += 16; $thisfile_asf_markerobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_markerobject['reserved']); if ($thisfile_asf_markerobject['reserved'] != $this->GUIDtoBytestring('4CFEDB20-75F6-11CF-9C0F-00A0C90349CB')) { $info['warning'][] = 'marker_object.reserved GUID {' . $this->BytestringToGUID($thisfile_asf_markerobject['reserved_1']) . '} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}'; break; } $thisfile_asf_markerobject['markers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_markerobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; if ($thisfile_asf_markerobject['reserved_2'] != 0) { $info['warning'][] = 'marker_object.reserved_2 (' . getid3_lib::PrintHexBytes($thisfile_asf_markerobject['reserved_2']) . ') does not match expected value of "0"'; break; } $thisfile_asf_markerobject['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_markerobject['name'] = substr($ASFHeaderData, $offset, $thisfile_asf_markerobject['name_length']); $offset += $thisfile_asf_markerobject['name_length']; for ($MarkersCounter = 0; $MarkersCounter < $thisfile_asf_markerobject['markers_count']; $MarkersCounter++) { $thisfile_asf_markerobject['markers'][$MarkersCounter]['offset'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); $offset += 8; $thisfile_asf_markerobject['markers'][$MarkersCounter]['presentation_time'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); $offset += 8; $thisfile_asf_markerobject['markers'][$MarkersCounter]['entry_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_markerobject['markers'][$MarkersCounter]['send_time'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_markerobject['markers'][$MarkersCounter]['flags'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description'] = substr($ASFHeaderData, $offset, $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']); $offset += $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']; $PaddingLength = $thisfile_asf_markerobject['markers'][$MarkersCounter]['entry_length'] - 4 - 4 - 4 - $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']; if ($PaddingLength > 0) { $thisfile_asf_markerobject['markers'][$MarkersCounter]['padding'] = substr($ASFHeaderData, $offset, $PaddingLength); $offset += $PaddingLength; } } break; case GETID3_ASF_Bitrate_Mutual_Exclusion_Object: // Bitrate Mutual Exclusion Object: (optional) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Bitrate Mutual Exclusion object - GETID3_ASF_Bitrate_Mutual_Exclusion_Object // Object Size QWORD 64 // size of Bitrate Mutual Exclusion object, including 42 bytes of Bitrate Mutual Exclusion Object header // Exlusion Type GUID 128 // nature of mutual exclusion relationship. one of: (GETID3_ASF_Mutex_Bitrate, GETID3_ASF_Mutex_Unknown) // Stream Numbers Count WORD 16 // number of video streams // Stream Numbers WORD variable // array of mutually exclusive video stream numbers. 1 <= valid <= 127 // shortcut $thisfile_asf['bitrate_mutual_exclusion_object'] = array(); $thisfile_asf_bitratemutualexclusionobject =& $thisfile_asf['bitrate_mutual_exclusion_object']; $thisfile_asf_bitratemutualexclusionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_bitratemutualexclusionobject['objectid'] = $NextObjectGUID; $thisfile_asf_bitratemutualexclusionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_bitratemutualexclusionobject['objectsize'] = $NextObjectSize; $thisfile_asf_bitratemutualexclusionobject['reserved'] = substr($ASFHeaderData, $offset, 16); $thisfile_asf_bitratemutualexclusionobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']); $offset += 16; if ($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Bitrate && $thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Unknown) { $info['warning'][] = 'bitrate_mutual_exclusion_object.reserved GUID {' . $this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']) . '} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {' . $this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate) . '} or "GETID3_ASF_Mutex_Unknown" GUID {' . $this->BytestringToGUID(GETID3_ASF_Mutex_Unknown) . '}'; //return false; break; } $thisfile_asf_bitratemutualexclusionobject['stream_numbers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; for ($StreamNumberCounter = 0; $StreamNumberCounter < $thisfile_asf_bitratemutualexclusionobject['stream_numbers_count']; $StreamNumberCounter++) { $thisfile_asf_bitratemutualexclusionobject['stream_numbers'][$StreamNumberCounter] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; } break; case GETID3_ASF_Error_Correction_Object: // Error Correction Object: (optional, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Error Correction object - GETID3_ASF_Error_Correction_Object // Object Size QWORD 64 // size of Error Correction object, including 44 bytes of Error Correction Object header // Error Correction Type GUID 128 // type of error correction. one of: (GETID3_ASF_No_Error_Correction, GETID3_ASF_Audio_Spread) // Error Correction Data Length DWORD 32 // number of bytes in Error Correction Data field // Error Correction Data BYTESTREAM variable // structure depends on value of Error Correction Type field // shortcut $thisfile_asf['error_correction_object'] = array(); $thisfile_asf_errorcorrectionobject =& $thisfile_asf['error_correction_object']; $thisfile_asf_errorcorrectionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_errorcorrectionobject['objectid'] = $NextObjectGUID; $thisfile_asf_errorcorrectionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_errorcorrectionobject['objectsize'] = $NextObjectSize; $thisfile_asf_errorcorrectionobject['error_correction_type'] = substr($ASFHeaderData, $offset, 16); $offset += 16; $thisfile_asf_errorcorrectionobject['error_correction_guid'] = $this->BytestringToGUID($thisfile_asf_errorcorrectionobject['error_correction_type']); $thisfile_asf_errorcorrectionobject['error_correction_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; switch ($thisfile_asf_errorcorrectionobject['error_correction_type']) { case GETID3_ASF_No_Error_Correction: // should be no data, but just in case there is, skip to the end of the field $offset += $thisfile_asf_errorcorrectionobject['error_correction_data_length']; break; case GETID3_ASF_Audio_Spread: // Field Name Field Type Size (bits) // Span BYTE 8 // number of packets over which audio will be spread. // Virtual Packet Length WORD 16 // size of largest audio payload found in audio stream // Virtual Chunk Length WORD 16 // size of largest audio payload found in audio stream // Silence Data Length WORD 16 // number of bytes in Silence Data field // Silence Data BYTESTREAM variable // hardcoded: 0x00 * (Silence Data Length) bytes $thisfile_asf_errorcorrectionobject['span'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 1)); $offset += 1; $thisfile_asf_errorcorrectionobject['virtual_packet_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_errorcorrectionobject['virtual_chunk_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_errorcorrectionobject['silence_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_errorcorrectionobject['silence_data'] = substr($ASFHeaderData, $offset, $thisfile_asf_errorcorrectionobject['silence_data_length']); $offset += $thisfile_asf_errorcorrectionobject['silence_data_length']; break; default: $info['warning'][] = 'error_correction_object.error_correction_type GUID {' . $this->BytestringToGUID($thisfile_asf_errorcorrectionobject['reserved']) . '} does not match expected "GETID3_ASF_No_Error_Correction" GUID {' . $this->BytestringToGUID(GETID3_ASF_No_Error_Correction) . '} or "GETID3_ASF_Audio_Spread" GUID {' . $this->BytestringToGUID(GETID3_ASF_Audio_Spread) . '}'; //return false; break; } break; case GETID3_ASF_Content_Description_Object: // Content Description Object: (optional, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Content Description object - GETID3_ASF_Content_Description_Object // Object Size QWORD 64 // size of Content Description object, including 34 bytes of Content Description Object header // Title Length WORD 16 // number of bytes in Title field // Author Length WORD 16 // number of bytes in Author field // Copyright Length WORD 16 // number of bytes in Copyright field // Description Length WORD 16 // number of bytes in Description field // Rating Length WORD 16 // number of bytes in Rating field // Title WCHAR 16 // array of Unicode characters - Title // Author WCHAR 16 // array of Unicode characters - Author // Copyright WCHAR 16 // array of Unicode characters - Copyright // Description WCHAR 16 // array of Unicode characters - Description // Rating WCHAR 16 // array of Unicode characters - Rating // shortcut $thisfile_asf['content_description_object'] = array(); $thisfile_asf_contentdescriptionobject =& $thisfile_asf['content_description_object']; $thisfile_asf_contentdescriptionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_contentdescriptionobject['objectid'] = $NextObjectGUID; $thisfile_asf_contentdescriptionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_contentdescriptionobject['objectsize'] = $NextObjectSize; $thisfile_asf_contentdescriptionobject['title_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_contentdescriptionobject['author_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_contentdescriptionobject['copyright_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_contentdescriptionobject['description_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_contentdescriptionobject['rating_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_contentdescriptionobject['title'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['title_length']); $offset += $thisfile_asf_contentdescriptionobject['title_length']; $thisfile_asf_contentdescriptionobject['author'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['author_length']); $offset += $thisfile_asf_contentdescriptionobject['author_length']; $thisfile_asf_contentdescriptionobject['copyright'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['copyright_length']); $offset += $thisfile_asf_contentdescriptionobject['copyright_length']; $thisfile_asf_contentdescriptionobject['description'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['description_length']); $offset += $thisfile_asf_contentdescriptionobject['description_length']; $thisfile_asf_contentdescriptionobject['rating'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['rating_length']); $offset += $thisfile_asf_contentdescriptionobject['rating_length']; $ASFcommentKeysToCopy = array('title' => 'title', 'author' => 'artist', 'copyright' => 'copyright', 'description' => 'comment', 'rating' => 'rating'); foreach ($ASFcommentKeysToCopy as $keytocopyfrom => $keytocopyto) { if (!empty($thisfile_asf_contentdescriptionobject[$keytocopyfrom])) { $thisfile_asf_comments[$keytocopyto][] = $this->TrimTerm($thisfile_asf_contentdescriptionobject[$keytocopyfrom]); } } break; case GETID3_ASF_Extended_Content_Description_Object: // Extended Content Description Object: (optional, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Extended Content Description object - GETID3_ASF_Extended_Content_Description_Object // Object Size QWORD 64 // size of ExtendedContent Description object, including 26 bytes of Extended Content Description Object header // Content Descriptors Count WORD 16 // number of entries in Content Descriptors list // Content Descriptors array of: variable // // * Descriptor Name Length WORD 16 // size in bytes of Descriptor Name field // * Descriptor Name WCHAR variable // array of Unicode characters - Descriptor Name // * Descriptor Value Data Type WORD 16 // Lookup array: // 0x0000 = Unicode String (variable length) // 0x0001 = BYTE array (variable length) // 0x0002 = BOOL (DWORD, 32 bits) // 0x0003 = DWORD (DWORD, 32 bits) // 0x0004 = QWORD (QWORD, 64 bits) // 0x0005 = WORD (WORD, 16 bits) // * Descriptor Value Length WORD 16 // number of bytes stored in Descriptor Value field // * Descriptor Value variable variable // value for Content Descriptor // shortcut $thisfile_asf['extended_content_description_object'] = array(); $thisfile_asf_extendedcontentdescriptionobject =& $thisfile_asf['extended_content_description_object']; $thisfile_asf_extendedcontentdescriptionobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_extendedcontentdescriptionobject['objectid'] = $NextObjectGUID; $thisfile_asf_extendedcontentdescriptionobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_extendedcontentdescriptionobject['objectsize'] = $NextObjectSize; $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; for ($ExtendedContentDescriptorsCounter = 0; $ExtendedContentDescriptorsCounter < $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count']; $ExtendedContentDescriptorsCounter++) { // shortcut $thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter] = array(); $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current =& $thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter]; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['base_offset'] = $offset + 30; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name'] = substr($ASFHeaderData, $offset, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length']); $offset += $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length']; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = substr($ASFHeaderData, $offset, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length']); $offset += $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length']; switch ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']) { case 0x0: // Unicode string break; case 0x1: // BYTE array // do nothing break; case 0x2: // BOOL $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = (bool) getid3_lib::LittleEndian2Int($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); break; case 0x3: // DWORD // DWORD case 0x4: // QWORD // QWORD case 0x5: // WORD $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = getid3_lib::LittleEndian2Int($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); break; default: $info['warning'][] = 'extended_content_description.content_descriptors.' . $ExtendedContentDescriptorsCounter . '.value_type is invalid (' . $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'] . ')'; //return false; break; } switch ($this->TrimConvert(strtolower($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']))) { case 'wm/albumartist': case 'artist': // Note: not 'artist', that comes from 'author' tag $thisfile_asf_comments['albumartist'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); break; case 'wm/albumtitle': case 'album': $thisfile_asf_comments['album'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); break; case 'wm/genre': case 'genre': $thisfile_asf_comments['genre'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); break; case 'wm/partofset': $thisfile_asf_comments['partofset'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); break; case 'wm/tracknumber': case 'tracknumber': // be careful casting to int: casting unicode strings to int gives unexpected results (stops parsing at first non-numeric character) $thisfile_asf_comments['track'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); foreach ($thisfile_asf_comments['track'] as $key => $value) { if (preg_match('/^[0-9\\x00]+$/', $value)) { $thisfile_asf_comments['track'][$key] = intval(str_replace("", '', $value)); } } break; case 'wm/track': if (empty($thisfile_asf_comments['track'])) { $thisfile_asf_comments['track'] = array(1 + $this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); } break; case 'wm/year': case 'year': case 'date': $thisfile_asf_comments['year'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); break; case 'wm/lyrics': case 'lyrics': $thisfile_asf_comments['lyrics'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); break; case 'isvbr': if ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']) { $thisfile_audio['bitrate_mode'] = 'vbr'; $thisfile_video['bitrate_mode'] = 'vbr'; } break; case 'id3': // id3v2 module might not be loaded if (class_exists('getid3_id3v2')) { $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); $tempfilehandle = fopen($tempfile, 'wb'); $tempThisfileInfo = array('encoding' => $info['encoding']); fwrite($tempfilehandle, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); fclose($tempfilehandle); $getid3_temp = new getID3(); $getid3_temp->openfile($tempfile); $getid3_id3v2 = new getid3_id3v2($getid3_temp); $getid3_id3v2->Analyze(); $info['id3v2'] = $getid3_temp->info['id3v2']; unset($getid3_temp, $getid3_id3v2); unlink($tempfile); } break; case 'wm/encodingtime': $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['encoding_time_unix'] = $this->FILETIMEtoUNIXtime($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); $thisfile_asf_comments['encoding_time_unix'] = array($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['encoding_time_unix']); break; case 'wm/picture': $WMpicture = $this->ASF_WMpicture($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); foreach ($WMpicture as $key => $value) { $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current[$key] = $value; } unset($WMpicture); /* $wm_picture_offset = 0; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 1)); $wm_picture_offset += 1; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type'] = $this->WMpictureTypeLookup($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id']); $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_size'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 4)); $wm_picture_offset += 4; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = ''; do { $next_byte_pair = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 2); $wm_picture_offset += 2; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] .= $next_byte_pair; } while ($next_byte_pair !== "\x00\x00"); $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_description'] = ''; do { $next_byte_pair = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 2); $wm_picture_offset += 2; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_description'] .= $next_byte_pair; } while ($next_byte_pair !== "\x00\x00"); $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['dataoffset'] = $wm_picture_offset; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'] = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset); unset($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); $imageinfo = array(); $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = ''; $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], $imageinfo); unset($imageinfo); if (!empty($imagechunkcheck)) { $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); } if (!isset($thisfile_asf_comments['picture'])) { $thisfile_asf_comments['picture'] = array(); } $thisfile_asf_comments['picture'][] = array('data'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], 'image_mime'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime']); */ break; default: switch ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']) { case 0: // Unicode string if (substr($this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']), 0, 3) == 'WM/') { $thisfile_asf_comments[str_replace('wm/', '', strtolower($this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name'])))] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); } break; case 1: break; } break; } } break; case GETID3_ASF_Stream_Bitrate_Properties_Object: // Stream Bitrate Properties Object: (optional, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Stream Bitrate Properties object - GETID3_ASF_Stream_Bitrate_Properties_Object // Object Size QWORD 64 // size of Extended Content Description object, including 26 bytes of Stream Bitrate Properties Object header // Bitrate Records Count WORD 16 // number of records in Bitrate Records // Bitrate Records array of: variable // // * Flags WORD 16 // // * * Stream Number bits 7 (0x007F) // number of this stream // * * Reserved bits 9 (0xFF80) // hardcoded: 0 // * Average Bitrate DWORD 32 // in bits per second // shortcut $thisfile_asf['stream_bitrate_properties_object'] = array(); $thisfile_asf_streambitratepropertiesobject =& $thisfile_asf['stream_bitrate_properties_object']; $thisfile_asf_streambitratepropertiesobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_streambitratepropertiesobject['objectid'] = $NextObjectGUID; $thisfile_asf_streambitratepropertiesobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_streambitratepropertiesobject['objectsize'] = $NextObjectSize; $thisfile_asf_streambitratepropertiesobject['bitrate_records_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < $thisfile_asf_streambitratepropertiesobject['bitrate_records_count']; $BitrateRecordsCounter++) { $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags']['stream_number'] = $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags_raw'] & 0x7f; $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['bitrate'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); $offset += 4; } break; case GETID3_ASF_Padding_Object: // Padding Object: (optional) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Padding object - GETID3_ASF_Padding_Object // Object Size QWORD 64 // size of Padding object, including 24 bytes of ASF Padding Object header // Padding Data BYTESTREAM variable // ignore // shortcut $thisfile_asf['padding_object'] = array(); $thisfile_asf_paddingobject =& $thisfile_asf['padding_object']; $thisfile_asf_paddingobject['offset'] = $NextObjectOffset + $offset; $thisfile_asf_paddingobject['objectid'] = $NextObjectGUID; $thisfile_asf_paddingobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_paddingobject['objectsize'] = $NextObjectSize; $thisfile_asf_paddingobject['padding_length'] = $thisfile_asf_paddingobject['objectsize'] - 16 - 8; $thisfile_asf_paddingobject['padding'] = substr($ASFHeaderData, $offset, $thisfile_asf_paddingobject['padding_length']); $offset += $NextObjectSize - 16 - 8; break; case GETID3_ASF_Extended_Content_Encryption_Object: case GETID3_ASF_Content_Encryption_Object: // WMA DRM - just ignore $offset += $NextObjectSize - 16 - 8; break; default: // Implementations shall ignore any standard or non-standard object that they do not know how to handle. if ($this->GUIDname($NextObjectGUIDtext)) { $info['warning'][] = 'unhandled GUID "' . $this->GUIDname($NextObjectGUIDtext) . '" {' . $NextObjectGUIDtext . '} in ASF header at offset ' . ($offset - 16 - 8); } else { $info['warning'][] = 'unknown GUID {' . $NextObjectGUIDtext . '} in ASF header at offset ' . ($offset - 16 - 8); } $offset += $NextObjectSize - 16 - 8; break; } } if (isset($thisfile_asf_streambitrateproperties['bitrate_records_count'])) { $ASFbitrateAudio = 0; $ASFbitrateVideo = 0; for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < $thisfile_asf_streambitrateproperties['bitrate_records_count']; $BitrateRecordsCounter++) { if (isset($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter])) { switch ($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter]['type_raw']) { case 1: $ASFbitrateVideo += $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate']; break; case 2: $ASFbitrateAudio += $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate']; break; default: // do nothing break; } } } if ($ASFbitrateAudio > 0) { $thisfile_audio['bitrate'] = $ASFbitrateAudio; } if ($ASFbitrateVideo > 0) { $thisfile_video['bitrate'] = $ASFbitrateVideo; } } if (isset($thisfile_asf['stream_properties_object']) && is_array($thisfile_asf['stream_properties_object'])) { $thisfile_audio['bitrate'] = 0; $thisfile_video['bitrate'] = 0; foreach ($thisfile_asf['stream_properties_object'] as $streamnumber => $streamdata) { switch ($streamdata['stream_type']) { case GETID3_ASF_Audio_Media: // Field Name Field Type Size (bits) // Codec ID / Format Tag WORD 16 // unique ID of audio codec - defined as wFormatTag field of WAVEFORMATEX structure // Number of Channels WORD 16 // number of channels of audio - defined as nChannels field of WAVEFORMATEX structure // Samples Per Second DWORD 32 // in Hertz - defined as nSamplesPerSec field of WAVEFORMATEX structure // Average number of Bytes/sec DWORD 32 // bytes/sec of audio stream - defined as nAvgBytesPerSec field of WAVEFORMATEX structure // Block Alignment WORD 16 // block size in bytes of audio codec - defined as nBlockAlign field of WAVEFORMATEX structure // Bits per sample WORD 16 // bits per sample of mono data. set to zero for variable bitrate codecs. defined as wBitsPerSample field of WAVEFORMATEX structure // Codec Specific Data Size WORD 16 // size in bytes of Codec Specific Data buffer - defined as cbSize field of WAVEFORMATEX structure // Codec Specific Data BYTESTREAM variable // array of codec-specific data bytes // shortcut $thisfile_asf['audio_media'][$streamnumber] = array(); $thisfile_asf_audiomedia_currentstream =& $thisfile_asf['audio_media'][$streamnumber]; $audiomediaoffset = 0; $thisfile_asf_audiomedia_currentstream = getid3_riff::parseWAVEFORMATex(substr($streamdata['type_specific_data'], $audiomediaoffset, 16)); $audiomediaoffset += 16; $thisfile_audio['lossless'] = false; switch ($thisfile_asf_audiomedia_currentstream['raw']['wFormatTag']) { case 0x1: // PCM // PCM case 0x163: // WMA9 Lossless $thisfile_audio['lossless'] = true; break; } if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) { if (isset($dataarray['flags']['stream_number']) && $dataarray['flags']['stream_number'] == $streamnumber) { $thisfile_asf_audiomedia_currentstream['bitrate'] = $dataarray['bitrate']; $thisfile_audio['bitrate'] += $dataarray['bitrate']; break; } } } else { if (!empty($thisfile_asf_audiomedia_currentstream['bytes_sec'])) { $thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bytes_sec'] * 8; } elseif (!empty($thisfile_asf_audiomedia_currentstream['bitrate'])) { $thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bitrate']; } } $thisfile_audio['streams'][$streamnumber] = $thisfile_asf_audiomedia_currentstream; $thisfile_audio['streams'][$streamnumber]['wformattag'] = $thisfile_asf_audiomedia_currentstream['raw']['wFormatTag']; $thisfile_audio['streams'][$streamnumber]['lossless'] = $thisfile_audio['lossless']; $thisfile_audio['streams'][$streamnumber]['bitrate'] = $thisfile_audio['bitrate']; $thisfile_audio['streams'][$streamnumber]['dataformat'] = 'wma'; unset($thisfile_audio['streams'][$streamnumber]['raw']); $thisfile_asf_audiomedia_currentstream['codec_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $audiomediaoffset, 2)); $audiomediaoffset += 2; $thisfile_asf_audiomedia_currentstream['codec_data'] = substr($streamdata['type_specific_data'], $audiomediaoffset, $thisfile_asf_audiomedia_currentstream['codec_data_size']); $audiomediaoffset += $thisfile_asf_audiomedia_currentstream['codec_data_size']; break; case GETID3_ASF_Video_Media: // Field Name Field Type Size (bits) // Encoded Image Width DWORD 32 // width of image in pixels // Encoded Image Height DWORD 32 // height of image in pixels // Reserved Flags BYTE 8 // hardcoded: 0x02 // Format Data Size WORD 16 // size of Format Data field in bytes // Format Data array of: variable // // * Format Data Size DWORD 32 // number of bytes in Format Data field, in bytes - defined as biSize field of BITMAPINFOHEADER structure // * Image Width LONG 32 // width of encoded image in pixels - defined as biWidth field of BITMAPINFOHEADER structure // * Image Height LONG 32 // height of encoded image in pixels - defined as biHeight field of BITMAPINFOHEADER structure // * Reserved WORD 16 // hardcoded: 0x0001 - defined as biPlanes field of BITMAPINFOHEADER structure // * Bits Per Pixel Count WORD 16 // bits per pixel - defined as biBitCount field of BITMAPINFOHEADER structure // * Compression ID FOURCC 32 // fourcc of video codec - defined as biCompression field of BITMAPINFOHEADER structure // * Image Size DWORD 32 // image size in bytes - defined as biSizeImage field of BITMAPINFOHEADER structure // * Horizontal Pixels / Meter DWORD 32 // horizontal resolution of target device in pixels per meter - defined as biXPelsPerMeter field of BITMAPINFOHEADER structure // * Vertical Pixels / Meter DWORD 32 // vertical resolution of target device in pixels per meter - defined as biYPelsPerMeter field of BITMAPINFOHEADER structure // * Colors Used Count DWORD 32 // number of color indexes in the color table that are actually used - defined as biClrUsed field of BITMAPINFOHEADER structure // * Important Colors Count DWORD 32 // number of color index required for displaying bitmap. if zero, all colors are required. defined as biClrImportant field of BITMAPINFOHEADER structure // * Codec Specific Data BYTESTREAM variable // array of codec-specific data bytes // shortcut $thisfile_asf['video_media'][$streamnumber] = array(); $thisfile_asf_videomedia_currentstream =& $thisfile_asf['video_media'][$streamnumber]; $videomediaoffset = 0; $thisfile_asf_videomedia_currentstream['image_width'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['image_height'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['flags'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 1)); $videomediaoffset += 1; $thisfile_asf_videomedia_currentstream['format_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2)); $videomediaoffset += 2; $thisfile_asf_videomedia_currentstream['format_data']['format_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['format_data']['image_width'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['format_data']['image_height'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['format_data']['reserved'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2)); $videomediaoffset += 2; $thisfile_asf_videomedia_currentstream['format_data']['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2)); $videomediaoffset += 2; $thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc'] = substr($streamdata['type_specific_data'], $videomediaoffset, 4); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['format_data']['image_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['format_data']['horizontal_pels'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['format_data']['vertical_pels'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['format_data']['colors_used'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['format_data']['colors_important'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); $videomediaoffset += 4; $thisfile_asf_videomedia_currentstream['format_data']['codec_data'] = substr($streamdata['type_specific_data'], $videomediaoffset); if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) { if (isset($dataarray['flags']['stream_number']) && $dataarray['flags']['stream_number'] == $streamnumber) { $thisfile_asf_videomedia_currentstream['bitrate'] = $dataarray['bitrate']; $thisfile_video['streams'][$streamnumber]['bitrate'] = $dataarray['bitrate']; $thisfile_video['bitrate'] += $dataarray['bitrate']; break; } } } $thisfile_asf_videomedia_currentstream['format_data']['codec'] = getid3_riff::fourccLookup($thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']); $thisfile_video['streams'][$streamnumber]['fourcc'] = $thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']; $thisfile_video['streams'][$streamnumber]['codec'] = $thisfile_asf_videomedia_currentstream['format_data']['codec']; $thisfile_video['streams'][$streamnumber]['resolution_x'] = $thisfile_asf_videomedia_currentstream['image_width']; $thisfile_video['streams'][$streamnumber]['resolution_y'] = $thisfile_asf_videomedia_currentstream['image_height']; $thisfile_video['streams'][$streamnumber]['bits_per_sample'] = $thisfile_asf_videomedia_currentstream['format_data']['bits_per_pixel']; break; default: break; } } } while (ftell($this->getid3->fp) < $info['avdataend']) { $NextObjectDataHeader = fread($this->getid3->fp, 24); $offset = 0; $NextObjectGUID = substr($NextObjectDataHeader, 0, 16); $offset += 16; $NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID); $NextObjectSize = getid3_lib::LittleEndian2Int(substr($NextObjectDataHeader, $offset, 8)); $offset += 8; switch ($NextObjectGUID) { case GETID3_ASF_Data_Object: // Data Object: (mandatory, one only) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Data object - GETID3_ASF_Data_Object // Object Size QWORD 64 // size of Data object, including 50 bytes of Data Object header. may be 0 if FilePropertiesObject.BroadcastFlag == 1 // File ID GUID 128 // unique identifier. identical to File ID field in Header Object // Total Data Packets QWORD 64 // number of Data Packet entries in Data Object. invalid if FilePropertiesObject.BroadcastFlag == 1 // Reserved WORD 16 // hardcoded: 0x0101 // shortcut $thisfile_asf['data_object'] = array(); $thisfile_asf_dataobject =& $thisfile_asf['data_object']; $DataObjectData = $NextObjectDataHeader . fread($this->getid3->fp, 50 - 24); $offset = 24; $thisfile_asf_dataobject['objectid'] = $NextObjectGUID; $thisfile_asf_dataobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_dataobject['objectsize'] = $NextObjectSize; $thisfile_asf_dataobject['fileid'] = substr($DataObjectData, $offset, 16); $offset += 16; $thisfile_asf_dataobject['fileid_guid'] = $this->BytestringToGUID($thisfile_asf_dataobject['fileid']); $thisfile_asf_dataobject['total_data_packets'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 8)); $offset += 8; $thisfile_asf_dataobject['reserved'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 2)); $offset += 2; if ($thisfile_asf_dataobject['reserved'] != 0x101) { $info['warning'][] = 'data_object.reserved (' . getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']) . ') does not match expected value of "0x0101"'; //return false; break; } // Data Packets array of: variable // // * Error Correction Flags BYTE 8 // // * * Error Correction Data Length bits 4 // if Error Correction Length Type == 00, size of Error Correction Data in bytes, else hardcoded: 0000 // * * Opaque Data Present bits 1 // // * * Error Correction Length Type bits 2 // number of bits for size of the error correction data. hardcoded: 00 // * * Error Correction Present bits 1 // If set, use Opaque Data Packet structure, else use Payload structure // * Error Correction Data $info['avdataoffset'] = ftell($this->getid3->fp); fseek($this->getid3->fp, $thisfile_asf_dataobject['objectsize'] - 50, SEEK_CUR); // skip actual audio/video data $info['avdataend'] = ftell($this->getid3->fp); break; case GETID3_ASF_Simple_Index_Object: // Simple Index Object: (optional, recommended, one per video stream) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for Simple Index object - GETID3_ASF_Data_Object // Object Size QWORD 64 // size of Simple Index object, including 56 bytes of Simple Index Object header // File ID GUID 128 // unique identifier. may be zero or identical to File ID field in Data Object and Header Object // Index Entry Time Interval QWORD 64 // interval between index entries in 100-nanosecond units // Maximum Packet Count DWORD 32 // maximum packet count for all index entries // Index Entries Count DWORD 32 // number of Index Entries structures // Index Entries array of: variable // // * Packet Number DWORD 32 // number of the Data Packet associated with this index entry // * Packet Count WORD 16 // number of Data Packets to sent at this index entry // shortcut $thisfile_asf['simple_index_object'] = array(); $thisfile_asf_simpleindexobject =& $thisfile_asf['simple_index_object']; $SimpleIndexObjectData = $NextObjectDataHeader . fread($this->getid3->fp, 56 - 24); $offset = 24; $thisfile_asf_simpleindexobject['objectid'] = $NextObjectGUID; $thisfile_asf_simpleindexobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_simpleindexobject['objectsize'] = $NextObjectSize; $thisfile_asf_simpleindexobject['fileid'] = substr($SimpleIndexObjectData, $offset, 16); $offset += 16; $thisfile_asf_simpleindexobject['fileid_guid'] = $this->BytestringToGUID($thisfile_asf_simpleindexobject['fileid']); $thisfile_asf_simpleindexobject['index_entry_time_interval'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 8)); $offset += 8; $thisfile_asf_simpleindexobject['maximum_packet_count'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4)); $offset += 4; $thisfile_asf_simpleindexobject['index_entries_count'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4)); $offset += 4; $IndexEntriesData = $SimpleIndexObjectData . fread($this->getid3->fp, 6 * $thisfile_asf_simpleindexobject['index_entries_count']); for ($IndexEntriesCounter = 0; $IndexEntriesCounter < $thisfile_asf_simpleindexobject['index_entries_count']; $IndexEntriesCounter++) { $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_number'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4)); $offset += 4; $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_count'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4)); $offset += 2; } break; case GETID3_ASF_Index_Object: // 6.2 ASF top-level Index Object (optional but recommended when appropriate, 0 or 1) // Field Name Field Type Size (bits) // Object ID GUID 128 // GUID for the Index Object - GETID3_ASF_Index_Object // Object Size QWORD 64 // Specifies the size, in bytes, of the Index Object, including at least 34 bytes of Index Object header // Index Entry Time Interval DWORD 32 // Specifies the time interval between each index entry in ms. // Index Specifiers Count WORD 16 // Specifies the number of Index Specifiers structures in this Index Object. // Index Blocks Count DWORD 32 // Specifies the number of Index Blocks structures in this Index Object. // Index Entry Time Interval DWORD 32 // Specifies the time interval between index entries in milliseconds. This value cannot be 0. // Index Specifiers Count WORD 16 // Specifies the number of entries in the Index Specifiers list. Valid values are 1 and greater. // Index Specifiers array of: varies // // * Stream Number WORD 16 // Specifies the stream number that the Index Specifiers refer to. Valid values are between 1 and 127. // * Index Type WORD 16 // Specifies Index Type values as follows: // 1 = Nearest Past Data Packet - indexes point to the data packet whose presentation time is closest to the index entry time. // 2 = Nearest Past Media Object - indexes point to the closest data packet containing an entire object or first fragment of an object. // 3 = Nearest Past Cleanpoint. - indexes point to the closest data packet containing an entire object (or first fragment of an object) that has the Cleanpoint Flag set. // Nearest Past Cleanpoint is the most common type of index. // Index Entry Count DWORD 32 // Specifies the number of Index Entries in the block. // * Block Positions QWORD varies // Specifies a list of byte offsets of the beginnings of the blocks relative to the beginning of the first Data Packet (i.e., the beginning of the Data Object + 50 bytes). The number of entries in this list is specified by the value of the Index Specifiers Count field. The order of those byte offsets is tied to the order in which Index Specifiers are listed. // * Index Entries array of: varies // // * * Offsets DWORD varies // An offset value of 0xffffffff indicates an invalid offset value // shortcut $thisfile_asf['asf_index_object'] = array(); $thisfile_asf_asfindexobject =& $thisfile_asf['asf_index_object']; $ASFIndexObjectData = $NextObjectDataHeader . fread($this->getid3->fp, 34 - 24); $offset = 24; $thisfile_asf_asfindexobject['objectid'] = $NextObjectGUID; $thisfile_asf_asfindexobject['objectid_guid'] = $NextObjectGUIDtext; $thisfile_asf_asfindexobject['objectsize'] = $NextObjectSize; $thisfile_asf_asfindexobject['entry_time_interval'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); $offset += 4; $thisfile_asf_asfindexobject['index_specifiers_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); $offset += 2; $thisfile_asf_asfindexobject['index_blocks_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); $offset += 4; $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count']); for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $IndexSpecifierStreamNumber = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); $offset += 2; $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['stream_number'] = $IndexSpecifierStreamNumber; $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); $offset += 2; $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type_text'] = $this->ASFIndexObjectIndexTypeLookup($thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type']); } $ASFIndexObjectData .= fread($this->getid3->fp, 4); $thisfile_asf_asfindexobject['index_entry_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); $offset += 4; $ASFIndexObjectData .= fread($this->getid3->fp, 8 * $thisfile_asf_asfindexobject['index_specifiers_count']); for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $thisfile_asf_asfindexobject['block_positions'][$IndexSpecifiersCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 8)); $offset += 8; } $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']); for ($IndexEntryCounter = 0; $IndexEntryCounter < $thisfile_asf_asfindexobject['index_entry_count']; $IndexEntryCounter++) { for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $thisfile_asf_asfindexobject['offsets'][$IndexSpecifiersCounter][$IndexEntryCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); $offset += 4; } } break; default: // Implementations shall ignore any standard or non-standard object that they do not know how to handle. if ($this->GUIDname($NextObjectGUIDtext)) { $info['warning'][] = 'unhandled GUID "' . $this->GUIDname($NextObjectGUIDtext) . '" {' . $NextObjectGUIDtext . '} in ASF body at offset ' . ($offset - 16 - 8); } else { $info['warning'][] = 'unknown GUID {' . $NextObjectGUIDtext . '} in ASF body at offset ' . (ftell($this->getid3->fp) - 16 - 8); } fseek($this->getid3->fp, $NextObjectSize - 16 - 8, SEEK_CUR); break; } } if (isset($thisfile_asf_codeclistobject['codec_entries']) && is_array($thisfile_asf_codeclistobject['codec_entries'])) { foreach ($thisfile_asf_codeclistobject['codec_entries'] as $streamnumber => $streamdata) { switch ($streamdata['information']) { case 'WMV1': case 'WMV2': case 'WMV3': case 'MSS1': case 'MSS2': case 'WMVA': case 'WVC1': case 'WMVP': case 'WVP2': $thisfile_video['dataformat'] = 'wmv'; $info['mime_type'] = 'video/x-ms-wmv'; break; case 'MP42': case 'MP43': case 'MP4S': case 'mp4s': $thisfile_video['dataformat'] = 'asf'; $info['mime_type'] = 'video/x-ms-asf'; break; default: switch ($streamdata['type_raw']) { case 1: if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) { $thisfile_video['dataformat'] = 'wmv'; if ($info['mime_type'] == 'video/x-ms-asf') { $info['mime_type'] = 'video/x-ms-wmv'; } } break; case 2: if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) { $thisfile_audio['dataformat'] = 'wma'; if ($info['mime_type'] == 'video/x-ms-asf') { $info['mime_type'] = 'audio/x-ms-wma'; } } break; } break; } } } switch (isset($thisfile_audio['codec']) ? $thisfile_audio['codec'] : '') { case 'MPEG Layer-3': $thisfile_audio['dataformat'] = 'mp3'; break; default: break; } if (isset($thisfile_asf_codeclistobject['codec_entries'])) { foreach ($thisfile_asf_codeclistobject['codec_entries'] as $streamnumber => $streamdata) { switch ($streamdata['type_raw']) { case 1: // video $thisfile_video['encoder'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][$streamnumber]['name']); break; case 2: // audio $thisfile_audio['encoder'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][$streamnumber]['name']); // AH 2003-10-01 $thisfile_audio['encoder_options'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][0]['description']); $thisfile_audio['codec'] = $thisfile_audio['encoder']; break; default: $info['warning'][] = 'Unknown streamtype: [codec_list_object][codec_entries][' . $streamnumber . '][type_raw] == ' . $streamdata['type_raw']; break; } } } if (isset($info['audio'])) { $thisfile_audio['lossless'] = isset($thisfile_audio['lossless']) ? $thisfile_audio['lossless'] : false; $thisfile_audio['dataformat'] = !empty($thisfile_audio['dataformat']) ? $thisfile_audio['dataformat'] : 'asf'; } if (!empty($thisfile_video['dataformat'])) { $thisfile_video['lossless'] = isset($thisfile_audio['lossless']) ? $thisfile_audio['lossless'] : false; $thisfile_video['pixel_aspect_ratio'] = isset($thisfile_audio['pixel_aspect_ratio']) ? $thisfile_audio['pixel_aspect_ratio'] : (double) 1; $thisfile_video['dataformat'] = !empty($thisfile_video['dataformat']) ? $thisfile_video['dataformat'] : 'asf'; } if (!empty($thisfile_video['streams'])) { $thisfile_video['streams']['resolution_x'] = 0; $thisfile_video['streams']['resolution_y'] = 0; foreach ($thisfile_video['streams'] as $key => $valuearray) { if ($valuearray['resolution_x'] > $thisfile_video['streams']['resolution_x'] || $valuearray['resolution_y'] > $thisfile_video['streams']['resolution_y']) { $thisfile_video['resolution_x'] = $valuearray['resolution_x']; $thisfile_video['resolution_y'] = $valuearray['resolution_y']; } } } $info['bitrate'] = (isset($thisfile_audio['bitrate']) ? $thisfile_audio['bitrate'] : 0) + (isset($thisfile_video['bitrate']) ? $thisfile_video['bitrate'] : 0); if ((!isset($info['playtime_seconds']) || $info['playtime_seconds'] <= 0) && $info['bitrate'] > 0) { $info['playtime_seconds'] = ($info['filesize'] - $info['avdataoffset']) / ($info['bitrate'] / 8); } return true; }
public function Analyze() { $info =& $this->getid3->info; // http://www.volweb.cz/str/tags.htm if (!getid3_lib::intValueSupported($info['filesize'])) { $info['warning'][] = 'Unable to check for Lyrics3 because file is larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB'; return false; } $this->fseek(0 - 128 - 9 - 6, SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size] $lyrics3_id3v1 = $this->fread(128 + 9 + 6); $lyrics3lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size $lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200 $id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1 if ($lyrics3end == 'LYRICSEND') { // Lyrics3v1, ID3v1, no APE $lyrics3size = 5100; $lyrics3offset = $info['filesize'] - 128 - $lyrics3size; $lyrics3version = 1; } elseif ($lyrics3end == 'LYRICS200') { // Lyrics3v2, ID3v1, no APE // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); $lyrics3offset = $info['filesize'] - 128 - $lyrics3size; $lyrics3version = 2; } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) { // Lyrics3v1, no ID3v1, no APE $lyrics3size = 5100; $lyrics3offset = $info['filesize'] - $lyrics3size; $lyrics3version = 1; $lyrics3offset = $info['filesize'] - $lyrics3size; } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) { // Lyrics3v2, no ID3v1, no APE $lyrics3size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' $lyrics3offset = $info['filesize'] - $lyrics3size; $lyrics3version = 2; } else { if (isset($info['ape']['tag_offset_start']) && $info['ape']['tag_offset_start'] > 15) { $this->fseek($info['ape']['tag_offset_start'] - 15); $lyrics3lsz = $this->fread(6); $lyrics3end = $this->fread(9); if ($lyrics3end == 'LYRICSEND') { // Lyrics3v1, APE, maybe ID3v1 $lyrics3size = 5100; $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size; $info['avdataend'] = $lyrics3offset; $lyrics3version = 1; $info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; } elseif ($lyrics3end == 'LYRICS200') { // Lyrics3v2, APE, maybe ID3v1 $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size; $lyrics3version = 2; $info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; } } } if (isset($lyrics3offset)) { $info['avdataend'] = $lyrics3offset; $this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size); if (!isset($info['ape'])) { $GETID3_ERRORARRAY =& $info['warning']; getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.tag.apetag.php', __FILE__, true); $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_apetag = new getid3_apetag($getid3_temp); $getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start']; $getid3_apetag->Analyze(); if (!empty($getid3_temp->info['ape'])) { $info['ape'] = $getid3_temp->info['ape']; } if (!empty($getid3_temp->info['replay_gain'])) { $info['replay_gain'] = $getid3_temp->info['replay_gain']; } unset($getid3_temp, $getid3_apetag); } } return true; }
function FLACparseMETAdata() { $info =& $this->getid3->info; do { $METAdataBlockOffset = ftell($this->getid3->fp); $METAdataBlockHeader = fread($this->getid3->fp, 4); $METAdataLastBlockFlag = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x80); $METAdataBlockType = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x7f; $METAdataBlockLength = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 1, 3)); $METAdataBlockTypeText = getid3_flac::FLACmetaBlockTypeLookup($METAdataBlockType); if ($METAdataBlockLength < 0) { $info['error'][] = 'corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE (' . $METAdataBlockType . ') at offset ' . $METAdataBlockOffset; break; } $info['flac'][$METAdataBlockTypeText]['raw'] = array(); $ThisFileInfo_flac_METAdataBlockTypeText_raw =& $info['flac'][$METAdataBlockTypeText]['raw']; $ThisFileInfo_flac_METAdataBlockTypeText_raw['offset'] = $METAdataBlockOffset; $ThisFileInfo_flac_METAdataBlockTypeText_raw['last_meta_block'] = $METAdataLastBlockFlag; $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type'] = $METAdataBlockType; $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type_text'] = $METAdataBlockTypeText; $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_length'] = $METAdataBlockLength; if ($METAdataBlockOffset + 4 + $METAdataBlockLength > $info['filesize']) { $info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_TYPE (' . $METAdataBlockType . ') at offset ' . $METAdataBlockOffset . ' extends beyond end of file'; break; } if ($METAdataBlockLength < 1) { $info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_LENGTH (' . $METAdataBlockLength . ') at offset ' . $METAdataBlockOffset . ' is invalid'; break; } $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'] = fread($this->getid3->fp, $METAdataBlockLength); $info['avdataoffset'] = ftell($this->getid3->fp); switch ($METAdataBlockTypeText) { case 'STREAMINFO': // 0x00 if (!$this->FLACparseSTREAMINFO($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { return false; } break; case 'PADDING': // 0x01 // ignore break; case 'APPLICATION': // 0x02 if (!$this->FLACparseAPPLICATION($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { return false; } break; case 'SEEKTABLE': // 0x03 if (!$this->FLACparseSEEKTABLE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { return false; } break; case 'VORBIS_COMMENT': // 0x04 $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = ftell($this->getid3->fp) - $METAdataBlockLength; $getid3_temp->info['audio']['dataformat'] = 'flac'; $getid3_temp->info['flac'] = $info['flac']; $getid3_ogg = new getid3_ogg($getid3_temp); $getid3_ogg->ParseVorbisCommentsFilepointer(); $maybe_copy_keys = array('vendor', 'comments_raw', 'comments', 'replay_gain'); foreach ($maybe_copy_keys as $maybe_copy_key) { if (!empty($getid3_temp->info['ogg'][$maybe_copy_key])) { $info['ogg'][$maybe_copy_key] = $getid3_temp->info['ogg'][$maybe_copy_key]; } } if (!empty($getid3_temp->info['replay_gain'])) { $info['replay_gain'] = $getid3_temp->info['replay_gain']; } unset($getid3_temp, $getid3_ogg); break; case 'CUESHEET': // 0x05 if (!getid3_flac::FLACparseCUESHEET($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { return false; } break; case 'PICTURE': // 0x06 if (!getid3_flac::FLACparsePICTURE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { return false; } break; default: $info['warning'][] = 'Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE (' . $METAdataBlockType . ') at offset ' . $METAdataBlockOffset; break; } } while ($METAdataLastBlockFlag === false); if (isset($info['flac']['PICTURE'])) { foreach ($info['flac']['PICTURE'] as $key => $valuearray) { if (!empty($valuearray['image_mime']) && !empty($valuearray['data'])) { $info['ogg']['comments']['picture'][] = array('image_mime' => $valuearray['image_mime'], 'data' => $valuearray['data']); } } } if (isset($info['flac']['STREAMINFO'])) { $info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset']; $info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8); if ($info['flac']['uncompressed_audio_bytes'] == 0) { $info['error'][] = 'Corrupt FLAC file: uncompressed_audio_bytes == zero'; return false; } $info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes']; } // set md5_data_source - built into flac 0.5+ if (isset($info['flac']['STREAMINFO']['audio_signature'])) { if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("", 16)) { $info['warning'][] = 'FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'; } else { $info['md5_data_source'] = ''; $md5 = $info['flac']['STREAMINFO']['audio_signature']; for ($i = 0; $i < strlen($md5); $i++) { $info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT); } if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) { unset($info['md5_data_source']); } } } $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; if ($info['audio']['bits_per_sample'] == 8) { // special case // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed $info['warning'][] = 'FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file'; } if (!empty($info['ogg']['vendor'])) { $info['audio']['encoder'] = $info['ogg']['vendor']; } return true; }