“一仔播放器” WPF 的开源项目 (六 视频截图Ffmpeg、MediaToolkit 、图片缩略裁剪)
1.0 版,弄出来后,便迫不及待的和身边的宝妈分享,这下好了反馈了问题一推,例如通过配置文件填写路径、视频图片要自己截图,打开软件加载时没有加载过程等等,甚至软件名称怎么不是自己宝宝的?或许自己是弄软件都觉得简单,可人家不这样认为,麻烦死了。那只好优化一下了。
·
章节前言
1.0 版,弄出来后,便迫不及待的和身边的宝妈分享,这下好了反馈了问题一推,例如通过配置文件填写路径、视频图片要自己截图,打开软件加载时没有加载过程等等,甚至软件名称怎么不是自己宝宝的?
不要太高估你的客户,他们真的不会这些。那只好优化一下了,视频图片改为自动截取,为了美观需要剪裁一下
视频截图
万事揭百度,这些方法都来自于园里其他博主写的,Ffmpeg 是个不错的视频处理组件,功能强大,教程也多,但就是 ffmpeg.exe 软件有点大,相比 MediaToolkit 没太多教程,好处就是dll 要小很多,我使用这俩个都实现视频截图的方法,不过最终用 MediaToolkit ,毕竟软件打包要小点好。
Ffmpeg
前提要到官网下载 ffmpeg.exe 放置执行文件夹下
/// <summary>
/// Ffmpeg 命令行的方式提取
/// </summary>
public static class FfmpegHelper
{
private static System.Diagnostics.ProcessStartInfo cmdFfmpeg;
private static System.Diagnostics.ProcessStartInfo cmdFfprobe;
static FfmpegHelper()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
string ffmpegPath = "/usr/local/ffmpeg/ffmpeg";
string ffprobePath = "/usr/local/ffmpeg/ffprobe";
cmdFfmpeg = new System.Diagnostics.ProcessStartInfo(ffmpegPath);
cmdFfprobe = new System.Diagnostics.ProcessStartInfo(ffprobePath);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
string ffmpegPath = AppDomain.CurrentDomain.BaseDirectory + "ffmpeg\\ffmpeg.exe";
string ffprobePath = AppDomain.CurrentDomain.BaseDirectory + "ffmpeg\\ffprobe.exe";
cmdFfmpeg = new System.Diagnostics.ProcessStartInfo(ffmpegPath);
cmdFfprobe = new System.Diagnostics.ProcessStartInfo(ffprobePath);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
string ffmpegPath = "/usr/local/ffmpeg/ffmpeg";
string ffprobePath = "/usr/local/ffmpeg/ffprobe";
cmdFfmpeg = new System.Diagnostics.ProcessStartInfo(ffmpegPath);
cmdFfprobe = new System.Diagnostics.ProcessStartInfo(ffprobePath);
}
cmdFfmpeg.RedirectStandardError = false; // 输出错误
cmdFfmpeg.RedirectStandardOutput = true; //输出打印
cmdFfmpeg.UseShellExecute = false; //使用Shell
cmdFfmpeg.CreateNoWindow = true; //创建黑窗
cmdFfprobe.RedirectStandardError = false; //set false
cmdFfprobe.RedirectStandardOutput = true;
cmdFfprobe.UseShellExecute = false; //set true
cmdFfprobe.CreateNoWindow = true; //don't need the black window
}
/// <summary>
/// 获取视频信息
/// </summary>
/// <param name="path"></param>
public static async Task<string> GetVideoInfo(string path)
{
string command = $"-i {path} -print_format json -show_format -show_streams -show_data";
cmdFfprobe.Arguments = command;
System.Diagnostics.Process cmd = new System.Diagnostics.Process();
cmd.StartInfo = cmdFfprobe;
cmd.Start();
string InfoStr = await cmd.StandardOutput.ReadToEndAsync();
cmd.WaitForExit();
return InfoStr;
}
/// <summary>
/// 视频截图
/// </summary>
/// <param name="path">视频地址</param>
/// <param name="outPath">图片地址</param>
/// <param name="m">第几秒处</param>
public static void VideoScreenshot(string path, string outPath, int m = 5)
{
string command = $"-i \"{path}\" -ss " + m + " -y -q:v 7 -f image2 -t 0.001 \"{outPath}\"";
cmdFfmpeg.Arguments = command;
System.Diagnostics.Process cmd = new System.Diagnostics.Process();
cmd.StartInfo = cmdFfmpeg;
cmd.Start();
cmd.WaitForExit();
}
}
MediaToolkit
到NuGet去查找应用
public class VideoSnapshot
{
/// <summary>
/// 视频截图
/// </summary>
/// <param name="videoFilePath">视频文件</param>
/// <param name="outputImagePath">存放路径</param>
/// <param name="m">截取第?秒的视频帧</param>
public static void ExtractSnapshot(string videoFilePath, string outputImagePath, int m = 5)
{
TimeSpan position = TimeSpan.FromSeconds(m);
try
{
if (!File.Exists(videoFilePath))
{
Console.WriteLine("文件不存在!!!");
return;
}
// 创建 MediaFile 对象,指定输入和输出文件路径
var inputFile = new MediaFile { Filename = videoFilePath };
var outputFile = new MediaFile { Filename = outputImagePath };
// 创建 ConversionOptions 对象,并设置截图的时间点
var conversionOptions = new ConversionOptions { Seek = position };
// 使用Engine类来处理视频截图
using (var engine = new Engine())
{
// 获取视频文件的元数据信息
engine.GetMetadata(inputFile);
// 截取指定时间点的视频帧,并保存为图像文件
engine.GetThumbnail(inputFile, outputFile, conversionOptions);
}
Console.WriteLine($"视频截图成功:{outputImagePath}");
}
catch (Exception ex)
{
Console.WriteLine($"视频截图失败:{ex.Message}");
}
}
/*
engine.GetThumbnail(inputFile, outputFile, position);
engine 是一个 Engine 类型的对象,它是 MediaToolkit 库中的核心引擎,用于处理多媒体文件。
GetThumbnail 是 Engine 类的一个方法,用于从指定的多媒体文件中获取缩略图或截图。
inputFile 是 MediaFile 类型的对象,表示输入的多媒体文件。
outputFile 是 MediaFile 类型的对象,表示输出的缩略图或截图的文件。
position 是一个 TimeSpan 类型的参数,表示截图的时间点。
*/
}
缩略图、裁剪
视频截图后,为了美观需要对图片进行裁剪
/// <summary>
/// 缩略图
/// </summary>
public class ThumbnailMaker
{
/// <summary>
/// 制作图片的缩略图
/// </summary>
/// <param name="originalImage">原图</param>
/// <param name="width">指定的宽度</param>
/// <param name="height">指定的高度</param>
/// <param name="mode">指定的模式</param>
/// <remarks>
/// <paramref name="mode">
/// <para>ThumbnailMode.UsrHeightWidth:指定高宽缩放(可能变形)</para>
/// <para>ThumbnailMode.UsrHeightWidthBound:指定高宽缩放(可能变形,过小则不变)</para>
/// <para>ThumbnailMode.UsrWidth:指定宽,高按比例</para>
/// <para>ThumbnailMode.UsrWidthBound:指定宽(过小则不变),高按比例</para>
/// <para>ThumbnailMode.UsrHeight:指定高,宽按比例</para>
/// <para>ThumbnailMode.UsrHeightBound:指定高(过小则不变,宽按比例)</para>
/// </paramref>
/// </remarks>
/// <returns></retCut,urns>
public static Image MakeThumbnail(Image originalImage, int width, int height, ThumbnailMode mode)
{
int towidth = width;
int toheight = height;
int x = 0;
int y = 0;
int initWidth = originalImage.Width;
int initHeight = originalImage.Height;
switch (mode)
{
case ThumbnailMode.UsrHeightWidth: //指定高宽缩放(可能变形)
break;
case ThumbnailMode.UsrHeightWidthBound: //指定高宽缩放(可能变形)(过小则不变)
if (originalImage.Width <= width && originalImage.Height <= height)
{
return originalImage;
}
if (originalImage.Width < width)
{
towidth = originalImage.Width;
}
if (originalImage.Height < height)
{
toheight = originalImage.Height;
}
break;
case ThumbnailMode.UsrWidth: //指定宽,高按比例
toheight = originalImage.Height * width / originalImage.Width;
break;
case ThumbnailMode.UsrWidthBound: //指定宽(过小则不变),高按比例
if (originalImage.Width <= width)
{
return originalImage;
}
else
{
toheight = originalImage.Height * width / originalImage.Width;
}
break;
case ThumbnailMode.UsrHeight: //指定高,宽按比例
towidth = originalImage.Width * height / originalImage.Height;
break;
case ThumbnailMode.UsrHeightBound: //指定高(过小则不变),宽按比例
if (originalImage.Height <= height)
{
return originalImage;
}
else
{
towidth = originalImage.Width * height / originalImage.Height;
}
break;
case ThumbnailMode.Cut: //指定高宽裁减(不变形)
//计算宽高比
double srcScale = (double)originalImage.Width / (double)originalImage.Height;
double destScale = (double)towidth / (double)toheight;
//宽高比相同
if (srcScale - destScale >= 0 && srcScale - destScale <= 0.001)
{
x = 0;
y = 0;
initWidth = originalImage.Width;
initHeight = originalImage.Height;
}
//源宽高比大于目标宽高比
//(源的宽比目标的宽大)
else if (srcScale > destScale)
{
initWidth = originalImage.Height * towidth / toheight;
initHeight = originalImage.Height;
x = (originalImage.Width - initWidth) / 2;
y = 0;
}
//源宽高比小于目标宽高小,源的高度大于目标的高度
else
{
initWidth = originalImage.Width;
initHeight = originalImage.Width * height / towidth;
x = 0;
y = (originalImage.Height - initHeight) / 2;
}
break;
default:
break;
}
Image bitmap = new Bitmap(towidth, toheight);
//新建一个画板
using (Graphics g = Graphics.FromImage(bitmap))
{
//设置高质量插值法
g.CompositingQuality = CompositingQuality.HighQuality;
//设置高质量,低速段呈现的平滑程度
g.SmoothingMode = SmoothingMode.HighQuality;
//在指定的位置上,并按指定大小绘制原图片的指定部分
g.DrawImage(originalImage, new Rectangle(0, 0, towidth, toheight), new Rectangle(x, y, initWidth, initHeight), GraphicsUnit.Pixel);
}
return bitmap;
}
public static Image MakeThumbnail(Stream originalStream, int width, int height, ThumbnailMode mode)
{
Image originalImage = Image.FromStream(originalStream);
try
{
return MakeThumbnail(originalImage, width, height, mode);
}
catch (Exception)
{
originalStream.Dispose();
return null;
}
}
public static void MakeThumbnail(Image originalImage, string savePath, int width, int height, ThumbnailMode mode)
{
Image image = MakeThumbnail(originalImage, width, height, mode);
try
{
image.Save(savePath);
}
catch (Exception)
{
image.Dispose();
}
finally { image.Dispose(); }
}
public static void MakeThumbnail(string originalImagePath, string savePath, int width, int height, ThumbnailMode mode)
{
Image image = Image.FromFile(originalImagePath);
try
{
MakeThumbnail(image, savePath, width, height, mode);
}
catch (Exception)
{
image.Dispose();
}
finally
{
image.Dispose();
}
}
}
public enum ThumbnailMode
{
/// <summary>
/// 宽高缩放模式,可能变形
/// </summary>
UsrHeightWidth,
UsrHeightWidthBound,
/// <summary>
/// 指定宽度,高按比例
/// </summary>
UsrWidth,
/// <summary>
/// 指定宽(过小则不变),高按比例
/// </summary>
UsrWidthBound,
/// <summary>
/// 自定高度,宽按比例
/// </summary>
UsrHeight,
/// <summary>
/// 指定高(过小则不变),宽按比例
/// </summary>
UsrHeightBound,
/// <summary>
/// 剪切
/// </summary>
Cut,
NONE,
}
使用方法
项目中在遍历完文件夹后,便对没有图片的文件夹进行视频截图
/// <summary>
/// 创建缩略图
/// </summary>
void CreateThumbnails()
{
foreach (var item in VideoList)
{
//检测没有图片
if (item.PicturePath == @"pack://application:,,,/YiZaiPlayer;component/Resource/null.png")
{
string pathTemp = item.FolderPath + "\\temp.jpg"; //临时
string pathPreview = item.FolderPath + "\\preview.jpg"; //预览图
string[] _fileVideos = Utils.GitFileVideos(item.FolderPath);
//Common.FfmpegHelper.VideoScreenshot(_fileVideos[0], pathTemp); //临时预览图生成,方法一
Common.VideoSnapshot.ExtractSnapshot(_fileVideos[0], pathTemp); //临时预览图生成,方法二
if (File.Exists(pathTemp))
{
Common.ThumbnailMaker.MakeThumbnail(pathTemp, pathPreview, 200, 200, Common.ThumbnailMode.Cut);
item.PicturePath = pathPreview;
File.Delete(pathTemp);
}
}
}
}
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)