private function parseEBML(&$info) { // http://www.matroska.org/technical/specs/index.html#EBMLBasics $this->current_offset = $info['avdataoffset']; while ($this->getEBMLelement($top_element, $info['avdataend'])) { switch ($top_element['id']) { case self::ID_EBML: $info['matroska']['header']['offset'] = $top_element['offset']; $info['matroska']['header']['length'] = $top_element['length']; while ($this->getEBMLelement($element_data, $top_element['end'], true)) { switch ($element_data['id']) { case self::ID_EBMLVERSION: case self::ID_EBMLREADVERSION: case self::ID_EBMLMAXIDLENGTH: case self::ID_EBMLMAXSIZELENGTH: case self::ID_DOCTYPEVERSION: case self::ID_DOCTYPEREADVERSION: $element_data['data'] = Utils::BigEndian2Int($element_data['data']); break; case self::ID_DOCTYPE: $element_data['data'] = Utils::trimNullByte($element_data['data']); $info['matroska']['doctype'] = $element_data['data']; $info['fileformat'] = $element_data['data']; break; default: $this->unhandledElement('header', __LINE__, $element_data); } unset($element_data['offset'], $element_data['end']); $info['matroska']['header']['elements'][] = $element_data; } break; case self::ID_SEGMENT: $info['matroska']['segment'][0]['offset'] = $top_element['offset']; $info['matroska']['segment'][0]['length'] = $top_element['length']; while ($this->getEBMLelement($element_data, $top_element['end'])) { if ($element_data['id'] != self::ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required $info['matroska']['segments'][] = $element_data; } switch ($element_data['id']) { case self::ID_SEEKHEAD: // Contains the position of other level 1 elements. while ($this->getEBMLelement($seek_entry, $element_data['end'])) { switch ($seek_entry['id']) { case self::ID_SEEK: // Contains a single seek entry to an EBML element while ($this->getEBMLelement($sub_seek_entry, $seek_entry['end'], true)) { switch ($sub_seek_entry['id']) { case self::ID_SEEKID: $seek_entry['target_id'] = self::EBML2Int($sub_seek_entry['data']); $seek_entry['target_name'] = self::EBMLidName($seek_entry['target_id']); break; case self::ID_SEEKPOSITION: $seek_entry['target_offset'] = $element_data['offset'] + Utils::BigEndian2Int($sub_seek_entry['data']); break; default: $this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry); } } if ($seek_entry['target_id'] != self::ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required $info['matroska']['seek'][] = $seek_entry; } break; default: $this->unhandledElement('seekhead', __LINE__, $seek_entry); } } break; case self::ID_TRACKS: // A top-level block of information with many tracks described. $info['matroska']['tracks'] = $element_data; while ($this->getEBMLelement($track_entry, $element_data['end'])) { switch ($track_entry['id']) { case self::ID_TRACKENTRY: //subelements: Describes a track with all elements. while ($this->getEBMLelement($subelement, $track_entry['end'], [self::ID_VIDEO, self::ID_AUDIO, self::ID_CONTENTENCODINGS, self::ID_CODECPRIVATE])) { switch ($subelement['id']) { case self::ID_TRACKNUMBER: case self::ID_TRACKUID: case self::ID_TRACKTYPE: case self::ID_MINCACHE: case self::ID_MAXCACHE: case self::ID_MAXBLOCKADDITIONID: case self::ID_DEFAULTDURATION: // nanoseconds per frame $track_entry[$subelement['id_name']] = Utils::BigEndian2Int($subelement['data']); break; case self::ID_TRACKTIMECODESCALE: $track_entry[$subelement['id_name']] = Utils::BigEndian2Float($subelement['data']); break; case self::ID_CODECID: case self::ID_LANGUAGE: case self::ID_NAME: case self::ID_CODECNAME: $track_entry[$subelement['id_name']] = Utils::trimNullByte($subelement['data']); break; case self::ID_CODECPRIVATE: $track_entry[$subelement['id_name']] = $this->readEBMLelementData($subelement['length'], true); break; case self::ID_FLAGENABLED: case self::ID_FLAGDEFAULT: case self::ID_FLAGFORCED: case self::ID_FLAGLACING: case self::ID_CODECDECODEALL: $track_entry[$subelement['id_name']] = (bool) Utils::BigEndian2Int($subelement['data']); break; case self::ID_VIDEO: while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { switch ($sub_subelement['id']) { case self::ID_PIXELWIDTH: case self::ID_PIXELHEIGHT: case self::ID_PIXELCROPBOTTOM: case self::ID_PIXELCROPTOP: case self::ID_PIXELCROPLEFT: case self::ID_PIXELCROPRIGHT: case self::ID_DISPLAYWIDTH: case self::ID_DISPLAYHEIGHT: case self::ID_DISPLAYUNIT: case self::ID_ASPECTRATIOTYPE: case self::ID_STEREOMODE: case self::ID_OLDSTEREOMODE: $track_entry[$sub_subelement['id_name']] = Utils::BigEndian2Int($sub_subelement['data']); break; case self::ID_FLAGINTERLACED: $track_entry[$sub_subelement['id_name']] = (bool) Utils::BigEndian2Int($sub_subelement['data']); break; case self::ID_GAMMAVALUE: $track_entry[$sub_subelement['id_name']] = Utils::BigEndian2Float($sub_subelement['data']); break; case self::ID_COLOURSPACE: $track_entry[$sub_subelement['id_name']] = Utils::trimNullByte($sub_subelement['data']); break; default: $this->unhandledElement('track.video', __LINE__, $sub_subelement); } } break; case self::ID_AUDIO: while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { switch ($sub_subelement['id']) { case self::ID_CHANNELS: case self::ID_BITDEPTH: $track_entry[$sub_subelement['id_name']] = Utils::BigEndian2Int($sub_subelement['data']); break; case self::ID_SAMPLINGFREQUENCY: case self::ID_OUTPUTSAMPLINGFREQUENCY: $track_entry[$sub_subelement['id_name']] = Utils::BigEndian2Float($sub_subelement['data']); break; case self::ID_CHANNELPOSITIONS: $track_entry[$sub_subelement['id_name']] = Utils::trimNullByte($sub_subelement['data']); break; default: $this->unhandledElement('track.audio', __LINE__, $sub_subelement); } } break; case self::ID_CONTENTENCODINGS: while ($this->getEBMLelement($sub_subelement, $subelement['end'])) { switch ($sub_subelement['id']) { case self::ID_CONTENTENCODING: while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], [self::ID_CONTENTCOMPRESSION, self::ID_CONTENTENCRYPTION])) { switch ($sub_sub_subelement['id']) { case self::ID_CONTENTENCODINGORDER: case self::ID_CONTENTENCODINGSCOPE: case self::ID_CONTENTENCODINGTYPE: $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']] = Utils::BigEndian2Int($sub_sub_subelement['data']); break; case self::ID_CONTENTCOMPRESSION: while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { switch ($sub_sub_sub_subelement['id']) { case self::ID_CONTENTCOMPALGO: $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = Utils::BigEndian2Int($sub_sub_sub_subelement['data']); break; case self::ID_CONTENTCOMPSETTINGS: $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; default: $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); } } break; case self::ID_CONTENTENCRYPTION: while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { switch ($sub_sub_sub_subelement['id']) { case self::ID_CONTENTENCALGO: case self::ID_CONTENTSIGALGO: case self::ID_CONTENTSIGHASHALGO: $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = Utils::BigEndian2Int($sub_sub_sub_subelement['data']); break; case self::ID_CONTENTENCKEYID: case self::ID_CONTENTSIGNATURE: case self::ID_CONTENTSIGKEYID: $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; default: $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); } } break; default: $this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement); } } break; default: $this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement); } } break; default: $this->unhandledElement('track', __LINE__, $subelement); } } $info['matroska']['tracks']['tracks'][] = $track_entry; break; default: $this->unhandledElement('tracks', __LINE__, $track_entry); } } break; case self::ID_INFO: // Contains miscellaneous general information and statistics on the file. $info_entry = []; while ($this->getEBMLelement($subelement, $element_data['end'], true)) { switch ($subelement['id']) { case self::ID_TIMECODESCALE: $info_entry[$subelement['id_name']] = Utils::BigEndian2Int($subelement['data']); break; case self::ID_DURATION: $info_entry[$subelement['id_name']] = Utils::BigEndian2Float($subelement['data']); break; case self::ID_DATEUTC: $info_entry[$subelement['id_name']] = Utils::BigEndian2Int($subelement['data']); $info_entry[$subelement['id_name'] . '_unix'] = self::EBMLdate2unix($info_entry[$subelement['id_name']]); break; case self::ID_SEGMENTUID: case self::ID_PREVUID: case self::ID_NEXTUID: $info_entry[$subelement['id_name']] = Utils::trimNullByte($subelement['data']); break; case self::ID_SEGMENTFAMILY: $info_entry[$subelement['id_name']][] = Utils::trimNullByte($subelement['data']); break; case self::ID_SEGMENTFILENAME: case self::ID_PREVFILENAME: case self::ID_NEXTFILENAME: case self::ID_TITLE: case self::ID_MUXINGAPP: case self::ID_WRITINGAPP: $info_entry[$subelement['id_name']] = Utils::trimNullByte($subelement['data']); $info['matroska']['comments'][strtolower($subelement['id_name'])][] = $info_entry[$subelement['id_name']]; break; case self::ID_CHAPTERTRANSLATE: $chaptertranslate_entry = []; while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { switch ($sub_subelement['id']) { case self::ID_CHAPTERTRANSLATEEDITIONUID: $chaptertranslate_entry[$sub_subelement['id_name']][] = Utils::BigEndian2Int($sub_subelement['data']); break; case self::ID_CHAPTERTRANSLATECODEC: $chaptertranslate_entry[$sub_subelement['id_name']] = Utils::BigEndian2Int($sub_subelement['data']); break; case self::ID_CHAPTERTRANSLATEID: $chaptertranslate_entry[$sub_subelement['id_name']] = Utils::trimNullByte($sub_subelement['data']); break; default: $this->unhandledElement('info.chaptertranslate', __LINE__, $sub_subelement); } } $info_entry[$subelement['id_name']] = $chaptertranslate_entry; break; default: $this->unhandledElement('info', __LINE__, $subelement); } } $info['matroska']['info'][] = $info_entry; break; case self::ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams. if (self::$hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway $this->current_offset = $element_data['end']; break; } $cues_entry = []; while ($this->getEBMLelement($subelement, $element_data['end'])) { switch ($subelement['id']) { case self::ID_CUEPOINT: $cuepoint_entry = []; while ($this->getEBMLelement($sub_subelement, $subelement['end'], [self::ID_CUETRACKPOSITIONS])) { switch ($sub_subelement['id']) { case self::ID_CUETRACKPOSITIONS: $cuetrackpositions_entry = []; while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { switch ($sub_sub_subelement['id']) { case self::ID_CUETRACK: case self::ID_CUECLUSTERPOSITION: case self::ID_CUEBLOCKNUMBER: case self::ID_CUECODECSTATE: $cuetrackpositions_entry[$sub_sub_subelement['id_name']] = Utils::BigEndian2Int($sub_sub_subelement['data']); break; default: $this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement); } } $cuepoint_entry[$sub_subelement['id_name']][] = $cuetrackpositions_entry; break; case self::ID_CUETIME: $cuepoint_entry[$sub_subelement['id_name']] = Utils::BigEndian2Int($sub_subelement['data']); break; default: $this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement); } } $cues_entry[] = $cuepoint_entry; break; default: $this->unhandledElement('cues', __LINE__, $subelement); } } $info['matroska']['cues'] = $cues_entry; break; case self::ID_TAGS: // Element containing elements specific to Tracks/Chapters. $tags_entry = []; while ($this->getEBMLelement($subelement, $element_data['end'], false)) { switch ($subelement['id']) { case self::ID_TAG: $tag_entry = []; while ($this->getEBMLelement($sub_subelement, $subelement['end'], false)) { switch ($sub_subelement['id']) { case self::ID_TARGETS: $targets_entry = []; while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { switch ($sub_sub_subelement['id']) { case self::ID_TARGETTYPEVALUE: $targets_entry[$sub_sub_subelement['id_name']] = Utils::BigEndian2Int($sub_sub_subelement['data']); $targets_entry[strtolower($sub_sub_subelement['id_name']) . '_long'] = self::TargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]); break; case self::ID_TARGETTYPE: $targets_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; break; case self::ID_TAGTRACKUID: case self::ID_TAGEDITIONUID: case self::ID_TAGCHAPTERUID: case self::ID_TAGATTACHMENTUID: $targets_entry[$sub_sub_subelement['id_name']][] = Utils::BigEndian2Int($sub_sub_subelement['data']); break; default: $this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement); } } $tag_entry[$sub_subelement['id_name']] = $targets_entry; break; case self::ID_SIMPLETAG: $tag_entry[$sub_subelement['id_name']][] = $this->HandleEMBLSimpleTag($sub_subelement['end']); break; default: $this->unhandledElement('tags.tag', __LINE__, $sub_subelement); } } $tags_entry[] = $tag_entry; break; default: $this->unhandledElement('tags', __LINE__, $subelement); } } $info['matroska']['tags'] = $tags_entry; break; case self::ID_ATTACHMENTS: // Contain attached files. while ($this->getEBMLelement($subelement, $element_data['end'])) { switch ($subelement['id']) { case self::ID_ATTACHEDFILE: $attachedfile_entry = []; while ($this->getEBMLelement($sub_subelement, $subelement['end'], [self::ID_FILEDATA])) { switch ($sub_subelement['id']) { case self::ID_FILEDESCRIPTION: case self::ID_FILENAME: case self::ID_FILEMIMETYPE: $attachedfile_entry[$sub_subelement['id_name']] = $sub_subelement['data']; break; case self::ID_FILEDATA: $attachedfile_entry['data_offset'] = $this->current_offset; $attachedfile_entry['data_length'] = $sub_subelement['length']; $attachedfile_entry[$sub_subelement['id_name']] = $this->saveAttachment($attachedfile_entry['FileName'], $attachedfile_entry['data_offset'], $attachedfile_entry['data_length']); $this->current_offset = $sub_subelement['end']; break; case self::ID_FILEUID: $attachedfile_entry[$sub_subelement['id_name']] = Utils::BigEndian2Int($sub_subelement['data']); break; default: $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement); } } $info['matroska']['attachments'][] = $attachedfile_entry; break; default: $this->unhandledElement('attachments', __LINE__, $subelement); } } break; case self::ID_CHAPTERS: while ($this->getEBMLelement($subelement, $element_data['end'])) { switch ($subelement['id']) { case self::ID_EDITIONENTRY: $editionentry_entry = []; while ($this->getEBMLelement($sub_subelement, $subelement['end'], [self::ID_CHAPTERATOM])) { switch ($sub_subelement['id']) { case self::ID_EDITIONUID: $editionentry_entry[$sub_subelement['id_name']] = Utils::BigEndian2Int($sub_subelement['data']); break; case self::ID_EDITIONFLAGHIDDEN: case self::ID_EDITIONFLAGDEFAULT: case self::ID_EDITIONFLAGORDERED: $editionentry_entry[$sub_subelement['id_name']] = (bool) Utils::BigEndian2Int($sub_subelement['data']); break; case self::ID_CHAPTERATOM: $chapteratom_entry = []; while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], [self::ID_CHAPTERTRACK, self::ID_CHAPTERDISPLAY])) { switch ($sub_sub_subelement['id']) { case self::ID_CHAPTERSEGMENTUID: case self::ID_CHAPTERSEGMENTEDITIONUID: $chapteratom_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; break; case self::ID_CHAPTERFLAGENABLED: case self::ID_CHAPTERFLAGHIDDEN: $chapteratom_entry[$sub_sub_subelement['id_name']] = (bool) Utils::BigEndian2Int($sub_sub_subelement['data']); break; case self::ID_CHAPTERUID: case self::ID_CHAPTERTIMESTART: case self::ID_CHAPTERTIMEEND: $chapteratom_entry[$sub_sub_subelement['id_name']] = Utils::BigEndian2Int($sub_sub_subelement['data']); break; case self::ID_CHAPTERTRACK: $chaptertrack_entry = []; while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { switch ($sub_sub_sub_subelement['id']) { case self::ID_CHAPTERTRACKNUMBER: $chaptertrack_entry[$sub_sub_sub_subelement['id_name']] = Utils::BigEndian2Int($sub_sub_sub_subelement['data']); break; default: $this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement); } } $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry; break; case self::ID_CHAPTERDISPLAY: $chapterdisplay_entry = []; while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { switch ($sub_sub_sub_subelement['id']) { case self::ID_CHAPSTRING: case self::ID_CHAPLANGUAGE: case self::ID_CHAPCOUNTRY: $chapterdisplay_entry[$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; default: $this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement); } } $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry; break; default: $this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement); } } $editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry; break; default: $this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement); } } $info['matroska']['chapters'][] = $editionentry_entry; break; default: $this->unhandledElement('chapters', __LINE__, $subelement); } } break; case self::ID_CLUSTER: // The lower level element containing the (monolithic) Block structure. $cluster_entry = []; while ($this->getEBMLelement($subelement, $element_data['end'], [self::ID_CLUSTERSILENTTRACKS, self::ID_CLUSTERBLOCKGROUP, self::ID_CLUSTERSIMPLEBLOCK])) { switch ($subelement['id']) { case self::ID_CLUSTERTIMECODE: case self::ID_CLUSTERPOSITION: case self::ID_CLUSTERPREVSIZE: $cluster_entry[$subelement['id_name']] = Utils::BigEndian2Int($subelement['data']); break; case self::ID_CLUSTERSILENTTRACKS: $cluster_silent_tracks = []; while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { switch ($sub_subelement['id']) { case self::ID_CLUSTERSILENTTRACKNUMBER: $cluster_silent_tracks[] = Utils::BigEndian2Int($sub_subelement['data']); break; default: $this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement); } } $cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks; break; case self::ID_CLUSTERBLOCKGROUP: $cluster_block_group = ['offset' => $this->current_offset]; while ($this->getEBMLelement($sub_subelement, $subelement['end'], [self::ID_CLUSTERBLOCK])) { switch ($sub_subelement['id']) { case self::ID_CLUSTERBLOCK: $cluster_block_group[$sub_subelement['id_name']] = $this->HandleEMBLClusterBlock($sub_subelement, self::ID_CLUSTERBLOCK, $info); break; case self::ID_CLUSTERREFERENCEPRIORITY: // unsigned-int // unsigned-int case self::ID_CLUSTERBLOCKDURATION: // unsigned-int $cluster_block_group[$sub_subelement['id_name']] = Utils::BigEndian2Int($sub_subelement['data']); break; case self::ID_CLUSTERREFERENCEBLOCK: // signed-int $cluster_block_group[$sub_subelement['id_name']][] = Utils::BigEndian2Int($sub_subelement['data'], false, true); break; case self::ID_CLUSTERCODECSTATE: $cluster_block_group[$sub_subelement['id_name']] = Utils::trimNullByte($sub_subelement['data']); break; default: $this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement); } } $cluster_entry[$subelement['id_name']][] = $cluster_block_group; break; case self::ID_CLUSTERSIMPLEBLOCK: $cluster_entry[$subelement['id_name']][] = $this->HandleEMBLClusterBlock($subelement, self::ID_CLUSTERSIMPLEBLOCK, $info); break; default: $this->unhandledElement('cluster', __LINE__, $subelement); } $this->current_offset = $subelement['end']; } if (!self::$hide_clusters) { $info['matroska']['cluster'][] = $cluster_entry; } // check to see if all the data we need exists already, if so, break out of the loop if (!self::$parse_whole_file) { if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) { return; } } } } break; default: $this->unhandledElement('segment', __LINE__, $element_data); } } break; default: $this->unhandledElement('root', __LINE__, $top_element); } } }