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 + } +}