对于 FFmpeg.NET 开源项目,我的更改
项目地址: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() });
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 ?? ,
conversionOptions.CustomHeight ?? );
}
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()
});
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(,, ),
new TimeSpan(,, ),
new TimeSpan(,, ),
new TimeSpan(,, ),
new TimeSpan(,, ),
new TimeSpan(,, ),
};
var ffmpeg = new Engine(TaskffmpegNETExeFullPath);
//ffmpeg.Progress += OnProgress;
//ffmpeg.Data += OnDataSimpleShow;
ffmpeg.Error += OnError;
ffmpeg.Complete += OnComplete; var inputFile = new MediaFile(_videoFileFullPath); int i = ;
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 = , // 截图的宽度
CustomHeight = , // 截图的高度
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(, ''),
tsItem.Minutes.ToString().PadLeft(, ''),
tsItem.Seconds.ToString().PadLeft(, '')
)
});
}
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
运行结果
获取视频信息的运行结果
谢谢浏览!
对于 FFmpeg.NET 开源项目,我的更改的更多相关文章
- FFMPEG相关开源项目
1.FFmpeg build for android random architectures with example jnihttps://github.com/appunite/AndroidF ...
- Github上关于iOS的各种开源项目集合(强烈建议大家收藏,查看,总有一款你需要)
下拉刷新 EGOTableViewPullRefresh - 最早的下拉刷新控件. SVPullToRefresh - 下拉刷新控件. MJRefresh - 仅需一行代码就可以为UITableVie ...
- iOS及Mac开源项目和学习资料【超级全面】
UI 下拉刷新 EGOTableViewPullRefresh – 最早的下拉刷新控件. SVPullToRefresh – 下拉刷新控件. MJRefresh – 仅需一行代码就可以为UITable ...
- 直接拿来用!最火的iOS开源项目
1. AFNetworking 在众多iOS开源项目中,AFNetworking可以称得上是最受开发者欢迎的库项目.AFNetworking是一个轻量级的iOS.Mac OS X网络通信类库,现在是G ...
- iOS开发--iOS及Mac开源项目和学习资料
文/零距离仰望星空(简书作者)原文链接:http://www.jianshu.com/p/f6cdbc8192ba著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 原文出处:codecl ...
- iOS、mac开源项目及库汇总
原文地址:http://blog.csdn.net/qq_26359763/article/details/51076499 iOS每日一记------------之 中级完美大整理 iOS.m ...
- github上关于iOS的各种开源项目集合(转)
UI 下拉刷新 EGOTableViewPullRefresh - 最早的下拉刷新控件. SVPullToRefresh - 下拉刷新控件. MJRefresh - 仅需一行代码就可以为UITable ...
- 直接拿来用!最火的iOS开源项目(一~三)
结束了GitHub平台上“最受欢迎的Android开源项目”系列盘点之后,我们正式迎来了“GitHub上最受欢迎的iOS开源项目”系列盘点.今天,我们将介绍20个在GitHub上非常受开发者欢迎的iO ...
- 直接拿来用!最火的iOS开源项目(三)
相比Android,GitHub上的iOS开源项目更可谓是姹紫嫣红.尽管效果各异,但究其根源,却都是因为开发者本身对于某种效果的需求以及热爱.在“直接拿来用!最火的iOS开源项目”系列文章(一).(二 ...
随机推荐
- Lucene搜索核心代码TermInfosReader
TermInfosReader类是Lucene搜索的核心代码,所有的搜索最终都是落到通过term查询,TermInfosReader里定义了支持的基础的term查询功能. 前置知识: 词元字典文件(t ...
- AOD.NET实现数据库事物Transaction
在开始介绍文章主要内容前先简单说一下事务 1.事务介绍 事务是一种机制.是一种操作序列,它包含了一组数据库操作命令,这组命令要么全部执行,要么全部不执行.因此事务是一个不可分割的工作逻辑单元.在数据库 ...
- Mysql之架构篇
1.主从复制解决方案 这是MySQL自身提供的一种高可用解决方案,数据同步方法采用的是MySQL replication技术.MySQL replication就是从服务器到主服务器拉取二进制日志文件 ...
- Flask框架整理及配置文件
阅读目录 Flask目录结构(蓝图) pro_flask包的init.py文件, 用于注册所有的蓝图 manage.py文件,作为整个项目的启动文件 views包中的blog.py,必须要通过sess ...
- Visual Assist X(网上收集,仅供学习与研究,支持正版)
Visual AssistX是一款非常好的Microsoft Visual Studio插件,它可以完全集成到您的Microsoft开发环境中,升级了您的IDE, 在不改变编程习惯的同时就可以感受到V ...
- Python查看帮助---help函数
查看所有的关键字:help("keywords") 查看所有的modules:help("modules") 单看所有的modules中包含指定字符串的modu ...
- python GIL全局解释器锁,多线程多进程效率比较,进程池,协程,TCP服务端实现协程
GIL全局解释器锁 ''' python解释器: - Cpython C语言 - Jpython java ... 1.GIL: 全局解释器锁 - 翻译: 在同一个进程下开启的多线程,同一时刻只能有一 ...
- MyBatis 使用 foreach 批量插入
MyBatis 使用 foreach 批量插入 参考博文 老司机学习MyBatis之动态SQL使用foreach在MySQL中批量插入 使用MyBatis一次性插入多条数据时候可以使用 <for ...
- LG2530 「SHOI2001」化工厂装箱员 高维DP+记忆化搜索
问题描述 LG2530 题解 设\(opt[i][a][b][c][d]\)代表装到第\(i\)个后,第\(1,2,3\)手上分别还剩\(a,b,c\)个的最小操作数. 记忆化搜索即可. 启示:如果状 ...
- itsdangerous模块
使用场景: 在取消订阅时,可以在URL里序列化并且签名一个用户的ID或在任何的激活账户的链接或类似的情形下使用.这种情况下不需要生成一个一次性的token并把它们存到数据库中. 被签名的对象可以被存入 ...