代码改变世界

对于 FFmpeg.NET 开源项目,我的更改

  音乐让我说  阅读(2708)  评论(2编辑  收藏  举报

项目地址:https://github.com/cmxl/FFmpeg.NET

官方介绍

.NET wrapper for common ffmpeg tasks


FFmpeg.NET provides a straightforward interface for handling media data, making tasks such as converting, slicing and editing both audio and video completely effortless.

Under the hood, FFmpeg.NET is a .NET wrapper for FFmpeg; a free (LGPLv2.1) multimedia framework containing multiple audio and video codecs, supporting muxing, demuxing and transcoding tasks on many media formats.

Some major parts are taken from https://github.com/AydinAdn/MediaToolkit. Many features have been refactored. The library has been ported to Netstandard and made threadsafe.

You need to provide the ffmpeg executable path to the Engine constructor.

 

 

关于下载 FFmpeg

 网站:http://ffmpeg.org/   然后点击“Download” 或者直接跳转到 https://ffmpeg.zeranoe.com/builds/

 

官方示例

代码如下:

复制代码
using FFmpeg.NET.Events;
using System;
using System.Threading.Tasks;

namespace FFmpeg.NET.Sample
{
    internal class Program
    {
        private static async Task Main(string[] args)
        {
            try
            {
                var inputFile = new MediaFile(@"..\..\..\..\..\tests\FFmpeg.NET.Tests\MediaFiles\SampleVideo_1280x720_1mb.mp4");
                var outputFile = new MediaFile(@"output.mkv");
                var thumbNailFile = new MediaFile(@"thumb.png");

                var ffmpeg = new Engine(@"..\..\..\..\..\lib\ffmpeg\v4\ffmpeg.exe");
                ffmpeg.Progress += OnProgress;
                ffmpeg.Data += OnData;
                ffmpeg.Error += OnError;
                ffmpeg.Complete += OnComplete;
                var output = await ffmpeg.ConvertAsync(inputFile, outputFile);
                var thumbNail = await ffmpeg.GetThumbnailAsync(output, thumbNailFile, new ConversionOptions { Seek = TimeSpan.FromSeconds(3) });
                var metadata = await ffmpeg.GetMetaDataAsync(output);
                Console.WriteLine(metadata.FileInfo.FullName);
                Console.WriteLine(metadata);
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc);
            }
            finally
            {
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
        }

        private static void OnProgress(object sender, ConversionProgressEventArgs e)
        {
            Console.WriteLine("[{0} => {1}]", e.Input.FileInfo.Name, e.Output?.FileInfo.Name);
            Console.WriteLine("Bitrate: {0}", e.Bitrate);
            Console.WriteLine("Fps: {0}", e.Fps);
            Console.WriteLine("Frame: {0}", e.Frame);
            Console.WriteLine("ProcessedDuration: {0}", e.ProcessedDuration);
            Console.WriteLine("Size: {0} kb", e.SizeKb);
            Console.WriteLine("TotalDuration: {0}\n", e.TotalDuration);
        }

        private static void OnData(object sender, ConversionDataEventArgs e)
            => Console.WriteLine("[{0} => {1}]: {2}", e.Input.FileInfo.Name, e.Output?.FileInfo.Name, e.Data);

        private static void OnComplete(object sender, ConversionCompleteEventArgs e)
            => Console.WriteLine("Completed conversion from {0} to {1}", e.Input.FileInfo.FullName, e.Output?.FileInfo.FullName);

        private static void OnError(object sender, ConversionErrorEventArgs e)
            => Console.WriteLine("[{0} => {1}]: Error: {2}\n{3}", e.Input.FileInfo.Name, e.Output?.FileInfo.Name, e.Exception.ExitCode, e.Exception.InnerException);
    }
}
复制代码

 

我对源代码的更改

1. 在 FFmpeg.NET.ConversionOptions 类 

 

文本代码:

复制代码
        /// <summary>
        /// 额外增加的 FFmpeg 参数字符串(注意:特殊字符请注意转义)
        /// </summary>
        public string ExtraFFmpegArgs { get; set; } = null;

        /// <summary>
        /// FFmpeg 的 DrawText 参数字符串(注意:特殊字符请注意转义)
        /// </summary>
        public string FFmpegDrawTextArgs { get; set; } = null;
复制代码

 

 

 

2.  在 FFmpeg.NET.FFmpegArgumentBuilder 类的 GetThumbnail 方法

 

 

文本代码:

复制代码
            // Video size / resolution
            if (conversionOptions.VideoSize == VideoSize.Custom)
            {
                commandBuilder.AppendFormat(" -s {0}:{1} ", 
                    conversionOptions.CustomWidth ?? 100, 
                    conversionOptions.CustomHeight ?? 100);
            }
            if (!string.IsNullOrEmpty(conversionOptions.FFmpegDrawTextArgs))
            {
                commandBuilder.AppendFormat(" -vf drawtext=\"{0}\" ", conversionOptions.FFmpegDrawTextArgs);
            }
            if (!string.IsNullOrEmpty(conversionOptions.ExtraFFmpegArgs))
            {
                commandBuilder.AppendFormat(" {0} ", conversionOptions.ExtraFFmpegArgs);
            }
复制代码

 

 

3. 在 FFmpeg.NET.FFmpegArgumentBuilder 类的 Convert 方法

 

文本代码:

            if (!string.IsNullOrEmpty(conversionOptions.FFmpegDrawTextArgs))
            {
                commandBuilder.AppendFormat(" -vf drawtext=\"{0}\" ", conversionOptions.FFmpegDrawTextArgs);
            }
            if (!string.IsNullOrEmpty(conversionOptions.ExtraFFmpegArgs))
            {
                commandBuilder.AppendFormat(" {0} ", conversionOptions.ExtraFFmpegArgs);
            }

 

 

 

 

 

我的 WinForm 示例

首先需要在 web.config 或 app.config 中配置

  <appSettings>
    <!-- Task FFmpeg.NET 需要的参数 -->
    <add key="TaskffmpegNETExeFullPath" value="D:\参考资料\C#\FFmpeg_Binary\ffmpeg-20190325-6e42021-win32and64-shared\v4\ffmpeg.exe" />
  </appSettings>

 

代码如下:

 

复制代码
    public partial class ffmpegTest02 : FormBase
    {
        private static readonly string TaskffmpegNETExeFullPath = ConfigurationManager.AppSettings["TaskffmpegNETExeFullPath"];

        string _videoFileFullPath = @"D:\Workspace\TestVideo.mp4";

        public ffmpegTest02()
        {
            base.InitForm();
            InitializeComponent();
            base.InitControls(this.listInfoLog);
        }

        private void OnProgress(object sender, ConversionProgressEventArgs e)
        {
            ShowAndLog(string.Format("[{0} => {1}]", e.Input.FileInfo.Name, e.Output?.FileInfo.Name), false, null);
            ShowAndLog(string.Format("Bitrate: {0}", e.Bitrate), false, null);
            ShowAndLog(string.Format("Fps: {0}", e.Fps), false, null);
            ShowAndLog(string.Format("Frame: {0}", e.Frame), false, null);
            ShowAndLog(string.Format("ProcessedDuration: {0}", e.ProcessedDuration), false, null);
            ShowAndLog(string.Format("Size: {0} kb", e.SizeKb), false, null);
            ShowAndLog(string.Format("TotalDuration: {0}\n", e.TotalDuration), false, null);
        }

        /// <summary>
        /// 此事件短时间(比如:1秒以内)会调用多次
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnData(object sender, ConversionDataEventArgs e)
        {
            ShowAndLog(string.Format("[从源文件 {0} 到目标文件 {1}],数据 {2}", e.Input.FileInfo.Name, e.Output?.FileInfo.Name, e.Data),
                false,
                null);
        }

        /// <summary>
        /// 此事件短时间(比如:1秒以内)会调用多次
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDataSimpleShow(object sender, ConversionDataEventArgs e)
        {
            ShowAndLog(string.Format("[从源文件 {0} 到目标文件 {1}]。", e.Input.FileInfo.Name, e.Output?.FileInfo.Name),
                false,
                null);
        }

        private void OnComplete(object sender, ConversionCompleteEventArgs e)
        {
            ShowAndLog(string.Format("从 {0} 到 {1} 处理完成。 ", e.Input.FileInfo.FullName, e.Output?.FileInfo.FullName),
                false,
                null);
        }

        private void OnError(object sender, ConversionErrorEventArgs e)
        {
            ShowAndLog(string.Format("[{0} => {1}]: 错误: {2}\n{3}", e.Input.FileInfo.Name, e.Output?.FileInfo.Name, e.Exception.ExitCode, e.Exception.InnerException),
                false,
                null);
        }

        private async void btnStartConvertAndSnapshot_Click(object sender, EventArgs e)
        {
            string mkvOutputFileFullPath = string.Format(@"D:\Workspace\TestVideo-{0}.mkv", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
            string mkvThumbOutputFileFullPath = string.Format(@"D:\Workspace\TestVideo-mkv-thumb-{0}.png", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
            try
            {
                var inputFile = new MediaFile(_videoFileFullPath);
                var outputFile = new MediaFile(mkvOutputFileFullPath);
                var thumbNailFile = new MediaFile(mkvThumbOutputFileFullPath);

                var ffmpeg = new Engine(TaskffmpegNETExeFullPath);
                //ffmpeg.Progress += OnProgress;
                //ffmpeg.Data += OnDataSimpleShow;
                ffmpeg.Error += OnError;
                ffmpeg.Complete += OnComplete;
                var output = await ffmpeg.ConvertAsync(inputFile, outputFile);
                var thumbNail = await ffmpeg.GetThumbnailAsync(output, thumbNailFile, new ConversionOptions
                {
                    Seek = TimeSpan.FromSeconds(61)
                });
                var metadata = await ffmpeg.GetMetaDataAsync(output);
                Console.WriteLine(metadata.FileInfo.FullName);
                Console.WriteLine(metadata);
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc);
            }
        }

        private async void btnStartSnapshot_Click(object sender, EventArgs e)
        {
            ShowAndLog("ffmpeg 准备开始转换,请稍后...", false, null);
            Stopwatch globalWatch = Stopwatch.StartNew();
            TimeSpan[] timeSpanArray = new TimeSpan[]
            {
                new TimeSpan(0,0, 5),
                new TimeSpan(0,1, 5),
                new TimeSpan(0,2, 5),
                new TimeSpan(0,3, 5),
                new TimeSpan(0,4, 5),
                new TimeSpan(0,4, 55),
            };
            var ffmpeg = new Engine(TaskffmpegNETExeFullPath);
            //ffmpeg.Progress += OnProgress;
            //ffmpeg.Data += OnDataSimpleShow;
            ffmpeg.Error += OnError;
            ffmpeg.Complete += OnComplete;

            var inputFile = new MediaFile(_videoFileFullPath);

            int i = 0;
            string mkvThumbOutputFileFullPath;
            List<string> allThumbFullName = new List<string>();
            foreach (TimeSpan tsItem in timeSpanArray)
            {
                i++;
                mkvThumbOutputFileFullPath = string.Format(@"D:\Workspace\TestVideo-mp4-thumb-{0}-{1}.jpg", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss.fff"), i);
                allThumbFullName.Add(mkvThumbOutputFileFullPath);
                var thumbNailFile = new MediaFile(mkvThumbOutputFileFullPath);
                await ffmpeg.GetThumbnailAsync(inputFile, thumbNailFile, new ConversionOptions
                {
                    Seek = tsItem,
                    //下面表示3行代码表示需要自定义截图的尺寸
                    VideoSize = VideoSize.Custom, 
                    CustomWidth = 800, // 截图的宽度
                    CustomHeight = 450, // 截图的高度
                    FFmpegDrawTextArgs = string.Format("borderw=10:bordercolor=white:fontcolor=#A9A9A8:fontsize=100:fontfile=FreeSerif.ttf:text='{0}\\:{1}\\:{2}':x =(w-text_w-50):y=(h-text_h-50)",
                        tsItem.Hours.ToString().PadLeft(2, '0'),
                        tsItem.Minutes.ToString().PadLeft(2, '0'),
                        tsItem.Seconds.ToString().PadLeft(2, '0')
                    )
                });
            }
            ShowAndLog("ffmpeg 转换完成。结果如下:", false,null);
            globalWatch.Stop();
            ShowAndLog(string.Format("运行结束!共耗时 {0} 毫秒。", globalWatch.ElapsedMilliseconds), false, null);
        }

        private async void btnGetVideoInfo_Click(object sender, EventArgs e)
        {
            ShowAndLog("ffmpeg 准备开始获取,请稍后...", false, null);
            Stopwatch globalWatch = Stopwatch.StartNew();
            var ffmpeg = new Engine(TaskffmpegNETExeFullPath);
            //ffmpeg.Progress += OnProgress;
            //ffmpeg.Data += OnDataSimpleShow;
            ffmpeg.Error += OnError;
            ffmpeg.Complete += OnComplete;

            var inputFile = new MediaFile(_videoFileFullPath);
            MetaData md = await ffmpeg.GetMetaDataAsync(inputFile);

            ShowAndLog(string.Format("Duration:{0}", md.Duration), false, null);
            ShowAndLog(string.Format("VideoData:{0}", JsonHelper.SerializeToJson(md.VideoData)), false, null);
            ShowAndLog(string.Format("AudioData:{0}", JsonHelper.SerializeToJson(md.AudioData)), false, null);

            globalWatch.Stop();
            ShowAndLog(string.Format("运行结束!共耗时 {0} 毫秒。", globalWatch.ElapsedMilliseconds), false, null);

        }
    }
复制代码

 

 

 

 

我的 UI

 

我的FFmpeg配置

获取截图,FFmpeg 运行的命令大概是:

ffmpeg.exe -ss 00:00:05 -i TestVideo.mp4 -vframes 1 -s  800:450   hello.jpg

其中, -ss 表示从第 5 秒开始截图,  -i 表示视频文件的路径,-vframes 表示截取 1 帧, -s 表示截取宽度为 800,高度为 450 像素的图片,hello.jpg 表示图片的名称。

 

其它的 FFmpeg 配置

1.下面是 FFmpeg 配置是在截图的时候,在图片的正中间显示 “当前时间”

ffmpeg.exe -ss 00:00:05 -i TestVideo.mp4 -vframes 1 -s  800:450 -vf  drawtext="fontcolor=red:fontsize=50:fontfile=FreeSerif.ttf:text='%{localtime\:%H\\\:%M\\\:%S}':x =(w-text_w)/2:y=(h-text_h)/ 2"   hello.jpg

 

2.下面是 FFmpeg 配置是在截图的时候,在图片的右下角(距离右边100像素,距离底部100像素)显示 “当前时间”

 

ffmpeg.exe -ss 00:00:05 -i TestVideo.mp4 -vframes 1 -s  800:450 -vf  drawtext="fontcolor=red:fontsize=50:fontfile=FreeSerif.ttf:text='%{localtime\:%H\\\:%M\\\:%S}':x =(w-text_w-100):y=(h-text_h-100)"   hello.jpg

 

3. 显示当前播放进度

 

ffmpeg.exe -ss 00:00:07 -i TestVideo.mp4 -vframes 1 -s  800:450 -vf  drawtext="fontcolor=red:fontsize=50:fontfile=FreeSerif.ttf:text='hms\: %{pts\:hms}, flt\: %{pts\:flt}':x =(w-text_w-50):y=(h-text_h-50)"   hello.jpg

 

4. 经过实际测试,暂时还是无法自动获取播放进度,只好把时间写死在参数中了

 

ffmpeg.exe -ss 00:04:45 -i TestVideo.mp4 -vframes 1 -s  800:450 -vf  drawtext="fontcolor=red:fontsize=50:fontfile=FreeSerif.ttf:text='00\:04\:45':x =(w-text_w-50):y=(h-text_h-50)"   hello.jpg

 

运行结果

 

 

 

 

 

 

 

 

 

 

 

 

获取视频信息的运行结果

 

 

 

谢谢浏览!

 

点击右上角即可分享
微信分享提示