refactor: Improve BatchesFolder concatenation behavior

This commit is contained in:
Myzel394 2023-11-29 18:31:53 +01:00
parent fe9d9d7298
commit 1be3912812
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
4 changed files with 87 additions and 52 deletions

View File

@ -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",
)
}
}

View File

@ -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<Iterable<String>, String, String, CompletableDeferred<Unit>>
abstract val ffmpegParameters: Array<String>
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) {

View File

@ -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)
}

View File

@ -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",
)
}
}