package com.pingpang.image;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.highgui.HighGui;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.VideoWriter;
import org.opencv.videoio.Videoio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class VideoUtil {
	private static Logger logger = LoggerFactory.getLogger(VideoUtil.class);
	/**
	 * BASE64Encoder 加密
	 * 
	 * @param data 要加密的数据
	 * @return 加密后的字符串
	 */
	public static String encryptBASE64(byte[] data) {
		// BASE64Encoder encoder = new BASE64Encoder();
		// String encode = encoder.encode(data);
		// 从JKD 9开始rt.jar包已废除，从JDK 1.8开始使用java.util.Base64.Encoder
		String encode = Base64.getMimeEncoder().encodeToString(data);
		return encode;
	}

	/**
	 * BASE64Decoder 解密
	 * 
	 * @param data 要解密的字符串
	 * @return 解密后的byte[]
	 * @throws Exception
	 */
	public static byte[] decryptBASE64(String data) throws Exception {
		// BASE64Decoder decoder = new BASE64Decoder();
		// byte[] buffer = decoder.decodeBuffer(data);
		// 从JKD 9开始rt.jar包已废除，从JDK 1.8开始使用java.util.Base64.Decoder
		if (null != data && data.indexOf(";base64,") > -1) {
			data = data.substring(data.indexOf(";base64,") + 8);
		}
		byte[] buffer = Base64.getMimeDecoder().decode(data);
		return buffer;
	}

	/**
	 * 将文件转成base64 字符串
	 * 
	 * @param path文件路径
	 * @return *
	 * @throws Exception
	 */
	public static String encodeBase64File(String path) throws Exception {
		File file = new File(path);
		FileInputStream inputFile = new FileInputStream(file);
		byte[] buffer = new byte[(int) file.length()];
		inputFile.read(buffer);
		inputFile.close();
		return encryptBASE64(buffer);
	}

	/**
	 * 将base64字符解码保存文件
	 *
	 * @param base64Code
	 * @param targetPath
	 * @throws Exception
	 */
	public static void decoderBase64File(String base64Code, String targetPath, String catalogue) throws Exception {
		File file = new File(catalogue);
		if (file.exists() == false) {
			file.mkdirs();
		}
		byte[] buffer = decryptBASE64(base64Code);
		FileOutputStream out = new FileOutputStream(targetPath,true);
		out.write(buffer);
		out.close();
	}
	
	/**
	  * 添加报文头的
	  * 将base64字符解码保存文件
	 * @param base64Code
	 * @param targetPath
	 * @throws Exception
	 */
	public static void decoderBase64FileAddHead(String head,String base64Code, String targetPath, String catalogue) throws Exception {
		File file = new File(catalogue);
		if (file.exists() == false) {
			file.mkdirs();
		}
		byte[] buffer = decryptBASE64(base64Code);
		FileOutputStream out = new FileOutputStream(targetPath,true);
		out.write(addBytes(head.getBytes(),buffer));
		out.close();
	}

	
	public static byte[] addBytes(byte[] data1, byte[] data2) { 
		 byte[] data3 = new byte[data1.length + data2.length]; 
		 System.arraycopy(data1, 0, data3, 0, data1.length); 
		 System.arraycopy(data2, 0, data3, data1.length, data2.length); 
		 return data3; 
	}
	
	public static String getHead(String base64Code,int start,int end) throws Exception {
		byte[] buffer = decryptBASE64(base64Code);
		byte[] head =new byte[end-start];
		System.arraycopy(buffer, 0, head, start, end);
		return new String(head);
	}
	
	/**
	  * 运行命令行程序
	 * @param cmd
	 * @return
	 * @throws IOException 
	 */
	public static boolean runCmd(List<String> cmd ) throws IOException{
		logger.info("执行命令:{}", cmd);
		long starTime=System.currentTimeMillis();
		ProcessBuilder processBuilder = new ProcessBuilder();
		processBuilder.command(cmd);
		
		// 将标准输入流和错误输入流合并，通过标准输入流读取信息
		processBuilder.redirectErrorStream(true);
		// 启动进程
		Process start = processBuilder.start();
		// 获取输入流
		InputStream inputStream = start.getInputStream();
		// 转成字符输入流
		InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
		int len = -1;
		char[] c = new char[1024];
		StringBuffer outputString = new StringBuffer();
		// 读取进程输入流中的内容
		while ((len = inputStreamReader.read(c)) != -1) {
			String s = new String(c, 0, len);
			outputString.append(s);
		}
		logger.debug(outputString.toString());
		inputStream.close();
		logger.info("执行命令时间:{}", System.currentTimeMillis()-starTime);
		return true;
			
//			Process process = Runtime.getRuntime().exec(cmd);
//			InputStream inputStream = process.getInputStream();
//			BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
//			String tmp = "";
//			while ((tmp = reader.readLine()) != null) {
//				logger.info(tmp);
//				if (reader != null) {
//					reader.close();
//				}
//				if (process != null) {
//					process.destroy();
//					process = null;
//				}
//				return true;
//			}
//			return false;
	}
	
	/**
	 * ffmpeg 视频转码
	 * crf取值范围是[0-51]，0代表无损，默认值是23，51是质量最差，所以这个值的设置越低视频质量越好
	 * @param src
	 * @param target
	 * @return
	 * @throws IOException 
	 */
	public static boolean videoChange(String src,String target) throws IOException {
		//String changeCmd="ffmpeg -i "+src+" "+target;
		List<String> command = new ArrayList<String>();
		command.add("ffmpeg");
		command.add("-i");
		command.add(src);
		command.add("-crf");//
		command.add("5");
		command.add(target);
		return runCmd(command);
	}
	
	/**
	  * 音频视频合并
	 * @param voiceSrc
	 * @param videoSrc
	 * @param targetVideoSrc
	 * @return
	 * @throws IOException 
	 */
	public static boolean videoMerge(String voiceSrc,String videoSrc,String targetVideoSrc) throws IOException {
		//String mergeCmd="ffmpeg -i "+voiceSrc+" -i "+videoSrc+" "+targetVideoSrc;
		List<String> command = new ArrayList<String>();
		command.add("ffmpeg");
		command.add("-i");
		command.add(voiceSrc);
		command.add("-i");
		command.add(videoSrc);
		//command.add("-shortest");
		command.add(targetVideoSrc);
		return runCmd(command);
	}
	
	/**
	  *  提取音频数据
	 * -vn表示去掉视频，-c:a copy表示不改变音频编码，直接拷贝。
	 * 
	 * ffmpeg -i huoying.mp4 -f wav -ar 16000 huoying.wav
	 *                       -f wav wav编码 
	 *                       -ar 16000 音频采样率
	 * ffmpeg -i test2.webm  -f wav -vn -vcodec copy test2.wav
	 * @param videoStr
	 * @param targetVoice
	 * @return
	 * @throws IOException 
	 */
	public static boolean videoGetVoice(String videoStr,String targetVoice) throws IOException {
		//String voiceGetCmd="ffmpeg -i "+videoStr+" -f wav -codec copy "+targetVoice;
		List<String> command = new ArrayList<String>();
		command.add("ffmpeg");
		command.add("-i");
		command.add(videoStr);
		command.add("-vn");
		//command.add("-f");
		//command.add("wav");
		command.add("-vcodec");
		command.add("copy");
		command.add(targetVoice);
		return runCmd(command);
	}
	
	/**
	  *  提取音频数据
	 * -vn表示去掉视频，-c:a copy表示不改变音频编码，直接拷贝。
	 * 
	 * ffmpeg -i test2.webm -an -vcodec copy test3.avi
	 * @param videoStr
	 * @param targetVoice
	 * @return
	 * @throws IOException 
	 */
	public static boolean videoGetVideo(String videoStr,String targetVideo) throws IOException {
		//String voiceGetCmd="ffmpeg -i "+videoStr+" -f wav -codec copy "+targetVoice;
		List<String> command = new ArrayList<String>();
		command.add("ffmpeg");
		command.add("-i");
		command.add(videoStr);
		command.add("-an");
		command.add("-vcodec");
		command.add("copy");
		command.add(targetVideo);
		return runCmd(command);
	}
	
	/**
	 * 
	 * @param base64Code 文件字符串
	 * @param targetPath 文件路径
	 * @param catalogue  文件目录
	 * @param type       文件类型
	 * @return 
	 * @throws Exception
	 */
	public static String getChangeVideoBase64(String base64Code, String targetPath, String catalogue,String type) throws Exception {
		
		logger.info("文件目录{},路径{}",catalogue,targetPath);
		//1.保存文件
		decoderBase64File(base64Code, targetPath, catalogue);
		
		File file = new File(targetPath);
		String fileName=file.getName().substring(0, file.getName().lastIndexOf("."));
		
		//转码webm->avi
		//String targetAVI=catalogue+fileName+".avi";
		//logger.info("webm->avi路径:{}",targetAVI);
		//videoChange(targetPath,targetAVI);
		//videoGetVideo(targetPath,targetAVI);
		
		//2.提取音频文件
		String voiceFilePath=catalogue+fileName+".m4a";
		logger.info("音频提提取路径:{}",voiceFilePath);
		videoGetVoice(targetPath,voiceFilePath);
		
		//3.视频编码
		//System.load(Core.NATIVE_LIBRARY_NAME);
		//image 初始化会加载的
		//System.load("E:/opencv4.4.0/opencv/build/java/x64/opencv_java440.dll");
		VideoCapture capture=new VideoCapture();
        capture.open(targetPath);//1 读取视频文件的路径
        
        //处理后的文件名+1
		Size size = new Size(capture.get(Videoio.CAP_PROP_FRAME_WIDTH),capture.get(Videoio.CAP_PROP_FRAME_HEIGHT));
        VideoWriter  vw=new VideoWriter(catalogue+fileName+"1"+type,
        		                       VideoWriter.fourcc('V', 'P', '0', '9'),
        		                       //capture.get(Videoio.CAP_PROP_FPS),
        		                       30,
        		                       size,
        		                       true);
        logger.info("处理文件路径:{},是否打开{},FPS:{}",catalogue+fileName+"1"+type,vw.isOpened()+"",capture.get(Videoio.CAP_PROP_FPS));
        if(!capture.isOpened()){
            logger.info("读取视频文件失败！");
            return null;
        }
        
        //测试用
        //ImageUtil iu=new ImageUtil();
        //iu.run();
        
        Mat video=new Mat(); 
        int i=0;
        long starTime=System.currentTimeMillis();
		while (capture.isOpened() && capture.read(video)) {
			logger.info("当前" + (i++));
//			if (i++ % 5 != 0) {
//				// capture.grab();
//				logger.info("跳过" + i);
//				continue;
//			}
			// capture.read(video);//2 视频文件的视频写入 Mat video 中
			Mat current = ImageUtil.getVideoFace(video);
			if(null==current) {
				break;
			}
		    vw.write(current);
		}
        capture.release();
    	vw.release();
    	logger.info("识别时间:{}", System.currentTimeMillis()-starTime);
    	
        videoMerge(voiceFilePath,catalogue+fileName+"1"+type,catalogue+fileName+"2"+type);
        //转码avi->webm
      	//videoChange(catalogue+fileName+"2.avi",catalogue+fileName+"2.webm");
      		
		return encodeBase64File(catalogue+fileName+"2"+type);
	}
	
	public static void main(String[] args) throws Exception {
		String str = "data:video/x-matroska;codecs=avc1,opus;base64,1221222";
		System.out.println(str.substring(0,str.indexOf(";base64,")));
		System.out.println(str.substring(str.indexOf(";base64,") + 8));
		
		//测试用
        ImageUtil iu=new ImageUtil();
        iu.run();
		
		getChangeVideoBase64(encodeBase64File("E:/opencv4.4.0/age-and-gender-classification/img/040201/test2.webm"),
				             "E:/opencv4.4.0/age-and-gender-classification/img/040201/test3.webm",
				             "E:/opencv4.4.0/age-and-gender-classification/img/040201/",
				             ".webm");
		
//		//System.loadLibrary("opencv_java412");
//		System.load("E:/opencv4.4.0/opencv/build/java/x64/opencv_java440.dll");
//		VideoCapture capture=new VideoCapture();
//        capture.open("E:/opencv4.4.0/age-and-gender-classification/img/040201/test1.avi");//1 读取视频文件的路径
//        //capture.open("E:\\迅雷下载\\[阳光电影-www.ygdy8.com]司藤-08.mp4");//1 读取视频文件的路径
//		Size size = new Size(capture.get(Videoio.CAP_PROP_FRAME_WIDTH),capture.get(Videoio.CAP_PROP_FRAME_HEIGHT));
//        VideoWriter  vw=new VideoWriter("E:/opencv4.4.0/age-and-gender-classification/img/040201/test2.avi",
//        		                       VideoWriter.fourcc('M','J','P','G'),
//        		                       capture.get(Videoio.CAP_PROP_FPS),
//        		                       size,
//        		                       true);
//        System.out.println(vw.isOpened());
//        if(!capture.isOpened()){
//            System.out.println("读取视频文件失败！");
//            return;
//        }
//        ImageUtil iu=new ImageUtil();
//        iu.run();
//        Mat video=new Mat(); 
//        int index=0;
//        while(capture.isOpened()) {
//           try {	
//            capture.read(video);//2 视频文件的视频写入 Mat video 中
//            Mat current=ImageUtil.getFace(video);
//            vw.write(current);
//            HighGui.imshow("本地视频识别人脸", current);//3 显示图像
//            index=HighGui.waitKey(100);//4 获取键盘输入
//            if(index==27) {//5 如果是 Esc 则退出
//                capture.release();
//                return;
//            }
//           }catch(Exception e) {
//        	   System.out.println("ERROR");
//        	   e.printStackTrace();
//        	   capture.release();
//        	   vw.release();
//        	   HighGui.destroyAllWindows();
//        	   return;
//           }
//        }
	}
}
