Last active
April 3, 2022 01:40
-
-
Save kokeiro001/a8a6194296ea7973a55c6fe3c2865cf2 to your computer and use it in GitHub Desktop.
ffmpegを使って動画から画像を抽出するやつ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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