package org.jeecg.modules.opencv.service.impl;

import cn.com.ctop.common.module.utils.*;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.opencv.entity.ModelVideoCompositeInfo;
import org.jeecg.modules.opencv.entity.ModelVideoInfo;
import org.jeecg.modules.opencv.entity.ModelVideoInfoConfig;
import org.jeecg.modules.opencv.service.IFfmpegService;
import org.jeecg.modules.opencv.service.IModelVideoCompositeInfoService;
import org.jeecg.modules.opencv.service.IModelVideoInfoConfigService;
import org.jeecg.modules.opencv.service.IModelVideoInfoService;
import org.jeecg.modules.opencv.utils.ImageUtils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class FfmpegServiceImpl implements IFfmpegService {
    /**
     * FFmpeg全路径
     */
    @Value("${jeecg.ffmpeg.bin}")
    private String FFMPEG_PATH;

    /**
     * 从视频中抽取音频文件
     */
    @Override
    public void getM4aFromMp4(String videoPath, String m4apath) {
        /**
         * D:\java\ffmpeg\bin\ffmpeg.exe -i D:\data\test\\video.mp4 -vn -y -acodec copy D:\data\test\\video.m4a
         */
        List<String> command = new ArrayList<>();
        command.add(FFMPEG_PATH);
        command.add("-i");
        command.add(videoPath);
        command.add("-vn");
        command.add("-y");
        command.add("-acodec");
        command.add("copy");
        command.add(m4apath);
        runCommand(command);
        log.info("提取音频文件成功:{}", m4apath);
    }

    /**
     * 截取音频文件
     */
    @Override
    public void cutM4a(String srcPath, String targetPath, String startSecond, String endSecond) {
        /**
         *D:\java\ffmpeg\bin\ffmpeg.exe -i D:\data\test\\video.m4a -vn -acodec copy -ss 00:00:00.000 -t 00:00:08.000 D:\data\test\\cut.m4a
         */
        List<String> command = new ArrayList<>();
        command.add(FFMPEG_PATH);
        command.add("-i");
        command.add(srcPath);
        command.add("-vn");
        command.add("-acodec");
        command.add("copy");
        command.add("-ss");
        command.add(startSecond);
        command.add("-t");
        command.add(endSecond);
        command.add(targetPath);
        runCommand(command);
        log.info("音频文件截取成功:{}", targetPath);
    }

    /**
     * 音频拼接
     * D:\java\ffmpeg\bin\ffmpeg.exe -i D:\data\test\\cut.m4a -i D:\data\test\\fill.m4a -filter_complex "[0:0] [1:0] concat=n=2:v=0:a=1 [a]" -map [a] D:\data\test\\final.m4a
     */
    @Override
    public void audioMosaic(String audioPart1, String audioPart2, String finalAudio) {
        StringBuffer command = new StringBuffer();
        command.append(FFMPEG_PATH);
        command.append(" -i ");
        command.append(audioPart1);
        command.append(" -i ");
        command.append(audioPart2);
        command.append(" -filter_complex ");
        command.append("'[0:0] [1:0] concat=n=2:v=0:a=1 [a]'");
        command.append(" -map ");
        command.append("[a] ");
        command.append(finalAudio);
        Runtime runtime = Runtime.getRuntime();
        Process pro = null;
        try {
            pro = runtime.exec(new String[]{"sh", "-c", command.toString()});
            printProcessMsg(pro);
            int status = pro.waitFor();
            if (status == 0) {
                log.info("音频文件合成成功:{}", command.toString());
            } else {
                log.info("音频文件合成失败:{}", command.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理process输出流和错误流，防止进程阻塞，在process.waitFor();前调用
     *
     * @param exec
     * @throws IOException
     */
    private void printProcessMsg(Process exec) throws IOException {
        //防止ffmpeg进程塞满缓存造成死锁
        InputStream error = exec.getErrorStream();
        InputStream is = exec.getInputStream();

        StringBuffer result = new StringBuffer();
        String line = null;
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(error));
            BufferedReader br2 = new BufferedReader(new InputStreamReader(is));

            while ((line = br.readLine()) != null) {
                result.append(line + "\n");
            }
//            log.info("FFMPEG视频转换进程错误信息：" + result.toString());

            result = new StringBuffer();
            line = null;

            while ((line = br2.readLine()) != null) {
                result.append(line + "\n");
            }
//            log.info("FFMPEG视频转换进程输出内容为：" + result.toString());
        } catch (IOException e2) {
            e2.printStackTrace();
        } finally {
            error.close();
            is.close();
        }

    }

    /**
     * 音频+视频合成视频
     *
     * @param srcVideoPath
     * @param srcAudioPath
     * @param finalVideoPath D:\java\ffmpeg\bin\ffmpeg.exe -i D:\data\test\\merge.mp4 -i D:\data\test\\final.m4a -c:v copy -c:a aac -strict experimental D:\data\test\\final.mp4
     */
    @Override
    public void mergeAudioAndVideo(String srcVideoPath, String srcAudioPath, String finalVideoPath) {
        StringBuffer command = new StringBuffer();
        command.append(FFMPEG_PATH);
        command.append(" -i ");
        command.append(srcVideoPath);
        command.append(" -i ");
        command.append(srcAudioPath);
        command.append(" -c:v");
        command.append(" copy");
        command.append(" -c:a");
        command.append(" aac");
        command.append(" -strict");
        command.append(" experimental ");
        command.append(finalVideoPath);
        Runtime runtime = Runtime.getRuntime();
        Process pro = null;
        try {
            pro = runtime.exec(new String[]{"sh", "-c", command.toString()});
            printProcessMsg(pro);
            int status = pro.waitFor();
            if (status == 0) {
                log.info("视频合成成功:{}", command.toString());
            } else {
                log.info("视频合成失败:{}", command.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将图片合成为视频
     * D:\java\ffmpeg\bin\ffmpeg.exe -f image2 -i D:\\data\\video\\%d.jpg  -vcodec libx264 -r 25  D:\\data\\merge.mp4
     *
     * @param imagePath
     * @param frameNumber
     * @param videoFilePath
     */
    @Override
    public void mergeImageToVideo(String imagePath, Integer frameNumber, String videoFilePath) {
        List<String> command = new ArrayList<>();
        command.add(FFMPEG_PATH);
        command.add("-f");
        command.add("image2");
        command.add("-i");
        command.add(imagePath + "%d.jpg");
        command.add("-vcodec");
        command.add("libx264");
        command.add("-r");
        command.add(frameNumber + "");
        command.add(videoFilePath);
        runCommand(command);
        log.info("图片合成视频成功:{}", videoFilePath);
    }

    private void runCommand(List<String> command) {
        try {
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(command);
            builder.redirectErrorStream(true);
            Process process = builder.start();
            //获取进程的标准输入流
            final InputStream is1 = process.getInputStream();
            //获取进城的错误流
            final InputStream is2 = process.getErrorStream();
            //启动两个线程，一个线程负责读标准输出流，另一个负责读标准错误流
            new Thread() {
                @Override
                public void run() {
                    BufferedReader br1 = new BufferedReader(new InputStreamReader(is1));
                    try {
                        String line1 = null;
                        while ((line1 = br1.readLine()) != null) {
                            if (line1 != null) {
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            is1.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();

            new Thread() {
                @Override
                public void run() {
                    BufferedReader br2 = new BufferedReader(new InputStreamReader(is2));
                    try {
                        String line2 = null;
                        while ((line2 = br2.readLine()) != null) {
                            if (line2 != null) {
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            is2.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
            // 等待进程执行结束
            process.waitFor();
            log.info("ffmpeg命令执行完成");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Map<String, Object> videoMerge(JSONObject data, String userId) {
        Map<String, Object> result = new HashMap<>();
        try {
            Long modelId = data.getLong("modelId");
            String url = data.getString("url");
            if (null == modelId || modelId == 0 || null == url || "".equals(url.trim())) {
                ResultMapUtils.setResultMap(result, StatusCode.MODEL_VIDEO_NOT_EXIST);
                return result;
            }
            ModelVideoInfo videoInfo = modelVideoInfoService.getById(modelId);
            if (null == videoInfo || videoInfo.getStatus() == 0) {
                ResultMapUtils.setResultMap(result, StatusCode.MODEL_VIDEO_NOT_EXIST);
                return result;
            }
            //1:
            url = "https:" + url;
            String getUrl = mergeVideo(videoInfo, url, userId);
            ResultMapUtils.setResultMap(result, StatusCode.COMMON_SUCCESS);
            result.put("url", getUrl);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            ResultMapUtils.setResultMap(result, StatusCode.COMMON_SERVER_ERROR);
            return result;
        }
    }

    @Autowired
    private IModelVideoInfoConfigService configService;

    private String mergeVideo(ModelVideoInfo videoInfo, String url, String userId) throws Exception {
        Long current = System.currentTimeMillis();
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        //根据url下载视频
        String srcVideoPath = PropertiesUtils.getValue("kuaishou_config", "model_video_src_path");
        String fillVideoPath = PropertiesUtils.getValue("kuaishou_config", "model_video_fill_path");
        String finalVideoPath = PropertiesUtils.getValue("kuaishou_config", "model_video_final_path");

        String fillFilePath = LoadFileUtil.downLoadFromUrl(url, fillVideoPath);
        Long current1 = System.currentTimeMillis();
        log.info("替换视频下载完成：总时长{}毫秒", current1 - current);
        String srcFilePath = LoadFileUtil.downLoadFromUrl(videoInfo.getModelVideoUrl(), srcVideoPath);
        Long current2 = System.currentTimeMillis();
        log.info("模板文件下载完成：总时长{}毫秒", current2 - current1);
        String splitSrcPath = srcVideoPath + current + File.separator;
        LoadFileUtil.checkFile(splitSrcPath);
        String splitFillPath = fillVideoPath + current + File.separator;
        LoadFileUtil.checkFile(splitFillPath);
        String splitFinalPath = finalVideoPath + current + File.separator;
        LoadFileUtil.checkFile(splitFinalPath);
        //1:切分视频
        videoSplitToImage(srcFilePath, splitSrcPath);
        Long current3 = System.currentTimeMillis();
        log.info("模板文件切分完成：总时长{}毫秒", current3 - current2);
        videoSplitToImage(fillFilePath, splitFillPath);
        Long current4 = System.currentTimeMillis();
        log.info("填充文件切分完成：总时长{}毫秒", current4 - current3);
        ModelVideoInfoConfig config = configService.getByModelId(videoInfo.getId());
        if (null == config) {
            log.error("配置文件尚未配置=>modelId:{}", videoInfo.getId());
            return null;
        }
        if (config.getType() == 1) {
            //简单的最终追加
            //2:合并图片
            //视频替换开始帧数
            int exchangeStartFrameNum = config.getFullScreenStartFrame();
            //视频替换结束帧数
            int exchangeEndFrameNum = config.getFullScreenEndFrame();
            //2.1 复制原始视频无变化图片
            for (int i = 1; i < exchangeStartFrameNum; i++) {
                Mat srcNoChangeImage = Imgcodecs.imread(splitSrcPath + i + ".jpg");
                Imgcodecs.imwrite(splitFinalPath + i + ".jpg", srcNoChangeImage);
            }

            //2.2 转场动画(从fill文件夹的第一帧开始)
            String getImageName = splitFillPath + "1.jpg";
            Mat image = Imgcodecs.imread(getImageName, Imgcodecs.IMREAD_COLOR);
            //(1)径向模糊图片
            Mat radialBlur = ImageUtils.radialBlur(image);
            Imgcodecs.imwrite(splitFinalPath + exchangeStartFrameNum + ".jpg", radialBlur);
            exchangeStartFrameNum++;
            //(2)放大后径向模糊图片
            Mat bigImamge = new Mat();
            Imgproc.resize(image, bigImamge, new Size(image.cols() * 1.25, image.rows() * 1.25));
            Mat destBigImamge = ImageUtils.cutFromCenter(bigImamge, image.width(), image.height());
            Mat radialBlurDestBigImamge = ImageUtils.radialBlur(destBigImamge);
            Imgcodecs.imwrite(splitFinalPath + exchangeStartFrameNum + ".jpg", radialBlurDestBigImamge);
            exchangeStartFrameNum++;
            //(3)缩小之后重复平铺加径向模糊
            Mat smallImage = new Mat();
            Mat halfImage = new Mat();
            Mat wholeImage = new Mat();
            Imgproc.resize(image, smallImage, new Size(image.cols() / 2, image.rows() / 2));
            ImageUtils.hconcat(smallImage, smallImage, halfImage);
            ImageUtils.vconcat(halfImage, halfImage, wholeImage);
            wholeImage = ImageUtils.radialBlur(wholeImage);
            Imgcodecs.imwrite(splitFinalPath + exchangeStartFrameNum + ".jpg", wholeImage);
            exchangeStartFrameNum++;
            //2.3复制填充视频无变化图片
            int checkNum = 2;
            for (int i = exchangeStartFrameNum; i < exchangeEndFrameNum; i++) {
                String fillImagePath = splitFillPath + checkNum + ".jpg";
                File file = new File(fillImagePath);
                if (file.exists()) {
                    Mat srcMat = Imgcodecs.imread(fillImagePath);
                    Imgcodecs.imwrite(splitFinalPath + i + ".jpg", srcMat);
                } else {
                    break;
                }
                checkNum++;
            }
            Long current5 = System.currentTimeMillis();
            log.info("图片替换完成：总时长{}毫秒", current5 - current4);
            //3开始合成视频
            //3.1 抽取音频文件
            String srcM4aFilePath = splitSrcPath + "src.m4a";
            getM4aFromMp4(srcFilePath, srcM4aFilePath);
            Long current6 = System.currentTimeMillis();
            log.info("抽取模板音频文件完成：总时长{}毫秒", current6 - current4);
            String fillM4aFilePath = splitSrcPath + "fill.m4a";
            getM4aFromMp4(fillFilePath, fillM4aFilePath);
            Long current7 = System.currentTimeMillis();
            log.info("抽取填充音频文件完成：总时长{}毫秒", current7 - current6);
            //3.2 截取源模板音频文件
            String cutM4aFilePath = splitSrcPath + "cut.m4a";
            //计算需要截取的音频时长
            String secondStr = getSecond(exchangeStartFrameNum);
            cutM4a(srcM4aFilePath, cutM4aFilePath, "00:00:00.000", secondStr);
            Long current8 = System.currentTimeMillis();
            log.info("音频文件截取完成：总时长{}毫秒", current8 - current7);
            //3.3 合并两个音频文件
            String finalM4aFilePath = splitSrcPath + "final.m4a";
            audioMosaic(cutM4aFilePath, fillM4aFilePath, finalM4aFilePath);
            Long current9 = System.currentTimeMillis();
            log.info("音频文件合成完成：总时长{}毫秒", current9 - current8);
            //3.4 将最终生成的图片合成为视频
            String noVoiceVideoFilePath = splitSrcPath + "novoice.mp4";
            mergeImageToVideo(splitFinalPath, 25, noVoiceVideoFilePath);
            Long current10 = System.currentTimeMillis();
            log.info("图片合成视频完成：总时长{}毫秒", current10 - current9);
            //3.5 合并视频和音频
            String finalVideoFilePath = splitSrcPath + "final.mp4";
            mergeAudioAndVideo(noVoiceVideoFilePath, finalM4aFilePath, finalVideoFilePath);
            Long current11 = System.currentTimeMillis();
            log.info("视频合成完成：总时长{}毫秒", current11 - current10);
            //3.6 上传视频文件至OSS并返回url存入数据库
            InputStream inputStream = new FileInputStream(finalVideoFilePath);
            String finalUrl = OSSUtils.uploadFile2OSS(inputStream, "model/final/", videoInfo.getId() + "_" + current + ".mp4");
            Long current12 = System.currentTimeMillis();
            log.info("文件上传oss完成：总时长{}毫秒", current12 - current11);
            ModelVideoCompositeInfo compositeInfo = new ModelVideoCompositeInfo(userId, videoInfo, finalUrl);
            compositeInfoService.saveOrUpdate(compositeInfo);
            //删除文件
            deleteFile(splitSrcPath, splitFillPath, splitFinalPath, srcFilePath, fillFilePath);
            log.info("视频合成成功：总时长{}毫秒", System.currentTimeMillis() - current);
            return finalUrl;
        } else if (config.getType() == 2) {
            //2:合并图片

        } else {
            log.error("此类模板视频尚未配置=>type:{}", config.getType());
        }
        return null;
    }

    @Override
    public void videoSplitToImage(String srcFilePath, String splitSrcPath) {
        StringBuffer command = new StringBuffer();
        command.append(FFMPEG_PATH);
        command.append(" -i ");
        command.append(srcFilePath);
        command.append(" -threads 5");
        command.append(" -preset");
        command.append(" ultrafast ");
        command.append(splitSrcPath + "%d.jpg");
        Runtime runtime = Runtime.getRuntime();
        Process pro = null;
        try {
            pro = runtime.exec(new String[]{"sh", "-c", command.toString()});
            printProcessMsg(pro);
            int status = pro.waitFor();
            if (status == 0) {
                log.info("视频切分成功:{}", command.toString());
            } else {
                log.info("视频切分失败:{}", command.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void deleteFile(String splitSrcPath, String splitFillPath, String splitFinalPath, String srcFilePath, String fillFilePath) {
        LoadFileUtil.delFile(splitFillPath);
        LoadFileUtil.delFile(splitSrcPath);
        LoadFileUtil.delFile(splitFinalPath);
        LoadFileUtil.delFile(srcFilePath);
        LoadFileUtil.delFile(fillFilePath);
    }

    @Autowired
    private IModelVideoCompositeInfoService compositeInfoService;

    private String getSecond(int exchangeStartFrameNum) {
        BigDecimal second = new BigDecimal(exchangeStartFrameNum).divide(new BigDecimal("25"), 3, RoundingMode.HALF_UP);
        if (second.compareTo(new BigDecimal("10")) < 0) {
            return "00:00:0" + second.toString();
        } else {
            return "00:00:" + second.toString();
        }
    }

    @Autowired
    private IModelVideoInfoService modelVideoInfoService;

//    public void cornerCheck(){
//        int srcPhoneFrameNumFrom = 163;
//        int changeSenceFrameNumFrom = 234;
//        //2.1 复制原始视频无变化图片
//        for (int i = 1; i < srcPhoneFrameNumFrom; i++) {
//            Mat srcNoChangeImage = Imgcodecs.imread(srcPath + i + ".jpg");
//            Imgcodecs.imwrite(targetPath + i + ".jpg", srcNoChangeImage);
//        }
//        String getImageName = "";
//        //2.2 填充手机内部图片画面
//        for (int i = 163; i < changeSenceFrameNumFrom; i++) {
//            String backImg = srcPath + (i) + ".jpg";
//            String phoneImg = fillPath + (i - 162) + ".jpg";
//            String targetImg = targetPath + i + ".jpg";
//            Perspective.perpectiveImg(backImg, phoneImg, targetImg);
//            getImageName = targetImg;
//        }
//        //2.3 转场动画
//        Mat image = Imgcodecs.imread(getImageName, Imgcodecs.IMREAD_COLOR);
//        //(1)径向模糊图片
//        Mat radialBlur = ImageUtils.radialBlur(image);
//        Imgcodecs.imwrite(targetPath + changeSenceFrameNumFrom + ".jpg", radialBlur);
//        changeSenceFrameNumFrom++;
//        //(2)放大后径向模糊图片
//        Mat bigImamge = new Mat();
//        Imgproc.resize(image, bigImamge, new Size(image.cols() * 1.25, image.rows() * 1.25));
//        Mat destBigImamge = ImageUtils.cutFromCenter(bigImamge, image.width(), image.height());
//        Mat radialBlurDestBigImamge = ImageUtils.radialBlur(destBigImamge);
//        Imgcodecs.imwrite(targetPath + changeSenceFrameNumFrom + ".jpg", radialBlurDestBigImamge);
//        changeSenceFrameNumFrom++;
//        //(3)缩小之后重复平铺加径向模糊
//        Mat smallImage = new Mat();
//        Mat halfImage = new Mat();
//        Mat wholeImage = new Mat();
//        Imgproc.resize(image, smallImage, new Size(image.cols() / 2, image.rows() / 2));
//        ImageUtils.hconcat(smallImage, smallImage, halfImage);
//        ImageUtils.vconcat(halfImage, halfImage, wholeImage);
//        wholeImage = ImageUtils.radialBlur(wholeImage);
//        Imgcodecs.imwrite(targetPath + changeSenceFrameNumFrom + ".jpg", wholeImage);
//        changeSenceFrameNumFrom++;
//        //复制填充视频无变化图片
//        for (int i = changeSenceFrameNumFrom; i < 100000; i++) {
//            int checkNum = i - 3 - srcPhoneFrameNumFrom + 1;
//            String fillImagePath = fillPath + checkNum + ".jpg";
//            File file = new File(fillImagePath);
//            if (file.exists()) {
//                Mat srcMat = Imgcodecs.imread(fillImagePath);
//                Imgcodecs.imwrite(targetPath + i + ".jpg", srcMat);
//            } else {
//                break;
//            }
//        }
//    }
}
