public MatroskaFile(string path) { this._path = path; this._stream = new FastFileStream(path); // read header Element headerElement = this.ReadElement(); if (headerElement != null && headerElement.Id == ElementId.Ebml) { // read segment this._stream.Seek(headerElement.DataSize, SeekOrigin.Current); this._segmentElement = this.ReadElement(); if (this._segmentElement != null && this._segmentElement.Id == ElementId.Segment) { this._valid = true; // matroska file must start with ebml header and segment } } }
/// <summary> /// The read subtitle block. /// </summary> /// <param name="blockElement"> /// The block element. /// </param> /// <param name="clusterTimeCode"> /// The cluster time code. /// </param> /// <returns> /// The <see cref="MatroskaSubtitle"/>. /// </returns> private MatroskaSubtitle ReadSubtitleBlock(Element blockElement, long clusterTimeCode) { int trackNumber = (int)this.ReadVariableLengthUInt(); if (trackNumber != this._subtitleRipTrackNumber) { this._stream.Seek(blockElement.EndPosition, SeekOrigin.Begin); return null; } short timeCode = this.ReadInt16(); // lacing byte flags = (byte)this._stream.ReadByte(); int frames; switch (flags & 6) { case 0: // 00000000 = No lacing Debug.Print("No lacing"); break; case 2: // 00000010 = Xiph lacing frames = this._stream.ReadByte() + 1; Debug.Print("Xiph lacing ({0} frames)", frames); break; case 4: // 00000100 = Fixed-size lacing frames = this._stream.ReadByte() + 1; for (int i = 0; i < frames; i++) { this._stream.ReadByte(); // frames } Debug.Print("Fixed-size lacing ({0} frames)", frames); break; case 6: // 00000110 = EMBL lacing frames = this._stream.ReadByte() + 1; Debug.Print("EBML lacing ({0} frames)", frames); break; } // save subtitle data int dataLength = (int)(blockElement.EndPosition - this._stream.Position); byte[] data = new byte[dataLength]; this._stream.Read(data, 0, dataLength); return new MatroskaSubtitle(data, clusterTimeCode + timeCode); }
/// <summary> /// The read block group element. /// </summary> /// <param name="clusterElement"> /// The cluster element. /// </param> /// <param name="clusterTimeCode"> /// The cluster time code. /// </param> private void ReadBlockGroupElement(Element clusterElement, long clusterTimeCode) { MatroskaSubtitle subtitle = null; Element element; while (this._stream.Position < clusterElement.EndPosition && (element = this.ReadElement()) != null) { switch (element.Id) { case ElementId.Block: subtitle = this.ReadSubtitleBlock(element, clusterTimeCode); if (subtitle == null) { return; } this._subtitleRip.Add(subtitle); break; case ElementId.BlockDuration: long duration = (long)this.ReadUInt((int)element.DataSize); if (subtitle != null) { subtitle.Duration = duration; } break; default: this._stream.Seek(element.DataSize, SeekOrigin.Current); break; } } }
/// <summary> /// The read cluster. /// </summary> /// <param name="clusterElement"> /// The cluster element. /// </param> private void ReadCluster(Element clusterElement) { long clusterTimeCode = 0; Element element; while (this._stream.Position < clusterElement.EndPosition && (element = this.ReadElement()) != null) { switch (element.Id) { case ElementId.Timecode: clusterTimeCode = (long)this.ReadUInt((int)element.DataSize); break; case ElementId.BlockGroup: this.ReadBlockGroupElement(element, clusterTimeCode); break; case ElementId.SimpleBlock: MatroskaSubtitle subtitle = this.ReadSubtitleBlock(element, clusterTimeCode); if (subtitle != null) { this._subtitleRip.Add(subtitle); } break; default: this._stream.Seek(element.DataSize, SeekOrigin.Current); break; } } }
/// <summary> /// The read tracks element. /// </summary> /// <param name="tracksElement"> /// The tracks element. /// </param> private void ReadTracksElement(Element tracksElement) { this._tracks = new List<MatroskaTrackInfo>(); Element element; while (this._stream.Position < tracksElement.EndPosition && (element = this.ReadElement()) != null) { if (element.Id == ElementId.TrackEntry) { this.ReadTrackEntryElement(element); } else { this._stream.Seek(element.DataSize, SeekOrigin.Current); } } }
/// <summary> /// The read info element. /// </summary> /// <param name="infoElement"> /// The info element. /// </param> private void ReadInfoElement(Element infoElement) { Element element; while (this._stream.Position < infoElement.EndPosition && (element = this.ReadElement()) != null) { switch (element.Id) { case ElementId.TimecodeScale: // Timestamp scale in nanoseconds (1.000.000 means all timestamps in the segment are expressed in milliseconds) this._timecodeScale = (int)this.ReadUInt((int)element.DataSize); break; case ElementId.Duration: // Duration of the segment (based on TimecodeScale) this._duration = element.DataSize == 4 ? this.ReadFloat32() : this.ReadFloat64(); this._duration /= this._timecodeScale * 1000000.0; break; default: this._stream.Seek(element.DataSize, SeekOrigin.Current); break; } } }
/// <summary> /// The read content encoding element. /// </summary> /// <param name="contentEncodingElement"> /// The content encoding element. /// </param> /// <param name="contentCompressionAlgorithm"> /// The content compression algorithm. /// </param> /// <param name="contentEncodingType"> /// The content encoding type. /// </param> private void ReadContentEncodingElement(Element contentEncodingElement, ref int contentCompressionAlgorithm, ref int contentEncodingType) { Element element; while (this._stream.Position < contentEncodingElement.EndPosition && (element = this.ReadElement()) != null) { switch (element.Id) { case ElementId.ContentEncodingOrder: ulong contentEncodingOrder = this.ReadUInt((int)element.DataSize); Debug.WriteLine("ContentEncodingOrder: " + contentEncodingOrder); break; case ElementId.ContentEncodingScope: ulong contentEncodingScope = this.ReadUInt((int)element.DataSize); Debug.WriteLine("ContentEncodingScope: " + contentEncodingScope); break; case ElementId.ContentEncodingType: contentEncodingType = (int)this.ReadUInt((int)element.DataSize); break; case ElementId.ContentCompression: Element compElement; while (this._stream.Position < element.EndPosition && (compElement = this.ReadElement()) != null) { switch (compElement.Id) { case ElementId.ContentCompAlgo: contentCompressionAlgorithm = (int)this.ReadUInt((int)compElement.DataSize); break; case ElementId.ContentCompSettings: ulong contentCompSettings = this.ReadUInt((int)compElement.DataSize); Debug.WriteLine("ContentCompSettings: " + contentCompSettings); break; default: this._stream.Seek(element.DataSize, SeekOrigin.Current); break; } } break; default: this._stream.Seek(element.DataSize, SeekOrigin.Current); break; } } }
/// <summary> /// The read track entry element. /// </summary> /// <param name="trackEntryElement"> /// The track entry element. /// </param> private void ReadTrackEntryElement(Element trackEntryElement) { long defaultDuration = 0; bool isVideo = false; bool isAudio = false; bool isSubtitle = false; int trackNumber = 0; string name = string.Empty; string language = string.Empty; string codecId = string.Empty; string codecPrivate = string.Empty; // var biCompression = string.Empty; int contentCompressionAlgorithm = -1; int contentEncodingType = -1; Element element; while (this._stream.Position < trackEntryElement.EndPosition && (element = this.ReadElement()) != null) { switch (element.Id) { case ElementId.DefaultDuration: defaultDuration = (int)this.ReadUInt((int)element.DataSize); break; case ElementId.Video: this.ReadVideoElement(element); isVideo = true; break; case ElementId.Audio: isAudio = true; break; case ElementId.TrackNumber: trackNumber = (int)this.ReadUInt((int)element.DataSize); break; case ElementId.Name: name = this.ReadString((int)element.DataSize, Encoding.UTF8); break; case ElementId.Language: language = this.ReadString((int)element.DataSize, Encoding.ASCII); break; case ElementId.CodecId: codecId = this.ReadString((int)element.DataSize, Encoding.ASCII); break; case ElementId.TrackType: switch (this._stream.ReadByte()) { case 1: isVideo = true; break; case 2: isAudio = true; break; case 17: isSubtitle = true; break; } break; case ElementId.CodecPrivate: codecPrivate = this.ReadString((int)element.DataSize, Encoding.UTF8); // if (codecPrivate.Length > 20) // biCompression = codecPrivate.Substring(16, 4); break; case ElementId.ContentEncodings: contentCompressionAlgorithm = 0; // default value contentEncodingType = 0; // default value Element contentEncodingElement = this.ReadElement(); if (contentEncodingElement != null && contentEncodingElement.Id == ElementId.ContentEncoding) { this.ReadContentEncodingElement(element, ref contentCompressionAlgorithm, ref contentEncodingType); } break; } this._stream.Seek(element.EndPosition, SeekOrigin.Begin); } this._tracks.Add(new MatroskaTrackInfo { TrackNumber = trackNumber, IsVideo = isVideo, IsAudio = isAudio, IsSubtitle = isSubtitle, Language = language, CodecId = codecId, CodecPrivate = codecPrivate, Name = name, ContentEncodingType = contentEncodingType, ContentCompressionAlgorithm = contentCompressionAlgorithm }); if (isVideo) { if (defaultDuration > 0) { this._frameRate = 1.0 / (defaultDuration / 1000000000.0); } this._videoCodecId = codecId; } }
/// <summary> /// The read video element. /// </summary> /// <param name="videoElement"> /// The video element. /// </param> private void ReadVideoElement(Element videoElement) { Element element; while (this._stream.Position < videoElement.EndPosition && (element = this.ReadElement()) != null) { switch (element.Id) { case ElementId.PixelWidth: this._pixelWidth = (int)this.ReadUInt((int)element.DataSize); break; case ElementId.PixelHeight: this._pixelHeight = (int)this.ReadUInt((int)element.DataSize); break; default: this._stream.Seek(element.DataSize, SeekOrigin.Current); break; } } }
/// <summary> /// The find track start in cluster. /// </summary> /// <param name="cluster"> /// The cluster. /// </param> /// <param name="targetTrackNumber"> /// The target track number. /// </param> /// <returns> /// The <see cref="long"/>. /// </returns> private long FindTrackStartInCluster(Element cluster, int targetTrackNumber) { long clusterTimeCode = 0L; long trackStartTime = -1L; bool done = false; Element element; while (this._stream.Position < cluster.EndPosition && (element = this.ReadElement()) != null && !done) { switch (element.Id) { case ElementId.None: done = true; break; case ElementId.Timecode: // Absolute timestamp of the cluster (based on TimecodeScale) clusterTimeCode = (long)this.ReadUInt((int)element.DataSize); break; case ElementId.BlockGroup: this.ReadBlockGroupElement(element, clusterTimeCode); break; case ElementId.SimpleBlock: int trackNumber = (int)this.ReadVariableLengthUInt(); if (trackNumber == targetTrackNumber) { // Timecode (relative to Cluster timecode, signed int16) trackStartTime = this.ReadInt16(); done = true; } break; } this._stream.Seek(element.EndPosition, SeekOrigin.Begin); } return (clusterTimeCode + trackStartTime) * this._timecodeScale / 1000000; }