using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Text;

/*
    Copyright (c) 2010 flixmonk. (http://DVDSwarm.codeplex.com)
    All Rights Reserved.
        
    Redistribution and use in source and binary forms, with or without modification,
    are permitted provided that the following conditions are met:
        
    * Redistributions of source code must retain the above copyright notice, this list
      of conditions and the following disclaimer.
        
    * Redistributions in binary form must reproduce the above copyright notice, this
    list of conditions and the following disclaimer in the documentation and/or other
    materials provided with the distribution. 
        
    * Neither the name of The Flixmonk nor the names of its contributors may be used to
    endorse or promote products derived from this software without specific prior written
    permission.
        
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
    SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
    OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace DVDSwarm.Dvd
{
    /// <summary>
    /// Indexes MPEG-2 video streams and writes DGIndex compatible D2v and log files.
    /// </summary>
    sealed public class Mpeg2Indexer
    {
        #region Fields

        private Int32 _bFrameCount = 0;
        private Boolean _cancelled = false;
        private FicSprout.Bacicworx.Media.VideoAspectRatio _displayAspectRatio = FicSprout.Bacicworx.Media.VideoAspectRatio.Aspect_1_78;
        private string _d2vFilename = string.Empty;
        private DateTime _endDateTime = DateTime.Now;
        private FicSprout.Bacicworx.Media.VideoFieldOrder _fieldOrder = FicSprout.Bacicworx.Media.VideoFieldOrder.TopFieldFirst;
        private Int32 _frameCount = 0;
        private FicSprout.Bacicworx.Media.VideoFramerate _framerate = FicSprout.Bacicworx.Media.VideoFramerate.Fps30;
        private SortedDictionary<Int32, Dvd.MpegFrameType> _frames = new SortedDictionary<Int32, MpegFrameType>();
        private FicSprout.Bacicworx.Media.VideoFrameType _frameType = FicSprout.Bacicworx.Media.VideoFrameType.Interlaced;
        private Int32 _iFrameCount = 0;
        private DateTime _lastRaisedDateTime = DateTime.Now;
        private string _logFilename = string.Empty;
        private Boolean _paused = false;
        private Int32 _pFrameCount = 0;
        private Int64 _position = 0;
        private Boolean _preview = false;
        private Int32 _progressPercent = 0;
        private FicSprout.Bacicworx.Media.SmpteTimecode _sourceDuration = new FicSprout.Bacicworx.Media.SmpteTimecode(0,
             FicSprout.Bacicworx.Media.VideoFramerate.Fps30, true);
        private string _sourceFilename = string.Empty;
        private Int32 _sourceHeight = 0;
        private Int64 _sourceLength = 0;
        private Int32 _sourceWidth = 0;
        private DateTime _startDateTime = DateTime.Now;
        private FicSprout.Bacicworx.Media.VideoType _videoType = FicSprout.Bacicworx.Media.VideoType.NtscVideo;
        private double _videoTypePercentage = 0;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the number of B-frames for the video.
        /// </summary>
        public Int32 BFrameCount
        {
            get
            {
                return _bFrameCount;
            }
        }
        /// <summary>
        /// Gets a value indicating whether the indexer was cancelled.
        /// </summary>
        public Boolean Cancelled
        {
            get
            {
                return _cancelled;
            }
        }
        /// <summary>
        /// Gets the display aspect ratio for the source video as specified in the
        /// transport stream. This value is independent of the JobInfo.AspectRatio.
        /// </summary>
        /// <remarks>
        /// DisplayAspectRatio is the value specified by the video stream. JobInfo.AspectRatio is the 
        /// value for the video when letterboxing is removed. For example: a 2.35 widescreen video stored
        /// in a fullscreeen video stream would have a DisplayAspectRatio of 1.33 and an AspectRatio of 2.35
        /// after cropping of black borders.
        /// </remarks>
        public FicSprout.Bacicworx.Media.VideoAspectRatio DisplayAspectRatio
        {
            get
            {
                return _displayAspectRatio;
            }
        }
        /// <summary>
        /// Gets the output filename of the D2V file.
        /// </summary>
        public string D2vFilename
        {
            get
            {
                return _d2vFilename;
            }
        }
        /// <summary>
        /// Gets the video field order.
        /// </summary>
        public FicSprout.Bacicworx.Media.VideoFieldOrder FieldOrder
        {
            get
            {
                return _fieldOrder;
            }
        }
        /// <summary>
        /// Gets the total number of frames for the video.
        /// </summary>
        public Int32 FrameCount
        {
            get
            {
                return _frameCount;
            }
        }
        /// <summary>
        /// Gets the video frame rate.
        /// </summary>
        public FicSprout.Bacicworx.Media.VideoFramerate Framerate
        {
            get
            {
                return _framerate;
            }
        }
        /// <summary>
        /// Gets the video frame type.
        /// </summary>
        public FicSprout.Bacicworx.Media.VideoFrameType FrameType
        {
            get
            {
                return _frameType;
            }
        }
        /// <summary>
        /// Gets the number of I-frames for the video.
        /// </summary>
        public Int32 IFrameCount
        {
            get
            {
                return _iFrameCount;
            }
        }
        /// <summary>
        /// Gets the output filename of the log file.
        /// </summary>
        public string LogFilename
        {
            get
            {
                return _logFilename;
            }
        }
        /// <summary>
        /// Gets a value indicating whether the indexer is paused.
        /// </summary>
        public Boolean Paused
        {
            get
            {
                return _paused;
            }
        }
        /// <summary>
        /// Gets the current index position in the source. If Inactive, -1 is returned.
        /// </summary>
        public Int64 Position
        {
            get
            {
                return _position;
            }
        }
        /// <summary>
        /// Gets a value indicating whether the indexer show only index the first 100 frames.
        /// </summary>
        public Boolean Preview
        {
            get
            {
                return _preview;
            }
        }
        /// <summary>
        /// Gets the progress percentage for the indexing operation. If
        /// Inactive, zero is returned.
        /// </summary>
        public Int32 ProgressPercent
        {
            get
            {
                return _progressPercent;
            }
        }
        /// <summary>
        /// Gets the video duration.
        /// </summary>
        public FicSprout.Bacicworx.Media.SmpteTimecode SourceDuration
        {
            get
            {
                return _sourceDuration;
            }
        }
        /// <summary>
        /// Gets the source video filename.
        /// </summary>
        public string SourceFilename
        {
            get
            {
                return _sourceFilename;
            }
        }
        /// <summary>
        /// Gets the source video height.
        /// </summary>
        public Int32 SourceHeight
        {
            get
            {
                return _sourceHeight;
            }
        }
        /// <summary>
        /// Gets the source video file length in bytes.
        /// </summary>
        public Int64 SourceLength
        {
            get
            {
                return _sourceLength;
            }
        }
        /// <summary>
        /// Gets the source video width.
        /// </summary>
        public Int32 SourceWidth
        {
            get
            {
                return _sourceWidth;
            }
        }
        /// <summary>
        /// Gets the video type.
        /// </summary>
        public FicSprout.Bacicworx.Media.VideoType VideoType
        {
            get
            {
                return _videoType;
            }
        }
        /// <summary>
        /// Gets the VideoType percentage.
        /// </summary>
        public double VideoTypePercentage
        {
            get
            {
                return _videoTypePercentage;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates a new instance of the Mpeg2Indexer class.
        /// </summary>
        /// <param name="path">The filename of the video to index.</param>
        /// <exception cref="System.ArgumentException">Thrown when path is null or empty.</exception>
        public Mpeg2Indexer(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentException("path is null or empty.");
            }

            _sourceFilename = path;
            _d2vFilename = Path.ChangeExtension(_sourceFilename, ".DvdSwarm_D2V");
        }

        /// <summary>
        /// Cancels the indexing operation.
        /// </summary>
        public void Cancel()
        {
            _cancelled = true;
        }

        /// <summary>
        /// Toggles the indexer between pause and resume.
        /// </summary>
        public void Pause()
        {
            _paused = !_paused;
        }

        /// <summary>
        /// Starts indexing of the video.
        /// </summary>
        public void Start(Boolean preview)
        {
            // Loop variables
            _preview = preview;
            TextWriter d2v = null;
            FileStream source = null;
            FicSprout.Bacicworx.IO.BoyerMooreSearch search = null;
            byte[] buffer = new byte[0x20000];
            Int32 bytesReadFromSource = 0;

            // State variables

            FicSprout.Bacicworx.Media.VideoAspectRatio displayAspectRatio = FicSprout.Bacicworx.Media.VideoAspectRatio.Aspect_1_33;
            Double filmPercentage = 0.0;
            Boolean firstIframeAfterGop = false;
            Int64 indexPositionPictureHeader = 0;
            Int64 indexPositionSequenceHeader = 0;
            Boolean processed = false;
            Boolean processedGop = false;
            Boolean writtenD2vSettings = false;

            try
            {
                d2v = new StreamWriter(_d2vFilename, false, Encoding.UTF8, 0x10000);
                source = new FileStream(_sourceFilename, FileMode.Open, FileAccess.Read, FileShare.None);
                _sourceLength = source.Length;
                search = new FicSprout.Bacicworx.IO.BoyerMooreSearch(buffer, new byte[3] { 0, 0, 1 });

                // D2V Header.
                d2v.WriteLine("DGIndexProjectFile16");
                d2v.WriteLine("1");
                d2v.WriteLine(_sourceFilename);
                d2v.WriteLine();

                // Parse video.
                bytesReadFromSource = source.Read(buffer, 0, buffer.Length);
                while (bytesReadFromSource > 0)
                {
                    Int64 index = search.IndexOf(0, bytesReadFromSource);

                    while (index != -1 && bytesReadFromSource > 0 && index < buffer.Length - 8)
                    {
                        // Picture header (0x00000100)?
                        if (buffer[index + 3] == 0)
                        {
                            indexPositionPictureHeader = index;
                            videoTemporalSequence = (buffer[index + 4] << 2) + ((buffer[index + 5] >> 6) & 3);
                            videoMpegFrameType = (MpegFrameType)((buffer[index + 5] >> 3) & 3);

                            if (videoMpegFrameType == MpegFrameType.B)
                            {
                                _bFrameCount++;
                            }
                            if (videoMpegFrameType == MpegFrameType.I)
                            {
                                _iFrameCount++;

                                // flush frames to log, then add i frame.
                            }
                            if (videoMpegFrameType == MpegFrameType.P)
                            {
                                _pFrameCount++;
                            }

                            if (!_frames.ContainsKey(videoTemporalSequence))
                            {
                                _frames.Add(videoTemporalSequence, videoMpegFrameType);
                            }

                            // Follows GOP?
                            if (processedGop)
                            {
                                firstIframeAfterGop = true;
                            }

                            _frameCount++;
                            index += 7;
                            processed = true;
                            processedGop = false;
                        }

                        // Sequence Extension (0x000001B3) ?
                        if (!processed && index < buffer.Length - 12 && buffer[index + 3] == 0xB3)
                        {
                            indexPositionSequenceHeader = index;

                            // Resolution.
                            width = (buffer[index + 4] << 4) + ((buffer[index + 5] >> 4) & 0xF);
                            height = ((buffer[index + 5] & 0xF) << 8) + buffer[index + 6];

                            // Display aspect ratio.
                            Int32 value = (buffer[index + 7] >> 8) & 0xF;
                            if (value == 1)
                            {
                                displayAspectRatio = FicSprout.Bacicworx.Media.VideoAspectRatio.Aspect_1_00;
                            }
                            if (value == 2)
                            {
                                displayAspectRatio = FicSprout.Bacicworx.Media.VideoAspectRatio.Aspect_1_33;
                            }
                            if (value == 3)
                            {
                                displayAspectRatio = FicSprout.Bacicworx.Media.VideoAspectRatio.Aspect_1_78;
                            }
                            if (value == 4)
                            {
                                displayAspectRatio = FicSprout.Bacicworx.Media.VideoAspectRatio.Aspect_2_21;
                            }

                            if (width > 720 || height > 576)
                            {
                                matrixCoefficients = 1; // HD video
                                matrixCoefficientsName = "ITU-R BT.709";
                            }
                            else
                            {
                                matrixCoefficients = 5; // SD video
                                matrixCoefficientsName = "ITU-R BT.470-2 System B, G";
                            }

                            // Frame rate.
                            framerateValue = buffer[index + 7] & 0xF;

                            index += 11;
                            processed = true;
                            processedGop = false;
                        }

                        // Extension header (0x000001B5) ?
                        if (!processed && index < buffer.Length - 12 && buffer[index + 3] == 0xB5)
                        {
                            // Sequence display extension?
                            if (index < buffer.Length - 10 && (buffer[index + 4] & 0xf0) == 0x20)
                            {
                                // Video format?
                                if (((buffer[index + 4] >> 1) & 3) == 1)
                                {
                                    format = FicSprout.Bacicworx.Media.VideoType.PalVideo;
                                }
                                if (((buffer[index + 4] >> 1) & 3) == 2)
                                {
                                    format = FicSprout.Bacicworx.Media.VideoType.NtscVideo;
                                }

                                // Color info?
                                if (index < buffer.Length - 12 && (buffer[index + 4] & 1) == 1)
                                {
                                    if ((buffer[index + 5] & 8) == 8)
                                    {
                                        progressiveSequence = true;
                                    }

                                    matrixCoefficients = buffer[index + 7];
                                    matrixCoefficientsName = "Unknown";

                                    if (matrixCoefficients == 1)
                                    {
                                        matrixCoefficientsName = "ITU-R BT.709";
                                    }
                                    if (matrixCoefficients == 4)
                                    {
                                        matrixCoefficientsName = "FCC";
                                    }
                                    if (matrixCoefficients == 5)
                                    {
                                        matrixCoefficientsName = "ITU-R BT.470-2 System B, G";
                                    }
                                    if (matrixCoefficients == 6)
                                    {
                                        matrixCoefficientsName = "SMPTE 170M";
                                    }
                                    if (matrixCoefficients == 6)
                                    {
                                        matrixCoefficientsName = "SMPTE 240M (1987)";
                                    }
                                    index += 2;
                                }
                                index += 9;
                                processed = true;
                                processedGop = false;
                            }

                            // Picture display extension is ignored.

                            // Picture coding extension?
                            if (!processed && index < buffer.Length - 9 && (buffer[index + 4] & 0xf0) == 0x80)
                            {
                                pictureStructure = (buffer[index + 6] & 3); // 1=top field, 2=bottom field, 3=frame
                                progressiveFrame = false;
                                repeatFirstField = false;
                                topFieldFirst = false;

                                if ((buffer[index + 7] & 0x80) == 0x80)
                                {
                                    topFieldFirst = true;
                                }
                                if ((buffer[index + 7] & 2) == 2)
                                {
                                    repeatFirstField = true;
                                }
                                if ((buffer[index + 8] & 0x80) == 0x80)
                                {
                                    progressiveFrame = true;
                                }

                                index += 10;
                                processed = true;
                                processedGop = false;
                            }
                        }

                        // GOP header (0x000001B8) ?
                        if (!processed && index < buffer.Length - 8 && buffer[index + 3] == 0xB8)
                        {
                            brokenGop = false;
                            closedGop = false;
                            dropFrame = false;

                            if ((buffer[index + 4] & 0x80) == 0x80)
                            {
                                dropFrame = true;
                            }
                            if ((buffer[index + 7] & 0x40) == 0x40)
                            {
                                closedGop = true;
                            }
                            if ((buffer[index + 7] & 0x20) == 0x20)
                            {
                                brokenGop = true;
                            }

                            processedGop = true;
                        }

                        // Field and frame counts.
                        if (pictureStructure == 3)
                        {
                            isFrame = true;
                        }
                        if (!isFrame)
                        {
                            fieldCount = 1;

                            if (progressiveSequence && !repeatFirstField)
                            {
                                fieldCount = 2;
                            }
                            if (progressiveSequence && repeatFirstField && !topFieldFirst)
                            {
                                fieldCount = 4;
                            }
                            if (progressiveSequence && repeatFirstField && topFieldFirst)
                            {
                                fieldCount = 6;
                            }
                            if (!progressiveSequence && !progressiveFrame)
                            {
                                fieldCount = 2;
                            }
                            if (!progressiveSequence && progressiveFrame && !repeatFirstField)
                            {
                                fieldCount = 2;
                            }
                            if (!progressiveSequence && progressiveFrame && repeatFirstField)
                            {
                                fieldCount = 3;
                            }
                        }

                        _sourceDuration += fieldDuration * fieldsCOunt;


                        if (!writtenD2vSettings && height > 0 && width > 0)
                        {
                            WriteD2vSettingsSection(d2v, displayAspectRatio, width, height, framerateValue);
                            writtenD2vSettings = true;
                        }

                        // New I-Frame? Output frame data.
                        if (videoMpegFrameType == MpegFrameType.I && _frames.Count > 0 && writtenD2vSettings)
                        {
                            WriteFrames();
                            _frames.Clear();
                            firstIframeAfterGop = false;
                        }


                        // Preview mode? [Process 2500 frames only]
                        if (_preview && _frameCount >= 2500)
                        {
                            break;
                        }

                        indexPositionPictureHeader = 0;
                        indexPositionSequenceHeader = 0;
                        processed = false;
                        index++;
                        index = search.IndexOf(index, bytesReadFromSource);
                    }

                    // Preview mode? [Process 2500 frames only]
                    if (_preview && _frameCount >= 2500)
                    {
                        break;
                    }

                    // Backtrack  so that we don't skip any headers located on the edge of buffer reads.
                    // 
                    if (source.Position < source.Length - 4)
                    {
                        source.Seek(-16, SeekOrigin.Current);
                    }
                    bytesReadFromSource = source.Read(buffer, 0, buffer.Length);
                    _position = source.Position - (bytesReadFromSource - index);
                    OnProgress();
                }
            }
            finally
            {
                _endDateTime = DateTime.Now;

                d2v.WriteLine(" ff");
                d2v.WriteLine();
                d2v.WriteLine("FINISHED " + filmPercentage.ToString("0.00") + "% FILM");
                d2v.WriteLine();
                d2v.WriteLine();
                d2v.WriteLine();
                d2v.WriteLine();
                d2v.WriteLine("Start: " + _startDateTime.ToString());
                d2v.WriteLine("End: " + _endDateTime.ToString());
                d2v.WriteLine("Elapsed: " + _endDateTime.Subtract(_startDateTime).ToString());
                d2v.Flush();
                d2v.Dispose();
                source.Dispose();
            }

            // Finished.
            _progressPercent = 100;
        }

        /// <summary>
        /// Writes the frames data to the D2V file.
        /// </summary>
        /// <param name="d2v">The TextWriter to send log output to.</param>
        /// <param name="frames">The frames to write.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when d2v is null.</exception>
        /// <exception cref="System.ArgumentNullException">Thrown when frames is null.</exception>
        private void WriteFrames(TextWriter d2v, SortedList<Int32, MpegFrameType> frames)
        {
            if (d2v == null)
            {
                throw new ArgumentNullException("d2v");
            }
            if (frames == null)
            {
                throw new ArgumentNullException("frames");
            }
            StringBuilder sb = new StringBuilder();

            // Info Field
            Int32 infoField = 0x800; // Start of data line.
            if (closedGop)
            {
                infoField = infoField | 0x400;
            }
            if (progressiveSequence)
            {
                infoField = infoField | 0x200;
            }
            if (firstIframeAfterGop)
            {
                infoField = infoField | 0x100;
            }
            sb.Append(infoField.ToString("x2"));
            sb.Append(" ");

            // Matrix, File ID, Stream.Position, Skip, VobId, CellId
            sb.Append(matrixCoefficients.ToString());
            sb.Append(" 0 ");
            sb.Append((source.Position - buffer.Length + indexPositionSequenceHeader).ToString() + "/" +
                (source.Position - buffer.Length + indexPositionPictureHeader).ToString() + " 0 0 0 ");

            // Frames
            Int32 firstFrameTemporalSequence = 0;
            while (!_frames.ContainsKey(firstFrameTemporalSequence))
            {
                firstFrameTemporalSequence++;
            }

            foreach (KeyValuePair<Int32, MpegFrameType> kvp in _frames)
            {
                Int32 value = 0;

                if (_frames.ContainsKey(0))
                {
                    value = value | 0x80;
                }
                if (progressiveFrame)
                {
                    value = value | 0x40;
                }
                value = value | ((Int32)kvp.Value << 4);
                if (topFieldFirst)
                {
                    value = value | 2;
                }
                if (repeatFirstField)
                {
                    value = value | 1;
                }
                sb.Append(value.ToString("x2") + " ");
            }

            d2v.WriteLine(sb.ToString().TrimEnd());
            _frames.Clear();
        }
        
        /// <summary>
        /// Writes the Settings section to the D2V file.
        /// </summary>
        /// <param name="d2v">The TextWriter to send log output to.</param>
        /// <param name="aspect">The video aspect ratio.</param>
        /// <param name="width">The video width.</param>
        /// <param name="height">The video height.</param>
        /// <param name="framerateValue">The raw parsed framerate value.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when d2v is null.</exception>
        private void WriteD2vSettingsSection(TextWriter d2v, FicSprout.Bacicworx.Media.VideoAspectRatio aspect,
            Int32 width, Int32 height, Int32 framerateValue)
        {
            if (d2v == null)
            {
                throw new ArgumentNullException("d2v");
            }

            d2v.WriteLine("Stream_Type=0");
            d2v.WriteLine("MPEG_Type=2");
            d2v.WriteLine("iDCT_Algorithm=3");
            d2v.WriteLine("YUVRGB_Scale=1");
            d2v.WriteLine("Luminance_Filter=0,0");
            d2v.WriteLine("Clipping=0,0,0,0");
            if (aspect == FicSprout.Bacicworx.Media.VideoAspectRatio.Aspect_1_00)
            {
                d2v.WriteLine("Aspect_Ratio=1:1");
            }
            if (aspect == FicSprout.Bacicworx.Media.VideoAspectRatio.Aspect_1_33)
            {
                d2v.WriteLine("Aspect_Ratio=4:3");
            }
            if (aspect == FicSprout.Bacicworx.Media.VideoAspectRatio.Aspect_1_78)
            {
                d2v.WriteLine("Aspect_Ratio=16:9");
            }
            if (aspect == FicSprout.Bacicworx.Media.VideoAspectRatio.Aspect_2_21)
            {
                d2v.WriteLine("Aspect_Ratio=2.21:1");
            }
            d2v.WriteLine("Picture_Size=" + width.ToString() + "x" + height.ToString());
            d2v.WriteLine("Field_Operation=0");

            string framerateString = string.Empty;
            if (framerateValue == 1)
            {
                framerateString = "23976 (24000/1001)";
            }
            if (framerateValue == 2)
            {
                framerateString = "24000 (24000/1000)";
            }
            if (framerateValue == 3)
            {
                framerateString = "25000 (25000/1000)";
            }
            if (framerateValue == 4)
            {
                framerateString = "29970 (30000/1001)";
            }
            if (framerateValue == 5)
            {
                framerateString = "30000 (30000/1000)";
            }
            if (framerateValue == 6)
            {
                framerateString = "50000 (50000/1000)";
            }
            if (framerateValue == 7)
            {
                framerateString = "59940 (60000/1001)";
            }
            if (framerateValue == 8)
            {
                framerateString = "60000 (60000/1000)";
            } 
            
            d2v.WriteLine("Frame_Rate=" + framerateString);
            d2v.WriteLine("Location=0,0,0," + (_sourceLength / 2048).ToString("x2"));
            d2v.WriteLine();
        }

        #endregion

        #region Published Events

        /// <summary>
        /// Raised when the progress percent has updated.
        /// </summary>
        public event EventHandler Progress;

        /// <summary>
        /// Raises the OnProgress event.
        /// </summary>
        public void OnProgress()
        {
            // Event assigned?
            if (Progress != null)
            {
                // Throttle event if it has been less than 50 milliseconds since we last raised an event.
                /*if (DateTime.Now.Subtract(_lastRaisedDateTime).Milliseconds < 50)
                {
                    Thread.Sleep(50);
                }
                */
                _lastRaisedDateTime = DateTime.Now;
                Progress(this, EventArgs.Empty);
            }
        }

        #endregion
    }
}
