diff --git a/app/build.gradle b/app/build.gradle
index 6a70c6dfd..b8f017945 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -106,12 +106,18 @@ if (is_proprietary) {
apply plugin: 'ly.img.android.sdk'
imglyConfig {
+ vesdk {
+ enabled true
+ licencePath 'vesdk_license'
+ }
+
pesdk {
enabled true
licencePath 'pesdk_license'
}
modules {
+ include 'ui:video-trim'
include 'ui:core'
include 'ui:text'
include 'ui:focus'
diff --git a/app/src/main/res/layout/activity_new_edit.xml b/app/src/main/res/layout/activity_new_photo_edit.xml
similarity index 55%
rename from app/src/main/res/layout/activity_new_edit.xml
rename to app/src/main/res/layout/activity_new_photo_edit.xml
index 3985d5cdb..aae3460c7 100644
--- a/app/src/main/res/layout/activity_new_edit.xml
+++ b/app/src/main/res/layout/activity_new_photo_edit.xml
@@ -1,7 +1,6 @@
-
diff --git a/app/src/main/res/layout/activity_new_video_edit.xml b/app/src/main/res/layout/activity_new_video_edit.xml
new file mode 100644
index 000000000..aae3460c7
--- /dev/null
+++ b/app/src/main/res/layout/activity_new_video_edit.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/app/src/proprietary/AndroidManifest.xml b/app/src/proprietary/AndroidManifest.xml
index ed281383b..a9eab7303 100644
--- a/app/src/proprietary/AndroidManifest.xml
+++ b/app/src/proprietary/AndroidManifest.xml
@@ -35,5 +35,18 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/proprietary/assets/vesdk_license b/app/src/proprietary/assets/vesdk_license
new file mode 100644
index 000000000..ea65fec16
--- /dev/null
+++ b/app/src/proprietary/assets/vesdk_license
@@ -0,0 +1 @@
+{"api_token":"8mq68_8PExmT5EixFLi8Ng","app_identifiers":["com.simplemobiletools.gallery.pro"],"available_actions":[],"domains":["https://api.photoeditorsdk.com"],"enterprise_license":false,"expires_at":1577836800,"features":["camera","library","export","whitelabel","customassets","adjustment","brush","filter","focus","overlay","sticker","text","textdesign","transform","trim"],"issued_at":1606584014,"minimum_sdk_version":"1.0","owner":"Tikap s.r.o.","platform":"Android","products":["vesdk"],"version":"2.4","signature":"Rr1ocNzO1ZVhN0fo/mfXnd1WGot4psPhlM4i7koo0Bd4zIF9MAkDL6EREOeDisNMMQD4zVtuXRPxM+miDMmZY/2PchwdF2hYssNqD76XAEEIwF3HXNouGCWfFScU2XkOKw9evrlfWgTlfO3H2rDgujo22qhSebDeAGP2satWBcvxkPrF6YJ5GYZUZHyCZ0/INQKyU6zAntfw2er8c46iBMiz00Evp0bYdXFpSk8KQCtgZ9koJYTyKVEFLz1BjOoJkkt8rLyHX2l9VVlRinY+0ss+N2oI5PQVwLqftvWIEL7pOGBrXY5EJFRITeXaVWlPZd7AAzxt54nK3G/5k/RwLoBDbN/q2Kv5fD3kZ7XqOklXgrBogEGm2KEu031Si7yMaOpG+mDJsyKuSh8TRwpsRYUdO+4m0uYqjo/WEmmStNVzCMau4Z9PRcsXmux3UKZHv6yHXxtGK0ZalfOwqCEN27KgTgyLIxEOYyt37cRN/iRUsHqpTG44qNwLPPUPE1VRkhV+XThi8ohJMl2vJeIvggabauuXOg+Hnmty4dZ61k5DGBPZhOxHGEEgKxzqBF5iwDKcnUVx/zhfDfmZo5OD8E2E3gC0MODov0M+cbDXBBE9rkV67zPRz5pUPN4G+gaNryndwRm6sa9xnq8TDaWaHsQtDRgXFQj8PV/XSJQfqTQ="}
\ No newline at end of file
diff --git a/app/src/proprietary/kotlin/com/simplemobiletools/gallery/pro/activities/NewPhotoEditActivity.kt b/app/src/proprietary/kotlin/com/simplemobiletools/gallery/pro/activities/NewPhotoEditActivity.kt
index 13970017d..bff43f0b3 100644
--- a/app/src/proprietary/kotlin/com/simplemobiletools/gallery/pro/activities/NewPhotoEditActivity.kt
+++ b/app/src/proprietary/kotlin/com/simplemobiletools/gallery/pro/activities/NewPhotoEditActivity.kt
@@ -51,7 +51,7 @@ class NewPhotoEditActivity : SimpleActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_new_edit)
+ setContentView(R.layout.activity_new_photo_edit)
if (checkAppSideloading()) {
return
@@ -201,7 +201,7 @@ class NewPhotoEditActivity : SimpleActivity() {
}
}
- // in case the user wants to overwrite the original file and it is on an SD card, delete it manually. Else the system just appends (1)
+ // In case the user wants to overwrite the original file and it is on an SD card, delete it manually first. Else the system just appends (1)
private fun handleFileOverwriting(path: String, callback: () -> Unit) {
if (getDoesFilePathExist(path) && isPathOnSD(path)) {
val fileDirItem = FileDirItem(path, path.getFilenameFromPath())
diff --git a/app/src/proprietary/kotlin/com/simplemobiletools/gallery/pro/activities/NewVideoEditActivity.kt b/app/src/proprietary/kotlin/com/simplemobiletools/gallery/pro/activities/NewVideoEditActivity.kt
new file mode 100644
index 000000000..3c4933b8e
--- /dev/null
+++ b/app/src/proprietary/kotlin/com/simplemobiletools/gallery/pro/activities/NewVideoEditActivity.kt
@@ -0,0 +1,297 @@
+package com.simplemobiletools.gallery.pro.activities
+
+import android.annotation.TargetApi
+import android.app.Activity
+import android.content.Intent
+import android.media.ExifInterface
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.provider.MediaStore
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
+import com.simplemobiletools.commons.helpers.REAL_FILE_PATH
+import com.simplemobiletools.commons.helpers.ensureBackgroundThread
+import com.simplemobiletools.commons.helpers.isNougatPlus
+import com.simplemobiletools.commons.models.FileDirItem
+import com.simplemobiletools.gallery.pro.R
+import com.simplemobiletools.gallery.pro.dialogs.SaveAsDialog
+import com.simplemobiletools.gallery.pro.extensions.config
+import com.simplemobiletools.gallery.pro.extensions.fixDateTaken
+import com.simplemobiletools.gallery.pro.extensions.tryDeleteFileDirItem
+import ly.img.android.pesdk.VideoEditorSettingsList
+import ly.img.android.pesdk.assets.filter.basic.FilterPackBasic
+import ly.img.android.pesdk.assets.font.basic.FontPackBasic
+import ly.img.android.pesdk.backend.model.config.CropAspectAsset
+import ly.img.android.pesdk.backend.model.constant.OutputMode
+import ly.img.android.pesdk.backend.model.state.BrushSettings
+import ly.img.android.pesdk.backend.model.state.LoadSettings
+import ly.img.android.pesdk.backend.model.state.VideoEditorSaveSettings
+import ly.img.android.pesdk.backend.model.state.manager.SettingsList
+import ly.img.android.pesdk.ui.activity.VideoEditorBuilder
+import ly.img.android.pesdk.ui.model.state.*
+import ly.img.android.pesdk.ui.panels.item.CropAspectItem
+import ly.img.android.pesdk.ui.panels.item.ToggleAspectItem
+import ly.img.android.pesdk.ui.panels.item.ToolItem
+import java.io.File
+import java.io.InputStream
+import java.io.OutputStream
+
+class NewVideoEditActivity : SimpleActivity() {
+ private val VESDK_EDIT_VIDEO = 1
+ private val SETTINGS_LIST = "SETTINGS_LIST"
+ private val SOURCE_URI = "SOURCE_URI"
+ private val RESULT_URI = "RESULT_URI"
+ private var sourceFileLastModified = 0L
+ private var oldExif: ExifInterface? = null
+
+ private lateinit var uri: Uri
+ private lateinit var saveUri: Uri
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_new_video_edit)
+
+ if (checkAppSideloading()) {
+ return
+ }
+
+ handlePermission(PERMISSION_WRITE_STORAGE) {
+ if (it) {
+ initEditActivity()
+ } else {
+ toast(R.string.no_storage_permissions)
+ finish()
+ }
+ }
+ }
+
+ private fun initEditActivity() {
+ if (intent.data == null) {
+ toast(R.string.invalid_video_path)
+ finish()
+ return
+ }
+
+ uri = intent.data!!
+ if (uri.scheme != "file" && uri.scheme != "content") {
+ toast(R.string.unknown_file_location)
+ finish()
+ return
+ }
+
+ if (intent.extras?.containsKey(REAL_FILE_PATH) == true) {
+ val realPath = intent.extras!!.getString(REAL_FILE_PATH)
+ uri = when {
+ isPathOnOTG(realPath!!) -> uri
+ realPath.startsWith("file:/") -> Uri.parse(realPath)
+ else -> Uri.fromFile(File(realPath))
+ }
+ } else {
+ (getRealPathFromURI(uri))?.apply {
+ uri = Uri.fromFile(File(this))
+ }
+ }
+
+ saveUri = when {
+ intent.extras?.containsKey(MediaStore.EXTRA_OUTPUT) == true -> intent.extras!!.get(MediaStore.EXTRA_OUTPUT) as Uri
+ else -> uri
+ }
+
+ openEditor(uri)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+ if (requestCode == VESDK_EDIT_VIDEO) {
+ val extras = resultData?.extras
+ val resultPath = extras?.get(RESULT_URI)?.toString() ?: ""
+ val sourcePath = Uri.decode(extras?.get(SOURCE_URI)?.toString() ?: "")
+ val settings = extras?.getParcelable(SETTINGS_LIST)
+ if (settings != null) {
+ val brush = settings.getSettingsModel(BrushSettings::class.java)
+ config.editorBrushColor = brush.brushColor
+ config.editorBrushHardness = brush.brushHardness
+ config.editorBrushSize = brush.brushSize
+ }
+
+ if (resultCode != Activity.RESULT_OK || resultPath.isEmpty()) {
+ toast(R.string.video_editing_cancelled)
+ finish()
+ } else {
+ val source = if (sourcePath.isEmpty() || sourcePath.startsWith("content")) {
+ internalStoragePath
+ } else {
+ sourcePath.substringAfter("file://")
+ }
+
+ SaveAsDialog(this, source, true, cancelCallback = {
+ toast(R.string.video_editing_failed)
+ finish()
+ }, callback = {
+ val destinationFilePath = it
+ handleSAFDialog(destinationFilePath) {
+ if (it) {
+ ensureBackgroundThread {
+ storeOldExif(source)
+ sourceFileLastModified = File(source).lastModified()
+
+ handleFileOverwriting(destinationFilePath) {
+ var inputStream: InputStream? = null
+ var outputStream: OutputStream? = null
+ try {
+ inputStream = contentResolver.openInputStream(Uri.parse(resultPath))
+ outputStream = getFileOutputStreamSync(destinationFilePath, destinationFilePath.getMimeType())
+ inputStream!!.copyTo(outputStream!!)
+ outputStream.flush()
+ inputStream.close()
+ outputStream.close()
+
+ try {
+ if (isNougatPlus()) {
+ val newExif = ExifInterface(destinationFilePath)
+ oldExif?.copyTo(newExif, false)
+ }
+ } catch (ignored: Exception) {
+ }
+
+ if (config.keepLastModified) {
+ // add 1 s to the last modified time to properly update the thumbnail
+ updateLastModified(destinationFilePath, sourceFileLastModified + 1000)
+ }
+
+ val paths = arrayListOf(destinationFilePath)
+ rescanPaths(arrayListOf(destinationFilePath)) {
+ fixDateTaken(paths, false)
+ }
+
+ setResult(Activity.RESULT_OK, intent)
+ toast(R.string.file_edited_successfully)
+ finish()
+ } catch (e: Exception) {
+ showErrorToast(e)
+ } finally {
+ inputStream?.close()
+ outputStream?.close()
+ }
+ }
+ }
+ } else {
+ toast(R.string.video_editing_failed)
+ finish()
+ }
+ }
+ })
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, resultData)
+ }
+
+ @TargetApi(Build.VERSION_CODES.N)
+ private fun storeOldExif(sourcePath: String) {
+ var inputStream: InputStream? = null
+ try {
+ if (isNougatPlus()) {
+ inputStream = contentResolver.openInputStream(Uri.fromFile(File(sourcePath)))
+ oldExif = ExifInterface(inputStream!!)
+ }
+ } catch (ignored: Exception) {
+ } finally {
+ inputStream?.close()
+ }
+ }
+
+ // In case the user wants to overwrite the original file and it is on an SD card, delete it manually first. Else the system just appends (1)
+ private fun handleFileOverwriting(path: String, callback: () -> Unit) {
+ if (getDoesFilePathExist(path) && isPathOnSD(path)) {
+ val fileDirItem = FileDirItem(path, path.getFilenameFromPath())
+ tryDeleteFileDirItem(fileDirItem, false, true) { success ->
+ if (success) {
+ callback()
+ } else {
+ toast(R.string.unknown_error_occurred)
+ finish()
+ }
+ }
+ } else {
+ callback()
+ }
+ }
+
+ private fun openEditor(inputVideo: Uri) {
+ val settingsList = createPesdkSettingsList()
+
+ settingsList.configure {
+ it.source = inputVideo
+ }
+
+ settingsList[LoadSettings::class].source = inputVideo
+
+ VideoEditorBuilder(this)
+ .setSettingsList(settingsList)
+ .startActivityForResult(this, VESDK_EDIT_VIDEO)
+ }
+
+ private fun createPesdkSettingsList(): VideoEditorSettingsList {
+ val settingsList = VideoEditorSettingsList().apply {
+ configure {
+ it.setFilterList(FilterPackBasic.getFilterPack())
+ }
+
+ configure {
+ it.setFontList(FontPackBasic.getFontPack())
+ }
+
+ config.getAssetMap(CropAspectAsset::class.java).apply {
+ add(CropAspectAsset("my_crop_1_2", 1, 2, false))
+ add(CropAspectAsset("my_crop_2_1", 2, 1, false))
+ add(CropAspectAsset("my_crop_19_9", 19, 9, false))
+ add(CropAspectAsset("my_crop_9_19", 9, 19, false))
+ add(CropAspectAsset("my_crop_5_4", 5, 4, false))
+ add(CropAspectAsset("my_crop_4_5", 4, 5, false))
+ add(CropAspectAsset("my_crop_37_18", 37, 18, false))
+ add(CropAspectAsset("my_crop_18_37", 18, 37, false))
+ add(CropAspectAsset("my_crop_16_10", 16, 10, false))
+ add(CropAspectAsset("my_crop_10_16", 10, 16, false))
+ }
+
+ getSettingsModel(UiConfigAspect::class.java).aspectList.apply {
+ add(ToggleAspectItem(CropAspectItem("my_crop_2_1"), CropAspectItem("my_crop_1_2")))
+ add(ToggleAspectItem(CropAspectItem("my_crop_19_9"), CropAspectItem("my_crop_9_19")))
+ add(ToggleAspectItem(CropAspectItem("my_crop_5_4"), CropAspectItem("my_crop_4_5")))
+ add(ToggleAspectItem(CropAspectItem("my_crop_37_18"), CropAspectItem("my_crop_18_37")))
+ add(ToggleAspectItem(CropAspectItem("my_crop_16_10"), CropAspectItem("my_crop_10_16")))
+ }
+
+ getSettingsModel(BrushSettings::class.java).apply {
+ brushColor = applicationContext.config.editorBrushColor
+ brushHardness = applicationContext.config.editorBrushHardness
+ brushSize = applicationContext.config.editorBrushSize
+ }
+
+ // do not use Text Design, it takes up too much space
+ val tools = getSettingsModel(UiConfigMainMenu::class.java).toolList
+ val newTools = tools.filterNot {
+ it.name!!.isEmpty()
+ }.toMutableList() as ArrayList
+
+ // move Focus at the end, as it is the least used
+ // on some devices it is not obvious that the toolbar can be scrolled horizontally, so move the best ones at the beginning to make them visible
+ val focus = newTools.firstOrNull { it.name == getString(R.string.pesdk_focus_title_name) }
+ if (focus != null) {
+ newTools.remove(focus)
+ newTools.add(focus)
+ }
+
+ getSettingsModel(UiConfigMainMenu::class.java).setToolList(newTools)
+
+ getSettingsModel(UiConfigTheme::class.java).theme = R.style.Imgly_Theme_NoFullscreen
+
+ configure {
+ it.setOutputToTemp()
+ it.outputMode = OutputMode.EXPORT_IF_NECESSARY
+ }
+ }
+
+ return settingsList
+ }
+}