FFMPEG .Net Wrapper – Part 5. Console and WPF examples

We can complete our series with two examples of how to use FFMPEG wrapper in your applications. In first example we use console type application. The one thing we need to remember is that prior to actually starting encoding we have to subscribe to EncodingEngine events. To make things cleaner, I created class DisplayInfo that contains methods that we can use. We have to also remember that if we define encoding job Metadata property, we have to cast it back to the same data type.


    public class Program
    {
        static void Main(string[] args)
        {            
            var ffmpeg = new EncodingEngine(@"C:\ffmpeg\ffmpeg.exe");
            var encodingJob = new EncodingJob();
            var videoArgs = new VideoArgs();
            var display = new DisplayInfo();


            var inputFile = @"C:\input\testFile.wtv";
            var outputFile = @"C:\videos\testConverted.mkv";

           job.Arguments = videoArgs.Convert(inputFile,VideoEncoder.Libx264,
				 VideoResize.TV720p, VideoPreset.VeryFast,
				 ConstantRateFactor.CrfNormal, AudioCodec.Ac3,
				 outputFile);
            
            string title = "My conversion test file";

            encodingJob.Metadata = title;
	    
	    //subscribe to events
            ffmpeg.VideoEncoding += display.DisplayProgress;
            ffmpeg.VideoEncoded += display.DisplayCompleted;
            ffmpeg.Exited += display.DisplayExitCode;
            
            ffmpeg.DoWork(encodingJob);
           
 	    Console.WriteLine("Completed");
            Console.ReadLine();

        }       
    }


    public class DisplayInfo
    {

        public void DisplayProgress(object sender, EncodingEventArgs e)
        {
         	Console.WriteLine("Frame {0} Fps {1} Size {2} Time {3}
			Bitrate {4} Speed {5} Quantizer {6} Progress {7}",
                	e.Frame, e.Fps, e.Size, e.Time, e.Bitrate,
		        e.Speed, e.Quantizer, e.Progress);
        }


        public void DisplayCompleted(object sender, EncodedEventArgs e)
        {
            var metadata = (string)e.EncodingJob.Metadata;
            Console.WriteLine("Title : {0}", metadata );

        }

        public void DisplayExitCode(object sender, ExitedEventArgs e)
        {

            Console.WriteLine("ExitCode : {0}", e.ExitCode);


        }

    }

Second example shows how we can do the same thing in WPF project. We can use that same method in WinForms project too. The main problem we encounter when we want to send progress information to UI is that FFMPEG is running on different thread than UI. As a result our application would throw cross-thread not valid exception. To avoid this we have to create delegate that would make actual calls to UI control.

Microsoft simplified this process by creating Background Worker class. It has three events DoWork, ProgressChanged and RunWorkerCompleted plus methods that relate to these events. Each event uses predefined EventArgs . DoWork is self-explanatory. The other two methods are communicating with UI. Just like in previous example we have to create a method that would subscribe to VideoEncoding event from EncodingEngine class. For this, I created method called GetProgress. The only challenge over here is that VideoEncoding event sends our custom EncodingEventArgs and they are not what background worker expects. We have to transform them into ProgressChangedEventArgs. This is done in GetProgress method. We need to raise ReportProgress event and send two pieces of information, ProgressPercentage as integer and UserState object that could be any data type we define. I decided to create class called EncodingStats that would include all additional info I would like to display. This data is received by bw_ProgressChanged and send to UI controls, label and progress bar. Before we start encoding we need to remember to set maximum value in progress bar. For testing purposes you should find out file duration in seconds or use FFPROBE wrapper to retrieve this information directly from a file.


public partial class MainWindow : Window
    {
        private BackgroundWorker bw;
        private EncodingEngine ffmpeg;

        public MainWindow()
        {
            InitializeComponent();
            this.ffmpeg = new EncodingEngine(@"C:\ffmpeg\ffmpeg.exe");
            this.bw = new BackgroundWorker();
            
	    //subscribe to events
	    this.bw.DoWork += new DoWorkEventHandler(bw_DoWork);
            this.bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
            this.bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
            this.bw.WorkerReportsProgress = true;

            this.ffmpeg.VideoEncoding += GetProgress;
            
        }


        private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.label1.Content = "The job is: " + e.Result.ToString();
            this.button1.IsEnabled = true;
        }

        private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //set progress bar maximum value
	    //use ffprobe to get file duration
	   this.progressBarStatus.Maximum = 0;

	

	    //cast back to EncodingStats object
	    var encodingStats = (EncodingStats)e.UserState;

            this.label1.Content = e.ProgressPercentage.ToString() + "% complete" +
			 " Size: " + encodingStats.Size +
			 " Speed: " + encodingStats.Speed;

            this.progressBarStatus.Value = e.ProgressPercentage;

        }

        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            this.Encode();
            e.Result = "Completed";
        }

        public void Encode()
        {
            var arguments = new EncodingArgs();
            var encodingJob = new EncodingJob();
            var videoArgs = new VideoArgs();

            var inputFile = @"C:\input\testFile.wtv";
            var outputFile = @"C:\videos\testConverted.mkv";

            job.Arguments = videoArgs.Convert(inputFile,VideoEncoder.Libx264,
				 VideoResize.TV720p, VideoPreset.VeryFast,
				 ConstantRateFactor.CrfNormal, AudioCodec.Ac3,
				 outputFile);


            ffmpeg.DoWork(encodingJob);

        }

        public void GetProgress(object sender, EncodingEventArgs e)
        {
		//raise background worker event
		//send ProgressChangedEventArgs  - Progress Percentage and UserState object
            bw.ReportProgress((int)e.Progress, new EncodingStats { Size = e.Size,
					 Frame = e.Frame, Speed = e.Speed });
            
        }

        private void button1_Click_1(object sender, RoutedEventArgs e)
        {

            if (!this.bw.IsBusy)
            {
                this.bw.RunWorkerAsync();
                this.button1.IsEnabled = false;
            }
        }
    }


public class EncodingStats
    {
        public string Frame { get; set; }
        public string Fps { get; set; }
        public string Size { get; set; }
        public string Time { get; set; }
        public string Bitrate { get; set; }
        public string Speed { get; set; }
        public string Quantizer { get; set; }
        public string Data { get; set; }
        public double Progress { get; set; }

    }

This concludes the series on how to create FFMPEG. Net Wrapper. This was really interesting experience and there is one aspect of it that I find invaluable which is working with events. Sometimes this topic gets overlooked when you are learning C#. Unfortunately some of the tutorials I came across did not paid too much attention to this subject. I understand it is fairly advanced topic, but regardless of that it is one of the ways we can communicate between various objects. I hope you will find this series of articles helpful.

FFMPEG .Net Wrapper – Part 4. What happened to duration?

After looking at some of FFMPEG wrappers that are available, I noticed that some of them at least try to retrieve duration property directly from the console output of the file that is being encoded. I will try to explain why this is not such a good idea. First and foremost FFMPEG is note designed for this purpose. There is another tool called FFPROBE that is capable of retrieving all necessary data in friendlier JSON and XML formats. However let’s assume that we want to do that using FFMPEG. In the typical situation when we convert some audio or video file, right before we see statistics, FFMPEG displays all additional information about streams, metadata etc. There could be 40 to 50 lines of details including single tag we want which is file duration in seconds. If we take this console output and match it using Regex, we will find this tag and almost immediately we will get empty string. It is because FFMPEG output stream is constantly updated. We will need some way of retaining duration value as constant and this is where things get complicated. . If we include duration property in EncodingEventArgs, we will be sending empty strings to UI and duration will be shown only once.

To spare ourselves trouble is better to completely hide banner information. That’s why I included hard coded “-v quiet” option in Convert method. Option “-stats “ensures that we can only see actual conversion statistics. This way there will be no delay is sending that information to UI.

We could probe the file for single purpose of getting duration property. We can do that by omitting output file parameter. FFMPEG would complain that is missing something, but it would work.


ffmpeg.exe –i inputFile 

It’s questionable whether we can use DoWork method for this purpose. We probably could create another private method that would subscribe to ErrorDataReceived event. However it looks like we will be trying to do two different things with the same method, convert and probe. Another way will be to create separate DoProbe method or Prober class that will simply copy most of the code from EncodingEngine class. It sounds repetitive.

I will post later article about FFPROBE wrapper that I created. It has an option of selecting XML or JSON file formats. Output string can be easily parsed and manipulated and it contains much more information that what we can get through FFMPEG.

FFMPEG .Net Wrapper – Part 3. Working with Events

The way to start encoding with command line tool is by using Process class. In basic form we have to provide arguments and a path to executable file. We have to also initiate Start and WaitForExit methods. Now all we have to do is to wait for entire encoding process to finish. If we write some simple method that instantiates Process class in Windows Form or WPF application, we will see console window with FFMPEG statistics. However, if we would like to send some message to console before or after encoding process starts, we will see that there are actually two different console windows. One is used exclusively by FFMPEG, and the other one is used by our program. At this point things get bit complicated because we have to capture and parse FFMPEG output.

The proper way to do it is by redirecting FFMPEG output to our primary console window and read the stream internally. There are actually two different streams of data that we have to consider. One of them is called Standard Output and the second one is called Standard Error. In most cases command line tools use Standard Output for displaying current information to a user. FFMPEG is different in that regard. All the logging and progress information is tunneled through Standard Error.

There is an article on MSDN about redirecting Standard Output property. It describes in details things that we have to pay attention to. One thing we have to be aware is that we cannot and should not read both Standard Output and Standard Error outputs at the same. This would lead to the FFMPEG locks up. To avoid these potential deadlocks we will have to do reading operations asynchronously.

Process class allows us to raise events. The way events work is that we can send data through a pipeline called delegate. First we need to raise an event and verify that there is another object on other side that is listening. In order to receive the data, both object that sends data and listener must use the same data type. There are already predefined event types that we can use, but we can also create our own data types depending on what we want to communicate to other objects.

The flow of the operation is that we will subscribe to ErrorDataReceived event available to us in Process class. We will read this data in a private method that would raise event OnVideoEncoding defined in our class and transform data into a custom Event object called EncodingEventArgs.

Our class has two additional events, VideoEncoded and Exited. The first one would send object called EncodingJob when conversion process is completed. The Exited event would send Process exit code. The success code is 0, failed code is 1.



public class EncodingEngine
    {
        private Process _process;
        private string _encoderPath;
        
        public event EventHandler VideoEncoded;
        public event EventHandler VideoEncoding;
        public event EventHandler Exited;
        
        public EncodingEngine(string encoderPath)
        {
            _encoderPath = encoderPath;
            _process = new Process();

        }

        public void Cancel()
        {

            StreamWriter myStreamWriter = this._process.StandardInput;
            myStreamWriter.WriteLine("q");


        }

        public void DoWork(EncodingJob encodingJob)
        {

            this._process.EnableRaisingEvents = true;
            
	   this._process.OutputDataReceived += new DataReceivedEventHandler(this.GetStandardOutputDataReceived);
           
		//subscribe to event
            this._process.ErrorDataReceived += new DataReceivedEventHandler(this.GetStandardErrorDataReceived);

            this._process.Exited += new EventHandler(ProcessExited);

            this._process.StartInfo.FileName = _encoderPath;

            this._process.StartInfo.Arguments = encodingJob.Arguments;

            this._process.StartInfo.UseShellExecute = false;
            this._process.StartInfo.RedirectStandardError = true;
            this._process.StartInfo.RedirectStandardOutput = true;
            this._process.StartInfo.RedirectStandardInput = true;
            this._process.StartInfo.CreateNoWindow = true;
            this._process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

            this._process.Start();
            this._process.BeginErrorReadLine();
            this._process.BeginOutputReadLine();

            this._process.WaitForExit();
            this._process.Close();
            
		//raise event
             OnVideoEncoded(new EncodedEventArgs() {EncodingJob = encodingJob });

        }


        protected virtual void OnVideoEncoded(EncodedEventArgs e)
        {
		
            VideoEncoded?.Invoke(this,e);

        }

       
        protected virtual void OnVideoEncoding(EncodingEventArgs e)
        {

            VideoEncoding?.Invoke(this, e);

        }

        protected virtual void OnExit(ExitedEventArgs e)
        {

            Exited?.Invoke(this, e);

        }


        protected virtual void OnErrorReceived(ErrorEventArgs e)
        {

            ErrorReceived?.Invoke(this, e);

        }


        private void ProcessExited(object sender, EventArgs e)
        {
            OnExit(new ExitedEventArgs() {ExitCode = _process.ExitCode.ToString()});
          
        }

        private void GetStandardErrorDataReceived(object sender, DataReceivedEventArgs e)
        {

             //raise event        
            OnVideoEncoding(new EncodingEventArgs() {

                Frame = e.Data.GetRegexValue(RegexKey.Frame,RegexGroup.Two),             
                Fps = e.Data.GetRegexValue(RegexKey.Fps, RegexGroup.Two),
                Size = e.Data.GetRegexValue(RegexKey.Size, RegexGroup.Two),
                Time = e.Data.GetRegexValue(RegexKey.Time, RegexGroup.Two),
                Bitrate = e.Data.GetRegexValue(RegexKey.Bitrate, RegexGroup.Two),
                Speed = e.Data.GetRegexValue(RegexKey.Speed, RegexGroup.Two),
                Quantizer = e.Data.GetRegexValue(RegexKey.Quantizer, RegexGroup.Two),
                Progress = e.Data.GetRegexValue(RegexKey.Time, RegexGroup.Two).ParseTotalSeconds(),
                Data = e.Data } );
        
        }

        private void GetStandardOutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            //Console.WriteLine(e.Data);

        }


    }

public class EncodingEventArgs: EventArgs
    {  
        public string Frame { get; set; }
        public string Fps { get; set; }
        public string Size { get; set; }
        public string Time { get; set; }  
        public string Bitrate { get; set; }
        public string Speed { get; set;}
        public string Quantizer { get; set; }
        public string Data { get; set;}
        public double Progress { get; set; }
    }

public class EncodedEventArgs: EventArgs
    {          
        public EncodingJob EncodingJob {get;set;}
    }

public class ExitedEventArgs
    {
        public string ExitCode { get; set; }

    }

There is one more thing we have to talk about and that is EncodingJob.


public class EncodingJob
    {
        public string Arguments { get; set; }
        public object Metadata {get;set;}
    }

It has only two properties. Arguments property is rather straight forward. It would include input and output file plus required parameters. Metadata property could be any additional object that we would like to send to other objects that subscribe to VideoEncoded event. This could be useful if we would like to do some post-processing after video conversion is completed. The only thing we need to remember is that we have to cast Metadata back to the object that we initially send.

There is one more method in this class that we did not talk about and it is Cancel method. It would allow us to halt encoding process. In order to do that we have to send to FFMPEG keyboard key “q”. Unfortunately our interaction with FFMPEG is very limited. Note that for this to work we have redirected Standard Input too.

FFMPEG .Net Wrapper – Part 2. Parsing console output using Regex

Before we move on to explain how to convert and read FFMPEG output data at the same time, we have to decide what method to use for parsing encoder’s console output. During the encoding process FFMPEG displays some valuable data including current position, bitrate, speed etc. It looks something like this


frame=54314 fps=168 q=36.0 size= 108790kB time=00:30:10.30 bitrate= 492.3kbits/
frame=54364 fps=162 q=36.0 size= 108941kB time=00:30:11.97 bitrate= 492.5kbits/
frame=54466 fps=162 q=36.0 size= 109232kB time=00:30:15.38 bitrate= 492.9kbits/
frame=54572 fps=162 q=36.0 size= 109475kB time=00:30:18.91 bitrate= 493.1kbits/
frame=54696 fps=162 q=36.0 size= 109745kB time=00:30:23.05 bitrate= 493.1kbits/
frame=54845 fps=162 q=36.0 size= 109940kB time=00:30:28.02 bitrate= 492.7kbits/
frame=54978 fps=162 q=36.0 size= 110152kB time=00:30:32.46 bitrate= 492.4kbits/
frame=55117 fps=163 q=36.0 size= 110367kB time=00:30:37.10 bitrate= 492.1kbits/
frame=55206 fps=156 q=36.0 size= 110558kB time=00:30:40.07 bitrate= 492.2kbits/

The information that we can see is constantly updated. We have to read each line of data and retrieve value for each individual keyword. One of the way to do it is to split each line into array of words and then search for the items that we need. But I think much more efficient way is to use Regex.

Regex can be confusing and intimidating at the same time, but once you understand some basics you will realize that it is very powerful tool. Regex is designed to look for characters and it can distinguish between letter type characters and numeric type characters. To instruct Regex that you want to look for the given character you have to include back slash. To instruct Regex to search for the next character of the same type we include plus sign.


“\w+” 	search of letter type character and any letter character that follows it
“\d+” 	search for numeric type character and any numeric character that follows it

We have to remember that empty space is also recognized as a character and that we can use plus sign with it like this “\ +”

Let say that we want to find part of the string that starts with “frame= 357”. To do that we would have to write “frame= \d+”. Regex would match this and return entire part of the string including word “frame”, but that’s not what we want. We only really care about the numbers that follow the equal sign. The way to do it is to define groups of characters. The only thing is to add brackets that will divide what we are looking for into smaller sections. After modification our search pattern will look like this “(frame=) (\d+)”. Regex assigns group Id to each part enclosed within brackets. There are three groups in total

Group id 0 = “(frame=) (\d+)”
Group id 1 = “(frame=)
Group id 2 = “(\d+)”.

We are interested in group with id number 2. There is a tiny little problem. FFMPEG is not consistent with the way output data is formatted. Sometimes there is only one empty space character after equal sign. Next line could have multiple empty characters in between. Therefore we have to include plus sign in between groups


"(frame=) +(\d+)”

Now we have to create similar Regex search patterns for each item that we want to retrieve. Similarly to what we have done with FFMPEG arguments, we will store all Regex patterns in a dictionary. As I mentioned before FFMPEG output data varies sometimes. We have to ensure that there is an empty space right after equal sign. Regex will first replace “=” with “= ” and then perform matching operation. For that we need an object


public class RegexSearchPattern
    {
        public string OriginalKey { get; set;}
        public string ReplaceKey { get; set;}
        public string RegexSearchKey { get; set;}

    }

Below is a dictionary that we will be using


public class RegexPatterns
    {
        private Dictionary _patterns;

        public RegexPatterns ()
        {
            _patterns = new Dictionary();

            
            _patterns.Add("Frame", new RegexSearchPattern() {OriginalKey = "frame=", ReplaceKey = "frame= ", RegexSearchKey = @"(frame=) +(\d+)" });
            _patterns.Add("Fps", new RegexSearchPattern() { OriginalKey = "fps=", ReplaceKey = "fps= ", RegexSearchKey = @"(fps=) +(\d+)" });
            _patterns.Add("Time", new RegexSearchPattern() { OriginalKey = "time=", ReplaceKey = "time= ", RegexSearchKey = @"(time=) +(\d+:\d+:\d+.\d+)" });
            _patterns.Add("Size", new RegexSearchPattern() { OriginalKey = "size=", ReplaceKey = "size= ", RegexSearchKey = @"(size=) +(\d+)" });
            _patterns.Add("Bitrate", new RegexSearchPattern() { OriginalKey = "bitrate=", ReplaceKey = "bitrate= ", RegexSearchKey = @"(bitrate=) +(\d+.\d+)" });
            _patterns.Add("Speed", new RegexSearchPattern() { OriginalKey = "speed=", ReplaceKey = "speed= ", RegexSearchKey = @"(speed=) +(\d+.\d+)" });
            _patterns.Add("Quantizer", new RegexSearchPattern() { OriginalKey = "q=", ReplaceKey = "q= ", RegexSearchKey = @"(q=) +(\d+\.\d+)" });
           
        }


        public RegexSearchPattern GetValue(RegexKey regexKey)
        {

            RegexSearchPattern regexSearchPattern = new RegexSearchPattern();

            _patterns.TryGetValue(regexKey.ToString(), out regexSearchPattern);

            return regexSearchPattern;
        }

    }

We also need to define our enums


public enum RegexKey
    {
        Frame,
        Fps,
        Time,
        Size,
        Bitrate,
        Speed,
        Quantizer,
    }

public enum RegexGroup
    {
        All = 0,
        One = 1,
        Two = 2,
    }

To tie this together we need an extension method that would retrieve regex value from the string


public static class Extensions
    {

        public static string GetRegexValue(this string line, RegexKey regexKey, RegexGroup regexGroup)
        {
            string result = "";
            string replaceLine = "";

            var patterns = new RegexPatterns();
            var keys = patterns.GetValue(regexKey);

            if (line != null)
            {

                replaceLine = Regex.Replace(line, keys.OriginalKey, keys.ReplaceKey);
                var match = Regex.Match(replaceLine, keys.RegexSearchKey);

                result = match.Groups[(int)regexGroup].Value;


            }


             return result;



        }

        public static double ParseTotalSeconds(this string time)
        {
            double totalSeconds = 0;

            TimeSpan result;

            if (TimeSpan.TryParse(time, out result))
            {
                totalSeconds = result.TotalSeconds;
            }
            return totalSeconds;


        }


    }

FFMPEG .Net Wrapper – Part 1. Creating arguments

FFMPEG is a powerful command line tool that is widely used is all types of commercial and non-commercial applications. Chances are that if you used some type of video converter, it was FFMPEG that did all the heavy lifting. Using this command line tool is not the easiest thing to do. Official documentation is rich in various options that can be used in different situations. The standard way of interaction is through command line and it requires writing what we want to do in a certain order. Basically, the simplest example would look something like this


	ffmpeg.exe   -i inputFile  -“options”   outputFile

There could be slight modifications when switch “-i” that stands for input file is not the first item in the row, but overall in most cases we have to follow this pattern. If you ever tried to convert video by writing all switches and options in command line than you will know that this method is prone to errors. Some developers try different approach by creating batch scripts. The script would convert entire content of given folder. Not a bad idea, but what we lack is some level of flexibility. It is hard to reuse what we create when our needs change. In this case we need some different approach. The most common way is to create a wrapper that would simplify the process of passing arguments to the conversion tool. Google search will reveal that there are various projects available on GitHub for us to use. Some of them are older and development has already stopped. There are also commercial type projects that might be interesting to some of developers.

I decided to tackle this project mostly because I have a need for FFFMPEG wrapper in my main Web Application. This was actually interesting experience. I had a clear goal of creating something that can be easy modified and its functionality can be extended at the same time. The wrapper should be able to do couple things

    convert audio and video files
    have ability to create overload conversion methods
    provide feedback about current progress position to UI
    extend functionality by using various filters,
    extract frames, extract streams, cut and merge videos

As I mentioned before we need to pass required arguments to FFMPEG. It gets little bit complicated. The line below is an example of arguments that would be passed during video conversion


-vf scale=-1:720 -c:v libx264 -preset veryfast -crf 23 -c:a aac -b:a 160k  

Some of the parts of this line must remain constant and some of them can be modified. To make it simple we can change these small sections : “-1:720”, “libx264”, “veryfast”, “aac”, “160k”. These are parameters that would affect video resolution, converted file output format, encoding speed, audio type and bitrate. In some situations not all of these parameters are required in order to successfully convert video file. Therefore we have problem, because we should not retype these arguments for every possible combination of outcomes. Instead we need a method for combining multiple parts into one single argument line. And the perfect place for storing all our pieces is a Dictionary. We can search for keys defined with enums and return corresponding value.

Starting point is a class called EncodingArgs. It has private dictionary field called “_arguments” and new instance of the dictionary is created in the class constructor. There is also a method that retrieves value from a dictionary. For the demonstration purposes we include values that are required for converting video file. In addition we create necessary enum classes.


public class EncodingArgs
    {

        private Dictionary _arguments;
        

        public EncodingArgs()
        {
            _arguments = new Dictionary();

            
            //Audio codec
            _arguments.Add("Ac3", " -c:a ac3 ");
            _arguments.Add("Aac", " -c:a aac ");
            _arguments.Add("Mp3", " -c:a mp3 ");
            
            //Bitrate
            _arguments.Add("BitrateLow", " -ab 64k ");
            _arguments.Add("BitrateMedium", " -ab 112k ");
            _arguments.Add("BitrateNormal", " -ab 128k ");
            _arguments.Add("BitrateHigh", " -ab 160k ");
            _arguments.Add("BitrateVeryHigh", " -ab 192k ");

            //CRF
            _arguments.Add("CrfNormal", " -crf 23 ");
            _arguments.Add("CrfLow", " -crf 18 ");
            _arguments.Add("CrfHigh", " -crf 26 ");

            //Resize
            _arguments.Add("Mobile960", " -vf scale=960:-1 ");
            _arguments.Add("TV720p", " -vf scale=-1:720 ");
            _arguments.Add("FullHD1080p", " -vf scale=-1:1080 ");

            //Video Encoder
            _arguments.Add("Libx264", " -c:v libx264 ");
            _arguments.Add("Libxvid", " -c:v libxvid ");
            _arguments.Add("Libx265", " -c:v libx265 ");
            _arguments.Add("Mpeg2video", " -c:v Mpeg2video ");

            //Presets
            _arguments.Add("UltraFast", " -preset ultrafast ");
            _arguments.Add("SuperFast", " -preset superfast ");
            _arguments.Add("VeryFast", " -preset veryfast ");
            _arguments.Add("Faster", " -preset faster ");
            _arguments.Add("Fast", " -preset fast ");
            _arguments.Add("Medium", " -preset medium ");
            _arguments.Add("Slow", " -preset slow ");
            _arguments.Add("Slower", " -preset slower ");
            _arguments.Add("VerySlow", " -preset veryslow ");

        }

        public string GetValue(string argument)
        {

            string value = "";

            _arguments.TryGetValue(argument, out value);

            return value;
        }

    }

public enum VideoResize
    {
        Mobile960,
        TV720p,
        FullHD1080p
    }

public enum VideoPreset
    {
        UltraFast,
        SuperFast,
        VeryFast,
        Faster,
        Fast,
        Medium,
        Slow,
        Slower,
        VerySlow
    }

public enum VideoEncoder
    {
        Libx264,
        Libxvid,
        Libx265,
    }

public enum ConstantRateFactor
    {
        CrfLow,
        CrfNormal,
        CrfHigh
    }

public enum AudioCodec
    {
        Aac,
        Ac3,
        Mp3
    }


public enum Bitrate
    {
        BitrateLow,
        BitrateMedium,
        BitrateNormal,
        BitrateHigh,
        BitrateVeryHigh,
    }

As you can see the strings that are stored in a dictionary consist of both switches and predefined values for given option. This way we can construct command line arguments by arranging selected pieces. This action is done in another class called VideoArgs. We can also create another class called AudioArgs that would contain methods for audio conversion. Convert method returns a string that is properly formatted. However, note that even though we retrieve most of the values from the dictionary, there are still some option switches that need to be included.

“ -i ” input file
“ -y “ override existing file
“-v quiet” hide information that is displayed before encoding begins
“-stats ” displays encoding statistics

Some of these options can be included in dictionary like everything else however switch referring to input file should be hard coded. We can also create conversion overload methods that would include or omit some options. At this point I decided to leave them the way it is. I will explain later why I included option ” -v quiet -stats ” in this class instead of storing it in EncodingArgs dictionary.


public class VideoArgs
    {
       
        private EncodingArgs _arguments;
        
        public VideoArgs()
        {
          
            _arguments = new EncodingArgs();
      
        }
    
        public string Convert(string inputFile,VideoEncoder videoEncoder, VideoResize videoResize, VideoPreset videoPreset, ConstantRateFactor videoConstantRateFactor, AudioCodec audioCodec, Bitrate audioBitrate, string outputFile)
        {
            var arguments = "-i " + inputFile +
                 " -v quiet -stats " +
                 _arguments.GetValue(videoResize.ToString()) +
                 _arguments.GetValue(videoEncoder.ToString()) +
                 _arguments.GetValue(videoPreset.ToString()) +
                 _arguments.GetValue(ConstantRateFactor.ToString()) +
                 _arguments.GetValue(audioCodec.ToString()) +
                 _arguments.GetValue(audioBitrate.ToString()) +
                 " -y " + outputFile;

            return arguments;

        }

     
    }

That pesky WTV file format.

I spent whole week trying to put something together that would do a job. Soccer tournament Euro 2016 is around a corner and I wanted to record games and test encoding files with Handbrake.

I have to say that this whole experience is really frustrating. Handbrake supports WTV files recorded with Windows Media Center, but every once a while Handbrake desktop app stops conversion process for no apparent reasons. It would seem that there might be issue with wtv itself. So out of 10 files, it is most likely that at least one of them would fail to complete. Similar things were happening while I was testing HandbrakeCLI on my HTPC computer. Console window would just stay open for hours and job would refuse to finish.

I tried different approach and converted WTV file to MPEG format using FFMPEG and setup a job for HandbrakeCLI. Again result is unpredictable. Some files would complete and some simply would refuse to continue.

Since Handbrake was giving me so much trouble, I decided to do conversion in FFMPEG only. First change WTV file to MPEG and then convert MPEG to MKV format. It seemed in the beginning that FFMPEG does a better job, but at some point it failed too. Encoding process stalled for hours.

At this point I had to revert to converting WTV file to DVR-MS format first using Microsoft built-in WTVConverter.exe . This actually reduced errors significantly. I believe that it happened only once or twice and after reboot of HTPC job started and continued until it was finish.

Both Handbrake and FFMPEG support WTV format, however it is better to use following path for unattended video conversion:

  • WTV to DVR-MS using WTVConverter.exe
  • DVR-MS to MPEG using FFMPEG
  • MPEG to MKV/MP4 using FFMPEG or HandbrakeCLI

Encoding is fairly fast. I would say about 1:1, but with additional conversion steps whole process takes longer. Initially I was copying WTV file to temp folder and then do conversion. Once I eliminated that step, process is little bit faster. Recording that is 2-3 hours long can take between 20GB to 30GB. When comes to HTPC performance while doing encoding job, I did not notice major impact on either watching Live or recorded TV show. My HTPC is not the most powerful PC. It uses quad core AMD processor of older generation. I would provide specs later.

 

EDIT: I did about 50 jobs of recorded games and so far I did not have any single failure.