diff --git a/README.md b/README.md index 9958ee8..84f3306 100644 --- a/README.md +++ b/README.md @@ -62,27 +62,130 @@ FFmpeg Android runs on the following architectures: ### Dependency - [MobileFFmpeg](https://github.com/tanersener/mobile-ffmpeg) -### Gradle Dependency -* Add it in your root build.gradle at the end of repositories: +**Note:** This library includes the mobile-ffmpeg.aar file (full version) in the `SSffmpegVideoOperation/libs/` directory. The full version includes all FFmpeg features and codecs needed for comprehensive video operations. - ``` +**For JitPack users:** The mobile-ffmpeg dependency is automatically included when you use the JitPack dependency. No additional setup required! + +### Integration Guide + +This library uses the mobile-ffmpeg AAR dependency which is included in the library's libs folder. Follow these simple steps to integrate the library into your project. + +#### Step 1: Download the Library +* Download or clone this repository +* Copy the `SSffmpegVideoOperation` module folder to your project + +#### Step 2: Integration Methods + +**Option A: Using JitPack (Recommended - Simple One-Line Integration)** + +This is the easiest way to integrate the library. JitPack will build the library along with the mobile-ffmpeg dependency automatically. + +* Add JitPack repository to your root build.gradle: + + ```gradle allprojects { repositories { - ... + google() + mavenCentral() maven { url 'https://jitpack.io' } } } ``` -* Add the dependency in your app's build.gradle file +* Add the dependency in your app's build.gradle file: - ``` + ```gradle dependencies { implementation 'com.github.SimformSolutionsPvtLtd:SSffmpegVideoOperation:1.0.8' } ``` -This is all you have to do to load the FFmpeg library. +**That's it!** JitPack will automatically handle the mobile-ffmpeg.aar dependency that's included in this repository. No additional configuration needed. + +**Option B: Local Integration (For Customization)** + +If you want to modify the library or integrate it locally, follow these steps: + +1. **Add the library module to your `settings.gradle`:** + ```gradle + include ':app', ':SSffmpegVideoOperation' + // If the SSffmpegVideoOperation folder is in a different location: + // project(':SSffmpegVideoOperation').projectDir = new File('path/to/SSffmpegVideoOperation') + ``` + +2. **Configure repositories in your app's `build.gradle`:** + ```gradle + android { + // ... your existing configuration + } + + repositories { + google() + mavenCentral() + flatDir { + dirs '../SSffmpegVideoOperation/libs' + } + } + + dependencies { + implementation project(':SSffmpegVideoOperation') + // ... your other dependencies + } + ``` + +3. **The SSffmpegVideoOperation module is already configured with:** + - `libs/mobile-ffmpeg.aar` - The full version of mobile-ffmpeg + - Proper repository configuration in its `build.gradle`: + ```gradle + repositories { + flatDir { + dirs 'libs' + } + } + + dependencies { + implementation(name: 'mobile-ffmpeg', ext: 'aar') + // ... other dependencies + } + ``` + +**Important Notes:** +- The `mobile-ffmpeg.aar` file is already included in `SSffmpegVideoOperation/libs/` +- No additional setup is required for the AAR dependency +- The library uses `flatDir` repository to resolve the local AAR file +- Make sure to sync your project after adding the module + +## How JitPack Integration Works + +When you use the JitPack dependency (`implementation 'com.github.SimformSolutionsPvtLtd:SSffmpegVideoOperation:1.0.8'`): + +1. **JitPack automatically builds** your library from the GitHub repository +2. **Includes mobile-ffmpeg.aar** - The AAR file in `SSffmpegVideoOperation/libs/` is packaged with the library +3. **Resolves dependencies** - All transitive dependencies are handled automatically +4. **No local setup needed** - Users don't need to manually handle AAR files or repository configurations + +This approach gives you the best of both worlds: +- **Simple integration** for users via JitPack +- **Full control** over the mobile-ffmpeg dependency +- **No external dependencies** on repositories that might change or become unavailable + +#### Step 3: Add Required Permissions +Add these permissions to your AndroidManifest.xml: +```xml + + + + +``` + +#### Step 4: ProGuard Configuration (if using ProGuard/R8) +Add these rules to your proguard-rules.pro: +``` +-keep class com.arthenica.mobileffmpeg.** { *; } +-keep class com.simform.videooperations.** { *; } +``` + +This setup ensures proper AAR dependency resolution and avoids the "Direct local .aar file dependencies are not supported" error when building AAR libraries. ### Run FFmpeg command In this sample code we will run the FFmpeg -version command in background call. @@ -138,6 +241,64 @@ CallBackOfQuery().callQuery(query, object : FFmpegCallBack { same for other queries. And you can apply your query also +## Troubleshooting + +### Common Issues and Solutions + +#### 1. "Could not find mobile-ffmpeg.aar" Error +**Solution:** +- Ensure the `SSffmpegVideoOperation/libs/mobile-ffmpeg.aar` file exists +- Check that your app's build.gradle has the correct flatDir repository configuration: + ```gradle + repositories { + flatDir { + dirs '../SSffmpegVideoOperation/libs' + } + } + ``` + +#### 2. "INSTALL_PARSE_FAILED_NO_CERTIFICATES" Error +**Solution:** Ensure your release builds are properly signed. Add signing configuration to your app's build.gradle: +```gradle +android { + signingConfigs { + debug { + storeFile file("${System.getProperty('user.home')}/.android/debug.keystore") + storePassword "android" + keyAlias "androiddebugkey" + keyPassword "android" + } + } + buildTypes { + release { + signingConfig signingConfigs.debug // Use debug signing for testing + // For production, create and use a proper release keystore + } + } +} +``` + +#### 3. Build Sync Issues +**Solution:** +- Make sure the SSffmpegVideoOperation module is properly included in settings.gradle +- Verify the module path is correct +- Clean and rebuild the project + +#### 4. FFmpeg Commands Not Working +**Solution:** +- Check if you have the required permissions +- Ensure input and output file paths are correct and accessible +- Verify the FFmpeg command syntax + +#### 5. Memory Issues with Large Videos +**Solution:** +- Process videos in smaller chunks +- Use appropriate compression settings +- Consider using background processing for large files + +#### 6. "flatDir should be avoided" Warning +**Solution:** This warning can be ignored. While flatDir is not the recommended approach for published libraries, it's acceptable for local AAR dependencies and works reliably for this use case. + ## Medium Blog For more info go to __[Multimedia Operations for Android using FFmpeg](https://medium.com/simform-engineering/multimedia-operations-for-android-using-ffmpeg-78f1fb480a83)__ diff --git a/SSffmpegVideoOperation/.gitignore b/SSffmpegVideoOperation/.gitignore index f0df5ca..f406bdd 100644 --- a/SSffmpegVideoOperation/.gitignore +++ b/SSffmpegVideoOperation/.gitignore @@ -9,6 +9,7 @@ /captures .externalNativeBuild libs/ +!libs/mobile-ffmpeg.aar .idea/gradle.xml .idea/misc.xml .idea/modules.xml diff --git a/SSffmpegVideoOperation/build.gradle b/SSffmpegVideoOperation/build.gradle index 23c767e..509a0fb 100644 --- a/SSffmpegVideoOperation/build.gradle +++ b/SSffmpegVideoOperation/build.gradle @@ -19,18 +19,19 @@ afterEvaluate { } repositories { + google() + mavenCentral() flatDir { dirs 'libs' } } android { - compileSdkVersion 30 - buildToolsVersion "29.0.3" - + namespace 'com.simform.videooperations' defaultConfig { + compileSdk 35 minSdkVersion 24 - targetSdkVersion 30 + targetSdkVersion 35 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -69,11 +70,10 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.core:core-ktx:1.8.0' - implementation 'androidx.appcompat:appcompat:1.4.2' - implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'com.google.android.material:material:1.12.0' implementation 'pub.devrel:easypermissions:3.0.0' - implementation(files("libs/mobile-ffmpeg.aar")) + implementation (name: 'mobile-ffmpeg', ext: 'aar') implementation 'com.github.jaiselrahman:FilePicker:1.3.2' } - diff --git a/SSffmpegVideoOperation/libs/mobile-ffmpeg.aar b/SSffmpegVideoOperation/libs/mobile-ffmpeg.aar new file mode 100644 index 0000000..49bfb03 Binary files /dev/null and b/SSffmpegVideoOperation/libs/mobile-ffmpeg.aar differ diff --git a/SSffmpegVideoOperation/src/main/java/com/simform/videooperations/Common.kt b/SSffmpegVideoOperation/src/main/java/com/simform/videooperations/Common.kt index 4f9acc1..766cc71 100644 --- a/SSffmpegVideoOperation/src/main/java/com/simform/videooperations/Common.kt +++ b/SSffmpegVideoOperation/src/main/java/com/simform/videooperations/Common.kt @@ -1,15 +1,22 @@ package com.simform.videooperations +import android.content.ContentResolver import android.content.Context import android.content.Intent import android.media.MediaExtractor import android.media.MediaFormat +import android.media.MediaMetadataRetriever +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.OpenableColumns import android.text.TextUtils import androidx.appcompat.app.AppCompatActivity import com.jaiselrahman.filepicker.activity.FilePickerActivity import com.jaiselrahman.filepicker.config.Configurations import java.io.File import java.io.FileInputStream +import java.io.FileOutputStream import java.io.IOException import java.text.DecimalFormat import java.util.Formatter @@ -162,7 +169,20 @@ object Common { } fun getFilePath(context: Context, fileExtension: String) : String { - val dir = File(context.getExternalFilesDir(Common.OUT_PUT_DIR).toString()) + val dir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + when { + TextUtils.equals(fileExtension, VIDEO) -> Environment.DIRECTORY_MOVIES + TextUtils.equals(fileExtension, IMAGE) || TextUtils.equals(fileExtension, GIF) -> Environment.DIRECTORY_PICTURES + TextUtils.equals(fileExtension, MP3) -> Environment.DIRECTORY_MUSIC + else -> Environment.DIRECTORY_DOWNLOADS + }.let { + File(Environment.getExternalStoragePublicDirectory(it).toString()) + } + } else { + // Fallback to app's private external files dir on older Android versions + File(context.getExternalFilesDir(Common.OUT_PUT_DIR).toString()) + } + if (!dir.exists()) { dir.mkdirs() } @@ -181,7 +201,12 @@ object Common { extension = ".mp3" } } - val dest = File(dir.path + File.separator + Common.OUT_PUT_DIR + System.currentTimeMillis().div(1000L) + extension) + val dest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + File(dir, System.currentTimeMillis().div(1000L).toString() + extension) + } else { + // Fallback for devices below Android 14 + File(dir.path + File.separator + OUT_PUT_DIR + System.currentTimeMillis().div(1000L) + extension) + } return dest.absolutePath } @@ -196,4 +221,47 @@ object Common { } } } + + fun saveFileToTempAndGetPath(context: Context, uri: Uri): String? { + val contentResolver = context.contentResolver + val fileName = getFileNameFromUri(contentResolver, uri) + val tempFile = File(context.cacheDir, fileName) + + return try { + contentResolver.openInputStream(uri)?.use { inputStream -> + FileOutputStream(tempFile).use { outputStream -> + inputStream.copyTo(outputStream) + } + } + tempFile.absolutePath + } catch (e: Exception) { + e.printStackTrace() + null + } + } + + private fun getFileNameFromUri(contentResolver: ContentResolver, uri: Uri): String { + var name = "temp_file" + val returnCursor = contentResolver.query(uri, null, null, null, null) + returnCursor?.use { + val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (it.moveToFirst() && nameIndex >= 0) { + name = it.getString(nameIndex) + } + } + return name + } + + fun getDurationFromFile(file: File): Long { + val retriever = MediaMetadataRetriever() + return try { + retriever.setDataSource(file.absolutePath) + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L + } catch (e: Exception) { + e.printStackTrace() + 0L + } finally { + retriever.release() + } + } } \ No newline at end of file diff --git a/SSffmpegVideoOperation/src/main/java/com/simform/videooperations/FFmpegQueryExtension.kt b/SSffmpegVideoOperation/src/main/java/com/simform/videooperations/FFmpegQueryExtension.kt index ba5f614..55367f3 100644 --- a/SSffmpegVideoOperation/src/main/java/com/simform/videooperations/FFmpegQueryExtension.kt +++ b/SSffmpegVideoOperation/src/main/java/com/simform/videooperations/FFmpegQueryExtension.kt @@ -46,7 +46,7 @@ public class FFmpegQueryExtension { add("-i") add(input) add("-s") - add("${width}x${height}") + add("${((width ?: 0)/2) * 2}x${((height ?: 0)/2) * 2}") // Ensure width and height are even numbers add("-vf") add("format=yuv420p,$fade") add("-t") diff --git a/app/build.gradle b/app/build.gradle index cbfac26..d1c6cab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,35 +1,57 @@ plugins { id 'com.android.application' id 'kotlin-android' - id 'kotlin-android-extensions' id 'kotlin-kapt' } android { - compileSdkVersion 31 - + namespace 'com.simform.videoimageeditor' + compileSdk 35 defaultConfig { applicationId "com.simform.videoimageeditor" minSdkVersion 24 - targetSdkVersion 31 + targetSdkVersion 35 versionCode 1 versionName "1.0" - testInstrumentationRunner "androidx...test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + signingConfigs { + debug { + storeFile file("${System.getProperty('user.home')}/.android/debug.keystore") + storePassword 'android' + keyAlias 'androiddebugkey' + keyPassword 'android' + } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.debug } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + kotlinOptions { jvmTarget = '11' } + + buildFeatures { + viewBinding true + } + flavorDimensions "default" +} - androidExtensions { - experimental = true +repositories { + flatDir { + dirs '../SSffmpegVideoOperation/libs' } } @@ -44,13 +66,15 @@ kapt { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.core:core-ktx:1.8.0' - implementation 'androidx.appcompat:appcompat:1.4.2' - implementation 'com.github.bumptech.glide:glide:4.12.0' - annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' - implementation 'com.google.android.material:material:1.6.1' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'com.github.bumptech.glide:glide:4.16.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' + implementation 'com.google.android.material:material:1.12.0' + implementation 'androidx.constraintlayout:constraintlayout:2.2.1' implementation 'pub.devrel:easypermissions:3.0.0' implementation 'com.github.jaiselrahman:FilePicker:1.3.2' implementation project(':SSffmpegVideoOperation') + implementation "androidx.activity:activity-ktx:1.10.1" + implementation "androidx.fragment:fragment-ktx:1.8.8" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 388fb50..a469178 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,8 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" package="com.simform.videoimageeditor"> - - + diff --git a/app/src/main/java/com/simform/videoimageeditor/BaseActivity.kt b/app/src/main/java/com/simform/videoimageeditor/BaseActivity.kt index 4ed6c78..44f70fc 100644 --- a/app/src/main/java/com/simform/videoimageeditor/BaseActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/BaseActivity.kt @@ -2,12 +2,16 @@ package com.simform.videoimageeditor import android.content.Intent import android.media.MediaMetadataRetriever +import android.os.Build import android.os.Bundle import android.view.MenuItem import android.view.View +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import com.jaiselrahman.filepicker.activity.FilePickerActivity import com.jaiselrahman.filepicker.model.MediaFile +import com.simform.videooperations.Common import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.FileSelection @@ -24,12 +28,108 @@ abstract class BaseActivity(view: Int, title: Int) : AppCompatActivity(), View.O var retriever: MediaMetadataRetriever? = null val utils = Utils() val ffmpegQueryExtension = FFmpegQueryExtension() + val pickSingleMedia = + registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> + if (uri != null) { + val mediaType = when (contentResolver.getType(uri)?.substringBefore('/')) { + "image" -> MediaFile.TYPE_IMAGE + "video" -> MediaFile.TYPE_VIDEO + "audio" -> MediaFile.TYPE_AUDIO + else -> MediaFile.TYPE_FILE + } + + val mediaFile = MediaFile().apply { + setUri(uri) + setMediaType(mediaType) + setMimeType(contentResolver.getType(uri)) + setName(uri.lastPathSegment ?: "Unknown") + } + + val requestCode = when (mediaType) { + MediaFile.TYPE_IMAGE -> Common.IMAGE_FILE_REQUEST_CODE + MediaFile.TYPE_VIDEO -> Common.VIDEO_FILE_REQUEST_CODE + MediaFile.TYPE_AUDIO -> Common.AUDIO_FILE_REQUEST_CODE + else -> Common.VIDEO_FILE_REQUEST_CODE // Default to video for unknown types + } + + val mediaFiles = listOf(mediaFile) + this.mediaFiles = mediaFiles + (this as FileSelection).selectedFiles(mediaFiles, requestCode) + } else { + Toast.makeText(this, "User cancelled the selection", Toast.LENGTH_SHORT).show() + } + } + + val pickMultipleMedia = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia()) { uris -> + if (uris.isNotEmpty()) { + val mediaFiles = uris.map { uri -> + val mediaType = when (contentResolver.getType(uri)?.substringBefore('/')) { + "image" -> MediaFile.TYPE_IMAGE + "video" -> MediaFile.TYPE_VIDEO + "audio" -> MediaFile.TYPE_AUDIO + else -> MediaFile.TYPE_FILE + } + + MediaFile().apply { + setUri(uri) + setMediaType(mediaType) + setMimeType(contentResolver.getType(uri)) + setName(uri.lastPathSegment ?: "Unknown") + } + } + val requestCode = when (contentResolver.getType(mediaFiles.first().uri)?.substringBefore('/')) { + "image" -> Common.IMAGE_FILE_REQUEST_CODE + "video" -> Common.VIDEO_FILE_REQUEST_CODE + "audio" -> Common.AUDIO_FILE_REQUEST_CODE + else -> Common.VIDEO_FILE_REQUEST_CODE// Default to video for unknown types + } + this.mediaFiles = mediaFiles + (this as FileSelection).selectedFiles(mediaFiles, requestCode) + } else { + Toast.makeText(this, "User cancelled the selection", Toast.LENGTH_SHORT).show() + } + + } + + val pickAudio = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri != null) { + // Got audio Uri, do something with it + val mediaFiles = listOf(MediaFile().apply { + setUri(uri) + setMediaType(MediaFile.TYPE_AUDIO) + setMimeType(contentResolver.getType(uri)) + setName(uri.lastPathSegment ?: "Unknown") + }) + this.mediaFiles = mediaFiles + (this as FileSelection).selectedFiles(mediaFiles, Common.AUDIO_FILE_REQUEST_CODE) + } else { + Toast.makeText(this, "No audio selected", Toast.LENGTH_SHORT).show() + } + } + + val pickMultipleAudio = registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { uris -> + if (uris != null) { + // Got audio Uri, do something with it + val mediaFiles = uris.map { uri -> + MediaFile().apply { + setUri(uri) + setMediaType(MediaFile.TYPE_AUDIO) + setMimeType(contentResolver.getType(uri)) + setName(uri.lastPathSegment ?: "Unknown") + } + } + this.mediaFiles = mediaFiles + (this as FileSelection).selectedFiles(mediaFiles, Common.AUDIO_FILE_REQUEST_CODE) + } else { + Toast.makeText(this, "No audio selected", Toast.LENGTH_SHORT).show() + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(layoutView) - utils.addSupportActionBar(this, toolbarTitle) + // Content view will be set by individual activities using view binding initialization() + utils.addSupportActionBar(this, toolbarTitle) } protected abstract fun initialization() @@ -43,7 +143,7 @@ abstract class BaseActivity(view: Int, title: Int) : AppCompatActivity(), View.O override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) - if (resultCode == RESULT_OK && data != null) { + if (resultCode == RESULT_OK && data != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { mediaFiles = data.getParcelableArrayListExtra(FilePickerActivity.MEDIA_FILES) (this as FileSelection).selectedFiles(mediaFiles,requestCode) } diff --git a/app/src/main/java/com/simform/videoimageeditor/MainActivity.kt b/app/src/main/java/com/simform/videoimageeditor/MainActivity.kt index 39905d7..76fcdaa 100644 --- a/app/src/main/java/com/simform/videoimageeditor/MainActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/MainActivity.kt @@ -1,18 +1,27 @@ package com.simform.videoimageeditor import android.view.View +import androidx.activity.enableEdgeToEdge +import com.simform.videoimageeditor.databinding.ActivityMainBinding import com.simform.videoimageeditor.middlewareActivity.OtherFFMPEGProcessActivity import com.simform.videoimageeditor.middlewareActivity.VideoProcessActivity -import kotlinx.android.synthetic.main.activity_main.imageGifOperation -import kotlinx.android.synthetic.main.activity_main.videoOperation +import com.simform.videoimageeditor.utils.enableEdgeToEdge class MainActivity : BaseActivity(R.layout.activity_main, R.string.ffpmeg_title) { + + private lateinit var binding: ActivityMainBinding + override fun initialization() { - supportActionBar?.title = getString(R.string.ffpmeg_title) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + enableEdgeToEdge(binding.toolbar.root) + binding.toolbar.textTitle.text = getString(R.string.ffpmeg_title) supportActionBar?.setDisplayHomeAsUpEnabled(false) supportActionBar?.setDisplayShowHomeEnabled(false) - videoOperation.setOnClickListener(this) - imageGifOperation.setOnClickListener(this) + binding.apply { + videoOperation.setOnClickListener(this@MainActivity) + imageGifOperation.setOnClickListener(this@MainActivity) + } } override fun onClick(v: View?) { diff --git a/app/src/main/java/com/simform/videoimageeditor/middlewareActivity/OtherFFMPEGProcessActivity.kt b/app/src/main/java/com/simform/videoimageeditor/middlewareActivity/OtherFFMPEGProcessActivity.kt index 62460af..33a39f4 100644 --- a/app/src/main/java/com/simform/videoimageeditor/middlewareActivity/OtherFFMPEGProcessActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/middlewareActivity/OtherFFMPEGProcessActivity.kt @@ -1,30 +1,35 @@ package com.simform.videoimageeditor.middlewareActivity import android.view.View +import androidx.activity.enableEdgeToEdge import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityOtherFfmpegProcessBinding import com.simform.videoimageeditor.otherFFMPEGProcessActivity.AudiosMergeActivity import com.simform.videoimageeditor.otherFFMPEGProcessActivity.ChangeAudioVolumeActivity import com.simform.videoimageeditor.otherFFMPEGProcessActivity.CompressAudioActivity import com.simform.videoimageeditor.otherFFMPEGProcessActivity.CropAudioActivity import com.simform.videoimageeditor.otherFFMPEGProcessActivity.FastAndSlowAudioActivity import com.simform.videoimageeditor.otherFFMPEGProcessActivity.MergeGIFActivity -import kotlinx.android.synthetic.main.activity_other_ffmpeg_process.btnAudiosVolumeUpdate -import kotlinx.android.synthetic.main.activity_other_ffmpeg_process.btnCompressAudio -import kotlinx.android.synthetic.main.activity_other_ffmpeg_process.btnCutAudio -import kotlinx.android.synthetic.main.activity_other_ffmpeg_process.btnFastAndSlowAudio -import kotlinx.android.synthetic.main.activity_other_ffmpeg_process.btnMergeAudios -import kotlinx.android.synthetic.main.activity_other_ffmpeg_process.btnMergeGIF +import com.simform.videoimageeditor.utils.enableEdgeToEdge class OtherFFMPEGProcessActivity : BaseActivity(R.layout.activity_other_ffmpeg_process, R.string.other_ffmpeg_operations) { + + private lateinit var binding: ActivityOtherFfmpegProcessBinding + override fun initialization() { - supportActionBar?.title = getString(R.string.other_ffmpeg_operations) - btnMergeGIF.setOnClickListener(this) - btnMergeAudios.setOnClickListener(this) - btnAudiosVolumeUpdate.setOnClickListener(this) - btnFastAndSlowAudio.setOnClickListener(this) - btnCutAudio.setOnClickListener(this) - btnCompressAudio.setOnClickListener(this) + binding = ActivityOtherFfmpegProcessBinding.inflate(layoutInflater) + setContentView(binding.root) + enableEdgeToEdge(binding.toolbar.root) + binding.toolbar.textTitle.text = getString(R.string.other_ffmpeg_operations) + binding.apply { + btnMergeGIF.setOnClickListener(this@OtherFFMPEGProcessActivity) + btnMergeAudios.setOnClickListener(this@OtherFFMPEGProcessActivity) + btnAudiosVolumeUpdate.setOnClickListener(this@OtherFFMPEGProcessActivity) + btnFastAndSlowAudio.setOnClickListener(this@OtherFFMPEGProcessActivity) + btnCutAudio.setOnClickListener(this@OtherFFMPEGProcessActivity) + btnCompressAudio.setOnClickListener(this@OtherFFMPEGProcessActivity) + } } override fun onClick(v: View?) { diff --git a/app/src/main/java/com/simform/videoimageeditor/middlewareActivity/VideoProcessActivity.kt b/app/src/main/java/com/simform/videoimageeditor/middlewareActivity/VideoProcessActivity.kt index a265433..604bddc 100644 --- a/app/src/main/java/com/simform/videoimageeditor/middlewareActivity/VideoProcessActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/middlewareActivity/VideoProcessActivity.kt @@ -3,35 +3,42 @@ package com.simform.videoimageeditor.middlewareActivity import android.view.View import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityVideoProcessBinding import com.simform.videoimageeditor.videoProcessActivity.* -import kotlinx.android.synthetic.main.activity_video_process.* /** * Created by Ashvin Vavaliya on 29,December,2020 * Simform Solutions Pvt Ltd. */ class VideoProcessActivity : BaseActivity(R.layout.activity_video_process, R.string.video_operations) { + private lateinit var binding: ActivityVideoProcessBinding + override fun initialization() { + binding = ActivityVideoProcessBinding.inflate(layoutInflater) + setContentView(binding.root) + supportActionBar?.title = getString(R.string.video_operations) - btnCutVideo.setOnClickListener(this) - btnImageToVideo.setOnClickListener(this) - btnAddWaterMarkOnVideo.setOnClickListener(this) - btnCombineImageVideo.setOnClickListener(this) - btnCombineImages.setOnClickListener(this) - btnCombineVideos.setOnClickListener(this) - btnCompressVideo.setOnClickListener(this) - btnExtractVideo.setOnClickListener(this) - btnExtractAudio.setOnClickListener(this) - btnMotion.setOnClickListener(this) - btnReverseVideo.setOnClickListener(this) - btnFadeInFadeOutVideo.setOnClickListener(this) - btnVideoConvertIntoGIF.setOnClickListener(this) - btnVideoRotateFlip.setOnClickListener(this) - btnMergeVideoAndAudio.setOnClickListener(this) - btnAddTextOnVideo.setOnClickListener(this) - btnRemoveAudioFromVideo.setOnClickListener(this) - btnMergeImageAndAudio.setOnClickListener(this) - btnSetAspectRatio.setOnClickListener(this) + binding.apply { + btnCutVideo.setOnClickListener(this@VideoProcessActivity) + btnImageToVideo.setOnClickListener(this@VideoProcessActivity) + btnAddWaterMarkOnVideo.setOnClickListener(this@VideoProcessActivity) + btnCombineImageVideo.setOnClickListener(this@VideoProcessActivity) + btnCombineImages.setOnClickListener(this@VideoProcessActivity) + btnCombineVideos.setOnClickListener(this@VideoProcessActivity) + btnCompressVideo.setOnClickListener(this@VideoProcessActivity) + btnExtractVideo.setOnClickListener(this@VideoProcessActivity) + btnExtractAudio.setOnClickListener(this@VideoProcessActivity) + btnMotion.setOnClickListener(this@VideoProcessActivity) + btnReverseVideo.setOnClickListener(this@VideoProcessActivity) + btnFadeInFadeOutVideo.setOnClickListener(this@VideoProcessActivity) + btnVideoConvertIntoGIF.setOnClickListener(this@VideoProcessActivity) + btnVideoRotateFlip.setOnClickListener(this@VideoProcessActivity) + btnMergeVideoAndAudio.setOnClickListener(this@VideoProcessActivity) + btnAddTextOnVideo.setOnClickListener(this@VideoProcessActivity) + btnRemoveAudioFromVideo.setOnClickListener(this@VideoProcessActivity) + btnMergeImageAndAudio.setOnClickListener(this@VideoProcessActivity) + btnSetAspectRatio.setOnClickListener(this@VideoProcessActivity) + } } override fun onClick(v: View?) { diff --git a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/AudiosMergeActivity.kt b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/AudiosMergeActivity.kt index 020c436..3d7e548 100644 --- a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/AudiosMergeActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/AudiosMergeActivity.kt @@ -1,35 +1,47 @@ package com.simform.videoimageeditor.otherFFMPEGProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.view.View import android.widget.Toast import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityAudiosMergeBinding +import com.simform.videoimageeditor.utils.enableEdgeToEdge import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.Common.DURATION_FIRST import com.simform.videooperations.FFmpegCallBack -import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage import com.simform.videooperations.Paths -import kotlinx.android.synthetic.main.activity_audios_merge.btnAudioPath -import kotlinx.android.synthetic.main.activity_audios_merge.btnMerge -import kotlinx.android.synthetic.main.activity_audios_merge.mProgressView -import kotlinx.android.synthetic.main.activity_audios_merge.tvInputPathAudio -import kotlinx.android.synthetic.main.activity_audios_merge.tvOutputPath class AudiosMergeActivity : BaseActivity(R.layout.activity_audios_merge, R.string.merge_audios) { + private lateinit var binding: ActivityAudiosMergeBinding private var isInputAudioSelected: Boolean = false + override fun initialization() { - btnAudioPath.setOnClickListener(this) - btnMerge.setOnClickListener(this) + binding = ActivityAudiosMergeBinding.inflate(layoutInflater) + setContentView(binding.root) + enableEdgeToEdge(binding.toolbar.root) + binding.toolbar.textTitle.text = getString(R.string.merge_audios) + binding.btnAudioPath.setOnClickListener(this) + binding.btnMerge.setOnClickListener(this) } override fun onClick(v: View?) { when (v?.id) { R.id.btnAudioPath -> { - Common.selectFile(this, maxSelection = 10, isImageSelection = false, isAudioSelection = true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickMultipleAudio.launch(arrayOf("audio/*")) + } else { + Common.selectFile( + this, + maxSelection = 10, + isImageSelection = false, + isAudioSelection = true + ) + } } R.id.btnMerge -> { mediaFiles?.size?.let { @@ -50,7 +62,12 @@ class AudiosMergeActivity : BaseActivity(R.layout.activity_audios_merge, R.strin mediaFiles?.let { for (element in it) { val paths = Paths() - paths.filePath = element.path + paths.filePath = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, element.uri) ?: "" + } else { + element.path ?: "" + } paths.isImageFile = true pathsList.add(paths) } @@ -59,11 +76,11 @@ class AudiosMergeActivity : BaseActivity(R.layout.activity_audios_merge, R.strin CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -79,15 +96,19 @@ class AudiosMergeActivity : BaseActivity(R.layout.activity_audios_merge, R.strin } private fun processStop() { - btnAudioPath.isEnabled = true - btnMerge.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnAudioPath.isEnabled = true + btnMerge.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnAudioPath.isEnabled = false - btnMerge.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnAudioPath.isEnabled = false + btnMerge.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } @SuppressLint("NewApi") @@ -97,7 +118,7 @@ class AudiosMergeActivity : BaseActivity(R.layout.activity_audios_merge, R.strin if (mediaFiles != null && mediaFiles.isNotEmpty()) { val size: Int = mediaFiles.size if (size > 1) { - tvInputPathAudio.text = "$size Audio selected" + binding.tvInputPathAudio.text = "$size Audio selected" isInputAudioSelected = true } else { Toast.makeText(this, getString(R.string.min_audio_selection_validation), Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/ChangeAudioVolumeActivity.kt b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/ChangeAudioVolumeActivity.kt index f40ceb0..a84ae06 100644 --- a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/ChangeAudioVolumeActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/ChangeAudioVolumeActivity.kt @@ -1,33 +1,45 @@ package com.simform.videoimageeditor.otherFFMPEGProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.view.View import android.widget.Toast import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityChangeAudioValumeBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack -import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_change_audio_valume.btnAudioPath -import kotlinx.android.synthetic.main.activity_change_audio_valume.btnChange -import kotlinx.android.synthetic.main.activity_change_audio_valume.mProgressView -import kotlinx.android.synthetic.main.activity_change_audio_valume.tvInputPathAudio -import kotlinx.android.synthetic.main.activity_change_audio_valume.tvOutputPath class ChangeAudioVolumeActivity : BaseActivity(R.layout.activity_change_audio_valume, R.string.change_audio_volume) { + private lateinit var binding: ActivityChangeAudioValumeBinding private var isInputAudioSelected: Boolean = false + override fun initialization() { - btnAudioPath.setOnClickListener(this) - btnChange.setOnClickListener(this) + binding = ActivityChangeAudioValumeBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnAudioPath.setOnClickListener(this@ChangeAudioVolumeActivity) + btnChange.setOnClickListener(this@ChangeAudioVolumeActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnAudioPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickAudio.launch(arrayOf("audio/*")) + } else { + Common.selectFile( + this, + maxSelection = 1, + isImageSelection = false, + isAudioSelection = true + ) + } } R.id.btnChange -> { mediaFiles?.size?.let { @@ -44,14 +56,14 @@ class ChangeAudioVolumeActivity : BaseActivity(R.layout.activity_change_audio_va private fun mergeAudioProcess() { val outputPath = Common.getFilePath(this, Common.MP3) - val query = ffmpegQueryExtension.audioVolumeUpdate(tvInputPathAudio.text.toString(), volume = 0.1f, output = outputPath) + val query = ffmpegQueryExtension.audioVolumeUpdate(binding.tvInputPathAudio.text.toString(), volume = 0.1f, output = outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -66,15 +78,19 @@ class ChangeAudioVolumeActivity : BaseActivity(R.layout.activity_change_audio_va } private fun processStop() { - btnAudioPath.isEnabled = true - btnChange.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnAudioPath.isEnabled = true + btnChange.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnAudioPath.isEnabled = false - btnChange.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnAudioPath.isEnabled = false + btnChange.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } @SuppressLint("NewApi") @@ -82,7 +98,12 @@ class ChangeAudioVolumeActivity : BaseActivity(R.layout.activity_change_audio_va when (requestCode) { Common.AUDIO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathAudio.text = mediaFiles[0].path + binding.tvInputPathAudio.text = + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path ?: "" + } isInputAudioSelected = true } else { Toast.makeText(this, getString(R.string.min_audio_selection_validation), Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/CompressAudioActivity.kt b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/CompressAudioActivity.kt index 8e67c43..2cf3ce8 100644 --- a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/CompressAudioActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/CompressAudioActivity.kt @@ -1,34 +1,47 @@ package com.simform.videoimageeditor.otherFFMPEGProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.view.View import android.widget.Toast import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityCompressAudioBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.Common.BITRATE_128 import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_compress_audio.btnAudioPath -import kotlinx.android.synthetic.main.activity_compress_audio.btnChange -import kotlinx.android.synthetic.main.activity_compress_audio.mProgressView -import kotlinx.android.synthetic.main.activity_compress_audio.tvInputPathAudio -import kotlinx.android.synthetic.main.activity_compress_audio.tvOutputPath class CompressAudioActivity : BaseActivity(R.layout.activity_compress_audio, R.string.compress_audio) { + private lateinit var binding: ActivityCompressAudioBinding private var isInputAudioSelected: Boolean = false + override fun initialization() { - btnAudioPath.setOnClickListener(this) - btnChange.setOnClickListener(this) + binding = ActivityCompressAudioBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnAudioPath.setOnClickListener(this@CompressAudioActivity) + btnChange.setOnClickListener(this@CompressAudioActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnAudioPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickAudio.launch(arrayOf("audio/*")) + } else { + Common.selectFile( + this, + maxSelection = 1, + isImageSelection = false, + isAudioSelection = true + ) + } } R.id.btnChange -> { mediaFiles?.size?.let { @@ -45,14 +58,14 @@ class CompressAudioActivity : BaseActivity(R.layout.activity_compress_audio, R.s private fun compressAudioProcess() { val outputPath = Common.getFilePath(this, Common.MP3) - val query = ffmpegQueryExtension.compressAudio(inputAudioPath = tvInputPathAudio.text.toString(), bitrate = BITRATE_128, output = outputPath) + val query = ffmpegQueryExtension.compressAudio(inputAudioPath = binding.tvInputPathAudio.text.toString(), bitrate = BITRATE_128, output = outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -67,15 +80,19 @@ class CompressAudioActivity : BaseActivity(R.layout.activity_compress_audio, R.s } private fun processStop() { - btnAudioPath.isEnabled = true - btnChange.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnAudioPath.isEnabled = true + btnChange.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnAudioPath.isEnabled = false - btnChange.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnAudioPath.isEnabled = false + btnChange.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } @SuppressLint("NewApi") @@ -83,7 +100,12 @@ class CompressAudioActivity : BaseActivity(R.layout.activity_compress_audio, R.s when (requestCode) { Common.AUDIO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathAudio.text = mediaFiles[0].path + binding.tvInputPathAudio.text = + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path ?: "" + } isInputAudioSelected = true } else { Toast.makeText(this, getString(R.string.min_audio_selection_validation), Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/CropAudioActivity.kt b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/CropAudioActivity.kt index 7eb8f79..5287667 100644 --- a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/CropAudioActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/CropAudioActivity.kt @@ -1,6 +1,7 @@ package com.simform.videoimageeditor.otherFFMPEGProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.text.TextUtils import android.view.View import android.widget.TextView @@ -8,53 +9,63 @@ import android.widget.Toast import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityCropAudioBinding import com.simform.videoimageeditor.ikovac.timepickerwithseconds.MyTimePickerDialog +import com.simform.videoimageeditor.utils.enableEdgeToEdge import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common +import com.simform.videooperations.Common.stringForTime import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.LogMessage +import java.io.File import java.text.ParseException import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -import kotlinx.android.synthetic.main.activity_crop_audio.btnAudioPath -import kotlinx.android.synthetic.main.activity_crop_audio.btnConvert -import kotlinx.android.synthetic.main.activity_crop_audio.btnSelectEndTime -import kotlinx.android.synthetic.main.activity_crop_audio.btnSelectStartTime -import kotlinx.android.synthetic.main.activity_crop_audio.edtEndTime -import kotlinx.android.synthetic.main.activity_crop_audio.edtStartTime -import kotlinx.android.synthetic.main.activity_crop_audio.mProgressView -import kotlinx.android.synthetic.main.activity_crop_audio.tvInputPath -import kotlinx.android.synthetic.main.activity_crop_audio.tvMaxTime -import kotlinx.android.synthetic.main.activity_crop_audio.tvOutputPath class CropAudioActivity : BaseActivity(R.layout.activity_crop_audio, R.string.crop_audio_using_time) { private var startTimeString: String? = null private var endTimeString: String? = null private var maxTimeString: String? = null + private lateinit var binding: ActivityCropAudioBinding override fun initialization() { - btnAudioPath.setOnClickListener(this) - btnSelectStartTime.setOnClickListener(this) - btnSelectEndTime.setOnClickListener(this) - btnConvert.setOnClickListener(this) + binding = ActivityCropAudioBinding.inflate(layoutInflater) + setContentView(binding.root) + enableEdgeToEdge(binding.toolbar.root) + binding.toolbar.textTitle.text = getString(R.string.crop_audio_using_time) + binding.apply { + btnAudioPath.setOnClickListener(this@CropAudioActivity) + btnSelectStartTime.setOnClickListener(this@CropAudioActivity) + btnSelectEndTime.setOnClickListener(this@CropAudioActivity) + btnConvert.setOnClickListener(this@CropAudioActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnAudioPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickAudio.launch(arrayOf("audio/*")) + } else { + Common.selectFile( + this, + maxSelection = 1, + isImageSelection = false, + isAudioSelection = true + ) + } } R.id.btnSelectStartTime -> { if (!TextUtils.isEmpty(maxTimeString) && !TextUtils.equals(maxTimeString, getString(R.string.zero_time))) { - selectTime(edtStartTime, true) + selectTime(binding.edtStartTime, true) } else { Toast.makeText(this, getString(R.string.input_audio_validate_message), Toast.LENGTH_SHORT).show() } } R.id.btnSelectEndTime -> { if (!TextUtils.isEmpty(maxTimeString) && !TextUtils.equals(maxTimeString, getString(R.string.zero_time))) { - selectTime(edtEndTime, false) + selectTime(binding.edtEndTime, false) } else { Toast.makeText(this, getString(R.string.input_audio_validate_message), Toast.LENGTH_SHORT).show() } @@ -87,9 +98,21 @@ class CropAudioActivity : BaseActivity(R.layout.activity_crop_audio, R.string.cr override fun selectedFiles(mediaFiles: List?, requestCode: Int) { if (requestCode == Common.AUDIO_FILE_REQUEST_CODE) { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPath.text = mediaFiles[0].path - maxTimeString = Common.stringForTime(mediaFiles[0].duration) - tvMaxTime.text = "Selected audio max time : $maxTimeString" + binding.tvInputPath.text = + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path ?: "" + } + maxTimeString = + stringForTime( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.getDurationFromFile(File(binding.tvInputPath.text.toString())) + } else { + mediaFiles[0].duration + } + ) + binding.tvMaxTime.text = "Selected audio max time : $maxTimeString" } else { Toast.makeText(this, getString(R.string.audio_not_selected_toast_message), Toast.LENGTH_SHORT).show() } @@ -145,14 +168,14 @@ class CropAudioActivity : BaseActivity(R.layout.activity_crop_audio, R.string.cr @SuppressLint("SetTextI18n") private fun cutProcess() { val outputPath = Common.getFilePath(this, Common.MP3) - val query = ffmpegQueryExtension.cutAudio(tvInputPath.text.toString(), startTimeString, endTimeString, outputPath) + val query = ffmpegQueryExtension.cutAudio(binding.tvInputPath.text.toString(), startTimeString, endTimeString, outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -167,18 +190,22 @@ class CropAudioActivity : BaseActivity(R.layout.activity_crop_audio, R.string.cr } private fun processStop() { - btnAudioPath.isEnabled = true - btnSelectStartTime.isEnabled = true - btnSelectEndTime.isEnabled = true - btnConvert.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnAudioPath.isEnabled = true + btnSelectStartTime.isEnabled = true + btnSelectEndTime.isEnabled = true + btnConvert.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnAudioPath.isEnabled = false - btnSelectStartTime.isEnabled = false - btnSelectEndTime.isEnabled = false - btnConvert.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnAudioPath.isEnabled = false + btnSelectStartTime.isEnabled = false + btnSelectEndTime.isEnabled = false + btnConvert.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/FastAndSlowAudioActivity.kt b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/FastAndSlowAudioActivity.kt index ff07164..b77153a 100644 --- a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/FastAndSlowAudioActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/FastAndSlowAudioActivity.kt @@ -1,33 +1,45 @@ package com.simform.videoimageeditor.otherFFMPEGProcessActivity +import android.os.Build import android.view.View import android.widget.Toast import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityFastAndSlowAudioBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_fast_and_slow_audio.btnAudioPath -import kotlinx.android.synthetic.main.activity_fast_and_slow_audio.btnMotion -import kotlinx.android.synthetic.main.activity_fast_and_slow_audio.mProgressView -import kotlinx.android.synthetic.main.activity_fast_and_slow_audio.motionType -import kotlinx.android.synthetic.main.activity_fast_and_slow_audio.tvInputPathAudio -import kotlinx.android.synthetic.main.activity_fast_and_slow_audio.tvOutputPath class FastAndSlowAudioActivity : BaseActivity(R.layout.activity_fast_and_slow_audio, R.string.fast_slow_motion_video) { + private lateinit var binding: ActivityFastAndSlowAudioBinding private var isInputAudioSelected: Boolean = false + override fun initialization() { - btnAudioPath.setOnClickListener(this) - btnMotion.setOnClickListener(this) + binding = ActivityFastAndSlowAudioBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnAudioPath.setOnClickListener(this@FastAndSlowAudioActivity) + btnMotion.setOnClickListener(this@FastAndSlowAudioActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnAudioPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickAudio.launch(arrayOf("audio/*")) + } else { + Common.selectFile( + this, + maxSelection = 1, + isImageSelection = false, + isAudioSelection = true + ) + } } R.id.btnMotion -> { when { @@ -46,17 +58,17 @@ class FastAndSlowAudioActivity : BaseActivity(R.layout.activity_fast_and_slow_au private fun motionProcess() { val outputPath = Common.getFilePath(this, Common.MP3) var atempo = 2.0 - if (!motionType.isChecked) { + if (!binding.motionType.isChecked) { atempo = 0.5 } - val query = ffmpegQueryExtension.audioMotion(tvInputPathAudio.text.toString(), outputPath, atempo) + val query = ffmpegQueryExtension.audioMotion(binding.tvInputPathAudio.text.toString(), outputPath, atempo) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -74,7 +86,12 @@ class FastAndSlowAudioActivity : BaseActivity(R.layout.activity_fast_and_slow_au when (requestCode) { Common.AUDIO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathAudio.text = mediaFiles[0].path + binding.tvInputPathAudio.text = + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path ?: "" + } isInputAudioSelected = true } else { Toast.makeText(this, getString(R.string.audio_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -84,14 +101,18 @@ class FastAndSlowAudioActivity : BaseActivity(R.layout.activity_fast_and_slow_au } private fun processStop() { - btnAudioPath.isEnabled = true - btnMotion.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnAudioPath.isEnabled = true + btnMotion.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnAudioPath.isEnabled = false - btnMotion.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnAudioPath.isEnabled = false + btnMotion.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/MergeGIFActivity.kt b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/MergeGIFActivity.kt index d30e8ac..0b31abd 100644 --- a/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/MergeGIFActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/MergeGIFActivity.kt @@ -1,6 +1,7 @@ package com.simform.videoimageeditor.otherFFMPEGProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.text.TextUtils import android.view.View import android.widget.ImageView @@ -14,69 +15,79 @@ import com.bumptech.glide.request.transition.Transition import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityMergeGifBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack -import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage import com.simform.videooperations.Paths import java.io.File -import kotlinx.android.synthetic.main.activity_merge_gif.btnGifPath -import kotlinx.android.synthetic.main.activity_merge_gif.btnMerge -import kotlinx.android.synthetic.main.activity_merge_gif.edtXPos -import kotlinx.android.synthetic.main.activity_merge_gif.edtXScale -import kotlinx.android.synthetic.main.activity_merge_gif.edtYPos -import kotlinx.android.synthetic.main.activity_merge_gif.edtYScale -import kotlinx.android.synthetic.main.activity_merge_gif.mFirstGif -import kotlinx.android.synthetic.main.activity_merge_gif.mProgressView -import kotlinx.android.synthetic.main.activity_merge_gif.tvInputPathGif -import kotlinx.android.synthetic.main.activity_merge_gif.tvOutputPath +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts class MergeGIFActivity : BaseActivity(R.layout.activity_merge_gif, R.string.merge_gif) { + private lateinit var binding: ActivityMergeGifBinding private var isInputGifSelected: Boolean = false + override fun initialization() { - btnGifPath.setOnClickListener(this) - btnMerge.setOnClickListener(this) + binding = ActivityMergeGifBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnGifPath.setOnClickListener(this@MergeGIFActivity) + btnMerge.setOnClickListener(this@MergeGIFActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnGifPath -> { - Common.selectFile(this, maxSelection = 2, isImageSelection = true, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickMultipleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + } else { + Common.selectFile( + this, + maxSelection = 2, + isImageSelection = true, + isAudioSelection = false + ) + } } R.id.btnMerge -> { - when { - !isInputGifSelected -> { - Toast.makeText(this, getString(R.string.input_gif_validate_message), Toast.LENGTH_SHORT).show() - } - TextUtils.isEmpty(edtXPos.text.toString()) -> { - Toast.makeText(this, getString(R.string.x_position_validation), Toast.LENGTH_SHORT).show() - } - edtXPos.text.toString().toFloat() > 100 || edtXPos.text.toString().toFloat() <= 0 -> { - Toast.makeText(this, getString(R.string.x_validation_invalid), Toast.LENGTH_SHORT).show() - } - TextUtils.isEmpty(edtYPos.text.toString()) -> { - Toast.makeText(this, getString(R.string.y_position_validation), Toast.LENGTH_SHORT).show() - } - edtYPos.text.toString().toFloat() > 100 || edtYPos.text.toString().toFloat() <= 0 -> { - Toast.makeText(this, getString(R.string.y_validation_invalid), Toast.LENGTH_SHORT).show() - } - TextUtils.isEmpty(edtXScale.text.toString()) -> { - Toast.makeText(this, getString(R.string.x_width_validation), Toast.LENGTH_SHORT).show() - } - edtXScale.text.toString().toFloat() > 100 || edtXScale.text.toString().toFloat() <= 0 -> { - Toast.makeText(this, getString(R.string.x_width_invalid), Toast.LENGTH_SHORT).show() - } - TextUtils.isEmpty(edtYScale.text.toString()) -> { - Toast.makeText(this, getString(R.string.y_height_validation), Toast.LENGTH_SHORT).show() - } - edtYScale.text.toString().toFloat() > 100 || edtYScale.text.toString().toFloat() <= 0 -> { - Toast.makeText(this, getString(R.string.y_height_invalid), Toast.LENGTH_SHORT).show() - } - else -> { - processStart() - combineGifProcess() + with(binding) { + when { + !isInputGifSelected -> { + Toast.makeText(this@MergeGIFActivity, getString(R.string.input_gif_validate_message), Toast.LENGTH_SHORT).show() + } + TextUtils.isEmpty(edtXPos.text.toString()) -> { + Toast.makeText(this@MergeGIFActivity, getString(R.string.x_position_validation), Toast.LENGTH_SHORT).show() + } + edtXPos.text.toString().toFloat() > 100 || edtXPos.text.toString().toFloat() <= 0 -> { + Toast.makeText(this@MergeGIFActivity, getString(R.string.x_validation_invalid), Toast.LENGTH_SHORT).show() + } + TextUtils.isEmpty(edtYPos.text.toString()) -> { + Toast.makeText(this@MergeGIFActivity, getString(R.string.y_position_validation), Toast.LENGTH_SHORT).show() + } + edtYPos.text.toString().toFloat() > 100 || edtYPos.text.toString().toFloat() <= 0 -> { + Toast.makeText(this@MergeGIFActivity, getString(R.string.y_validation_invalid), Toast.LENGTH_SHORT).show() + } + TextUtils.isEmpty(edtXScale.text.toString()) -> { + Toast.makeText(this@MergeGIFActivity, getString(R.string.x_width_validation), Toast.LENGTH_SHORT).show() + } + edtXScale.text.toString().toFloat() > 100 || edtXScale.text.toString().toFloat() <= 0 -> { + Toast.makeText(this@MergeGIFActivity, getString(R.string.x_width_invalid), Toast.LENGTH_SHORT).show() + } + TextUtils.isEmpty(edtYScale.text.toString()) -> { + Toast.makeText(this@MergeGIFActivity, getString(R.string.y_height_validation), Toast.LENGTH_SHORT).show() + } + edtYScale.text.toString().toFloat() > 100 || edtYScale.text.toString().toFloat() <= 0 -> { + Toast.makeText(this@MergeGIFActivity, getString(R.string.y_height_invalid), Toast.LENGTH_SHORT).show() + } + else -> { + processStart() + combineGifProcess() + } } } } @@ -89,33 +100,38 @@ class MergeGIFActivity : BaseActivity(R.layout.activity_merge_gif, R.string.merg mediaFiles?.let { for (element in it) { val paths = Paths() - paths.filePath = element.path + paths.filePath = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, element.uri) ?: "" + } else { + element.path + } paths.isImageFile = true pathsList.add(paths) } val xPos = width?.let { width -> - (edtXPos.text.toString().toFloat().times(width)).div(100) + (binding.edtXPos.text.toString().toFloat().times(width)).div(100) } val yPos = height?.let { height -> - (edtYPos.text.toString().toFloat().times(height)).div(100) + (binding.edtYPos.text.toString().toFloat().times(height)).div(100) } val widthScale = width?.let { width -> - (edtXScale.text.toString().toFloat().times(width)).div(100) + (binding.edtXScale.text.toString().toFloat().times(width)).div(100) } val heightScale = height?.let { height -> - (edtYScale.text.toString().toFloat().times(height)).div(100) + (binding.edtYScale.text.toString().toFloat().times(height)).div(100) } val query = ffmpegQueryExtension.mergeGIF(pathsList, xPos, yPos, widthScale, heightScale, outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -131,15 +147,19 @@ class MergeGIFActivity : BaseActivity(R.layout.activity_merge_gif, R.string.merg } private fun processStop() { - btnGifPath.isEnabled = true - btnMerge.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnGifPath.isEnabled = true + btnMerge.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnGifPath.isEnabled = false - btnMerge.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnGifPath.isEnabled = false + btnMerge.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } @SuppressLint("NewApi") @@ -150,8 +170,15 @@ class MergeGIFActivity : BaseActivity(R.layout.activity_merge_gif, R.string.merg val size: Int = mediaFiles.size var isGifFile = true for (i in 0 until size) { - if (File(mediaFiles[i].path).extension != "gif") { - isGifFile = false + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (mediaFiles[i].mimeType != "image/gif") { + isGifFile = false + } + } else { + // For older versions, check the file extension + if (File(mediaFiles[i].path).extension != "gif") { + isGifFile = false + } } } if (size == 2 && isGifFile) { @@ -159,13 +186,13 @@ class MergeGIFActivity : BaseActivity(R.layout.activity_merge_gif, R.string.merg .asGif() .apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.RESOURCE)) .load(mediaFiles[0].path) - .into(object : ViewTarget(mFirstGif) { + .into(object : ViewTarget(binding.mFirstGif) { override fun onResourceReady(gifDrawable: GifDrawable, transition: Transition?) { width = gifDrawable.intrinsicWidth height = gifDrawable.intrinsicHeight } }) - tvInputPathGif.text = "$size GIF selected" + binding.tvInputPathGif.text = "$size GIF selected" isInputGifSelected = true } else if (size != 2) { Toast.makeText(this, getString(R.string.please_selected_minimum_2_gif_file), Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/simform/videoimageeditor/utils/ExtensionsUtils.kt b/app/src/main/java/com/simform/videoimageeditor/utils/ExtensionsUtils.kt new file mode 100644 index 0000000..1e5ab18 --- /dev/null +++ b/app/src/main/java/com/simform/videoimageeditor/utils/ExtensionsUtils.kt @@ -0,0 +1,21 @@ +package com.simform.videoimageeditor.utils + +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat + +fun AppCompatActivity.enableEdgeToEdge(view: View?) { + view?.let { + ViewCompat.setOnApplyWindowInsetsListener(it) { view, windowInsets -> + val systemBarInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + view.setPadding( + systemBarInsets.left, + systemBarInsets.top, + systemBarInsets.right, + 0 + ) + windowInsets + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AddTextOnVideoActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AddTextOnVideoActivity.kt index 2261a6e..d451aac 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AddTextOnVideoActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AddTextOnVideoActivity.kt @@ -2,65 +2,75 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint import android.media.MediaMetadataRetriever +import android.os.Build import android.text.TextUtils import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityAddTextOnVideoBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.Common.getFileFromAssets import com.simform.videooperations.FFmpegCallBack -import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage import java.util.concurrent.CompletableFuture.runAsync -import kotlinx.android.synthetic.main.activity_add_text_on_video.btnAdd -import kotlinx.android.synthetic.main.activity_add_text_on_video.btnVideoPath -import kotlinx.android.synthetic.main.activity_add_text_on_video.edtText -import kotlinx.android.synthetic.main.activity_add_text_on_video.edtXPos -import kotlinx.android.synthetic.main.activity_add_text_on_video.edtYPos -import kotlinx.android.synthetic.main.activity_add_text_on_video.mProgressView -import kotlinx.android.synthetic.main.activity_add_text_on_video.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_add_text_on_video.tvOutputPath class AddTextOnVideoActivity : BaseActivity(R.layout.activity_add_text_on_video, R.string.add_text_on_video) { + private lateinit var binding: ActivityAddTextOnVideoBinding private var isInputVideoSelected = false + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnAdd.setOnClickListener(this) + binding = ActivityAddTextOnVideoBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@AddTextOnVideoActivity) + btnAdd.setOnClickListener(this@AddTextOnVideoActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 13 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnAdd -> { - when { - !isInputVideoSelected -> { - Toast.makeText(this, getString(R.string.input_video_validate_message), Toast.LENGTH_SHORT).show() - } - TextUtils.isEmpty(edtText.text.toString()) -> { - Toast.makeText(this, getString(R.string.please_add_text_validation), Toast.LENGTH_SHORT).show() - } - TextUtils.isEmpty(edtXPos.text.toString()) -> { - Toast.makeText(this, getString(R.string.x_position_validation), Toast.LENGTH_SHORT).show() - } - edtXPos.text.toString().toFloat() > 100 || edtXPos.text.toString().toFloat() <= 0 -> { - Toast.makeText(this, getString(R.string.x_validation_invalid), Toast.LENGTH_SHORT).show() - } - TextUtils.isEmpty(edtYPos.text.toString()) -> { - Toast.makeText(this, getString(R.string.y_position_validation), Toast.LENGTH_SHORT).show() - } - edtYPos.text.toString().toFloat() > 100 || edtYPos.text.toString().toFloat() <= 0 -> { - Toast.makeText(this, getString(R.string.y_validation_invalid), Toast.LENGTH_SHORT).show() - } - else -> { - processStart() - addTextProcess() + with(binding) { + when { + !isInputVideoSelected -> { + Toast.makeText(this@AddTextOnVideoActivity, getString(R.string.input_video_validate_message), Toast.LENGTH_SHORT).show() + } + TextUtils.isEmpty(edtText.text.toString()) -> { + Toast.makeText(this@AddTextOnVideoActivity, getString(R.string.please_add_text_validation), Toast.LENGTH_SHORT).show() + } + TextUtils.isEmpty(edtXPos.text.toString()) -> { + Toast.makeText(this@AddTextOnVideoActivity, getString(R.string.x_position_validation), Toast.LENGTH_SHORT).show() + } + edtXPos.text.toString().toFloat() > 100 || edtXPos.text.toString().toFloat() <= 0 -> { + Toast.makeText(this@AddTextOnVideoActivity, getString(R.string.x_validation_invalid), Toast.LENGTH_SHORT).show() + } + TextUtils.isEmpty(edtYPos.text.toString()) -> { + Toast.makeText(this@AddTextOnVideoActivity, getString(R.string.y_position_validation), Toast.LENGTH_SHORT).show() + } + edtYPos.text.toString().toFloat() > 100 || edtYPos.text.toString().toFloat() <= 0 -> { + Toast.makeText(this@AddTextOnVideoActivity, getString(R.string.y_validation_invalid), Toast.LENGTH_SHORT).show() + } + else -> { + processStart() + addTextProcess() + } } } + } } } @@ -68,20 +78,20 @@ class AddTextOnVideoActivity : BaseActivity(R.layout.activity_add_text_on_video, private fun addTextProcess() { val outputPath = Common.getFilePath(this, Common.VIDEO) val xPos = width?.let { - (edtXPos.text.toString().toFloat().times(it)).div(100) + (binding.edtXPos.text.toString().toFloat().times(it)).div(100) } val yPos = height?.let { - (edtYPos.text.toString().toFloat().times(it)).div(100) + (binding.edtYPos.text.toString().toFloat().times(it)).div(100) } val fontPath = getFileFromAssets(this, "little_lord.ttf").absolutePath - val query = ffmpegQueryExtension.addTextOnVideo(tvInputPathVideo.text.toString(), edtText.text.toString(), xPos, yPos, fontPath = fontPath, isTextBackgroundDisplay = true, fontSize = 28, fontcolor = "red", output = outputPath) + val query = ffmpegQueryExtension.addTextOnVideo(binding.tvInputPathVideo.text.toString(), binding.edtText.text.toString(), xPos, yPos, fontPath = fontPath, isTextBackgroundDisplay = true, fontSize = 28, fontcolor = "red", output = outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -96,15 +106,19 @@ class AddTextOnVideoActivity : BaseActivity(R.layout.activity_add_text_on_video, } private fun processStop() { - btnVideoPath.isEnabled = true - btnAdd.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnAdd.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnAdd.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnAdd.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } @SuppressLint("NewApi") @@ -112,11 +126,15 @@ class AddTextOnVideoActivity : BaseActivity(R.layout.activity_add_text_on_video, when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true runAsync { retriever = MediaMetadataRetriever() - retriever?.setDataSource(tvInputPathVideo.text.toString()) + retriever?.setDataSource(binding.tvInputPathVideo.text.toString()) val bit = retriever?.frameAtTime width = bit?.width height = bit?.height diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AddWaterMarkOnVideoActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AddWaterMarkOnVideoActivity.kt index cad0a3a..77e8e60 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AddWaterMarkOnVideoActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AddWaterMarkOnVideoActivity.kt @@ -2,12 +2,16 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint import android.media.MediaMetadataRetriever +import android.os.Build import android.text.TextUtils import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityAddWaterMarkOnVideoBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.Common.VIDEO @@ -16,32 +20,40 @@ import com.simform.videooperations.Common.selectFile import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.LogMessage import java.util.concurrent.CompletableFuture.runAsync -import kotlinx.android.synthetic.main.activity_add_water_mark_on_video.btnAdd -import kotlinx.android.synthetic.main.activity_add_water_mark_on_video.btnImagePath -import kotlinx.android.synthetic.main.activity_add_water_mark_on_video.btnVideoPath -import kotlinx.android.synthetic.main.activity_add_water_mark_on_video.edtXPos -import kotlinx.android.synthetic.main.activity_add_water_mark_on_video.edtYPos -import kotlinx.android.synthetic.main.activity_add_water_mark_on_video.mProgressView -import kotlinx.android.synthetic.main.activity_add_water_mark_on_video.tvInputPathImage -import kotlinx.android.synthetic.main.activity_add_water_mark_on_video.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_add_water_mark_on_video.tvOutputPath class AddWaterMarkOnVideoActivity : BaseActivity(R.layout.activity_add_water_mark_on_video, R.string.add_water_mark_on_video) { + private lateinit var binding: ActivityAddWaterMarkOnVideoBinding private var isInputVideoSelected = false private var isWaterMarkImageSelected = false + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnImagePath.setOnClickListener(this) - btnAdd.setOnClickListener(this) + binding = ActivityAddWaterMarkOnVideoBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@AddWaterMarkOnVideoActivity) + btnImagePath.setOnClickListener(this@AddWaterMarkOnVideoActivity) + btnAdd.setOnClickListener(this@AddWaterMarkOnVideoActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 13 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnImagePath -> { - selectFile(this, maxSelection = 1, isImageSelection = true, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + } else { + // Fallback for devices below Android 13 + Common.selectFile(this, maxSelection = 1, isImageSelection = true, isAudioSelection = false) + } } R.id.btnAdd -> { when { @@ -51,16 +63,16 @@ class AddWaterMarkOnVideoActivity : BaseActivity(R.layout.activity_add_water_mar !isWaterMarkImageSelected -> { Toast.makeText(this, getString(R.string.input_image_validate_message), Toast.LENGTH_SHORT).show() } - TextUtils.isEmpty(edtXPos.text.toString()) -> { + TextUtils.isEmpty(binding.edtXPos.text.toString()) -> { Toast.makeText(this, getString(R.string.x_position_validation), Toast.LENGTH_SHORT).show() } - edtXPos.text.toString().toFloat() > 100 || edtXPos.text.toString().toFloat() <= 0 -> { + binding.edtXPos.text.toString().toFloat() > 100 || binding.edtXPos.text.toString().toFloat() <= 0 -> { Toast.makeText(this, getString(R.string.x_validation_invalid), Toast.LENGTH_SHORT).show() } - TextUtils.isEmpty(edtYPos.text.toString()) -> { + TextUtils.isEmpty(binding.edtYPos.text.toString()) -> { Toast.makeText(this, getString(R.string.y_position_validation), Toast.LENGTH_SHORT).show() } - edtYPos.text.toString().toFloat() > 100 || edtYPos.text.toString().toFloat() <= 0 -> { + binding.edtYPos.text.toString().toFloat() > 100 || binding.edtYPos.text.toString().toFloat() <= 0 -> { Toast.makeText(this, getString(R.string.y_validation_invalid), Toast.LENGTH_SHORT).show() } else -> { @@ -77,11 +89,16 @@ class AddWaterMarkOnVideoActivity : BaseActivity(R.layout.activity_add_water_mar when (fileRequestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true runAsync { retriever = MediaMetadataRetriever() - retriever?.setDataSource(tvInputPathVideo.text.toString()) + retriever?.setDataSource(binding.tvInputPathVideo.text.toString()) val bit = retriever?.frameAtTime width = bit?.width height = bit?.height @@ -92,7 +109,12 @@ class AddWaterMarkOnVideoActivity : BaseActivity(R.layout.activity_add_water_mar } Common.IMAGE_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathImage.text = mediaFiles[0].path + binding.tvInputPathImage.text = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isWaterMarkImageSelected = true } else { Toast.makeText(this, getString(R.string.image_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -104,19 +126,19 @@ class AddWaterMarkOnVideoActivity : BaseActivity(R.layout.activity_add_water_mar private fun addWaterMarkProcess() { val outputPath = getFilePath(this, VIDEO) val xPos = width?.let { - (edtXPos.text.toString().toFloat().times(it)).div(100) + (binding.edtXPos.text.toString().toFloat().times(it)).div(100) } val yPos = height?.let { - (edtYPos.text.toString().toFloat().times(it)).div(100) + (binding.edtYPos.text.toString().toFloat().times(it)).div(100) } - val query = ffmpegQueryExtension.addVideoWaterMark(tvInputPathVideo.text.toString(), tvInputPathImage.text.toString(), xPos, yPos, outputPath) + val query = ffmpegQueryExtension.addVideoWaterMark(binding.tvInputPathVideo.text.toString(), binding.tvInputPathImage.text.toString(), xPos, yPos, outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -131,16 +153,20 @@ class AddWaterMarkOnVideoActivity : BaseActivity(R.layout.activity_add_water_mar } private fun processStop() { - btnVideoPath.isEnabled = true - btnImagePath.isEnabled = true - btnAdd.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnImagePath.isEnabled = true + btnAdd.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnImagePath.isEnabled = false - btnAdd.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnImagePath.isEnabled = false + btnAdd.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AspectRatioActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AspectRatioActivity.kt index 17431e6..420d7d8 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AspectRatioActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AspectRatioActivity.kt @@ -1,34 +1,44 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityAspectRatioBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.Common.RATIO_1 import com.simform.videooperations.FFmpegCallBack -import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_aspect_ratio.btnAspectRatio -import kotlinx.android.synthetic.main.activity_aspect_ratio.btnVideoPath -import kotlinx.android.synthetic.main.activity_aspect_ratio.mProgressView -import kotlinx.android.synthetic.main.activity_aspect_ratio.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_aspect_ratio.tvOutputPath class AspectRatioActivity : BaseActivity(R.layout.activity_aspect_ratio, R.string.apply_aspect_ratio) { + private lateinit var binding: ActivityAspectRatioBinding private var isInputVideoSelected: Boolean = false + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnAspectRatio.setOnClickListener(this) + binding = ActivityAspectRatioBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@AspectRatioActivity) + btnAspectRatio.setOnClickListener(this@AspectRatioActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 13 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnAspectRatio -> { when { @@ -46,15 +56,15 @@ class AspectRatioActivity : BaseActivity(R.layout.activity_aspect_ratio, R.strin private fun applyRatioProcess() { val outputPath = Common.getFilePath(this, Common.VIDEO) - val query = ffmpegQueryExtension.applyRatio(tvInputPathVideo.text.toString(), RATIO_1, outputPath) + val query = ffmpegQueryExtension.applyRatio(binding.tvInputPathVideo.text.toString(), RATIO_1, outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -74,7 +84,11 @@ class AspectRatioActivity : BaseActivity(R.layout.activity_aspect_ratio, R.strin when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -84,15 +98,19 @@ class AspectRatioActivity : BaseActivity(R.layout.activity_aspect_ratio, R.strin } private fun processStop() { - btnVideoPath.isEnabled = true - btnAspectRatio.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnAspectRatio.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnAspectRatio.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnAspectRatio.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineImageAndVideoActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineImageAndVideoActivity.kt index 596cf15..1d31b8f 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineImageAndVideoActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineImageAndVideoActivity.kt @@ -2,44 +2,58 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint import android.media.MediaMetadataRetriever +import android.os.Build import android.text.TextUtils import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityMergeImageAndVideoBinding +import com.simform.videoimageeditor.utils.enableEdgeToEdge import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.LogMessage import com.simform.videooperations.Paths import java.util.concurrent.CompletableFuture.runAsync -import kotlinx.android.synthetic.main.activity_merge_image_and_video.btnCombine -import kotlinx.android.synthetic.main.activity_merge_image_and_video.btnImagePath -import kotlinx.android.synthetic.main.activity_merge_image_and_video.btnVideoPath -import kotlinx.android.synthetic.main.activity_merge_image_and_video.edtSecond -import kotlinx.android.synthetic.main.activity_merge_image_and_video.mProgressView -import kotlinx.android.synthetic.main.activity_merge_image_and_video.tvInputPathImage -import kotlinx.android.synthetic.main.activity_merge_image_and_video.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_merge_image_and_video.tvOutputPath class CombineImageAndVideoActivity : BaseActivity(R.layout.activity_merge_image_and_video, R.string.merge_image_and_video) { + private lateinit var binding: ActivityMergeImageAndVideoBinding private var isInputVideoSelected = false private var isWaterMarkImageSelected = false override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnImagePath.setOnClickListener(this) - btnCombine.setOnClickListener(this) + binding = ActivityMergeImageAndVideoBinding.inflate(layoutInflater) + setContentView(binding.root) + enableEdgeToEdge(binding.toolbar.root) + binding.toolbar.textTitle.text = getString(R.string.merge_image_and_video) + binding.apply { + btnVideoPath.setOnClickListener(this@CombineImageAndVideoActivity) + btnImagePath.setOnClickListener(this@CombineImageAndVideoActivity) + btnCombine.setOnClickListener(this@CombineImageAndVideoActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnImagePath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = true, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = true, isAudioSelection = false) + } } R.id.btnCombine -> { when { @@ -49,7 +63,7 @@ class CombineImageAndVideoActivity : BaseActivity(R.layout.activity_merge_image_ !isWaterMarkImageSelected -> { Toast.makeText(this, getString(R.string.input_image_validate_message), Toast.LENGTH_SHORT).show() } - TextUtils.isEmpty(edtSecond.text.toString().trim()) || edtSecond.text.toString().trim().toInt() == 0 -> { + TextUtils.isEmpty(binding.edtSecond.text.toString().trim()) || binding.edtSecond.text.toString().trim().toInt() == 0 -> { Toast.makeText(this, getString(R.string.please_enter_second), Toast.LENGTH_SHORT).show() } else -> { @@ -66,11 +80,16 @@ class CombineImageAndVideoActivity : BaseActivity(R.layout.activity_merge_image_ when (fileRequestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true runAsync { retriever = MediaMetadataRetriever() - retriever?.setDataSource(tvInputPathVideo.text.toString()) + retriever?.setDataSource(binding.tvInputPathVideo.text.toString()) val bit = retriever?.frameAtTime width = bit?.width height = bit?.height @@ -81,7 +100,12 @@ class CombineImageAndVideoActivity : BaseActivity(R.layout.activity_merge_image_ } Common.IMAGE_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathImage.text = mediaFiles[0].path + binding.tvInputPathImage.text = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isWaterMarkImageSelected = true } else { Toast.makeText(this, getString(R.string.image_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -95,25 +119,25 @@ class CombineImageAndVideoActivity : BaseActivity(R.layout.activity_merge_image_ val paths = ArrayList() val videoPaths1 = Paths() - videoPaths1.filePath = tvInputPathImage.text.toString() + videoPaths1.filePath = binding.tvInputPathImage.text.toString() videoPaths1.isImageFile = true val videoPaths2 = Paths() - videoPaths2.filePath = tvInputPathVideo.text.toString() + videoPaths2.filePath = binding.tvInputPathVideo.text.toString() videoPaths2.isImageFile = false paths.add(videoPaths1) paths.add(videoPaths2) - val query = ffmpegQueryExtension.combineImagesAndVideos(paths, width, height, edtSecond.text.toString(), outputPath) + val query = ffmpegQueryExtension.combineImagesAndVideos(paths, width, height, binding.edtSecond.text.toString(), outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -128,16 +152,20 @@ class CombineImageAndVideoActivity : BaseActivity(R.layout.activity_merge_image_ } private fun processStop() { - btnVideoPath.isEnabled = true - btnImagePath.isEnabled = true - btnCombine.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnImagePath.isEnabled = true + btnCombine.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnImagePath.isEnabled = false - btnCombine.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnImagePath.isEnabled = false + btnCombine.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineImagesActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineImagesActivity.kt index 99cd490..f3cd0ac 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineImagesActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineImagesActivity.kt @@ -1,43 +1,56 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.text.TextUtils import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityCombineImagesBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage import com.simform.videooperations.Paths -import kotlinx.android.synthetic.main.activity_combine_images.btnCombine -import kotlinx.android.synthetic.main.activity_combine_images.btnImagePath -import kotlinx.android.synthetic.main.activity_combine_images.edtSecond -import kotlinx.android.synthetic.main.activity_combine_images.mProgressView -import kotlinx.android.synthetic.main.activity_combine_images.tvInputPathImage -import kotlinx.android.synthetic.main.activity_combine_images.tvOutputPath class CombineImagesActivity : BaseActivity(R.layout.activity_combine_images, R.string.merge_images) { + private lateinit var binding: ActivityCombineImagesBinding private var isImageSelected: Boolean = false + override fun initialization() { - btnImagePath.setOnClickListener(this) - btnCombine.setOnClickListener(this) + binding = ActivityCombineImagesBinding.inflate(layoutInflater) + setContentView(binding.root) + binding.apply { + btnImagePath.setOnClickListener(this@CombineImagesActivity) + btnCombine.setOnClickListener(this@CombineImagesActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnImagePath -> { - Common.selectFile(this, maxSelection = 25, isImageSelection = true, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickMultipleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + } else { + Common.selectFile( + this, + maxSelection = 25, + isImageSelection = true, + isAudioSelection = false + ) + } } R.id.btnCombine -> { when { !isImageSelected -> { Toast.makeText(this, getString(R.string.input_image_validate_message), Toast.LENGTH_SHORT).show() } - TextUtils.isEmpty(edtSecond.text.toString().trim()) || edtSecond.text.toString().trim().toInt() == 0 -> { + TextUtils.isEmpty(binding.edtSecond.text.toString().trim()) || binding.edtSecond.text.toString().trim().toInt() == 0 -> { Toast.makeText(this, getString(R.string.please_enter_second), Toast.LENGTH_SHORT).show() } else -> { @@ -55,7 +68,7 @@ class CombineImagesActivity : BaseActivity(R.layout.activity_combine_images, R.s Common.IMAGE_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { val size: Int = mediaFiles.size - tvInputPathImage.text = "$size" + (if (size == 1) " Image " else " Images ") + "selected" + binding.tvInputPathImage.text = "$size" + (if (size == 1) " Image " else " Images ") + "selected" isImageSelected = true } else { Toast.makeText(this, getString(R.string.image_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -65,15 +78,21 @@ class CombineImagesActivity : BaseActivity(R.layout.activity_combine_images, R.s } private fun processStop() { - btnImagePath.isEnabled = true - btnCombine.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnImagePath.isEnabled = true + btnCombine.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnImagePath.isEnabled = false - btnCombine.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + binding.apply { + btnImagePath.isEnabled = false + btnCombine.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } + } } private fun combineImagesProcess() { @@ -82,20 +101,25 @@ class CombineImagesActivity : BaseActivity(R.layout.activity_combine_images, R.s mediaFiles?.let { for (element in it) { val paths = Paths() - paths.filePath = element.path + paths.filePath = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, element.uri) ?: "" + } else { + element.path + } paths.isImageFile = true pathsList.add(paths) } - val query = ffmpegQueryExtension.combineImagesAndVideos(pathsList, 640, 480, edtSecond.text.toString(), outputPath) + val query = ffmpegQueryExtension.combineImagesAndVideos(pathsList, 640, 480, binding.edtSecond.text.toString(), outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineVideosActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineVideosActivity.kt index 1a8e96b..6a7f8d3 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineVideosActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CombineVideosActivity.kt @@ -2,37 +2,44 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint import android.media.MediaMetadataRetriever +import android.os.Build import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityCombineVideosBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack -import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage import com.simform.videooperations.Paths import java.util.concurrent.CompletableFuture -import kotlinx.android.synthetic.main.activity_combine_videos.btnCombine -import kotlinx.android.synthetic.main.activity_combine_videos.btnVideoPath -import kotlinx.android.synthetic.main.activity_combine_videos.mProgressView -import kotlinx.android.synthetic.main.activity_combine_videos.tvInputPathImage -import kotlinx.android.synthetic.main.activity_combine_videos.tvOutputPath -import kotlinx.android.synthetic.main.activity_merge_image_and_video.tvInputPathVideo class CombineVideosActivity : BaseActivity(R.layout.activity_combine_videos, R.string.merge_videos) { + private lateinit var binding: ActivityCombineVideosBinding private var isVideoSelected: Boolean = false override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnCombine.setOnClickListener(this) + binding = ActivityCombineVideosBinding.inflate(layoutInflater) + setContentView(binding.root) + binding.apply { + btnVideoPath.setOnClickListener(this@CombineVideosActivity) + btnCombine.setOnClickListener(this@CombineVideosActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 5, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickMultipleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 5, isImageSelection = false, isAudioSelection = false) + } } R.id.btnCombine -> { when { @@ -54,11 +61,18 @@ class CombineVideosActivity : BaseActivity(R.layout.activity_combine_videos, R.s Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { val size: Int = mediaFiles.size - tvInputPathImage.text = "$size" + (if (size == 1) " Video " else " Videos ") + "selected" + binding.tvInputPathImage.text = "$size" + (if (size == 1) " Video " else " Videos ") + "selected" + this.mediaFiles = mediaFiles isVideoSelected = true CompletableFuture.runAsync { retriever = MediaMetadataRetriever() - retriever?.setDataSource(tvInputPathVideo.text.toString()) + retriever?.setDataSource( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) ?: "" + } else { + mediaFiles[0].path + } + ) val bit = retriever?.frameAtTime if (bit != null) { width = bit.width @@ -73,15 +87,19 @@ class CombineVideosActivity : BaseActivity(R.layout.activity_combine_videos, R.s } private fun processStop() { - btnVideoPath.isEnabled = true - btnCombine.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnCombine.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnCombine.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnCombine.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } private fun combineVideosProcess() { @@ -90,7 +108,13 @@ class CombineVideosActivity : BaseActivity(R.layout.activity_combine_videos, R.s mediaFiles?.let { for (element in it) { val paths = Paths() - paths.filePath = element.path + paths.filePath = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, element.uri) ?: "" + } else { + element.path + } + paths.isImageFile = false pathsList.add(paths) } @@ -103,11 +127,11 @@ class CombineVideosActivity : BaseActivity(R.layout.activity_combine_videos, R.s ) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CompressVideoActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CompressVideoActivity.kt index 9361390..b763165 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CompressVideoActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CompressVideoActivity.kt @@ -2,37 +2,50 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint import android.media.MediaMetadataRetriever +import android.os.Build import android.view.View import android.widget.Toast +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityCompressVideoBinding +import com.simform.videoimageeditor.utils.enableEdgeToEdge import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack -import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage import java.io.File import java.util.concurrent.CompletableFuture -import kotlinx.android.synthetic.main.activity_compress_video.btnCompress -import kotlinx.android.synthetic.main.activity_compress_video.btnVideoPath -import kotlinx.android.synthetic.main.activity_compress_video.inputFileSize -import kotlinx.android.synthetic.main.activity_compress_video.mProgressView -import kotlinx.android.synthetic.main.activity_compress_video.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_compress_video.tvOutputPath class CompressVideoActivity : BaseActivity(R.layout.activity_compress_video, R.string.compress_a_video) { private var isInputVideoSelected: Boolean = false + private lateinit var binding: ActivityCompressVideoBinding override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnCompress.setOnClickListener(this) + binding = ActivityCompressVideoBinding.inflate(layoutInflater) + setContentView(binding.root) + enableEdgeToEdge(binding.toolbar.root) + binding.toolbar.textTitle.text = getString(R.string.compress_a_video) + + binding.apply { + btnVideoPath.setOnClickListener(this@CompressVideoActivity) + btnCompress.setOnClickListener(this@CompressVideoActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + // check if device is 14 or plus + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnCompress -> { when { @@ -53,16 +66,21 @@ class CompressVideoActivity : BaseActivity(R.layout.activity_compress_video, R.s when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } + isInputVideoSelected = true CompletableFuture.runAsync { retriever = MediaMetadataRetriever() - retriever?.setDataSource(tvInputPathVideo.text.toString()) + retriever?.setDataSource(binding.tvInputPathVideo.text.toString()) val bit = retriever?.frameAtTime width = bit?.width height = bit?.height } - inputFileSize.text = "Input file Size : ${Common.getFileSize(File(tvInputPathVideo.text.toString()))}" + binding.inputFileSize.text = "Input file Size : ${Common.getFileSize(File(binding.tvInputPathVideo.text.toString()))}" } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() } @@ -72,15 +90,15 @@ class CompressVideoActivity : BaseActivity(R.layout.activity_compress_video, R.s private fun compressProcess() { val outputPath = Common.getFilePath(this, Common.VIDEO) - val query = ffmpegQueryExtension.compressor(tvInputPathVideo.text.toString(), width, height, outputPath) + val query = ffmpegQueryExtension.compressor(binding.tvInputPathVideo.text.toString(), width, height, outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } @SuppressLint("SetTextI18n") override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path_with_size), outputPath, Common.getFileSize(File(outputPath))) + binding.tvOutputPath.text = String.format(getString(R.string.output_path_with_size), outputPath, Common.getFileSize(File(outputPath))) processStop() } @@ -95,14 +113,18 @@ class CompressVideoActivity : BaseActivity(R.layout.activity_compress_video, R.s } private fun processStop() { - btnVideoPath.isEnabled = true - btnCompress.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnCompress.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnCompress.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnCompress.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CutVideoUsingTimeActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CutVideoUsingTimeActivity.kt index 73e23c7..177820b 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CutVideoUsingTimeActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/CutVideoUsingTimeActivity.kt @@ -1,14 +1,20 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.text.TextUtils import android.view.View import android.widget.TextView import android.widget.Toast +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityCutVideoUsingTimeBinding import com.simform.videoimageeditor.ikovac.timepickerwithseconds.MyTimePickerDialog +import com.simform.videoimageeditor.utils.enableEdgeToEdge import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.Common.TIME_FORMAT @@ -16,49 +22,54 @@ import com.simform.videooperations.Common.VIDEO_FILE_REQUEST_CODE import com.simform.videooperations.Common.stringForTime import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.LogMessage +import java.io.File import java.text.ParseException import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -import kotlinx.android.synthetic.main.activity_cut_video_using_time.btnConvert -import kotlinx.android.synthetic.main.activity_cut_video_using_time.btnSelectEndTime -import kotlinx.android.synthetic.main.activity_cut_video_using_time.btnSelectStartTime -import kotlinx.android.synthetic.main.activity_cut_video_using_time.btnVideoPath -import kotlinx.android.synthetic.main.activity_cut_video_using_time.edtEndTime -import kotlinx.android.synthetic.main.activity_cut_video_using_time.edtStartTime -import kotlinx.android.synthetic.main.activity_cut_video_using_time.mProgressView -import kotlinx.android.synthetic.main.activity_cut_video_using_time.tvInputPath -import kotlinx.android.synthetic.main.activity_cut_video_using_time.tvMaxTime -import kotlinx.android.synthetic.main.activity_cut_video_using_time.tvOutputPath class CutVideoUsingTimeActivity : BaseActivity(R.layout.activity_cut_video_using_time, R.string.cut_video_using_time) { private var startTimeString: String? = null private var endTimeString: String? = null private var maxTimeString: String? = null + private lateinit var binding: ActivityCutVideoUsingTimeBinding override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnSelectStartTime.setOnClickListener(this) - btnSelectEndTime.setOnClickListener(this) - btnConvert.setOnClickListener(this) + binding = ActivityCutVideoUsingTimeBinding.inflate(layoutInflater) + setContentView(binding.root) + enableEdgeToEdge(binding.toolbar.root) + binding.toolbar.textTitle.text = getString(R.string.cut_video_using_time) + + binding.apply { + btnVideoPath.setOnClickListener(this@CutVideoUsingTimeActivity) + btnSelectStartTime.setOnClickListener(this@CutVideoUsingTimeActivity) + btnSelectEndTime.setOnClickListener(this@CutVideoUsingTimeActivity) + btnConvert.setOnClickListener(this@CutVideoUsingTimeActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + // check if device is 14 or plus + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnSelectStartTime -> { if (!TextUtils.isEmpty(maxTimeString) && !TextUtils.equals(maxTimeString, getString(R.string.zero_time))) { - selectTime(edtStartTime, true) + selectTime(binding.edtStartTime, true) } else { Toast.makeText(this, getString(R.string.input_video_validate_message), Toast.LENGTH_SHORT).show() } } R.id.btnSelectEndTime -> { if (!TextUtils.isEmpty(maxTimeString) && !TextUtils.equals(maxTimeString, getString(R.string.zero_time))) { - selectTime(edtEndTime, false) + selectTime(binding.edtEndTime, false) } else { Toast.makeText(this, getString(R.string.input_video_validate_message), Toast.LENGTH_SHORT).show() } @@ -91,9 +102,21 @@ class CutVideoUsingTimeActivity : BaseActivity(R.layout.activity_cut_video_using override fun selectedFiles(mediaFiles: List?, requestCode: Int) { if (requestCode == VIDEO_FILE_REQUEST_CODE) { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPath.text = mediaFiles[0].path - maxTimeString = stringForTime(mediaFiles[0].duration) - tvMaxTime.text = "Selected video max time : $maxTimeString" + binding.tvInputPath.text = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } + maxTimeString = + stringForTime( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.getDurationFromFile(File(binding.tvInputPath.text.toString())) + } else { + mediaFiles[0].duration + } + ) + binding.tvMaxTime.text = "Selected video max time : $maxTimeString" } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() } @@ -149,14 +172,14 @@ class CutVideoUsingTimeActivity : BaseActivity(R.layout.activity_cut_video_using @SuppressLint("SetTextI18n") private fun cutProcess() { val outputPath = Common.getFilePath(this, Common.VIDEO) - val query = ffmpegQueryExtension.cutVideo(tvInputPath.text.toString(), startTimeString, endTimeString, outputPath) + val query = ffmpegQueryExtension.cutVideo(binding.tvInputPath.text.toString(), startTimeString, endTimeString, outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -171,18 +194,22 @@ class CutVideoUsingTimeActivity : BaseActivity(R.layout.activity_cut_video_using } private fun processStop() { - btnVideoPath.isEnabled = true - btnSelectStartTime.isEnabled = true - btnSelectEndTime.isEnabled = true - btnConvert.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnSelectStartTime.isEnabled = true + btnSelectEndTime.isEnabled = true + btnConvert.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnSelectStartTime.isEnabled = false - btnSelectEndTime.isEnabled = false - btnConvert.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnSelectStartTime.isEnabled = false + btnSelectEndTime.isEnabled = false + btnConvert.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ExtractAudioActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ExtractAudioActivity.kt index 635a322..f543f26 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ExtractAudioActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ExtractAudioActivity.kt @@ -5,22 +5,25 @@ import android.widget.Toast import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityExtractAudioBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_extract_audio.btnExtract -import kotlinx.android.synthetic.main.activity_extract_audio.btnVideoPath -import kotlinx.android.synthetic.main.activity_extract_audio.mProgressView -import kotlinx.android.synthetic.main.activity_extract_audio.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_extract_audio.tvOutputPath -class ExtractAudioActivity : BaseActivity(R.layout.activity_extract_audio, R.string.extract_audio_from_video) { +class ExtractAudioActivity : BaseActivity(R.layout.activity_extract_audio, R.string.extract_audio) { + private lateinit var binding: ActivityExtractAudioBinding private var isInputVideoSelected: Boolean = false + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnExtract.setOnClickListener(this) + binding = ActivityExtractAudioBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@ExtractAudioActivity) + btnExtract.setOnClickListener(this@ExtractAudioActivity) + } } override fun onClick(v: View?) { @@ -35,24 +38,24 @@ class ExtractAudioActivity : BaseActivity(R.layout.activity_extract_audio, R.str } else -> { processStart() - extractProcess() + extractAudioProcess() } } } } } - private fun extractProcess() { + private fun extractAudioProcess() { val outputPath = Common.getFilePath(this, Common.MP3) - val query = ffmpegQueryExtension.extractAudio(tvInputPathVideo.text.toString(), outputPath) + val query = ffmpegQueryExtension.extractAudio(binding.tvInputPathVideo.text.toString(), outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -70,7 +73,7 @@ class ExtractAudioActivity : BaseActivity(R.layout.activity_extract_audio, R.str when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = mediaFiles[0].path isInputVideoSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -80,14 +83,18 @@ class ExtractAudioActivity : BaseActivity(R.layout.activity_extract_audio, R.str } private fun processStop() { - btnVideoPath.isEnabled = true - btnExtract.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnExtract.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnExtract.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnExtract.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ExtractImagesActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ExtractImagesActivity.kt index 9d9c436..a38d59f 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ExtractImagesActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ExtractImagesActivity.kt @@ -1,34 +1,45 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityExtractImagesBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.Statistics import java.io.File -import kotlinx.android.synthetic.main.activity_extract_images.btnExtract -import kotlinx.android.synthetic.main.activity_extract_images.btnVideoPath -import kotlinx.android.synthetic.main.activity_extract_images.mProgressView -import kotlinx.android.synthetic.main.activity_extract_images.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_extract_images.tvOutputPath class ExtractImagesActivity : BaseActivity(R.layout.activity_extract_images, R.string.extract_frame_from_video) { + private lateinit var binding: ActivityExtractImagesBinding private var isInputVideoSelected: Boolean = false + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnExtract.setOnClickListener(this) + binding = ActivityExtractImagesBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@ExtractImagesActivity) + btnExtract.setOnClickListener(this@ExtractImagesActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnExtract -> { when { @@ -49,7 +60,12 @@ class ExtractImagesActivity : BaseActivity(R.layout.activity_extract_images, R.s when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -61,16 +77,16 @@ class ExtractImagesActivity : BaseActivity(R.layout.activity_extract_images, R.s @SuppressLint("SetTextI18n") private fun extractProcess() { val outputPath = Common.getFilePath(this, Common.IMAGE) - val query = ffmpegQueryExtension.extractImages(tvInputPathVideo.text.toString(), outputPath, spaceOfFrame = 4f) + val query = ffmpegQueryExtension.extractImages(binding.tvInputPathVideo.text.toString(), outputPath, spaceOfFrame = 4f) var totalFramesExtracted = 0 CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun statisticsProcess(statistics: Statistics) { totalFramesExtracted = statistics.videoFrameNumber - tvOutputPath.text = "Frames : ${statistics.videoFrameNumber}" + binding.tvOutputPath.text = "Frames : ${statistics.videoFrameNumber}" } override fun success() { - tvOutputPath.text = "Output Directory : \n${File(getExternalFilesDir(Common.OUT_PUT_DIR).toString()).absolutePath} \n\nTotal Frames Extracted: $totalFramesExtracted" + binding.tvOutputPath.text = "Output Directory : \n${outputPath} \n\nTotal Frames Extracted: $totalFramesExtracted" processStop() } @@ -85,14 +101,18 @@ class ExtractImagesActivity : BaseActivity(R.layout.activity_extract_images, R.s } private fun processStop() { - btnVideoPath.isEnabled = true - btnExtract.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnExtract.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnExtract.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnExtract.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/FastAndSlowVideoMotionActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/FastAndSlowVideoMotionActivity.kt index 8d813cf..775f74d 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/FastAndSlowVideoMotionActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/FastAndSlowVideoMotionActivity.kt @@ -1,33 +1,43 @@ package com.simform.videoimageeditor.videoProcessActivity +import android.os.Build import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityFastAndSlowVideoMotionBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_fast_and_slow_video_motion.btnMotion -import kotlinx.android.synthetic.main.activity_fast_and_slow_video_motion.btnVideoPath -import kotlinx.android.synthetic.main.activity_fast_and_slow_video_motion.mProgressView -import kotlinx.android.synthetic.main.activity_fast_and_slow_video_motion.motionType -import kotlinx.android.synthetic.main.activity_fast_and_slow_video_motion.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_fast_and_slow_video_motion.tvOutputPath class FastAndSlowVideoMotionActivity : BaseActivity(R.layout.activity_fast_and_slow_video_motion, R.string.fast_slow_motion_video) { + private lateinit var binding: ActivityFastAndSlowVideoMotionBinding private var isInputVideoSelected: Boolean = false + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnMotion.setOnClickListener(this) + binding = ActivityFastAndSlowVideoMotionBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@FastAndSlowVideoMotionActivity) + btnMotion.setOnClickListener(this@FastAndSlowVideoMotionActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnMotion -> { when { @@ -47,18 +57,18 @@ class FastAndSlowVideoMotionActivity : BaseActivity(R.layout.activity_fast_and_s val outputPath = Common.getFilePath(this, Common.VIDEO) var setpts = 0.5 var atempo = 2.0 - if (!motionType.isChecked) { + if (!binding.motionType.isChecked) { setpts = 2.0 atempo = 0.5 } - val query = ffmpegQueryExtension.videoMotion(tvInputPathVideo.text.toString(), outputPath, setpts, atempo) + val query = ffmpegQueryExtension.videoMotion(binding.tvInputPathVideo.text.toString(), outputPath, setpts, atempo) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -76,7 +86,11 @@ class FastAndSlowVideoMotionActivity : BaseActivity(R.layout.activity_fast_and_s when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -86,14 +100,18 @@ class FastAndSlowVideoMotionActivity : BaseActivity(R.layout.activity_fast_and_s } private fun processStop() { - btnVideoPath.isEnabled = true - btnMotion.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnMotion.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnMotion.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnMotion.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ImageToVideoConvertActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ImageToVideoConvertActivity.kt index c79cfc8..4df2198 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ImageToVideoConvertActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ImageToVideoConvertActivity.kt @@ -4,9 +4,12 @@ import android.annotation.SuppressLint import android.text.TextUtils import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityImageToVideoConvertBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack @@ -14,31 +17,37 @@ import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.ISize import com.simform.videooperations.LogMessage import com.simform.videooperations.SizeOfImage -import kotlinx.android.synthetic.main.activity_image_to_video_convert.btnConvert -import kotlinx.android.synthetic.main.activity_image_to_video_convert.btnImagePath -import kotlinx.android.synthetic.main.activity_image_to_video_convert.edtSecond -import kotlinx.android.synthetic.main.activity_image_to_video_convert.mProgressView -import kotlinx.android.synthetic.main.activity_image_to_video_convert.tvInputPath -import kotlinx.android.synthetic.main.activity_image_to_video_convert.tvOutputPath class ImageToVideoConvertActivity : BaseActivity(R.layout.activity_image_to_video_convert, R.string.image_to_video) { + private lateinit var binding: ActivityImageToVideoConvertBinding private var isFileSelected: Boolean = false + override fun initialization() { - btnImagePath.setOnClickListener(this) - btnConvert.setOnClickListener(this) + binding = ActivityImageToVideoConvertBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnImagePath.setOnClickListener(this@ImageToVideoConvertActivity) + btnConvert.setOnClickListener(this@ImageToVideoConvertActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnImagePath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = true, isAudioSelection = false) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = true, isAudioSelection = false) + } } R.id.btnConvert -> { when { !isFileSelected -> { Toast.makeText(this, getString(R.string.input_image_validate_message), Toast.LENGTH_SHORT).show() } - TextUtils.isEmpty(edtSecond.text.toString().trim()) || edtSecond.text.toString().trim().toInt() == 0 -> { + TextUtils.isEmpty(binding.edtSecond.text.toString().trim()) || binding.edtSecond.text.toString().trim().toInt() == 0 -> { Toast.makeText(this, getString(R.string.please_enter_second), Toast.LENGTH_SHORT).show() } else -> { @@ -55,7 +64,12 @@ class ImageToVideoConvertActivity : BaseActivity(R.layout.activity_image_to_vide if (requestCode == Common.IMAGE_FILE_REQUEST_CODE) { if (mediaFiles != null && mediaFiles.isNotEmpty()) { isFileSelected = true - tvInputPath.text = mediaFiles[0].path + binding.tvInputPath.text = + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } } else { isFileSelected = false Toast.makeText(this, getString(R.string.image_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -65,16 +79,16 @@ class ImageToVideoConvertActivity : BaseActivity(R.layout.activity_image_to_vide private fun createVideo() { val outputPath = Common.getFilePath(this, Common.VIDEO) - val size: ISize = SizeOfImage(tvInputPath.text.toString()) - val query = ffmpegQueryExtension.imageToVideo(tvInputPath.text.toString(), outputPath, edtSecond.text.toString().toInt(), size.width(), size.height()) + val size: ISize = SizeOfImage(binding.tvInputPath.text.toString()) + val query = ffmpegQueryExtension.imageToVideo(binding.tvInputPath.text.toString(), outputPath, binding.edtSecond.text.toString().toInt(), size.width(), size.height()) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -90,14 +104,18 @@ class ImageToVideoConvertActivity : BaseActivity(R.layout.activity_image_to_vide } private fun processStop() { - btnImagePath.isEnabled = true - btnConvert.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnImagePath.isEnabled = true + btnConvert.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnImagePath.isEnabled = false - btnConvert.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnImagePath.isEnabled = false + btnConvert.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/MergeAudioVideoActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/MergeAudioVideoActivity.kt index c7c5e8d..399d779 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/MergeAudioVideoActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/MergeAudioVideoActivity.kt @@ -5,26 +5,27 @@ import android.widget.Toast import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityMergeAudioVideoBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_merge_audio_video.btnMerge -import kotlinx.android.synthetic.main.activity_merge_audio_video.btnMp3Path -import kotlinx.android.synthetic.main.activity_merge_audio_video.btnVideoPath -import kotlinx.android.synthetic.main.activity_merge_audio_video.mProgressView -import kotlinx.android.synthetic.main.activity_merge_audio_video.tvInputPathAudio -import kotlinx.android.synthetic.main.activity_merge_audio_video.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_merge_audio_video.tvOutputPath class MergeAudioVideoActivity : BaseActivity(R.layout.activity_merge_audio_video, R.string.merge_video_and_audio) { + private lateinit var binding: ActivityMergeAudioVideoBinding private var isInputVideoSelected: Boolean = false private var isInputAudioSelected: Boolean = false + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnMp3Path.setOnClickListener(this) - btnMerge.setOnClickListener(this) + binding = ActivityMergeAudioVideoBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@MergeAudioVideoActivity) + btnMp3Path.setOnClickListener(this@MergeAudioVideoActivity) + btnMerge.setOnClickListener(this@MergeAudioVideoActivity) + } } override fun onClick(v: View?) { @@ -54,15 +55,15 @@ class MergeAudioVideoActivity : BaseActivity(R.layout.activity_merge_audio_video private fun mergeProcess() { val outputPath = Common.getFilePath(this, Common.VIDEO) - val query = ffmpegQueryExtension.mergeAudioVideo(tvInputPathVideo.text.toString(), tvInputPathAudio.text.toString(), outputPath) + val query = ffmpegQueryExtension.mergeAudioVideo(binding.tvInputPathVideo.text.toString(), binding.tvInputPathAudio.text.toString(), outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -81,7 +82,7 @@ class MergeAudioVideoActivity : BaseActivity(R.layout.activity_merge_audio_video when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = mediaFiles[0].path isInputVideoSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -89,7 +90,7 @@ class MergeAudioVideoActivity : BaseActivity(R.layout.activity_merge_audio_video } Common.AUDIO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathAudio.text = mediaFiles[0].path + binding.tvInputPathAudio.text = mediaFiles[0].path isInputAudioSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -99,16 +100,20 @@ class MergeAudioVideoActivity : BaseActivity(R.layout.activity_merge_audio_video } private fun processStop() { - btnVideoPath.isEnabled = true - btnMp3Path.isEnabled = true - btnMerge.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnMp3Path.isEnabled = true + btnMerge.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnMp3Path.isEnabled = false - btnMerge.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnMp3Path.isEnabled = false + btnMerge.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/MergeImageAndMP3Activity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/MergeImageAndMP3Activity.kt index 3c9dfaf..c2475b9 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/MergeImageAndMP3Activity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/MergeImageAndMP3Activity.kt @@ -5,26 +5,26 @@ import android.widget.Toast import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityMergeImageAndMp3Binding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack -import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_merge_image_and_mp3.btnImagePath -import kotlinx.android.synthetic.main.activity_merge_image_and_mp3.btnMerge -import kotlinx.android.synthetic.main.activity_merge_image_and_mp3.btnMp3Path -import kotlinx.android.synthetic.main.activity_merge_image_and_mp3.mProgressView -import kotlinx.android.synthetic.main.activity_merge_image_and_mp3.tvInputPathAudio -import kotlinx.android.synthetic.main.activity_merge_image_and_mp3.tvInputPathImage -import kotlinx.android.synthetic.main.activity_merge_image_and_mp3.tvOutputPath class MergeImageAndMP3Activity : BaseActivity(R.layout.activity_merge_image_and_mp3, R.string.merge_image_and_audio) { + private lateinit var binding: ActivityMergeImageAndMp3Binding private var isInputImageSelected: Boolean = false private var isInputMP3Selected: Boolean = false + override fun initialization() { - btnImagePath.setOnClickListener(this) - btnMp3Path.setOnClickListener(this) - btnMerge.setOnClickListener(this) + binding = ActivityMergeImageAndMp3Binding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnImagePath.setOnClickListener(this@MergeImageAndMP3Activity) + btnMp3Path.setOnClickListener(this@MergeImageAndMP3Activity) + btnMerge.setOnClickListener(this@MergeImageAndMP3Activity) + } } override fun onClick(v: View?) { @@ -54,15 +54,15 @@ class MergeImageAndMP3Activity : BaseActivity(R.layout.activity_merge_image_and_ private fun mergeProcess() { val outputPath = Common.getFilePath(this, Common.VIDEO) - val query = ffmpegQueryExtension.mergeImageAndAudio(tvInputPathImage.text.toString(), tvInputPathAudio.text.toString(), outputPath) + val query = ffmpegQueryExtension.mergeImageAndAudio(binding.tvInputPathImage.text.toString(), binding.tvInputPathAudio.text.toString(), outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -81,7 +81,7 @@ class MergeImageAndMP3Activity : BaseActivity(R.layout.activity_merge_image_and_ when (requestCode) { Common.IMAGE_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathImage.text = mediaFiles[0].path + binding.tvInputPathImage.text = mediaFiles[0].path isInputImageSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -89,7 +89,7 @@ class MergeImageAndMP3Activity : BaseActivity(R.layout.activity_merge_image_and_ } Common.AUDIO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathAudio.text = mediaFiles[0].path + binding.tvInputPathAudio.text = mediaFiles[0].path isInputMP3Selected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -99,16 +99,20 @@ class MergeImageAndMP3Activity : BaseActivity(R.layout.activity_merge_image_and_ } private fun processStop() { - btnImagePath.isEnabled = true - btnMp3Path.isEnabled = true - btnMerge.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnImagePath.isEnabled = true + btnMp3Path.isEnabled = true + btnMerge.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnImagePath.isEnabled = false - btnMp3Path.isEnabled = false - btnMerge.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnImagePath.isEnabled = false + btnMp3Path.isEnabled = false + btnMerge.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/RemoveAudioFromVideoActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/RemoveAudioFromVideoActivity.kt index a3146a3..00252c2 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/RemoveAudioFromVideoActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/RemoveAudioFromVideoActivity.kt @@ -1,33 +1,44 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityRemoveAudioFromVideoBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_remove_audio_from_video.btnRemove -import kotlinx.android.synthetic.main.activity_remove_audio_from_video.btnVideoPath -import kotlinx.android.synthetic.main.activity_remove_audio_from_video.mProgressView -import kotlinx.android.synthetic.main.activity_remove_audio_from_video.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_remove_audio_from_video.tvOutputPath class RemoveAudioFromVideoActivity : BaseActivity(R.layout.activity_remove_audio_from_video, R.string.audio_remove_from_video) { + private lateinit var binding: ActivityRemoveAudioFromVideoBinding private var isInputVideoSelected: Boolean = false + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnRemove.setOnClickListener(this) + binding = ActivityRemoveAudioFromVideoBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@RemoveAudioFromVideoActivity) + btnRemove.setOnClickListener(this@RemoveAudioFromVideoActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnRemove -> { when { @@ -45,15 +56,15 @@ class RemoveAudioFromVideoActivity : BaseActivity(R.layout.activity_remove_audio private fun removeAudioProcess() { val outputPath = Common.getFilePath(this, Common.VIDEO) - val query = ffmpegQueryExtension.removeAudioFromVideo(tvInputPathVideo.text.toString(), outputPath) + val query = ffmpegQueryExtension.removeAudioFromVideo(binding.tvInputPathVideo.text.toString(), outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -73,7 +84,11 @@ class RemoveAudioFromVideoActivity : BaseActivity(R.layout.activity_remove_audio when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -83,14 +98,18 @@ class RemoveAudioFromVideoActivity : BaseActivity(R.layout.activity_remove_audio } private fun processStop() { - btnVideoPath.isEnabled = true - btnRemove.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnRemove.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnRemove.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnRemove.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ReverseVideoActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ReverseVideoActivity.kt index 5654239..7363076 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ReverseVideoActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/ReverseVideoActivity.kt @@ -1,33 +1,43 @@ package com.simform.videoimageeditor.videoProcessActivity +import android.os.Build import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityReverseBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_reverse.btnMotion -import kotlinx.android.synthetic.main.activity_reverse.btnVideoPath -import kotlinx.android.synthetic.main.activity_reverse.isWithAudioSwitch -import kotlinx.android.synthetic.main.activity_reverse.mProgressView -import kotlinx.android.synthetic.main.activity_reverse.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_reverse.tvOutputPath class ReverseVideoActivity : BaseActivity(R.layout.activity_reverse, R.string.reverse_video) { + private lateinit var binding: ActivityReverseBinding private var isInputVideoSelected: Boolean = false + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnMotion.setOnClickListener(this) + binding = ActivityReverseBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@ReverseVideoActivity) + btnMotion.setOnClickListener(this@ReverseVideoActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnMotion -> { when { @@ -45,15 +55,15 @@ class ReverseVideoActivity : BaseActivity(R.layout.activity_reverse, R.string.re private fun reverseProcess() { val outputPath = Common.getFilePath(this, Common.VIDEO) - val query = ffmpegQueryExtension.videoReverse(tvInputPathVideo.text.toString(), isWithAudioSwitch.isChecked, outputPath) + val query = ffmpegQueryExtension.videoReverse(binding.tvInputPathVideo.text.toString(), binding.isWithAudioSwitch.isChecked, outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -72,7 +82,11 @@ class ReverseVideoActivity : BaseActivity(R.layout.activity_reverse, R.string.re when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -82,14 +96,18 @@ class ReverseVideoActivity : BaseActivity(R.layout.activity_reverse, R.string.re } private fun processStop() { - btnVideoPath.isEnabled = true - btnMotion.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnMotion.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnMotion.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnMotion.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoFadeInFadeOutActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoFadeInFadeOutActivity.kt index f4fe4bc..b367178 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoFadeInFadeOutActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoFadeInFadeOutActivity.kt @@ -2,11 +2,15 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint import android.media.MediaMetadataRetriever +import android.os.Build import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityVideoFadeInFadeOutBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack @@ -14,24 +18,31 @@ import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit -import kotlinx.android.synthetic.main.activity_video_fade_in_fade_out.btnApplyFadeInFadeOut -import kotlinx.android.synthetic.main.activity_video_fade_in_fade_out.btnVideoPath -import kotlinx.android.synthetic.main.activity_video_fade_in_fade_out.mProgressView -import kotlinx.android.synthetic.main.activity_video_fade_in_fade_out.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_video_fade_in_fade_out.tvOutputPath class VideoFadeInFadeOutActivity : BaseActivity(R.layout.activity_video_fade_in_fade_out, R.string.video_fade_in_and_fade_out) { + private lateinit var binding: ActivityVideoFadeInFadeOutBinding private var isInputVideoSelected: Boolean = false private var selectedVideoDurationInSecond = 0L + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnApplyFadeInFadeOut.setOnClickListener(this) + binding = ActivityVideoFadeInFadeOutBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@VideoFadeInFadeOutActivity) + btnApplyFadeInFadeOut.setOnClickListener(this@VideoFadeInFadeOutActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnApplyFadeInFadeOut -> { when { @@ -49,15 +60,15 @@ class VideoFadeInFadeOutActivity : BaseActivity(R.layout.activity_video_fade_in_ private fun fadeInFadeOutProcess() { val outputPath = Common.getFilePath(this, Common.VIDEO) - val query = ffmpegQueryExtension.videoFadeInFadeOut(tvInputPathVideo.text.toString(), selectedVideoDurationInSecond, fadeInEndSeconds = 3, fadeOutStartSeconds = 3, output = outputPath) + val query = ffmpegQueryExtension.videoFadeInFadeOut(binding.tvInputPathVideo.text.toString(), selectedVideoDurationInSecond, fadeInEndSeconds = 3, fadeOutStartSeconds = 3, output = outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -77,11 +88,15 @@ class VideoFadeInFadeOutActivity : BaseActivity(R.layout.activity_video_fade_in_ when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true CompletableFuture.runAsync { retriever = MediaMetadataRetriever() - retriever?.setDataSource(tvInputPathVideo.text.toString()) + retriever?.setDataSource(binding.tvInputPathVideo.text.toString()) val time = retriever?.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) time?.toLong()?.let { selectedVideoDurationInSecond = TimeUnit.MILLISECONDS.toSeconds(it) @@ -95,14 +110,18 @@ class VideoFadeInFadeOutActivity : BaseActivity(R.layout.activity_video_fade_in_ } private fun processStop() { - btnVideoPath.isEnabled = true - btnApplyFadeInFadeOut.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnApplyFadeInFadeOut.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnApplyFadeInFadeOut.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnApplyFadeInFadeOut.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoRotateFlipActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoRotateFlipActivity.kt index 8c02811..d24ae71 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoRotateFlipActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoRotateFlipActivity.kt @@ -1,45 +1,49 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityVideoRotateFlipBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage -import kotlinx.android.synthetic.main.activity_video_rotate_flip.btn90Clockwise -import kotlinx.android.synthetic.main.activity_video_rotate_flip.btn90ClockwiseVerticalFlip -import kotlinx.android.synthetic.main.activity_video_rotate_flip.btn90CounterClockwise -import kotlinx.android.synthetic.main.activity_video_rotate_flip.btn90CounterClockwiseVerticalFlip -import kotlinx.android.synthetic.main.activity_video_rotate_flip.btnRotate180 -import kotlinx.android.synthetic.main.activity_video_rotate_flip.btnRotate270 -import kotlinx.android.synthetic.main.activity_video_rotate_flip.btnRotate90 -import kotlinx.android.synthetic.main.activity_video_rotate_flip.btnVideoPath -import kotlinx.android.synthetic.main.activity_video_rotate_flip.mProgressView -import kotlinx.android.synthetic.main.activity_video_rotate_flip.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_video_rotate_flip.tvOutputPath class VideoRotateFlipActivity : BaseActivity(R.layout.activity_video_rotate_flip, R.string.video_rotate) { + private lateinit var binding: ActivityVideoRotateFlipBinding private var isInputVideoSelected: Boolean = false + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnRotate90.setOnClickListener(this) - btnRotate180.setOnClickListener(this) - btnRotate270.setOnClickListener(this) - btn90CounterClockwiseVerticalFlip.setOnClickListener(this) - btn90Clockwise.setOnClickListener(this) - btn90CounterClockwise.setOnClickListener(this) - btn90ClockwiseVerticalFlip.setOnClickListener(this) + binding = ActivityVideoRotateFlipBinding.inflate(layoutInflater) + setContentView(binding.root) + binding.apply { + btnVideoPath.setOnClickListener(this@VideoRotateFlipActivity) + btnRotate90.setOnClickListener(this@VideoRotateFlipActivity) + btnRotate180.setOnClickListener(this@VideoRotateFlipActivity) + btnRotate270.setOnClickListener(this@VideoRotateFlipActivity) + btn90CounterClockwiseVerticalFlip.setOnClickListener(this@VideoRotateFlipActivity) + btn90Clockwise.setOnClickListener(this@VideoRotateFlipActivity) + btn90CounterClockwise.setOnClickListener(this@VideoRotateFlipActivity) + btn90ClockwiseVerticalFlip.setOnClickListener(this@VideoRotateFlipActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnRotate90 -> { rotateDegree(90, true) @@ -80,18 +84,18 @@ class VideoRotateFlipActivity : BaseActivity(R.layout.activity_video_rotate_flip private fun rotateProcess(degree: Int, isRotate: Boolean) { val outputPath = Common.getFilePath(this, Common.VIDEO) val query = if (isRotate) { - ffmpegQueryExtension.rotateVideo(tvInputPathVideo.text.toString(), degree, outputPath) + ffmpegQueryExtension.rotateVideo(binding.tvInputPathVideo.text.toString(), degree, outputPath) } else { - ffmpegQueryExtension.flipVideo(tvInputPathVideo.text.toString(), degree, outputPath) + ffmpegQueryExtension.flipVideo(binding.tvInputPathVideo.text.toString(), degree, outputPath) } CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -111,7 +115,11 @@ class VideoRotateFlipActivity : BaseActivity(R.layout.activity_video_rotate_flip when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -121,18 +129,22 @@ class VideoRotateFlipActivity : BaseActivity(R.layout.activity_video_rotate_flip } private fun processStop() { - btnVideoPath.isEnabled = true - btnRotate90.isEnabled = true - btnRotate180.isEnabled = true - btnRotate270.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnRotate90.isEnabled = true + btnRotate180.isEnabled = true + btnRotate270.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnRotate90.isEnabled = false - btnRotate180.isEnabled = false - btnRotate270.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnRotate90.isEnabled = false + btnRotate180.isEnabled = false + btnRotate270.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoToGifActivity.kt b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoToGifActivity.kt index fcf4850..1518bd9 100644 --- a/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoToGifActivity.kt +++ b/app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/VideoToGifActivity.kt @@ -1,34 +1,45 @@ package com.simform.videoimageeditor.videoProcessActivity import android.annotation.SuppressLint +import android.os.Build import android.view.View import android.widget.Toast +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import com.jaiselrahman.filepicker.model.MediaFile import com.simform.videoimageeditor.BaseActivity import com.simform.videoimageeditor.R +import com.simform.videoimageeditor.databinding.ActivityVideoToGifBinding import com.simform.videooperations.CallBackOfQuery import com.simform.videooperations.Common import com.simform.videooperations.FFmpegCallBack import com.simform.videooperations.FFmpegQueryExtension import com.simform.videooperations.LogMessage import java.util.concurrent.CyclicBarrier -import kotlinx.android.synthetic.main.activity_video_to_gif.btnConvertIntoGif -import kotlinx.android.synthetic.main.activity_video_to_gif.btnVideoPath -import kotlinx.android.synthetic.main.activity_video_to_gif.mProgressView -import kotlinx.android.synthetic.main.activity_video_to_gif.tvInputPathVideo -import kotlinx.android.synthetic.main.activity_video_to_gif.tvOutputPath class VideoToGifActivity : BaseActivity(R.layout.activity_video_to_gif, R.string.video_to_gif) { private var isInputVideoSelected: Boolean = false + private lateinit var binding: ActivityVideoToGifBinding + override fun initialization() { - btnVideoPath.setOnClickListener(this) - btnConvertIntoGif.setOnClickListener(this) + binding = ActivityVideoToGifBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.apply { + btnVideoPath.setOnClickListener(this@VideoToGifActivity) + btnConvertIntoGif.setOnClickListener(this@VideoToGifActivity) + } } override fun onClick(v: View?) { when (v?.id) { R.id.btnVideoPath -> { - Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) + } else { + // Fallback for devices below Android 14 + Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false) + } } R.id.btnConvertIntoGif -> { when { @@ -46,15 +57,15 @@ class VideoToGifActivity : BaseActivity(R.layout.activity_video_to_gif, R.string private fun convertProcess() { val outputPath = Common.getFilePath(this, Common.GIF) - val query = ffmpegQueryExtension.convertVideoToGIF(tvInputPathVideo.text.toString(), outputPath) + val query = ffmpegQueryExtension.convertVideoToGIF(binding.tvInputPathVideo.text.toString(), outputPath) CallBackOfQuery().callQuery(query, object : FFmpegCallBack { override fun process(logMessage: LogMessage) { - tvOutputPath.text = logMessage.text + binding.tvOutputPath.text = logMessage.text } override fun success() { - tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) + binding.tvOutputPath.text = String.format(getString(R.string.output_path), outputPath) processStop() } @@ -74,7 +85,11 @@ class VideoToGifActivity : BaseActivity(R.layout.activity_video_to_gif, R.string when (requestCode) { Common.VIDEO_FILE_REQUEST_CODE -> { if (mediaFiles != null && mediaFiles.isNotEmpty()) { - tvInputPathVideo.text = mediaFiles[0].path + binding.tvInputPathVideo.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri) + } else { + mediaFiles[0].path + } isInputVideoSelected = true } else { Toast.makeText(this, getString(R.string.video_not_selected_toast_message), Toast.LENGTH_SHORT).show() @@ -84,14 +99,18 @@ class VideoToGifActivity : BaseActivity(R.layout.activity_video_to_gif, R.string } private fun processStop() { - btnVideoPath.isEnabled = true - btnConvertIntoGif.isEnabled = true - mProgressView.visibility = View.GONE + binding.apply { + btnVideoPath.isEnabled = true + btnConvertIntoGif.isEnabled = true + mProgressView.root.visibility = View.GONE + } } private fun processStart() { - btnVideoPath.isEnabled = false - btnConvertIntoGif.isEnabled = false - mProgressView.visibility = View.VISIBLE + binding.apply { + btnVideoPath.isEnabled = false + btnConvertIntoGif.isEnabled = false + mProgressView.root.visibility = View.VISIBLE + } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 0000000..4388a7d --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/activity_add_text_on_video.xml b/app/src/main/res/layout/activity_add_text_on_video.xml index 636328d..dd2e982 100644 --- a/app/src/main/res/layout/activity_add_text_on_video.xml +++ b/app/src/main/res/layout/activity_add_text_on_video.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.AddTextOnVideoActivity"> + + diff --git a/app/src/main/res/layout/activity_add_water_mark_on_video.xml b/app/src/main/res/layout/activity_add_water_mark_on_video.xml index aee695d..17c153c 100644 --- a/app/src/main/res/layout/activity_add_water_mark_on_video.xml +++ b/app/src/main/res/layout/activity_add_water_mark_on_video.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.AddWaterMarkOnVideoActivity"> + + diff --git a/app/src/main/res/layout/activity_aspect_ratio.xml b/app/src/main/res/layout/activity_aspect_ratio.xml index 60d968e..52e1bbf 100644 --- a/app/src/main/res/layout/activity_aspect_ratio.xml +++ b/app/src/main/res/layout/activity_aspect_ratio.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.AddWaterMarkOnVideoActivity"> + + diff --git a/app/src/main/res/layout/activity_audios_merge.xml b/app/src/main/res/layout/activity_audios_merge.xml index c1af1c2..2a1609f 100644 --- a/app/src/main/res/layout/activity_audios_merge.xml +++ b/app/src/main/res/layout/activity_audios_merge.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.AddTextOnVideoActivity"> + + diff --git a/app/src/main/res/layout/activity_change_audio_valume.xml b/app/src/main/res/layout/activity_change_audio_valume.xml index 24eaf18..fcba5b8 100644 --- a/app/src/main/res/layout/activity_change_audio_valume.xml +++ b/app/src/main/res/layout/activity_change_audio_valume.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.AddTextOnVideoActivity"> + + diff --git a/app/src/main/res/layout/activity_combine_images.xml b/app/src/main/res/layout/activity_combine_images.xml index dcc65ea..c98de32 100644 --- a/app/src/main/res/layout/activity_combine_images.xml +++ b/app/src/main/res/layout/activity_combine_images.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.CombineImagesActivity"> + + diff --git a/app/src/main/res/layout/activity_combine_videos.xml b/app/src/main/res/layout/activity_combine_videos.xml index 499732e..b31b17d 100644 --- a/app/src/main/res/layout/activity_combine_videos.xml +++ b/app/src/main/res/layout/activity_combine_videos.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.CombineImagesActivity"> + + diff --git a/app/src/main/res/layout/activity_compress_audio.xml b/app/src/main/res/layout/activity_compress_audio.xml index 9755f0b..e091eaf 100644 --- a/app/src/main/res/layout/activity_compress_audio.xml +++ b/app/src/main/res/layout/activity_compress_audio.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.AddTextOnVideoActivity"> + + diff --git a/app/src/main/res/layout/activity_compress_video.xml b/app/src/main/res/layout/activity_compress_video.xml index 8bbe555..188bee5 100644 --- a/app/src/main/res/layout/activity_compress_video.xml +++ b/app/src/main/res/layout/activity_compress_video.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.AddWaterMarkOnVideoActivity"> + + diff --git a/app/src/main/res/layout/activity_crop_audio.xml b/app/src/main/res/layout/activity_crop_audio.xml index da43905..e137518 100644 --- a/app/src/main/res/layout/activity_crop_audio.xml +++ b/app/src/main/res/layout/activity_crop_audio.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.CutVideoUsingTimeActivity"> + + diff --git a/app/src/main/res/layout/activity_cut_video_using_time.xml b/app/src/main/res/layout/activity_cut_video_using_time.xml index 47955a5..d3e672f 100644 --- a/app/src/main/res/layout/activity_cut_video_using_time.xml +++ b/app/src/main/res/layout/activity_cut_video_using_time.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.CutVideoUsingTimeActivity"> + + diff --git a/app/src/main/res/layout/activity_extract_audio.xml b/app/src/main/res/layout/activity_extract_audio.xml index 304ecfa..80462d9 100644 --- a/app/src/main/res/layout/activity_extract_audio.xml +++ b/app/src/main/res/layout/activity_extract_audio.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.AddWaterMarkOnVideoActivity"> + + diff --git a/app/src/main/res/layout/activity_extract_images.xml b/app/src/main/res/layout/activity_extract_images.xml index 245f20c..6f687c9 100644 --- a/app/src/main/res/layout/activity_extract_images.xml +++ b/app/src/main/res/layout/activity_extract_images.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.AddWaterMarkOnVideoActivity"> + + diff --git a/app/src/main/res/layout/activity_fast_and_slow_audio.xml b/app/src/main/res/layout/activity_fast_and_slow_audio.xml index af6d753..f439ca8 100644 --- a/app/src/main/res/layout/activity_fast_and_slow_audio.xml +++ b/app/src/main/res/layout/activity_fast_and_slow_audio.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".otherFFMPEGProcessActivity.FastAndSlowAudioActivity"> + + diff --git a/app/src/main/res/layout/activity_fast_and_slow_video_motion.xml b/app/src/main/res/layout/activity_fast_and_slow_video_motion.xml index d80fc63..0969178 100644 --- a/app/src/main/res/layout/activity_fast_and_slow_video_motion.xml +++ b/app/src/main/res/layout/activity_fast_and_slow_video_motion.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.AddWaterMarkOnVideoActivity"> + + diff --git a/app/src/main/res/layout/activity_image_to_video_convert.xml b/app/src/main/res/layout/activity_image_to_video_convert.xml index ecfbec0..ff6ecf6 100644 --- a/app/src/main/res/layout/activity_image_to_video_convert.xml +++ b/app/src/main/res/layout/activity_image_to_video_convert.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" tools:context=".videoProcessActivity.ImageToVideoConvertActivity"> + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0ae63af..f087d38 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,6 +7,12 @@ android:orientation="vertical" tools:context=".MainActivity"> + +