| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 3 ///////////////////////////////////////////////////////////////// 4 /// getID3() by James Heinrich <info@getid3.org> // 5 // available at https://github.com/JamesHeinrich/getID3 // 6 // or https://www.getid3.org // 7 // or http://getid3.sourceforge.net // 8 // see readme.txt for more details // 9 ///////////////////////////////////////////////////////////////// 10 // // 11 // module.audio-video.matriska.php // 12 // module for analyzing Matroska containers // 13 // dependencies: NONE // 14 // /// 15 ///////////////////////////////////////////////////////////////// 16 17 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers 18 exit; 19 } 20 21 define('EBML_ID_CHAPTERS', 0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation. 22 define('EBML_ID_SEEKHEAD', 0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements. 23 define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found <http://www.matroska.org/technical/specs/tagging/index.html>. 24 define('EBML_ID_INFO', 0x0549A966); // [15][49][A9][66] -- Contains miscellaneous general information and statistics on the file. 25 define('EBML_ID_TRACKS', 0x0654AE6B); // [16][54][AE][6B] -- A top-level block of information with many tracks described. 26 define('EBML_ID_SEGMENT', 0x08538067); // [18][53][80][67] -- This element contains all other top-level (level 1) elements. Typically a Matroska file is composed of 1 segment. 27 define('EBML_ID_ATTACHMENTS', 0x0941A469); // [19][41][A4][69] -- Contain attached files. 28 define('EBML_ID_EBML', 0x0A45DFA3); // [1A][45][DF][A3] -- Set the EBML characteristics of the data to follow. Each EBML document has to start with this. 29 define('EBML_ID_CUES', 0x0C53BB6B); // [1C][53][BB][6B] -- A top-level element to speed seeking access. All entries are local to the segment. 30 define('EBML_ID_CLUSTER', 0x0F43B675); // [1F][43][B6][75] -- The lower level element containing the (monolithic) Block structure. 31 define('EBML_ID_LANGUAGE', 0x02B59C); // [22][B5][9C] -- Specifies the language of the track in the Matroska languages form. 32 define('EBML_ID_TRACKTIMECODESCALE', 0x03314F); // [23][31][4F] -- The scale to apply on this track to work at normal speed in relation with other tracks (mostly used to adjust video speed when the audio length differs). 33 define('EBML_ID_DEFAULTDURATION', 0x03E383); // [23][E3][83] -- Number of nanoseconds (i.e. not scaled) per frame. 34 define('EBML_ID_CODECNAME', 0x058688); // [25][86][88] -- A human-readable string specifying the codec. 35 define('EBML_ID_CODECDOWNLOADURL', 0x06B240); // [26][B2][40] -- A URL to download about the codec used. 36 define('EBML_ID_TIMECODESCALE', 0x0AD7B1); // [2A][D7][B1] -- Timecode scale in nanoseconds (1.000.000 means all timecodes in the segment are expressed in milliseconds). 37 define('EBML_ID_COLOURSPACE', 0x0EB524); // [2E][B5][24] -- Same value as in AVI (32 bits). 38 define('EBML_ID_GAMMAVALUE', 0x0FB523); // [2F][B5][23] -- Gamma Value. 39 define('EBML_ID_CODECSETTINGS', 0x1A9697); // [3A][96][97] -- A string describing the encoding setting used. 40 define('EBML_ID_CODECINFOURL', 0x1B4040); // [3B][40][40] -- A URL to find information about the codec used. 41 define('EBML_ID_PREVFILENAME', 0x1C83AB); // [3C][83][AB] -- An escaped filename corresponding to the previous segment. 42 define('EBML_ID_PREVUID', 0x1CB923); // [3C][B9][23] -- A unique ID to identify the previous chained segment (128 bits). 43 define('EBML_ID_NEXTFILENAME', 0x1E83BB); // [3E][83][BB] -- An escaped filename corresponding to the next segment. 44 define('EBML_ID_NEXTUID', 0x1EB923); // [3E][B9][23] -- A unique ID to identify the next chained segment (128 bits). 45 define('EBML_ID_CONTENTCOMPALGO', 0x0254); // [42][54] -- The compression algorithm used. Algorithms that have been specified so far are: 46 define('EBML_ID_CONTENTCOMPSETTINGS', 0x0255); // [42][55] -- Settings that might be needed by the decompressor. For Header Stripping (ContentCompAlgo=3), the bytes that were removed from the beggining of each frames of the track. 47 define('EBML_ID_DOCTYPE', 0x0282); // [42][82] -- A string that describes the type of document that follows this EBML header ('matroska' in our case). 48 define('EBML_ID_DOCTYPEREADVERSION', 0x0285); // [42][85] -- The minimum DocType version an interpreter has to support to read this file. 49 define('EBML_ID_EBMLVERSION', 0x0286); // [42][86] -- The version of EBML parser used to create the file. 50 define('EBML_ID_DOCTYPEVERSION', 0x0287); // [42][87] -- The version of DocType interpreter used to create the file. 51 define('EBML_ID_EBMLMAXIDLENGTH', 0x02F2); // [42][F2] -- The maximum length of the IDs you'll find in this file (4 or less in Matroska). 52 define('EBML_ID_EBMLMAXSIZELENGTH', 0x02F3); // [42][F3] -- The maximum length of the sizes you'll find in this file (8 or less in Matroska). This does not override the element size indicated at the beginning of an element. Elements that have an indicated size which is larger than what is allowed by EBMLMaxSizeLength shall be considered invalid. 53 define('EBML_ID_EBMLREADVERSION', 0x02F7); // [42][F7] -- The minimum EBML version a parser has to support to read this file. 54 define('EBML_ID_CHAPLANGUAGE', 0x037C); // [43][7C] -- The languages corresponding to the string, in the bibliographic ISO-639-2 form. 55 define('EBML_ID_CHAPCOUNTRY', 0x037E); // [43][7E] -- The countries corresponding to the string, same 2 octets as in Internet domains. 56 define('EBML_ID_SEGMENTFAMILY', 0x0444); // [44][44] -- A randomly generated unique ID that all segments related to each other must use (128 bits). 57 define('EBML_ID_DATEUTC', 0x0461); // [44][61] -- Date of the origin of timecode (value 0), i.e. production date. 58 define('EBML_ID_TAGLANGUAGE', 0x047A); // [44][7A] -- Specifies the language of the tag specified, in the Matroska languages form. 59 define('EBML_ID_TAGDEFAULT', 0x0484); // [44][84] -- Indication to know if this is the default/original language to use for the given tag. 60 define('EBML_ID_TAGBINARY', 0x0485); // [44][85] -- The values of the Tag if it is binary. Note that this cannot be used in the same SimpleTag as TagString. 61 define('EBML_ID_TAGSTRING', 0x0487); // [44][87] -- The value of the Tag. 62 define('EBML_ID_DURATION', 0x0489); // [44][89] -- Duration of the segment (based on TimecodeScale). 63 define('EBML_ID_CHAPPROCESSPRIVATE', 0x050D); // [45][0D] -- Some optional data attached to the ChapProcessCodecID information. For ChapProcessCodecID = 1, it is the "DVD level" equivalent. 64 define('EBML_ID_CHAPTERFLAGENABLED', 0x0598); // [45][98] -- Specify wether the chapter is enabled. It can be enabled/disabled by a Control Track. When disabled, the movie should skip all the content between the TimeStart and TimeEnd of this chapter. 65 define('EBML_ID_TAGNAME', 0x05A3); // [45][A3] -- The name of the Tag that is going to be stored. 66 define('EBML_ID_EDITIONENTRY', 0x05B9); // [45][B9] -- Contains all information about a segment edition. 67 define('EBML_ID_EDITIONUID', 0x05BC); // [45][BC] -- A unique ID to identify the edition. It's useful for tagging an edition. 68 define('EBML_ID_EDITIONFLAGHIDDEN', 0x05BD); // [45][BD] -- If an edition is hidden (1), it should not be available to the user interface (but still to Control Tracks). 69 define('EBML_ID_EDITIONFLAGDEFAULT', 0x05DB); // [45][DB] -- If a flag is set (1) the edition should be used as the default one. 70 define('EBML_ID_EDITIONFLAGORDERED', 0x05DD); // [45][DD] -- Specify if the chapters can be defined multiple times and the order to play them is enforced. 71 define('EBML_ID_FILEDATA', 0x065C); // [46][5C] -- The data of the file. 72 define('EBML_ID_FILEMIMETYPE', 0x0660); // [46][60] -- MIME type of the file. 73 define('EBML_ID_FILENAME', 0x066E); // [46][6E] -- Filename of the attached file. 74 define('EBML_ID_FILEREFERRAL', 0x0675); // [46][75] -- A binary value that a track/codec can refer to when the attachment is needed. 75 define('EBML_ID_FILEDESCRIPTION', 0x067E); // [46][7E] -- A human-friendly name for the attached file. 76 define('EBML_ID_FILEUID', 0x06AE); // [46][AE] -- Unique ID representing the file, as random as possible. 77 define('EBML_ID_CONTENTENCALGO', 0x07E1); // [47][E1] -- The encryption algorithm used. The value '0' means that the contents have not been encrypted but only signed. Predefined values: 78 define('EBML_ID_CONTENTENCKEYID', 0x07E2); // [47][E2] -- For public key algorithms this is the ID of the public key the data was encrypted with. 79 define('EBML_ID_CONTENTSIGNATURE', 0x07E3); // [47][E3] -- A cryptographic signature of the contents. 80 define('EBML_ID_CONTENTSIGKEYID', 0x07E4); // [47][E4] -- This is the ID of the private key the data was signed with. 81 define('EBML_ID_CONTENTSIGALGO', 0x07E5); // [47][E5] -- The algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values: 82 define('EBML_ID_CONTENTSIGHASHALGO', 0x07E6); // [47][E6] -- The hash algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values: 83 define('EBML_ID_MUXINGAPP', 0x0D80); // [4D][80] -- Muxing application or library ("libmatroska-0.4.3"). 84 define('EBML_ID_SEEK', 0x0DBB); // [4D][BB] -- Contains a single seek entry to an EBML element. 85 define('EBML_ID_CONTENTENCODINGORDER', 0x1031); // [50][31] -- Tells when this modification was used during encoding/muxing starting with 0 and counting upwards. The decoder/demuxer has to start with the highest order number it finds and work its way down. This value has to be unique over all ContentEncodingOrder elements in the segment. 86 define('EBML_ID_CONTENTENCODINGSCOPE', 0x1032); // [50][32] -- A bit field that describes which elements have been modified in this way. Values (big endian) can be OR'ed. Possible values: 87 define('EBML_ID_CONTENTENCODINGTYPE', 0x1033); // [50][33] -- A value describing what kind of transformation has been done. Possible values: 88 define('EBML_ID_CONTENTCOMPRESSION', 0x1034); // [50][34] -- Settings describing the compression used. Must be present if the value of ContentEncodingType is 0 and absent otherwise. Each block must be decompressable even if no previous block is available in order not to prevent seeking. 89 define('EBML_ID_CONTENTENCRYPTION', 0x1035); // [50][35] -- Settings describing the encryption used. Must be present if the value of ContentEncodingType is 1 and absent otherwise. 90 define('EBML_ID_CUEREFNUMBER', 0x135F); // [53][5F] -- Number of the referenced Block of Track X in the specified Cluster. 91 define('EBML_ID_NAME', 0x136E); // [53][6E] -- A human-readable track name. 92 define('EBML_ID_CUEBLOCKNUMBER', 0x1378); // [53][78] -- Number of the Block in the specified Cluster. 93 define('EBML_ID_TRACKOFFSET', 0x137F); // [53][7F] -- A value to add to the Block's Timecode. This can be used to adjust the playback offset of a track. 94 define('EBML_ID_SEEKID', 0x13AB); // [53][AB] -- The binary ID corresponding to the element name. 95 define('EBML_ID_SEEKPOSITION', 0x13AC); // [53][AC] -- The position of the element in the segment in octets (0 = first level 1 element). 96 define('EBML_ID_STEREOMODE', 0x13B8); // [53][B8] -- Stereo-3D video mode. 97 define('EBML_ID_OLDSTEREOMODE', 0x13B9); // [53][B9] -- Bogus StereoMode value used in old versions of libmatroska. DO NOT USE. (0: mono, 1: right eye, 2: left eye, 3: both eyes). 98 define('EBML_ID_PIXELCROPBOTTOM', 0x14AA); // [54][AA] -- The number of video pixels to remove at the bottom of the image (for HDTV content). 99 define('EBML_ID_DISPLAYWIDTH', 0x14B0); // [54][B0] -- Width of the video frames to display. 100 define('EBML_ID_DISPLAYUNIT', 0x14B2); // [54][B2] -- Type of the unit for DisplayWidth/Height (0: pixels, 1: centimeters, 2: inches). 101 define('EBML_ID_ASPECTRATIOTYPE', 0x14B3); // [54][B3] -- Specify the possible modifications to the aspect ratio (0: free resizing, 1: keep aspect ratio, 2: fixed). 102 define('EBML_ID_DISPLAYHEIGHT', 0x14BA); // [54][BA] -- Height of the video frames to display. 103 define('EBML_ID_PIXELCROPTOP', 0x14BB); // [54][BB] -- The number of video pixels to remove at the top of the image. 104 define('EBML_ID_PIXELCROPLEFT', 0x14CC); // [54][CC] -- The number of video pixels to remove on the left of the image. 105 define('EBML_ID_PIXELCROPRIGHT', 0x14DD); // [54][DD] -- The number of video pixels to remove on the right of the image. 106 define('EBML_ID_FLAGFORCED', 0x15AA); // [55][AA] -- Set if that track MUST be used during playback. There can be many forced track for a kind (audio, video or subs), the player should select the one which language matches the user preference or the default + forced track. Overlay MAY happen between a forced and non-forced track of the same kind. 107 define('EBML_ID_MAXBLOCKADDITIONID', 0x15EE); // [55][EE] -- The maximum value of BlockAddID. A value 0 means there is no BlockAdditions for this track. 108 define('EBML_ID_WRITINGAPP', 0x1741); // [57][41] -- Writing application ("mkvmerge-0.3.3"). 109 define('EBML_ID_CLUSTERSILENTTRACKS', 0x1854); // [58][54] -- The list of tracks that are not used in that part of the stream. It is useful when using overlay tracks on seeking. Then you should decide what track to use. 110 define('EBML_ID_CLUSTERSILENTTRACKNUMBER', 0x18D7); // [58][D7] -- One of the track number that are not used from now on in the stream. It could change later if not specified as silent in a further Cluster. 111 define('EBML_ID_ATTACHEDFILE', 0x21A7); // [61][A7] -- An attached file. 112 define('EBML_ID_CONTENTENCODING', 0x2240); // [62][40] -- Settings for one content encoding like compression or encryption. 113 define('EBML_ID_BITDEPTH', 0x2264); // [62][64] -- Bits per sample, mostly used for PCM. 114 define('EBML_ID_CODECPRIVATE', 0x23A2); // [63][A2] -- Private data only known to the codec. 115 define('EBML_ID_TARGETS', 0x23C0); // [63][C0] -- Contain all UIDs where the specified meta data apply. It is void to describe everything in the segment. 116 define('EBML_ID_CHAPTERPHYSICALEQUIV', 0x23C3); // [63][C3] -- Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see complete list of values. 117 define('EBML_ID_TAGCHAPTERUID', 0x23C4); // [63][C4] -- A unique ID to identify the Chapter(s) the tags belong to. If the value is 0 at this level, the tags apply to all chapters in the Segment. 118 define('EBML_ID_TAGTRACKUID', 0x23C5); // [63][C5] -- A unique ID to identify the Track(s) the tags belong to. If the value is 0 at this level, the tags apply to all tracks in the Segment. 119 define('EBML_ID_TAGATTACHMENTUID', 0x23C6); // [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment. 120 define('EBML_ID_TAGEDITIONUID', 0x23C9); // [63][C9] -- A unique ID to identify the EditionEntry(s) the tags belong to. If the value is 0 at this level, the tags apply to all editions in the Segment. 121 define('EBML_ID_TARGETTYPE', 0x23CA); // [63][CA] -- An informational string that can be used to display the logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see TargetType). 122 define('EBML_ID_TRACKTRANSLATE', 0x2624); // [66][24] -- The track identification for the given Chapter Codec. 123 define('EBML_ID_TRACKTRANSLATETRACKID', 0x26A5); // [66][A5] -- The binary value used to represent this track in the chapter codec data. The format depends on the ChapProcessCodecID used. 124 define('EBML_ID_TRACKTRANSLATECODEC', 0x26BF); // [66][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu). 125 define('EBML_ID_TRACKTRANSLATEEDITIONUID', 0x26FC); // [66][FC] -- Specify an edition UID on which this translation applies. When not specified, it means for all editions found in the segment. 126 define('EBML_ID_SIMPLETAG', 0x27C8); // [67][C8] -- Contains general information about the target. 127 define('EBML_ID_TARGETTYPEVALUE', 0x28CA); // [68][CA] -- A number to indicate the logical level of the target (see TargetType). 128 define('EBML_ID_CHAPPROCESSCOMMAND', 0x2911); // [69][11] -- Contains all the commands associated to the Atom. 129 define('EBML_ID_CHAPPROCESSTIME', 0x2922); // [69][22] -- Defines when the process command should be handled (0: during the whole chapter, 1: before starting playback, 2: after playback of the chapter). 130 define('EBML_ID_CHAPTERTRANSLATE', 0x2924); // [69][24] -- A tuple of corresponding ID used by chapter codecs to represent this segment. 131 define('EBML_ID_CHAPPROCESSDATA', 0x2933); // [69][33] -- Contains the command information. The data should be interpreted depending on the ChapProcessCodecID value. For ChapProcessCodecID = 1, the data correspond to the binary DVD cell pre/post commands. 132 define('EBML_ID_CHAPPROCESS', 0x2944); // [69][44] -- Contains all the commands associated to the Atom. 133 define('EBML_ID_CHAPPROCESSCODECID', 0x2955); // [69][55] -- Contains the type of the codec used for the processing. A value of 0 means native Matroska processing (to be defined), a value of 1 means the DVD command set is used. More codec IDs can be added later. 134 define('EBML_ID_CHAPTERTRANSLATEID', 0x29A5); // [69][A5] -- The binary value used to represent this segment in the chapter codec data. The format depends on the ChapProcessCodecID used. 135 define('EBML_ID_CHAPTERTRANSLATECODEC', 0x29BF); // [69][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu). 136 define('EBML_ID_CHAPTERTRANSLATEEDITIONUID', 0x29FC); // [69][FC] -- Specify an edition UID on which this correspondance applies. When not specified, it means for all editions found in the segment. 137 define('EBML_ID_CONTENTENCODINGS', 0x2D80); // [6D][80] -- Settings for several content encoding mechanisms like compression or encryption. 138 define('EBML_ID_MINCACHE', 0x2DE7); // [6D][E7] -- The minimum number of frames a player should be able to cache during playback. If set to 0, the reference pseudo-cache system is not used. 139 define('EBML_ID_MAXCACHE', 0x2DF8); // [6D][F8] -- The maximum cache size required to store referenced frames in and the current frame. 0 means no cache is needed. 140 define('EBML_ID_CHAPTERSEGMENTUID', 0x2E67); // [6E][67] -- A segment to play in place of this chapter. Edition ChapterSegmentEditionUID should be used for this segment, otherwise no edition is used. 141 define('EBML_ID_CHAPTERSEGMENTEDITIONUID', 0x2EBC); // [6E][BC] -- The edition to play from the segment linked in ChapterSegmentUID. 142 define('EBML_ID_TRACKOVERLAY', 0x2FAB); // [6F][AB] -- Specify that this track is an overlay track for the Track specified (in the u-integer). That means when this track has a gap (see SilentTracks) the overlay track should be used instead. The order of multiple TrackOverlay matters, the first one is the one that should be used. If not found it should be the second, etc. 143 define('EBML_ID_TAG', 0x3373); // [73][73] -- Element containing elements specific to Tracks/Chapters. 144 define('EBML_ID_SEGMENTFILENAME', 0x3384); // [73][84] -- A filename corresponding to this segment. 145 define('EBML_ID_SEGMENTUID', 0x33A4); // [73][A4] -- A randomly generated unique ID to identify the current segment between many others (128 bits). 146 define('EBML_ID_CHAPTERUID', 0x33C4); // [73][C4] -- A unique ID to identify the Chapter. 147 define('EBML_ID_TRACKUID', 0x33C5); // [73][C5] -- A unique ID to identify the Track. This should be kept the same when making a direct stream copy of the Track to another file. 148 define('EBML_ID_ATTACHMENTLINK', 0x3446); // [74][46] -- The UID of an attachment that is used by this codec. 149 define('EBML_ID_CLUSTERBLOCKADDITIONS', 0x35A1); // [75][A1] -- Contain additional blocks to complete the main one. An EBML parser that has no knowledge of the Block structure could still see and use/skip these data. 150 define('EBML_ID_CHANNELPOSITIONS', 0x347B); // [7D][7B] -- Table of horizontal angles for each successive channel, see appendix. 151 define('EBML_ID_OUTPUTSAMPLINGFREQUENCY', 0x38B5); // [78][B5] -- Real output sampling frequency in Hz (used for SBR techniques). 152 define('EBML_ID_TITLE', 0x3BA9); // [7B][A9] -- General name of the segment. 153 define('EBML_ID_CHAPTERDISPLAY', 0x00); // [80] -- Contains all possible strings to use for the chapter display. 154 define('EBML_ID_TRACKTYPE', 0x03); // [83] -- A set of track types coded on 8 bits (1: video, 2: audio, 3: complex, 0x10: logo, 0x11: subtitle, 0x12: buttons, 0x20: control). 155 define('EBML_ID_CHAPSTRING', 0x05); // [85] -- Contains the string to use as the chapter atom. 156 define('EBML_ID_CODECID', 0x06); // [86] -- An ID corresponding to the codec, see the codec page for more info. 157 define('EBML_ID_FLAGDEFAULT', 0x08); // [88] -- Set if that track (audio, video or subs) SHOULD be used if no language found matches the user preference. 158 define('EBML_ID_CHAPTERTRACKNUMBER', 0x09); // [89] -- UID of the Track to apply this chapter too. In the absense of a control track, choosing this chapter will select the listed Tracks and deselect unlisted tracks. Absense of this element indicates that the Chapter should be applied to any currently used Tracks. 159 define('EBML_ID_CLUSTERSLICES', 0x0E); // [8E] -- Contains slices description. 160 define('EBML_ID_CHAPTERTRACK', 0x0F); // [8F] -- List of tracks on which the chapter applies. If this element is not present, all tracks apply 161 define('EBML_ID_CHAPTERTIMESTART', 0x11); // [91] -- Timecode of the start of Chapter (not scaled). 162 define('EBML_ID_CHAPTERTIMEEND', 0x12); // [92] -- Timecode of the end of Chapter (timecode excluded, not scaled). 163 define('EBML_ID_CUEREFTIME', 0x16); // [96] -- Timecode of the referenced Block. 164 define('EBML_ID_CUEREFCLUSTER', 0x17); // [97] -- Position of the Cluster containing the referenced Block. 165 define('EBML_ID_CHAPTERFLAGHIDDEN', 0x18); // [98] -- If a chapter is hidden (1), it should not be available to the user interface (but still to Control Tracks). 166 define('EBML_ID_FLAGINTERLACED', 0x1A); // [9A] -- Set if the video is interlaced. 167 define('EBML_ID_CLUSTERBLOCKDURATION', 0x1B); // [9B] -- The duration of the Block (based on TimecodeScale). This element is mandatory when DefaultDuration is set for the track. When not written and with no DefaultDuration, the value is assumed to be the difference between the timecode of this Block and the timecode of the next Block in "display" order (not coding order). This element can be useful at the end of a Track (as there is not other Block available), or when there is a break in a track like for subtitle tracks. 168 define('EBML_ID_FLAGLACING', 0x1C); // [9C] -- Set if the track may contain blocks using lacing. 169 define('EBML_ID_CHANNELS', 0x1F); // [9F] -- Numbers of channels in the track. 170 define('EBML_ID_CLUSTERBLOCKGROUP', 0x20); // [A0] -- Basic container of information containing a single Block or BlockVirtual, and information specific to that Block/VirtualBlock. 171 define('EBML_ID_CLUSTERBLOCK', 0x21); // [A1] -- Block containing the actual data to be rendered and a timecode relative to the Cluster Timecode. 172 define('EBML_ID_CLUSTERBLOCKVIRTUAL', 0x22); // [A2] -- A Block with no data. It must be stored in the stream at the place the real Block should be in display order. 173 define('EBML_ID_CLUSTERSIMPLEBLOCK', 0x23); // [A3] -- Similar to Block but without all the extra information, mostly used to reduced overhead when no extra feature is needed. 174 define('EBML_ID_CLUSTERCODECSTATE', 0x24); // [A4] -- The new codec state to use. Data interpretation is private to the codec. This information should always be referenced by a seek entry. 175 define('EBML_ID_CLUSTERBLOCKADDITIONAL', 0x25); // [A5] -- Interpreted by the codec as it wishes (using the BlockAddID). 176 define('EBML_ID_CLUSTERBLOCKMORE', 0x26); // [A6] -- Contain the BlockAdditional and some parameters. 177 define('EBML_ID_CLUSTERPOSITION', 0x27); // [A7] -- Position of the Cluster in the segment (0 in live broadcast streams). It might help to resynchronise offset on damaged streams. 178 define('EBML_ID_CODECDECODEALL', 0x2A); // [AA] -- The codec can decode potentially damaged data. 179 define('EBML_ID_CLUSTERPREVSIZE', 0x2B); // [AB] -- Size of the previous Cluster, in octets. Can be useful for backward playing. 180 define('EBML_ID_TRACKENTRY', 0x2E); // [AE] -- Describes a track with all elements. 181 define('EBML_ID_CLUSTERENCRYPTEDBLOCK', 0x2F); // [AF] -- Similar to SimpleBlock but the data inside the Block are Transformed (encrypt and/or signed). 182 define('EBML_ID_PIXELWIDTH', 0x30); // [B0] -- Width of the encoded video frames in pixels. 183 define('EBML_ID_CUETIME', 0x33); // [B3] -- Absolute timecode according to the segment time base. 184 define('EBML_ID_SAMPLINGFREQUENCY', 0x35); // [B5] -- Sampling frequency in Hz. 185 define('EBML_ID_CHAPTERATOM', 0x36); // [B6] -- Contains the atom information to use as the chapter atom (apply to all tracks). 186 define('EBML_ID_CUETRACKPOSITIONS', 0x37); // [B7] -- Contain positions for different tracks corresponding to the timecode. 187 define('EBML_ID_FLAGENABLED', 0x39); // [B9] -- Set if the track is used. 188 define('EBML_ID_PIXELHEIGHT', 0x3A); // [BA] -- Height of the encoded video frames in pixels. 189 define('EBML_ID_CUEPOINT', 0x3B); // [BB] -- Contains all information relative to a seek point in the segment. 190 define('EBML_ID_CRC32', 0x3F); // [BF] -- The CRC is computed on all the data of the Master element it's in, regardless of its position. It's recommended to put the CRC value at the beggining of the Master element for easier reading. All level 1 elements should include a CRC-32. 191 define('EBML_ID_CLUSTERBLOCKADDITIONID', 0x4B); // [CB] -- The ID of the BlockAdditional element (0 is the main Block). 192 define('EBML_ID_CLUSTERLACENUMBER', 0x4C); // [CC] -- The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc). While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback. 193 define('EBML_ID_CLUSTERFRAMENUMBER', 0x4D); // [CD] -- The number of the frame to generate from this lace with this delay (allow you to generate many frames from the same Block/Frame). 194 define('EBML_ID_CLUSTERDELAY', 0x4E); // [CE] -- The (scaled) delay to apply to the element. 195 define('EBML_ID_CLUSTERDURATION', 0x4F); // [CF] -- The (scaled) duration to apply to the element. 196 define('EBML_ID_TRACKNUMBER', 0x57); // [D7] -- The track number as used in the Block Header (using more than 127 tracks is not encouraged, though the design allows an unlimited number). 197 define('EBML_ID_CUEREFERENCE', 0x5B); // [DB] -- The Clusters containing the required referenced Blocks. 198 define('EBML_ID_VIDEO', 0x60); // [E0] -- Video settings. 199 define('EBML_ID_AUDIO', 0x61); // [E1] -- Audio settings. 200 define('EBML_ID_CLUSTERTIMESLICE', 0x68); // [E8] -- Contains extra time information about the data contained in the Block. While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback. 201 define('EBML_ID_CUECODECSTATE', 0x6A); // [EA] -- The position of the Codec State corresponding to this Cue element. 0 means that the data is taken from the initial Track Entry. 202 define('EBML_ID_CUEREFCODECSTATE', 0x6B); // [EB] -- The position of the Codec State corresponding to this referenced element. 0 means that the data is taken from the initial Track Entry. 203 define('EBML_ID_VOID', 0x6C); // [EC] -- Used to void damaged data, to avoid unexpected behaviors when using damaged data. The content is discarded. Also used to reserve space in a sub-element for later use. 204 define('EBML_ID_CLUSTERTIMECODE', 0x67); // [E7] -- Absolute timecode of the cluster (based on TimecodeScale). 205 define('EBML_ID_CLUSTERBLOCKADDID', 0x6E); // [EE] -- An ID to identify the BlockAdditional level. 206 define('EBML_ID_CUECLUSTERPOSITION', 0x71); // [F1] -- The position of the Cluster containing the required Block. 207 define('EBML_ID_CUETRACK', 0x77); // [F7] -- The track for which a position is given. 208 define('EBML_ID_CLUSTERREFERENCEPRIORITY', 0x7A); // [FA] -- This frame is referenced and has the specified cache priority. In cache only a frame of the same or higher priority can replace this frame. A value of 0 means the frame is not referenced. 209 define('EBML_ID_CLUSTERREFERENCEBLOCK', 0x7B); // [FB] -- Timecode of another frame used as a reference (ie: B or P frame). The timecode is relative to the block it's attached to. 210 define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- Relative position of the data that should be in position of the virtual block. 211 212 213 /** 214 * Matroska constants 215 */ 216 define('MATROSKA_DEFAULT_TIMECODESCALE', 1000000); 217 218 /** 219 * Matroska scan modes are internal state flags for how much of the file we are scanning 220 */ 221 define('MATROSKA_SCAN_HEADER', 0); 222 define('MATROSKA_SCAN_WHOLE_FILE', 1); 223 define('MATROSKA_SCAN_FIRST_CLUSTER', 2); 224 define('MATROSKA_SCAN_LAST_CLUSTER', 3); 225 226 /** 227 * @tutorial http://www.matroska.org/technical/specs/index.html 228 * 229 * @todo Rewrite EBML parser to reduce it's size and honor default element values 230 * @todo After rewrite implement stream size calculation, that will provide additional useful info and enable AAC/FLAC audio bitrate detection 231 */ 232 class getid3_matroska extends getid3_handler 233 { 234 /** 235 * If true, do not return information about CLUSTER chunks, since there's a lot of them 236 * and they're not usually useful [default: TRUE]. 237 * 238 * @var bool 239 */ 240 public $hide_clusters = true; 241 242 /** 243 * True to parse the whole file, not only header [default: FALSE]. 244 * 245 * @var bool 246 */ 247 public $parse_whole_file = false; 248 249 /* 250 * Private parser settings/placeholders. 251 */ 252 private $EBMLbuffer = ''; 253 private $EBMLbuffer_offset = 0; 254 private $EBMLbuffer_length = 0; 255 private $current_offset = 0; 256 private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID); 257 private $scan_mode = MATROSKA_SCAN_HEADER; 258 259 /** 260 * @return bool 261 */ 262 public function Analyze() 263 { 264 $info = &$this->getid3->info; 265 $this->scan_mode = $this->parse_whole_file ? MATROSKA_SCAN_WHOLE_FILE : MATROSKA_SCAN_HEADER; 266 267 // parse container 268 try { 269 $this->parseEBML($info); 270 } catch (Exception $e) { 271 $this->error('EBML parser: '.$e->getMessage()); 272 } 273 274 $this->playtimeFromMetadata($info); 275 276 // If there was no duration metadata, this might be an incomplete file or a streaming file 277 // We need Cluster information so we can use their timecodes to estimate playtime. 278 if (!isset($info['playtime_seconds']) && !$this->parse_whole_file) { 279 // Scan the start and end of file for Clusters to estimate duration 280 $this->scanStartEndForClusters($info); 281 } 282 283 if (isset($info['matroska']['cluster']) && is_array($info['matroska']['cluster'])) { 284 if (!isset($info['playtime_seconds']) && !empty($info['matroska']['cluster'])) { 285 // estimate playtime using clusters if not yet known 286 $this->calculatePlaytimeFromClusters($info); 287 } 288 289 // Remove cluster information from output if hide_clusters is true 290 // These could have been set during scanStartEndForClusters() 291 if ($this->hide_clusters) { 292 unset($info['matroska']['cluster']); 293 } 294 } 295 296 // extract tags 297 if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { 298 foreach ($info['matroska']['tags'] as $key => $infoarray) { 299 $this->ExtractCommentsSimpleTag($infoarray); 300 } 301 } 302 303 // process tracks 304 if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { 305 foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { 306 307 $track_info = array(); 308 $track_info['dataformat'] = self::CodecIDtoCommonName($trackarray['CodecID']); 309 $track_info['default'] = (isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true); 310 if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; } 311 312 switch ($trackarray['TrackType']) { 313 314 case 1: // Video 315 $track_info['resolution_x'] = $trackarray['PixelWidth']; 316 $track_info['resolution_y'] = $trackarray['PixelHeight']; 317 $track_info['display_unit'] = self::displayUnit(isset($trackarray['DisplayUnit']) ? $trackarray['DisplayUnit'] : 0); 318 $track_info['display_x'] = (isset($trackarray['DisplayWidth']) ? $trackarray['DisplayWidth'] : $trackarray['PixelWidth']); 319 $track_info['display_y'] = (isset($trackarray['DisplayHeight']) ? $trackarray['DisplayHeight'] : $trackarray['PixelHeight']); 320 321 if (isset($trackarray['PixelCropBottom'])) { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; } 322 if (isset($trackarray['PixelCropTop'])) { $track_info['crop_top'] = $trackarray['PixelCropTop']; } 323 if (isset($trackarray['PixelCropLeft'])) { $track_info['crop_left'] = $trackarray['PixelCropLeft']; } 324 if (isset($trackarray['PixelCropRight'])) { $track_info['crop_right'] = $trackarray['PixelCropRight']; } 325 if (!empty($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } 326 if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } 327 328 switch ($trackarray['CodecID']) { 329 case 'V_MS/VFW/FOURCC': 330 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); 331 332 $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']); 333 $track_info['codec'] = getid3_riff::fourccLookup($parsed['fourcc']); 334 $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; 335 break; 336 337 /*case 'V_MPEG4/ISO/AVC': 338 $h264['profile'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 1, 1)); 339 $h264['level'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 3, 1)); 340 $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 4, 1)); 341 $h264['NALUlength'] = ($rn & 3) + 1; 342 $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 5, 1)); 343 $nsps = ($rn & 31); 344 $offset = 6; 345 for ($i = 0; $i < $nsps; $i ++) { 346 $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); 347 $h264['SPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); 348 $offset += 2 + $length; 349 } 350 $npps = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 1)); 351 $offset += 1; 352 for ($i = 0; $i < $npps; $i ++) { 353 $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); 354 $h264['PPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); 355 $offset += 2 + $length; 356 } 357 $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $h264; 358 break;*/ 359 } 360 361 if (isset($trackarray['TrackUID'])) { 362 $info['video']['streams'][$trackarray['TrackUID']] = $track_info; 363 } else { 364 $this->warning('Missing mandatory TrackUID for video track'); 365 } 366 break; 367 368 case 2: // Audio 369 $track_info['sample_rate'] = (isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0); 370 $track_info['channels'] = (isset($trackarray['Channels']) ? $trackarray['Channels'] : 1); 371 $track_info['language'] = (isset($trackarray['Language']) ? $trackarray['Language'] : 'eng'); 372 if (isset($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } 373 if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } 374 375 switch ($trackarray['CodecID']) { 376 case 'A_PCM/INT/LIT': 377 case 'A_PCM/INT/BIG': 378 $track_info['bitrate'] = $track_info['sample_rate'] * $track_info['channels'] * $trackarray['BitDepth']; 379 break; 380 381 case 'A_AC3': 382 case 'A_EAC3': 383 case 'A_DTS': 384 case 'A_MPEG/L3': 385 case 'A_MPEG/L2': 386 case 'A_FLAC': 387 $module_dataformat = ($track_info['dataformat'] == 'mp2' ? 'mp3' : ($track_info['dataformat'] == 'eac3' ? 'ac3' : $track_info['dataformat'])); 388 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.$module_dataformat.'.php', __FILE__, true); 389 390 if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { 391 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set'); 392 break; 393 } 394 395 // create temp instance 396 $getid3_temp = new getID3(); 397 if ($track_info['dataformat'] != 'flac') { 398 $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp); 399 } 400 $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; 401 if ($track_info['dataformat'][0] == 'm' || $track_info['dataformat'] == 'flac') { 402 $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; 403 } 404 405 // analyze 406 $class = 'getid3_'.$module_dataformat; 407 $header_data_key = $track_info['dataformat'][0] == 'm' ? 'mpeg' : $track_info['dataformat']; 408 $getid3_audio = new $class($getid3_temp, __CLASS__); 409 if ($track_info['dataformat'] == 'flac') { 410 $getid3_audio->AnalyzeString($trackarray['CodecPrivate']); 411 } 412 else { 413 $getid3_audio->Analyze(); 414 } 415 if (!empty($getid3_temp->info[$header_data_key])) { 416 $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; 417 if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { 418 foreach ($getid3_temp->info['audio'] as $sub_key => $value) { 419 $track_info[$sub_key] = $value; 420 } 421 } 422 } 423 else { 424 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because '.$class.'::Analyze() failed at offset '.$getid3_temp->info['avdataoffset']); 425 } 426 427 // copy errors and warnings 428 if (!empty($getid3_temp->info['error'])) { 429 foreach ($getid3_temp->info['error'] as $newerror) { 430 $this->warning($class.'() says: ['.$newerror.']'); 431 } 432 } 433 if (!empty($getid3_temp->info['warning'])) { 434 foreach ($getid3_temp->info['warning'] as $newerror) { 435 $this->warning($class.'() says: ['.$newerror.']'); 436 } 437 } 438 unset($getid3_temp, $getid3_audio); 439 break; 440 441 case 'A_AAC': 442 case 'A_AAC/MPEG2/LC': 443 case 'A_AAC/MPEG2/LC/SBR': 444 case 'A_AAC/MPEG4/LC': 445 case 'A_AAC/MPEG4/LC/SBR': 446 $this->warning($trackarray['CodecID'].' audio data contains no header, audio/video bitrates can\'t be calculated'); 447 break; 448 449 case 'A_VORBIS': 450 if (!isset($trackarray['CodecPrivate'])) { 451 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data not set'); 452 break; 453 } 454 $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1); 455 if ($vorbis_offset === false) { 456 $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data does not contain "vorbis" keyword'); 457 break; 458 } 459 $vorbis_offset -= 1; 460 461 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); 462 463 // create temp instance 464 $getid3_temp = new getID3(); 465 466 // analyze 467 $getid3_ogg = new getid3_ogg($getid3_temp); 468 $oggpageinfo['page_seqno'] = 0; 469 $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); 470 if (!empty($getid3_temp->info['ogg'])) { 471 $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg']; 472 if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { 473 foreach ($getid3_temp->info['audio'] as $sub_key => $value) { 474 $track_info[$sub_key] = $value; 475 } 476 } 477 } 478 479 // copy errors and warnings 480 if (!empty($getid3_temp->info['error'])) { 481 foreach ($getid3_temp->info['error'] as $newerror) { 482 $this->warning('getid3_ogg() says: ['.$newerror.']'); 483 } 484 } 485 if (!empty($getid3_temp->info['warning'])) { 486 foreach ($getid3_temp->info['warning'] as $newerror) { 487 $this->warning('getid3_ogg() says: ['.$newerror.']'); 488 } 489 } 490 491 if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) { 492 $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal']; 493 } 494 unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset); 495 break; 496 497 case 'A_MS/ACM': 498 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); 499 500 $parsed = getid3_riff::parseWAVEFORMATex($trackarray['CodecPrivate']); 501 foreach ($parsed as $sub_key => $value) { 502 if ($sub_key != 'raw') { 503 $track_info[$sub_key] = $value; 504 } 505 } 506 $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; 507 break; 508 509 default: 510 $this->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"'); 511 break; 512 } 513 if (isset($trackarray['TrackUID'])) { 514 $info['audio']['streams'][$trackarray['TrackUID']] = $track_info; 515 } else { 516 $this->warning('Missing mandatory TrackUID for audio track'); 517 } 518 break; 519 } 520 } 521 522 if (!empty($info['video']['streams'])) { 523 $info['video'] = self::getDefaultStreamInfo($info['video']['streams']); 524 } 525 if (!empty($info['audio']['streams'])) { 526 $info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']); 527 } 528 } 529 530 // process attachments 531 if (isset($info['matroska']['attachments']) && $this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE) { 532 foreach ($info['matroska']['attachments'] as $i => $entry) { 533 if (strpos($entry['FileMimeType'], 'image/') === 0 && !empty($entry['FileData'])) { 534 $info['matroska']['comments']['picture'][] = array('data' => $entry['FileData'], 'image_mime' => $entry['FileMimeType'], 'filename' => $entry['FileName']); 535 } 536 } 537 } 538 539 // determine mime type 540 if (!empty($info['video']['streams'])) { 541 $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska'); 542 } elseif (!empty($info['audio']['streams'])) { 543 $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska'); 544 } elseif (isset($info['mime_type'])) { 545 unset($info['mime_type']); 546 } 547 548 // use _STATISTICS_TAGS if available to set audio/video bitrates 549 if (!empty($info['matroska']['tags'])) { 550 $_STATISTICS_byTrackUID = array(); 551 foreach ($info['matroska']['tags'] as $key1 => $value1) { 552 if (!empty($value1['Targets']['TagTrackUID'][0]) && !empty($value1['SimpleTag'])) { 553 foreach ($value1['SimpleTag'] as $key2 => $value2) { 554 if (!empty($value2['TagName']) && isset($value2['TagString'])) { 555 $_STATISTICS_byTrackUID[$value1['Targets']['TagTrackUID'][0]][$value2['TagName']] = $value2['TagString']; 556 } 557 } 558 } 559 } 560 foreach (array('audio','video') as $avtype) { 561 if (!empty($info[$avtype]['streams'])) { 562 foreach ($info[$avtype]['streams'] as $trackUID => $trackdata) { 563 if (!isset($trackdata['bitrate']) && !empty($_STATISTICS_byTrackUID[$trackUID]['BPS'])) { 564 $info[$avtype]['streams'][$trackUID]['bitrate'] = (int) $_STATISTICS_byTrackUID[$trackUID]['BPS']; 565 @$info[$avtype]['bitrate'] += $info[$avtype]['streams'][$trackUID]['bitrate']; 566 } 567 } 568 } 569 } 570 } 571 572 return true; 573 } 574 575 /** 576 * @param array $info 577 */ 578 private function parseEBML(&$info) { 579 // http://www.matroska.org/technical/specs/index.html#EBMLBasics 580 $this->current_offset = $info['avdataoffset']; 581 582 while ($this->getEBMLelement($top_element, $info['avdataend'])) { 583 switch ($top_element['id']) { 584 585 case EBML_ID_EBML: 586 $info['matroska']['header']['offset'] = $top_element['offset']; 587 $info['matroska']['header']['length'] = $top_element['length']; 588 589 while ($this->getEBMLelement($element_data, $top_element['end'], true)) { 590 switch ($element_data['id']) { 591 592 case EBML_ID_EBMLVERSION: 593 case EBML_ID_EBMLREADVERSION: 594 case EBML_ID_EBMLMAXIDLENGTH: 595 case EBML_ID_EBMLMAXSIZELENGTH: 596 case EBML_ID_DOCTYPEVERSION: 597 case EBML_ID_DOCTYPEREADVERSION: 598 $element_data['data'] = getid3_lib::BigEndian2Int($element_data['data']); 599 break; 600 601 case EBML_ID_DOCTYPE: 602 $element_data['data'] = getid3_lib::trimNullByte($element_data['data']); 603 $info['matroska']['doctype'] = $element_data['data']; 604 $info['fileformat'] = $element_data['data']; 605 break; 606 607 default: 608 $this->unhandledElement('header', __LINE__, $element_data); 609 break; 610 } 611 612 unset($element_data['offset'], $element_data['end']); 613 $info['matroska']['header']['elements'][] = $element_data; 614 } 615 break; 616 617 case EBML_ID_SEGMENT: 618 $info['matroska']['segment'][0]['offset'] = $top_element['offset']; 619 $info['matroska']['segment'][0]['length'] = $top_element['length']; 620 621 while ($this->getEBMLelement($element_data, $top_element['end'])) { 622 if ($element_data['id'] != EBML_ID_CLUSTER || !$this->hide_clusters) { // collect clusters only if required 623 $info['matroska']['segments'][] = $element_data; 624 } 625 switch ($element_data['id']) { 626 627 case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements. 628 629 while ($this->getEBMLelement($seek_entry, $element_data['end'])) { 630 switch ($seek_entry['id']) { 631 632 case EBML_ID_SEEK: // Contains a single seek entry to an EBML element 633 while ($this->getEBMLelement($sub_seek_entry, $seek_entry['end'], true)) { 634 635 switch ($sub_seek_entry['id']) { 636 637 case EBML_ID_SEEKID: 638 $seek_entry['target_id'] = self::EBML2Int($sub_seek_entry['data']); 639 $seek_entry['target_name'] = self::EBMLidName($seek_entry['target_id']); 640 break; 641 642 case EBML_ID_SEEKPOSITION: 643 $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($sub_seek_entry['data']); 644 break; 645 646 default: 647 $this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry); } 648 break; 649 } 650 if (!isset($seek_entry['target_id'])) { 651 $this->warning('seek_entry[target_id] unexpectedly not set at '.$seek_entry['offset']); 652 break; 653 } 654 if (($seek_entry['target_id'] != EBML_ID_CLUSTER) || !$this->hide_clusters) { // collect clusters only if required 655 $info['matroska']['seek'][] = $seek_entry; 656 } 657 break; 658 659 default: 660 $this->unhandledElement('seekhead', __LINE__, $seek_entry); 661 break; 662 } 663 } 664 break; 665 666 case EBML_ID_TRACKS: // A top-level block of information with many tracks described. 667 $info['matroska']['tracks'] = $element_data; 668 669 while ($this->getEBMLelement($track_entry, $element_data['end'])) { 670 switch ($track_entry['id']) { 671 672 case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. 673 674 while ($this->getEBMLelement($subelement, $track_entry['end'], array(EBML_ID_VIDEO, EBML_ID_AUDIO, EBML_ID_CONTENTENCODINGS, EBML_ID_CODECPRIVATE))) { 675 switch ($subelement['id']) { 676 677 case EBML_ID_TRACKUID: 678 $track_entry[$subelement['id_name']] = getid3_lib::PrintHexBytes($subelement['data'], true, false); 679 break; 680 case EBML_ID_TRACKNUMBER: 681 case EBML_ID_TRACKTYPE: 682 case EBML_ID_MINCACHE: 683 case EBML_ID_MAXCACHE: 684 case EBML_ID_MAXBLOCKADDITIONID: 685 case EBML_ID_DEFAULTDURATION: // nanoseconds per frame 686 $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); 687 break; 688 689 case EBML_ID_TRACKTIMECODESCALE: 690 $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); 691 break; 692 693 case EBML_ID_CODECID: 694 case EBML_ID_LANGUAGE: 695 case EBML_ID_NAME: 696 case EBML_ID_CODECNAME: 697 $track_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); 698 break; 699 700 case EBML_ID_CODECPRIVATE: 701 $track_entry[$subelement['id_name']] = $this->readEBMLelementData($subelement['length'], true); 702 break; 703 704 case EBML_ID_FLAGENABLED: 705 case EBML_ID_FLAGDEFAULT: 706 case EBML_ID_FLAGFORCED: 707 case EBML_ID_FLAGLACING: 708 case EBML_ID_CODECDECODEALL: 709 $track_entry[$subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($subelement['data']); 710 break; 711 712 case EBML_ID_VIDEO: 713 714 while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { 715 switch ($sub_subelement['id']) { 716 717 case EBML_ID_PIXELWIDTH: 718 case EBML_ID_PIXELHEIGHT: 719 case EBML_ID_PIXELCROPBOTTOM: 720 case EBML_ID_PIXELCROPTOP: 721 case EBML_ID_PIXELCROPLEFT: 722 case EBML_ID_PIXELCROPRIGHT: 723 case EBML_ID_DISPLAYWIDTH: 724 case EBML_ID_DISPLAYHEIGHT: 725 case EBML_ID_DISPLAYUNIT: 726 case EBML_ID_ASPECTRATIOTYPE: 727 case EBML_ID_STEREOMODE: 728 case EBML_ID_OLDSTEREOMODE: 729 $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 730 break; 731 732 case EBML_ID_FLAGINTERLACED: 733 $track_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']); 734 break; 735 736 case EBML_ID_GAMMAVALUE: 737 $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); 738 break; 739 740 case EBML_ID_COLOURSPACE: 741 $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); 742 break; 743 744 default: 745 $this->unhandledElement('track.video', __LINE__, $sub_subelement); 746 break; 747 } 748 } 749 break; 750 751 case EBML_ID_AUDIO: 752 753 while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { 754 switch ($sub_subelement['id']) { 755 756 case EBML_ID_CHANNELS: 757 case EBML_ID_BITDEPTH: 758 $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 759 break; 760 761 case EBML_ID_SAMPLINGFREQUENCY: 762 case EBML_ID_OUTPUTSAMPLINGFREQUENCY: 763 $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); 764 break; 765 766 case EBML_ID_CHANNELPOSITIONS: 767 $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); 768 break; 769 770 default: 771 $this->unhandledElement('track.audio', __LINE__, $sub_subelement); 772 break; 773 } 774 } 775 break; 776 777 case EBML_ID_CONTENTENCODINGS: 778 779 while ($this->getEBMLelement($sub_subelement, $subelement['end'])) { 780 switch ($sub_subelement['id']) { 781 782 case EBML_ID_CONTENTENCODING: 783 784 while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CONTENTCOMPRESSION, EBML_ID_CONTENTENCRYPTION))) { 785 switch ($sub_sub_subelement['id']) { 786 787 case EBML_ID_CONTENTENCODINGORDER: 788 case EBML_ID_CONTENTENCODINGSCOPE: 789 case EBML_ID_CONTENTENCODINGTYPE: 790 $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 791 break; 792 793 case EBML_ID_CONTENTCOMPRESSION: 794 795 while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { 796 switch ($sub_sub_sub_subelement['id']) { 797 798 case EBML_ID_CONTENTCOMPALGO: 799 $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); 800 break; 801 802 case EBML_ID_CONTENTCOMPSETTINGS: 803 $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; 804 break; 805 806 default: 807 $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); 808 break; 809 } 810 } 811 break; 812 813 case EBML_ID_CONTENTENCRYPTION: 814 815 while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { 816 switch ($sub_sub_sub_subelement['id']) { 817 818 case EBML_ID_CONTENTENCALGO: 819 case EBML_ID_CONTENTSIGALGO: 820 case EBML_ID_CONTENTSIGHASHALGO: 821 $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); 822 break; 823 824 case EBML_ID_CONTENTENCKEYID: 825 case EBML_ID_CONTENTSIGNATURE: 826 case EBML_ID_CONTENTSIGKEYID: 827 $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; 828 break; 829 830 default: 831 $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); 832 break; 833 } 834 } 835 break; 836 837 default: 838 $this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement); 839 break; 840 } 841 } 842 break; 843 844 default: 845 $this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement); 846 break; 847 } 848 } 849 break; 850 851 default: 852 $this->unhandledElement('track', __LINE__, $subelement); 853 break; 854 } 855 } 856 857 $info['matroska']['tracks']['tracks'][] = $track_entry; 858 break; 859 860 default: 861 $this->unhandledElement('tracks', __LINE__, $track_entry); 862 break; 863 } 864 } 865 break; 866 867 case EBML_ID_INFO: // Contains miscellaneous general information and statistics on the file. 868 $info_entry = array(); 869 870 while ($this->getEBMLelement($subelement, $element_data['end'], true)) { 871 switch ($subelement['id']) { 872 873 case EBML_ID_TIMECODESCALE: 874 $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); 875 break; 876 877 case EBML_ID_DURATION: 878 $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); 879 break; 880 881 case EBML_ID_DATEUTC: 882 $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); 883 $info_entry[$subelement['id_name'].'_unix'] = self::EBMLdate2unix($info_entry[$subelement['id_name']]); 884 break; 885 886 case EBML_ID_SEGMENTUID: 887 case EBML_ID_PREVUID: 888 case EBML_ID_NEXTUID: 889 $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); 890 break; 891 892 case EBML_ID_SEGMENTFAMILY: 893 $info_entry[$subelement['id_name']][] = getid3_lib::trimNullByte($subelement['data']); 894 break; 895 896 case EBML_ID_SEGMENTFILENAME: 897 case EBML_ID_PREVFILENAME: 898 case EBML_ID_NEXTFILENAME: 899 case EBML_ID_TITLE: 900 case EBML_ID_MUXINGAPP: 901 case EBML_ID_WRITINGAPP: 902 $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); 903 $info['matroska']['comments'][strtolower($subelement['id_name'])][] = $info_entry[$subelement['id_name']]; 904 break; 905 906 case EBML_ID_CHAPTERTRANSLATE: 907 $chaptertranslate_entry = array(); 908 909 while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { 910 switch ($sub_subelement['id']) { 911 912 case EBML_ID_CHAPTERTRANSLATEEDITIONUID: 913 $chaptertranslate_entry[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data']); 914 break; 915 916 case EBML_ID_CHAPTERTRANSLATECODEC: 917 $chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 918 break; 919 920 case EBML_ID_CHAPTERTRANSLATEID: 921 $chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); 922 break; 923 924 default: 925 $this->unhandledElement('info.chaptertranslate', __LINE__, $sub_subelement); 926 break; 927 } 928 } 929 $info_entry[$subelement['id_name']] = $chaptertranslate_entry; 930 break; 931 932 default: 933 $this->unhandledElement('info', __LINE__, $subelement); 934 break; 935 } 936 } 937 $info['matroska']['info'][] = $info_entry; 938 break; 939 940 case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams. 941 if ($this->hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway 942 $this->current_offset = $element_data['end']; 943 break; 944 } 945 $cues_entry = array(); 946 947 while ($this->getEBMLelement($subelement, $element_data['end'])) { 948 switch ($subelement['id']) { 949 950 case EBML_ID_CUEPOINT: 951 $cuepoint_entry = array(); 952 953 while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CUETRACKPOSITIONS))) { 954 switch ($sub_subelement['id']) { 955 956 case EBML_ID_CUETRACKPOSITIONS: 957 $cuetrackpositions_entry = array(); 958 959 while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { 960 switch ($sub_sub_subelement['id']) { 961 962 case EBML_ID_CUETRACK: 963 case EBML_ID_CUECLUSTERPOSITION: 964 case EBML_ID_CUEBLOCKNUMBER: 965 case EBML_ID_CUECODECSTATE: 966 $cuetrackpositions_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 967 break; 968 969 default: 970 $this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement); 971 break; 972 } 973 } 974 $cuepoint_entry[$sub_subelement['id_name']][] = $cuetrackpositions_entry; 975 break; 976 977 case EBML_ID_CUETIME: 978 $cuepoint_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 979 break; 980 981 default: 982 $this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement); 983 break; 984 } 985 } 986 $cues_entry[] = $cuepoint_entry; 987 break; 988 989 default: 990 $this->unhandledElement('cues', __LINE__, $subelement); 991 break; 992 } 993 } 994 $info['matroska']['cues'] = $cues_entry; 995 break; 996 997 case EBML_ID_TAGS: // Element containing elements specific to Tracks/Chapters. 998 $tags_entry = array(); 999 1000 while ($this->getEBMLelement($subelement, $element_data['end'], false)) { 1001 switch ($subelement['id']) { 1002 1003 case EBML_ID_TAG: 1004 $tag_entry = array(); 1005 1006 while ($this->getEBMLelement($sub_subelement, $subelement['end'], false)) { 1007 switch ($sub_subelement['id']) { 1008 1009 case EBML_ID_TARGETS: 1010 $targets_entry = array(); 1011 1012 while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { 1013 switch ($sub_sub_subelement['id']) { 1014 1015 case EBML_ID_TARGETTYPEVALUE: 1016 $targets_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 1017 $targets_entry[strtolower($sub_sub_subelement['id_name']).'_long'] = self::TargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]); 1018 break; 1019 1020 case EBML_ID_TARGETTYPE: 1021 $targets_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; 1022 break; 1023 1024 case EBML_ID_TAGTRACKUID: 1025 case EBML_ID_TAGEDITIONUID: 1026 case EBML_ID_TAGCHAPTERUID: 1027 case EBML_ID_TAGATTACHMENTUID: 1028 $targets_entry[$sub_sub_subelement['id_name']][] = getid3_lib::PrintHexBytes($sub_sub_subelement['data'], true, false); 1029 break; 1030 1031 default: 1032 $this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement); 1033 break; 1034 } 1035 } 1036 $tag_entry[$sub_subelement['id_name']] = $targets_entry; 1037 break; 1038 1039 case EBML_ID_SIMPLETAG: 1040 $tag_entry[$sub_subelement['id_name']][] = $this->HandleEMBLSimpleTag($sub_subelement['end']); 1041 break; 1042 1043 default: 1044 $this->unhandledElement('tags.tag', __LINE__, $sub_subelement); 1045 break; 1046 } 1047 } 1048 $tags_entry[] = $tag_entry; 1049 break; 1050 1051 default: 1052 $this->unhandledElement('tags', __LINE__, $subelement); 1053 break; 1054 } 1055 } 1056 $info['matroska']['tags'] = $tags_entry; 1057 break; 1058 1059 case EBML_ID_ATTACHMENTS: // Contain attached files. 1060 1061 while ($this->getEBMLelement($subelement, $element_data['end'])) { 1062 switch ($subelement['id']) { 1063 1064 case EBML_ID_ATTACHEDFILE: 1065 $attachedfile_entry = array(); 1066 1067 while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_FILEDATA))) { 1068 switch ($sub_subelement['id']) { 1069 1070 case EBML_ID_FILEDESCRIPTION: 1071 case EBML_ID_FILENAME: 1072 case EBML_ID_FILEMIMETYPE: 1073 $attachedfile_entry[$sub_subelement['id_name']] = $sub_subelement['data']; 1074 break; 1075 1076 case EBML_ID_FILEDATA: 1077 $attachedfile_entry['data_offset'] = $this->current_offset; 1078 $attachedfile_entry['data_length'] = $sub_subelement['length']; 1079 1080 $attachedfile_entry[$sub_subelement['id_name']] = $this->saveAttachment( 1081 $attachedfile_entry['FileName'], 1082 $attachedfile_entry['data_offset'], 1083 $attachedfile_entry['data_length']); 1084 1085 $this->current_offset = $sub_subelement['end']; 1086 break; 1087 1088 case EBML_ID_FILEUID: 1089 $attachedfile_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 1090 break; 1091 1092 default: 1093 $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement); 1094 break; 1095 } 1096 } 1097 $info['matroska']['attachments'][] = $attachedfile_entry; 1098 break; 1099 1100 default: 1101 $this->unhandledElement('attachments', __LINE__, $subelement); 1102 break; 1103 } 1104 } 1105 break; 1106 1107 case EBML_ID_CHAPTERS: 1108 1109 while ($this->getEBMLelement($subelement, $element_data['end'])) { 1110 switch ($subelement['id']) { 1111 1112 case EBML_ID_EDITIONENTRY: 1113 $editionentry_entry = array(); 1114 1115 while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CHAPTERATOM))) { 1116 switch ($sub_subelement['id']) { 1117 1118 case EBML_ID_EDITIONUID: 1119 $editionentry_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 1120 break; 1121 1122 case EBML_ID_EDITIONFLAGHIDDEN: 1123 case EBML_ID_EDITIONFLAGDEFAULT: 1124 case EBML_ID_EDITIONFLAGORDERED: 1125 $editionentry_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']); 1126 break; 1127 1128 case EBML_ID_CHAPTERATOM: 1129 $chapteratom_entry = array(); 1130 1131 while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CHAPTERTRACK, EBML_ID_CHAPTERDISPLAY))) { 1132 switch ($sub_sub_subelement['id']) { 1133 1134 case EBML_ID_CHAPTERSEGMENTUID: 1135 case EBML_ID_CHAPTERSEGMENTEDITIONUID: 1136 $chapteratom_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; 1137 break; 1138 1139 case EBML_ID_CHAPTERFLAGENABLED: 1140 case EBML_ID_CHAPTERFLAGHIDDEN: 1141 $chapteratom_entry[$sub_sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 1142 break; 1143 1144 case EBML_ID_CHAPTERUID: 1145 case EBML_ID_CHAPTERTIMESTART: 1146 case EBML_ID_CHAPTERTIMEEND: 1147 $chapteratom_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); 1148 break; 1149 1150 case EBML_ID_CHAPTERTRACK: 1151 $chaptertrack_entry = array(); 1152 1153 while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { 1154 switch ($sub_sub_sub_subelement['id']) { 1155 1156 case EBML_ID_CHAPTERTRACKNUMBER: 1157 $chaptertrack_entry[$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); 1158 break; 1159 1160 default: 1161 $this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement); 1162 break; 1163 } 1164 } 1165 $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry; 1166 break; 1167 1168 case EBML_ID_CHAPTERDISPLAY: 1169 $chapterdisplay_entry = array(); 1170 1171 while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { 1172 switch ($sub_sub_sub_subelement['id']) { 1173 1174 case EBML_ID_CHAPSTRING: 1175 case EBML_ID_CHAPLANGUAGE: 1176 case EBML_ID_CHAPCOUNTRY: 1177 $chapterdisplay_entry[$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; 1178 break; 1179 1180 default: 1181 $this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement); 1182 break; 1183 } 1184 } 1185 $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry; 1186 break; 1187 1188 default: 1189 $this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement); 1190 break; 1191 } 1192 } 1193 $editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry; 1194 break; 1195 1196 default: 1197 $this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement); 1198 break; 1199 } 1200 } 1201 $info['matroska']['chapters'][] = $editionentry_entry; 1202 break; 1203 1204 default: 1205 $this->unhandledElement('chapters', __LINE__, $subelement); 1206 break; 1207 } 1208 } 1209 break; 1210 1211 case EBML_ID_CLUSTER: // The lower level element containing the (monolithic) Block structure. 1212 $cluster_entry = array(); 1213 1214 while ($this->getEBMLelement($subelement, $element_data['end'], array(EBML_ID_CLUSTERSILENTTRACKS, EBML_ID_CLUSTERBLOCKGROUP, EBML_ID_CLUSTERSIMPLEBLOCK))) { 1215 switch ($subelement['id']) { 1216 1217 case EBML_ID_CLUSTERTIMECODE: 1218 case EBML_ID_CLUSTERPOSITION: 1219 case EBML_ID_CLUSTERPREVSIZE: 1220 $cluster_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); 1221 break; 1222 1223 case EBML_ID_CLUSTERSILENTTRACKS: 1224 $cluster_silent_tracks = array(); 1225 1226 while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { 1227 switch ($sub_subelement['id']) { 1228 1229 case EBML_ID_CLUSTERSILENTTRACKNUMBER: 1230 $cluster_silent_tracks[] = getid3_lib::BigEndian2Int($sub_subelement['data']); 1231 break; 1232 1233 default: 1234 $this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement); 1235 break; 1236 } 1237 } 1238 $cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks; 1239 break; 1240 1241 case EBML_ID_CLUSTERBLOCKGROUP: 1242 $cluster_block_group = array('offset' => $this->current_offset); 1243 1244 while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CLUSTERBLOCK))) { 1245 switch ($sub_subelement['id']) { 1246 1247 case EBML_ID_CLUSTERBLOCK: 1248 $cluster_block_group[$sub_subelement['id_name']] = $this->HandleEMBLClusterBlock($sub_subelement, EBML_ID_CLUSTERBLOCK, $info); 1249 break; 1250 1251 case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int 1252 case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int 1253 $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); 1254 break; 1255 1256 case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int 1257 $cluster_block_group[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data'], false, true); 1258 break; 1259 1260 case EBML_ID_CLUSTERCODECSTATE: 1261 $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); 1262 break; 1263 1264 default: 1265 $this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement); 1266 break; 1267 } 1268 } 1269 $cluster_entry[$subelement['id_name']][] = $cluster_block_group; 1270 break; 1271 1272 case EBML_ID_CLUSTERSIMPLEBLOCK: 1273 $cluster_entry[$subelement['id_name']][] = $this->HandleEMBLClusterBlock($subelement, EBML_ID_CLUSTERSIMPLEBLOCK, $info); 1274 break; 1275 1276 default: 1277 $this->unhandledElement('cluster', __LINE__, $subelement); 1278 break; 1279 } 1280 $this->current_offset = $subelement['end']; 1281 } 1282 1283 if (!$this->hide_clusters || $this->playtimeFromMetadata($info) === false) { 1284 $info['matroska']['cluster'][] = $cluster_entry; 1285 } 1286 if ($this->scan_mode === MATROSKA_SCAN_FIRST_CLUSTER) { 1287 // Stop parsing after finding first cluster 1288 return; 1289 } 1290 1291 // check to see if all the data we need exists already, if so, break out of the loop 1292 if ($this->scan_mode === MATROSKA_SCAN_HEADER) { 1293 if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { 1294 if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { 1295 if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) { 1296 return; 1297 } 1298 } 1299 } 1300 } 1301 break; 1302 1303 default: 1304 $this->unhandledElement('segment', __LINE__, $element_data); 1305 break; 1306 } 1307 } 1308 break; 1309 1310 default: 1311 $this->unhandledElement('root', __LINE__, $top_element); 1312 break; 1313 } 1314 } 1315 } 1316 1317 /** 1318 * @param int $min_data 1319 * 1320 * @return bool 1321 */ 1322 private function EnsureBufferHasEnoughData($min_data=1024) { 1323 if (($this->current_offset - $this->EBMLbuffer_offset) >= ($this->EBMLbuffer_length - $min_data)) { 1324 $read_bytes = max($min_data, $this->getid3->fread_buffer_size()); 1325 1326 try { 1327 $this->fseek($this->current_offset); 1328 $this->EBMLbuffer_offset = $this->current_offset; 1329 $this->EBMLbuffer = $this->fread($read_bytes); 1330 $this->EBMLbuffer_length = strlen($this->EBMLbuffer); 1331 } catch (getid3_exception $e) { 1332 $this->warning('EBML parser: '.$e->getMessage()); 1333 return false; 1334 } 1335 1336 if ($this->EBMLbuffer_length == 0 && $this->feof()) { 1337 return $this->error('EBML parser: ran out of file at offset '.$this->current_offset); 1338 } 1339 } 1340 return true; 1341 } 1342 1343 /** 1344 * @return int|float|false 1345 */ 1346 private function readEBMLint() { 1347 $actual_offset = $this->current_offset - $this->EBMLbuffer_offset; 1348 1349 // get length of integer 1350 $first_byte_int = ord($this->EBMLbuffer[$actual_offset]); 1351 if (0x80 & $first_byte_int) { 1352 $length = 1; 1353 } elseif (0x40 & $first_byte_int) { 1354 $length = 2; 1355 } elseif (0x20 & $first_byte_int) { 1356 $length = 3; 1357 } elseif (0x10 & $first_byte_int) { 1358 $length = 4; 1359 } elseif (0x08 & $first_byte_int) { 1360 $length = 5; 1361 } elseif (0x04 & $first_byte_int) { 1362 $length = 6; 1363 } elseif (0x02 & $first_byte_int) { 1364 $length = 7; 1365 } elseif (0x01 & $first_byte_int) { 1366 $length = 8; 1367 } else { 1368 throw new Exception('invalid EBML integer (leading 0x00) at '.$this->current_offset); 1369 } 1370 1371 // read 1372 $int_value = self::EBML2Int(substr($this->EBMLbuffer, $actual_offset, $length)); 1373 $this->current_offset += $length; 1374 1375 return $int_value; 1376 } 1377 1378 /** 1379 * @param int $length 1380 * @param bool $check_buffer 1381 * 1382 * @return string|false 1383 */ 1384 private function readEBMLelementData($length, $check_buffer=false) { 1385 if ($check_buffer && !$this->EnsureBufferHasEnoughData($length)) { 1386 return false; 1387 } 1388 $data = substr($this->EBMLbuffer, $this->current_offset - $this->EBMLbuffer_offset, $length); 1389 $this->current_offset += $length; 1390 return $data; 1391 } 1392 1393 /** 1394 * @param array $element 1395 * @param int $parent_end 1396 * @param array|bool $get_data 1397 * 1398 * @return bool 1399 */ 1400 private function getEBMLelement(&$element, $parent_end, $get_data=false) { 1401 if ($this->current_offset >= $parent_end) { 1402 return false; 1403 } 1404 1405 if (!$this->EnsureBufferHasEnoughData()) { 1406 $this->current_offset = PHP_INT_MAX; // do not exit parser right now, allow to finish current loop to gather maximum information 1407 return false; 1408 } 1409 1410 $element = array(); 1411 1412 // set offset 1413 $element['offset'] = $this->current_offset; 1414 1415 // get ID 1416 $element['id'] = $this->readEBMLint(); 1417 1418 // get name 1419 $element['id_name'] = self::EBMLidName($element['id']); 1420 1421 // get length 1422 $element['length'] = $this->readEBMLint(); 1423 1424 // get end offset 1425 $element['end'] = $this->current_offset + $element['length']; 1426 1427 // get raw data 1428 $dont_parse = (in_array($element['id'], $this->unuseful_elements) || $element['id_name'] == dechex($element['id'])); 1429 if (($get_data === true || (is_array($get_data) && !in_array($element['id'], $get_data))) && !$dont_parse) { 1430 $element['data'] = $this->readEBMLelementData($element['length'], $element); 1431 } 1432 1433 return true; 1434 } 1435 1436 /** 1437 * @param string $type 1438 * @param int $line 1439 * @param array $element 1440 */ 1441 private function unhandledElement($type, $line, $element) { 1442 // warn only about unknown and missed elements, not about unuseful 1443 if (!in_array($element['id'], $this->unuseful_elements)) { 1444 $this->warning('Unhandled '.$type.' element ['.basename(__FILE__).':'.$line.'] ('.$element['id'].'::'.$element['id_name'].' ['.$element['length'].' bytes]) at '.$element['offset']); 1445 } 1446 1447 // increase offset for unparsed elements 1448 if (!isset($element['data'])) { 1449 $this->current_offset = $element['end']; 1450 } 1451 } 1452 1453 /** 1454 * @param array $SimpleTagArray 1455 * 1456 * @return bool 1457 */ 1458 private function ExtractCommentsSimpleTag($SimpleTagArray) { 1459 if (!empty($SimpleTagArray['SimpleTag'])) { 1460 foreach ($SimpleTagArray['SimpleTag'] as $SimpleTagKey => $SimpleTagData) { 1461 if (!empty($SimpleTagData['TagName']) && !empty($SimpleTagData['TagString'])) { 1462 $this->getid3->info['matroska']['comments'][strtolower($SimpleTagData['TagName'])][] = $SimpleTagData['TagString']; 1463 } 1464 if (!empty($SimpleTagData['SimpleTag'])) { 1465 $this->ExtractCommentsSimpleTag($SimpleTagData); 1466 } 1467 } 1468 } 1469 1470 return true; 1471 } 1472 1473 /** 1474 * @param int $parent_end 1475 * 1476 * @return array 1477 */ 1478 private function HandleEMBLSimpleTag($parent_end) { 1479 $simpletag_entry = array(); 1480 1481 while ($this->getEBMLelement($element, $parent_end, array(EBML_ID_SIMPLETAG))) { 1482 switch ($element['id']) { 1483 1484 case EBML_ID_TAGNAME: 1485 case EBML_ID_TAGLANGUAGE: 1486 case EBML_ID_TAGSTRING: 1487 case EBML_ID_TAGBINARY: 1488 $simpletag_entry[$element['id_name']] = $element['data']; 1489 break; 1490 1491 case EBML_ID_SIMPLETAG: 1492 $simpletag_entry[$element['id_name']][] = $this->HandleEMBLSimpleTag($element['end']); 1493 break; 1494 1495 case EBML_ID_TAGDEFAULT: 1496 $simpletag_entry[$element['id_name']] = (bool)getid3_lib::BigEndian2Int($element['data']); 1497 break; 1498 1499 default: 1500 $this->unhandledElement('tag.simpletag', __LINE__, $element); 1501 break; 1502 } 1503 } 1504 1505 return $simpletag_entry; 1506 } 1507 1508 /** 1509 * @param array $element 1510 * @param int $block_type 1511 * @param array $info 1512 * 1513 * @return array 1514 */ 1515 private function HandleEMBLClusterBlock($element, $block_type, &$info) { 1516 // http://www.matroska.org/technical/specs/index.html#block_structure 1517 // http://www.matroska.org/technical/specs/index.html#simpleblock_structure 1518 1519 $block_data = array(); 1520 $block_data['tracknumber'] = $this->readEBMLint(); 1521 $block_data['timecode'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(2), false, true); 1522 $block_data['flags_raw'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); 1523 1524 if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { 1525 $block_data['flags']['keyframe'] = (($block_data['flags_raw'] & 0x80) >> 7); 1526 //$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0x70) >> 4); 1527 } 1528 else { 1529 //$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0xF0) >> 4); 1530 } 1531 $block_data['flags']['invisible'] = (bool)(($block_data['flags_raw'] & 0x08) >> 3); 1532 $block_data['flags']['lacing'] = (($block_data['flags_raw'] & 0x06) >> 1); // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing 1533 if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { 1534 $block_data['flags']['discardable'] = (($block_data['flags_raw'] & 0x01)); 1535 } 1536 else { 1537 //$block_data['flags']['reserved2'] = (($block_data['flags_raw'] & 0x01) >> 0); 1538 } 1539 $block_data['flags']['lacing_type'] = self::BlockLacingType($block_data['flags']['lacing']); 1540 1541 // Lace (when lacing bit is set) 1542 if ($block_data['flags']['lacing'] > 0) { 1543 $block_data['lace_frames'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)) + 1; // Number of frames in the lace-1 (uint8) 1544 if ($block_data['flags']['lacing'] != 0x02) { 1545 for ($i = 1; $i < $block_data['lace_frames']; $i ++) { // 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). 1546 if ($block_data['flags']['lacing'] == 0x03) { // EBML lacing 1547 $block_data['lace_frames_size'][$i] = $this->readEBMLint(); // TODO: read size correctly, calc size for the last frame. For now offsets are deteminded OK with readEBMLint() and that's the most important thing. 1548 } 1549 else { // Xiph lacing 1550 $block_data['lace_frames_size'][$i] = 0; 1551 do { 1552 $size = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); 1553 $block_data['lace_frames_size'][$i] += $size; 1554 } 1555 while ($size == 255); 1556 } 1557 } 1558 if ($block_data['flags']['lacing'] == 0x01) { // calc size of the last frame only for Xiph lacing, till EBML sizes are now anyway determined incorrectly 1559 $block_data['lace_frames_size'][] = $element['end'] - $this->current_offset - array_sum($block_data['lace_frames_size']); 1560 } 1561 } 1562 } 1563 1564 if (!isset($info['matroska']['track_data_offsets'][$block_data['tracknumber']])) { 1565 $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['offset'] = $this->current_offset; 1566 $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length'] = $element['end'] - $this->current_offset; 1567 //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] = 0; 1568 } 1569 //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] += $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length']; 1570 //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['duration'] = $block_data['timecode'] * ((isset($info['matroska']['info'][0]['TimecodeScale']) ? $info['matroska']['info'][0]['TimecodeScale'] : 1000000) / 1000000000); 1571 1572 // set offset manually 1573 $this->current_offset = $element['end']; 1574 1575 return $block_data; 1576 } 1577 1578 /** 1579 * @param string $EBMLstring 1580 * 1581 * @return int|float|false 1582 */ 1583 private static function EBML2Int($EBMLstring) { 1584 // http://matroska.org/specs/ 1585 1586 // Element ID coded with an UTF-8 like system: 1587 // 1xxx xxxx - Class A IDs (2^7 -2 possible values) (base 0x8X) 1588 // 01xx xxxx xxxx xxxx - Class B IDs (2^14-2 possible values) (base 0x4X 0xXX) 1589 // 001x xxxx xxxx xxxx xxxx xxxx - Class C IDs (2^21-2 possible values) (base 0x2X 0xXX 0xXX) 1590 // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - Class D IDs (2^28-2 possible values) (base 0x1X 0xXX 0xXX 0xXX) 1591 // Values with all x at 0 and 1 are reserved (hence the -2). 1592 1593 // Data size, in octets, is also coded with an UTF-8 like system : 1594 // 1xxx xxxx - value 0 to 2^7-2 1595 // 01xx xxxx xxxx xxxx - value 0 to 2^14-2 1596 // 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2 1597 // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2 1598 // 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2 1599 // 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2 1600 // 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 1601 // 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 1602 1603 $first_byte_int = ord($EBMLstring[0]); 1604 if (0x80 & $first_byte_int) { 1605 $EBMLstring[0] = chr($first_byte_int & 0x7F); 1606 } elseif (0x40 & $first_byte_int) { 1607 $EBMLstring[0] = chr($first_byte_int & 0x3F); 1608 } elseif (0x20 & $first_byte_int) { 1609 $EBMLstring[0] = chr($first_byte_int & 0x1F); 1610 } elseif (0x10 & $first_byte_int) { 1611 $EBMLstring[0] = chr($first_byte_int & 0x0F); 1612 } elseif (0x08 & $first_byte_int) { 1613 $EBMLstring[0] = chr($first_byte_int & 0x07); 1614 } elseif (0x04 & $first_byte_int) { 1615 $EBMLstring[0] = chr($first_byte_int & 0x03); 1616 } elseif (0x02 & $first_byte_int) { 1617 $EBMLstring[0] = chr($first_byte_int & 0x01); 1618 } elseif (0x01 & $first_byte_int) { 1619 $EBMLstring[0] = chr($first_byte_int & 0x00); 1620 } 1621 1622 return getid3_lib::BigEndian2Int($EBMLstring); 1623 } 1624 1625 /** 1626 * @param int $EBMLdatestamp 1627 * 1628 * @return float 1629 */ 1630 private static function EBMLdate2unix($EBMLdatestamp) { 1631 // Date - signed 8 octets integer in nanoseconds with 0 indicating the precise beginning of the millennium (at 2001-01-01T00:00:00,000000000 UTC) 1632 // 978307200 == mktime(0, 0, 0, 1, 1, 2001) == January 1, 2001 12:00:00am UTC 1633 return round(($EBMLdatestamp / 1000000000) + 978307200); 1634 } 1635 1636 /** 1637 * @param int $target_type 1638 * 1639 * @return string|int 1640 */ 1641 public static function TargetTypeValue($target_type) { 1642 // http://www.matroska.org/technical/specs/tagging/index.html 1643 static $TargetTypeValue = array(); 1644 if (empty($TargetTypeValue)) { 1645 $TargetTypeValue[10] = 'A: ~ V:shot'; // the lowest hierarchy found in music or movies 1646 $TargetTypeValue[20] = 'A:subtrack/part/movement ~ V:scene'; // corresponds to parts of a track for audio (like a movement) 1647 $TargetTypeValue[30] = 'A:track/song ~ V:chapter'; // the common parts of an album or a movie 1648 $TargetTypeValue[40] = 'A:part/session ~ V:part/session'; // when an album or episode has different logical parts 1649 $TargetTypeValue[50] = 'A:album/opera/concert ~ V:movie/episode/concert'; // the most common grouping level of music and video (equals to an episode for TV series) 1650 $TargetTypeValue[60] = 'A:edition/issue/volume/opus ~ V:season/sequel/volume'; // a list of lower levels grouped together 1651 $TargetTypeValue[70] = 'A:collection ~ V:collection'; // the high hierarchy consisting of many different lower items 1652 } 1653 return (isset($TargetTypeValue[$target_type]) ? $TargetTypeValue[$target_type] : $target_type); 1654 } 1655 1656 /** 1657 * @param int $lacingtype 1658 * 1659 * @return string|int 1660 */ 1661 public static function BlockLacingType($lacingtype) { 1662 // http://matroska.org/technical/specs/index.html#block_structure 1663 static $BlockLacingType = array(); 1664 if (empty($BlockLacingType)) { 1665 $BlockLacingType[0x00] = 'no lacing'; 1666 $BlockLacingType[0x01] = 'Xiph lacing'; 1667 $BlockLacingType[0x02] = 'fixed-size lacing'; 1668 $BlockLacingType[0x03] = 'EBML lacing'; 1669 } 1670 return (isset($BlockLacingType[$lacingtype]) ? $BlockLacingType[$lacingtype] : $lacingtype); 1671 } 1672 1673 /** 1674 * @param string $codecid 1675 * 1676 * @return string 1677 */ 1678 public static function CodecIDtoCommonName($codecid) { 1679 // http://www.matroska.org/technical/specs/codecid/index.html 1680 static $CodecIDlist = array(); 1681 if (empty($CodecIDlist)) { 1682 $CodecIDlist['A_AAC'] = 'aac'; 1683 $CodecIDlist['A_AAC/MPEG2/LC'] = 'aac'; 1684 $CodecIDlist['A_AC3'] = 'ac3'; 1685 $CodecIDlist['A_EAC3'] = 'eac3'; 1686 $CodecIDlist['A_DTS'] = 'dts'; 1687 $CodecIDlist['A_FLAC'] = 'flac'; 1688 $CodecIDlist['A_MPEG/L1'] = 'mp1'; 1689 $CodecIDlist['A_MPEG/L2'] = 'mp2'; 1690 $CodecIDlist['A_MPEG/L3'] = 'mp3'; 1691 $CodecIDlist['A_PCM/INT/LIT'] = 'pcm'; // PCM Integer Little Endian 1692 $CodecIDlist['A_PCM/INT/BIG'] = 'pcm'; // PCM Integer Big Endian 1693 $CodecIDlist['A_QUICKTIME/QDMC'] = 'quicktime'; // Quicktime: QDesign Music 1694 $CodecIDlist['A_QUICKTIME/QDM2'] = 'quicktime'; // Quicktime: QDesign Music v2 1695 $CodecIDlist['A_VORBIS'] = 'vorbis'; 1696 $CodecIDlist['V_MPEG1'] = 'mpeg'; 1697 $CodecIDlist['V_THEORA'] = 'theora'; 1698 $CodecIDlist['V_REAL/RV40'] = 'real'; 1699 $CodecIDlist['V_REAL/RV10'] = 'real'; 1700 $CodecIDlist['V_REAL/RV20'] = 'real'; 1701 $CodecIDlist['V_REAL/RV30'] = 'real'; 1702 $CodecIDlist['V_QUICKTIME'] = 'quicktime'; // Quicktime 1703 $CodecIDlist['V_MPEG4/ISO/AP'] = 'mpeg4'; 1704 $CodecIDlist['V_MPEG4/ISO/ASP'] = 'mpeg4'; 1705 $CodecIDlist['V_MPEG4/ISO/AVC'] = 'h264'; 1706 $CodecIDlist['V_MPEG4/ISO/SP'] = 'mpeg4'; 1707 $CodecIDlist['V_VP8'] = 'vp8'; 1708 $CodecIDlist['V_MS/VFW/FOURCC'] = 'vcm'; // Microsoft (TM) Video Codec Manager (VCM) 1709 $CodecIDlist['A_MS/ACM'] = 'acm'; // Microsoft (TM) Audio Codec Manager (ACM) 1710 } 1711 return (isset($CodecIDlist[$codecid]) ? $CodecIDlist[$codecid] : $codecid); 1712 } 1713 1714 /** 1715 * @param int $value 1716 * 1717 * @return string 1718 */ 1719 private static function EBMLidName($value) { 1720 static $EBMLidList = array(); 1721 if (empty($EBMLidList)) { 1722 $EBMLidList[EBML_ID_ASPECTRATIOTYPE] = 'AspectRatioType'; 1723 $EBMLidList[EBML_ID_ATTACHEDFILE] = 'AttachedFile'; 1724 $EBMLidList[EBML_ID_ATTACHMENTLINK] = 'AttachmentLink'; 1725 $EBMLidList[EBML_ID_ATTACHMENTS] = 'Attachments'; 1726 $EBMLidList[EBML_ID_AUDIO] = 'Audio'; 1727 $EBMLidList[EBML_ID_BITDEPTH] = 'BitDepth'; 1728 $EBMLidList[EBML_ID_CHANNELPOSITIONS] = 'ChannelPositions'; 1729 $EBMLidList[EBML_ID_CHANNELS] = 'Channels'; 1730 $EBMLidList[EBML_ID_CHAPCOUNTRY] = 'ChapCountry'; 1731 $EBMLidList[EBML_ID_CHAPLANGUAGE] = 'ChapLanguage'; 1732 $EBMLidList[EBML_ID_CHAPPROCESS] = 'ChapProcess'; 1733 $EBMLidList[EBML_ID_CHAPPROCESSCODECID] = 'ChapProcessCodecID'; 1734 $EBMLidList[EBML_ID_CHAPPROCESSCOMMAND] = 'ChapProcessCommand'; 1735 $EBMLidList[EBML_ID_CHAPPROCESSDATA] = 'ChapProcessData'; 1736 $EBMLidList[EBML_ID_CHAPPROCESSPRIVATE] = 'ChapProcessPrivate'; 1737 $EBMLidList[EBML_ID_CHAPPROCESSTIME] = 'ChapProcessTime'; 1738 $EBMLidList[EBML_ID_CHAPSTRING] = 'ChapString'; 1739 $EBMLidList[EBML_ID_CHAPTERATOM] = 'ChapterAtom'; 1740 $EBMLidList[EBML_ID_CHAPTERDISPLAY] = 'ChapterDisplay'; 1741 $EBMLidList[EBML_ID_CHAPTERFLAGENABLED] = 'ChapterFlagEnabled'; 1742 $EBMLidList[EBML_ID_CHAPTERFLAGHIDDEN] = 'ChapterFlagHidden'; 1743 $EBMLidList[EBML_ID_CHAPTERPHYSICALEQUIV] = 'ChapterPhysicalEquiv'; 1744 $EBMLidList[EBML_ID_CHAPTERS] = 'Chapters'; 1745 $EBMLidList[EBML_ID_CHAPTERSEGMENTEDITIONUID] = 'ChapterSegmentEditionUID'; 1746 $EBMLidList[EBML_ID_CHAPTERSEGMENTUID] = 'ChapterSegmentUID'; 1747 $EBMLidList[EBML_ID_CHAPTERTIMEEND] = 'ChapterTimeEnd'; 1748 $EBMLidList[EBML_ID_CHAPTERTIMESTART] = 'ChapterTimeStart'; 1749 $EBMLidList[EBML_ID_CHAPTERTRACK] = 'ChapterTrack'; 1750 $EBMLidList[EBML_ID_CHAPTERTRACKNUMBER] = 'ChapterTrackNumber'; 1751 $EBMLidList[EBML_ID_CHAPTERTRANSLATE] = 'ChapterTranslate'; 1752 $EBMLidList[EBML_ID_CHAPTERTRANSLATECODEC] = 'ChapterTranslateCodec'; 1753 $EBMLidList[EBML_ID_CHAPTERTRANSLATEEDITIONUID] = 'ChapterTranslateEditionUID'; 1754 $EBMLidList[EBML_ID_CHAPTERTRANSLATEID] = 'ChapterTranslateID'; 1755 $EBMLidList[EBML_ID_CHAPTERUID] = 'ChapterUID'; 1756 $EBMLidList[EBML_ID_CLUSTER] = 'Cluster'; 1757 $EBMLidList[EBML_ID_CLUSTERBLOCK] = 'ClusterBlock'; 1758 $EBMLidList[EBML_ID_CLUSTERBLOCKADDID] = 'ClusterBlockAddID'; 1759 $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONAL] = 'ClusterBlockAdditional'; 1760 $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONID] = 'ClusterBlockAdditionID'; 1761 $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONS] = 'ClusterBlockAdditions'; 1762 $EBMLidList[EBML_ID_CLUSTERBLOCKDURATION] = 'ClusterBlockDuration'; 1763 $EBMLidList[EBML_ID_CLUSTERBLOCKGROUP] = 'ClusterBlockGroup'; 1764 $EBMLidList[EBML_ID_CLUSTERBLOCKMORE] = 'ClusterBlockMore'; 1765 $EBMLidList[EBML_ID_CLUSTERBLOCKVIRTUAL] = 'ClusterBlockVirtual'; 1766 $EBMLidList[EBML_ID_CLUSTERCODECSTATE] = 'ClusterCodecState'; 1767 $EBMLidList[EBML_ID_CLUSTERDELAY] = 'ClusterDelay'; 1768 $EBMLidList[EBML_ID_CLUSTERDURATION] = 'ClusterDuration'; 1769 $EBMLidList[EBML_ID_CLUSTERENCRYPTEDBLOCK] = 'ClusterEncryptedBlock'; 1770 $EBMLidList[EBML_ID_CLUSTERFRAMENUMBER] = 'ClusterFrameNumber'; 1771 $EBMLidList[EBML_ID_CLUSTERLACENUMBER] = 'ClusterLaceNumber'; 1772 $EBMLidList[EBML_ID_CLUSTERPOSITION] = 'ClusterPosition'; 1773 $EBMLidList[EBML_ID_CLUSTERPREVSIZE] = 'ClusterPrevSize'; 1774 $EBMLidList[EBML_ID_CLUSTERREFERENCEBLOCK] = 'ClusterReferenceBlock'; 1775 $EBMLidList[EBML_ID_CLUSTERREFERENCEPRIORITY] = 'ClusterReferencePriority'; 1776 $EBMLidList[EBML_ID_CLUSTERREFERENCEVIRTUAL] = 'ClusterReferenceVirtual'; 1777 $EBMLidList[EBML_ID_CLUSTERSILENTTRACKNUMBER] = 'ClusterSilentTrackNumber'; 1778 $EBMLidList[EBML_ID_CLUSTERSILENTTRACKS] = 'ClusterSilentTracks'; 1779 $EBMLidList[EBML_ID_CLUSTERSIMPLEBLOCK] = 'ClusterSimpleBlock'; 1780 $EBMLidList[EBML_ID_CLUSTERTIMECODE] = 'ClusterTimecode'; 1781 $EBMLidList[EBML_ID_CLUSTERTIMESLICE] = 'ClusterTimeSlice'; 1782 $EBMLidList[EBML_ID_CODECDECODEALL] = 'CodecDecodeAll'; 1783 $EBMLidList[EBML_ID_CODECDOWNLOADURL] = 'CodecDownloadURL'; 1784 $EBMLidList[EBML_ID_CODECID] = 'CodecID'; 1785 $EBMLidList[EBML_ID_CODECINFOURL] = 'CodecInfoURL'; 1786 $EBMLidList[EBML_ID_CODECNAME] = 'CodecName'; 1787 $EBMLidList[EBML_ID_CODECPRIVATE] = 'CodecPrivate'; 1788 $EBMLidList[EBML_ID_CODECSETTINGS] = 'CodecSettings'; 1789 $EBMLidList[EBML_ID_COLOURSPACE] = 'ColourSpace'; 1790 $EBMLidList[EBML_ID_CONTENTCOMPALGO] = 'ContentCompAlgo'; 1791 $EBMLidList[EBML_ID_CONTENTCOMPRESSION] = 'ContentCompression'; 1792 $EBMLidList[EBML_ID_CONTENTCOMPSETTINGS] = 'ContentCompSettings'; 1793 $EBMLidList[EBML_ID_CONTENTENCALGO] = 'ContentEncAlgo'; 1794 $EBMLidList[EBML_ID_CONTENTENCKEYID] = 'ContentEncKeyID'; 1795 $EBMLidList[EBML_ID_CONTENTENCODING] = 'ContentEncoding'; 1796 $EBMLidList[EBML_ID_CONTENTENCODINGORDER] = 'ContentEncodingOrder'; 1797 $EBMLidList[EBML_ID_CONTENTENCODINGS] = 'ContentEncodings'; 1798 $EBMLidList[EBML_ID_CONTENTENCODINGSCOPE] = 'ContentEncodingScope'; 1799 $EBMLidList[EBML_ID_CONTENTENCODINGTYPE] = 'ContentEncodingType'; 1800 $EBMLidList[EBML_ID_CONTENTENCRYPTION] = 'ContentEncryption'; 1801 $EBMLidList[EBML_ID_CONTENTSIGALGO] = 'ContentSigAlgo'; 1802 $EBMLidList[EBML_ID_CONTENTSIGHASHALGO] = 'ContentSigHashAlgo'; 1803 $EBMLidList[EBML_ID_CONTENTSIGKEYID] = 'ContentSigKeyID'; 1804 $EBMLidList[EBML_ID_CONTENTSIGNATURE] = 'ContentSignature'; 1805 $EBMLidList[EBML_ID_CRC32] = 'CRC32'; 1806 $EBMLidList[EBML_ID_CUEBLOCKNUMBER] = 'CueBlockNumber'; 1807 $EBMLidList[EBML_ID_CUECLUSTERPOSITION] = 'CueClusterPosition'; 1808 $EBMLidList[EBML_ID_CUECODECSTATE] = 'CueCodecState'; 1809 $EBMLidList[EBML_ID_CUEPOINT] = 'CuePoint'; 1810 $EBMLidList[EBML_ID_CUEREFCLUSTER] = 'CueRefCluster'; 1811 $EBMLidList[EBML_ID_CUEREFCODECSTATE] = 'CueRefCodecState'; 1812 $EBMLidList[EBML_ID_CUEREFERENCE] = 'CueReference'; 1813 $EBMLidList[EBML_ID_CUEREFNUMBER] = 'CueRefNumber'; 1814 $EBMLidList[EBML_ID_CUEREFTIME] = 'CueRefTime'; 1815 $EBMLidList[EBML_ID_CUES] = 'Cues'; 1816 $EBMLidList[EBML_ID_CUETIME] = 'CueTime'; 1817 $EBMLidList[EBML_ID_CUETRACK] = 'CueTrack'; 1818 $EBMLidList[EBML_ID_CUETRACKPOSITIONS] = 'CueTrackPositions'; 1819 $EBMLidList[EBML_ID_DATEUTC] = 'DateUTC'; 1820 $EBMLidList[EBML_ID_DEFAULTDURATION] = 'DefaultDuration'; 1821 $EBMLidList[EBML_ID_DISPLAYHEIGHT] = 'DisplayHeight'; 1822 $EBMLidList[EBML_ID_DISPLAYUNIT] = 'DisplayUnit'; 1823 $EBMLidList[EBML_ID_DISPLAYWIDTH] = 'DisplayWidth'; 1824 $EBMLidList[EBML_ID_DOCTYPE] = 'DocType'; 1825 $EBMLidList[EBML_ID_DOCTYPEREADVERSION] = 'DocTypeReadVersion'; 1826 $EBMLidList[EBML_ID_DOCTYPEVERSION] = 'DocTypeVersion'; 1827 $EBMLidList[EBML_ID_DURATION] = 'Duration'; 1828 $EBMLidList[EBML_ID_EBML] = 'EBML'; 1829 $EBMLidList[EBML_ID_EBMLMAXIDLENGTH] = 'EBMLMaxIDLength'; 1830 $EBMLidList[EBML_ID_EBMLMAXSIZELENGTH] = 'EBMLMaxSizeLength'; 1831 $EBMLidList[EBML_ID_EBMLREADVERSION] = 'EBMLReadVersion'; 1832 $EBMLidList[EBML_ID_EBMLVERSION] = 'EBMLVersion'; 1833 $EBMLidList[EBML_ID_EDITIONENTRY] = 'EditionEntry'; 1834 $EBMLidList[EBML_ID_EDITIONFLAGDEFAULT] = 'EditionFlagDefault'; 1835 $EBMLidList[EBML_ID_EDITIONFLAGHIDDEN] = 'EditionFlagHidden'; 1836 $EBMLidList[EBML_ID_EDITIONFLAGORDERED] = 'EditionFlagOrdered'; 1837 $EBMLidList[EBML_ID_EDITIONUID] = 'EditionUID'; 1838 $EBMLidList[EBML_ID_FILEDATA] = 'FileData'; 1839 $EBMLidList[EBML_ID_FILEDESCRIPTION] = 'FileDescription'; 1840 $EBMLidList[EBML_ID_FILEMIMETYPE] = 'FileMimeType'; 1841 $EBMLidList[EBML_ID_FILENAME] = 'FileName'; 1842 $EBMLidList[EBML_ID_FILEREFERRAL] = 'FileReferral'; 1843 $EBMLidList[EBML_ID_FILEUID] = 'FileUID'; 1844 $EBMLidList[EBML_ID_FLAGDEFAULT] = 'FlagDefault'; 1845 $EBMLidList[EBML_ID_FLAGENABLED] = 'FlagEnabled'; 1846 $EBMLidList[EBML_ID_FLAGFORCED] = 'FlagForced'; 1847 $EBMLidList[EBML_ID_FLAGINTERLACED] = 'FlagInterlaced'; 1848 $EBMLidList[EBML_ID_FLAGLACING] = 'FlagLacing'; 1849 $EBMLidList[EBML_ID_GAMMAVALUE] = 'GammaValue'; 1850 $EBMLidList[EBML_ID_INFO] = 'Info'; 1851 $EBMLidList[EBML_ID_LANGUAGE] = 'Language'; 1852 $EBMLidList[EBML_ID_MAXBLOCKADDITIONID] = 'MaxBlockAdditionID'; 1853 $EBMLidList[EBML_ID_MAXCACHE] = 'MaxCache'; 1854 $EBMLidList[EBML_ID_MINCACHE] = 'MinCache'; 1855 $EBMLidList[EBML_ID_MUXINGAPP] = 'MuxingApp'; 1856 $EBMLidList[EBML_ID_NAME] = 'Name'; 1857 $EBMLidList[EBML_ID_NEXTFILENAME] = 'NextFilename'; 1858 $EBMLidList[EBML_ID_NEXTUID] = 'NextUID'; 1859 $EBMLidList[EBML_ID_OUTPUTSAMPLINGFREQUENCY] = 'OutputSamplingFrequency'; 1860 $EBMLidList[EBML_ID_PIXELCROPBOTTOM] = 'PixelCropBottom'; 1861 $EBMLidList[EBML_ID_PIXELCROPLEFT] = 'PixelCropLeft'; 1862 $EBMLidList[EBML_ID_PIXELCROPRIGHT] = 'PixelCropRight'; 1863 $EBMLidList[EBML_ID_PIXELCROPTOP] = 'PixelCropTop'; 1864 $EBMLidList[EBML_ID_PIXELHEIGHT] = 'PixelHeight'; 1865 $EBMLidList[EBML_ID_PIXELWIDTH] = 'PixelWidth'; 1866 $EBMLidList[EBML_ID_PREVFILENAME] = 'PrevFilename'; 1867 $EBMLidList[EBML_ID_PREVUID] = 'PrevUID'; 1868 $EBMLidList[EBML_ID_SAMPLINGFREQUENCY] = 'SamplingFrequency'; 1869 $EBMLidList[EBML_ID_SEEK] = 'Seek'; 1870 $EBMLidList[EBML_ID_SEEKHEAD] = 'SeekHead'; 1871 $EBMLidList[EBML_ID_SEEKID] = 'SeekID'; 1872 $EBMLidList[EBML_ID_SEEKPOSITION] = 'SeekPosition'; 1873 $EBMLidList[EBML_ID_SEGMENT] = 'Segment'; 1874 $EBMLidList[EBML_ID_SEGMENTFAMILY] = 'SegmentFamily'; 1875 $EBMLidList[EBML_ID_SEGMENTFILENAME] = 'SegmentFilename'; 1876 $EBMLidList[EBML_ID_SEGMENTUID] = 'SegmentUID'; 1877 $EBMLidList[EBML_ID_SIMPLETAG] = 'SimpleTag'; 1878 $EBMLidList[EBML_ID_CLUSTERSLICES] = 'ClusterSlices'; 1879 $EBMLidList[EBML_ID_STEREOMODE] = 'StereoMode'; 1880 $EBMLidList[EBML_ID_OLDSTEREOMODE] = 'OldStereoMode'; 1881 $EBMLidList[EBML_ID_TAG] = 'Tag'; 1882 $EBMLidList[EBML_ID_TAGATTACHMENTUID] = 'TagAttachmentUID'; 1883 $EBMLidList[EBML_ID_TAGBINARY] = 'TagBinary'; 1884 $EBMLidList[EBML_ID_TAGCHAPTERUID] = 'TagChapterUID'; 1885 $EBMLidList[EBML_ID_TAGDEFAULT] = 'TagDefault'; 1886 $EBMLidList[EBML_ID_TAGEDITIONUID] = 'TagEditionUID'; 1887 $EBMLidList[EBML_ID_TAGLANGUAGE] = 'TagLanguage'; 1888 $EBMLidList[EBML_ID_TAGNAME] = 'TagName'; 1889 $EBMLidList[EBML_ID_TAGTRACKUID] = 'TagTrackUID'; 1890 $EBMLidList[EBML_ID_TAGS] = 'Tags'; 1891 $EBMLidList[EBML_ID_TAGSTRING] = 'TagString'; 1892 $EBMLidList[EBML_ID_TARGETS] = 'Targets'; 1893 $EBMLidList[EBML_ID_TARGETTYPE] = 'TargetType'; 1894 $EBMLidList[EBML_ID_TARGETTYPEVALUE] = 'TargetTypeValue'; 1895 $EBMLidList[EBML_ID_TIMECODESCALE] = 'TimecodeScale'; 1896 $EBMLidList[EBML_ID_TITLE] = 'Title'; 1897 $EBMLidList[EBML_ID_TRACKENTRY] = 'TrackEntry'; 1898 $EBMLidList[EBML_ID_TRACKNUMBER] = 'TrackNumber'; 1899 $EBMLidList[EBML_ID_TRACKOFFSET] = 'TrackOffset'; 1900 $EBMLidList[EBML_ID_TRACKOVERLAY] = 'TrackOverlay'; 1901 $EBMLidList[EBML_ID_TRACKS] = 'Tracks'; 1902 $EBMLidList[EBML_ID_TRACKTIMECODESCALE] = 'TrackTimecodeScale'; 1903 $EBMLidList[EBML_ID_TRACKTRANSLATE] = 'TrackTranslate'; 1904 $EBMLidList[EBML_ID_TRACKTRANSLATECODEC] = 'TrackTranslateCodec'; 1905 $EBMLidList[EBML_ID_TRACKTRANSLATEEDITIONUID] = 'TrackTranslateEditionUID'; 1906 $EBMLidList[EBML_ID_TRACKTRANSLATETRACKID] = 'TrackTranslateTrackID'; 1907 $EBMLidList[EBML_ID_TRACKTYPE] = 'TrackType'; 1908 $EBMLidList[EBML_ID_TRACKUID] = 'TrackUID'; 1909 $EBMLidList[EBML_ID_VIDEO] = 'Video'; 1910 $EBMLidList[EBML_ID_VOID] = 'Void'; 1911 $EBMLidList[EBML_ID_WRITINGAPP] = 'WritingApp'; 1912 } 1913 1914 return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value)); 1915 } 1916 1917 /** 1918 * @param int $value 1919 * 1920 * @return string 1921 */ 1922 public static function displayUnit($value) { 1923 // http://www.matroska.org/technical/specs/index.html#DisplayUnit 1924 static $units = array( 1925 0 => 'pixels', 1926 1 => 'centimeters', 1927 2 => 'inches', 1928 3 => 'Display Aspect Ratio'); 1929 1930 return (isset($units[$value]) ? $units[$value] : 'unknown'); 1931 } 1932 1933 /** 1934 * @param array $streams 1935 * 1936 * @return array 1937 */ 1938 private static function getDefaultStreamInfo($streams) 1939 { 1940 $stream = array(); 1941 foreach (array_reverse($streams) as $stream) { 1942 if ($stream['default']) { 1943 break; 1944 } 1945 } 1946 1947 $unset = array('default', 'name'); 1948 foreach ($unset as $u) { 1949 if (isset($stream[$u])) { 1950 unset($stream[$u]); 1951 } 1952 } 1953 1954 $info = $stream; 1955 $info['streams'] = $streams; 1956 1957 return $info; 1958 } 1959 1960 /** 1961 * @param array $info 1962 * 1963 * @return float|bool Duration when present in metadata or false 1964 */ 1965 private function playtimeFromMetadata(&$info) { 1966 if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { 1967 foreach ($info['matroska']['info'] as $infoarray) { 1968 if (isset($infoarray['Duration'])) { 1969 // TimecodeScale is how many nanoseconds each Duration unit is 1970 $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : MATROSKA_DEFAULT_TIMECODESCALE) / 1000000000); 1971 return $info['playtime_seconds']; 1972 } 1973 } 1974 } 1975 return false; 1976 } 1977 1978 /** 1979 * @param int $offset New starting offset for the buffer 1980 * 1981 * @return void 1982 */ 1983 private function resetParserBuffer($offset) { 1984 $this->current_offset = $offset; 1985 $this->EBMLbuffer = ''; 1986 $this->EBMLbuffer_offset = 0; 1987 $this->EBMLbuffer_length = 0; 1988 } 1989 1990 /** 1991 * Scan start and end of file for cluster information when Duration is missing 1992 * Only use this if no Duration was found in the Info element and we are not in parse_whole_file mode 1993 * 1994 * @param array $info 1995 * 1996 * @return void 1997 */ 1998 private function scanStartEndForClusters(&$info) { 1999 // Scan beginning of file for first cluster 2000 $this->resetParserBuffer($info['avdataoffset']); 2001 $this->scan_mode = MATROSKA_SCAN_FIRST_CLUSTER; 2002 2003 try { 2004 $this->parseEBML($info); 2005 } catch (Exception $e) { 2006 $this->error('EBML parser (start of file): '.$e->getMessage()); 2007 } 2008 2009 // Scan end of file for last cluster 2010 if (is_array($info['matroska']['cluster']) && !empty($info['matroska']['cluster'])) { 2011 // Scan maximum 1MB window before EOF 2012 $this->resetParserBuffer(max(0, $info['avdataend'] - (1024 * 1024))); 2013 $this->scan_mode = MATROSKA_SCAN_LAST_CLUSTER; 2014 2015 try { 2016 $this->parseEBML($info); 2017 } catch (Exception $e) { 2018 $this->error('EBML parser (end of file): '.$e->getMessage()); 2019 } 2020 } 2021 2022 // Reset to header parsing mode (this method is only called during header-only parsing) 2023 $this->scan_mode = MATROSKA_SCAN_HEADER; 2024 } 2025 2026 /** 2027 * Fetch TimecodeScale from Info element 2028 * 2029 * @param array $info 2030 * 2031 * @return int TimecodeScale value 2032 */ 2033 private function getTimecodeScale(&$info) { 2034 $timecodeScale = MATROSKA_DEFAULT_TIMECODESCALE; 2035 if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { 2036 foreach ($info['matroska']['info'] as $infoarray) { 2037 if (isset($infoarray['TimecodeScale'])) { 2038 $timecodeScale = $infoarray['TimecodeScale']; 2039 break; 2040 } 2041 } 2042 } 2043 return $timecodeScale; 2044 } 2045 2046 /** 2047 * Calculate duration from scanned cluster timecodes 2048 * 2049 * @param array $info 2050 * 2051 * @return void 2052 */ 2053 private function calculatePlaytimeFromClusters(&$info) { 2054 $minTimecode = null; 2055 $maxTimecode = null; 2056 if (isset($info['matroska']['cluster']) && is_array($info['matroska']['cluster'])) { 2057 foreach ($info['matroska']['cluster'] as $cluster) { 2058 if (isset($cluster['ClusterTimecode'])) { 2059 if ($minTimecode === null || $cluster['ClusterTimecode'] < $minTimecode) { 2060 $minTimecode = $cluster['ClusterTimecode']; 2061 } 2062 if ($maxTimecode === null || $cluster['ClusterTimecode'] > $maxTimecode) { 2063 $maxTimecode = $cluster['ClusterTimecode']; 2064 } 2065 } 2066 } 2067 } 2068 if ($maxTimecode !== null && $minTimecode !== null && $maxTimecode > $minTimecode) { 2069 $info['playtime_seconds'] = ($maxTimecode - $minTimecode) * ($this->getTimecodeScale($info) / 1000000000); 2070 } 2071 } 2072 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Mon Jun 15 08:20:09 2026 | Cross-referenced by PHPXref |