public static FileType GetFileType(FileInfo file) { byte[] markerMKV = new byte[] { 0x1A, 0x45, 0xDF, 0xA3 }; byte[] markerAVI = new byte[] { 0x52, 0x49, 0x46, 0x46 }; byte[] markerRAR = new byte[] { 0x52, 0x61, 0x72, 0x21 }; byte[] fileHeader = new byte[4]; using (FileStream fs = file.OpenRead()) fs.Read(fileHeader, 0, 4); if (ByteArrayComparer.AreEqual(fileHeader, markerRAR)) { using (RarStream rs = new RarStream(file.FullName)) rs.Read(fileHeader, 0, 4); } if (ByteArrayComparer.AreEqual(fileHeader, markerMKV)) { return(FileType.MKV); } else if (ByteArrayComparer.AreEqual(fileHeader, markerAVI)) { return(FileType.AVI); } else { return(FileType.Unknown); } }
public static void FindSampleStreams(SortedList <int, TrackData> tracks, FileInfo inFile) { Stream fs; if (RarFileNameComparer.IsRarFile(inFile.Name)) { fs = new RarStream(inFile.FullName); } else { fs = inFile.OpenRead(); } using (EbmlReader rdr = new EbmlReader(fs, EbmlReadMode.MKV)) { int clustercount = 0; bool done = false; while (rdr.Read() && !done) { switch (rdr.ElementType) { case EbmlElementType.Segment: case EbmlElementType.BlockGroup: rdr.MoveToChild(); break; case EbmlElementType.Cluster: // simple progress indicator since this can take a while (cluster is good because they're about 1mb each) Console.Write("\b{0}", Program.spinners[clustercount++ % Program.spinners.Length]); rdr.MoveToChild(); break; case EbmlElementType.Block: if (!tracks.ContainsKey(rdr.Block.TrackNumber)) { tracks.Add(rdr.Block.TrackNumber, new TrackData() { TrackNumber = (ushort)rdr.Block.TrackNumber }); } TrackData track = tracks[rdr.Block.TrackNumber]; // it's possible the sample didn't require or contain data for all tracks in the main file // if that happens, we obviously don't want to try to match the data if (track.SignatureBytes != null && (track.MatchOffset == 0 || track.CheckBytes.Length < track.SignatureBytes.Length)) { // here, the data we're looking for might not start in the first frame (lace) of the block, so we need to check them all byte[] buff = rdr.ReadContents(); int offset = 0; for (int i = 0; i < rdr.Block.FrameLengths.Length; i++) { if (track.CheckBytes != null && track.CheckBytes.Length < track.SignatureBytes.Length) { byte[] checkBytes = new byte[Math.Min(track.SignatureBytes.Length, rdr.Block.FrameLengths[i] + track.CheckBytes.Length)]; Buffer.BlockCopy(track.CheckBytes, 0, checkBytes, 0, track.CheckBytes.Length); Buffer.BlockCopy(buff, offset, checkBytes, track.CheckBytes.Length, checkBytes.Length - track.CheckBytes.Length); if (ByteArrayComparer.AreEqual(track.SignatureBytes, checkBytes, checkBytes.Length)) { track.CheckBytes = checkBytes; } else { // it was only a partial match. start over track.CheckBytes = null; track.MatchOffset = 0; track.MatchLength = 0; } } // this is a bit weird, but if we had a false positive match going and discovered it above, we check this frame again // to see if it's the start of a new match (rare problem, but it can happen with subtitles especially) if (track.CheckBytes == null) { byte[] checkBytes = new byte[Math.Min(track.SignatureBytes.Length, rdr.Block.FrameLengths[i])]; Buffer.BlockCopy(buff, offset, checkBytes, 0, checkBytes.Length); if (ByteArrayComparer.AreEqual(track.SignatureBytes, checkBytes, checkBytes.Length)) { track.CheckBytes = checkBytes; track.MatchOffset = rdr.Block.ElementStartPos + rdr.Block.RawHeader.Length + rdr.Block.RawBlockHeader.Length + offset; track.MatchLength = Math.Min(track.DataLength, rdr.Block.FrameLengths[i]); } } else { track.MatchLength += Math.Min(track.DataLength - track.MatchLength, rdr.Block.FrameLengths[i]); } offset += rdr.Block.FrameLengths[i]; } } else if (track.MatchLength < track.DataLength) { track.MatchLength += Math.Min(track.DataLength - track.MatchLength, rdr.Element.Length); rdr.SkipContents(); bool tracksDone = true; foreach (TrackData t in tracks.Values) { if (t.MatchLength < t.DataLength) { tracksDone = false; break; } } done = tracksDone; } else { rdr.SkipContents(); } break; default: rdr.SkipContents(); break; } } } Console.Write('\b'); }
public bool Read() { Debug.Assert(readReady || (mode == EbmlReadMode.SRS && ElementType == EbmlElementType.Block), "Read() is invalid at this time", "MoveToChild(), ReadContents(), or SkipContents() must be called before Read() can be called again"); long elementStartPos = ebmlStream.Position; byte idLengthDescriptor = 0; byte dataLengthDescriptor = 0; if (elementStartPos + 2 > fileLength) { return(false); } currentElement = null; readReady = false; idLengthDescriptor = (byte)ebmlStream.ReadByte(); elementHeader[0] = idLengthDescriptor; idLengthDescriptor = EbmlHelper.GetUIntLength(idLengthDescriptor); ebmlStream.Read(elementHeader, 1, idLengthDescriptor - 1); dataLengthDescriptor = (byte)ebmlStream.ReadByte(); elementHeader[idLengthDescriptor] = dataLengthDescriptor; dataLengthDescriptor = EbmlHelper.GetUIntLength(dataLengthDescriptor); ebmlStream.Read(elementHeader, idLengthDescriptor + 1, dataLengthDescriptor - 1); // these comparisons are ordered by the frequency with which they will be encountered to avoid unnecessary processing if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Block, idLengthDescriptor) || ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.SimpleBlock, idLengthDescriptor)) { ElementType = EbmlElementType.Block; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.BlockGroup, idLengthDescriptor)) { ElementType = EbmlElementType.BlockGroup; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Cluster, idLengthDescriptor)) { ElementType = EbmlElementType.Cluster; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Timecode, idLengthDescriptor)) { ElementType = EbmlElementType.Timecode; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Segment, idLengthDescriptor)) { ElementType = EbmlElementType.Segment; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.TimecodeScale, idLengthDescriptor)) { ElementType = EbmlElementType.TimecodeScale; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Crc32, idLengthDescriptor)) { ElementType = EbmlElementType.Crc32; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.AttachmentList, idLengthDescriptor)) { ElementType = EbmlElementType.AttachmentList; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Attachment, idLengthDescriptor)) { ElementType = EbmlElementType.Attachment; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.AttachedFileName, idLengthDescriptor)) { ElementType = EbmlElementType.AttachedFileName; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.AttachedFileData, idLengthDescriptor)) { ElementType = EbmlElementType.AttachedFileData; } else if (mode == EbmlReadMode.SRS && ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.ReSample, idLengthDescriptor)) { ElementType = EbmlElementType.ReSample; } else if (mode == EbmlReadMode.SRS && ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.ReSampleFile, idLengthDescriptor)) { ElementType = EbmlElementType.ReSampleFile; } else if (mode == EbmlReadMode.SRS && ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.ReSampleTrack, idLengthDescriptor)) { ElementType = EbmlElementType.ReSampleTrack; } else { ElementType = EbmlElementType.Unknown; } long elementLength = EbmlHelper.GetEbmlUInt(elementHeader, idLengthDescriptor, dataLengthDescriptor); // sanity check on element length. skip check on Segment element so we can still report expected size. this is only applied on samples since a partial movie might still be useful long endOffset = elementStartPos + idLengthDescriptor + dataLengthDescriptor + elementLength; if (mode == EbmlReadMode.Sample && ElementType != EbmlElementType.Segment && endOffset > fileLength) { throw new InvalidDataException(string.Format("Invalid element length at 0x{0:x8}", elementStartPos)); } if (ElementType != EbmlElementType.Block) { byte[] rawHeader = new byte[idLengthDescriptor + dataLengthDescriptor]; Buffer.BlockCopy(elementHeader, 0, rawHeader, 0, idLengthDescriptor + dataLengthDescriptor); currentElement = new EbmlElement() { ElementStartPos = elementStartPos, RawHeader = rawHeader, Length = elementLength }; } else { // first thing in the block is the track number byte trackDescriptor = (byte)ebmlStream.ReadByte(); byte[] blockHeader = new byte[4]; blockHeader[0] = trackDescriptor; trackDescriptor = EbmlHelper.GetUIntLength(trackDescriptor); // incredibly unlikely the track number is > 1 byte, but just to be safe... if (trackDescriptor > 1) { byte[] newBlockHeader = new byte[trackDescriptor + 3]; newBlockHeader[0] = blockHeader[0]; ebmlStream.Read(newBlockHeader, 1, trackDescriptor - 1); blockHeader = newBlockHeader; } int trackno = (int)EbmlHelper.GetEbmlUInt(blockHeader, 0, trackDescriptor); // read in time code (2 bytes) and flags (1 byte) ebmlStream.Read(blockHeader, trackDescriptor, 3); short timecode = (short)((blockHeader[blockHeader.Length - 3] << 8) + blockHeader[blockHeader.Length - 2]); // need to grab the flags (last byte of the header) to check for lacing EbmlLaceType laceType = (EbmlLaceType)(blockHeader[blockHeader.Length - 1] & (byte)EbmlLaceType.Ebml); int dataLength = (int)elementLength - blockHeader.Length; int bytesConsumed; int[] frameSizes = EbmlHelper.GetBlockFrameLengths(laceType, dataLength, ebmlStream, out bytesConsumed); if (bytesConsumed > 0) { byte[] newBlockHeader = new byte[blockHeader.Length + bytesConsumed]; Buffer.BlockCopy(blockHeader, 0, newBlockHeader, 0, blockHeader.Length); ebmlStream.Seek(-bytesConsumed, SeekOrigin.Current); ebmlStream.Read(newBlockHeader, blockHeader.Length, bytesConsumed); blockHeader = newBlockHeader; } elementLength -= blockHeader.Length; byte[] rawHeader = new byte[idLengthDescriptor + dataLengthDescriptor]; Buffer.BlockCopy(elementHeader, 0, rawHeader, 0, idLengthDescriptor + dataLengthDescriptor); currentElement = new BlockElement() { ElementStartPos = elementStartPos, RawHeader = rawHeader, Length = elementLength, FrameLengths = frameSizes, TrackNumber = trackno, Timecode = timecode, RawBlockHeader = blockHeader }; } // the following line will write mkvinfo-like output from the parser (extremely useful for debugging) //Console.WriteLine("{0}: {1} bytes @ {2}", ElementType, elementLength, elementStartPos); return(true); }
public static void FindSampleStreams(SortedList <int, TrackData> tracks, FileInfo inFile) { Stream fs; if (RarFileNameComparer.IsRarFile(inFile.Name)) { fs = new RarStream(inFile.FullName); } else { fs = inFile.OpenRead(); } using (RiffReader rdr = new RiffReader(fs, RiffReadMode.AVI)) { int blockcount = 0; bool done = false; while (rdr.Read() && !done) { if (rdr.ChunkType == RiffChunkType.List) { rdr.MoveToChild(); } else // normal chunk { if (rdr.ChunkType == RiffChunkType.Movi) { if (++blockcount % 15 == 0) { Console.Write("\b{0}", Program.spinners[blockcount % Program.spinners.Length]); } int trackno = rdr.MoviChunk.StreamNumber; if (!tracks.ContainsKey(trackno)) { tracks.Add(trackno, new TrackData()); } TrackData track = tracks[trackno]; track.TrackNumber = (byte)trackno; if (track.MatchOffset == 0 || track.CheckBytes.Length < track.SignatureBytes.Length) { // it's possible the sample didn't require or contain data for all tracks in the main file // if that happens, we obviously don't want to try to match the data if (track.SignatureBytes != null) { if (track.CheckBytes != null && track.CheckBytes.Length < track.SignatureBytes.Length) { byte[] checkBytes = new byte[Math.Min(track.SignatureBytes.Length, rdr.MoviChunk.Length + track.CheckBytes.Length)]; track.CheckBytes.CopyTo(checkBytes, 0); Buffer.BlockCopy(rdr.ReadContents(), 0, checkBytes, track.CheckBytes.Length, checkBytes.Length - track.CheckBytes.Length); if (ByteArrayComparer.AreEqual(track.SignatureBytes, checkBytes, checkBytes.Length)) { track.CheckBytes = checkBytes; } else { // it was only a partial match. start over track.CheckBytes = null; track.MatchOffset = 0; track.MatchLength = 0; } } // this is a bit weird, but if we had a false positive match going and discovered it above, we check this frame again // to see if it's the start of a new match (probably will never happen with AVI, but it does in MKV, so just in case...) if (track.CheckBytes == null) { byte[] chunkBytes = rdr.ReadContents(); byte searchByte = track.SignatureBytes[0]; int foundPos = -1; while ((foundPos = Array.IndexOf <byte>(chunkBytes, searchByte, foundPos + 1)) > -1) { byte[] checkBytes = new byte[Math.Min(track.SignatureBytes.Length, chunkBytes.Length - foundPos)]; Buffer.BlockCopy(chunkBytes, foundPos, checkBytes, 0, checkBytes.Length); if (ByteArrayComparer.AreEqual(track.SignatureBytes, checkBytes, checkBytes.Length)) { track.CheckBytes = checkBytes; track.MatchOffset = rdr.Chunk.ChunkStartPos + rdr.Chunk.RawHeader.Length + foundPos; track.MatchLength = Math.Min(track.DataLength, chunkBytes.Length - foundPos); break; } } } else { track.MatchLength += Math.Min(track.DataLength - track.MatchLength, rdr.MoviChunk.Length); } } } else if (track.MatchLength < track.DataLength) { track.MatchLength += Math.Min(track.DataLength - track.MatchLength, rdr.MoviChunk.Length); bool tracksDone = true; foreach (TrackData t in tracks.Values) { if (t.MatchLength < t.DataLength) { tracksDone = false; break; } } done = tracksDone; } rdr.SkipContents(); } else { rdr.SkipContents(); } } } } Console.Write('\b'); }