+ * Created by Administrator on 13-8-17.
+ */
+public class DialogItem {
+
+
+ public long dialog_id;
+ public int role_id;
+ public String content_en;
+ public String content_cn;
+ public int time_begin;
+ public int time_end;
+ public String fea;
+ public String fea_v2;
+ public String fea_content;
+ public int fea_byte;
+ public int fea_v2_byte;
+ public int expl_count;
+
+ /**
+ * 获取对应文件路径
+ *
+ * @return
+ */
+ public String getMp3FilePath() {
+ return "/storage/emulated/0/.mofunshow/records" + "/" + this.dialog_id + ".mp3";
+ }
+
+ public String getRecordFilePath() {
+ return "/storage/emulated/0/.mofunshow/records" + "/" + this.dialog_id + ".wav";
+ }
+
+ /**
+ * 判断是否属于某角色,若传入0则认为是全角色,返回true
+ *
+ * @param roleId
+ * @return
+ */
+ public boolean isBelongToRole(long roleId) {
+ return (roleId <= 0) || (roleId > 0
+ && roleId == role_id);
+ }
+}
diff --git a/app/src/main/java/com/code/FileUtil.java b/app/src/main/java/com/code/FileUtil.java
new file mode 100644
index 0000000..2e40bca
--- /dev/null
+++ b/app/src/main/java/com/code/FileUtil.java
@@ -0,0 +1,53 @@
+package com.code;
+
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Created by Administrator on 13-8-19.
+ */
+public class FileUtil {
+
+
+ public static boolean writeJsonFile(String file_name, String json_content) {
+ FileOutputStream fout = null;
+
+ try {
+ fout = new FileOutputStream(file_name);
+ byte[] bytes = json_content.getBytes();
+ fout.write(bytes);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ } finally {
+ if (fout != null) {
+ try {
+ fout.flush();
+ fout.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return true;
+ }
+
+ public static String readJsonFile(String file_name) {
+ String json_content = "";
+ try {
+ FileInputStream fin = new FileInputStream(file_name);
+ int length = fin.available();
+ byte[] buffer = new byte[length];
+ fin.read(buffer);
+ json_content = new String(buffer, "UTF-8");
+ fin.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return json_content;
+ }
+ return json_content;
+ }
+}
diff --git a/app/src/main/java/com/code/MainActivity.java b/app/src/main/java/com/code/MainActivity.java
new file mode 100644
index 0000000..9a07143
--- /dev/null
+++ b/app/src/main/java/com/code/MainActivity.java
@@ -0,0 +1,144 @@
+package com.code;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.media.MediaMetadataRetriever;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import org.ffmpeg.android.R;
+import org.ffmpeg.android.test.MixTest;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+import butterknife.ButterKnife;
+import butterknife.InjectView;
+
+/**
+ * @author Chad
+ * @title com.code
+ * @description
+ * @modifier
+ * @date
+ * @since 16/5/31 上午12:04
+ **/
+public class MainActivity extends Activity {
+
+ @InjectView(R.id.text_mix)
+ Button textMix;
+ String mp4FilePath = "/storage/emulated/0/.mofunshow/movies/90331/20160529171913002801000325.mp4";
+ String bgMp3FilePath = "/storage/emulated/0/.mofunshow/movies/90331/20160520191953876045000830.aac";
+ String jsonFilePath = "/storage/emulated/0/.mofunshow/movies/90331/20160520192149267651000519.json";
+ String bgWavFilePath = "/storage/emulated/0/.mofunshow/movies/90331/2015082214101800000358814.wav";
+ String mp4OutPath = "/storage/emulated/0/.mofunshow/movies/90331/test_out.mp4";
+ String mp4AllPath = "/storage/emulated/0/.mofunshow/movies/90331/test_out_all.mp4";
+ String pngPath = "/storage/emulated/0/.mofunshow/movies/90331/1.png";
+ String pngTempPath = "/storage/emulated/0/.mofunshow/movies/90331/temp.png";
+ String tempPath = "/storage/emulated/0/ffmpeg/";
+ @InjectView(R.id.test_merge)
+ Button testMerge;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_activity);
+ ButterKnife.inject(this);
+ new File(tempPath).mkdirs();
+ File[] files = new File("/system/fonts/").listFiles();
+ for (File file : files) {
+ String fileName = file.getName().toLowerCase();
+ if (fileName.contains("miui")) {
+ if (fileName.toLowerCase().equals("miui-regular")) {
+ Config.SYSTEM_DEFAULT_FONT_PATH = file.getAbsolutePath();
+ break;
+ }
+ Config.SYSTEM_DEFAULT_FONT_PATH = file.getAbsolutePath();
+ break;
+ }
+ }
+ textMix.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+// List dialog_list;
+// dialog_list = new Gson().fromJson(FileUtil.readJsonFile(jsonFilePath), new TypeToken>() {}.getType());
+// try {
+// MixTest.test(tempPath,mp4FilePath,bgMp3FilePath,new Clip(mp4OutPath),getApplicationContext());
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+
+// try {
+// List list = new ArrayList();
+// list.add(getImageFileFromVideo(mp4FilePath,pngTempPath));
+// MixTest.testJpegToMp4(mp4FilePath,tempPath,list,mp4OutPath,getApplicationContext());
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+ try {
+ MixTest.testMakeLastFrameFilter(getImageFileFromVideo(mp4FilePath, pngTempPath), getTimeLengthFromVideo(mp4FilePath), mp4FilePath, tempPath, mp4OutPath, getApplicationContext());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+ });
+ testMerge.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ try {
+// MixTest.testMergeMp4(mp4FilePath, mp4OutPath, tempPath, mp4AllPath, getApplicationContext());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+
+ }
+
+ public Bitmap getBitmapsFromVideo(String mp4FilePath) {
+ Bitmap bitmap = null;
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(mp4FilePath);
+ // 取得视频的长度(单位为毫秒)
+ String time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+ // 取得视频的长度(单位为秒)
+ int seconds = Integer.valueOf(time) / 1000;
+ // 得到每一秒时刻的bitmap比如第一秒,第二秒
+ bitmap = retriever.getFrameAtTime(seconds * 1000 * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
+ return bitmap;
+ }
+
+ public long getTimeLengthFromVideo(String mp4FilePath) {
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(mp4FilePath);
+ // 取得视频的长度(单位为毫秒)
+ String time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+ // 取得视频的长度(单位为秒)
+ int milins = Integer.valueOf(time);
+ return milins;
+ }
+
+ public String getImageFileFromVideo(String mp4FilePath, String outPath) {
+ Bitmap bitmap = null;
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(mp4FilePath);
+ // 取得视频的长度(单位为毫秒)
+ String time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+ // 取得视频的长度(单位为秒)
+ int millSeconds = Integer.valueOf(time);
+ int seconds = millSeconds / 1000;
+ // 得到每一秒时刻的bitmap比如第一秒,第二秒
+ bitmap = retriever.getFrameAtTime(millSeconds * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(outPath);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 80, fos);
+ fos.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return outPath;
+ }
+}
diff --git a/app/src/main/java/net/sourceforge/sox/CrossfadeCat.java b/app/src/main/java/net/sourceforge/sox/CrossfadeCat.java
new file mode 100644
index 0000000..72d9ecc
--- /dev/null
+++ b/app/src/main/java/net/sourceforge/sox/CrossfadeCat.java
@@ -0,0 +1,99 @@
+package net.sourceforge.sox;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Concatenates two files together with a crossfade of user
+ * defined mClipLength.
+ *
+ * It is a Java port of the scripts/crossfade_cat.sh script
+ * in the sox source tree.
+ *
+ * Original script by Kester Clegg, with modifications by Chris
+ * Bagwell.
+ *
+ * @author Abel Luck
+ */
+// TODO make runnable?
+public class CrossfadeCat {
+ private final static String TAG = "SOX-XFADE";
+ private SoxController mController;
+ private String mFirstFile;
+ private String mSecondFile;
+ private double mFadeLength;
+ private String mFinalMix;
+
+ public CrossfadeCat(SoxController controller, String firstFile, String secondFile, double fadeLength, String outFile) {
+ mController = controller;
+ mFirstFile = firstFile;
+ mSecondFile = secondFile;
+ mFadeLength = fadeLength;
+ mFinalMix = outFile;
+
+ //double mClipLength = mController.getLength(mFirstFile);
+ }
+
+ public boolean start() throws Exception {
+ // find mClipLength of first file
+
+
+ // Obtain trimLength seconds of fade out position from the first File
+ double firstFileLength = mController.getLength(mFirstFile);
+ double trimLength = firstFileLength - mFadeLength;
+
+ String trimmedOne = mController.trimAudio(mFirstFile, trimLength, mFadeLength);
+
+ if (trimmedOne == null)
+ throw new IOException("audio trim did not complete: " + mFirstFile);
+
+ // We assume a fade out is needed (i.e., firstFile doesn't already fade out)
+
+ String fadedOne = mController.fadeAudio(trimmedOne, "t", 0, mFadeLength, mFadeLength);
+ if (fadedOne == null)
+ throw new IOException("audio fade did not complete: " + trimmedOne);
+
+ // Get crossfade section from the second file
+ String trimmedTwo = mController.trimAudio(mSecondFile, 0, mFadeLength);
+ if (trimmedTwo == null)
+ throw new IOException("audio trim did not complete: " + mSecondFile);
+
+ String fadedTwo = mController.fadeAudio(trimmedTwo, "t", mFadeLength, -1, -1);
+ if (fadedTwo == null)
+ throw new IOException("audio fade did not complete: " + trimmedTwo);
+
+ // Mix crossfaded files together at full volume
+ ArrayList files = new ArrayList();
+ files.add(fadedOne);
+ files.add(fadedTwo);
+
+ String crossfaded = new File(mFirstFile).getCanonicalPath() + "-x-" + new File(mSecondFile).getName() + ".wav";
+ crossfaded = mController.combineMix(files, crossfaded);
+ if (crossfaded == null)
+ throw new IOException("crossfade did not complete");
+
+ // Trim off crossfade sections from originals
+ String trimmedThree = mController.trimAudio(mFirstFile, 0, trimLength);
+ if (trimmedThree == null)
+ throw new IOException("crossfade trim beginning did not complete");
+
+ String trimmedFour = mController.trimAudio(mSecondFile, mFadeLength, -1);
+ if (trimmedFour == null)
+ throw new IOException("crossfade trim end did not complete");
+
+ // Combine into final mix
+ files.clear();
+ files.add(trimmedThree);
+ files.add(crossfaded);
+ files.add(trimmedFour);
+ mFinalMix = mController.combine(files, mFinalMix);
+
+ if (mFinalMix == null)
+ throw new IOException("final mix did not complete");
+
+ return true;
+ }
+
+
+}
diff --git a/app/src/main/java/net/sourceforge/sox/SoxController.java b/app/src/main/java/net/sourceforge/sox/SoxController.java
new file mode 100644
index 0000000..d23fde9
--- /dev/null
+++ b/app/src/main/java/net/sourceforge/sox/SoxController.java
@@ -0,0 +1,380 @@
+package net.sourceforge.sox;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import org.ffmpeg.android.R;
+import org.ffmpeg.android.ShellUtils.ShellCallback;
+
+import android.content.Context;
+import android.util.Log;
+
+public class SoxController {
+ private final static String TAG = "SOX";
+ String[] libraryAssets = {"sox"};
+ private String soxBin;
+ private File fileBinDir;
+ private ShellCallback callback;
+
+ public SoxController(Context context, File fileAppRoot, ShellCallback _callback) throws FileNotFoundException, IOException {
+ callback = _callback;
+
+ installBinaries(context, false);
+ fileBinDir = new File(soxBin).getParentFile();
+
+ }
+
+
+ public void installBinaries(Context context, boolean overwrite) {
+ soxBin = installBinary(context, R.raw.sox, "sox", overwrite);
+ }
+
+ public String getBinaryPath() {
+ return soxBin;
+ }
+
+ private static String installBinary(Context ctx, int resId, String filename, boolean upgrade) {
+ try {
+ File f = new File(ctx.getDir("bin", 0), filename);
+ if (f.exists()) {
+ f.delete();
+ }
+ copyRawFile(ctx, resId, f, "0755");
+ return f.getCanonicalPath();
+ } catch (Exception e) {
+ Log.e(TAG, "installBinary failed: " + e.getLocalizedMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Copies a raw resource file, given its ID to the given location
+ *
+ * @param ctx context
+ * @param resid resource id
+ * @param file destination file
+ * @param mode file permissions (E.g.: "755")
+ * @throws IOException on error
+ * @throws InterruptedException when interrupted
+ */
+ private static void copyRawFile(Context ctx, int resid, File file, String mode) throws IOException, InterruptedException {
+ final String abspath = file.getAbsolutePath();
+ // Write the iptables binary
+ final FileOutputStream out = new FileOutputStream(file);
+ final InputStream is = ctx.getResources().openRawResource(resid);
+ byte buf[] = new byte[1024];
+ int len;
+ while ((len = is.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ out.close();
+ is.close();
+ // Change the permissions
+ Runtime.getRuntime().exec("chmod " + mode + " " + abspath).waitFor();
+ }
+
+
+ private class LengthParser implements ShellCallback {
+ public double length;
+ public int retValue = -1;
+
+ @Override
+ public void shellOut(String shellLine) {
+ if (!shellLine.startsWith("Length"))
+ return;
+ String[] split = shellLine.split(":");
+ if (split.length != 2) return;
+
+ String lengthStr = split[1].trim();
+
+ try {
+ length = Double.parseDouble(lengthStr);
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void processComplete(int exitValue) {
+ retValue = exitValue;
+
+ }
+ }
+
+ /**
+ * Retrieve the length of the audio file
+ * sox file.wav 2>&1 -n stat | grep Length | cut -d : -f 2 | cut -f 1
+ *
+ * @return the length in seconds or null
+ */
+ public double getLength(String path) {
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(soxBin);
+ cmd.add(path);
+ cmd.add("-n");
+ cmd.add("stat");
+
+ LengthParser sc = new LengthParser();
+
+ try {
+ execSox(cmd, sc);
+ } catch (Exception e) {
+ return -1;
+ }
+
+ return sc.length;
+ }
+
+ /**
+ * Discard all audio not between start and length (length = end by default)
+ * sox -e signed-integer -b 16 outFile trim
+ *
+ * @param start
+ * @param length (optional)
+ * @return path to trimmed audio
+ */
+ public String trimAudio(String path, double start, double length) throws Exception {
+ ArrayList cmd = new ArrayList();
+
+ File file = new File(path);
+ String outFile = file.getCanonicalPath() + "_trimmed.wav";
+ cmd.add(soxBin);
+ cmd.add(path);
+ cmd.add("-e");
+ cmd.add("signed-integer");
+ cmd.add("-b");
+ cmd.add("16");
+ cmd.add(outFile);
+ cmd.add("trim");
+ cmd.add(start + "");
+ if (length != -1)
+ cmd.add(length + "");
+
+ int rc = execSox(cmd, callback);
+ if (rc != 0) {
+ outFile = null;
+ }
+
+ if (file.exists())
+ return outFile;
+ else
+ return null;
+
+ }
+
+ /**
+ * Fade audio file
+ * sox outFile fade
+ *
+ * @param path
+ * @param type
+ * @param fadeInLength specify 0 if no fade in is desired
+ * @param stopTime (optional)
+ * @param fadeOutLength (optional)
+ * @return
+ */
+ public String fadeAudio(String path, String type, double fadeInLength, double stopTime, double fadeOutLength) throws IOException {
+
+ final List curves = Arrays.asList(new String[]{"q", "h", "t", "l", "p"});
+
+ if (!curves.contains(type)) {
+ throw new RuntimeException("fadeAudio: passed invalid type: " + type);
+
+ }
+
+ File file = new File(path);
+ String outFile = file.getCanonicalPath() + "_faded.wav";
+
+ ArrayList cmd = new ArrayList();
+ cmd.add(soxBin);
+ cmd.add(path);
+ cmd.add(outFile);
+ cmd.add("fade");
+ cmd.add(type);
+ cmd.add(fadeInLength + "");
+ if (stopTime != -1)
+ cmd.add(stopTime + "");
+ if (fadeOutLength != -1)
+ cmd.add(fadeOutLength + "");
+
+ try {
+ int rc = execSox(cmd, callback);
+ if (rc != 0) {
+ //Log.e(TAG, "fadeAudio receieved non-zero return code!");
+
+ outFile = null;
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return outFile;
+ }
+
+ /**
+ * Combine and mix audio files
+ * sox -m -v 1.0 file[0] -v 1.0 file[1] ... -v 1.0 file[n] outFile
+ * TODO support passing of volume
+ *
+ * @param files
+ * @return combined and mixed file (null on failure)
+ */
+ public String combineMix(List files, String outFile) {
+ ArrayList cmd = new ArrayList();
+ cmd.add(soxBin);
+ cmd.add("-m");
+
+ for (String file : files) {
+ cmd.add("-v");
+ cmd.add("1.0");
+ cmd.add(file);
+ }
+ cmd.add(outFile);
+
+ try {
+ int rc = execSox(cmd, callback);
+ if (rc != 0) {
+ // Log.e(TAG, "combineMix receieved non-zero return code!");
+ outFile = null;
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return outFile;
+ }
+
+ /**
+ * Simple combiner
+ * sox file[0] file[1] ... file[n]
+ *
+ * @param files
+ * @param outFile
+ * @return outFile or null on failure
+ */
+ public String combine(List files, String outFile) throws Exception {
+ ArrayList cmd = new ArrayList();
+ cmd.add(soxBin);
+
+ for (String file : files) {
+ cmd.add(file);
+ }
+ cmd.add(outFile);
+
+ int rc = execSox(cmd, callback);
+ if (rc != 0) {
+ throw new Exception("exit code: " + rc);
+
+ }
+
+ return outFile;
+ }
+
+ /**
+ * Takes a seconds.frac value and formats it into:
+ * hh:mm:ss:ss.frac
+ *
+ * @param seconds
+ */
+ /*
+ public String formatTimePeriod(double seconds) {
+
+ long milliTime = (long)(seconds * 100f);
+ Date dateTime = new Date(milliTime);
+ return String.format(Locale.US, "%s:%s.%s", dateTime.getHours(),dateTime.getMinutes(),dateTime.getSeconds());
+ }*/
+ public int execSox(List cmd, ShellCallback sc) throws IOException,
+ InterruptedException {
+
+ String soxBin = new File(fileBinDir, "sox").getCanonicalPath();
+
+ Runtime.getRuntime().exec("chmod 700 " + soxBin);
+ return execProcess(cmd, sc);
+ }
+
+ private int execProcess(List cmds, ShellCallback sc)
+ throws IOException, InterruptedException {
+
+ //ensure that the arguments are in the correct Locale format
+ for (String cmd : cmds) {
+ cmd = String.format(Locale.US, "%s", cmd);
+ }
+
+ ProcessBuilder pb = new ProcessBuilder(cmds);
+ pb.directory(fileBinDir);
+
+ StringBuffer cmdlog = new StringBuffer();
+
+ for (String cmd : cmds) {
+ cmdlog.append(cmd);
+ cmdlog.append(' ');
+ }
+
+ sc.shellOut(cmdlog.toString());
+
+ // pb.redirectErrorStream(true);
+ Process process = pb.start();
+
+ // any error message?
+ StreamGobbler errorGobbler = new StreamGobbler(
+ process.getErrorStream(), "ERROR", sc);
+
+ // any output?
+ StreamGobbler outputGobbler = new StreamGobbler(
+ process.getInputStream(), "OUTPUT", sc);
+
+ // kick them off
+ errorGobbler.start();
+ outputGobbler.start();
+
+ int exitVal = process.waitFor();
+
+ while (outputGobbler.isAlive() || errorGobbler.isAlive()) ;
+
+ sc.processComplete(exitVal);
+
+ return exitVal;
+ }
+
+ class StreamGobbler extends Thread {
+ InputStream is;
+ String type;
+ ShellCallback sc;
+
+ StreamGobbler(InputStream is, String type, ShellCallback sc) {
+ this.is = is;
+ this.type = type;
+ this.sc = sc;
+ }
+
+ public void run() {
+ try {
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader br = new BufferedReader(isr);
+ String line = null;
+ while ((line = br.readLine()) != null)
+ if (sc != null)
+ sc.shellOut(line);
+
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/ffmpeg/android/Clip.java b/app/src/main/java/org/ffmpeg/android/Clip.java
new file mode 100644
index 0000000..6ffa2c1
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/Clip.java
@@ -0,0 +1,68 @@
+package org.ffmpeg.android;
+
+public class Clip implements Cloneable {
+
+ public int width = -1;
+ public int height = -1;
+
+ public String videoCodec;
+ public String videoFps;
+ public int videoBitrate = -1;
+ public String videoBitStreamFilter;
+
+ public float frameRate = 25;
+
+ public String audioCodec;
+ public int audioChannels = -1;
+ public int audioBitrate = -1;
+ public String audioQuality;
+ public int audioVolume = -1;
+ public String audioBitStreamFilter;
+
+ public String path;
+ public String format;
+ public String mimeType;
+
+ public String startTime; //00:00:00 or seconds format
+ public double duration = -1; //00:00:00 or seconds format
+
+ public String videoFilter;
+ public String audioFilter;
+
+ public String qscale;
+ public String aspect;
+ public int passCount = 1; //default
+
+ public Clip() {
+
+ }
+
+ public Clip(String path) {
+ this.path = path;
+ }
+
+ public Clip clone() throws CloneNotSupportedException {
+ return (Clip) super.clone();
+ }
+
+ public boolean isImage() {
+ if (mimeType != null)
+ return mimeType.startsWith("image");
+ else
+ return false;
+ }
+
+ public boolean isVideo() {
+ if (mimeType != null)
+ return mimeType.startsWith("video");
+ else
+ return false;
+ }
+
+ public boolean isAudio() {
+ if (mimeType != null)
+ return mimeType.startsWith("audio");
+ else
+ return false;
+ }
+}
diff --git a/app/src/main/java/org/ffmpeg/android/FfmpegController.java b/app/src/main/java/org/ffmpeg/android/FfmpegController.java
new file mode 100644
index 0000000..a4bd16e
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/FfmpegController.java
@@ -0,0 +1,1819 @@
+package org.ffmpeg.android;
+
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.media.MediaMetadataRetriever;
+import android.util.Log;
+
+import org.ffmpeg.android.ShellUtils.ShellCallback;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+public class FfmpegController {
+
+
+ private String mFfmpegBin;
+
+ private final static String TAG = "FFMPEG";
+
+ private File mFileTemp;
+
+ private String mCmdCat = "sh cat";
+
+ public FfmpegController(Context context, File fileTemp, boolean overwrite) throws FileNotFoundException, IOException {
+ mFileTemp = fileTemp;
+ installBinaries(context, overwrite);
+ }
+
+ public FfmpegController(Context context, File fileTemp) throws FileNotFoundException, IOException {
+ mFileTemp = fileTemp;
+ installBinaries(context, false);
+ }
+ public void installBinaries(Context context, boolean overwrite) {
+ mFfmpegBin = installBinary(context, R.raw.ffmpeg, "ffmpeg", overwrite);
+ }
+
+ public String getBinaryPath() {
+ return mFfmpegBin;
+ }
+
+ private static String installBinary(Context ctx, int resId, String filename, boolean upgrade) {
+ try {
+ File f = new File(ctx.getDir("bin", 0), filename);
+ if (upgrade) {
+ if (f.exists()) {
+ f.delete();
+ }
+ copyRawFile(ctx, resId, f, "0755");
+ }
+ return f.getCanonicalPath();
+ } catch (Exception e) {
+ Log.e(TAG, "installBinary failed: " + e.getLocalizedMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Copies a raw resource file, given its ID to the given location
+ *
+ * @param ctx context
+ * @param resid resource id
+ * @param file destination file
+ * @param mode file permissions (E.g.: "755")
+ * @throws IOException on error
+ * @throws InterruptedException when interrupted
+ */
+ private static void copyRawFile(Context ctx, int resid, File file, String mode) throws IOException, InterruptedException {
+ final String abspath = file.getAbsolutePath();
+ // Write the iptables binary
+ final FileOutputStream out = new FileOutputStream(file);
+ final InputStream is = ctx.getResources().openRawResource(resid);
+ byte buf[] = new byte[1024];
+ int len;
+ while ((len = is.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ out.close();
+ is.close();
+ // Change the permissions
+ Runtime.getRuntime().exec("chmod " + mode + " " + abspath).waitFor();
+ }
+
+
+ private void execFFMPEG(List cmd, ShellCallback sc, File fileExec) throws IOException, InterruptedException {
+
+ enablePermissions();
+
+ execProcess(cmd, sc, fileExec);
+ }
+
+ private void enablePermissions() throws IOException {
+ Runtime.getRuntime().exec("chmod 700 " + mFfmpegBin);
+
+ }
+
+ private void execFFMPEG(List cmd, ShellCallback sc) throws IOException, InterruptedException {
+ execFFMPEG(cmd, sc, new File(mFfmpegBin).getParentFile());
+ }
+
+ private int execProcess(List cmds, ShellCallback sc, File fileExec) throws IOException, InterruptedException {
+
+ //ensure that the arguments are in the correct Locale format
+ for (String cmd : cmds) {
+ cmd = String.format(Locale.US, "%s", cmd);
+ }
+
+ ProcessBuilder pb = new ProcessBuilder(cmds);
+ pb.directory(fileExec);
+
+ StringBuffer cmdlog = new StringBuffer();
+
+ for (String cmd : cmds) {
+ cmdlog.append(cmd);
+ cmdlog.append(' ');
+ }
+
+ sc.shellOut(cmdlog.toString());
+
+ //pb.redirectErrorStream(true);
+
+ Process process = pb.start();
+
+
+ // any error message?
+ StreamGobbler errorGobbler = new StreamGobbler(
+ process.getErrorStream(), "ERROR", sc);
+
+ // any output?
+ StreamGobbler outputGobbler = new
+ StreamGobbler(process.getInputStream(), "OUTPUT", sc);
+
+ errorGobbler.start();
+ outputGobbler.start();
+
+ int exitVal = process.waitFor();
+
+ sc.processComplete(exitVal);
+
+ return exitVal;
+
+ }
+
+
+ private int execProcess(String cmd, ShellCallback sc, File fileExec) throws IOException, InterruptedException {
+
+ //ensure that the argument is in the correct Locale format
+ cmd = String.format(Locale.US, "%s", cmd);
+
+ ProcessBuilder pb = new ProcessBuilder(cmd);
+ pb.directory(fileExec);
+
+ // pb.redirectErrorStream(true);
+ Process process = pb.start();
+
+
+ // any error message?
+ StreamGobbler errorGobbler = new
+ StreamGobbler(process.getErrorStream(), "ERROR", sc);
+
+ // any output?
+ StreamGobbler outputGobbler = new
+ StreamGobbler(process.getInputStream(), "OUTPUT", sc);
+
+ // kick them off
+ errorGobbler.start();
+ outputGobbler.start();
+
+
+ int exitVal = process.waitFor();
+
+ sc.processComplete(exitVal);
+
+ return exitVal;
+
+
+ }
+
+
+ public class Argument {
+ String key;
+ String value;
+
+ public static final String VIDEOCODEC = "-vcodec";
+ public static final String AUDIOCODEC = "-acodec";
+
+ public static final String CODEC_VIDEO = "-c:v";
+
+ public static final String VIDEOBITSTREAMFILTER = "-vbsf";
+ public static final String AUDIOBITSTREAMFILTER = "-absf";
+
+ public static final String VERBOSITY = "-v";
+ public static final String FILE_INPUT = "-i";
+ public static final String SIZE = "-s";
+ public static final String FRAMERATE = "-r";
+ public static final String FORMAT = "-f";
+ public static final String BITRATE_VIDEO = "-b:v";
+
+ public static final String BITRATE_AUDIO = "-b:a";
+ public static final String CHANNELS_AUDIO = "-ac";
+ public static final String FREQ_AUDIO = "-ar";
+ public static final String NO_AUDIO = "-an";
+
+ public static final String STARTTIME = "-ss";
+ public static final String DURATION = "-t";
+
+ public static final String THREAD = "-threads";
+
+ public static final String PROFILE = "-profile:v";
+
+ public static final String PRESET = "-preset";
+ public static final String PIX_FORMAT = "-pix_fmt";
+ public static final String MAP = "-map";
+ public static final String FILTER_COMPLEX = "-filter_complex";
+
+
+ }
+
+ public void processVideo(Clip in, Clip out, boolean enableExperimental, ShellCallback sc) throws Exception {
+
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ if (in.format != null) {
+ cmd.add(Argument.FORMAT);
+ cmd.add(in.format);
+ }
+
+ if (in.videoCodec != null) {
+ cmd.add(Argument.VIDEOCODEC);
+ cmd.add(in.videoCodec);
+ }
+
+ if (in.audioCodec != null) {
+ cmd.add(Argument.AUDIOCODEC);
+ cmd.add(in.audioCodec);
+ }
+
+ cmd.add("-i");
+ cmd.add(new File(in.path).getCanonicalPath());
+
+ if (out.videoBitrate > 0) {
+ cmd.add(Argument.BITRATE_VIDEO);
+ cmd.add(out.videoBitrate + "k");
+ }
+
+ if (out.width > 0) {
+ cmd.add(Argument.SIZE);
+ cmd.add(out.width + "x" + out.height);
+
+ }
+ if (out.videoFps != null) {
+ cmd.add(Argument.FRAMERATE);
+ cmd.add(out.videoFps);
+ }
+
+ if (out.videoCodec != null) {
+ cmd.add(Argument.VIDEOCODEC);
+ cmd.add(out.videoCodec);
+ }
+
+ if (out.videoBitStreamFilter != null) {
+ cmd.add(Argument.VIDEOBITSTREAMFILTER);
+ cmd.add(out.videoBitStreamFilter);
+ }
+
+
+ if (out.videoFilter != null) {
+ cmd.add("-vf");
+ cmd.add(out.videoFilter);
+ }
+
+ if (out.audioCodec != null) {
+ cmd.add(Argument.AUDIOCODEC);
+ cmd.add(out.audioCodec);
+ }
+
+ if (out.audioBitStreamFilter != null) {
+ cmd.add(Argument.AUDIOBITSTREAMFILTER);
+ cmd.add(out.audioBitStreamFilter);
+ }
+ if (out.audioChannels > 0) {
+ cmd.add(Argument.CHANNELS_AUDIO);
+ cmd.add(out.audioChannels + "");
+ }
+
+ if (out.audioBitrate > 0) {
+ cmd.add(Argument.BITRATE_AUDIO);
+ cmd.add(out.audioBitrate + "k");
+ }
+
+ if (out.format != null) {
+ cmd.add("-f");
+ cmd.add(out.format);
+ }
+
+ if (out.duration > 0) {
+ cmd.add(Argument.DURATION);
+ cmd.add(String.valueOf(out.duration));
+ }
+
+ if (enableExperimental) {
+ cmd.add("-strict");
+ cmd.add("-2");//experimental
+ }
+
+ cmd.add(new File(out.path).getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+ }
+
+
+ public Clip createSlideshowFromImagesAndAudio(ArrayList images, Clip audio, Clip out, int durationPerSlide, ShellCallback sc) throws Exception {
+
+ final String imageBasePath = new File(mFileTemp, "image-").getCanonicalPath();
+ final String imageBaseVariablePath = imageBasePath + "%03d.jpg";
+
+
+ ArrayList cmd = new ArrayList();
+
+
+ String newImagePath = null;
+ int imageCounter = 0;
+
+ Clip imageCover = images.get(0); //add the first image twice
+
+ cmd = new ArrayList();
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ cmd.add("-i");
+ cmd.add(new File(imageCover.path).getCanonicalPath());
+
+ if (out.width != -1 && out.height != -1) {
+ cmd.add("-s");
+ cmd.add(out.width + "x" + out.height);
+ }
+
+ newImagePath = imageBasePath + String.format(Locale.US, "%03d", imageCounter++) + ".jpg";
+ cmd.add(newImagePath);
+
+ execFFMPEG(cmd, sc);
+
+ for (Clip image : images) {
+ cmd = new ArrayList();
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ cmd.add("-i");
+ cmd.add(new File(image.path).getCanonicalPath());
+
+ if (out.width != -1 && out.height != -1) {
+ cmd.add("-s");
+ cmd.add(out.width + "x" + out.height);
+ }
+
+ newImagePath = imageBasePath + String.format(Locale.US, "%03d", imageCounter++) + ".jpg";
+ cmd.add(newImagePath);
+
+ execFFMPEG(cmd, sc);
+
+
+ }
+
+ //then combine them
+ cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ cmd.add("-loop");
+ cmd.add("0");
+
+ cmd.add("-f");
+ cmd.add("image2");
+
+ cmd.add("-r");
+ cmd.add("1/" + durationPerSlide);
+
+ cmd.add("-i");
+ cmd.add(imageBaseVariablePath);
+
+ cmd.add("-strict");
+ cmd.add("-2");//experimental
+
+ String fileTempMpg = new File(mFileTemp, "tmp.mpg").getCanonicalPath();
+
+ cmd.add(fileTempMpg);
+
+ execFFMPEG(cmd, sc);
+
+ //now combine and encode
+ cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ cmd.add("-i");
+ cmd.add(fileTempMpg);
+
+ if (audio != null && audio.path != null) {
+ cmd.add("-i");
+ cmd.add(new File(audio.path).getCanonicalPath());
+
+ cmd.add("-map");
+ cmd.add("0:0");
+
+ cmd.add("-map");
+ cmd.add("1:0");
+
+ cmd.add(Argument.AUDIOCODEC);
+ cmd.add("aac");
+
+ cmd.add(Argument.BITRATE_AUDIO);
+ cmd.add("128k");
+
+ }
+
+ cmd.add("-strict");
+ cmd.add("-2");//experimental
+
+ cmd.add(Argument.VIDEOCODEC);
+
+
+ if (out.videoCodec != null)
+ cmd.add(out.videoCodec);
+ else
+ cmd.add("mpeg4");
+
+ if (out.videoBitrate != -1) {
+ cmd.add(Argument.BITRATE_VIDEO);
+ cmd.add(out.videoBitrate + "k");
+ }
+
+ cmd.add(new File(out.path).getCanonicalPath());
+
+
+ execFFMPEG(cmd, sc);
+
+ return out;
+ }
+
+ public Clip removeVideoAudio(Clip videoIn, Clip out, ShellCallback sc) throws Exception {
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ cmd.add("-i");
+ cmd.add(new File(videoIn.path).getCanonicalPath());
+
+ cmd.add(Argument.AUDIOCODEC);
+ if (out.audioCodec != null)
+ cmd.add(out.audioCodec);
+ else {
+ cmd.add("copy");
+
+ }
+
+ cmd.add(Argument.NO_AUDIO);
+ if (out.videoBitrate != -1) {
+ cmd.add(Argument.BITRATE_VIDEO);
+ cmd.add(out.videoBitrate + "k");
+ }
+
+ if (out.videoFps != null) {
+ cmd.add(Argument.FRAMERATE);
+ cmd.add(out.videoFps);
+ }
+
+ if (out.audioBitrate != -1) {
+ cmd.add(Argument.BITRATE_AUDIO);
+ cmd.add(out.audioBitrate + "k");
+ }
+ cmd.add("-y");
+
+ cmd.add("-cutoff");
+ cmd.add("15000");
+
+ if (out.width > 0) {
+ cmd.add(Argument.SIZE);
+ cmd.add(out.width + "x" + out.height);
+
+ }
+
+ if (out.format != null) {
+ cmd.add("-f");
+ cmd.add(out.format);
+ }
+
+ File fileOut = new File(out.path);
+ cmd.add(fileOut.getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+
+ return out;
+
+ }
+
+
+ /*
+ * ffmpeg -y -loop 0 -f image2 -r 0.5 -i image-%03d.jpg -s:v 1280x720 -b:v 1M \
+ -i soundtrack.mp3 -t 01:05:00 -map 0:0 -map 1:0 out.avi
+
+ -loop_input – loops the images. Disable this if you want to stop the encoding when all images are used or the soundtrack is finished.
+
+-r 0.5 – sets the framerate to 0.5, which means that each image will be shown for 2 seconds. Just take the inverse, for example if you want each image to last for 3 seconds, set it to 0.33.
+
+-i image-%03d.jpg – use these input files. %03d means that there will be three digit numbers for the images.
+
+-s 1280x720 – sets the output frame size.
+
+-b 1M – sets the bitrate. You want 500MB for one hour, which equals to 4000MBit in 3600 seconds, thus a bitrate of approximately 1MBit/s should be sufficient.
+
+-i soundtrack.mp3 – use this soundtrack file. Can be any format.
+
+-t 01:05:00 – set the output length in hh:mm:ss format.
+
+out.avi – create this output file. Change it as you like, for example using another container like MP4.
+ */
+ public Clip combineAudioAndVideo(Clip videoIn, Clip audioIn, Clip out, ShellCallback sc) throws Exception {
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ cmd.add("-i");
+ cmd.add(new File(videoIn.path).getCanonicalPath());
+
+ cmd.add("-i");
+ cmd.add(new File(audioIn.path).getCanonicalPath());
+
+
+ cmd.add("-strict");
+ cmd.add("-2");//experimental
+
+ cmd.add(Argument.AUDIOCODEC);
+ if (out.audioCodec != null)
+ cmd.add(out.audioCodec);
+ else {
+ cmd.add("copy");
+
+ }
+
+ cmd.add(Argument.VIDEOCODEC);
+ if (out.videoCodec != null)
+ cmd.add(out.videoCodec);
+ else {
+ cmd.add("copy");
+ }
+
+ if (out.videoBitrate != -1) {
+ cmd.add(Argument.BITRATE_VIDEO);
+ cmd.add(out.videoBitrate + "k");
+ }
+
+ if (out.videoFps != null) {
+ cmd.add(Argument.FRAMERATE);
+ cmd.add(out.videoFps);
+ }
+
+ if (out.audioBitrate != -1) {
+ cmd.add(Argument.BITRATE_AUDIO);
+ cmd.add(out.audioBitrate + "k");
+ }
+ cmd.add("-y");
+
+ cmd.add("-cutoff");
+ cmd.add("15000");
+
+ if (out.width > 0) {
+ cmd.add(Argument.SIZE);
+ cmd.add(out.width + "x" + out.height);
+
+ }
+
+ if (out.format != null) {
+ cmd.add("-f");
+ cmd.add(out.format);
+ }
+
+ File fileOut = new File(out.path);
+ cmd.add(fileOut.getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+
+ return out;
+
+ }
+
+ public Clip mergMp4(String configPath, String outPath, ShellCallback sc) throws Exception {
+ Clip result = new Clip();
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+// cmd.add("-loop");
+// cmd.add("1");
+
+ cmd.add("-f");
+
+ cmd.add("concat");
+
+ cmd.add("-i");
+ cmd.add(new File(configPath).getCanonicalPath());
+
+ cmd.add("-c");
+ cmd.add("copy");
+
+
+ result.path = outPath;
+ result.mimeType = "video/mp4";
+
+ cmd.add(new File(result.path).getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+ return result;
+ }
+
+ public Clip filterMp4Complex(Clip mediaIn, Clip pngIn, String outPath, ShellCallback sc) throws Exception {
+ Clip result = new Clip();
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+// cmd.add("-loop");
+// cmd.add("1");
+
+ cmd.add("-i");
+ cmd.add(new File(mediaIn.path).getCanonicalPath());
+
+ cmd.add("-i");
+ cmd.add(new File(pngIn.path).getCanonicalPath());
+
+ cmd.add("-filter_complex");
+ String filter = "geq=lum='if(lte(T,0.6), 255*T*(1/0.6),255)',format=gray[grad];"
+ + "[0:v]boxblur=8[blur];"
+ + "[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];"
+ + "[lay]drawtext=fontfile=/system/fonts/DroidSans.ttf:text=ABCDEF:fontsize=23:fontcolor=white@1.0:x=main_w/2:y=main_h/2[text];"
+ + "[text][grad]alphamerge[alpha];"
+ + "[0:v][alpha]overlay";
+ cmd.add(filter);
+
+ result.path = outPath;
+ result.videoBitrate = mediaIn.videoBitrate;
+ result.videoFps = mediaIn.videoFps;
+ result.mimeType = "video/mp4";
+
+ cmd.add(new File(result.path).getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+ return result;
+ }
+
+ public Clip convertImageToMP4(Clip mediaIn, Clip mediaLogo, int duration, String outPath, ShellCallback sc) throws Exception {
+ Clip result = new Clip();
+ ArrayList cmd = new ArrayList();
+
+ // ffmpeg -loop 1 -i IMG_1338.jpg -t 10 -r 29.97 -s 640x480 -qscale 5 test.mp4
+
+ cmd = new ArrayList();
+
+ //convert images to MP4
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ cmd.add("-loop");
+ cmd.add("1");
+
+ cmd.add("-i");
+ cmd.add(new File(mediaIn.path).getCanonicalPath());
+
+ cmd.add("-i");
+ cmd.add(new File(mediaLogo.path).getCanonicalPath());
+
+ cmd.add(Argument.FRAMERATE);
+ cmd.add("24");
+
+ cmd.add(Argument.DURATION);
+ cmd.add(String.valueOf(duration));
+
+ cmd.add("-preset");
+ cmd.add("medium");
+
+ cmd.add("-c:v");
+ cmd.add("libx264");
+
+ cmd.add("-pix_fmt");
+ cmd.add("yuv420p");
+
+ cmd.add("-map");
+ cmd.add("0:0");
+
+ cmd.add("-filter_complex");
+ String filter = "geq=lum='if(lte(T,0.6), 255*T*(1/0.6),255)',format=gray[grad];"
+ + "[0:v]boxblur=8[blur];"
+ + "[blur]setpts=PTS-STARTPTS[setpts]"
+ + "[setpts][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];"
+ + "[lay]drawtext=fontfile=/system/fonts/DroidSans.ttf:text=ABCDEF:fontsize=23:fontcolor=white@1.0:x=main_w/2:y=main_h/2[text];"
+ + "[text][grad]alphamerge[alpha];"
+ + "[0:v][alpha]overlay";
+ cmd.add(filter);
+
+ if (mediaIn.width != -1) {
+ cmd.add(Argument.SIZE);
+ cmd.add(mediaIn.width + "x" + mediaIn.height);
+ // cmd.add("-vf");
+ // cmd.add("\"scale=-1:" + mediaIn.width + "\"");
+ }
+
+ if (mediaIn.videoBitrate != -1) {
+ cmd.add(Argument.BITRATE_VIDEO);
+ cmd.add(mediaIn.videoBitrate + "k");
+ }
+
+ cmd.add("-profile:v");
+ cmd.add("high");
+
+ cmd.add("-g");
+ cmd.add("12");
+
+
+// cmd.add("-map 0:0 -map 1:0");
+ // -ar 44100 -acodec pcm_s16le -f s16le -ac 2 -i /dev/zero -acodec aac -ab 128k \
+ // -map 0:0 -map 1:0
+
+ result.path = outPath;
+ result.videoBitrate = mediaIn.videoBitrate;
+ result.videoFps = mediaIn.videoFps;
+ result.mimeType = "video/mp4";
+
+ cmd.add(new File(result.path).getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+ return result;
+ }
+
+ public Clip makeLastFrameFilter(float startTime, Clip mediaIn, Clip mediaLogo, int duration, String outPath, ShellCallback sc) throws Exception {
+ Clip result = new Clip();
+ ArrayList cmd = new ArrayList();
+
+ // ffmpeg -loop 1 -i IMG_1338.jpg -t 10 -r 29.97 -s 640x480 -qscale 5 test.mp4
+
+ cmd = new ArrayList();
+
+ //convert images to MP4
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+//
+// cmd.add("-loop");
+// cmd.add("1");
+
+ cmd.add(Argument.STARTTIME);
+ cmd.add(String.valueOf(startTime));
+
+ cmd.add(Argument.DURATION);
+ cmd.add(String.valueOf(duration));
+
+ cmd.add("-i");
+ cmd.add(new File(mediaIn.path).getCanonicalPath());
+
+ cmd.add("-i");
+ cmd.add(new File(mediaLogo.path).getCanonicalPath());
+
+ cmd.add(Argument.FRAMERATE);
+ cmd.add("24");
+
+ cmd.add("-preset");
+ cmd.add("medium");
+
+
+// cmd.add(Argument.AUDIOCODEC);
+// if (mediaIn.audioCodec != null)
+// cmd.add(mediaIn.audioCodec);
+// else {
+// cmd.add("copy");
+//
+// }
+//
+// cmd.add(Argument.VIDEOCODEC);
+// if (mediaIn.videoCodec != null)
+// cmd.add(mediaIn.videoCodec);
+// else {
+// cmd.add("copy");
+// }
+//
+// cmd.add("-t");
+// cmd.add(duration + "");
+
+// cmd.add("-qscale");
+// cmd.add("5"); //a good value 1 is best 30 is worst
+
+ cmd.add("-c:v");
+ cmd.add("libx264");
+
+ cmd.add("-pix_fmt");
+ cmd.add("yuv420p");
+
+ cmd.add("-map");
+ cmd.add("0:0");
+
+ cmd.add("-filter_complex");
+ String filter = "geq=lum='if(lte(T,0.6), 255*T*(1/0.6),255)',format=gray[grad];"
+ + "[0:v]boxblur=8[blur];"
+ + "[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];"
+ + "[lay]drawtext=fontfile=/system/fonts/DroidSans.ttf:text=猪八戒香肠嘴™配音作品:fontsize=23:fontcolor=white@1.0:x=main_w/2:y=main_h/2[text];"
+ + "[text][grad]alphamerge[alpha];"
+ + "[0:v][alpha]overlay";
+ cmd.add(filter);
+
+ if (mediaIn.width != -1) {
+ cmd.add(Argument.SIZE);
+ cmd.add(mediaIn.width + "x" + mediaIn.height);
+ // cmd.add("-vf");
+ // cmd.add("\"scale=-1:" + mediaIn.width + "\"");
+ }
+
+ if (mediaIn.videoBitrate != -1) {
+ cmd.add(Argument.BITRATE_VIDEO);
+ cmd.add(mediaIn.videoBitrate + "k");
+ }
+
+ cmd.add("-profile:v");
+ cmd.add("high");
+
+ cmd.add("-g");
+ cmd.add("12");
+
+
+// cmd.add("-map 0:0 -map 1:0");
+ // -ar 44100 -acodec pcm_s16le -f s16le -ac 2 -i /dev/zero -acodec aac -ab 128k \
+ // -map 0:0 -map 1:0
+
+ result.path = outPath;
+ result.videoBitrate = mediaIn.videoBitrate;
+ result.videoFps = mediaIn.videoFps;
+ result.mimeType = "video/mp4";
+
+ cmd.add(new File(result.path).getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+ return result;
+ }
+
+
+ public Clip makeLastFrameFilter2(String lastFrame, float startTime, Clip mediaIn, Clip mediaLogo, float duration, String outPath, ShellCallback sc) throws Exception {
+ Clip result = new Clip();
+ ArrayList cmd = new ArrayList();
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+//
+// cmd.add(Argument.THREAD);
+// cmd.add("4");
+
+ cmd.add(Argument.STARTTIME);
+ cmd.add(String.valueOf(startTime));
+
+ cmd.add(Argument.DURATION);
+ cmd.add(String.valueOf(duration));
+
+ cmd.add(Argument.FILE_INPUT);
+ cmd.add(new File(mediaIn.path).getCanonicalPath());
+
+ cmd.add(Argument.FILE_INPUT);
+ cmd.add(new File(mediaLogo.path).getCanonicalPath());
+
+ cmd.add(Argument.FILE_INPUT);
+ cmd.add(new File(lastFrame).getCanonicalPath());
+
+ cmd.add(Argument.FRAMERATE);
+ cmd.add(String.valueOf(mediaIn.frameRate));
+
+ cmd.add(Argument.PRESET);
+ cmd.add("medium");
+
+ cmd.add(Argument.CODEC_VIDEO);
+ cmd.add("libx264");
+
+ cmd.add(Argument.PIX_FORMAT);
+ cmd.add("yuv420p");
+
+ cmd.add(Argument.MAP);
+ cmd.add("0:0");
+
+ cmd.add(Argument.FILTER_COMPLEX);
+ String filter = "geq=lum='if(lte(T,0.5), 255*T*(1/0.5),255)',format=gray[grad];"
+ + "[0:v][2:v]overlay=0:0 [lay1];"
+ + "[lay1]boxblur=8[blur];"
+ + "[blur][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2 [lay];"
+// + "[lay]drawtext=fontfile="+ Config.SYSTEM_DEFAULT_FONT_PATH+":text=主演:猪八戒香肠嘴™:fontsize=23:fontcolor=white@1.0:x=main_w/2-20:y=main_h/2+20[text];"
+ + "[lay][grad]alphamerge[alpha];"
+ + "[0:v][2:v]overlay=0:0[lay1];"
+ + "[lay1][alpha]overlay";
+
+ cmd.add(filter);
+
+ if (mediaIn.width != -1) {
+ cmd.add(Argument.SIZE);
+ cmd.add(mediaIn.width + "x" + mediaIn.height);
+ }
+
+ if (mediaIn.videoBitrate != -1) {
+ cmd.add(Argument.BITRATE_VIDEO);
+ cmd.add(mediaIn.videoBitrate + "k");
+ }
+
+ cmd.add(Argument.PROFILE);
+ cmd.add("high");
+
+ cmd.add("-g");
+ cmd.add("12");
+
+
+// cmd.add("-map 0:0 -map 1:0");
+ // -ar 44100 -acodec pcm_s16le -f s16le -ac 2 -i /dev/zero -acodec aac -ab 128k \
+ // -map 0:0 -map 1:0
+
+ result.path = outPath;
+ result.videoBitrate = mediaIn.videoBitrate;
+ result.videoFps = mediaIn.videoFps;
+ result.mimeType = "video/mp4";
+
+ cmd.add(new File(result.path).getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+ return result;
+ }
+ //based on this gist: https://gist.github.com/3757344
+ //ffmpeg -i input1.mp4 -vcodec copy -vbsf h264_mp4toannexb -acodec copy part1.ts
+ //ffmpeg -i input2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.ts
+
+ public Clip convertToMP4Stream(Clip mediaIn, String startTime, double duration, String outPath, ShellCallback sc) throws Exception {
+ ArrayList cmd = new ArrayList();
+
+ Clip mediaOut = new Clip();
+ mediaOut.path = outPath;
+
+ String mediaPath = new File(mediaIn.path).getCanonicalPath();
+
+ cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ if (startTime != null) {
+ cmd.add(Argument.STARTTIME);
+ cmd.add(startTime);
+ }
+
+ if (duration != -1) {
+ cmd.add(Argument.DURATION);
+
+ double dValue = mediaIn.duration;
+ int hours = (int) (dValue / 3600f);
+ dValue -= (hours * 3600);
+
+ cmd.add("0");
+ cmd.add(String.format(Locale.US, "%s", hours));
+ cmd.add(":");
+
+ int min = (int) (dValue / 60f);
+ dValue -= (min * 60);
+
+ cmd.add("0");
+ cmd.add(String.format(Locale.US, "%s", min));
+ cmd.add(":");
+
+ cmd.add(String.format(Locale.US, "%f", dValue));
+
+ //cmd.add("00:00:" + String.format(Locale.US,"%f",mediaIn.duration));
+
+
+ }
+
+ cmd.add("-i");
+ cmd.add(mediaPath);
+
+ cmd.add("-f");
+ cmd.add("mpegts");
+
+ cmd.add("-c");
+ cmd.add("copy");
+
+ cmd.add("-an");
+
+ //cmd.add(Argument.VIDEOBITSTREAMFILTER);
+ cmd.add("-bsf:v");
+ cmd.add("h264_mp4toannexb");
+
+ File fileOut = new File(mediaOut.path);
+ mediaOut.path = fileOut.getCanonicalPath();
+
+ cmd.add(mediaOut.path);
+
+ execFFMPEG(cmd, sc);
+
+ return mediaOut;
+ }
+
+
+ public Clip convertToWaveAudio(Clip mediaIn, String outPath, int sampleRate, int channels, ShellCallback sc) throws Exception {
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ if (mediaIn.startTime != null) {
+ cmd.add("-ss");
+ cmd.add(mediaIn.startTime);
+ }
+
+ if (mediaIn.duration != -1) {
+ cmd.add("-t");
+ cmd.add(String.format(Locale.US, "%f", mediaIn.duration));
+ }
+
+ cmd.add("-i");
+ cmd.add(new File(mediaIn.path).getCanonicalPath());
+
+
+ cmd.add("-ar");
+ cmd.add(sampleRate + "");
+
+ cmd.add("-ac");
+ cmd.add(channels + "");
+
+ cmd.add("-vn");
+
+ Clip mediaOut = new Clip();
+
+ File fileOut = new File(outPath);
+ mediaOut.path = fileOut.getCanonicalPath();
+
+ cmd.add(mediaOut.path);
+
+ execFFMPEG(cmd, sc);
+
+ return mediaOut;
+ }
+
+ public Clip convertTo3GPAudio(Clip mediaIn, Clip mediaOut, ShellCallback sc) throws Exception {
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+ cmd.add("-i");
+ cmd.add(new File(mediaIn.path).getCanonicalPath());
+
+ if (mediaIn.startTime != null) {
+ cmd.add("-ss");
+ cmd.add(mediaIn.startTime);
+ }
+
+ if (mediaIn.duration != -1) {
+ cmd.add("-t");
+ cmd.add(String.format(Locale.US, "%f", mediaIn.duration));
+
+ }
+
+ cmd.add("-vn");
+
+ if (mediaOut.audioCodec != null) {
+ cmd.add("-acodec");
+ cmd.add(mediaOut.audioCodec);
+ }
+
+ if (mediaOut.audioBitrate != -1) {
+ cmd.add("-ab");
+ cmd.add(mediaOut.audioBitrate + "k");
+ }
+
+ cmd.add("-strict");
+ cmd.add("-2");
+
+ File fileOut = new File(mediaOut.path);
+
+ cmd.add(fileOut.getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+ return mediaOut;
+ }
+
+ public Clip convert(Clip mediaIn, String outPath, ShellCallback sc) throws Exception {
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+ cmd.add("-i");
+ cmd.add(new File(mediaIn.path).getCanonicalPath());
+
+ if (mediaIn.startTime != null) {
+ cmd.add("-ss");
+ cmd.add(mediaIn.startTime);
+ }
+
+ if (mediaIn.duration != -1) {
+ cmd.add("-t");
+ cmd.add(String.format(Locale.US, "%f", mediaIn.duration));
+
+ }
+
+
+ Clip mediaOut = new Clip();
+
+
+ File fileOut = new File(outPath);
+
+ mediaOut.path = fileOut.getCanonicalPath();
+
+ cmd.add(mediaOut.path);
+
+ execFFMPEG(cmd, sc);
+
+ return mediaOut;
+ }
+
+ public Clip convertToMPEG(Clip mediaIn, String outPath, ShellCallback sc) throws Exception {
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+ cmd.add("-i");
+ cmd.add(new File(mediaIn.path).getCanonicalPath());
+
+ if (mediaIn.startTime != null) {
+ cmd.add("-ss");
+ cmd.add(mediaIn.startTime);
+ }
+
+ if (mediaIn.duration != -1) {
+ cmd.add("-t");
+ cmd.add(String.format(Locale.US, "%f", mediaIn.duration));
+
+ }
+
+
+ //cmd.add("-strict");
+ //cmd.add("experimental");
+
+ //everything to mpeg
+ cmd.add("-f");
+ cmd.add("mpeg");
+
+ Clip mediaOut = mediaIn.clone();
+
+ File fileOut = new File(outPath);
+
+ mediaOut.path = fileOut.getCanonicalPath();
+
+ cmd.add(mediaOut.path);
+
+ execFFMPEG(cmd, sc);
+
+ return mediaOut;
+ }
+
+ public void concatAndTrimFilesMPEG(ArrayList videos, Clip out, boolean preConvert, ShellCallback sc) throws Exception {
+
+ int idx = 0;
+
+ if (preConvert) {
+ for (Clip mdesc : videos) {
+ if (mdesc.path == null)
+ continue;
+
+ //extract MPG video
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+ cmd.add("-i");
+ cmd.add(mdesc.path);
+
+ if (mdesc.startTime != null) {
+ cmd.add("-ss");
+ cmd.add(mdesc.startTime);
+ }
+
+ if (mdesc.duration != -1) {
+ cmd.add("-t");
+ cmd.add(String.format(Locale.US, "%f", mdesc.duration));
+
+ }
+
+ /*
+ cmd.add ("-acodec");
+ cmd.add("pcm_s16le");
+
+ cmd.add ("-vcodec");
+ cmd.add("mpeg2video");
+ */
+
+ if (out.audioCodec == null)
+ cmd.add("-an"); //no audio
+
+ //cmd.add("-strict");
+ //cmd.add("experimental");
+
+ //everything to mpeg
+ cmd.add("-f");
+ cmd.add("mpeg");
+ cmd.add(out.path + '.' + idx + ".mpg");
+
+ execFFMPEG(cmd, sc);
+
+ idx++;
+ }
+ }
+
+ StringBuffer cmdRun = new StringBuffer();
+
+ cmdRun.append(mCmdCat);
+
+ idx = 0;
+
+ for (Clip vdesc : videos) {
+ if (vdesc.path == null)
+ continue;
+
+ if (preConvert)
+ cmdRun.append(out.path).append('.').append(idx++).append(".mpg").append(' '); //leave a space at the end!
+ else
+ cmdRun.append(vdesc.path).append(' ');
+ }
+
+ String mCatPath = out.path + ".full.mpg";
+
+ cmdRun.append("> ");
+ cmdRun.append(mCatPath);
+
+ String[] cmds = {"sh", "-c", cmdRun.toString()};
+ Runtime.getRuntime().exec(cmds).waitFor();
+
+
+ Clip mInCat = new Clip();
+ mInCat.path = mCatPath;
+
+ processVideo(mInCat, out, false, sc);
+
+ out.path = mCatPath;
+ }
+
+ public void extractAudio(Clip mdesc, String audioFormat, File audioOutPath, ShellCallback sc) throws IOException, InterruptedException {
+
+ //no just extract the audio
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+ cmd.add("-i");
+ cmd.add(new File(mdesc.path).getCanonicalPath());
+
+ cmd.add("-vn");
+
+ if (mdesc.startTime != null) {
+ cmd.add("-ss");
+ cmd.add(mdesc.startTime);
+ }
+
+ if (mdesc.duration != -1) {
+ cmd.add("-t");
+ cmd.add(String.format(Locale.US, "%f", mdesc.duration));
+
+ }
+
+ cmd.add("-f");
+ cmd.add(audioFormat); //wav
+
+ //everything to WAV!
+ cmd.add(audioOutPath.getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+ }
+
+ private class FileMover {
+
+ InputStream inputStream;
+ File destination;
+
+ public FileMover(InputStream _inputStream, File _destination) {
+ inputStream = _inputStream;
+ destination = _destination;
+ }
+
+ public void moveIt() throws IOException {
+
+ OutputStream destinationOut = new BufferedOutputStream(new FileOutputStream(destination));
+
+ int numRead;
+ byte[] buf = new byte[1024];
+ while ((numRead = inputStream.read(buf)) >= 0) {
+ destinationOut.write(buf, 0, numRead);
+ }
+
+ destinationOut.flush();
+ destinationOut.close();
+ }
+ }
+
+ public int killVideoProcessor(boolean asRoot, boolean waitFor) throws IOException {
+ int killDelayMs = 300;
+
+ int result = -1;
+
+ int procId = -1;
+
+ while ((procId = ShellUtils.findProcessId(mFfmpegBin)) != -1) {
+
+ // Log.d(TAG, "Found PID=" + procId + " - killing now...");
+
+ String[] cmd = {ShellUtils.SHELL_CMD_KILL + ' ' + procId + ""};
+
+ try {
+ result = ShellUtils.doShellCommand(cmd, new ShellCallback() {
+
+ @Override
+ public void shellOut(String msg) {
+
+ }
+
+ @Override
+ public void processComplete(int exitValue) {
+
+
+ }
+
+ }, asRoot, waitFor);
+ Thread.sleep(killDelayMs);
+ } catch (Exception e) {
+ }
+ }
+
+ return result;
+ }
+
+
+ public Clip trim(Clip mediaIn, boolean withSound, String outPath, ShellCallback sc) throws Exception {
+ ArrayList cmd = new ArrayList();
+
+ Clip mediaOut = new Clip();
+
+ String mediaPath = mediaIn.path;
+
+ cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+
+ if (mediaIn.startTime != null) {
+ cmd.add(Argument.STARTTIME);
+ cmd.add(mediaIn.startTime);
+ }
+
+ if (mediaIn.duration != -1) {
+ cmd.add("-t");
+ cmd.add(String.format(Locale.US, "%f", mediaIn.duration));
+
+ }
+
+ cmd.add("-i");
+ cmd.add(mediaPath);
+
+ if (!withSound)
+ cmd.add("-an");
+
+ cmd.add("-strict");
+ cmd.add("-2");//experimental
+
+ mediaOut.path = outPath;
+
+ cmd.add(mediaOut.path);
+
+ execFFMPEG(cmd, sc);
+
+ return mediaOut;
+ }
+
+ public void concatAndTrimFilesMP4Stream(ArrayList videos, Clip out, boolean preconvertClipsToMP4, boolean useCatCmd, ShellCallback sc) throws Exception {
+
+
+ File fileExportOut = new File(out.path);
+
+ StringBuffer sbCat = new StringBuffer();
+
+ int tmpIdx = 0;
+
+
+ for (Clip vdesc : videos) {
+
+ Clip mdOut = null;
+
+ if (preconvertClipsToMP4) {
+ File fileOut = new File(mFileTemp, tmpIdx + "-trim.mp4");
+ if (fileOut.exists())
+ fileOut.delete();
+
+ boolean withSound = false;
+
+ mdOut = trim(vdesc, withSound, fileOut.getCanonicalPath(), sc);
+
+ fileOut = new File(mFileTemp, tmpIdx + ".ts");
+ if (fileOut.exists())
+ fileOut.delete();
+
+ mdOut = convertToMP4Stream(mdOut, null, -1, fileOut.getCanonicalPath(), sc);
+ } else {
+ File fileOut = new File(mFileTemp, tmpIdx + ".ts");
+ if (fileOut.exists())
+ fileOut.delete();
+ mdOut = convertToMP4Stream(vdesc, vdesc.startTime, vdesc.duration, fileOut.getCanonicalPath(), sc);
+ }
+
+ if (mdOut != null) {
+ if (sbCat.length() > 0)
+ sbCat.append("|");
+
+ sbCat.append(new File(mdOut.path).getCanonicalPath());
+ tmpIdx++;
+ }
+ }
+
+ File fileExportOutTs = new File(fileExportOut.getCanonicalPath() + ".ts");
+
+ if (useCatCmd) {
+
+ //cat 0.ts 1.ts > foo.ts
+ StringBuffer cmdBuff = new StringBuffer();
+
+ cmdBuff.append(mCmdCat);
+ cmdBuff.append(" ");
+
+ StringTokenizer st = new StringTokenizer(sbCat.toString(), "|");
+
+ while (st.hasMoreTokens())
+ cmdBuff.append(st.nextToken()).append(" ");
+
+ cmdBuff.append("> ");
+
+ cmdBuff.append(fileExportOut.getCanonicalPath() + ".ts");
+
+ Runtime.getRuntime().exec(cmdBuff.toString());
+
+ ArrayList cmd = new ArrayList();
+
+ cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+ cmd.add("-i");
+
+ cmd.add(fileExportOut.getCanonicalPath() + ".ts");
+
+ cmd.add("-c");
+ cmd.add("copy");
+
+ cmd.add("-an");
+
+ cmd.add(fileExportOut.getCanonicalPath());
+
+ execFFMPEG(cmd, sc, null);
+
+
+ } else {
+
+ //ffmpeg -i "concat:intermediate1.ts|intermediate2.ts" -c copy -bsf:a aac_adtstoasc output.mp4
+ ArrayList cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+ cmd.add("-i");
+ cmd.add("concat:" + sbCat.toString());
+
+ cmd.add("-c");
+ cmd.add("copy");
+
+ cmd.add("-an");
+
+ cmd.add(fileExportOut.getCanonicalPath());
+
+ execFFMPEG(cmd, sc);
+
+ }
+
+ if ((!fileExportOut.exists()) || fileExportOut.length() == 0) {
+ throw new Exception("There was a problem rendering the video: " + fileExportOut.getCanonicalPath());
+ }
+
+
+ }
+
+ public Clip getInfo(Clip in) throws IOException, InterruptedException {
+ ArrayList cmd = new ArrayList();
+
+ cmd = new ArrayList();
+
+ cmd.add(mFfmpegBin);
+ cmd.add("-y");
+ cmd.add("-i");
+
+ cmd.add(new File(in.path).getCanonicalPath());
+
+ InfoParser ip = new InfoParser(in);
+ execFFMPEG(cmd, ip, null);
+
+ try {
+ Thread.sleep(200);
+ } catch (Exception e) {
+ }
+
+
+ return in;
+
+ }
+
+ private class InfoParser implements ShellCallback {
+
+ private Clip mMedia;
+ private int retValue;
+
+ public InfoParser(Clip media) {
+ mMedia = media;
+ }
+
+ @Override
+ public void shellOut(String shellLine) {
+ if (shellLine.contains("Duration:")) {
+
+// Duration: 00:01:01.75, start: 0.000000, bitrate: 8184 kb/s
+
+ String[] timecode = shellLine.split(",")[0].split(":");
+
+
+ double duration = 0;
+
+ duration = Double.parseDouble(timecode[1].trim()) * 60 * 60; //hours
+ duration += Double.parseDouble(timecode[2].trim()) * 60; //minutes
+ duration += Double.parseDouble(timecode[3].trim()); //seconds
+
+ mMedia.duration = duration;
+
+
+ }
+
+ // Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080, 16939 kb/s, 30.02 fps, 30 tbr, 90k tbn, 180k tbc
+ else if (shellLine.contains(": Video:")) {
+ String[] line = shellLine.split(":");
+ String[] videoInfo = line[3].split(",");
+
+ mMedia.videoCodec = videoInfo[0];
+ }
+
+ //Stream #0:1(eng): Audio: aac (mp4a / 0x6134706D), 48000 Hz, stereo, s16, 121 kb/s
+ else if (shellLine.contains(": Audio:")) {
+ String[] line = shellLine.split(":");
+ String[] audioInfo = line[3].split(",");
+
+ mMedia.audioCodec = audioInfo[0];
+
+ }
+
+
+ //
+ //Stream #0.0(und): Video: h264 (Baseline), yuv420p, 1280x720, 8052 kb/s, 29.97 fps, 90k tbr, 90k tbn, 180k tbc
+ //Stream #0.1(und): Audio: mp2, 22050 Hz, 2 channels, s16, 127 kb/s
+
+ }
+
+ @Override
+ public void processComplete(int exitValue) {
+ retValue = exitValue;
+
+ }
+ }
+
+ private class StreamGobbler extends Thread {
+ InputStream is;
+ String type;
+ ShellCallback sc;
+
+ StreamGobbler(InputStream is, String type, ShellCallback sc) {
+ this.is = is;
+ this.type = type;
+ this.sc = sc;
+ }
+
+ public void run() {
+ try {
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader br = new BufferedReader(isr);
+ String line = null;
+ while ((line = br.readLine()) != null)
+ if (sc != null)
+ sc.shellOut(line);
+
+ } catch (IOException ioe) {
+ // Log.e(TAG,"error reading shell slog",ioe);
+ ioe.printStackTrace();
+ }
+ }
+ }
+
+ public static Bitmap getVideoFrame(String videoPath, long frameTime) throws Exception {
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+
+ try {
+ retriever.setDataSource(videoPath);
+ return retriever.getFrameAtTime(frameTime, MediaMetadataRetriever.OPTION_CLOSEST);
+
+ } finally {
+ try {
+ retriever.release();
+ } catch (RuntimeException ex) {
+ }
+ }
+ }
+}
+
+/*
+ * Main options:
+-L show license
+-h show help
+-? show help
+-help show help
+--help show help
+-version show version
+-formats show available formats
+-codecs show available codecs
+-bsfs show available bit stream filters
+-protocols show available protocols
+-filters show available filters
+-pix_fmts show available pixel formats
+-sample_fmts show available audio sample formats
+-loglevel loglevel set libav* logging level
+-v loglevel set libav* logging level
+-debug flags set debug flags
+-report generate a report
+-f fmt force format
+-i filename input file name
+-y overwrite output files
+-n do not overwrite output files
+-c codec codec name
+-codec codec codec name
+-pre preset preset name
+-t duration record or transcode "duration" seconds of audio/video
+-fs limit_size set the limit file size in bytes
+-ss time_off set the start time offset
+-itsoffset time_off set the input ts offset
+-itsscale scale set the input ts scale
+-timestamp time set the recording timestamp ('now' to set the current time)
+-metadata string=string add metadata
+-dframes number set the number of data frames to record
+-timelimit limit set max runtime in seconds
+-target type specify target file type ("vcd", "svcd", "dvd", "dv", "dv50", "pal-vcd", "ntsc-svcd", ...)
+-xerror exit on error
+-frames number set the number of frames to record
+-tag fourcc/tag force codec tag/fourcc
+-filter filter_list set stream filterchain
+-stats print progress report during encoding
+-attach filename add an attachment to the output file
+-dump_attachment filename extract an attachment into a file
+-bsf bitstream_filters A comma-separated list of bitstream filters
+-dcodec codec force data codec ('copy' to copy stream)
+
+Advanced options:
+-map file.stream[:syncfile.syncstream] set input stream mapping
+-map_channel file.stream.channel[:syncfile.syncstream] map an audio channel from one stream to another
+-map_meta_data outfile[,metadata]:infile[,metadata] DEPRECATED set meta data information of outfile from infile
+-map_metadata outfile[,metadata]:infile[,metadata] set metadata information of outfile from infile
+-map_chapters input_file_index set chapters mapping
+-benchmark add timings for benchmarking
+-dump dump each input packet
+-hex when dumping packets, also dump the payload
+-re read input at native frame rate
+-loop_input deprecated, use -loop
+-loop_output deprecated, use -loop
+-vsync video sync method
+-async audio sync method
+-adrift_threshold threshold audio drift threshold
+-copyts copy timestamps
+-copytb source copy input stream time base when stream copying
+-shortest finish encoding within shortest input
+-dts_delta_threshold threshold timestamp discontinuity delta threshold
+-copyinkf copy initial non-keyframes
+-q q use fixed quality scale (VBR)
+-qscale q use fixed quality scale (VBR)
+-streamid streamIndex:value set the value of an outfile streamid
+-muxdelay seconds set the maximum demux-decode delay
+-muxpreload seconds set the initial demux-decode delay
+-fpre filename set options from indicated preset file
+
+Video options:
+-vframes number set the number of video frames to record
+-r rate set frame rate (Hz value, fraction or abbreviation)
+-s size set frame size (WxH or abbreviation)
+-aspect aspect set aspect ratio (4:3, 16:9 or 1.3333, 1.7777)
+-bits_per_raw_sample number set the number of bits per raw sample
+-croptop size Removed, use the crop filter instead
+-cropbottom size Removed, use the crop filter instead
+-cropleft size Removed, use the crop filter instead
+-cropright size Removed, use the crop filter instead
+-padtop size Removed, use the pad filter instead
+-padbottom size Removed, use the pad filter instead
+-padleft size Removed, use the pad filter instead
+-padright size Removed, use the pad filter instead
+-padcolor color Removed, use the pad filter instead
+-vn disable video
+-vcodec codec force video codec ('copy' to copy stream)
+-sameq use same quantizer as source (implies VBR)
+-same_quant use same quantizer as source (implies VBR)
+-pass n select the pass number (1 or 2)
+-passlogfile prefix select two pass log file name prefix
+-vf filter list video filters
+-b bitrate video bitrate (please use -b:v)
+-dn disable data
+
+Advanced Video options:
+-pix_fmt format set pixel format
+-intra use only intra frames
+-vdt n discard threshold
+-rc_override override rate control override for specific intervals
+-deinterlace deinterlace pictures
+-psnr calculate PSNR of compressed frames
+-vstats dump video coding statistics to file
+-vstats_file file dump video coding statistics to file
+-intra_matrix matrix specify intra matrix coeffs
+-inter_matrix matrix specify inter matrix coeffs
+-top top=1/bottom=0/auto=-1 field first
+-dc precision intra_dc_precision
+-vtag fourcc/tag force video tag/fourcc
+-qphist show QP histogram
+-force_fps force the selected framerate, disable the best supported framerate selection
+-force_key_frames timestamps force key frames at specified timestamps
+-vbsf video bitstream_filters deprecated
+-vpre preset set the video options to the indicated preset
+
+Audio options:
+-aframes number set the number of audio frames to record
+-aq quality set audio quality (codec-specific)
+-ar rate set audio sampling rate (in Hz)
+-ac channels set number of audio channels
+-an disable audio
+-acodec codec force audio codec ('copy' to copy stream)
+-vol volume change audio volume (256=normal)
+-rmvol volume rematrix volume (as factor)
+
+ */
+
+/*
+ * //./ffmpeg -y -i test.mp4 -vframes 999999 -vf 'redact=blurbox.txt [out] [d], [d]nullsink' -acodec copy outputa.mp4
+
+ //ffmpeg -v 10 -y -i /sdcard/org.witness.sscvideoproto/videocapture1042744151.mp4 -vcodec libx264
+ //-b 3000k -s 720x480 -r 30 -acodec copy -f mp4 -vf 'redact=/data/data/org.witness.sscvideoproto/redact_unsort.txt'
+ ///sdcard/org.witness.sscvideoproto/new.mp4
+
+ //"-vf" , "redact=" + Environment.getExternalStorageDirectory().getPath() + "/" + PACKAGENAME + "/redact_unsort.txt",
+
+
+ // Need to make sure this will create a legitimate mp4 file
+ //"-acodec", "ac3", "-ac", "1", "-ar", "16000", "-ab", "32k",
+
+
+ String[] ffmpegCommand = {"/data/data/"+PACKAGENAME+"/ffmpeg", "-v", "10", "-y", "-i", recordingFile.getPath(),
+ "-vcodec", "libx264", "-b", "3000k", "-vpre", "baseline", "-s", "720x480", "-r", "30",
+ //"-vf", "drawbox=10:20:200:60:red@0.5",
+ "-vf" , "\"movie="+ overlayImage.getPath() +" [logo];[in][logo] overlay=0:0 [out]\"",
+ "-acodec", "copy",
+ "-f", "mp4", savePath.getPath()+"/output.mp4"};
+
+
+
+
+//ffmpeg -i source-video.avi -s 480x320 -vcodec mpeg4 -acodec aac -ac 1 -ar 16000 -r 13 -ab 32000 -aspect 3:2 output-video.mp4/
+
+
+ */
+
+
+/* concat doesn't seem to work
+cmd.add("-i");
+
+StringBuffer concat = new StringBuffer();
+
+for (int i = 0; i < videos.size(); i++)
+{
+ if (i > 0)
+ concat.append("|");
+
+ concat.append(out.path + '.' + i + ".wav");
+
+}
+
+cmd.add("concat:\"" + concat.toString() + "\"");
+*/
+
diff --git a/app/src/main/java/org/ffmpeg/android/ShellUtils.java b/app/src/main/java/org/ffmpeg/android/ShellUtils.java
new file mode 100644
index 0000000..c2ffebd
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/ShellUtils.java
@@ -0,0 +1,234 @@
+/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */
+/* See LICENSE for licensing information */
+package org.ffmpeg.android;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.StringTokenizer;
+
+
+import android.util.Log;
+
+public class ShellUtils {
+
+ //various console cmds
+ public final static String SHELL_CMD_CHMOD = "chmod";
+ public final static String SHELL_CMD_KILL = "kill -9";
+ public final static String SHELL_CMD_RM = "rm";
+ public final static String SHELL_CMD_PS = "ps";
+ public final static String SHELL_CMD_PIDOF = "pidof";
+
+ public final static String CHMOD_EXE_VALUE = "700";
+
+ public static boolean isRootPossible() {
+
+ StringBuilder log = new StringBuilder();
+
+ try {
+
+ // Check if Superuser.apk exists
+ File fileSU = new File("/system/app/Superuser.apk");
+ if (fileSU.exists())
+ return true;
+
+ fileSU = new File("/system/bin/su");
+ if (fileSU.exists())
+ return true;
+
+ //Check for 'su' binary
+ String[] cmd = {"which su"};
+ int exitCode = ShellUtils.doShellCommand(null, cmd, new ShellCallback() {
+
+ @Override
+ public void shellOut(String msg) {
+
+ //System.out.print(msg);
+
+ }
+
+ @Override
+ public void processComplete(int exitValue) {
+ // TODO Auto-generated method stub
+
+ }
+
+ }, false, true).exitValue();
+
+ if (exitCode == 0) {
+ logMessage("Can acquire root permissions");
+ return true;
+
+ }
+
+ } catch (IOException e) {
+ //this means that there is no root to be had (normally) so we won't log anything
+ logException("Error checking for root access", e);
+
+ } catch (Exception e) {
+ logException("Error checking for root access", e);
+ //this means that there is no root to be had (normally)
+ }
+
+ logMessage("Could not acquire root permissions");
+
+
+ return false;
+ }
+
+
+ public static int findProcessId(String command) {
+ int procId = -1;
+
+ try {
+ procId = findProcessIdWithPidOf(command);
+
+ if (procId == -1)
+ procId = findProcessIdWithPS(command);
+ } catch (Exception e) {
+ try {
+ procId = findProcessIdWithPS(command);
+ } catch (Exception e2) {
+ logException("Unable to get proc id for: " + command, e2);
+ }
+ }
+
+ return procId;
+ }
+
+ //use 'pidof' command
+ public static int findProcessIdWithPidOf(String command) throws Exception {
+
+ int procId = -1;
+
+ Runtime r = Runtime.getRuntime();
+
+ Process procPs = null;
+
+ String baseName = new File(command).getName();
+ //fix contributed my mikos on 2010.12.10
+ procPs = r.exec(new String[]{SHELL_CMD_PIDOF, baseName});
+ //procPs = r.exec(SHELL_CMD_PIDOF);
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
+ String line = null;
+
+ while ((line = reader.readLine()) != null) {
+
+ try {
+ //this line should just be the process id
+ procId = Integer.parseInt(line.trim());
+ break;
+ } catch (NumberFormatException e) {
+ logException("unable to parse process pid: " + line, e);
+ }
+ }
+
+
+ return procId;
+
+ }
+
+ //use 'ps' command
+ public static int findProcessIdWithPS(String command) throws Exception {
+
+ int procId = -1;
+
+ Runtime r = Runtime.getRuntime();
+
+ Process procPs = null;
+
+ procPs = r.exec(SHELL_CMD_PS);
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
+ String line = null;
+
+ while ((line = reader.readLine()) != null) {
+ if (line.indexOf(' ' + command) != -1) {
+
+ StringTokenizer st = new StringTokenizer(line, " ");
+ st.nextToken(); //proc owner
+
+ procId = Integer.parseInt(st.nextToken().trim());
+
+ break;
+ }
+ }
+
+
+ return procId;
+
+ }
+
+ public static int doShellCommand(String[] cmds, ShellCallback sc, boolean runAsRoot, boolean waitFor) throws Exception {
+ return doShellCommand(null, cmds, sc, runAsRoot, waitFor).exitValue();
+
+ }
+
+ public static Process doShellCommand(Process proc, String[] cmds, ShellCallback sc, boolean runAsRoot, boolean waitFor) throws Exception {
+
+
+ if (proc == null) {
+ if (runAsRoot)
+ proc = Runtime.getRuntime().exec("su");
+ else
+ proc = Runtime.getRuntime().exec("sh");
+ }
+
+ OutputStreamWriter out = new OutputStreamWriter(proc.getOutputStream());
+
+ for (int i = 0; i < cmds.length; i++) {
+ logMessage("executing shell cmd: " + cmds[i] + "; runAsRoot=" + runAsRoot + ";waitFor=" + waitFor);
+
+ out.write(cmds[i]);
+ out.write("\n");
+ }
+
+ out.flush();
+ out.write("exit\n");
+ out.flush();
+
+ if (waitFor) {
+
+ final char buf[] = new char[20];
+
+ // Consume the "stdout"
+ InputStreamReader reader = new InputStreamReader(proc.getInputStream());
+ int read = 0;
+ while ((read = reader.read(buf)) != -1) {
+ if (sc != null) sc.shellOut(new String(buf));
+ }
+
+ // Consume the "stderr"
+ reader = new InputStreamReader(proc.getErrorStream());
+ read = 0;
+ while ((read = reader.read(buf)) != -1) {
+ if (sc != null) sc.shellOut(new String(buf));
+ }
+
+ proc.waitFor();
+
+ }
+
+ sc.processComplete(proc.exitValue());
+
+ return proc;
+
+ }
+
+ public static void logMessage(String msg) {
+
+ }
+
+ public static void logException(String msg, Exception e) {
+
+ }
+
+ public interface ShellCallback {
+ public void shellOut(String shellLine);
+
+ public void processComplete(int exitValue);
+ }
+}
diff --git a/app/src/main/java/org/ffmpeg/android/filters/CropVideoFilter.java b/app/src/main/java/org/ffmpeg/android/filters/CropVideoFilter.java
new file mode 100644
index 0000000..a64c306
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/filters/CropVideoFilter.java
@@ -0,0 +1,133 @@
+package org.ffmpeg.android.filters;
+
+public class CropVideoFilter extends VideoFilter {
+
+ private String mOutWidth;
+ private String mOutHeight;
+ private String mX;
+ private String mY;
+
+ public CropVideoFilter(String width, String height, String x, String y) {
+ mOutWidth = width;
+ mOutHeight = height;
+ mX = x;
+ mY = y;
+ }
+
+ @Override
+ public String getFilterString() {
+
+ StringBuffer result = new StringBuffer();
+
+ result.append("crop=");
+
+ if (mOutWidth != null)
+ result.append(mOutWidth).append(":");
+
+ if (mOutHeight != null)
+ result.append(mOutHeight).append(":");
+
+ if (mX != null)
+ result.append(mX).append(":");
+
+ if (mY != null)
+ result.append(mY).append(":");
+
+ result.deleteCharAt(result.length() - 1); //remove the last semicolon!
+
+ return result.toString();
+ }
+
+}
+
+/*
+Crop the input video to out_w:out_h:x:y:keep_aspect
+
+The keep_aspect parameter is optional, if specified and set to a non-zero value will force the output display aspect ratio to be the same of the input, by changing the output sample aspect ratio.
+
+The out_w, out_h, x, y parameters are expressions containing the following constants:
+
+‘x, y’
+the computed values for x and y. They are evaluated for each new frame.
+
+‘in_w, in_h’
+the input width and height
+
+‘iw, ih’
+same as in_w and in_h
+
+‘out_w, out_h’
+the output (cropped) width and height
+
+‘ow, oh’
+same as out_w and out_h
+
+‘a’
+same as iw / ih
+
+‘sar’
+input sample aspect ratio
+
+‘dar’
+input display aspect ratio, it is the same as (iw / ih) * sar
+
+‘hsub, vsub’
+horizontal and vertical chroma subsample values. For example for the pixel format "yuv422p" hsub is 2 and vsub is 1.
+
+‘n’
+the number of input frame, starting from 0
+
+‘pos’
+the position in the file of the input frame, NAN if unknown
+
+‘t’
+timestamp expressed in seconds, NAN if the input timestamp is unknown
+
+The out_w and out_h parameters specify the expressions for the width and height of the output (cropped) video. They are evaluated just at the configuration of the filter.
+
+The default value of out_w is "in_w", and the default value of out_h is "in_h".
+
+The expression for out_w may depend on the value of out_h, and the expression for out_h may depend on out_w, but they cannot depend on x and y, as x and y are evaluated after out_w and out_h.
+
+The x and y parameters specify the expressions for the position of the top-left corner of the output (non-cropped) area. They are evaluated for each frame. If the evaluated value is not valid, it is approximated to the nearest valid value.
+
+The default value of x is "(in_w-out_w)/2", and the default value for y is "(in_h-out_h)/2", which set the cropped area at the center of the input image.
+
+The expression for x may depend on y, and the expression for y may depend on x.
+
+Follow some examples:
+
+
+# crop the central input area with size 100x100
+crop=100:100
+
+# crop the central input area with size 2/3 of the input video
+"crop=2/3*in_w:2/3*in_h"
+
+# crop the input video central square
+crop=in_h
+
+# delimit the rectangle with the top-left corner placed at position
+# 100:100 and the right-bottom corner corresponding to the right-bottom
+# corner of the input image.
+crop=in_w-100:in_h-100:100:100
+
+# crop 10 pixels from the left and right borders, and 20 pixels from
+# the top and bottom borders
+"crop=in_w-2*10:in_h-2*20"
+
+# keep only the bottom right quarter of the input image
+"crop=in_w/2:in_h/2:in_w/2:in_h/2"
+
+# crop height for getting Greek harmony
+"crop=in_w:1/PHI*in_w"
+
+# trembling effect
+"crop=in_w/2:in_h/2:(in_w-out_w)/2+((in_w-out_w)/2)*sin(n/10):(in_h-out_h)/2 +((in_h-out_h)/2)*sin(n/7)"
+
+# erratic camera effect depending on timestamp
+"crop=in_w/2:in_h/2:(in_w-out_w)/2+((in_w-out_w)/2)*sin(t*10):(in_h-out_h)/2 +((in_h-out_h)/2)*sin(t*13)"
+
+# set x depending on the value of y
+"crop=in_w/2:in_h/2:y:10+10*sin(n/10)"
+*/
\ No newline at end of file
diff --git a/app/src/main/java/org/ffmpeg/android/filters/DrawBoxVideoFilter.java b/app/src/main/java/org/ffmpeg/android/filters/DrawBoxVideoFilter.java
new file mode 100644
index 0000000..ad81e4f
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/filters/DrawBoxVideoFilter.java
@@ -0,0 +1,52 @@
+package org.ffmpeg.android.filters;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+
+public class DrawBoxVideoFilter extends OverlayVideoFilter {
+
+ public int x;
+ public int y;
+ public int width;
+ public int height;
+ public String color;
+
+ public DrawBoxVideoFilter(int x, int y, int width, int height, int alpha, String color, File tmpDir) throws Exception {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.color = color;
+
+ if (alpha < 0 || alpha > 255) {
+ throw new IllegalArgumentException("Alpha must be an integer betweeen 0 and 255");
+ }
+ Paint paint = new Paint();
+ paint.setAlpha(alpha);
+
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+ bitmap.eraseColor(Color.parseColor(color));
+
+ Bitmap temp_box = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+ Canvas canvas = new Canvas(temp_box);
+ canvas.drawBitmap(bitmap, 0, 0, paint);
+
+ File outputFile;
+ outputFile = File.createTempFile("box_" + width + height + color, ".png", tmpDir);
+ FileOutputStream os = new FileOutputStream(outputFile);
+ temp_box.compress(Bitmap.CompressFormat.PNG, 100, os);
+ overlayFile = outputFile;
+ xParam = Integer.toString(x);
+ yParam = Integer.toString(y);
+
+ }
+}
diff --git a/app/src/main/java/org/ffmpeg/android/filters/DrawTextVideoFilter.java b/app/src/main/java/org/ffmpeg/android/filters/DrawTextVideoFilter.java
new file mode 100644
index 0000000..147d73b
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/filters/DrawTextVideoFilter.java
@@ -0,0 +1,124 @@
+package org.ffmpeg.android.filters;
+
+import java.io.File;
+
+public class DrawTextVideoFilter extends VideoFilter {
+
+ private String mX;
+ private String mY;
+ private String mText;
+ private String mFontColor;
+ private int mFontSize;
+ private File mFileFont;
+ private int mBox;
+ private String mBoxColor;
+
+ public final static String X_CENTERED = "(w-text_w)/2";
+ public final static String Y_CENTERED = "(h-text_h-line_h)/2";
+
+ public final static String X_LEFT = "0";
+ public final static String Y_BOTTOM = "(h-text_h-line_h)";
+
+ public DrawTextVideoFilter(String text) {
+ mX = X_CENTERED;
+ mY = Y_CENTERED;
+
+ mText = text;
+ mFontColor = "white";
+ mFontSize = 36;
+ mFileFont = new File("/system/fonts/Roboto-Regular.ttf");
+ if (!mFileFont.exists())
+ mFileFont = new File("/system/fonts/DroidSerif-Regular.ttf");
+
+ mBox = 1;
+ mBoxColor = "black@0.5";//0x00000000@1
+
+ }
+
+ public DrawTextVideoFilter(String text, String x, String y, String fontColor, int fontSize, File fontFile, boolean showBox, String boxColor, String boxOpacity) {
+ mX = x;
+ mY = y;
+
+ mText = text;
+ mFontColor = fontColor;
+ mFontSize = fontSize;
+
+ mFileFont = fontFile;
+
+ mBox = showBox ? 1 : 0;
+ mBoxColor = boxColor + '@' + boxOpacity;
+
+ }
+
+ @Override
+ public String getFilterString() {
+
+ StringBuffer result = new StringBuffer();
+ result.append("drawtext=");
+ result.append("fontfile='").append(mFileFont.getAbsolutePath()).append("':");
+ result.append("text='").append(mText).append("':");
+ result.append("x=").append(mX).append(":");
+ result.append("y=").append(mY).append(":");
+ result.append("fontcolor=").append(mFontColor).append(":");
+ result.append("fontsize=").append(mFontSize).append(":");
+ result.append("box=").append(mBox).append(":");
+ result.append("boxcolor=").append(mBoxColor);
+
+ return result.toString();
+ }
+
+}
+
+/*
+ * //mdout.videoFilter = "drawtext=fontfile=/system/fonts/DroidSans.ttf: text='this is awesome':x=(w-text_w)/2:y=H-60 :fontcolor=white :box=1:boxcolor=0x00000000@1";
+
+ File fontFile = new File("/system/fonts/Roboto-Regular.ttf");
+ if (!fontFile.exists())
+ fontFile = new File("/system/fonts/DroidSans.ttf");
+
+ mdout.videoFilter = "drawtext=fontfile='" + fontFile.getAbsolutePath() + "':text='this is awesome':x=(main_w-text_w)/2:y=50:fontsize=24:fontcolor=white";
+ */
+
+/**
+ * /system/fonts
+ *
+ * AndroidClock.ttf
+ * AndroidClock_Highlight.ttf
+ * AndroidClock_Solid.ttf
+ * AndroidEmoji.ttf
+ * AnjaliNewLipi-light.ttf
+ * Clockopia.ttf
+ * DroidNaskh-Regular-SystemUI.ttf
+ * DroidNaskh-Regular.ttf
+ * DroidSans-Bold.ttf
+ * DroidSans.ttf
+ * DroidSansArmenian.ttf
+ * DroidSansDevanagari-Regular.ttf
+ * DroidSansEthiopic-Regular.ttf
+ * DroidSansFallback.ttf
+ * DroidSansGeorgian.ttf
+ * DroidSansHebrew-Bold.ttf
+ * DroidSansHebrew-Regular.ttf
+ * DroidSansMono.ttf
+ * DroidSansTamil-Bold.ttf
+ * DroidSansTamil-Regular.ttf
+ * DroidSansThai.ttf
+ * DroidSerif-Bold.ttf
+ * DroidSerif-BoldItalic.ttf
+ * DroidSerif-Italic.ttf
+ * DroidSerif-Regular.ttf
+ * Lohit-Bengali.ttf
+ * Lohit-Kannada.ttf
+ * Lohit-Telugu.ttf
+ * MTLmr3m.ttf
+ * Roboto-Bold.ttf
+ * Roboto-BoldItalic.ttf
+ * Roboto-Italic.ttf
+ * Roboto-Light.ttf
+ * Roboto-LightItalic.ttf
+ * Roboto-Regular.ttf
+ * RobotoCondensed-Bold.ttf
+ * RobotoCondensed-BoldItalic.ttf
+ * RobotoCondensed-Italic.ttf
+ * RobotoCondensed-Regular.ttf
+ */
\ No newline at end of file
diff --git a/app/src/main/java/org/ffmpeg/android/filters/FadeVideoFilter.java b/app/src/main/java/org/ffmpeg/android/filters/FadeVideoFilter.java
new file mode 100644
index 0000000..094e5b6
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/filters/FadeVideoFilter.java
@@ -0,0 +1,28 @@
+package org.ffmpeg.android.filters;
+
+public class FadeVideoFilter extends VideoFilter {
+
+ private String mAction; //in our out
+ private int mStart;
+ private int mLength;
+
+ public FadeVideoFilter(String action, int start, int length) {
+ mAction = action;
+ mStart = start;
+ mLength = length;
+ }
+
+ @Override
+ public String getFilterString() {
+
+ StringBuffer result = new StringBuffer();
+ result.append("fade=");
+ result.append(mAction).append(':').append(mStart).append(':').append(mLength);
+
+ return result.toString();
+ }
+
+}
+
+///fade=in:0:25, fade=out:975:25
+
diff --git a/app/src/main/java/org/ffmpeg/android/filters/OverlayVideoFilter.java b/app/src/main/java/org/ffmpeg/android/filters/OverlayVideoFilter.java
new file mode 100644
index 0000000..9cdab2d
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/filters/OverlayVideoFilter.java
@@ -0,0 +1,65 @@
+package org.ffmpeg.android.filters;
+
+import java.io.File;
+
+/**
+ * @class overlay overlay one image or video on top of another
+ * @desc x is the x coordinate of the overlayed video on the main video,
+ * y is the y coordinate. The parameters are expressions containing
+ * the following parameters:
+ *
+ * main_w, main_h
+ * main input width and height
+ *
+ * W, H
+ * same as main_w and main_h
+ *
+ * overlay_w, overlay_h
+ * overlay input width and height
+ *
+ * w, h
+ * same as overlay_w and overlay_h
+ *
+ * @examples
draw the overlay at 10 pixels from the bottom right
+ * corner of the main video.
+ * main_w-overlay_w-10
+ * main_h-overlay_h-10
+ * draw the overlay in the bottom left corner of the input
+ * 10
+ * main_h-overlay_h-10 [out]
+ */
+public class OverlayVideoFilter extends VideoFilter {
+
+ public File overlayFile;
+ public String xParam, yParam;
+
+ public OverlayVideoFilter() {
+
+ }
+
+ public OverlayVideoFilter(File fileMovieOverlay, int x, int y) {
+ this.overlayFile = fileMovieOverlay;
+ this.xParam = Integer.toString(x);
+ this.yParam = Integer.toString(y);
+ }
+
+ public OverlayVideoFilter(File fileMovieOverlay, String xExpression, String yExpression) {
+ this.overlayFile = fileMovieOverlay;
+ this.xParam = xExpression;
+ this.yParam = yExpression;
+ }
+
+ public String getFilterString() {
+ if (overlayFile != null)
+ return "movie="
+ + overlayFile.getAbsolutePath()
+ + " [logo];[in][logo] "
+ + "overlay=" + xParam + ":" + yParam
+ + " [out]";
+ else
+ return "";
+
+ }
+}
+
+//"\"movie="+ overlayImage.getPath() +" [logo];[in][logo] overlay=0:0 [out]\"",
\ No newline at end of file
diff --git a/app/src/main/java/org/ffmpeg/android/filters/RedactVideoFilter.java b/app/src/main/java/org/ffmpeg/android/filters/RedactVideoFilter.java
new file mode 100644
index 0000000..b971c53
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/filters/RedactVideoFilter.java
@@ -0,0 +1,23 @@
+package org.ffmpeg.android.filters;
+
+import java.io.File;
+
+public class RedactVideoFilter extends VideoFilter {
+
+ private File fileRedactList;
+
+ public RedactVideoFilter(File fileRedactList) {
+ this.fileRedactList = fileRedactList;
+ }
+
+ public String getFilterString() {
+ if (fileRedactList != null)
+ return "redact=" + fileRedactList.getAbsolutePath();
+ else
+ return "";
+
+ }
+}
+
+//redact=blurbox.txt [out] [d], [d]nullsink
+//"redact=" + Environment.getExternalStorageDirectory().getPath() + "/" + PACKAGENAME + "/redact_unsort.txt",
\ No newline at end of file
diff --git a/app/src/main/java/org/ffmpeg/android/filters/TransposeVideoFilter.java b/app/src/main/java/org/ffmpeg/android/filters/TransposeVideoFilter.java
new file mode 100644
index 0000000..b02264f
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/filters/TransposeVideoFilter.java
@@ -0,0 +1,29 @@
+package org.ffmpeg.android.filters;
+
+/*
+ * works for video and images
+ * 0 = 90CounterCLockwise and Vertical Flip (default)
+1 = 90Clockwise
+2 = 90CounterClockwise
+3 = 90Clockwise and Vertical Flip
+ */
+public class TransposeVideoFilter extends VideoFilter {
+
+ private int mTranspose = -1;
+
+ public final static int NINETY_COUNTER_CLOCKWISE_AND_VERTICAL_FLIP = 0;
+ public final static int NINETY_CLOCKWISE = 1;
+ public final static int NINETY_COUNTER_CLOCKWISE = 2;
+ public final static int NINETY_CLOCKWISE_AND_VERTICAL_FLIP = 3;
+
+ public TransposeVideoFilter(int transpose) {
+ mTranspose = transpose;
+ }
+
+ @Override
+ public String getFilterString() {
+
+ return "transpose=" + mTranspose;
+ }
+
+}
diff --git a/app/src/main/java/org/ffmpeg/android/filters/VideoFilter.java b/app/src/main/java/org/ffmpeg/android/filters/VideoFilter.java
new file mode 100644
index 0000000..5251689
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/filters/VideoFilter.java
@@ -0,0 +1,25 @@
+package org.ffmpeg.android.filters;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public abstract class VideoFilter {
+
+ public abstract String getFilterString();
+
+ public static String format(ArrayList listFilters) {
+ StringBuffer result = new StringBuffer();
+
+ Iterator it = listFilters.iterator();
+ VideoFilter vf;
+
+ while (it.hasNext()) {
+ vf = it.next();
+ result.append(vf.getFilterString());
+
+ if (it.hasNext())
+ result.append(",");
+ }
+ return result.toString();
+ }
+}
diff --git a/app/src/main/java/org/ffmpeg/android/test/ConcatTest.java b/app/src/main/java/org/ffmpeg/android/test/ConcatTest.java
new file mode 100644
index 0000000..19e178c
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/test/ConcatTest.java
@@ -0,0 +1,62 @@
+package org.ffmpeg.android.test;
+
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import net.sourceforge.sox.SoxController;
+
+import org.ffmpeg.android.Clip;
+import org.ffmpeg.android.FfmpegController;
+import org.ffmpeg.android.ShellUtils;
+
+public class ConcatTest {
+
+ public static void test(String videoRoot, String fileTmpPath, String fileOut, double fadeLen) throws Exception {
+ File fileTmp = new File(fileTmpPath);
+ File fileAppRoot = new File("");
+ File fileVideoRoot = new File(videoRoot);
+
+ FfmpegController fc = new FfmpegController(null, fileTmp);
+ SoxController sxCon = new SoxController(null, fileAppRoot, null);
+
+ ArrayList listVideos = new ArrayList();
+
+ String[] fileList = fileVideoRoot.list();
+ for (String fileVideo : fileList) {
+ if (fileVideo.endsWith("mp4")) {
+ Clip clip = new Clip();
+ clip.path = new File(fileVideoRoot, fileVideo).getCanonicalPath();
+
+ fc.getInfo(clip);
+
+ clip.duration = clip.duration - fadeLen;
+ listVideos.add(clip);
+
+
+ }
+ }
+
+ Clip clipOut = new Clip();
+ clipOut.path = new File(fileOut).getCanonicalPath();
+
+ fc.concatAndTrimFilesMP4Stream(listVideos, clipOut, false, false, new ShellUtils.ShellCallback() {
+
+ @Override
+ public void shellOut(String shellLine) {
+
+ System.out.println("fc>" + shellLine);
+ }
+
+ @Override
+ public void processComplete(int exitValue) {
+
+ if (exitValue < 0)
+ System.err.println("concat non-zero exit: " + exitValue);
+ }
+ });
+
+
+ }
+}
diff --git a/app/src/main/java/org/ffmpeg/android/test/ConvertTest.java b/app/src/main/java/org/ffmpeg/android/test/ConvertTest.java
new file mode 100644
index 0000000..a1d6178
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/test/ConvertTest.java
@@ -0,0 +1,5 @@
+package org.ffmpeg.android.test;
+
+public class ConvertTest {
+
+}
diff --git a/app/src/main/java/org/ffmpeg/android/test/CrossfadeTest.java b/app/src/main/java/org/ffmpeg/android/test/CrossfadeTest.java
new file mode 100644
index 0000000..da73756
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/test/CrossfadeTest.java
@@ -0,0 +1,188 @@
+package org.ffmpeg.android.test;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import net.sourceforge.sox.CrossfadeCat;
+import net.sourceforge.sox.SoxController;
+
+import org.ffmpeg.android.Clip;
+import org.ffmpeg.android.FfmpegController;
+import org.ffmpeg.android.ShellUtils;
+
+public class CrossfadeTest {
+
+
+ public static void test(String videoRoot, String fileTmpPath, String clipOutPath, double fadeLen) throws Exception {
+ File fileTmp = new File(fileTmpPath);
+ File fileAppRoot = new File("");
+ File fileVideoRoot = new File(videoRoot);
+
+ String fadeType = "l";
+ int sampleRate = 22050;
+ int channels = 1;
+
+ FfmpegController ffmpegc = new FfmpegController(null, fileTmp);
+
+ Clip clipOut = new Clip();
+ clipOut.path = clipOutPath;
+ clipOut.audioCodec = "aac";
+ clipOut.audioBitrate = 56;
+
+
+ ArrayList listVideos = new ArrayList();
+
+ String[] fileList = fileVideoRoot.list();
+ for (String fileVideo : fileList) {
+ if (fileVideo.endsWith("mp4")) {
+ Clip clip = new Clip();
+ clip.path = new File(fileVideoRoot, fileVideo).getCanonicalPath();
+ //clip.startTime = "00:00:03";
+ //clip.duration = "00:00:02";
+
+ ffmpegc.getInfo(clip);
+
+ //System.out.println("clip " + fileVideo + " duration=" + clip.duration);
+
+ listVideos.add(clip);
+
+ }
+ }
+
+ //now add 1 second cross fade to each audio file and cat them together
+ SoxController sxCon = new SoxController(null, fileAppRoot, new ShellUtils.ShellCallback() {
+
+ @Override
+ public void shellOut(String shellLine) {
+
+ // System.out.println("sxCon> " + shellLine);
+
+ }
+
+ @Override
+ public void processComplete(int exitValue) {
+
+
+ if (exitValue != 0) {
+ System.err.println("sxCon> EXIT=" + exitValue);
+
+ RuntimeException re = new RuntimeException("non-zero exit: " + exitValue);
+ re.printStackTrace();
+ throw re;
+ }
+
+ }
+ });
+
+ ArrayList alAudio = new ArrayList();
+
+ //convert each input file to a WAV so we can use Sox to process
+ int wavIdx = 0;
+
+ for (Clip mediaIn : listVideos) {
+ if (new File(mediaIn.path).exists()) {
+
+ if (mediaIn.audioCodec == null) {
+ //there is no audio track so let's generate silence
+
+
+ } else {
+ Clip audioOut = ffmpegc.convertToWaveAudio(mediaIn, new File(fileTmp, wavIdx + ".wav").getCanonicalPath(), sampleRate, channels, new ShellUtils.ShellCallback() {
+
+ @Override
+ public void shellOut(String shellLine) {
+
+ // System.out.println("convertToWav> " + shellLine);
+
+ }
+
+ @Override
+ public void processComplete(int exitValue) {
+
+ if (exitValue != 0) {
+
+ System.err.println("convertToWav> EXIT=" + exitValue);
+
+ RuntimeException re = new RuntimeException("non-zero exit: " + exitValue);
+ re.printStackTrace();
+ throw re;
+ }
+ }
+ });
+
+ alAudio.add(audioOut);
+
+ /*
+ float duration = (float) sxCon.getLength(new File(audioOut.path).getCanonicalPath());
+
+ if (mediaIn.duration == null)
+ {
+ mediaIn.duration = String.format(Locale.US, "%f", duration);
+ }*/
+ ffmpegc.getInfo(mediaIn);
+
+
+ wavIdx++;
+ }
+ } else {
+ throw new FileNotFoundException(mediaIn.path);
+ }
+ }
+
+ if (alAudio.size() > 0) {
+ String fileOut = alAudio.get(0).path;
+
+ System.out.println("mix length=" + sxCon.getLength(fileOut));
+
+ for (int i = 1; i < alAudio.size(); i++) {
+
+ File fileAdd = new File(alAudio.get(i).path);
+
+ CrossfadeCat xCat = new CrossfadeCat(sxCon, fileOut, fileAdd.getCanonicalPath(), fadeLen, fileOut);
+ xCat.start();
+
+ fileAdd.deleteOnExit();
+
+ System.out.println("mix length=" + sxCon.getLength(fileOut));
+
+ }
+
+
+ //1 second fade in and fade out, t = triangle or linear
+ //String fadeLenStr = sxCon.formatTimePeriod(fadeLen);
+
+
+ String fadeFileOut = sxCon.fadeAudio(fileOut, fadeType, fadeLen, sxCon.getLength(fileOut) - fadeLen, fadeLen);
+
+ //now export the final file to our requested output format mOut.mimeType = AppConstants.MimeTypes.MP4_AUDIO;
+
+ Clip mdFinalIn = new Clip();
+ mdFinalIn.path = fadeFileOut;
+
+
+ System.out.println("final duration: " + sxCon.getLength(fadeFileOut));
+
+ Clip exportOut = ffmpegc.convertTo3GPAudio(mdFinalIn, clipOut, new ShellUtils.ShellCallback() {
+
+ @Override
+ public void shellOut(String shellLine) {
+
+ //System.out.println("convertTo3gp> " + shellLine);
+ }
+
+ @Override
+ public void processComplete(int exitValue) {
+
+ if (exitValue < 0) {
+ RuntimeException re = new RuntimeException("non-zero exit: " + exitValue);
+ re.printStackTrace();
+ throw re;
+ }
+ }
+ });
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/ffmpeg/android/test/FilterTest.java b/app/src/main/java/org/ffmpeg/android/test/FilterTest.java
new file mode 100644
index 0000000..0426df6
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/test/FilterTest.java
@@ -0,0 +1,65 @@
+package org.ffmpeg.android.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.ffmpeg.android.filters.CropVideoFilter;
+import org.ffmpeg.android.filters.DrawBoxVideoFilter;
+import org.ffmpeg.android.filters.DrawTextVideoFilter;
+import org.ffmpeg.android.filters.FadeVideoFilter;
+import org.ffmpeg.android.filters.TransposeVideoFilter;
+import org.ffmpeg.android.filters.VideoFilter;
+
+import android.app.Activity;
+import android.content.Context;
+
+public class FilterTest {
+
+
+ public static void test(String title, String textColor, File fileFont, String boxColor, String opacity) throws Exception {
+ ArrayList listFilters = new ArrayList();
+
+ File fileDir = new File("tmp");
+ fileDir.mkdir();
+
+ int height = 480;
+ int width = 720;
+ int lowerThird = height / 3;
+ DrawBoxVideoFilter vf = new DrawBoxVideoFilter(0, height - lowerThird, width, lowerThird, 100, "blue", fileDir);
+
+ DrawTextVideoFilter vfTitle =
+ new DrawTextVideoFilter(title,
+ DrawTextVideoFilter.X_CENTERED, DrawTextVideoFilter.Y_CENTERED,
+ textColor,
+ 38,
+ fileFont,
+ true,
+ boxColor,
+ opacity);
+
+ float fps = 29.97f;
+ int fadeTime = (int) (fps * 3);
+ //fades in first 3 seconds
+ FadeVideoFilter vfFadeIn = new FadeVideoFilter("in", 0, fadeTime);
+
+ //fades out last 50 frames
+ int totalFrames = (int) (14.37 * 29.97);
+ FadeVideoFilter vfFadeOut = new FadeVideoFilter("out", totalFrames - fadeTime, fadeTime);
+
+ //crops video in 100 pixels on each side
+ CropVideoFilter vfCrop = new CropVideoFilter("in_w-100", "in_h-100", "100", "100");
+
+ //rotates video 90 degress clockwise
+ TransposeVideoFilter vfTranspose = new TransposeVideoFilter(TransposeVideoFilter.NINETY_CLOCKWISE);
+
+ listFilters.add(vfTranspose);
+ listFilters.add(vfCrop);
+ listFilters.add(vfTitle);
+ listFilters.add(vfFadeIn);
+ listFilters.add(vfFadeOut);
+
+
+ fileDir.deleteOnExit();
+ }
+}
diff --git a/app/src/main/java/org/ffmpeg/android/test/MixTest.java b/app/src/main/java/org/ffmpeg/android/test/MixTest.java
new file mode 100644
index 0000000..eb9d4d8
--- /dev/null
+++ b/app/src/main/java/org/ffmpeg/android/test/MixTest.java
@@ -0,0 +1,383 @@
+package org.ffmpeg.android.test;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.os.Environment;
+import android.text.TextPaint;
+import android.util.Log;
+
+import com.coremedia.iso.boxes.Container;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
+import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
+import com.googlecode.mp4parser.authoring.tracks.AppendTrack;
+
+import org.ffmpeg.android.Clip;
+import org.ffmpeg.android.FfmpegController;
+import org.ffmpeg.android.R;
+import org.ffmpeg.android.ShellUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.util.LinkedList;
+import java.util.List;
+
+public class MixTest {
+
+ public static void test(String fileTmpPath, String videoClipPath, String audioClipPath, final Clip clipOut, Context context) throws Exception {
+ File fileTmp = new File(fileTmpPath);
+ File fileAppRoot = new File("");
+
+ final FfmpegController fc = new FfmpegController(context, fileTmp);
+
+ final Clip clipVideo = new Clip(videoClipPath);
+ //fc.getInfo(clipVideo);
+ clipVideo.videoCodec = "mp4";
+ final Clip clipAudio = new Clip(audioClipPath);
+ //fc.getInfo(clipAudio);
+ clipAudio.audioCodec = "aac";
+ final Clip clipTemp = new Clip("/storage/emulated/0/.mofunshow/movies/90331/test_temp.mp4");
+ final Clip clipPic = new Clip("/storage/emulated/0/.mofunshow/movies/90331/1.png");
+// fc.removeVideoAudio(clipVideo, clipTemp, new ShellUtils.ShellCallback() {
+// @Override
+// public void shellOut(String shellLine) {
+// System.out.println("removeVideoAudio> " + shellLine);
+// }
+//
+// @Override
+// public void processComplete(int exitValue) {
+// if (exitValue != 0)
+// System.err.println("removeVideoAudio concat non-zero exit: " + exitValue);
+// try {
+// fc.combineAudioAndVideo(clipTemp, clipAudio, clipOut, new ShellUtils.ShellCallback() {
+//
+// @Override
+// public void shellOut(String shellLine) {
+// System.out.println("MIX> " + shellLine);
+// }
+//
+// @Override
+// public void processComplete(int exitValue) {
+//
+// if (exitValue != 0)
+// System.err.println("combineAudioAndVideo concat non-zero exit: " + exitValue);
+// }
+// });
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+// }
+// });
+// fc.combineAudioAndVideo(clipVideo, clipAudio, clipOut, new ShellUtils.ShellCallback() {
+//
+// @Override
+// public void shellOut(String shellLine) {
+// System.out.println("MIX> " + shellLine);
+// }
+//
+// @Override
+// public void processComplete(int exitValue) {
+//
+// if (exitValue != 0)
+// System.err.println("combineAudioAndVideo concat non-zero exit: " + exitValue);
+// }
+// });
+// fc.convertImageToMP4(clipPic, 3, "/storage/emulated/0/.mofunshow/movies/90331/test_temp.mp4", new ShellUtils.ShellCallback() {
+// @Override
+// public void shellOut(String shellLine) {
+// System.out.println("convertImageToMP4> " + shellLine);
+// }
+//
+// @Override
+// public void processComplete(int exitValue) {
+// System.out.println("convertImageToMP4> complete");
+// }
+// });
+
+
+ }
+
+ public static void testJpegToMp4(final String mp4FilePath, String fileTmpPath, List picFiles, final String clipOut, Context context) throws Exception {
+ File fileTmp = new File(fileTmpPath);
+ final FfmpegController fc = new FfmpegController(context, fileTmp);
+ final String clipTemp = "/storage/emulated/0/.mofunshow/movies/90331/test_temp.mp4";
+ String clipLogo = "/storage/emulated/0/.mofunshow/movies/90331/logo.png";
+ final Clip clipPic = new Clip(picFiles.get(0));
+ clipPic.height = 450;
+ clipPic.width = 800;
+ clipPic.videoBitrate = 256;
+
+ Clip logo = new Clip(clipLogo);
+
+ fc.convertImageToMP4(clipPic, logo, 2, clipOut, new ShellUtils.ShellCallback() {
+ @Override
+ public void shellOut(String shellLine) {
+ System.out.println("convertImageToMP4> " + shellLine);
+ }
+
+ @Override
+ public void processComplete(int exitValue) {
+ System.out.println("convertImageToMP4> complete");
+// List fileList = new ArrayList();
+// fileList.add(mp4FilePath);
+//// fileList.add(clipOut);
+// fileList.add(clipOut);
+// newClipMethod(fileList,clipTemp);
+// try {
+// fc.filterMp4Complex(new Clip(clipOut), clipPic,clipTemp, new ShellUtils.ShellCallback() {
+// @Override
+// public void shellOut(String shellLine) {
+// System.out.println("filterMp4Complex> " + shellLine);
+// }
+//
+// @Override
+// public void processComplete(int exitValue) {
+// System.out.println("filterMp4Complex> complete");
+// }
+// });
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+// try {
+// fc.mergMp4(new Clip(mp4FilePath), new Clip(clipOut), clipTemp, new ShellUtils.ShellCallback() {
+// @Override
+// public void shellOut(String shellLine) {
+// System.out.println("mergMp4> " + shellLine);
+// }
+//
+// @Override
+// public void processComplete(int exitValue) {
+// System.out.println("mergMp4> complete");
+// }
+// });
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+ }
+ });
+
+
+ }
+
+ public static void testMergeMp4(FfmpegController fc, final String mp4FilePath, final String filterPath, String fileTmpPath, final String clipOut, Context context) throws Exception {
+ File fileTmp = new File(fileTmpPath);
+
+ String content = "file '" + mp4FilePath + "'\r\n"
+ + "file '" + filterPath + "'";
+
+ if (fc == null) {
+ fc = new FfmpegController(context, fileTmp);
+ }
+ fc.mergMp4(saveConfig(content, fileTmpPath), clipOut, new ShellUtils.ShellCallback() {
+ @Override
+ public void shellOut(String shellLine) {
+ System.out.println("mergMp4> " + shellLine);
+ }
+
+ @Override
+ public void processComplete(int exitValue) {
+ System.out.println("mergMp4> complete");
+ }
+ });
+
+
+ }
+
+ private static String saveConfig(String content, String tempPath) {
+ String textName = "config.txt";
+ writeTxtToFile(content, tempPath, textName);
+ return tempPath + "/" + textName;
+
+ }
+
+ // 将字符串写入到文本文件中
+ public static void writeTxtToFile(String strcontent, String filePath, String fileName) {
+ //生成文件夹之后,再生成文件,不然会出错
+ makeFilePath(filePath, fileName);
+
+ String strFilePath = filePath + fileName;
+ // 每次写入时,都换行写
+ String strContent = strcontent + "\r\n";
+ try {
+ File file = new File(strFilePath);
+ if (!file.exists()) {
+ Log.d("TestFile", "Create the file:" + strFilePath);
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ } else {
+ file.delete();
+ }
+ RandomAccessFile raf = new RandomAccessFile(file, "rwd");
+ raf.seek(file.length());
+ raf.write(strContent.getBytes());
+ raf.close();
+ } catch (Exception e) {
+ Log.e("TestFile", "Error on write File:" + e);
+ }
+ }
+
+ // 生成文件
+ public static File makeFilePath(String filePath, String fileName) {
+ File file = null;
+ makeRootDirectory(filePath);
+ try {
+ file = new File(filePath + fileName);
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return file;
+ }
+
+ // 生成文件夹
+ public static void makeRootDirectory(String filePath) {
+ File file = null;
+ try {
+ file = new File(filePath);
+ if (!file.exists()) {
+ file.mkdir();
+ }
+ } catch (Exception e) {
+ Log.i("error:", e + "");
+ }
+
+ }
+
+ public static void newClipMethod(List fileList, String output) {
+ List moviesList = new LinkedList();
+ try {
+ for (String file : fileList) {
+ moviesList.add(MovieCreator.build(file));
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ List