After reading below article do not forget to checkout Important Update to Another Simple C# Wrapper For FFmpeg
When you want to encode uploaded videos to your website you are in luck if you use PHP, encoding uploaded videos to your web site on the fly quite easy on Linux based servers. Install FFmpeg and get a PHP wrapper for FFmpeg and you are good to go.
Story is a little bit different on Windows servers. There is no support from .Net framework; although there are couple of open source C# .Net wrappers for FFmpeg that encodes video on the fly, they are mostly incomplete. There are also commercial ones such as Mediahandler are not free/open-source.
In short, I couldn't get what i wanted from open-source ones so I've decided to write my own C# wrapper for FFmpeg.
My .Net skills are no where near good but here what i ended up with.
See usage in the example at the bottom of this post. Download whole project VideoEncoder.rar (48.54 kb) or download changes made by Anders (see comments) VideoEncoderAsync.rar (6.79 kb)
Don't forget to download FFmpeg win32 build and put in to FFmpeg folder in the project
My solution has 5 classes;
Encoder.cs, does the main job and gets FFmpegPath as input.
It has 3 methods :
- EncodeVideo: Encodes video with FFmpeg according to commands
- GetVideoThumbnail: Gets video thumbnail image without encoding video
- GetVideoInfo: Gets information about source video and assigns it to VideoFile class such as Duration, BitRate, AudioFormat, VideoFormat, Height, Width
To do: Implement a class to insert logo on to the video with ffmpeg -vhook
Source of Encoder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
|
using System; using System.Diagnostics; using System.IO; using System.Text.RegularExpressions; namespace VideoEncoder { public class Encoder { public EncodedVideo EncodeVideo(VideoFile input, string encodingCommand, string outputFile, bool getVideoThumbnail) { EncodedVideo encoded = new EncodedVideo(); Params = string .Format( "-i {0} {1} {2}" , input.Path, encodingCommand, outputFile); string output = RunProcess(Params); encoded.EncodingLog = output; encoded.EncodedVideoPath = outputFile; if (File.Exists(outputFile)) { encoded.Success = true ; //get thumbnail? if (getVideoThumbnail) { string saveThumbnailTo = outputFile + "_thumb.jpg" ; if (GetVideoThumbnail(input, saveThumbnailTo)) { encoded.ThumbnailPath = saveThumbnailTo; } } } else { encoded.Success = false ; } return encoded; } public bool GetVideoThumbnail(VideoFile input, string saveThumbnailTo) { if (!input.infoGathered) { GetVideoInfo(input); } int secs; //divide the duration in 3 to get a preview image in the middle of the clip //instead of a black image from the beginning. secs = ( int )Math.Round(TimeSpan.FromTicks(input.Duration.Ticks / 3).TotalSeconds, 0); string Params = string .Format( "-i {0} {1} -vcodec mjpeg -ss {2} -vframes 1 -an -f rawvideo" , input.Path, saveThumbnailTo, secs); string output = RunProcess(Params); if (File.Exists(saveThumbnailTo)) { return true ; } else { //try running again at frame 1 to get something Params = string .Format( "-i {0} {1} -vcodec mjpeg -ss {2} -vframes 1 -an -f rawvideo" , input.Path, saveThumbnailTo, 1); output = RunProcess(Params); if (File.Exists(saveThumbnailTo)) { return true ; } else { return false ; } } } public void GetVideoInfo(VideoFile input) { string Params = string .Format( "-i {0}" , input.Path); string output = RunProcess(Params); input.RawInfo = output; input.Duration = ExtractDuration(input.RawInfo); input.BitRate = ExtractBitrate(input.RawInfo); input.RawAudioFormat = ExtractRawAudioFormat(input.RawInfo); input.AudioFormat = ExtractAudioFormat(input.RawAudioFormat); input.RawVideoFormat = ExtractRawVideoFormat(input.RawInfo); input.VideoFormat = ExtractVideoFormat(input.RawVideoFormat); input.Width = ExtractVideoWidth(input.RawInfo); input.Height = ExtractVideoHeight(input.RawInfo); input.infoGathered = true ; } private string RunProcess( string Parameters) { //create a process info ProcessStartInfo oInfo = new ProcessStartInfo( this .FFmpegPath, Parameters); oInfo.UseShellExecute = false ; oInfo.CreateNoWindow = true ; oInfo.RedirectStandardOutput = true ; oInfo.RedirectStandardError = true ; //Create the output and streamreader to get the output string output = null ; StreamReader srOutput = null ; //try the process try { //run the process Process proc = System.Diagnostics.Process.Start(oInfo); proc.WaitForExit(); //get the output srOutput = proc.StandardError; //now put it in a string output = srOutput.ReadToEnd(); proc.Close(); } catch (Exception) { output = string .Empty; } finally { //now, if we succeded, close out the streamreader if (srOutput != null ) { srOutput.Close(); srOutput.Dispose(); } } return output; } public string FFmpegPath { get ; set ; } private string Params { get ; set ; } private TimeSpan ExtractDuration( string rawInfo) { TimeSpan t = new TimeSpan(0); Regex re = new Regex( "[D|d]uration:.((\d|:|\.)*)" , RegexOptions.Compiled); Match m = re.Match(rawInfo); if (m.Success) { string duration = m.Groups[1].Value; string [] timepieces = duration.Split( new char [] { ':' , '.' }); if (timepieces.Length == 4) { t = new TimeSpan(0, Convert.ToInt16(timepieces[0]), Convert.ToInt16(timepieces[1]), Convert.ToInt16(timepieces[2]), Convert.ToInt16(timepieces[3])); } } return t; } private double ExtractBitrate( string rawInfo) { Regex re = new Regex( "[B|b]itrate:.((\d|:)*)" , RegexOptions.Compiled); Match m = re.Match(rawInfo); double kb = 0.0; if (m.Success) { Double.TryParse(m.Groups[1].Value, out kb); } return kb; } private string ExtractRawAudioFormat( string rawInfo) { string a = string .Empty; Regex re = new Regex( "[A|a]udio:.*" , RegexOptions.Compiled); Match m = re.Match(rawInfo); if (m.Success) { a = m.Value; } return a.Replace( "Audio: " , "" ); } private string ExtractAudioFormat( string rawAudioFormat) { string [] parts = rawAudioFormat.Split( new string [] { ", " }, StringSplitOptions.None); return parts[0].Replace( "Audio: " , "" ); } private string ExtractRawVideoFormat( string rawInfo) { string v = string .Empty; Regex re = new Regex( "[V|v]ideo:.*" , RegexOptions.Compiled); Match m = re.Match(rawInfo); if (m.Success) { v = m.Value; } return v.Replace( "Video: " , "" ); ; } private string ExtractVideoFormat( string rawVideoFormat) { string [] parts = rawVideoFormat.Split( new string [] { ", " }, StringSplitOptions.None); return parts[0].Replace( "Video: " , "" ); } private int ExtractVideoWidth( string rawInfo) { int width = 0; Regex re = new Regex( "(\d{2,4})x(\d{2,4})" , RegexOptions.Compiled); Match m = re.Match(rawInfo); if (m.Success) { int .TryParse(m.Groups[1].Value, out width); } return width; } private int ExtractVideoHeight( string rawInfo) { int height = 0; Regex re = new Regex( "(\d{2,4})x(\d{2,4})" , RegexOptions.Compiled); Match m = re.Match(rawInfo); if (m.Success) { int .TryParse(m.Groups[2].Value, out height); } return height; } } } |
EncodedVideo.cs is output package for Encode method
Source of EncodedVideo.cs
1
2
3
4
5
6
7
8
9
10
11
|
namespace VideoEncoder { public class EncodedVideo { public string EncodedVideoPath { get ; set ; } public string ThumbnailPath { get ; set ; } public string EncodingLog { get ; set ; } public bool Success { get ; set ; } } } |
VideoFile.cs holds properties of input/source video
Source of VideoFile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
using System; using System.IO; namespace VideoEncoder { public class VideoFile { private string _Path; public string Path { get { return _Path; } set { _Path = value; } } public TimeSpan Duration { get ; set ; } public double BitRate { get ; set ; } public string RawAudioFormat { get ; set ; } public string AudioFormat { get ; set ; } public string RawVideoFormat { get ; set ; } public string VideoFormat { get ; set ; } public int Height { get ; set ; } public int Width { get ; set ; } public string RawInfo { get ; set ; } public bool infoGathered { get ; set ; } public VideoFile( string path) { _Path = path; Initialize(); } private void Initialize() { this .infoGathered = false ; if ( string .IsNullOrEmpty(_Path)) { throw new Exception( "Video file Path not set or empty." ); } if (!File.Exists(_Path)) { throw new Exception( "The video file " + _Path + " does not exist." ); } } } } |
Project also have two helper classes QuickAudioEncodingCommands.cs and QuickVideoEncodingCommands.cs to make video encoding easier. I simple created FFmpeg command-line snippets for easy encoding
Source of QuickAudioEncodingCommands.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/// /// /// Ready made encoding commands for FFmpeg /// Use when calling EncodeVideo commands as string encodingCommand /// Add remove as you like /// /// /// public class QuickAudioEncodingCommands { //mp3 public static string MP3128Kbps = "-y -ab 128k -ar 44100" ; public static string MP396Kbps = "-y -ab 96k -ar 44100" ; public static string MP364Kbps = "-y -ab 64k -ar 44100" ; public static string MP332Kbps = "-y -ab 32k -ar 44100" ; } |
Source of QuickVideoEncodingCommands.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
/// /// /// Ready made encoding commands for FFmpeg /// Use when calling EncodeVideo commands as string encodingCommand /// Add remove as you like /// /// /// namespace VideoEncoder { public class QuickVideoEncodingCommands { //-b static string LQVideoBitrate = "256k" ; static string MQVideoBitrate = "512k" ; static string HQVideoBitrate = "756k" ; static string VHQVideoBitrate = "1024k" ; //-ab static string LQAudioBitrate = "32k" ; static string MQAudioBitrate = "64k" ; static string HQAudioBitrate = "96k" ; static string VHQAudioBitrate = "128k" ; //-ar static string LQAudioSamplingFrequency = "22050" ; static string MQAudioSamplingFrequency = "44100" ; static string HQAudioSamplingFrequency = "44100" ; //-s static string SQCIF = "sqcif" ; //128x96 static string QCIF = "qcif" ; //176x144 static string QVGA = "qvga" ; //320x240 static string CIF = "cif" ; //352x288 static string VGA = "vga" ; //640x480 static string SVGA = "svga" ; //800x600 // todo //insert logo // //string LogoPath ="/Path/to/transparent/png"; //string PositionX ="0"; //string PositionY ="0"; //string.Format("-vhook "vhook/imlib2.dll -x {0} -y {1} -i {2}"", PositionX,PositionY,LogoPath); //flv public static string FLVLowQualityQCIF = string .Format( "-y -b {0} -ab {1} -ar {2} -s {3} -f flv" , LQVideoBitrate, LQAudioBitrate, LQAudioSamplingFrequency, QVGA); public static string FLVMediumQualityCIF = string .Format( "-y -b {0} -ab {1} -ar {2} -s {3} -f flv" , MQVideoBitrate, MQAudioBitrate, MQAudioSamplingFrequency, CIF); public static string FLVHighQualityVGA = string .Format( "-y -b {0} -ab {1} -ar {2} -s {3} -f flv" , HQVideoBitrate, HQAudioBitrate, HQAudioSamplingFrequency, VGA); public static string FLVVeryHighQualitySVGA = string .Format( "-y -b {0} -ab {1} -ar {2} -s {3} -f flv" , VHQVideoBitrate, VHQAudioBitrate, HQAudioSamplingFrequency, SVGA); public static string FLVLowQualityKeepOriginalSize = string .Format( "-y -b {0} -ab {1} -ar {2} -f flv" , LQVideoBitrate, LQAudioBitrate, LQAudioSamplingFrequency, QVGA); public static string FLVMediumQualityKeepOriginalSize = string .Format( "-y -b {0} -ab {1} -ar {2} -f flv" , MQVideoBitrate, MQAudioBitrate, MQAudioSamplingFrequency, CIF); public static string FLVHighQualityKeepOriginalSize = string .Format( "-y -b {0} -ab {1} -ar {2} -f flv" , HQVideoBitrate, HQAudioBitrate, HQAudioSamplingFrequency, VGA); public static string FLVVeryHighQualityKeepOriginalSize = string .Format( "-y -b {0} -ab {1} -ar {2} -f flv" , VHQVideoBitrate, VHQAudioBitrate, HQAudioSamplingFrequency, SVGA); //3gp public static string THREEGPLowQualitySQCIF = string .Format( "-y -acodec aac -ac 1 -b {0} -ab {1} -ar {2} -s {3} -f 3gp" , LQVideoBitrate, LQAudioBitrate, LQAudioSamplingFrequency, SQCIF); public static string THREEGPMediumQualityQCIF = string .Format( "-y -acodec aac -b {0} -ab {1} -ar {2} -s {3} -f 3gp" , MQVideoBitrate, MQAudioBitrate, MQAudioSamplingFrequency, QCIF); public static string THREEGPHighQualityCIF = string .Format( "-y -acodec aac -b {0} -ab {1} -ar {2} -s {3} -f 3gp" , VHQVideoBitrate, VHQAudioBitrate, HQAudioSamplingFrequency, CIF); //mp4 public static string MP4LowQualityKeepOriginalSize = string .Format( "-y -b {0} -ab {1} -ar {2} -f mp4" , LQVideoBitrate, LQAudioBitrate, LQAudioSamplingFrequency, QVGA); public static string MP4MediumQualityKeepOriginalSize = string .Format( "-y -b {0} -ab {1} -ar {2} -f mp4" , MQVideoBitrate, MQAudioBitrate, MQAudioSamplingFrequency, CIF); public static string MP4HighQualityKeepOriginalSize = string .Format( "-y -b {0} -ab {1} -ar {2} -f mp4" , HQVideoBitrate, HQAudioBitrate, HQAudioSamplingFrequency, VGA); public static string MP4LowQualityQVGA = string .Format( "-y -b {0} -ab {1} -ar {2} -s {3} -f mp4" , LQVideoBitrate, LQAudioBitrate, LQAudioSamplingFrequency, QVGA); public static string MP4MediumQualityCIF = string .Format( "-y -b {0} -ab {1} -ar {2} -s {3} -f mp4" , MQVideoBitrate, MQAudioBitrate, MQAudioSamplingFrequency, CIF); public static string MP4HighQualityVGA = string .Format( "-y -b {0} -ab {1} -ar {2} -s {3} -f mp4" , HQVideoBitrate, HQAudioBitrate, HQAudioSamplingFrequency, VGA); //WMV public static string WMVLowQualityQVGA = string .Format( "-y -vcodec wmv2 -acodec wmav2 -b {0} -ab {1} -ar {2} -s {3}" , LQVideoBitrate, LQAudioBitrate, LQAudioSamplingFrequency, QVGA); public static string WMVMediumQualityCIF = string .Format( "-y -vcodec wmv2 -acodec wmav2 -b {0} -ab {1} -ar {2} -s {3}" , MQVideoBitrate, MQAudioBitrate, MQAudioSamplingFrequency, CIF); public static string WMVHighQualityVGA = string .Format( "-y -vcodec wmv2 -acodec wmav2 -b {0} -ab {1} -ar {2} -s {3}" , HQVideoBitrate, HQAudioBitrate, HQAudioSamplingFrequency, VGA); public static string WMVVeryHighQualitySVGA = string .Format( "-y -vcodec wmv2 -acodec wmav2 -b {0} -ab {1} -ar {2} -s {3}" , VHQVideoBitrate, VHQAudioBitrate, HQAudioSamplingFrequency, SVGA); public static string WMVLowQualityKeepOriginalSize = string .Format( "-y -vcodec wmv2 -acodec wmav2 -b {0} -ab {1} -ar {2}" , LQVideoBitrate, LQAudioBitrate, LQAudioSamplingFrequency, QVGA); public static string WMVMediumQualityKeepOriginalSize = string .Format( "-y -vcodec wmv2 -acodec wmav2 -b {0} -ab {1} -ar {2}" , MQVideoBitrate, MQAudioBitrate, MQAudioSamplingFrequency, CIF); public static string WMVHighQualityKeepOriginalSize = string .Format( "-y -vcodec wmv2 -acodec wmav2 -b {0} -ab {1} -ar {2}" , HQVideoBitrate, HQAudioBitrate, HQAudioSamplingFrequency, VGA); public static string WMVVeryHighQualityKeepOriginalSize = string .Format( "-y -vcodec wmv2 -acodec wmav2 -b {0} -ab {1} -ar {2}" , VHQVideoBitrate, VHQAudioBitrate, HQAudioSamplingFrequency, SVGA); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
using System; using VideoEncoder; public partial class _Default : System.Web.UI.Page { protected void Page_Load( object sender, EventArgs e) { Encoder enc = new Encoder(); enc.FFmpegPath = Server.MapPath( "~/FFmpeg/ffmpeg.exe" ); VideoFile inputv = new VideoFile(Server.MapPath( "~/WorkingFolder/SourceVideo.wmv" )); string outputVPath = Server.MapPath( "~/WorkingFolder/OutputVideo.flv" ); string saveThumbnailTo = Server.MapPath( "~/WorkingFolder/OutputVideoThumbnail.jpg" ); // to get video thumbnail call enc.GetVideoThumbnail(inputv,saveThumbnailTo); // to encode video call EncodedVideo encoded = enc.EncodeVideo(inputv, QuickVideoEncodingCommands.FLVVeryHighQualityKeepOriginalSize, outputVPath, true ); if (encoded.Success) { Response.Write(encoded.ThumbnailPath); Response.Write( "<br>" ); Response.Write(encoded.EncodedVideoPath); Response.Write( "<br>" ); Response.Write(encoded.EncodingLog); } else { Response.Write(encoded.EncodingLog); } } } |
This project borrowed some code from http://jasonjano.wordpress.com/2010/02/09/a-simple-c-wrapper-for-ffmpeg/