Click here to Skip to main content
11,634,568 members (72,349 online)
Click here to Skip to main content

Another FFmpeg.exe C# Wrapper

, 6 Apr 2015 CPOL 25K 2K 30
Rate this:
Please Sign up or sign in to vote.
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:

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:

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:

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)

Share

About the Author

Bjørn
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.

You may also be interested in...

Comments and Discussions

 
Questionhow to run the code Pin
Member 1157828420-Apr-15 0:40
memberMember 1157828420-Apr-15 0:40 
AnswerRe: how to run the code Pin
Bjørn20-Apr-15 2:25
memberBjørn20-Apr-15 2:25 
QuestionHow to extract the audio source from a video via FFMpeg.dll ? Pin
Youzelin24-Dec-14 19:30
memberYouzelin24-Dec-14 19:30 
AnswerRe: How to extract the audio source from a video via FFMpeg.dll ? Pin
Bjørn4-Apr-15 10:44
memberBjørn4-Apr-15 10:44 
SuggestionA suggestion Pin
Nyarlatotep29-Aug-14 5:41
memberNyarlatotep29-Aug-14 5:41 
NewsRe: A suggestion Pin
Bjørn6-Apr-15 11:55
memberBjørn6-Apr-15 11:55 
GeneralThanks for sharing Pin
Kent K26-Jun-14 21:32
professionalKent K26-Jun-14 21:32 
GeneralRe: Thanks for sharing Pin
ozibella23-Jun-15 1:47
memberozibella23-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    Rant Rant    Admin Admin   

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150728.1 | Last Updated 6 Apr 2015
Article Copyright 2014 by Bjørn
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid