using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

namespace DownKyi.Core.FFmpeg
{
    public static class FFmpegHelper
    {
        private const string Tag = "FFmpegHelper";

        private static readonly bool is64Bit = false;
        private static readonly string exec = "";

        static FFmpegHelper()
        {
            is64Bit = IntPtr.Size == 8;

            if (is64Bit)
            {
                exec = Path.Combine(Environment.CurrentDirectory, "ffmpeg.exe");
            }
            else
            {
                exec = Path.Combine(Environment.CurrentDirectory, "ffmpeg.exe");
            }
        }

        /// <summary>
        /// 合并音频和视频
        /// </summary>
        /// <param name="video1">音频</param>
        /// <param name="video2">视频</param>
        /// <param name="destVideo"></param>
        public static bool MergeVideo(string video1, string video2, string destVideo)
        {
            string param = $"-y -i \"{video1}\" -i \"{video2}\" -acodec copy -vcodec copy -f mp4 \"{destVideo}\"";
            if (video1 == null || !File.Exists(video1))
            {
                param = $"-y -i \"{video2}\" -acodec copy -vcodec copy -f mp4 \"{destVideo}\"";
            }
            if (video2 == null || !File.Exists(video2))
            {
                param = $"-y -i \"{video1}\" -acodec copy \"{destVideo}\"";
            }
            if (!File.Exists(video1) && !File.Exists(video2)) { return false; }

            // 如果存在
            try { File.Delete(destVideo); }
            catch (IOException e)
            {
                Console.WriteLine("MergeVideo()发生IO异常: {0}", e);
                Logging.LogManager.Error(Tag, e);
                return false;
            }

            ExcuteProcess(exec, param, null, (s, e) => Console.WriteLine(e.Data));

            try
            {
                if (video1 != null) { File.Delete(video1); }
                if (video2 != null) { File.Delete(video2); }
            }
            catch (IOException e)
            {
                Console.WriteLine("MergeVideo()发生IO异常: {0}", e);
                Logging.LogManager.Error(Tag, e);
            }

            return true;
        }

        /// <summary>
        /// 拼接多个视频
        /// </summary>
        /// <param name="workingDirectory"></param>
        /// <param name="flvFiles"></param>
        /// <param name="destVideo"></param>
        /// <returns></returns>
        public static bool ConcatVideo(string workingDirectory, List<string> flvFiles, string destVideo)
        {
            // contact的文件名,不包含路径
            string concatFileName = Guid.NewGuid().ToString("N") + "_concat.txt";
            try
            {
                string contact = "";
                foreach (string flv in flvFiles)
                {
                    contact += $"file '{flv}'\n";
                }

                FileStream fileStream = new FileStream(workingDirectory + "/" + concatFileName, FileMode.Create);
                StreamWriter streamWriter = new StreamWriter(fileStream);
                //开始写入
                streamWriter.Write(contact);
                //清空缓冲区
                streamWriter.Flush();
                //关闭流
                streamWriter.Close();
                fileStream.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine("ConcatVideo()发生异常: {0}", e);
                Logging.LogManager.Error(Tag, e);
                return false;
            }

            // ffmpeg -y -f concat -safe 0 -i filelist.txt -c copy output.mkv
            // 加上-y,表示如果有同名文件,则默认覆盖
            string param = $"-y -f concat -safe 0 -i {concatFileName} -c copy \"{destVideo}\" -y";
            ExcuteProcess(exec, param, workingDirectory, (s, e) => Console.WriteLine(e.Data));

            // 删除临时文件
            try
            {
                // 删除concat文件
                File.Delete(workingDirectory + "/" + concatFileName);

                foreach (string flv in flvFiles)
                {
                    File.Delete(flv);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("ConcatVideo()发生异常: {0}", e);
                Logging.LogManager.Error(Tag, e);
            }

            return true;
        }

        /// <summary>
        /// 去水印,非常消耗cpu资源
        /// </summary>
        /// <param name="video"></param>
        /// <param name="destVideo"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="action"></param>
        public static void Delogo(string video, string destVideo, int x, int y, int width, int height, Action<string> action)
        {
            // ffmpeg -y -i "video.mp4" -vf delogo=x=1670:y=50:w=180:h=70:show=1 "delogo.mp4"
            string param = $"-y -i \"{video}\" -vf delogo=x={x}:y={y}:w={width}:h={height}:show=0 \"{destVideo}\" -hide_banner";
            ExcuteProcess(exec, param, null, (s, e) =>
            {
                Console.WriteLine(e.Data);
                action.Invoke(e.Data);
            });
        }

        /// <summary>
        /// 从一个视频中仅提取音频
        /// </summary>
        /// <param name="video">源视频</param>
        /// <param name="audio">目标音频</param>
        /// <param name="action">输出信息</param>
        public static void ExtractAudio(string video, string audio, Action<string> action)
        {
            // 抽取音频命令
            // ffmpeg -i 3.mp4 -vn -y -acodec copy 3.aac
            // ffmpeg -i 3.mp4 -vn -y -acodec copy 3.m4a
            string param = $"-i \"{video}\" -vn -y -acodec copy \"{audio}\" -hide_banner";
            ExcuteProcess(exec, param,
                null, (s, e) =>
                {
                    Console.WriteLine(e.Data);
                    action.Invoke(e.Data);
                });
        }

        /// <summary>
        /// 从一个视频中仅提取视频
        /// </summary>
        /// <param name="video">源视频</param>
        /// <param name="destVideo">目标视频</param>
        /// <param name="action">输出信息</param>
        public static void ExtractVideo(string video, string destVideo, Action<string> action)
        {
            // 提取视频 (Extract Video)
            // ffmpeg -i Life.of.Pi.has.subtitles.mkv -vcodec copy –an videoNoAudioSubtitle.mp4
            string param = $"-i \"{video}\" -y -vcodec copy -an \"{destVideo}\" -hide_banner";
            ExcuteProcess(exec, param,
                null, (s, e) =>
                {
                    Console.WriteLine(e.Data);
                    action.Invoke(e.Data);
                });
        }

        /// <summary>
        /// 提取视频的帧,输出为图片
        /// </summary>
        /// <param name="video"></param>
        /// <param name="image"></param>
        /// <param name="number"></param>
        public static void ExtractFrame(string video, string image, uint number)
        {
            // 提取帧
            // ffmpeg -i caiyilin.wmv -vframes 1 wm.bmp
            string param = $"-i \"{video}\" -y -vframes {number} \"{image}\"";
            ExcuteProcess(exec, param, null, (s, e) => Console.WriteLine(e.Data));
        }


        /// <summary>
        /// 执行一个控制台程序
        /// </summary>
        /// <param name="exe">程序名称</param>
        /// <param name="arg">参数</param>
        /// <param name="workingDirectory">工作路径</param>
        /// <param name="output">输出重定向</param>
        private static void ExcuteProcess(string exe, string arg, string workingDirectory, DataReceivedEventHandler output)
        {
            using (var p = new Process())
            {
                p.StartInfo.FileName = exe;
                p.StartInfo.Arguments = arg;

                // 工作目录
                if (workingDirectory != null)
                {
                    p.StartInfo.WorkingDirectory = workingDirectory;
                }

                p.StartInfo.UseShellExecute = false;    //输出信息重定向
                p.StartInfo.CreateNoWindow = true;
                p.StartInfo.RedirectStandardError = true;
                p.StartInfo.RedirectStandardOutput = true;

                // 将 StandardErrorEncoding 改为 UTF-8 才不会出现中文乱码
                p.StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8;
                p.StartInfo.StandardErrorEncoding = System.Text.Encoding.UTF8;

                p.OutputDataReceived += output;
                p.ErrorDataReceived += output;

                p.Start();                    //启动线程
                p.BeginOutputReadLine();
                p.BeginErrorReadLine();
                p.WaitForExit();            //等待进程结束
            }
        }

    }
}