package VideoHandle;

import android.content.Context;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.util.Log;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import Jni.FFmpegCmd;
import Jni.FileUtils;
import Jni.TrackUtils;
import Jni.VideoUitls;

/**
 * 视频编辑器
 * Created by YangJie on 2017/5/18.
 */

public class EpEditor {

	private static final int DEFAULT_WIDTH = 480;//默认输出宽度
	private static final int DEFAULT_HEIGHT = 360;//默认输出高度

	public enum Format {
		MP3, MP4
	}

	public enum PTS {
		VIDEO, AUDIO, ALL
	}

	private EpEditor() {
	}

	/**
	 * 处理单个视频
	 *
	 * @param epVideo      需要处理的视频
	 * @param outputOption 输出选项配置
	 */
	public static void exec(EpVideo epVideo, OutputOption outputOption, OnEditorListener onEditorListener) {
		boolean isFilter = false;
		ArrayList<EpDraw> epDraws = epVideo.getEpDraws();
		//开始处理
		CmdList cmd = new CmdList();
		cmd.append("ffmpeg");
		cmd.append("-y");
		if (epVideo.getVideoClip()) {
			cmd.append("-ss").append(epVideo.getClipStart()).append("-t").append(epVideo.getClipDuration()).append("-accurate_seek");
		}
		cmd.append("-i").append(epVideo.getVideoPath());
		//添加图片或者动图
		if (epDraws.size() > 0) {
			for (int i = 0; i < epDraws.size(); i++) {
				if (epDraws.get(i).isAnimation()) {
					cmd.append("-ignore_loop");
					cmd.append(0);
				}
				cmd.append("-i").append(epDraws.get(i).getPicPath());
			}
			cmd.append("-filter_complex");
			StringBuilder filter_complex = new StringBuilder();
			filter_complex.append("[0:v]").append(epVideo.getFilters() != null ? epVideo.getFilters() + "," : "")
					.append("scale=").append(outputOption.width == 0 ? "iw" : outputOption.width).append(":")
					.append(outputOption.height == 0 ? "ih" : outputOption.height)
					.append(outputOption.width == 0 ? "" : ",setdar=" + outputOption.getSar()).append("[outv0];");
			for (int i = 0; i < epDraws.size(); i++) {
				filter_complex.append("[").append(i + 1).append(":0]").append(epDraws.get(i).getPicFilter()).append("scale=").append(epDraws.get(i).getPicWidth()).append(":")
						.append(epDraws.get(i).getPicHeight()).append("[outv").append(i + 1).append("];");
			}
			for (int i = 0; i < epDraws.size(); i++) {
				if (i == 0) {
					filter_complex.append("[outv").append(i).append("]").append("[outv").append(i + 1).append("]");
				} else {
					filter_complex.append("[outo").append(i - 1).append("]").append("[outv").append(i + 1).append("]");
				}
				filter_complex.append("overlay=").append(epDraws.get(i).getPicX()).append(":").append(epDraws.get(i).getPicY())
						.append(epDraws.get(i).getTime());
				if (epDraws.get(i).isAnimation()) {
					filter_complex.append(":shortest=1");
				}
				if (i < epDraws.size() - 1) {
					filter_complex.append("[outo").append(i).append("];");
				}
			}
			cmd.append(filter_complex.toString());
			isFilter = true;
		} else {
			StringBuilder filter_complex = new StringBuilder();
			if (epVideo.getFilters() != null) {
				cmd.append("-filter_complex");
				filter_complex.append(epVideo.getFilters());
				isFilter = true;
			}
			//设置输出分辨率
			if (outputOption.width != 0) {
				if (epVideo.getFilters() != null) {
					filter_complex.append(",scale=").append(outputOption.width).append(":").append(outputOption.height)
							.append(",setdar=").append(outputOption.getSar());
				} else {
					cmd.append("-filter_complex");
					filter_complex.append("scale=").append(outputOption.width).append(":").append(outputOption.height)
							.append(",setdar=").append(outputOption.getSar());
					isFilter = true;
				}
			}
			if (!filter_complex.toString().equals("")) {
				cmd.append(filter_complex.toString());
			}
		}

		//输出选项
		cmd.append(outputOption.getOutputInfo().split(" "));
		if (!isFilter && outputOption.getOutputInfo().isEmpty()) {
			cmd.append("-vcodec");
			cmd.append("copy");
			cmd.append("-acodec");
			cmd.append("copy");
		} else {
			cmd.append("-preset");
			cmd.append("superfast");
		}
		cmd.append(outputOption.outPath);
		long duration = VideoUitls.getDuration(epVideo.getVideoPath());
		if (epVideo.getVideoClip()) {
			long clipTime = (long) ((epVideo.getClipDuration() - epVideo.getClipStart()) * 1000000);
			duration = clipTime < duration ? clipTime : duration;
		}
		//执行命令
		execCmd(cmd, duration, onEditorListener);
	}

	/**
	 * 合并多个视频
	 *
	 * @param epVideos     需要合并的视频集合
	 * @param outputOption 输出选项配置
	 */
	public static void merge(List<EpVideo> epVideos, OutputOption outputOption, OnEditorListener onEditorListener) {
		//检测是否有无音轨视频
		boolean isNoAudioTrack = false;
		for (EpVideo epVideo : epVideos) {
			MediaExtractor mediaExtractor = new MediaExtractor();
			try {
				mediaExtractor.setDataSource(epVideo.getVideoPath());
			} catch (IOException e) {
				e.printStackTrace();
				return;
			}
			int at = TrackUtils.selectAudioTrack(mediaExtractor);
			if (at == -1) {
				isNoAudioTrack = true;
				mediaExtractor.release();
				break;
			}
			mediaExtractor.release();
		}
		//设置默认宽高
		outputOption.width = outputOption.width == 0 ? DEFAULT_WIDTH : outputOption.width;
		outputOption.height = outputOption.height == 0 ? DEFAULT_HEIGHT : outputOption.height;
		//判断数量
		if (epVideos.size() > 1) {
			CmdList cmd = new CmdList();
			cmd.append("ffmpeg");
			cmd.append("-y");
			//添加输入标示
			for (EpVideo e : epVideos) {
				if (e.getVideoClip()) {
					cmd.append("-ss").append(e.getClipStart()).append("-t").append(e.getClipDuration()).append("-accurate_seek");
				}
				cmd.append("-i").append(e.getVideoPath());
			}
			for (EpVideo e : epVideos) {
				ArrayList<EpDraw> epDraws = e.getEpDraws();
				if (epDraws.size() > 0) {
					for (EpDraw ep : epDraws) {
						if (ep.isAnimation()) cmd.append("-ignore_loop").append(0);
						cmd.append("-i").append(ep.getPicPath());
					}
				}
			}
			//添加滤镜标识
			cmd.append("-filter_complex");
			StringBuilder filter_complex = new StringBuilder();
			for (int i = 0; i < epVideos.size(); i++) {
				StringBuilder filter = epVideos.get(i).getFilters() == null ? new StringBuilder("") : epVideos.get(i).getFilters().append(",");
				filter_complex.append("[").append(i).append(":v]").append(filter).append("scale=").append(outputOption.width).append(":").append(outputOption.height)
						.append(",setdar=").append(outputOption.getSar()).append("[outv").append(i).append("];");
			}
			//添加标记和处理宽高
			int drawNum = epVideos.size();//图标计数器
			for (int i = 0; i < epVideos.size(); i++) {
				for (int j = 0; j < epVideos.get(i).getEpDraws().size(); j++) {
					filter_complex.append("[").append(drawNum++).append(":0]").append(epVideos.get(i).getEpDraws().get(j).getPicFilter()).append("scale=")
							.append(epVideos.get(i).getEpDraws().get(j).getPicWidth()).append(":").append(epVideos.get(i).getEpDraws().get(j)
							.getPicHeight()).append("[p").append(i).append("a").append(j).append("];");
				}
			}
			//添加图标操作
			for (int i = 0; i < epVideos.size(); i++) {
				for (int j = 0; j < epVideos.get(i).getEpDraws().size(); j++) {
					filter_complex.append("[outv").append(i).append("][p").append(i).append("a").append(j).append("]overlay=")
							.append(epVideos.get(i).getEpDraws().get(j).getPicX()).append(":")
							.append(epVideos.get(i).getEpDraws().get(j).getPicY())
							.append(epVideos.get(i).getEpDraws().get(j).getTime());
					if (epVideos.get(i).getEpDraws().get(j).isAnimation()) {
						filter_complex.append(":shortest=1");
					}
					filter_complex.append("[outv").append(i).append("];");
				}
			}
			//开始合成视频
			for (int i = 0; i < epVideos.size(); i++) {
				filter_complex.append("[outv").append(i).append("]");
			}
			filter_complex.append("concat=n=").append(epVideos.size()).append(":v=1:a=0[outv]");
			//是否添加音轨
			if (!isNoAudioTrack) {
				filter_complex.append(";");
				for (int i = 0; i < epVideos.size(); i++) {
					filter_complex.append("[").append(i).append(":a]");
				}
				filter_complex.append("concat=n=").append(epVideos.size()).append(":v=0:a=1[outa]");
			}
			if (!filter_complex.toString().equals("")) {
				cmd.append(filter_complex.toString());
			}
			cmd.append("-map").append("[outv]");
			if (!isNoAudioTrack) {
				cmd.append("-map").append("[outa]");
			}
			cmd.append(outputOption.getOutputInfo().split(" "));
			cmd.append("-preset").append("superfast").append(outputOption.outPath);
			long duration = 0;
			for (EpVideo ep : epVideos) {
				long d = VideoUitls.getDuration(ep.getVideoPath());
				if (ep.getVideoClip()) {
					long clipTime = (long) ((ep.getClipDuration() - ep.getClipStart()) * 1000000);
					d = clipTime < d ? clipTime : d;
				}
				if (d != 0) {
					duration += d;
				} else {
					break;
				}
			}
			//执行命令
			execCmd(cmd, duration, onEditorListener);
		} else {
			throw new RuntimeException("Need more than one video");
		}
	}

	/**
	 * 无损合并多个视频
	 * <p>
	 * 注意：此方法要求视频格式非常严格，需要合并的视频必须分辨率相同，帧率和码率也得相同
	 *
	 * @param context          Context
	 * @param epVideos         需要合并的视频的集合
	 * @param outputOption     输出选项
	 * @param onEditorListener 回调监听
	 */
	public static void mergeByLc(Context context, List<EpVideo> epVideos, OutputOption outputOption, final OnEditorListener onEditorListener) {
		String appDir = context.getFilesDir().getAbsolutePath() + "/EpVideos/";
		String fileName = "ffmpeg_concat.txt";
		List<String> videos = new ArrayList<>();
		for (EpVideo e : epVideos) {
			videos.add(e.getVideoPath());
		}
		FileUtils.writeTxtToFile(videos, appDir, fileName);
		CmdList cmd = new CmdList();
		cmd.append("ffmpeg").append("-y").append("-f").append("concat").append("-safe")
				.append("0").append("-i").append(appDir + fileName)
				.append("-c").append("copy").append(outputOption.outPath);
		long duration = 0;
		for (EpVideo ep : epVideos) {
			long d = VideoUitls.getDuration(ep.getVideoPath());
			if (d != 0) {
				duration += d;
			} else {
				break;
			}
		}
		execCmd(cmd, duration, onEditorListener);
	}

	/**
	 * 添加背景音乐
	 *
	 * @param videoin          视频文件
	 * @param audioin          音频文件
	 * @param output           输出路径
	 * @param videoVolume      视频原声音音量(例:0.7为70%)
	 * @param audioVolume      背景音乐音量(例:1.5为150%)
	 * @param onEditorListener 回调监听
	 */
	public static void music(String videoin, String audioin, String output, float videoVolume, float audioVolume, OnEditorListener onEditorListener) {
		MediaExtractor mediaExtractor = new MediaExtractor();
		try {
			mediaExtractor.setDataSource(videoin);
		} catch (IOException e) {
			e.printStackTrace();
			return;
		}
		int at = TrackUtils.selectAudioTrack(mediaExtractor);
		CmdList cmd = new CmdList();
		cmd.append("ffmpeg").append("-y").append("-i").append(videoin);
		if (at == -1) {
			int vt = TrackUtils.selectVideoTrack(mediaExtractor);
			float duration = (float) mediaExtractor.getTrackFormat(vt).getLong(MediaFormat.KEY_DURATION) / 1000 / 1000;
			cmd.append("-ss").append("0").append("-t").append(duration).append("-i").append(audioin).append("-acodec").append("copy").append("-vcodec").append("copy");
		} else {
			cmd.append("-i").append(audioin).append("-filter_complex")
					.append("[0:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,volume=" + videoVolume + "[a0];[1:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,volume=" + audioVolume + "[a1];[a0][a1]amix=inputs=2:duration=first[aout]")
					.append("-map").append("[aout]").append("-ac").append("2").append("-c:v")
					.append("copy").append("-map").append("0:v:0");
		}
		cmd.append(output);
		mediaExtractor.release();
		long d = VideoUitls.getDuration(videoin);
		execCmd(cmd, d, onEditorListener);
	}

	/**
	 * 音视频分离
	 *
	 * @param videoin          视频文件
	 * @param out              输出文件路径
	 * @param format           输出类型
	 * @param onEditorListener 回调监听
	 */
	public static void demuxer(String videoin, String out, Format format, OnEditorListener onEditorListener) {
		CmdList cmd = new CmdList();
		cmd.append("ffmpeg").append("-y").append("-i").append(videoin);
		switch (format) {
			case MP3:
				cmd.append("-vn").append("-acodec").append("libmp3lame");
				break;
			case MP4:
				cmd.append("-vcodec").append("copy").append("-an");
				break;
		}
		cmd.append(out);
		long d = VideoUitls.getDuration(videoin);
		execCmd(cmd, d, onEditorListener);
	}

	/**
	 * 音视频倒放
	 *
	 * @param videoin          视频文件
	 * @param out              输出文件路径
	 * @param vr               是否视频倒放
	 * @param ar               是否音频倒放
	 * @param onEditorListener 回调监听
	 */
	public static void reverse(String videoin, String out, boolean vr, boolean ar, OnEditorListener onEditorListener) {
		if (!vr && !ar) {
			Log.e("ffmpeg", "parameter error");
			onEditorListener.onFailure();
			return;
		}
		CmdList cmd = new CmdList();
		cmd.append("ffmpeg").append("-y").append("-i").append(videoin).append("-filter_complex");
		String filter = "";
		if (vr) {
			filter += "[0:v]reverse[v];";
		}
		if (ar) {
			filter += "[0:a]areverse[a];";
		}
		cmd.append(filter.substring(0, filter.length() - 1));
		if (vr) {
			cmd.append("-map").append("[v]");
		}
		if (ar) {
			cmd.append("-map").append("[a]");
		}
		if (ar && !vr) {
			cmd.append("-acodec").append("libmp3lame");
		}
		cmd.append("-preset").append("superfast").append(out);
		long d = VideoUitls.getDuration(videoin);
		execCmd(cmd, d, onEditorListener);
	}

	/**
	 * 音视频变速
	 *
	 * @param videoin          音视频文件
	 * @param out              输出路径
	 * @param times            倍率（调整范围0.25-4）
	 * @param pts              加速类型
	 * @param onEditorListener 回调接口
	 */
	public static void changePTS(String videoin, String out, float times, PTS pts, OnEditorListener onEditorListener) {
		if (times < 0.25f || times > 4.0f) {
			Log.e("ffmpeg", "times can only be 0.25 to 4");
			onEditorListener.onFailure();
			return;
		}
		CmdList cmd = new CmdList();
		cmd.append("ffmpeg").append("-y").append("-i").append(videoin);
		String t = "atempo=" + times;
		if (times < 0.5f) {
			t = "atempo=0.5,atempo=" + (times / 0.5f);
		} else if (times > 2.0f) {
			t = "atempo=2.0,atempo=" + (times / 2.0f);
		}
		Log.v("ffmpeg", "atempo:" + t);
		switch (pts) {
			case VIDEO:
				cmd.append("-filter_complex").append("[0:v]setpts=" + (1 / times) + "*PTS").append("-an");
				break;
			case AUDIO:
				cmd.append("-filter:a").append(t);
				break;
			case ALL:
				cmd.append("-filter_complex").append("[0:v]setpts=" + (1 / times) + "*PTS[v];[0:a]" + t + "[a]")
						.append("-map").append("[v]").append("-map").append("[a]");
				break;
		}
		cmd.append("-preset").append("superfast").append(out);
		long d = VideoUitls.getDuration(videoin);
		double dd = d / times;
		long ddd = (long) dd;
		execCmd(cmd, ddd, onEditorListener);
	}

	/**
	 * 视频转图片
	 *
	 * @param videoin			音视频文件
	 * @param out				输出路径
	 * @param w					输出图片宽度
	 * @param h					输出图片高度
	 * @param rate				每秒视频生成图片数
	 * @param onEditorListener	回调接口
	 */
	public static void video2pic(String videoin, String out, int w, int h, float rate, OnEditorListener onEditorListener) {
		if (w <= 0 || h <= 0) {
			Log.e("ffmpeg", "width and height must greater than 0");
			onEditorListener.onFailure();
			return;
		}
		if(rate <= 0){
			Log.e("ffmpeg", "rate must greater than 0");
			onEditorListener.onFailure();
			return;
		}
		CmdList cmd = new CmdList();
		cmd.append("ffmpeg").append("-y").append("-i").append(videoin)
				.append("-r").append(rate).append("-s").append(w+"x"+h).append("-q:v").append(2)
				.append("-f").append("image2").append("-preset").append("superfast").append(out);
		long d = VideoUitls.getDuration(videoin);
		execCmd(cmd, d, onEditorListener);
	}

	/**
	 * 图片转视频
	 *
	 * @param videoin			视频文件
	 * @param out				输出路径
	 * @param w					输出视频宽度
	 * @param h					输出视频高度
	 * @param rate				输出视频帧率
	 * @param onEditorListener	回调接口
	 */
	public static void pic2video(String videoin, String out, int w, int h, float rate, OnEditorListener onEditorListener) {
		if (w < 0 || h < 0) {
			Log.e("ffmpeg", "width and height must greater than 0");
			onEditorListener.onFailure();
			return;
		}
		if(rate <= 0){
			Log.e("ffmpeg", "rate must greater than 0");
			onEditorListener.onFailure();
			return;
		}
		CmdList cmd = new CmdList();
		cmd.append("ffmpeg").append("-y").append("-f").append("image2").append("-i").append(videoin)
				.append("-vcodec").append("libx264")
				.append("-r").append(rate);
//				.append("-b").append("10M");
				if(w > 0 && h > 0) {
					cmd.append("-s").append(w + "x" + h);
				}
		cmd.append(out);
		long d = VideoUitls.getDuration(videoin);
		execCmd(cmd, d, onEditorListener);
	}


	/**
	 * 输出选项设置
	 */
	public static class OutputOption {
		static final int ONE_TO_ONE = 1;// 1:1
		static final int FOUR_TO_THREE = 2;// 4:3
		static final int SIXTEEN_TO_NINE = 3;// 16:9
		static final int NINE_TO_SIXTEEN = 4;// 9:16
		static final int THREE_TO_FOUR = 5;// 3:4

		String outPath;//输出路径
		public int frameRate = 0;//帧率
		public int bitRate = 0;//比特率(一般设置10M)
		public String outFormat = "";//输出格式(目前暂时只支持mp4,x264,mp3,gif)
		private int width = 0;//输出宽度
		private int height = 0;//输出高度
		private int sar = 6;//输出宽高比

		public OutputOption(String outPath) {
			this.outPath = outPath;
		}

		/**
		 * 获取宽高比
		 *
		 * @return 1
		 */
		public String getSar() {
			String res;
			switch (sar) {
				case ONE_TO_ONE:
					res = "1/1";
					break;
				case FOUR_TO_THREE:
					res = "4/3";
					break;
				case THREE_TO_FOUR:
					res = "3/4";
					break;
				case SIXTEEN_TO_NINE:
					res = "16/9";
					break;
				case NINE_TO_SIXTEEN:
					res = "9/16";
					break;
				default:
					res = width + "/" + height;
					break;
			}
			return res;
		}

		public void setSar(int sar) {
			this.sar = sar;
		}

		/**
		 * 获取输出信息
		 *
		 * @return 1
		 */
		String getOutputInfo() {
			StringBuilder res = new StringBuilder();
			if (frameRate != 0) {
				res.append(" -r ").append(frameRate);
			}
			if (bitRate != 0) {
				res.append(" -b ").append(bitRate).append("M");
			}
			if (!outFormat.isEmpty()) {
				res.append(" -f ").append(outFormat);
			}
			return res.toString();
		}

		/**
		 * 设置宽度
		 *
		 * @param width 宽
		 */
		public void setWidth(int width) {
			if (width % 2 != 0) width -= 1;
			this.width = width;
		}

		/**
		 * 设置高度
		 *
		 * @param height 高
		 */
		public void setHeight(int height) {
			if (height % 2 != 0) height -= 1;
			this.height = height;
		}
	}

	/**
	 * 开始处理
	 *
	 * @param cmd              命令
	 * @param duration         视频时长（单位微秒）
	 * @param onEditorListener 回调接口
	 */
	public static void execCmd(String cmd, long duration, final OnEditorListener onEditorListener) {
		cmd = "ffmpeg " + cmd;
		String[] cmds = cmd.split(" ");
		FFmpegCmd.exec(cmds, duration, new OnEditorListener() {
			@Override
			public void onSuccess() {
				onEditorListener.onSuccess();
			}

			@Override
			public void onFailure() {
				onEditorListener.onFailure();
			}

			@Override
			public void onProgress(final float progress) {
				onEditorListener.onProgress(progress);
			}
		});
	}

	/**
	 * 开始处理
	 *
	 * @param cmd              命令
	 * @param duration         视频时长（单位微秒）
	 * @param onEditorListener 回调接口
	 */
	private static void execCmd(CmdList cmd, long duration, final OnEditorListener onEditorListener) {
		String[] cmds = cmd.toArray(new String[cmd.size()]);
		String cmdLog = "";
		for (String ss : cmds) {
			cmdLog += cmds;
		}
		Log.v("EpMediaF", "cmd:" + cmdLog);
		FFmpegCmd.exec(cmds, duration, new OnEditorListener() {
			@Override
			public void onSuccess() {
				onEditorListener.onSuccess();
			}

			@Override
			public void onFailure() {
				onEditorListener.onFailure();
			}

			@Override
			public void onProgress(final float progress) {
				onEditorListener.onProgress(progress);
			}
		});
	}
}
