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

By | March 5, 2018

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;


        }


    }

Leave a Reply

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