Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions SSffmpegVideoOperation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@ repositories {
}

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"
Expand Down Expand Up @@ -69,9 +68,9 @@ 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 'com.github.jaiselrahman:FilePicker:1.3.2'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
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.activity.result.contract.ActivityResultContracts
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
Expand Down Expand Up @@ -162,7 +170,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()
}
Expand All @@ -181,7 +202,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, File.separator + System.currentTimeMillis().div(1000L) + extension)
} else {
// Fallback for devices below Android 14
File(dir.path + File.separator + OUT_PUT_DIR + System.currentTimeMillis().div(1000L) + extension)
}
return dest.absolutePath
}

Expand All @@ -196,4 +222,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()
}
}
}
38 changes: 23 additions & 15 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
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"
}

buildTypes {
Expand All @@ -23,14 +22,21 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = '11'
}
flavorDimensions "default"

androidExtensions {
experimental = true

buildFeatures {
viewBinding true
}

flavorDimensions "default"
}

final roomSchemaDir = "$projectDir/roomSchemas"
Expand All @@ -44,13 +50,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"
}
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
package="com.simform.videoimageeditor">

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
Expand Down
72 changes: 69 additions & 3 deletions app/src/main/java/com/simform/videoimageeditor/BaseActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -24,12 +28,74 @@ 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()
}

}

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()
Expand All @@ -43,7 +109,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)
}
Expand Down
15 changes: 11 additions & 4 deletions app/src/main/java/com/simform/videoimageeditor/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package com.simform.videoimageeditor

import android.view.View
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

class MainActivity : BaseActivity(R.layout.activity_main, R.string.ffpmeg_title) {

private lateinit var binding: ActivityMainBinding

override fun initialization() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

supportActionBar?.title = 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?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,30 @@ package com.simform.videoimageeditor.middlewareActivity
import android.view.View
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

class OtherFFMPEGProcessActivity : BaseActivity(R.layout.activity_other_ffmpeg_process, R.string.other_ffmpeg_operations) {

private lateinit var binding: ActivityOtherFfmpegProcessBinding

override fun initialization() {
binding = ActivityOtherFfmpegProcessBinding.inflate(layoutInflater)
setContentView(binding.root)
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.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?) {
Expand Down
Loading