Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / C#

Another FFmpeg.exe C# Wrapper

Rate me:
Please Sign up or sign in to vote.
5.00/5 (21 votes)
6 Apr 2015CPOL5 min read 110.2K   5.7K   44   19
Using the FFmepg.exe to create video snapshots
 
If you're looking for a PInvoke wrapper for FFmpeg, have a look at my new article about that: Invoke FFmpeg to export frames

Introduction

I am creating a library for my video files and want it to be capable of extracting as many information from a file as possible so the user (mainly me) can be as lazy adding new videos as possible. Information also means video snapshots so you can instantly see what video file it is. This article will be about taking snapshots from almost any video file.

Background

I am using C# at work - mainly C# 3.5 CF - and to my shame I do not have much experience with other programming languages. Coming with working on the Compact Framework and different mobile devices comes a resignation, that if you want to do something the Windows OS on a device does not offer natively, you end up improvising a lot. Luckily that skill let me reach my goal for the video library - and it is highly improvised.

I tried to use ActiveX and its COM interface in C#. I managed to grab frames at specified positions after editing the COM interface - I do not exactly remember where but I had to replace a byte parameter with a IntPtr one. The disappointment came when I tried other video formats than my standard test video (AVI DivX MP3), e.g. a MP4 container with a H.264 video and AAC audio codec or a simple FLV video. The MediaDet class could not handle these types although I had the correct codecs installed. I did some research and found out that there seems to be an interface missing in these codecs that is used by ActiveX.

My second approach was to use one of the many FFmpeg wrappers which wrap the FFmpeg DLLs directly into C#. But they did not want to work for me. Some did have some functionality but seeking (one of the most important methods to grab snapshots) did not work without decoding the whole video up to this point which of course took too long.

I played a bit with the ffmpeg comand-line utilities and found that they actually did exactly what I need - just having to use files is a down.

Getting media information

First I want to explain how to use the command-line arguments to grab media information and snapshots.

The ffprobe.exe offers a command-line output of the video properties, using the following arguments:

-hide_banner Hides the banner at the beginning of the command-line output
 
-show_format Outputs general information about the video file
 
-show_streams Outputs information about every stream in the video file
 
-pretty Formats the output in a MS INI format with [/...] end tags
 
{file} The input file - has to be at the end

So the command-line should look like this:

ffprobe.exe -hide_banner -show_format -show_streams -pretty {video_file}

To read the command-line output with C# a process has to be started with a redirected output. So I wrote this helper method to execute a command and return its output after the process has terminated:

C#
private static string Execute(string exePath, string parameters)
{
    string result = String.Empty;

    using (Process p = new Process())
    {
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.FileName = exePath;
        p.StartInfo.Arguments = parameters;
        p.Start();
        p.WaitForExit();

        result = p.StandardOutput.ReadToEnd();
    }

    return result;
}

The output looks like this example:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'c:\file.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 1
    compatible_brands: isomavc1
    creation_time   : 2013-05-05 07:16:05
  Duration: 01:06:09.07, start: 0.000000, bitrate: 887 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt470bg), 720x576 [SAR 64:45 DAR 16:9], 706 kb/s, 25 fps, 25 tbr, 25k tbn, 50 tbc (default)
    Metadata:
      creation_time   : 2013-05-05 07:16:05
    Stream #0:1(und): Audio: aac (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 176 kb/s (default)
    Metadata:
      creation_time   : 2013-05-05 07:16:07
[STREAM]
index=0
codec_name=h264
codec_long_name=H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
profile=High
codec_type=video
codec_time_base=1/50
codec_tag_string=avc1
codec_tag=0x31637661
width=720
height=576
duration=1:06:08.760000
bit_rate=706.941000 Kbit/s
[/STREAM]
[STREAM]
index=1
codec_name=aac
codec_long_name=AAC (Advanced Audio Coding)
codec_type=audio
codec_time_base=1/48000
codec_tag_string=mp4a
codec_tag=0x6134706d
duration=1:06:09.066667
bit_rate=176.062000 Kbit/s
[/STREAM]
[FORMAT]
filename=c:\file.mp4
nb_streams=2
nb_programs=0
format_name=mov,mp4,m4a,3gp,3g2,mj2
format_long_name=QuickTime / MOV
duration=1:06:09.066667
size=419.768014 Mibyte
bit_rate=887.178000 Kbit/s
[/FORMAT]

So I only have to parse these information. In the attached class this will be done in the constructor.

Taking a snapshot

The most important aspect of the ffmpeg.exe syntax is that the arguments used always apply to the next mentioned file (input or output) - so you first state what options and then the file to use. I am going to use these options:

-hide_banner Hides the banner at the beginning of the command-line output
 
-ss {hh:mm:ss.fff} Jumps to the specified position in the video - if this is defined before the input the input video is seeked, if defined before the output the input is decoded up to the position
 
-i {file} Defines the input file
 
-r {n} Sets the forced frame rate
 
-t {n} Sets the length of frames to output
 
-f {format} Sets the forced format to use for input or output - I am using 'image2' to get a JPEG output
 
{file} The output file - has to be at the end

So the command-line called should be something like:

ffmpeg.exe -hide_banner -ss {timespan} -i {video_file} -r 1 -t 1 -f image2 {temp_file} 

To supress a command-line console being shown while the snapshot is taken - and depending on the video file and the computer's performance this can take up to a few seconds - I am using the same method as above to execute the command. C# offers a method to directly get a temporary file name so there is almost nothing unordinary here:

C#
public Bitmap GetSnapshot(TimeSpan atPosition, string filename)
{
    if (filename.Contains(' '))
        filename = "\"" + filename + "\"";

    string tmpFileName = Path.GetTempFileName();
    if (tmpFileName.Contains(' '))
        tmpFileName = "\"" + tmpFileName + "\"";

    string cmdParams = String.Format("-hide_banner -ss {0} -i {1} -r 1 -t 1 -f image2 {2}", 
        atPosition, filename, tmpFileName);

    Bitmap result = null;
    try
    {
        Execute(FFMPEG_EXE_PATH, cmdParams);

        if (File.Exists(tmpFileName))
        {
            byte[] fileData = File.ReadAllBytes(tmpFileName);
            result = new Bitmap(new MemoryStream(fileData));
            File.Delete(tmpFileName);
        }
    }
    catch { }

    return result;
} 

The tricky part is to load the saved bitmap into C#: If you create the image using new Bitmap(tmpFileName), the file is locked until the Bitmap is disposed so the tmpFileName cannot be deleted. So I am reading all bytes first and initialize the Bitmap using a MemoryStream.

Using the code

I wrapped these methods with a few other helper methods into the attached class. You can simply use it by using something like this:

C#
FFmpegMediaInfo info = new FFmpegMediaInfo("C:\file.mp4");
double length = info.Duration.TotalSeconds;
double step = length / 10;
double pos = 0.0;
Dictionary<TimeSpan, Bitmap> snapshots = new Dictionary<TimeSpan,Bitmap>();
while (pos < length)
{
    TimeSpan position = TimeSpan.FromSeconds(pos);
    Bitmap bmp = info.GetSnapshot(position);
    snapshots[position] = bmp;
    pos += step;
}

This example opens the file C:\file.mp4 - the video information is automatically loaded in the constructor so the duration is known. Then there is a snapshot taken every tenth of the videos duration and stored in a Dictionary with the TimeStamp as the key.

History

Changes in Version 1.2:

  • Added descrition comments to Properties
  • Added more comments to code
  • Added a try-catch-wrapper around Int32 and Int64 parsing
  • Using Split() instead of IndexOf() and Substring() for output line parsing
  • Added an example of ffprobe.exe output data

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
I'm working mainly on .NET Compact Framework C# on mobile devices at work. At home it's .NET Full Framework C# and a bit JavaScript.

Comments and Discussions

 
QuestionBreaking the OGG audio file into different slots based on the timeslots Pin
Member 1210844027-Dec-20 5:35
Member 1210844027-Dec-20 5:35 
QuestionStartTime not Defined Pin
Member 1470243229-Dec-19 8:02
Member 1470243229-Dec-19 8:02 
QuestionThe temp file is always empty Pin
Member 1216720723-Mar-16 2:20
Member 1216720723-Mar-16 2:20 
AnswerRe: The temp file is always empty Pin
Bjørn23-Mar-16 15:46
Bjørn23-Mar-16 15:46 
GeneralRe: The temp file is always empty Pin
Member 1216720727-Mar-16 5:08
Member 1216720727-Mar-16 5:08 
GeneralRe: The temp file is always empty Pin
Bjørn10-Apr-16 11:16
Bjørn10-Apr-16 11:16 
GeneralRe: The temp file is always empty Pin
LionLoki30-Mar-16 17:14
LionLoki30-Mar-16 17:14 
GeneralRe: The temp file is always empty Pin
Bjørn10-Apr-16 11:45
Bjørn10-Apr-16 11:45 
QuestionC# RTPS from IP CAMERA Pin
Michael Corradi14-Dec-15 2:15
Michael Corradi14-Dec-15 2:15 
AnswerRe: C# RTPS from IP CAMERA Pin
Bjørn2-Jan-16 11:47
Bjørn2-Jan-16 11:47 
Questionhow to run the code Pin
Divsheen20-Apr-15 0:40
Divsheen20-Apr-15 0:40 
AnswerRe: how to run the code Pin
Bjørn20-Apr-15 2:25
Bjørn20-Apr-15 2:25 
QuestionHow to extract the audio source from a video via FFMpeg.dll ? Pin
Youzelin24-Dec-14 19:30
Youzelin24-Dec-14 19:30 
AnswerRe: How to extract the audio source from a video via FFMpeg.dll ? Pin
Bjørn4-Apr-15 10:44
Bjørn4-Apr-15 10:44 
SuggestionA suggestion Pin
Nyarlatotep29-Aug-14 5:41
Nyarlatotep29-Aug-14 5:41 
NewsRe: A suggestion Pin
Bjørn6-Apr-15 11:55
Bjørn6-Apr-15 11:55 
GeneralThanks for sharing Pin
Kent K26-Jun-14 21:32
professionalKent K26-Jun-14 21:32 
I used this in a console app I had already but that didn't support video files yet. This was a quick way to get that working in the app. The app tests whether my media collection is ok. ..by testing whether each can be opened. Anyway, this works with a few modification as I only want to test whether the file can be opened so I reduced the number of thumbnails and such.

I did find that the app hung in the Execute method (called by the GetSnapshot method) at p.WaitForExit(), which I found was due to ffprobe.exe asking whether it could overwrite the file that was created in the temp directory previously. So, I added the '-y' ffmpeg parameter that says to overwrite any output files and that fixed it. I added this where it is defined in the GetSnapShot method.
GeneralRe: Thanks for sharing Pin
ozibella23-Jun-15 1:47
ozibella23-Jun-15 1:47 
GeneralMy vote of 5 Pin
Volynsky Alex18-May-14 4:57
professionalVolynsky Alex18-May-14 4:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.