// (c) Giora Tamir (giora@gtamir.com), 2005 using System; using System.IO; using System.Runtime.Serialization; namespace Nikse.SubtitleEdit.Core.ContainerFormats { #region RiffParserException [Serializable] public class RiffParserException : ApplicationException { public RiffParserException() { } public RiffParserException(string message) : base(message) { } public RiffParserException(string message, Exception inner) : base(message, inner) { } public RiffParserException(SerializationInfo info, StreamingContext context) : base(info, context) { } } #endregion RiffParserException public class RiffParser : IDisposable { #region CONSTANTS public const int DWORDSIZE = 4; public const int TWODWORDSSIZE = 8; public const string RIFF4CC = "RIFF"; public const string RIFX4CC = "RIFX"; public const string LIST4CC = "LIST"; // Known file types public static readonly int ckidAVI = ToFourCC("AVI "); public static readonly int ckidWAV = ToFourCC("WAVE"); public static readonly int ckidRMID = ToFourCC("RMID"); #endregion CONSTANTS #region private members private string m_filename; private string m_shortname; private long m_filesize; private int m_datasize; private FileStream m_stream; private int m_fileriff; private int m_filetype; // For non-thread-safe memory optimization private byte[] m_eightBytes = new byte[TWODWORDSSIZE]; private byte[] m_fourBytes = new byte[DWORDSIZE]; #endregion private members #region Delegates /// /// Method to be called when a list element is found /// /// /// public delegate void ProcessListElement(RiffParser rp, int FourCCType, int length); /// /// Method to be called when a chunk element is found /// /// /// /// public delegate void ProcessChunkElement(RiffParser rp, int FourCCType, int unpaddedLength, int paddedLength); #endregion Delegates #region public Members /// /// RIFF data segment size /// public int DataSize { get { return m_datasize; } } /// /// Current file name /// public string FileName { get { return m_filename; } } /// /// Current short (name only) file name /// public string ShortName { get { return m_shortname; } } /// /// Return the general file type (RIFF or RIFX); /// public int FileRiff { get { return m_fileriff; } } /// /// Return the specific file type (AVI/WAV...) /// public int FileType { get { return m_filetype; } } #endregion public Members /// /// Determine if the file is a valid RIFF file /// /// File to examine /// True if file is a RIFF file public bool TryOpenFile(string filename) { // Sanity check if (null != m_stream) { return false; } bool errorOccured = false; // Opening a new file try { FileInfo fi = new FileInfo(filename); m_filename = fi.FullName; m_shortname = fi.Name; m_filesize = fi.Length; fi = null; //Console.WriteLine(ShortName + " is a valid file."); // Read the RIFF header m_stream = new FileStream(m_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); int FourCC; int datasize; int fileType; ReadTwoInts(out FourCC, out datasize); ReadOneInt(out fileType); m_fileriff = FourCC; m_filetype = fileType; // Check for a valid RIFF header string riff = FromFourCC(FourCC); if (riff != RIFF4CC && riff != RIFX4CC) { // Not a valid RIFF file errorOccured = true; return false; } // Good header. Check size //Console.WriteLine(ShortName + " has a valid type \"" + riff + "\""); //Console.WriteLine(ShortName + " has a specific type of \"" + FromFourCC(fileType) + "\""); m_datasize = datasize; if (m_filesize < m_datasize + TWODWORDSSIZE) { // Truncated file errorOccured = true; return false; } } catch { errorOccured = true; } finally { if (errorOccured && (null != m_stream)) { m_stream.Close(); m_stream.Dispose(); m_stream = null; } } return !errorOccured; } /// /// Read the next RIFF element invoking the correct delegate. /// Returns true if an element can be read /// /// Reference to number of bytes left in the current list /// Method to invoke if a chunk is found /// Method to invoke if a list is found /// public bool ReadElement(ref int bytesleft, ProcessChunkElement chunk, ProcessListElement list) { // Are we done? if (TWODWORDSSIZE > bytesleft) { return false; } //Console.WriteLine(m_stream.Position.ToString() + ", " + bytesleft.ToString()); // We have enough bytes, read int FourCC; int size; ReadTwoInts(out FourCC, out size); // Reduce bytes left bytesleft -= TWODWORDSSIZE; // Do we have enough bytes? if (bytesleft < size) { // Skip the bad data and throw an exception SkipData(bytesleft); bytesleft = 0; throw new RiffParserException("Element size mismatch for element " + FromFourCC(FourCC) + " need " + size + " but have only " + bytesleft); } // Examine the element, is it a list or a chunk string type = FromFourCC(FourCC); if (type == LIST4CC) { // We have a list ReadOneInt(out FourCC); if (null == list) { SkipData(size - 4); } else { // Invoke the list method list(this, FourCC, size - 4); } // Adjust size bytesleft -= size; } else { // Calculated padded size - padded to WORD boundary int paddedSize = size; if (0 != (size & 1)) ++paddedSize; if (null == chunk) { SkipData(paddedSize); } else { chunk(this, FourCC, size, paddedSize); } // Adjust size bytesleft -= paddedSize; } return true; } #region Stream access /// /// Non-thread-safe method to read two ints from the stream /// /// Output FourCC int /// Output chunk/list size public unsafe void ReadTwoInts(out int FourCC, out int size) { try { int readsize = m_stream.Read(m_eightBytes, 0, TWODWORDSSIZE); if (TWODWORDSSIZE != readsize) { throw new RiffParserException("Unable to read. Corrupt RIFF file " + FileName); } fixed (byte* bp = &m_eightBytes[0]) { FourCC = *((int*)bp); size = *((int*)(bp + DWORDSIZE)); } } catch (Exception ex) { throw new RiffParserException("Problem accessing RIFF file " + FileName, ex); } } /// /// Non-thread-safe read a single int from the stream /// /// Output int public unsafe void ReadOneInt(out int FourCC) { try { int readsize = m_stream.Read(m_fourBytes, 0, DWORDSIZE); if (DWORDSIZE != readsize) { throw new RiffParserException("Unable to read. Corrupt RIFF file " + FileName); } fixed (byte* bp = &m_fourBytes[0]) { FourCC = *((int*)bp); } } catch (Exception ex) { throw new RiffParserException("Problem accessing RIFF file " + FileName, ex); } } /// /// Skip the specified number of bytes /// /// Number of bytes to skip public void SkipData(int skipBytes) { try { m_stream.Seek(skipBytes, SeekOrigin.Current); } catch (Exception ex) { throw new RiffParserException("Problem seeking in file " + FileName, ex); } } /// /// Read the specified length into the byte array at the specified /// offset in the array /// /// Array of bytes to read into /// Offset in the array to start from /// Number of bytes to read /// Number of bytes actually read public int ReadData(Byte[] data, int offset, int length) { try { return m_stream.Read(data, offset, length); } catch (Exception ex) { throw new RiffParserException("Problem reading data in file " + FileName, ex); } } /// /// Close the RIFF file /// public void CloseFile() { if (null != m_stream) { m_stream.Close(); m_stream = null; } } #endregion Stream access #region FourCC conversion methods public static string FromFourCC(int FourCC) { var chars = new char[4]; chars[0] = (char)(FourCC & 0xFF); chars[1] = (char)((FourCC >> 8) & 0xFF); chars[2] = (char)((FourCC >> 16) & 0xFF); chars[3] = (char)((FourCC >> 24) & 0xFF); return new string(chars); } public static int ToFourCC(string FourCC) { if (FourCC.Length != 4) { throw new Exception("FourCC strings must be 4 characters long " + FourCC); } int result = ((int)FourCC[3]) << 24 | ((int)FourCC[2]) << 16 | ((int)FourCC[1]) << 8 | ((int)FourCC[0]); return result; } public static int ToFourCC(char[] FourCC) { if (FourCC.Length != 4) { throw new Exception("FourCC char arrays must be 4 characters long " + new string(FourCC)); } int result = ((int)FourCC[3]) << 24 | ((int)FourCC[2]) << 16 | ((int)FourCC[1]) << 8 | ((int)FourCC[0]); return result; } public static int ToFourCC(char c0, char c1, char c2, char c3) { int result = ((int)c3) << 24 | ((int)c2) << 16 | ((int)c1) << 8 | ((int)c0); return result; } #endregion FourCC conversion methods public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { if (m_stream != null) { m_stream.Dispose(); m_stream = null; } } } } }