transcode videos before sharing. change storage location

Daniel Gultsch created

Change summary

build.gradle                                                             |  1 
src/main/AndroidManifest.xml                                             |  2 
src/main/java/eu/siacs/conversations/persistance/FileBackend.java        | 32 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 79 
src/main/java/eu/siacs/conversations/ui/ConversationActivity.java        | 19 
src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java           | 15 
src/main/java/eu/siacs/conversations/ui/UiInformableCallback.java        |  5 
src/main/res/values/strings.xml                                          |  1 
src/main/res/xml/file_paths.xml                                          |  5 
9 files changed, 131 insertions(+), 28 deletions(-)

Detailed changes

build.gradle 🔗

@@ -40,6 +40,7 @@ dependencies {
     compile 'org.whispersystems:axolotl-android:1.3.4'
     compile 'com.makeramen:roundedimageview:2.2.0'
     compile "com.wefika:flowlayout:0.4.1"
+    compile 'net.ypresto.androidtranscoder:android-transcoder:0.2.0'
 }
 
 ext {

src/main/AndroidManifest.xml 🔗

@@ -19,6 +19,8 @@
         android:name="android.permission.READ_PHONE_STATE"
         tools:node="remove" />
 
+    <uses-sdk tools:overrideLibrary="net.ypresto.androidtranscoder" />
+
     <application
         android:allowBackup="true"
         android:icon="@drawable/ic_launcher"

src/main/java/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -71,7 +71,7 @@ public class FileBackend {
 	}
 
 	private void createNoMedia() {
-		final File nomedia = new File(getConversationsFileDirectory()+".nomedia");
+		final File nomedia = new File(getConversationsDirectory("Files")+".nomedia");
 		if (!nomedia.exists()) {
 			try {
 				nomedia.createNewFile();
@@ -82,7 +82,9 @@ public class FileBackend {
 	}
 
 	public void updateMediaScanner(File file) {
-		if (file.getAbsolutePath().startsWith(getConversationsImageDirectory())) {
+		String path = file.getAbsolutePath();
+		if (!path.startsWith(getConversationsDirectory("Files"))) {
+			new File(Environment.getExternalStorageDirectory()+"/Conversations/.nomedia").delete();
 			Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
 			intent.setData(Uri.fromFile(file));
 			mXmppConnectionService.sendBroadcast(intent);
@@ -118,14 +120,16 @@ public class FileBackend {
 			file = new DownloadableFile(path);
 		} else {
 			String mime = message.getMimeType();
-			if (mime != null && mime.startsWith("image")) {
-				file = new DownloadableFile(getConversationsImageDirectory() + path);
+			if (mime != null && mime.startsWith("image/")) {
+				file = new DownloadableFile(getConversationsDirectory("Images") + path);
+			} else if (mime != null && mime.startsWith("video/")) {
+				file = new DownloadableFile(getConversationsDirectory("Videos") + path);
 			} else {
-				file = new DownloadableFile(getConversationsFileDirectory() + path);
+				file = new DownloadableFile(getConversationsDirectory("Files") + path);
 			}
 		}
 		if (encrypted) {
-			return new DownloadableFile(getConversationsFileDirectory() + file.getName() + ".pgp");
+			return new DownloadableFile(getConversationsDirectory("Files") + file.getName() + ".pgp");
 		} else {
 			return file;
 		}
@@ -154,21 +158,11 @@ public class FileBackend {
 		return true;
 	}
 
-	public String getConversationsFileDirectory() {
+	public String getConversationsDirectory(final String type) {
 		if (Config.ONLY_INTERNAL_STORAGE) {
-			return mXmppConnectionService.getFilesDir().getAbsolutePath() + "/Files/";
+			return mXmppConnectionService.getFilesDir().getAbsolutePath()+"/"+type+"/";
 		} else {
-			return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Conversations/";
-		}
-	}
-
-	public String getConversationsImageDirectory() {
-		if (Config.ONLY_INTERNAL_STORAGE) {
-			return mXmppConnectionService.getFilesDir().getAbsolutePath()+"/Pictures/";
-		} else {
-			return Environment.getExternalStoragePublicDirectory(
-					Environment.DIRECTORY_PICTURES).getAbsolutePath()
-					+ "/Conversations/";
+			return Environment.getExternalStorageDirectory() +"/Conversations/Media/Conversations "+type+"/";
 		}
 	}
 

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -20,6 +20,7 @@ import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
@@ -37,11 +38,15 @@ import net.java.otr4j.session.Session;
 import net.java.otr4j.session.SessionID;
 import net.java.otr4j.session.SessionImpl;
 import net.java.otr4j.session.SessionStatus;
+import net.ypresto.androidtranscoder.MediaTranscoder;
+import net.ypresto.androidtranscoder.format.MediaFormatStrategyPresets;
 
 import org.openintents.openpgp.IOpenPgpService2;
 import org.openintents.openpgp.util.OpenPgpApi;
 import org.openintents.openpgp.util.OpenPgpServiceConnection;
 
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
 import java.math.BigInteger;
 import java.security.SecureRandom;
 import java.security.cert.CertificateException;
@@ -59,6 +64,7 @@ import java.util.ListIterator;
 import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicLong;
 
 import de.duenndns.ssl.MemorizingTrustManager;
@@ -97,6 +103,7 @@ import eu.siacs.conversations.persistance.DatabaseBackend;
 import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.ui.SettingsActivity;
 import eu.siacs.conversations.ui.UiCallback;
+import eu.siacs.conversations.ui.UiInformableCallback;
 import eu.siacs.conversations.utils.ConversationsFileObserver;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.ExceptionHelper;
@@ -457,10 +464,10 @@ public class XmppConnectionService extends Service {
 		}
 		message.setCounterpart(conversation.getNextCounterpart());
 		message.setType(Message.TYPE_FILE);
-		final String path = getFileBackend().getOriginalPath(uri);
 		mFileAddingExecutor.execute(new Runnable() {
-			@Override
-			public void run() {
+
+			private void processAsFile() {
+				final String path = getFileBackend().getOriginalPath(uri);
 				if (path != null) {
 					message.setRelativeFilePath(path);
 					getFileBackend().updateFileParams(message);
@@ -488,6 +495,72 @@ public class XmppConnectionService extends Service {
 					}
 				}
 			}
+
+			private void processAsVideo() throws FileNotFoundException {
+				Log.d(Config.LOGTAG,"processing file as video");
+				message.setRelativeFilePath(message.getUuid() + ".mp4");
+				final DownloadableFile file = getFileBackend().getFile(message);
+				file.getParentFile().mkdirs();
+				ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r");
+				FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+				final ArrayList<Integer> progressTracker = new ArrayList<>();
+				final UiInformableCallback<Message> informableCallback;
+				if (callback instanceof UiInformableCallback) {
+					informableCallback = (UiInformableCallback<Message>) callback;
+				} else {
+					informableCallback = null;
+				}
+				MediaTranscoder.Listener listener = new MediaTranscoder.Listener() {
+					@Override
+					public void onTranscodeProgress(double progress) {
+						int p = ((int) Math.round(progress * 100) / 20) * 20;
+						if (!progressTracker.contains(p) && p != 100 && p != 0) {
+							progressTracker.add(p);
+							if (informableCallback != null) {
+
+								informableCallback.inform(getString(R.string.transcoding_video_progress, p));
+							}
+						}
+					}
+
+					@Override
+					public void onTranscodeCompleted() {
+						if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+							getPgpEngine().encrypt(message, callback);
+						} else {
+							callback.success(message);
+						}
+					}
+
+					@Override
+					public void onTranscodeCanceled() {
+						processAsFile();
+					}
+
+					@Override
+					public void onTranscodeFailed(Exception e) {
+						Log.d(Config.LOGTAG,"video transcoding failed "+e.getMessage());
+						processAsFile();
+					}
+				};
+				MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(),
+						MediaFormatStrategyPresets.createAndroid720pStrategy(), listener);
+			}
+
+			@Override
+			public void run() {
+				final String mimeType = MimeUtils.guessMimeTypeFromUri(XmppConnectionService.this, uri);
+				if (mimeType != null && mimeType.startsWith("video/") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+					try {
+						processAsVideo();
+					} catch (Throwable e) {
+						processAsFile();
+					}
+				} else {
+					processAsFile();
+				}
+
+			}
 		});
 	}
 

src/main/java/eu/siacs/conversations/ui/ConversationActivity.java 🔗

@@ -1543,9 +1543,26 @@ public class ConversationActivity extends XmppActivity
 		}
 		final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG);
 		prepareFileToast.show();
-		xmppConnectionService.attachFileToConversation(conversation, uri, new UiCallback<Message>() {
+		xmppConnectionService.attachFileToConversation(conversation, uri, new UiInformableCallback<Message>() {
+			@Override
+			public void inform(final String text) {
+				hidePrepareFileToast(prepareFileToast);
+				runOnUiThread(new Runnable() {
+					@Override
+					public void run() {
+						replaceToast(text);
+					}
+				});
+			}
+
 			@Override
 			public void success(Message message) {
+				runOnUiThread(new Runnable() {
+					@Override
+					public void run() {
+						hideToast();
+					}
+				});
 				hidePrepareFileToast(prepareFileToast);
 				xmppConnectionService.sendMessage(message);
 			}

src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java 🔗

@@ -59,7 +59,17 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
 	private Toast mToast;
 	private AtomicInteger attachmentCounter = new AtomicInteger(0);
 
-	private UiCallback<Message> attachFileCallback = new UiCallback<Message>() {
+	private UiInformableCallback<Message> attachFileCallback = new UiInformableCallback<Message>() {
+
+		@Override
+		public void inform(final String text) {
+			runOnUiThread(new Runnable() {
+				@Override
+				public void run() {
+					replaceToast(text);
+				}
+			});
+		}
 
 		@Override
 		public void userInputRequried(PendingIntent pi, Message object) {
@@ -293,8 +303,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
 					} else {
 						replaceToast(getString(R.string.preparing_file));
 						ShareWithActivity.this.xmppConnectionService
-								.attachFileToConversation(conversation, share.uris.get(0),
-										attachFileCallback);
+								.attachFileToConversation(conversation, share.uris.get(0), attachFileCallback);
 					}
 				}
 			};

src/main/res/values/strings.xml 🔗

@@ -732,4 +732,5 @@
 	<string name="pref_automatically_delete_messages_description">Automatically delete messages from this device that are older than the configured time frame.</string>
 	<string name="encrypting_message">Encrypting message</string>
 	<string name="not_fetching_history_retention_period">Overstepping local retention period.</string>
+	<string name="transcoding_video_progress">Compressing video (%s%% completed)</string>
 </resources>

src/main/res/xml/file_paths.xml 🔗

@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <paths>
     <external-path name="external" path="/"/>
-    <files-path path="Pictures/" name="pics" />
+    <files-path path="Images/" name="pictures" />
+    <files-path path="Videos" name="videos"/>
     <files-path path="Files/" name="files" />
-    <cache-path path="Camera/" name="cam" />
+    <cache-path path="Camera/" name="camera" />
 </paths>