C#
[C#] ffmpeg 를 process 실행시 인코딩 예상 시간 구하기
by Jcoder
2021. 11. 3.
ffmepg 실행중
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.IO;
using System.Text;
using System.Runtime.InteropServices.ComTypes;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Concurrent;
namespace VideoEncoding_console
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Hello World!");
List<MetaData> metaDatas = new List<MetaData>();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Restart();
var test5 = await GetMetaDataAsync(@"C:\Users\tjdsk\Desktop\ed97dad20ef370904c790102d2fec112949ce8e987f10817ab7773c7ffc9b669.mp4");
stopwatch.Stop();
Console.WriteLine(test5);
Console.WriteLine(stopwatch.ElapsedMilliseconds + Environment.NewLine);
stopwatch.Restart();
var test6 = await GetMetaDataAsync(@"C:\Users\tjdsk\Desktop\tested97dad20ef370904c790102d2fec112949ce8e987f10817ab7773c7ffc9b669.mp4");
stopwatch.Stop();
Console.WriteLine(test6);
Console.WriteLine(stopwatch.ElapsedMilliseconds + Environment.NewLine);
metaDatas.Add(test5);
metaDatas.Add(test6);
foreach (var metaData in metaDatas.Where(metadata => metadata.FileFormat.Equals("f4v") && metadata.CompatibleBrands.Equals("isommp42m4v")))
{
stopwatch.Restart();
await ConvertingMP4Async(metaData);
stopwatch.Stop();
Console.WriteLine($"second : {stopwatch.ElapsedTicks / System.Diagnostics.Stopwatch.Frequency}");
}
Console.ReadLine();
}
static async Task<MetaData> GetMetaDataAsync(string fileFullPath)
{
// https://www.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10.pdf
// https://videocube.tistory.com/entry/MP4-%EB%B6%84%EC%84%9D-%ED%95%98%EA%B8%B0-MPEG4-%ED%8C%8C%ED%8A%B8-14
if (!System.IO.File.Exists(fileFullPath))
return null;
MetaData result = new MetaData();
using FileStream fileStream = new FileStream(fileFullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
if (fileStream.Length >= 4)
{
byte[] ftypbytesSize = new byte[4];
await fileStream.ReadAsync(ftypbytesSize.AsMemory(0, 4)); // ftyp
if (BitConverter.IsLittleEndian)
Array.Reverse(ftypbytesSize);
int ftypBoxSize = BitConverter.ToInt32(ftypbytesSize, 0); // ftyp 데이터 사이즈
if (ftypBoxSize > 0)
{
byte[] ftypbytes = new byte[ftypBoxSize];
int ftypReadbytes = await fileStream.ReadAsync(ftypbytes.AsMemory(0, ftypBoxSize)); // ftyp 데이터 읽기
string fileFormat = string.Empty;
string compatibleBrands = string.Empty;
string duration = string.Empty;
if (ftypReadbytes > 0)
{
fileFormat = Encoding.UTF8.GetString(ftypbytes, 4, 4).Trim(); // f4v 인지 isom 인지
compatibleBrands = Encoding.UTF8.GetString(ftypbytes, 12, 16).Replace("\0", "").Replace("\b", "").Trim(); // isommp42m4v 인지 isomiso2avc1mp41
byte[] mdatbytesSize = new byte[4];
fileStream.Position = fileStream.Position + 4; // mdat 파일 사이즈 부분으로 이동.
await fileStream.ReadAsync(mdatbytesSize.AsMemory(0, 4)); // mdat 사이즈 읽기
if (BitConverter.IsLittleEndian)
Array.Reverse(mdatbytesSize);
int mdatBoxSize = BitConverter.ToInt32(mdatbytesSize, 0);
if (mdatBoxSize > 0)
{
fileStream.Position = fileStream.Position + mdatBoxSize + 4 + 4 + 4; // mdat 데이터 부분 + moov + mvhd 사이즈 + mvhd
byte[] mvhdVersionbytes = new byte[1];
await fileStream.ReadAsync(mvhdVersionbytes.AsMemory(0, 1)); // Movie Header Atom 의 버전, 1: 64bits 0: 32: bits(시간)
if (mvhdVersionbytes[0] == 0) // 32비트 시간
{
fileStream.Position = fileStream.Position + 3 + 4 + 4; // Flags + Creation-time + Modification-tim
byte[] timescalebytes = new byte[4];
await fileStream.ReadAsync(timescalebytes.AsMemory(0, 4)); // timescale
byte[] durationbytes = new byte[4];
await fileStream.ReadAsync(durationbytes.AsMemory(0, 4)); // duration
if (BitConverter.IsLittleEndian)
{
Array.Reverse(timescalebytes);
Array.Reverse(durationbytes);
}
int timeScaleValue = BitConverter.ToInt32(timescalebytes, 0);
int durationValue = BitConverter.ToInt32(durationbytes, 0);
duration = $"{durationValue / timeScaleValue}";
}
else if (mvhdVersionbytes[0] == 1) // 64비트 시간
{
fileStream.Position = fileStream.Position + 3 + 8 + 8; // Flags + Creation-time + Modification-tim
byte[] timescalebytes = new byte[4];
await fileStream.ReadAsync(timescalebytes.AsMemory(0, 4)); // timescale
byte[] durationbytes = new byte[8];
await fileStream.ReadAsync(durationbytes.AsMemory(0, 8)); // duration
if (BitConverter.IsLittleEndian)
{
Array.Reverse(timescalebytes);
Array.Reverse(durationbytes);
}
int timeScaleValue = BitConverter.ToInt32(timescalebytes, 0);
long durationValue = BitConverter.ToInt64(durationbytes, 0);
duration = $"{durationValue / timeScaleValue}";
}
result.FullFilename = fileStream.Name;
result.Filename = Path.GetFileName(fileStream.Name);
result.FileSize = fileStream.Length.ToString();
result.Duration = duration;
result.FileFormat = fileFormat;
result.CompatibleBrands = compatibleBrands;
}
}
}
}
return string.IsNullOrEmpty(result.Filename) ? null : result;
}
static async Task ConvertingMP4Async(MetaData metaData)
{
if (!System.IO.File.Exists(metaData.FullFilename))
return;
string outputPath = Path.Combine(@"D:\ffmpegTest\Video", "converted" + metaData.Filename);
if (!System.IO.Directory.Exists(Path.GetDirectoryName(outputPath)))
System.IO.Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
//if (System.IO.File.Exists(outputPath))
//{
// var metadata = await GetMetaDataAsync(outputPath);
// if (metadata.FileFormat.Equals("isom") && metadata.CompatibleBrands.Equals("isomiso2avc1mp41"))
// {
// // 리네임
// Console.WriteLine($"이미 파일이 존재함. {outputPath}");
// return;
// }
//}
using var process = new Process();
process.StartInfo.FileName = @"D:\ffmpegTest\ffmpeg-4.4-essentials_build\bin\ffmpeg.exe";
process.StartInfo.Arguments = $"-i \"{metaData.FullFilename}\" \"{outputPath}\" -y";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
process.Start();
process.StandardInput.AutoFlush = true;
process.StandardInput.Close();
string ffmpeg라인읽기 = string.Empty;
double 동영상길이 = Convert.ToDouble(metaData.Duration);
var Duration = TimeSpan.FromSeconds(동영상길이);
while (!process.StandardError.EndOfStream)
{
ffmpeg라인읽기 = await process.StandardError.ReadLineAsync();
if (ffmpeg라인읽기.Contains("frame="))
{
// frame= 481 fps=118 q=23.0 size= 2304kB time=00:01:38.24 bitrate= 192.1kbits/s speed= 24x
// frame= 481 fps=118 q=23.0 Lsize= 2304kB time=00:01:38.24 bitrate= 192.1kbits/s speed= 24x
// frame= 1772 fps=224 q=-1.0 Lsize= 6648kB time=00:00:58.96 bitrate= 923.5kbits/s dup=1417 drop=0 speed=7.47x
var logSplits = ffmpeg라인읽기.Split(" ", StringSplitOptions.RemoveEmptyEntries);
string combine = string.Empty;
foreach (var item in logSplits)
combine += item;
if (combine.Contains("frame=") && combine.Contains("size=") && combine.Contains("time=") && combine.Contains("bitrate=") && combine.Contains("speed=")) // 해당 문자열이 포함되어 있을 때만
{
int ffmepgFrame = Convert.ToInt32(combine.Substring(combine.IndexOf("frame=") + 6, combine.IndexOf("fps=") - 6));
string ffmepgSize = combine.Substring(combine.IndexOf("size=") + 5, combine.IndexOf("time=") - (combine.IndexOf("size=") + 5));
string ffmepgTime = combine.Substring(combine.IndexOf("time=") + 5, combine.IndexOf("bitrate=") - (combine.IndexOf("time=") + 5)).Substring(0, 8);
string ffmepgbitrate = "0kbis/s";
if (combine.Contains("dup="))
ffmepgbitrate = combine.Substring(combine.IndexOf("bitrate=") + 8, combine.IndexOf("dup=") - (combine.IndexOf("bitrate=") + 8));
else
ffmepgbitrate = combine.Substring(combine.IndexOf("bitrate=") + 8, combine.IndexOf("speed=") - (combine.IndexOf("bitrate=") + 8));
string speed = combine.Substring(combine.IndexOf("speed=") + 6, combine.Length - (combine.IndexOf("speed=") + 6)).Replace("x", "");
var ffmepgTimes = ffmepgTime.Split(':');
double dConvertTime = (Convert.ToDouble(ffmepgTimes[0]) * 3600) + (Convert.ToDouble(ffmepgTimes[1]) * 60) + (Convert.ToDouble(ffmepgTimes[2]));
Console.WriteLine($"frame={ffmepgFrame}, ConvertingFileSize={ffmepgSize}, bitrate={ffmepgbitrate}, OriTime = {Duration} time={ffmepgTime}, speed={speed}x, Estimated Time Remaining = {TimeSpan.FromSeconds(동영상길이 - dConvertTime) / Convert.ToDouble(speed)}");
}
}
}
}
}
public class MetaData
{
public string FullFilename { get; set; }
public string Filename { get; set; }
public string Duration { get; set; }
public string FileSize { get; set; }
public string FileFormat { get; set; }
public string CompatibleBrands { get; set; }
public override string ToString() => $"Filename : {Filename}{Environment.NewLine}Duration: {Duration}{Environment.NewLine}Filesize: {FileSize}{Environment.NewLine}FileFormat: {FileFormat}{Environment.NewLine}CompatibleBrands: {CompatibleBrands}";
}
}