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

By | March 8, 2018

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.

Leave a Reply

Your email address will not be published. Required fields are marked *