public MatroskaFile(string path) { _path = path; _stream = new FastFileStream(path); // read header var headerElement = ReadElement(); if (headerElement != null && headerElement.Id == ElementId.Ebml) { // read segment _stream.Seek(headerElement.DataSize, SeekOrigin.Current); _segmentElement = ReadElement(); if (_segmentElement != null && _segmentElement.Id == ElementId.Segment) { _valid = true; // matroska file must start with ebml header and segment } } }
private void ReadVideoElement(Element videoElement) { Element element; while (_stream.Position < videoElement.EndPosition && (element = ReadElement()) != null) { switch (element.Id) { case ElementId.PixelWidth: _pixelWidth = (int)ReadUInt((int)element.DataSize); break; case ElementId.PixelHeight: _pixelHeight = (int)ReadUInt((int)element.DataSize); break; default: _stream.Seek(element.DataSize, SeekOrigin.Current); break; } } }
private void ReadTracksElement(Element tracksElement) { _tracks = new List<MatroskaTrackInfo>(); Element element; while (_stream.Position < tracksElement.EndPosition && (element = ReadElement()) != null) { if (element.Id == ElementId.TrackEntry) { ReadTrackEntryElement(element); } else { _stream.Seek(element.DataSize, SeekOrigin.Current); } } }
private void ReadTrackEntryElement(Element trackEntryElement) { long defaultDuration = 0; bool isVideo = false; bool isAudio = false; bool isSubtitle = false; var trackNumber = 0; string name = string.Empty; string language = "eng"; // default value string codecId = string.Empty; string codecPrivate = string.Empty; //var biCompression = string.Empty; int contentCompressionAlgorithm = -1; int contentEncodingType = -1; Element element; while (_stream.Position < trackEntryElement.EndPosition && (element = ReadElement()) != null) { switch (element.Id) { case ElementId.DefaultDuration: defaultDuration = (int)ReadUInt((int)element.DataSize); break; case ElementId.Video: ReadVideoElement(element); isVideo = true; break; case ElementId.Audio: isAudio = true; break; case ElementId.TrackNumber: trackNumber = (int)ReadUInt((int)element.DataSize); break; case ElementId.Name: name = ReadString((int)element.DataSize, Encoding.UTF8); break; case ElementId.Language: language = ReadString((int)element.DataSize, Encoding.ASCII); break; case ElementId.CodecId: codecId = ReadString((int)element.DataSize, Encoding.ASCII); break; case ElementId.TrackType: switch (_stream.ReadByte()) { case 1: isVideo = true; break; case 2: isAudio = true; break; case 17: isSubtitle = true; break; } break; case ElementId.CodecPrivate: codecPrivate = 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 var contentEncodingElement = ReadElement(); if (contentEncodingElement != null && contentEncodingElement.Id == ElementId.ContentEncoding) { ReadContentEncodingElement(element, ref contentCompressionAlgorithm, ref contentEncodingType); } break; } _stream.Seek(element.EndPosition, SeekOrigin.Begin); } _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) { _frameRate = 1.0 / (defaultDuration / 1000000000.0); } _videoCodecId = codecId; } }
private MatroskaSubtitle ReadSubtitleBlock(Element blockElement, long clusterTimeCode) { var trackNumber = (int)ReadVariableLengthUInt(); if (trackNumber != _subtitleRipTrackNumber) { _stream.Seek(blockElement.EndPosition, SeekOrigin.Begin); return null; } var timeCode = ReadInt16(); // lacing var flags = (byte)_stream.ReadByte(); int frames; switch (flags & 6) { case 0: // 00000000 = No lacing System.Diagnostics.Debug.Print("No lacing"); break; case 2: // 00000010 = Xiph lacing frames = _stream.ReadByte() + 1; System.Diagnostics.Debug.Print("Xiph lacing ({0} frames)", frames); break; case 4: // 00000100 = Fixed-size lacing frames = _stream.ReadByte() + 1; for (var i = 0; i < frames; i++) { _stream.ReadByte(); // frames } System.Diagnostics.Debug.Print("Fixed-size lacing ({0} frames)", frames); break; case 6: // 00000110 = EMBL lacing frames = _stream.ReadByte() + 1; System.Diagnostics.Debug.Print("EBML lacing ({0} frames)", frames); break; } // save subtitle data var dataLength = (int)(blockElement.EndPosition - _stream.Position); var data = new byte[dataLength]; _stream.Read(data, 0, dataLength); return new MatroskaSubtitle(data, clusterTimeCode + timeCode); }
private void ReadInfoElement(Element infoElement) { Element element; while (_stream.Position < infoElement.EndPosition && (element = 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) _timecodeScale = (int)ReadUInt((int)element.DataSize); break; case ElementId.Duration: // Duration of the segment (based on TimecodeScale) _duration = element.DataSize == 4 ? ReadFloat32() : ReadFloat64(); _duration /= _timecodeScale * 1000000.0; break; default: _stream.Seek(element.DataSize, SeekOrigin.Current); break; } } }
private void ReadContentEncodingElement(Element contentEncodingElement, ref int contentCompressionAlgorithm, ref int contentEncodingType) { Element element; while (_stream.Position < contentEncodingElement.EndPosition && (element = ReadElement()) != null) { switch (element.Id) { case ElementId.ContentEncodingOrder: var contentEncodingOrder = ReadUInt((int)element.DataSize); System.Diagnostics.Debug.WriteLine("ContentEncodingOrder: " + contentEncodingOrder); break; case ElementId.ContentEncodingScope: var contentEncodingScope = ReadUInt((int)element.DataSize); System.Diagnostics.Debug.WriteLine("ContentEncodingScope: " + contentEncodingScope); break; case ElementId.ContentEncodingType: contentEncodingType = (int)ReadUInt((int)element.DataSize); break; case ElementId.ContentCompression: Element compElement; while (_stream.Position < element.EndPosition && (compElement = ReadElement()) != null) { switch (compElement.Id) { case ElementId.ContentCompAlgo: contentCompressionAlgorithm = (int)ReadUInt((int)compElement.DataSize); break; case ElementId.ContentCompSettings: var contentCompSettings = ReadUInt((int)compElement.DataSize); System.Diagnostics.Debug.WriteLine("ContentCompSettings: " + contentCompSettings); break; default: _stream.Seek(element.DataSize, SeekOrigin.Current); break; } } break; default: _stream.Seek(element.DataSize, SeekOrigin.Current); break; } } }
private void ReadCluster(Element clusterElement) { long clusterTimeCode = 0; Element element; while (_stream.Position < clusterElement.EndPosition && (element = ReadElement()) != null) { switch (element.Id) { case ElementId.Timecode: clusterTimeCode = (long)ReadUInt((int)element.DataSize); break; case ElementId.BlockGroup: ReadBlockGroupElement(element, clusterTimeCode); break; case ElementId.SimpleBlock: var subtitle = ReadSubtitleBlock(element, clusterTimeCode); if (subtitle != null) { _subtitleRip.Add(subtitle); } break; default: _stream.Seek(element.DataSize, SeekOrigin.Current); break; } } }
private void ReadBlockGroupElement(Element clusterElement, long clusterTimeCode) { MatroskaSubtitle subtitle = null; Element element; while (_stream.Position < clusterElement.EndPosition && (element = ReadElement()) != null) { switch (element.Id) { case ElementId.Block: subtitle = ReadSubtitleBlock(element, clusterTimeCode); if (subtitle == null) { return; } _subtitleRip.Add(subtitle); break; case ElementId.BlockDuration: var duration = (long)ReadUInt((int)element.DataSize); if (subtitle != null) { subtitle.Duration = duration; } break; default: _stream.Seek(element.DataSize, SeekOrigin.Current); break; } } }
private long FindTrackStartInCluster(Element cluster, int targetTrackNumber) { long clusterTimeCode = 0L; long trackStartTime = -1L; bool done = false; Element element; while (_stream.Position < cluster.EndPosition && (element = 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)ReadUInt((int)element.DataSize); break; case ElementId.BlockGroup: ReadBlockGroupElement(element, clusterTimeCode); break; case ElementId.SimpleBlock: var trackNumber = (int)ReadVariableLengthUInt(); if (trackNumber == targetTrackNumber) { // Timecode (relative to Cluster timecode, signed int16) trackStartTime = ReadInt16(); done = true; } break; } _stream.Seek(element.EndPosition, SeekOrigin.Begin); } return (clusterTimeCode + trackStartTime) * _timecodeScale / 1000000; }