Skip to content

Instantly share code, notes, and snippets.

@kokeiro001
Last active April 3, 2022 01:40
Show Gist options
  • Save kokeiro001/a8a6194296ea7973a55c6fe3c2865cf2 to your computer and use it in GitHub Desktop.
Save kokeiro001/a8a6194296ea7973a55c6fe3c2865cf2 to your computer and use it in GitHub Desktop.
ffmpegを使って動画から画像を抽出するやつ
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace FfmpegPipeSandbox
{
class Program
{
static readonly string InputMoviePath = @"D:\tmp\sample.mp4";
static readonly TimeSpan Interval = TimeSpan.FromSeconds(5);
static void Main(string[] args)
{
{
// パイプを使って指定時間の画像を一枚抽出する
var image = new ImageExtractor().ExtractImageByPipe(InputMoviePath, TimeSpan.FromSeconds(10));
image.Save(@"D:\tmp\pipe.jpg");
}
{
// 指定時間の画像を一枚抽出して直接ローカルストレージに保存する
new ImageExtractor().ExtractImage2LocalStorage(InputMoviePath, @"D:\tmp\storage.jpg", TimeSpan.FromSeconds(10));
}
{
// ひたすらシークしまくって一定時間ごとに画像抽出する。直接ローカルストレージに保存。早い。
new ImageExtractor().ExtractImagesAsync(InputMoviePath, @"D:\tmp\images_seek\", Interval).Wait();
}
{
// ひたすらシークしまくりつつパイプ使って一定時間ごとに抽出する。早い。
var images = new ImageExtractor().ExtractImagesByPipeAsync(InputMoviePath, Interval).Result;
for (int i = 0; i < images.Length; i++)
{
images[i].Save(Path.Combine($@"D:\tmp\images_pipe\{i:D4}.jpg"));
}
}
{
// ffmpegのrオプション使って一定時間ごとに画像抽出する。直接ローカルストレージに保存。あんまり早くない。
new ImageExtractor().ExtractImagesByOptionR(InputMoviePath, @"D:\tmp\images_option_r\", Interval);
}
}
}
public class ImageExtractor
{
// [Download FFmpeg](https://www.ffmpeg.org/download.html)
static readonly string FfmpegPath = @"C:\Lib\ffmpeg-20170824-f0f4888-win64-static\bin\ffmpeg.exe";
static readonly string FfprobePath = @"C:\Lib\ffmpeg-20170824-f0f4888-win64-static\bin\ffprobe.exe";
/// <summary>動画ファイルからパイプを用いて画像を抽出する</summary>
public Image ExtractImageByPipe(string inputMoviePath, TimeSpan extractTime)
{
var arguments = $"-ss {extractTime.TotalSeconds} -i \"{inputMoviePath}\" -vframes 1 -f image2 pipe:1";
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
FileName = FfmpegPath,
Arguments = arguments,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
};
process.Start();
var image = Image.FromStream(process.StandardOutput.BaseStream);
process.WaitForExit();
return image;
}
}
/// <summary>動画ファイルから画像を抽出し、ストレージに保存する</summary>
public void ExtractImage2LocalStorage(string inputMoviePath, string outputImagePath, TimeSpan extractTime)
{
var arguments = $"-y -ss {extractTime.TotalSeconds} -i \"{inputMoviePath}\" -vframes 1 -f image2 \"{outputImagePath}\"";
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
FileName = FfmpegPath,
Arguments = arguments,
CreateNoWindow = true,
UseShellExecute = false,
};
process.Start();
process.WaitForExit();
}
}
/// <summary>ひたすらシークしまくって一定時間ごとに画像抽出する</summary>
public async Task ExtractImagesAsync(string inputMoviePath, string outputImageDir, TimeSpan interval)
{
var duration = GetMovieDuration(inputMoviePath);
var seekSecEnum = Enumerable.Range(0, (int)(duration.TotalSeconds / interval.TotalSeconds))
.Select(x => new { SeekSec = x * interval.TotalSeconds, No = x });
var tasks = seekSecEnum
.AsParallel()
.Select(x => Task.Run(() =>
{
var outputImagePath = Path.Combine(outputImageDir, $"{x.No:D4}.jpeg");
var arguments = $"-y -ss {x.SeekSec} -i \"{inputMoviePath}\" -vframes 1 -f image2 \"{outputImagePath}\"";
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
FileName = FfmpegPath,
Arguments = arguments,
CreateNoWindow = true,
UseShellExecute = false,
};
process.Start();
process.WaitForExit();
}
}));
await Task.WhenAll(tasks);
}
/// <summary>ひたすらシークしまくりつつパイプ使って一定時間ごとに抽出する</summary>
public async Task<Image[]> ExtractImagesByPipeAsync(string inputMoviePath, TimeSpan interval)
{
var duration = GetMovieDuration(inputMoviePath);
var seekSecEnum = Enumerable.Range(0, (int)(duration.TotalSeconds / interval.TotalSeconds))
.Select(x => new { SeekSec = x * interval.TotalSeconds, No = x });
var tasks = seekSecEnum
.AsParallel()
.AsOrdered()
.Select(x => Task.Run(() =>
{
var arguments = $"-loglevel error -ss {x.SeekSec} -i \"{inputMoviePath}\" -vframes 1 -f image2 pipe:1";
var process = Process.Start(new ProcessStartInfo(FfmpegPath, arguments)
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = false,
});
var image = Image.FromStream(process.StandardOutput.BaseStream);
process.WaitForExit();
return image;
}));
return await Task.WhenAll(tasks);
}
/// <summary>ffmpegのrオプション使って一定時間ごとに画像抽出する</summary>
public void ExtractImagesByOptionR(string inputMoviePath, string outputImageDir, TimeSpan interval)
{
var fps = 1.0 / interval.TotalSeconds;
var arguments = $"-y -i \"{inputMoviePath}\" -r {fps} -f image2 \"{outputImageDir}%04d.jpg\"";
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
FileName = FfmpegPath,
Arguments = arguments,
CreateNoWindow = true,
UseShellExecute = false,
};
process.Start();
process.WaitForExit();
}
}
/// <summary>動画の長さを取得する</summary>
TimeSpan GetMovieDuration(string inputMoviePath)
{
using (var process = new Process())
{
string cmd = $"-v error -select_streams v:0 -show_entries stream=duration -sexagesimal -of default=noprint_wrappers=1:nokey=1 \"{inputMoviePath}\"";
process.StartInfo = new ProcessStartInfo()
{
FileName = FfprobePath,
Arguments = cmd,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
process.Start();
var durationStr = process.StandardOutput.ReadLine().Replace("\r\n", "");
var timeSpan = TimeSpan.Parse(durationStr);
process.WaitForExit();
return timeSpan;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment