diff --git a/.classpath b/.classpath index 3a0c88f..d57ec02 100644 --- a/.classpath +++ b/.classpath @@ -1,9 +1,9 @@ - + - + diff --git a/README.md b/README.md index 4f677e1..7ad2b0c 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ Android applications. It depends on the [android-ffmpeg](https://github.com/guardianproject/android-ffmpeg) project to provide the ffmpeg binary. +For ease of developer use, we've included the FFMPEG and Sox binaries in the project, +however, we strongly recommend you compile them yourselves using the steps below. + It is licensed under the GPLv3. ## Building @@ -19,4 +22,11 @@ Then execute: cd external/android-ffmpeg ./configure_make_everything.sh +Once compiled, you will find the binaries at: + +external/android-ffmpeg/ffmpeg/ffmpeg +external/android-ffmpeg/data/data/info.guardianproject.ffmpeg/app_opt/bin/sox + +You should replace the files in res/raw with these. + Then build the project in Eclipse. diff --git a/external/android-ffmpeg b/external/android-ffmpeg index 53fc9d4..61ea21c 160000 --- a/external/android-ffmpeg +++ b/external/android-ffmpeg @@ -1 +1 @@ -Subproject commit 53fc9d4e2972792236d9e4c4c5793322947a884e +Subproject commit 61ea21c932d7498b8d330a421425e50e65c805aa diff --git a/project.properties b/project.properties index 484dab0..91d2b02 100644 --- a/project.properties +++ b/project.properties @@ -11,5 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-17 +target=android-19 android.library=true diff --git a/res/raw/ffmpeg b/res/raw/ffmpeg new file mode 100755 index 0000000..d24cdce Binary files /dev/null and b/res/raw/ffmpeg differ diff --git a/res/raw/ffmpeg.mp3 b/res/raw/ffmpeg.mp3 deleted file mode 120000 index 01c28da..0000000 --- a/res/raw/ffmpeg.mp3 +++ /dev/null @@ -1 +0,0 @@ -../../external/android-ffmpeg/ffmpeg/ffmpeg \ No newline at end of file diff --git a/res/raw/sox b/res/raw/sox new file mode 100755 index 0000000..60d1849 Binary files /dev/null and b/res/raw/sox differ diff --git a/res/raw/sox.mp3 b/res/raw/sox.mp3 deleted file mode 120000 index af7f4da..0000000 --- a/res/raw/sox.mp3 +++ /dev/null @@ -1 +0,0 @@ -../../external/android-ffmpeg/data/data/info.guardianproject.ffmpeg/app_opt/bin/sox \ No newline at end of file diff --git a/src/net/sourceforge/sox/CrossfadeCat.java b/src/net/sourceforge/sox/CrossfadeCat.java index 7de2225..78cfdd1 100644 --- a/src/net/sourceforge/sox/CrossfadeCat.java +++ b/src/net/sourceforge/sox/CrossfadeCat.java @@ -4,11 +4,9 @@ import java.io.IOException; import java.util.ArrayList; -import android.util.Log; - /** * Concatenates two files together with a crossfade of user - * defined length. + * defined mClipLength. * * It is a Java port of the scripts/crossfade_cat.sh script * in the sox source tree. @@ -34,32 +32,37 @@ public CrossfadeCat(SoxController controller, String firstFile, String secondFil mSecondFile = secondFile; mFadeLength = fadeLength; mFinalMix = outFile; + + //double mClipLength = mController.getLength(mFirstFile); } - public boolean start() throws IOException { - // find length of first file - double length = mController.getLength(mFirstFile); - - double trimLength = length - mFadeLength; + 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 ) - return abort(); - - // We assume a fade out is needed (i.e., firstFile doesn't already fade out) + 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 ) - return abort(); + 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 ) - return abort(); + throw new IOException("audio trim did not complete: " + mSecondFile); String fadedTwo = mController.fadeAudio(trimmedTwo, "t", mFadeLength, -1, -1); if( fadedTwo == null ) - return abort(); + throw new IOException("audio fade did not complete: " + trimmedTwo); // Mix crossfaded files together at full volume ArrayList files = new ArrayList(); @@ -69,30 +72,29 @@ public boolean start() throws IOException { String crossfaded = new File(mFirstFile).getCanonicalPath() + "-x-" + new File(mSecondFile).getName() +".wav"; crossfaded = mController.combineMix(files, crossfaded); if( crossfaded == null ) - return abort(); + throw new IOException("crossfade did not complete"); // Trim off crossfade sections from originals String trimmedThree = mController.trimAudio(mFirstFile, 0, trimLength); if( trimmedThree == null ) - return abort(); + throw new IOException("crossfade trim beginning did not complete"); String trimmedFour = mController.trimAudio(mSecondFile, mFadeLength, -1); if( trimmedFour == null ) - return abort(); - + 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; } - - private boolean abort() { - return false; - } - } diff --git a/src/net/sourceforge/sox/SoxController.java b/src/net/sourceforge/sox/SoxController.java index d8269d8..4ad6873 100644 --- a/src/net/sourceforge/sox/SoxController.java +++ b/src/net/sourceforge/sox/SoxController.java @@ -3,18 +3,16 @@ 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.text.DecimalFormat; -import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.List; import java.util.Locale; -import org.ffmpeg.android.BinaryInstaller; +import org.ffmpeg.android.R; import org.ffmpeg.android.ShellUtils.ShellCallback; import android.content.Context; @@ -25,23 +23,68 @@ public class SoxController { String[] libraryAssets = {"sox"}; private String soxBin; private File fileBinDir; - private Context context; private ShellCallback callback; - public SoxController(Context _context, ShellCallback _callback) throws FileNotFoundException, IOException { - context = _context; - fileBinDir = context.getDir("bin",0); + public SoxController(Context context, File fileAppRoot, ShellCallback _callback) throws FileNotFoundException, IOException { callback = _callback; - if (!new File(fileBinDir, libraryAssets[0]).exists()) - { - BinaryInstaller bi = new BinaryInstaller(context,fileBinDir); - bi.installFromRaw(); - } - - soxBin = new File(fileBinDir,"sox").getCanonicalPath(); + 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 { @@ -50,7 +93,6 @@ private class LengthParser implements ShellCallback { @Override public void shellOut(String shellLine) { - Log.d("sox", shellLine); if( !shellLine.startsWith("Length") ) return; String[] split = shellLine.split(":"); @@ -89,12 +131,10 @@ public double getLength(String path) { try { execSox(cmd, sc); - } catch (IOException e) { - Log.e("sox","error getting length ",e); - } catch (InterruptedException e) { - Log.e("sox","error getting length",e); - } - + } catch (Exception e) { + return -1; + } + return sc.length; } @@ -105,7 +145,7 @@ public double getLength(String path) { * @param length (optional) * @return path to trimmed audio */ - public String trimAudio(String path, double start, double length) throws IOException { + public String trimAudio(String path, double start, double length) throws Exception { ArrayList cmd = new ArrayList(); File file = new File(path); @@ -122,18 +162,9 @@ public String trimAudio(String path, double start, double length) throws IOExcep if( length != -1 ) cmd.add(length+""); - try { - int rc = execSox(cmd, callback); - if( rc != 0 ) { - Log.e(TAG, "trimAudio 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(); + int rc = execSox(cmd, callback); + if( rc != 0 ) { + outFile = null; } if (file.exists()) @@ -158,8 +189,8 @@ public String fadeAudio(String path, String type, double fadeInLength, double st final List curves = Arrays.asList( new String[]{ "q", "h", "t", "l", "p"} ); if(!curves.contains(type)) { - Log.e(TAG, "fadeAudio: passed invalid type: " + type); - return null; + throw new RuntimeException("fadeAudio: passed invalid type: " + type); + } File file = new File(path); @@ -180,7 +211,8 @@ public String fadeAudio(String path, String type, double fadeInLength, double st try { int rc = execSox(cmd, callback); if(rc != 0) { - Log.e(TAG, "fadeAudio receieved non-zero return code!"); + //Log.e(TAG, "fadeAudio receieved non-zero return code!"); + outFile = null; } } catch (IOException e) { @@ -215,7 +247,7 @@ public String combineMix(List files, String outFile) { try { int rc = execSox(cmd, callback); if(rc != 0) { - Log.e(TAG, "combineMix receieved non-zero return code!"); + // Log.e(TAG, "combineMix receieved non-zero return code!"); outFile = null; } } catch (IOException e) { @@ -235,7 +267,7 @@ public String combineMix(List files, String outFile) { * @param outFile * @return outFile or null on failure */ - public String combine(List files, String outFile) { + public String combine(List files, String outFile) throws Exception { ArrayList cmd = new ArrayList(); cmd.add(soxBin); @@ -244,19 +276,12 @@ public String combine(List files, String outFile) { } cmd.add(outFile); - try { int rc = execSox(cmd, callback); if(rc != 0) { - Log.e(TAG, "combine receieved non-zero return code!"); - outFile = null; + throw new Exception ("exit code: " + rc); + } - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + return outFile; } @@ -277,6 +302,7 @@ 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); } @@ -284,6 +310,12 @@ public int execSox(List cmd, ShellCallback sc) throws IOException, 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); @@ -294,8 +326,8 @@ private int execProcess(List cmds, ShellCallback sc) cmdlog.append(' '); } - Log.v(TAG, cmdlog.toString()); - + sc.shellOut(cmdlog.toString()); + // pb.redirectErrorStream(true); Process process = pb.start(); @@ -341,7 +373,7 @@ public void run() { sc.shellOut(line); } catch (IOException ioe) { - Log.e(TAG, "error reading shell slog", ioe); + ioe.printStackTrace(); } } } diff --git a/src/org/ffmpeg/android/BinaryInstaller.java b/src/org/ffmpeg/android/BinaryInstaller.java deleted file mode 100644 index d2d514c..0000000 --- a/src/org/ffmpeg/android/BinaryInstaller.java +++ /dev/null @@ -1,211 +0,0 @@ -package org.ffmpeg.android; -/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */ -/* See LICENSE for licensing information */ - -import java.io.BufferedReader; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.zip.GZIPInputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - - -import android.content.Context; -import android.util.Log; - -public class BinaryInstaller { - - - File installFolder; - Context context; - - private static int isARMv6 = -1; - private static String CHMOD_EXEC = "700"; - - private final static int FILE_WRITE_BUFFER_SIZE = 32256; - - public BinaryInstaller (Context context, File installFolder) - { - this.installFolder = installFolder; - - this.context = context; - } - - // - /* - * Extract the Tor binary from the APK file using ZIP - */ - public boolean installFromRaw () throws IOException, FileNotFoundException - { - - InputStream is; - File outFile; - - is = context.getResources().openRawResource(R.raw.ffmpeg); - outFile = new File(installFolder, "ffmpeg"); - streamToFile(is, outFile, false, false, "700"); - - is = context.getResources().openRawResource(R.raw.sox); - outFile = new File(installFolder, "sox"); - streamToFile(is, outFile, false, false, "700"); - - return true; - } - - - private static void copyAssetFile(Context ctx, String asset, File file) throws IOException, InterruptedException - { - - DataOutputStream out = new DataOutputStream(new FileOutputStream(file)); - InputStream is = new GZIPInputStream(ctx.getAssets().open(asset)); - - byte buf[] = new byte[8172]; - int len; - while ((len = is.read(buf)) > 0) { - out.write(buf, 0, len); - } - out.close(); - is.close(); - } - /* - * Write the inputstream contents to the file - */ - private static boolean streamToFile(InputStream stm, File outFile, boolean append, boolean zip, String mode) throws IOException - - { - byte[] buffer = new byte[FILE_WRITE_BUFFER_SIZE]; - - int bytecount; - - - OutputStream stmOut = new FileOutputStream(outFile, append); - - if (zip) - { - ZipInputStream zis = new ZipInputStream(stm); - ZipEntry ze = zis.getNextEntry(); - stm = zis; - - } - - while ((bytecount = stm.read(buffer)) > 0) - { - - stmOut.write(buffer, 0, bytecount); - - } - - stmOut.close(); - stm.close(); - - Runtime.getRuntime().exec("chmod "+mode+" "+outFile.getCanonicalPath()); - - - return true; - - } - - //copy the file from inputstream to File output - alternative impl - public void copyFile (InputStream is, File outputFile) - { - - try { - outputFile.createNewFile(); - DataOutputStream out = new DataOutputStream(new FileOutputStream(outputFile)); - DataInputStream in = new DataInputStream(is); - - int b = -1; - byte[] data = new byte[1024]; - - while ((b = in.read(data)) != -1) { - out.write(data); - } - - if (b == -1); //rejoice - - // - out.flush(); - out.close(); - in.close(); - // chmod? - - - - } catch (IOException ex) { - Log.e("ffmpeg", "error copying binary", ex); - } - - } - - - - /** - * Check if this is an ARMv6 device - * @return true if this is ARMv6 - */ - private static boolean isARMv6() { - if (isARMv6 == -1) { - BufferedReader r = null; - try { - isARMv6 = 0; - r = new BufferedReader(new FileReader("/proc/cpuinfo")); - for (String line = r.readLine(); line != null; line = r.readLine()) { - if (line.startsWith("Processor") && line.contains("ARMv6")) { - isARMv6 = 1; - break; - } else if (line.startsWith("CPU architecture") && (line.contains("6TE") || line.contains("5TE"))) { - isARMv6 = 1; - break; - } - } - } catch (Exception ex) { - } finally { - if (r != null) try {r.close();} catch (Exception ex) {} - } - } - return (isARMv6 == 1); - } - - /** - * 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, boolean isZipd) throws IOException, InterruptedException - { - final String abspath = file.getCanonicalPath(); - // Write the iptables binary - final FileOutputStream out = new FileOutputStream(file); - InputStream is = ctx.getResources().openRawResource(resid); - - if (isZipd) - { - ZipInputStream zis = new ZipInputStream(is); - ZipEntry ze = zis.getNextEntry(); - is = zis; - } - - 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(); - } - - -} diff --git a/src/org/ffmpeg/android/MediaDesc.java b/src/org/ffmpeg/android/Clip.java similarity index 59% rename from src/org/ffmpeg/android/MediaDesc.java rename to src/org/ffmpeg/android/Clip.java index cef9503..413ec5d 100644 --- a/src/org/ffmpeg/android/MediaDesc.java +++ b/src/org/ffmpeg/android/Clip.java @@ -1,6 +1,6 @@ package org.ffmpeg.android; -public class MediaDesc implements Cloneable +public class Clip implements Cloneable { public int width = -1; @@ -23,7 +23,7 @@ public class MediaDesc implements Cloneable public String mimeType; public String startTime; //00:00:00 or seconds format - public String duration; //00:00:00 or seconds format + public double duration = -1; //00:00:00 or seconds format public String videoFilter; public String audioFilter; @@ -32,20 +32,39 @@ public class MediaDesc implements Cloneable public String aspect; public int passCount = 1; //default - public MediaDesc clone () throws CloneNotSupportedException + public Clip () { - return (MediaDesc)super.clone(); + + } + + public Clip (String path) + { + this.path = path; + } + + public Clip clone () throws CloneNotSupportedException + { + return (Clip)super.clone(); } public boolean isImage() { - return mimeType.startsWith("image"); + if (mimeType != null) + return mimeType.startsWith("image"); + else + return false; } public boolean isVideo() { - return mimeType.startsWith("video"); + if (mimeType != null) + return mimeType.startsWith("video"); + else + return false; } public boolean isAudio() { - return mimeType.startsWith("audio"); + if (mimeType != null) + return mimeType.startsWith("audio"); + else + return false; } } diff --git a/src/org/ffmpeg/android/FfmpegController.java b/src/org/ffmpeg/android/FfmpegController.java index c37c152..0731be6 100644 --- a/src/org/ffmpeg/android/FfmpegController.java +++ b/src/org/ffmpeg/android/FfmpegController.java @@ -10,8 +10,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -20,61 +18,108 @@ import org.ffmpeg.android.ShellUtils.ShellCallback; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Bitmap; +import android.media.MediaMetadataRetriever; import android.util.Log; public class FfmpegController { - File fileBinDir; - Context mContext; - private String ffmpegBin; + + private String mFfmpegBin; private final static String TAG = "FFMPEG"; private File mFileTemp; - private String mCmdCat; + private String mCmdCat = "sh cat"; - public FfmpegController(Context context, File fileTemp) throws FileNotFoundException, IOException { - mContext = context; + public FfmpegController(Context context, File fileTemp) throws FileNotFoundException, IOException { mFileTemp = fileTemp; - checkBinary (); - + installBinaries(context, false); } - private void checkBinary () throws FileNotFoundException, IOException + public void installBinaries(Context context, boolean overwrite) { - fileBinDir = mContext.getDir("bin",0); - - long assetLength = mContext.getResources().openRawResourceFd(R.raw.ffmpeg).getLength(); - File fileBin = new File(fileBinDir,"ffmpeg"); - - if (!fileBin.exists() || fileBin.length() != assetLength) - { - BinaryInstaller bi = new BinaryInstaller(mContext,fileBinDir); - bi.installFromRaw(); + 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 (f.exists()) { + f.delete(); + } + copyRawFile(ctx, resId, f, "0755"); + return f.getCanonicalPath(); + } catch (Exception e) { + Log.e(TAG, "installBinary failed: " + e.getLocalizedMessage()); + return null; } - - ffmpegBin = fileBin.getCanonicalPath(); - - mCmdCat = "sh cat"; - } + /** + * 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 { - String ffmpegBin = new File(fileBinDir,"ffmpeg").getCanonicalPath(); - Runtime.getRuntime().exec("chmod 777 " +ffmpegBin); - + 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, fileBinDir); + 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); @@ -86,25 +131,22 @@ private int execProcess(List cmds, ShellCallback sc, File fileExec) thro cmdlog.append(' '); } - Log.d(TAG,cmdlog.toString()); + sc.shellOut(cmdlog.toString()); - pb.redirectErrorStream(true); - // pb.redirectErrorStream(true); - Process process = pb.start(); + //pb.redirectErrorStream(true); + + Process process = pb.start(); - - // any error message? - /* - StreamGobbler errorGobbler = new - StreamGobbler(process.getErrorStream(), "ERROR", sc); - */ - + + // 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(); + errorGobbler.start(); outputGobbler.start(); int exitVal = process.waitFor(); @@ -112,19 +154,18 @@ private int execProcess(List cmds, ShellCallback sc, File fileExec) thro 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); - Log.d(TAG,cmd); - // pb.redirectErrorStream(true); Process process = pb.start(); @@ -153,56 +194,56 @@ private int execProcess(String cmd, ShellCallback sc, File fileExec) throws IOEx } - public class FFMPEGArg + public class Argument { String key; String value; - public static final String ARG_VIDEOCODEC = "-vcodec"; - public static final String ARG_AUDIOCODEC = "-acodec"; + public static final String VIDEOCODEC = "-vcodec"; + public static final String AUDIOCODEC = "-acodec"; - public static final String ARG_VIDEOBITSTREAMFILTER = "-vbsf"; - public static final String ARG_AUDIOBITSTREAMFILTER = "-absf"; + public static final String VIDEOBITSTREAMFILTER = "-vbsf"; + public static final String AUDIOBITSTREAMFILTER = "-absf"; - public static final String ARG_VERBOSITY = "-v"; - public static final String ARG_FILE_INPUT = "-i"; - public static final String ARG_SIZE = "-s"; - public static final String ARG_FRAMERATE = "-r"; - public static final String ARG_FORMAT = "-f"; - public static final String ARG_BITRATE_VIDEO = "-b:v"; + 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 ARG_BITRATE_AUDIO = "-b:a"; - public static final String ARG_CHANNELS_AUDIO = "-ac"; - public static final String ARG_FREQ_AUDIO = "-ar"; + 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 ARG_STARTTIME = "-ss"; - public static final String ARG_DURATION = "-t"; + public static final String STARTTIME = "-ss"; + public static final String DURATION = "-t"; } - public void processVideo(MediaDesc in, MediaDesc out, boolean enableExperimental, ShellCallback sc) throws Exception { + public void processVideo(Clip in, Clip out, boolean enableExperimental, ShellCallback sc) throws Exception { ArrayList cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); if (in.format != null) { - cmd.add(FFMPEGArg.ARG_FORMAT); + cmd.add(Argument.FORMAT); cmd.add(in.format); } if (in.videoCodec != null) { - cmd.add(FFMPEGArg.ARG_VIDEOCODEC); + cmd.add(Argument.VIDEOCODEC); cmd.add(in.videoCodec); } if (in.audioCodec != null) { - cmd.add(FFMPEGArg.ARG_AUDIOCODEC); + cmd.add(Argument.AUDIOCODEC); cmd.add(in.audioCodec); } @@ -211,31 +252,31 @@ public void processVideo(MediaDesc in, MediaDesc out, boolean enableExperimental if (out.videoBitrate > 0) { - cmd.add(FFMPEGArg.ARG_BITRATE_VIDEO); + cmd.add(Argument.BITRATE_VIDEO); cmd.add(out.videoBitrate + "k"); } if (out.width > 0) { - cmd.add(FFMPEGArg.ARG_SIZE); + cmd.add(Argument.SIZE); cmd.add(out.width + "x" + out.height); } if (out.videoFps != null) { - cmd.add(FFMPEGArg.ARG_FRAMERATE); + cmd.add(Argument.FRAMERATE); cmd.add(out.videoFps); } if (out.videoCodec != null) { - cmd.add(FFMPEGArg.ARG_VIDEOCODEC); + cmd.add(Argument.VIDEOCODEC); cmd.add(out.videoCodec); } if (out.videoBitStreamFilter != null) { - cmd.add(FFMPEGArg.ARG_VIDEOBITSTREAMFILTER); + cmd.add(Argument.VIDEOBITSTREAMFILTER); cmd.add(out.videoBitStreamFilter); } @@ -248,24 +289,24 @@ public void processVideo(MediaDesc in, MediaDesc out, boolean enableExperimental if (out.audioCodec != null) { - cmd.add(FFMPEGArg.ARG_AUDIOCODEC); + cmd.add(Argument.AUDIOCODEC); cmd.add(out.audioCodec); } if (out.audioBitStreamFilter != null) { - cmd.add(FFMPEGArg.ARG_AUDIOBITSTREAMFILTER); + cmd.add(Argument.AUDIOBITSTREAMFILTER); cmd.add(out.audioBitStreamFilter); } if (out.audioChannels > 0) { - cmd.add(FFMPEGArg.ARG_CHANNELS_AUDIO); + cmd.add(Argument.CHANNELS_AUDIO); cmd.add(out.audioChannels+""); } if (out.audioBitrate > 0) { - cmd.add(FFMPEGArg.ARG_BITRATE_AUDIO); + cmd.add(Argument.BITRATE_AUDIO); cmd.add(out.audioBitrate + "k"); } @@ -274,6 +315,12 @@ public void processVideo(MediaDesc in, MediaDesc out, boolean enableExperimental cmd.add("-f"); cmd.add(out.format); } + + if (out.duration > 0) + { + cmd.add(Argument.DURATION); + cmd.add(out.duration); + } if (enableExperimental) { @@ -288,7 +335,7 @@ public void processVideo(MediaDesc in, MediaDesc out, boolean enableExperimental } - public MediaDesc createSlideshowFromImagesAndAudio (ArrayList images, MediaDesc audio, MediaDesc out, int durationPerSlide, ShellCallback sc) throws Exception + public Clip createSlideshowFromImagesAndAudio (ArrayList images, Clip audio, Clip out, int durationPerSlide, ShellCallback sc) throws Exception { final String imageBasePath = new File(mFileTemp,"image-").getCanonicalPath(); @@ -301,10 +348,10 @@ public MediaDesc createSlideshowFromImagesAndAudio (ArrayList images, String newImagePath = null; int imageCounter = 0; - MediaDesc imageCover = images.get(0); //add the first image twice + Clip imageCover = images.get(0); //add the first image twice cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); @@ -321,10 +368,10 @@ public MediaDesc createSlideshowFromImagesAndAudio (ArrayList images, execFFMPEG(cmd, sc); - for (MediaDesc image : images) + for (Clip image : images) { cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); @@ -347,7 +394,7 @@ public MediaDesc createSlideshowFromImagesAndAudio (ArrayList images, //then combine them cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-loop"); @@ -374,7 +421,7 @@ public MediaDesc createSlideshowFromImagesAndAudio (ArrayList images, //now combine and encode cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); @@ -391,10 +438,10 @@ public MediaDesc createSlideshowFromImagesAndAudio (ArrayList images, cmd.add("-map"); cmd.add("1:0"); - cmd.add(FFMPEGArg.ARG_AUDIOCODEC); + cmd.add(Argument.AUDIOCODEC); cmd.add("aac"); - cmd.add(FFMPEGArg.ARG_BITRATE_AUDIO); + cmd.add(Argument.BITRATE_AUDIO); cmd.add("128k"); } @@ -402,7 +449,7 @@ public MediaDesc createSlideshowFromImagesAndAudio (ArrayList images, cmd.add("-strict"); cmd.add("-2");//experimental - cmd.add(FFMPEGArg.ARG_VIDEOCODEC); + cmd.add(Argument.VIDEOCODEC); if (out.videoCodec != null) @@ -412,7 +459,7 @@ public MediaDesc createSlideshowFromImagesAndAudio (ArrayList images, if (out.videoBitrate != -1) { - cmd.add(FFMPEGArg.ARG_BITRATE_VIDEO); + cmd.add(Argument.BITRATE_VIDEO); cmd.add(out.videoBitrate + "k"); } @@ -444,11 +491,11 @@ public MediaDesc createSlideshowFromImagesAndAudio (ArrayList images, out.avi – create this output file. Change it as you like, for example using another container like MP4. */ - public MediaDesc combineAudioAndVideo (MediaDesc videoIn, MediaDesc audioIn, MediaDesc out, ShellCallback sc) throws Exception + public Clip combineAudioAndVideo (Clip videoIn, Clip audioIn, Clip out, ShellCallback sc) throws Exception { ArrayList cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); @@ -461,7 +508,7 @@ public MediaDesc combineAudioAndVideo (MediaDesc videoIn, MediaDesc audioIn, Med cmd.add("-strict"); cmd.add("-2");//experimental - cmd.add(FFMPEGArg.ARG_AUDIOCODEC); + cmd.add(Argument.AUDIOCODEC); if (out.audioCodec != null) cmd.add(out.audioCodec); else @@ -470,7 +517,7 @@ public MediaDesc combineAudioAndVideo (MediaDesc videoIn, MediaDesc audioIn, Med } - cmd.add(FFMPEGArg.ARG_VIDEOCODEC); + cmd.add(Argument.VIDEOCODEC); if (out.videoCodec != null) cmd.add(out.videoCodec); else @@ -480,19 +527,19 @@ public MediaDesc combineAudioAndVideo (MediaDesc videoIn, MediaDesc audioIn, Med if (out.videoBitrate != -1) { - cmd.add(FFMPEGArg.ARG_BITRATE_VIDEO); + cmd.add(Argument.BITRATE_VIDEO); cmd.add(out.videoBitrate + "k"); } if (out.videoFps != null) { - cmd.add(FFMPEGArg.ARG_FRAMERATE); + cmd.add(Argument.FRAMERATE); cmd.add(out.videoFps); } if (out.audioBitrate != -1) { - cmd.add(FFMPEGArg.ARG_BITRATE_AUDIO); + cmd.add(Argument.BITRATE_AUDIO); cmd.add(out.audioBitrate + "k"); } cmd.add("-y"); @@ -502,7 +549,7 @@ public MediaDesc combineAudioAndVideo (MediaDesc videoIn, MediaDesc audioIn, Med if (out.width > 0) { - cmd.add(FFMPEGArg.ARG_SIZE); + cmd.add(Argument.SIZE); cmd.add(out.width + "x" + out.height); } @@ -523,9 +570,9 @@ public MediaDesc combineAudioAndVideo (MediaDesc videoIn, MediaDesc audioIn, Med } - public MediaDesc convertImageToMP4 (MediaDesc mediaIn, int duration, String outPath, ShellCallback sc) throws Exception + public Clip convertImageToMP4 (Clip mediaIn, int duration, String outPath, ShellCallback sc) throws Exception { - MediaDesc result = new MediaDesc (); + 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 @@ -533,7 +580,7 @@ public MediaDesc convertImageToMP4 (MediaDesc mediaIn, int duration, String outP cmd = new ArrayList(); //convert images to MP4 - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-loop"); @@ -542,7 +589,7 @@ public MediaDesc convertImageToMP4 (MediaDesc mediaIn, int duration, String outP cmd.add("-i"); cmd.add(new File(mediaIn.path).getCanonicalPath()); - cmd.add(FFMPEGArg.ARG_FRAMERATE); + cmd.add(Argument.FRAMERATE); cmd.add(mediaIn.videoFps); cmd.add("-t"); @@ -553,7 +600,7 @@ public MediaDesc convertImageToMP4 (MediaDesc mediaIn, int duration, String outP if (mediaIn.width != -1) { - cmd.add(FFMPEGArg.ARG_SIZE); + cmd.add(Argument.SIZE); cmd.add(mediaIn.width + "x" + mediaIn.height); // cmd.add("-vf"); // cmd.add("\"scale=-1:" + mediaIn.width + "\""); @@ -561,7 +608,7 @@ public MediaDesc convertImageToMP4 (MediaDesc mediaIn, int duration, String outP if (mediaIn.videoBitrate != -1) { - cmd.add(FFMPEGArg.ARG_BITRATE_VIDEO); + cmd.add(Argument.BITRATE_VIDEO); cmd.add(mediaIn.videoBitrate + ""); } @@ -585,30 +632,50 @@ public MediaDesc convertImageToMP4 (MediaDesc mediaIn, int duration, String outP //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 MediaDesc convertToMP4Stream (MediaDesc mediaIn, String startTime, String duration, String outPath, ShellCallback sc) throws Exception + public Clip convertToMP4Stream (Clip mediaIn, String startTime, double duration, String outPath, ShellCallback sc) throws Exception { ArrayList cmd = new ArrayList(); - MediaDesc mediaOut = new MediaDesc(); + Clip mediaOut = new Clip(); mediaOut.path = outPath; String mediaPath = new File(mediaIn.path).getCanonicalPath(); cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); if (startTime != null) { - cmd.add(FFMPEGArg.ARG_STARTTIME); + cmd.add(Argument.STARTTIME); cmd.add(startTime); } - if (duration != null) + if (duration != -1) { - cmd.add(FFMPEGArg.ARG_DURATION); - cmd.add(duration); + 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"); @@ -622,7 +689,7 @@ public MediaDesc convertToMP4Stream (MediaDesc mediaIn, String startTime, String cmd.add("-an"); - //cmd.add(FFMPEGArg.ARG_VIDEOBITSTREAMFILTER); + //cmd.add(Argument.VIDEOBITSTREAMFILTER); cmd.add("-bsf:v"); cmd.add("h264_mp4toannexb"); @@ -637,11 +704,11 @@ public MediaDesc convertToMP4Stream (MediaDesc mediaIn, String startTime, String } - public MediaDesc convertToWaveAudio (MediaDesc mediaIn, String outPath, int sampleRate, int channels, ShellCallback sc) throws Exception + public Clip convertToWaveAudio (Clip mediaIn, String outPath, int sampleRate, int channels, ShellCallback sc) throws Exception { ArrayList cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); if (mediaIn.startTime != null) @@ -650,10 +717,10 @@ public MediaDesc convertToWaveAudio (MediaDesc mediaIn, String outPath, int samp cmd.add(mediaIn.startTime); } - if (mediaIn.duration != null) + if (mediaIn.duration != -1) { cmd.add("-t"); - cmd.add(mediaIn.duration); + cmd.add(String.format(Locale.US,"%f",mediaIn.duration)); } cmd.add("-i"); @@ -668,7 +735,7 @@ public MediaDesc convertToWaveAudio (MediaDesc mediaIn, String outPath, int samp cmd.add("-vn"); - MediaDesc mediaOut = new MediaDesc(); + Clip mediaOut = new Clip(); File fileOut = new File(outPath); mediaOut.path = fileOut.getCanonicalPath(); @@ -680,11 +747,11 @@ public MediaDesc convertToWaveAudio (MediaDesc mediaIn, String outPath, int samp return mediaOut; } - public MediaDesc convertTo3GPAudio (MediaDesc mediaIn, MediaDesc mediaOut, ShellCallback sc) throws Exception + public Clip convertTo3GPAudio (Clip mediaIn, Clip mediaOut, ShellCallback sc) throws Exception { ArrayList cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); cmd.add(new File(mediaIn.path).getCanonicalPath()); @@ -695,10 +762,11 @@ public MediaDesc convertTo3GPAudio (MediaDesc mediaIn, MediaDesc mediaOut, Shell cmd.add(mediaIn.startTime); } - if (mediaIn.duration != null) + if (mediaIn.duration != -1) { cmd.add("-t"); - cmd.add(mediaIn.duration); + cmd.add(String.format(Locale.US,"%f",mediaIn.duration)); + } cmd.add("-vn"); @@ -727,11 +795,11 @@ public MediaDesc convertTo3GPAudio (MediaDesc mediaIn, MediaDesc mediaOut, Shell return mediaOut; } - public MediaDesc convert (MediaDesc mediaIn, String outPath, ShellCallback sc) throws Exception + public Clip convert (Clip mediaIn, String outPath, ShellCallback sc) throws Exception { ArrayList cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); cmd.add(new File(mediaIn.path).getCanonicalPath()); @@ -742,14 +810,15 @@ public MediaDesc convert (MediaDesc mediaIn, String outPath, ShellCallback sc) t cmd.add(mediaIn.startTime); } - if (mediaIn.duration != null) + if (mediaIn.duration != -1) { cmd.add("-t"); - cmd.add(mediaIn.duration); + cmd.add(String.format(Locale.US,"%f",mediaIn.duration)); + } - MediaDesc mediaOut = new MediaDesc(); + Clip mediaOut = new Clip(); File fileOut = new File(outPath); @@ -763,11 +832,11 @@ public MediaDesc convert (MediaDesc mediaIn, String outPath, ShellCallback sc) t return mediaOut; } - public MediaDesc convertToMPEG (MediaDesc mediaIn, String outPath, ShellCallback sc) throws Exception + public Clip convertToMPEG (Clip mediaIn, String outPath, ShellCallback sc) throws Exception { ArrayList cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); cmd.add(new File(mediaIn.path).getCanonicalPath()); @@ -778,10 +847,11 @@ public MediaDesc convertToMPEG (MediaDesc mediaIn, String outPath, ShellCallback cmd.add(mediaIn.startTime); } - if (mediaIn.duration != null) + if (mediaIn.duration != -1) { cmd.add("-t"); - cmd.add(mediaIn.duration); + cmd.add(String.format(Locale.US,"%f",mediaIn.duration)); + } @@ -792,7 +862,7 @@ public MediaDesc convertToMPEG (MediaDesc mediaIn, String outPath, ShellCallback cmd.add("-f"); cmd.add("mpeg"); - MediaDesc mediaOut = mediaIn.clone(); + Clip mediaOut = mediaIn.clone(); File fileOut = new File(outPath); @@ -805,14 +875,14 @@ public MediaDesc convertToMPEG (MediaDesc mediaIn, String outPath, ShellCallback return mediaOut; } - public void concatAndTrimFilesMPEG (ArrayList videos,MediaDesc out, boolean preConvert, ShellCallback sc) throws Exception + public void concatAndTrimFilesMPEG (ArrayList videos,Clip out, boolean preConvert, ShellCallback sc) throws Exception { int idx = 0; if (preConvert) { - for (MediaDesc mdesc : videos) + for (Clip mdesc : videos) { if (mdesc.path == null) continue; @@ -820,7 +890,7 @@ public void concatAndTrimFilesMPEG (ArrayList videos,MediaDesc out, b //extract MPG video ArrayList cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); cmd.add(mdesc.path); @@ -831,10 +901,11 @@ public void concatAndTrimFilesMPEG (ArrayList videos,MediaDesc out, b cmd.add(mdesc.startTime); } - if (mdesc.duration != null) + if (mdesc.duration != -1) { cmd.add("-t"); - cmd.add(mdesc.duration); + cmd.add(String.format(Locale.US,"%f",mdesc.duration)); + } /* @@ -868,7 +939,7 @@ public void concatAndTrimFilesMPEG (ArrayList videos,MediaDesc out, b idx = 0; - for (MediaDesc vdesc : videos) + for (Clip vdesc : videos) { if (vdesc.path == null) continue; @@ -884,13 +955,11 @@ public void concatAndTrimFilesMPEG (ArrayList videos,MediaDesc out, b cmdRun.append("> "); cmdRun.append(mCatPath); - Log.d(TAG,"cat cmd: " + cmdRun.toString()); - String[] cmds = {"sh","-c",cmdRun.toString()}; Runtime.getRuntime().exec(cmds).waitFor(); - MediaDesc mInCat = new MediaDesc(); + Clip mInCat = new Clip(); mInCat.path = mCatPath; processVideo(mInCat, out, false, sc); @@ -898,13 +967,13 @@ public void concatAndTrimFilesMPEG (ArrayList videos,MediaDesc out, b out.path = mCatPath; } - public void extractAudio (MediaDesc mdesc, String audioFormat, File audioOutPath, ShellCallback sc) throws IOException, InterruptedException + 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(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); cmd.add(new File(mdesc.path).getCanonicalPath()); @@ -917,10 +986,11 @@ public void extractAudio (MediaDesc mdesc, String audioFormat, File audioOutPath cmd.add(mdesc.startTime); } - if (mdesc.duration != null) + if (mdesc.duration != -1) { cmd.add("-t"); - cmd.add(mdesc.duration); + cmd.add(String.format(Locale.US,"%f",mdesc.duration)); + } cmd.add("-f"); @@ -933,7 +1003,7 @@ public void extractAudio (MediaDesc mdesc, String audioFormat, File audioOutPath } - class FileMover { + private class FileMover { InputStream inputStream; File destination; @@ -962,16 +1032,14 @@ public int killVideoProcessor (boolean asRoot, boolean waitFor) throws IOExcepti { int killDelayMs = 300; - String ffmpegBin = new File(mContext.getDir("bin",0),"ffmpeg").getCanonicalPath(); - int result = -1; int procId = -1; - while ((procId = ShellUtils.findProcessId(ffmpegBin)) != -1) + while ((procId = ShellUtils.findProcessId(mFfmpegBin)) != -1) { - Log.d(TAG, "Found PID=" + procId + " - killing now..."); + // Log.d(TAG, "Found PID=" + procId + " - killing now..."); String[] cmd = { ShellUtils.SHELL_CMD_KILL + ' ' + procId + "" }; @@ -982,8 +1050,6 @@ public int killVideoProcessor (boolean asRoot, boolean waitFor) throws IOExcepti @Override public void shellOut(String msg) { - Log.d(TAG,"Killing ffmpeg:" + msg); - } @Override @@ -1001,29 +1067,30 @@ public void processComplete(int exitValue) { } - public MediaDesc trim (MediaDesc mediaIn, boolean withSound, String outPath, ShellCallback sc) throws Exception + public Clip trim (Clip mediaIn, boolean withSound, String outPath, ShellCallback sc) throws Exception { ArrayList cmd = new ArrayList(); - MediaDesc mediaOut = new MediaDesc(); + Clip mediaOut = new Clip(); String mediaPath = mediaIn.path; cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); if (mediaIn.startTime != null) { - cmd.add(FFMPEGArg.ARG_STARTTIME); + cmd.add(Argument.STARTTIME); cmd.add(mediaIn.startTime); } - if (mediaIn.duration != null) + if (mediaIn.duration != -1) { - cmd.add(FFMPEGArg.ARG_DURATION); - cmd.add(mediaIn.duration); + cmd.add("-t"); + cmd.add(String.format(Locale.US,"%f",mediaIn.duration)); + } cmd.add("-i"); @@ -1044,7 +1111,7 @@ public MediaDesc trim (MediaDesc mediaIn, boolean withSound, String outPath, She return mediaOut; } - public void concatAndTrimFilesMP4Stream (ArrayList videos,MediaDesc out, boolean preconvertClipsToMP4, boolean useCatCmd, ShellCallback sc) throws Exception + public void concatAndTrimFilesMP4Stream (ArrayList videos,Clip out, boolean preconvertClipsToMP4, boolean useCatCmd, ShellCallback sc) throws Exception { @@ -1055,10 +1122,10 @@ public void concatAndTrimFilesMP4Stream (ArrayList videos,MediaDesc o int tmpIdx = 0; - for (MediaDesc vdesc : videos) + for (Clip vdesc : videos) { - MediaDesc mdOut = null; + Clip mdOut = null; if (preconvertClipsToMP4) { @@ -1074,7 +1141,7 @@ public void concatAndTrimFilesMP4Stream (ArrayList videos,MediaDesc o if (fileOut.exists()) fileOut.delete(); - mdOut = convertToMP4Stream(mdOut,null,null,fileOut.getCanonicalPath(), sc); + mdOut = convertToMP4Stream(mdOut,null,-1,fileOut.getCanonicalPath(), sc); } else { @@ -1114,15 +1181,13 @@ public void concatAndTrimFilesMP4Stream (ArrayList videos,MediaDesc o cmdBuff.append(fileExportOut.getCanonicalPath() + ".ts"); - Log.d(TAG,"concat: " + cmdBuff.toString()); - Runtime.getRuntime().exec(cmdBuff.toString()); ArrayList cmd = new ArrayList(); cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); @@ -1145,7 +1210,7 @@ public void concatAndTrimFilesMP4Stream (ArrayList videos,MediaDesc o //ffmpeg -i "concat:intermediate1.ts|intermediate2.ts" -c copy -bsf:a aac_adtstoasc output.mp4 ArrayList cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); cmd.add("concat:" + sbCat.toString()); @@ -1169,13 +1234,13 @@ public void concatAndTrimFilesMP4Stream (ArrayList videos,MediaDesc o } - public MediaDesc getInfo (MediaDesc in) throws IOException, InterruptedException + public Clip getInfo (Clip in) throws IOException, InterruptedException { ArrayList cmd = new ArrayList(); cmd = new ArrayList(); - cmd.add(ffmpegBin); + cmd.add(mFfmpegBin); cmd.add("-y"); cmd.add("-i"); @@ -1184,12 +1249,9 @@ public MediaDesc getInfo (MediaDesc in) throws IOException, InterruptedException InfoParser ip = new InfoParser(in); execFFMPEG(cmd,ip, null); - while (in.duration == null) - { - try{Thread.sleep(100);} - catch (Exception e){} - - } + try{Thread.sleep(200);} + catch (Exception e){} + return in; @@ -1197,10 +1259,10 @@ public MediaDesc getInfo (MediaDesc in) throws IOException, InterruptedException private class InfoParser implements ShellCallback { - private MediaDesc mMedia; + private Clip mMedia; private int retValue; - public InfoParser (MediaDesc media) + public InfoParser (Clip media) { mMedia = media; } @@ -1210,7 +1272,6 @@ public void shellOut(String shellLine) { if (shellLine.contains("Duration:")) { - Log.d(TAG,"Parsing Length: " + shellLine); // Duration: 00:01:01.75, start: 0.000000, bitrate: 8184 kb/s String[] timecode = shellLine.split(",")[0].split(":"); @@ -1220,12 +1281,32 @@ public void shellOut(String shellLine) { duration = Double.parseDouble(timecode[1].trim())*60*60; //hours duration += Double.parseDouble(timecode[2].trim())*60; //minutes - duration += Double.parseDouble(timecode[3].trim()); //minutes + duration += Double.parseDouble(timecode[3].trim()); //seconds - mMedia.duration = String.format(Locale.US,"%.2f",duration); + 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 @@ -1240,7 +1321,7 @@ public void processComplete(int exitValue) { } } - class StreamGobbler extends Thread + private class StreamGobbler extends Thread { InputStream is; String type; @@ -1266,10 +1347,26 @@ public void run() } catch (IOException ioe) { - Log.e(TAG,"error reading shell slog",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) { + } + } + } } /* diff --git a/src/org/ffmpeg/android/MediaUtils.java b/src/org/ffmpeg/android/MediaUtils.java deleted file mode 100644 index c0bf004..0000000 --- a/src/org/ffmpeg/android/MediaUtils.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.ffmpeg.android; - -import android.graphics.Bitmap; -import android.media.MediaMetadataRetriever; -import android.util.Log; - -public class MediaUtils { - - public static Bitmap getVideoFrame(String videoPath,long frameTime) { - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - - try { - retriever.setDataSource(videoPath); - return retriever.getFrameAtTime(frameTime, MediaMetadataRetriever.OPTION_CLOSEST); - } catch (IllegalArgumentException ex) { - Log.w("FFMPEG.MediaUtils", "illegal argument exception"); - - } catch (RuntimeException ex) { - Log.w("FFMPEG.MediaUtils", "error getting video frame"); - } finally { - try { - retriever.release(); - } catch (RuntimeException ex) { - } - } - return null; - } - -} diff --git a/src/org/ffmpeg/android/filters/DrawBoxVideoFilter.java b/src/org/ffmpeg/android/filters/DrawBoxVideoFilter.java index 77d5b62..5d74a0a 100644 --- a/src/org/ffmpeg/android/filters/DrawBoxVideoFilter.java +++ b/src/org/ffmpeg/android/filters/DrawBoxVideoFilter.java @@ -18,16 +18,14 @@ public class DrawBoxVideoFilter extends OverlayVideoFilter { public int width; public int height; public String color; - private Context context; - public DrawBoxVideoFilter (int x, int y, int width, int height, int alpha, String color, Context context) + 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; - this.context = context; if( alpha < 0 || alpha > 255 ) { throw new IllegalArgumentException("Alpha must be an integer betweeen 0 and 255"); @@ -43,19 +41,13 @@ public DrawBoxVideoFilter (int x, int y, int width, int height, int alpha, Strin Canvas canvas = new Canvas(temp_box); canvas.drawBitmap(bitmap, 0, 0, paint); - try { - File outputFile; - outputFile = File.createTempFile("box_"+width+height+color, ".png", new File("/sdcard")/*context.getCacheDir()*/); - FileOutputStream os = new FileOutputStream(outputFile); - temp_box.compress(Bitmap.CompressFormat.PNG, 100, os); - overlayFile = outputFile; - xParam = Integer.toString(x); - yParam = Integer.toString(y); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - + 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/src/org/ffmpeg/android/test/ConcatTest.java b/src/org/ffmpeg/android/test/ConcatTest.java index 66e33ed..6b69962 100644 --- a/src/org/ffmpeg/android/test/ConcatTest.java +++ b/src/org/ffmpeg/android/test/ConcatTest.java @@ -1,5 +1,66 @@ 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/src/org/ffmpeg/android/test/CrossfadeTest.java b/src/org/ffmpeg/android/test/CrossfadeTest.java new file mode 100644 index 0000000..5b11505 --- /dev/null +++ b/src/org/ffmpeg/android/test/CrossfadeTest.java @@ -0,0 +1,204 @@ +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/src/org/ffmpeg/android/test/FilterTest.java b/src/org/ffmpeg/android/test/FilterTest.java index dee1072..93b7711 100644 --- a/src/org/ffmpeg/android/test/FilterTest.java +++ b/src/org/ffmpeg/android/test/FilterTest.java @@ -1,6 +1,7 @@ package org.ffmpeg.android.test; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import org.ffmpeg.android.filters.CropVideoFilter; @@ -16,24 +17,27 @@ public class FilterTest { - public void test (Context context) + 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",context); + DrawBoxVideoFilter vf = new DrawBoxVideoFilter(0,height-lowerThird,width,lowerThird,100,"blue",fileDir); DrawTextVideoFilter vfTitle = - new DrawTextVideoFilter("Rikshaw Rock and Roll", + new DrawTextVideoFilter(title, DrawTextVideoFilter.X_CENTERED,DrawTextVideoFilter.Y_CENTERED, - "green", + textColor, 38, - new File("/system/fonts/DroidSerif-Regular.ttf"), + fileFont, true, - "yellow", - "0.5"); + boxColor, + opacity); float fps = 29.97f; int fadeTime = (int)(fps*3); @@ -56,5 +60,8 @@ public void test (Context context) listFilters.add(vfFadeIn); listFilters.add(vfFadeOut); + + + fileDir.deleteOnExit(); } } diff --git a/src/org/ffmpeg/android/test/MixTest.java b/src/org/ffmpeg/android/test/MixTest.java new file mode 100644 index 0000000..4472471 --- /dev/null +++ b/src/org/ffmpeg/android/test/MixTest.java @@ -0,0 +1,41 @@ +package org.ffmpeg.android.test; + +import java.io.File; + +import org.ffmpeg.android.Clip; +import org.ffmpeg.android.FfmpegController; +import org.ffmpeg.android.ShellUtils; + +public class MixTest { + + public static void test (String fileTmpPath, String videoClipPath, String audioClipPath, Clip clipOut) throws Exception + { + File fileTmp = new File(fileTmpPath); + File fileAppRoot = new File(""); + + FfmpegController fc = new FfmpegController(null, fileTmp); + + Clip clipVideo = new Clip(videoClipPath); + //fc.getInfo(clipVideo); + + Clip clipAudio = new Clip(audioClipPath); + //fc.getInfo(clipAudio); + + 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("concat non-zero exit: " + exitValue); + } + }); + + } + +} diff --git a/src/org/ffmpeg/android/test/Tests.java b/src/org/ffmpeg/android/test/Tests.java new file mode 100644 index 0000000..a714fcf --- /dev/null +++ b/src/org/ffmpeg/android/test/Tests.java @@ -0,0 +1,81 @@ +package org.ffmpeg.android.test; + +import java.io.File; + +import org.ffmpeg.android.Clip; + +public class Tests { + + /** + * @param args + */ + public static void main(String[] args) throws Exception { + + + + String[] testpaths = { + // "/home/n8fr8/Desktop/smcampmovie", + // "/home/n8fr8/Desktop/smcampmovie2", + "/home/n8fr8/Desktop/sm3" + + }; + + int idx = -1; + + double fadeLen = 1; + + for (String testpath : testpaths) + { + idx++; + + System.out.println ("************************************"); + System.out.println ("CONCAT TEST: " + testpath); + + File fileVideoOutput = new File("/tmp/test" + idx + ".mp4"); + fileVideoOutput.delete(); + + ConcatTest.test(testpath, "/tmp", fileVideoOutput.getCanonicalPath(), fadeLen); + + if (!fileVideoOutput.exists()) + { + System.out.println("FAIL!! > output file did not get created: " + fileVideoOutput.getCanonicalPath()); + continue; + } + else + System.out.println("SUCCESS!! > " + fileVideoOutput.getCanonicalPath()); + + System.out.println ("************************************"); + System.out.println ("CROSSFADE TEST: " + testpath); + + File fileAudioOutput = new File("/tmp/test" + idx + ".3gp"); + fileAudioOutput.delete(); + CrossfadeTest.test(testpath, "/tmp", fileAudioOutput.getCanonicalPath(),fadeLen); + if (!fileAudioOutput.exists()) + { + System.out.println("FAIL!! > output file did not get created: " + fileAudioOutput.getCanonicalPath()); + continue; + } + else + System.out.println("SUCCESS!! > " + fileAudioOutput.getCanonicalPath()); + + System.out.println ("************************************"); + System.out.println ("MIX TEST: " + testpath); + + File fileMix = new File("/tmp/test" + idx + "mix.mp4"); + fileMix.delete(); + Clip clipMixOut = new Clip(fileMix.getCanonicalPath()); + MixTest.test("/tmp", fileVideoOutput.getCanonicalPath(), fileAudioOutput.getCanonicalPath(), clipMixOut); + if (!fileMix.exists()) + System.out.println("FAIL!! > output file did not get created: " + fileMix.getCanonicalPath()); + else + System.out.println("SUCCESS!! > " + fileMix.getCanonicalPath()); + + + } + + System.out.println("**********************"); + System.out.println("*******FIN**********"); + + } + +}