본문 바로가기
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}";
    }
}