public Element(long offset, Document doc) { this.Offset = offset; this.Document = doc; this.Parse(); }
static void Main(string[] args) { if (args.Length != 2) { Console.WriteLine("Usage: ebmlreader <inputfile.mkv> <outputfile.ass>"); return; } string inputFilename = args[0]; string outputFilename = args[1]; if (!File.Exists(inputFilename)) { Console.WriteLine("Input file \"" + inputFilename + "\" does not exist"); return; } StreamWriter subsFile = new StreamWriter(File.Open(outputFilename, FileMode.Create), System.Text.Encoding.UTF8); Document doc = new Document(File.Open(inputFilename, FileMode.Open)); Element segment = doc.TopLevelElements[1]; // first off find the track (if any) that holds the ASS long subsTrackNumber = -1; Element[] trackElements = segment.FindElements( CreatePath(Matroska.Tracks.ID, Matroska.TrackEntry.ID), null); if (trackElements.Length == 0) { Console.WriteLine("Unable to find any tracks (invalid Matroska file?)"); return; } foreach (Element trackElement in trackElements) { Element codecIdElement = trackElement.FindFirstElement(CreatePath(Matroska.TrackEntryCodecId.ID)); if (codecIdElement.Data.GetAsString() == "S_TEXT/ASS") { // store the codec private (ASS header essentially) Element codecPrivateElement = trackElement.FindFirstElement(CreatePath(Matroska.TrackEntryCodecPrivate.ID)); if (codecPrivateElement == null) { break; } //subsFile.Write(codecPrivateElement.Data.Get()); subsFile.WriteLine(codecPrivateElement.Data.GetAsString()); // grab the track number for later processing Element trackNumberElement = trackElement.FindFirstElement(CreatePath(Matroska.TrackNumber.ID)); if (trackNumberElement == null) { break; } subsTrackNumber = trackNumberElement.Data.GetAsUnsignedInteger(); break; } } if (subsTrackNumber == -1) { Console.WriteLine("Unable to find subtitle track"); return; } // get the timecode scale of the segment Element timecodeScaleElement = segment.FindFirstElement( CreatePath(Matroska.SegmentInfo.ID, Matroska.TimecodeScale.ID)); long timecodeScale = timecodeScaleElement.Data.GetAsUnsignedInteger(); // get the clusters (as we need the timecode from the cluster) Element[] clusters = segment.FindElements(CreatePath(Matroska.Cluster.ID), null); if (clusters.Length == 0) { Console.WriteLine("Unable to find any clusters (?!)"); return; } foreach (Element clusterElement in clusters) { // get the timecode (in nanoseconds) for this cluster Element timecodeElement = clusterElement.FindFirstElement(CreatePath(Matroska.ClusterTimecode.ID)); long timecode = timecodeElement.Data.GetAsUnsignedInteger() * timecodeScale; // get the block group (because we need the duration) Element[] blockGroups = clusterElement.FindElements(CreatePath(Matroska.BlockGroup.ID), null); // because lines may not come out in read order List<ASS.Line> lines = new List<ASS.Line>(); foreach (Element blockGroupElement in blockGroups) { // and finally get the blocks Element[] blocks = blockGroupElement.FindElements(CreatePath(Matroska.Block.ID), null); foreach (Element blockElement in blocks) { Matroska.Block mblock = new Matroska.Block(blockElement); if (mblock.TrackNumber != subsTrackNumber) // skip over any non-subs track block { continue; } long blockTimecode = mblock.Timecode * timecodeScale; Element blockDurationElement = blockGroupElement.FindFirstElement(CreatePath(Matroska.BlockDuration.ID)); long blockDuration = blockDurationElement.Data.GetAsUnsignedInteger() * timecodeScale; // all of the timecodes so far have been stored in nanoseconds (according to timecodeScale) // this converts them into seconds; those d's are important double start = (timecode + blockTimecode) / 1000000000d; double end = (timecode + blockTimecode + blockDuration) / 1000000000d; ASS.Line l = ASS.Line.FromMatroska(mblock.Data.GetAsString()); l.Start = ASS.Time.Format(start); l.End = ASS.Time.Format(end); lines.Add(l); } } lines.Sort((x, y) => x.ReadOrder.CompareTo(y.ReadOrder)); foreach (ASS.Line l in lines) { subsFile.WriteLine(l); } } subsFile.Flush(); subsFile.Close(); }