diff --git a/app/src/main/java/app/myzel394/alibi/helpers/AudioBatchesFolder.kt b/app/src/main/java/app/myzel394/alibi/helpers/AudioBatchesFolder.kt index 1873867..891a54a 100644 --- a/app/src/main/java/app/myzel394/alibi/helpers/AudioBatchesFolder.kt +++ b/app/src/main/java/app/myzel394/alibi/helpers/AudioBatchesFolder.kt @@ -3,6 +3,7 @@ package app.myzel394.alibi.helpers import android.content.Context import android.net.Uri import androidx.documentfile.provider.DocumentFile +import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateAudioFiles import com.arthenica.ffmpegkit.FFmpegKitConfig import java.time.LocalDateTime @@ -17,6 +18,9 @@ class AudioBatchesFolder( customFolder, subfolderName, ) { + override val concatenateFunction = ::concatenateAudioFiles + override val ffmpegParameters = FFMPEG_PARAMETERS + override fun getOutputFileForFFmpeg( date: LocalDateTime, extension: String, @@ -33,28 +37,6 @@ class AudioBatchesFolder( } } - override suspend fun concatenate( - recordingStart: LocalDateTime, - extension: String, - disableCache: Boolean, - ): String { - val outputFile = getOutputFileForFFmpeg( - date = recordingStart, - extension = extension, - ) - - if (disableCache || checkIfOutputAlreadyExists(recordingStart, extension)) { - val filePaths = getBatchesForFFmpeg() - - MediaConverter.concatenateAudioFiles( - inputFiles = filePaths, - outputFile = outputFile, - ).await() - } - - return outputFile - } - companion object { fun viaInternalFolder(context: Context) = AudioBatchesFolder(context, BatchType.INTERNAL) @@ -65,5 +47,18 @@ class AudioBatchesFolder( "_'internal" -> viaInternalFolder(context) else -> viaCustomFolder(context, DocumentFile.fromTreeUri(context, Uri.parse(folder))!!) } + + // Parameters to be passed in descending order + // Those parameters first try to concatenate without re-encoding + // if that fails, it'll try several fallback methods + // this is audio only + val FFMPEG_PARAMETERS = arrayOf( + " -c copy", + " -acodec copy", + " -c:a aac", + " -c:a libmp3lame", + " -c:a libopus", + " -c:a libvorbis", + ) } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt b/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt index c9c2827..3eb187f 100644 --- a/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt +++ b/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt @@ -2,14 +2,14 @@ package app.myzel394.alibi.helpers import android.content.Context import androidx.documentfile.provider.DocumentFile -import app.myzel394.alibi.ui.RECORDER_SUBFOLDER_NAME import java.io.File import java.time.LocalDateTime import java.time.format.DateTimeFormatter import com.arthenica.ffmpegkit.FFmpegKitConfig -import android.net.Uri import android.os.ParcelFileDescriptor +import kotlinx.coroutines.CompletableDeferred import java.io.FileDescriptor +import kotlin.reflect.KFunction3 abstract class BatchesFolder( open val context: Context, @@ -19,6 +19,9 @@ abstract class BatchesFolder( ) { private var customFileFileDescriptor: ParcelFileDescriptor? = null + abstract val concatenateFunction: KFunction3, String, String, CompletableDeferred> + abstract val ffmpegParameters: Array + fun initFolders() { when (type) { BatchType.INTERNAL -> getInternalFolder().mkdirs() @@ -111,11 +114,37 @@ abstract class BatchesFolder( extension: String, ): String - abstract suspend fun concatenate( + open suspend fun concatenate( recordingStart: LocalDateTime, extension: String, disableCache: Boolean = false, - ): String + onNextParameterTry: (String) -> Unit = {}, + ): String { + val outputFile = getOutputFileForFFmpeg( + date = recordingStart, + extension = extension, + ) + + if (disableCache || !checkIfOutputAlreadyExists(recordingStart, extension)) { + val filePaths = getBatchesForFFmpeg() + + for (parameter in ffmpegParameters) { + onNextParameterTry(parameter) + + try { + concatenateFunction( + filePaths, + outputFile, + parameter, + ) + } catch (e: MediaConverter.FFmpegException) { + continue + } + } + } + + return outputFile + } fun exportFolderForSettings(): String { return when (type) { diff --git a/app/src/main/java/app/myzel394/alibi/helpers/MediaConverter.kt b/app/src/main/java/app/myzel394/alibi/helpers/MediaConverter.kt index d2dd324..d45d95e 100644 --- a/app/src/main/java/app/myzel394/alibi/helpers/MediaConverter.kt +++ b/app/src/main/java/app/myzel394/alibi/helpers/MediaConverter.kt @@ -22,7 +22,6 @@ class MediaConverter { "-protocol_whitelist saf,concat,content,file,subfile" + " -i 'concat:$filePathsConcatenated'" + " -y" + - " -acodec copy" + extraCommand + " $outputFile" @@ -68,16 +67,17 @@ class MediaConverter { val command = " -f concat" + - " -y" + " -safe 0" + " -i ${listFile.absolutePath}" + - " -c copy" + extraCommand + + " -y" + " $outputFile" FFmpegKit.executeAsync( command ) { session -> + listFile.delete() + if (!ReturnCode.isSuccess(session!!.returnCode)) { Log.d( "Video Concatenation", @@ -89,7 +89,7 @@ class MediaConverter { ) ) - completer.completeExceptionally(Exception("Failed to concatenate videos")) + completer.completeExceptionally(FFmpegException("Failed to concatenate videos")) } else { completer.complete(Unit) } @@ -98,4 +98,6 @@ class MediaConverter { return completer } } + + class FFmpegException(message: String) : Exception(message) } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/helpers/VideoBatchesFolder.kt b/app/src/main/java/app/myzel394/alibi/helpers/VideoBatchesFolder.kt index ba01cb9..990b256 100644 --- a/app/src/main/java/app/myzel394/alibi/helpers/VideoBatchesFolder.kt +++ b/app/src/main/java/app/myzel394/alibi/helpers/VideoBatchesFolder.kt @@ -3,7 +3,9 @@ package app.myzel394.alibi.helpers import android.content.Context import android.net.Uri import androidx.documentfile.provider.DocumentFile +import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateVideoFiles import com.arthenica.ffmpegkit.FFmpegKitConfig +import com.arthenica.ffmpegkit.ReturnCode import java.time.LocalDateTime class VideoBatchesFolder( @@ -17,6 +19,9 @@ class VideoBatchesFolder( customFolder, subfolderName, ) { + override val concatenateFunction = ::concatenateVideoFiles + override val ffmpegParameters = FFMPEG_PARAMETERS + override fun getOutputFileForFFmpeg(date: LocalDateTime, extension: String): String { return when (type) { BatchType.INTERNAL -> asInternalGetOutputFile(date, extension).absolutePath @@ -30,28 +35,6 @@ class VideoBatchesFolder( } } - override suspend fun concatenate( - recordingStart: LocalDateTime, - extension: String, - disableCache: Boolean - ): String { - val outputFile = getOutputFileForFFmpeg( - date = recordingStart, - extension = extension, - ) - - if (disableCache || !checkIfOutputAlreadyExists(recordingStart, extension)) { - val filePaths = getBatchesForFFmpeg() - - MediaConverter.concatenateVideoFiles( - inputFiles = filePaths, - outputFile = outputFile, - ).await() - } - - return outputFile - } - companion object { fun viaInternalFolder(context: Context) = VideoBatchesFolder(context, BatchType.INTERNAL) @@ -65,5 +48,31 @@ class VideoBatchesFolder( DocumentFile.fromTreeUri(context, Uri.parse(folder))!! ) } + + // Parameters to be passed in descending order + // Those parameters first try to concatenate without re-encoding + // if that fails, it'll try several fallback methods + val FFMPEG_PARAMETERS = arrayOf( + " -c copy", + " -c:v copy", + " -c:v copy -c:a aac", + " -c:v copy -c:a libmp3lame", + " -c:v copy -c:a libopus", + " -c:v copy -c:a libvorbis", + " -c:a copy", + // There's nothing else we can do to avoid re-encoding, + // so we'll just have to re-encode the whole thing + " -c:v libx264 -c:a copy", + " -c:v libx264 -c:a aac", + " -c:v libx265 -c:a aac", + " -c:v libx264 -c:a libmp3lame", + " -c:v libx264 -c:a libopus", + " -c:v libx264 -c:a libvorbis", + " -c:v libx265 -c:a copy", + " -c:v libx265 -c:a aac", + " -c:v libx265 -c:a libmp3lame", + " -c:v libx265 -c:a libopus", + " -c:v libx265 -c:a libvorbis", + ) } } \ No newline at end of file