package org.witness.informacam.informa.embed; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringBufferInputStream; import org.apache.commons.io.IOUtils; import org.ffmpeg.android.FfmpegController; import org.ffmpeg.android.ShellUtils; import org.witness.informacam.InformaCam; import org.witness.informacam.models.media.IAsset; import org.witness.informacam.models.media.IMedia; import org.witness.informacam.models.transport.ITransportStub; import org.witness.informacam.utils.MediaHasher; import org.witness.informacam.utils.Constants.App.Storage; import org.witness.informacam.utils.Constants.App.Storage.Type; import org.witness.informacam.utils.Constants.Ffmpeg; import org.witness.informacam.utils.Constants.Logger; import org.witness.informacam.utils.Constants.MetadataEmbededListener; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; public class VideoConstructor { java.io.File fileBinDir; FfmpegController ffmpegCtrl; String ffmpegBinPath; IMedia media; IAsset destinationAsset, sourceAsset, metadata; ITransportStub connection; private final static String LOG = Ffmpeg.LOG; //private boolean intendedForIOCipher = false; public VideoConstructor(Context context) throws FileNotFoundException, IOException { fileBinDir = context.getDir("bin",Context.MODE_PRIVATE); ffmpegCtrl = new FfmpegController(context, context.getCacheDir()); ffmpegBinPath = ffmpegCtrl.getBinaryPath(); } public VideoConstructor(InformaCam informaCam, IMedia media, IAsset destinationAsset, ITransportStub connection) throws IOException { this(informaCam); this.media = media; this.destinationAsset = destinationAsset; this.connection = connection; sourceAsset = this.media.dcimEntry.fileAsset; metadata = media.getAsset(this.media.dcimEntry.name + ".j3m"); String metadataPath = metadata.path; String sourcePath = sourceAsset.path; int streamCount = 2; if (destinationAsset.source == Type.FILE_SYSTEM) { java.io.File fileDest = new java.io.File(destinationAsset.path); if (fileDest.exists()) fileDest.delete(); //delete a file if it is there else { fileDest.getParentFile().mkdirs(); } if(sourceAsset.source == Type.IOCIPHER) { String basePath = fileDest.getParentFile().getAbsolutePath(); // If the assets were in IOCIPHER, we have to save them to local storage. // unfortunately, Ffmpeg CLI works that way. metadataPath = metadata.copy(Type.IOCIPHER, Type.FILE_SYSTEM, basePath); sourcePath = sourceAsset.copy(Type.IOCIPHER, Type.FILE_SYSTEM, basePath); } constructVideo(streamCount,sourcePath,metadataPath,destinationAsset.path); } else if (destinationAsset.source == Type.IOCIPHER) { //FOR NOW, just set destination to source, so we don't need to make huge copies //should use JCodec here to make MKV file in java from source MP4 and metadata track //info.guardianproject.iocipher.FileInputStream fis = new info.guardianproject.iocipher.FileInputStream(new info.guardianproject.iocipher.File(sourceAsset.path)); //info.guardianproject.iocipher.FileOutputStream fos = new info.guardianproject.iocipher.FileOutputStream(new info.guardianproject.iocipher.File(destinationAsset.path)); // IOUtils.copyLarge(fis, fos); destinationAsset.path = sourceAsset.path; if(connection != null) { connection.setAsset(destinationAsset, "video/mp4", destinationAsset.source); ((MetadataEmbededListener) media).onMediaReadyForTransport(connection); } ((MetadataEmbededListener) media).onMetadataEmbeded(destinationAsset); } } private void constructVideo(int streamCount, String sourcePath, String metadataPath, String outPath) throws IOException { String[] ffmpegCommand = new String[] { ffmpegBinPath, "-y", "-i", sourcePath, "-attach", metadataPath, "-metadata:s:" + streamCount, "mimetype=\"text/plain\"", "-vcodec", "copy", "-acodec", "copy", outPath }; StringBuffer sb = new StringBuffer(); for(String f: ffmpegCommand) { sb.append(f + " "); } Log.d(LOG, "command to ffmpeg: " + sb.toString()); try { execProcess(ffmpegCommand, new ShellUtils.ShellCallback () { @Override public void shellOut(String shellLine) { Log.d(LOG, shellLine); } @Override public void processComplete(int exitValue) { Log.d(LOG, "ffmpeg process completed"); // if user wanted this encrypted, copy the destination asset /* if(intendedForIOCipher) { try { destinationAsset.copy(Type.FILE_SYSTEM, Type.IOCIPHER, media.rootFolder); storageType = Storage.Type.IOCIPHER; } catch (IOException e) { Logger.e(LOG, e); } java.io.File publicRoot = new java.io.File(IOUtility.buildPublicPath(new String[] { media.rootFolder })); informaCam.ioService.clear(publicRoot.getAbsolutePath(), Type.FILE_SYSTEM); }*/ if(connection != null) { connection.setAsset(destinationAsset, "video/mp4", destinationAsset.source); ((MetadataEmbededListener) media).onMediaReadyForTransport(connection); } ((MetadataEmbededListener) media).onMetadataEmbeded(destinationAsset); } },null); } catch (Exception e) { Logger.e(LOG, e); } } String newHash = null; public void testFFmpeg() { execProcess(new String[] {ffmpegBinPath, "-version"}, new ShellUtils.ShellCallback () { @Override public void shellOut(String shellLine) { Logger.d(LOG, shellLine); } @Override public void processComplete(int exitValue) { Logger.d(LOG, "DONE WITH: " + exitValue); } },null); } public String hashVideo(String pathToMedia, int fileType, String extension) { /** * Hashes the video frames * using FFMpeg's RGB hashing function and * hashes audio stream */ try { java.io.File tmpMedia = null; if (fileType == Type.FILE_SYSTEM) { tmpMedia = new java.io.File(pathToMedia); String[] cmdHash = new String[] { ffmpegBinPath, "-i", tmpMedia.getCanonicalPath(), "-vcodec", "copy", "-an", "-f", "md5", "-" }; //Logger.d(LOG, "ALSO, Storage.EXTERNAL_DIR: " + Storage.EXTERNAL_DIR); //Logger.d(LOG, "HASING VIDEO: " + tmpMedia.getAbsolutePath() + " (cannonical: " + tmpMedia.getCanonicalPath() + ")"); execProcess(cmdHash, new ShellUtils.ShellCallback () { @Override public void shellOut(String shellLine) { if(shellLine.contains("MD5=")) { String hashLine = shellLine.split("=")[1]; newHash = hashLine.split(" ")[0].trim(); } } @Override public void processComplete(int exitValue) { if (newHash == null) newHash = "unknown"; } },null); //wait for a hash to be found, or set to unknown while (newHash == null) { try { Thread.sleep(500); } catch (Exception e){} } return newHash; } else if (fileType == Type.IOCIPHER) { //just use the hash of the first thumbnail for now String thumbPath = pathToMedia + ".thumb.jpg"; InputStream is = new info.guardianproject.iocipher.FileInputStream(new info.guardianproject.iocipher.File(thumbPath)); newHash = MediaHasher.getJpegHash(is); //this is sha-1 is.close(); return newHash; /* try { String[] cmdHash = { ffmpegBinPath, "-i", "pipe:", "-vcodec", "copy", "-an", "-f", "md5", "-" }; InputStream is = new info.guardianproject.iocipher.FileInputStream(new info.guardianproject.iocipher.File(pathToMedia)); execProcess(cmdHash, new ShellUtils.ShellCallback () { @Override public void shellOut(String shellLine) { if(shellLine.contains("MD5=")) { String hashLine = shellLine.split("=")[1]; newHash = hashLine.split(" ")[0].trim(); } } @Override public void processComplete(int exitValue) { if (newHash == null) newHash = "unknown"; }https://news.google.com/ },is); while (newHash == null)https://news.google.com/ { try { Thread.sleep(500); } catch (Exception e){} } if (fileType == Type.IOCIPHER) tmpMedia.delete(); return newHash; } catch (Exception ex) { Log.d("VideoCon","error",ex); return null; } */ } else { return null; } } catch (Exception e) { Log.d("VideoCon","error",e); } return null; } private static void execProcess(String[] cmds, ShellUtils.ShellCallback sc, InputStream is) { ProcessBuilder pb = new ProcessBuilder(cmds); pb.redirectErrorStream(true); Process process; try { process = pb.start(); BufferedReader reader = null; if (is != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Pipe.pipe(process, is, baos); reader = new BufferedReader(new InputStreamReader(new StringBufferInputStream(baos.toString()))); } else { reader = new BufferedReader(new InputStreamReader(process.getInputStream())); } String line; while ((line = reader.readLine()) != null) { if (sc != null) { Log.d(LOG, line); sc.shellOut(line); } } int result = process.waitFor(); sc.processComplete(result); process.destroy(); } catch (Exception e) { Log.e(LOG, e.toString()); e.printStackTrace(); } } public Bitmap getAFrame(java.io.File source, int[] dims) throws IOException { return getAFrame(source, dims, 1); } public Bitmap getAFrame(java.io.File source, int[] dims, int frame) throws IOException { final java.io.File tmp = new java.io.File(Storage.EXTERNAL_DIR, "bmp_" + System.currentTimeMillis()); String[] ffmpegCommand = new String[] { ffmpegBinPath, "-t", String.valueOf(frame), "-i", source.getAbsolutePath(), "-ss", "0.5", "-s", (dims[0] + "x" + dims[1]), "-f", "image2", tmp.getAbsolutePath() }; StringBuffer sb = new StringBuffer(); for(String f: ffmpegCommand) { sb.append(f + " "); } Log.d(LOG, "command to ffmpeg: " + sb.toString()); try { execProcess(ffmpegCommand, new ShellUtils.ShellCallback () { @Override public void shellOut(String shellLine) { Log.d(LOG, shellLine); } @Override public void processComplete(int exitValue) { } },null); } catch (Exception e) { e.printStackTrace(); return null; } return BitmapFactory.decodeFile(tmp.getAbsolutePath()); } }