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.content.Context
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateAudioFiles
import com.arthenica.ffmpegkit.FFmpegKitConfig import com.arthenica.ffmpegkit.FFmpegKitConfig
import java.time.LocalDateTime import java.time.LocalDateTime
@ -17,6 +18,9 @@ class AudioBatchesFolder(
customFolder, customFolder,
subfolderName, subfolderName,
) { ) {
override val concatenateFunction = ::concatenateAudioFiles
override val ffmpegParameters = FFMPEG_PARAMETERS
override fun getOutputFileForFFmpeg( override fun getOutputFileForFFmpeg(
date: LocalDateTime, date: LocalDateTime,
extension: String, 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 { companion object {
fun viaInternalFolder(context: Context) = AudioBatchesFolder(context, BatchType.INTERNAL) fun viaInternalFolder(context: Context) = AudioBatchesFolder(context, BatchType.INTERNAL)
@ -65,5 +47,18 @@ class AudioBatchesFolder(
"_'internal" -> viaInternalFolder(context) "_'internal" -> viaInternalFolder(context)
else -> viaCustomFolder(context, DocumentFile.fromTreeUri(context, Uri.parse(folder))!!) 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 android.content.Context
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import app.myzel394.alibi.ui.RECORDER_SUBFOLDER_NAME
import java.io.File import java.io.File
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import com.arthenica.ffmpegkit.FFmpegKitConfig import com.arthenica.ffmpegkit.FFmpegKitConfig
import android.net.Uri
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import kotlinx.coroutines.CompletableDeferred
import java.io.FileDescriptor import java.io.FileDescriptor
import kotlin.reflect.KFunction3
abstract class BatchesFolder( abstract class BatchesFolder(
open val context: Context, open val context: Context,
@ -19,6 +19,9 @@ abstract class BatchesFolder(
) { ) {
private var customFileFileDescriptor: ParcelFileDescriptor? = null private var customFileFileDescriptor: ParcelFileDescriptor? = null
abstract val concatenateFunction: KFunction3<Iterable<String>, String, String, CompletableDeferred<Unit>>
abstract val ffmpegParameters: Array<String>
fun initFolders() { fun initFolders() {
when (type) { when (type) {
BatchType.INTERNAL -> getInternalFolder().mkdirs() BatchType.INTERNAL -> getInternalFolder().mkdirs()
@ -111,11 +114,37 @@ abstract class BatchesFolder(
extension: String, extension: String,
): String ): String
abstract suspend fun concatenate( open suspend fun concatenate(
recordingStart: LocalDateTime, recordingStart: LocalDateTime,
extension: String, extension: String,
disableCache: Boolean = false, 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 { fun exportFolderForSettings(): String {
return when (type) { return when (type) {

View File

@ -22,7 +22,6 @@ class MediaConverter {
"-protocol_whitelist saf,concat,content,file,subfile" + "-protocol_whitelist saf,concat,content,file,subfile" +
" -i 'concat:$filePathsConcatenated'" + " -i 'concat:$filePathsConcatenated'" +
" -y" + " -y" +
" -acodec copy" +
extraCommand + extraCommand +
" $outputFile" " $outputFile"
@ -68,16 +67,17 @@ class MediaConverter {
val command = val command =
" -f concat" + " -f concat" +
" -y" +
" -safe 0" + " -safe 0" +
" -i ${listFile.absolutePath}" + " -i ${listFile.absolutePath}" +
" -c copy" +
extraCommand + extraCommand +
" -y" +
" $outputFile" " $outputFile"
FFmpegKit.executeAsync( FFmpegKit.executeAsync(
command command
) { session -> ) { session ->
listFile.delete()
if (!ReturnCode.isSuccess(session!!.returnCode)) { if (!ReturnCode.isSuccess(session!!.returnCode)) {
Log.d( Log.d(
"Video Concatenation", "Video Concatenation",
@ -89,7 +89,7 @@ class MediaConverter {
) )
) )
completer.completeExceptionally(Exception("Failed to concatenate videos")) completer.completeExceptionally(FFmpegException("Failed to concatenate videos"))
} else { } else {
completer.complete(Unit) completer.complete(Unit)
} }
@ -98,4 +98,6 @@ class MediaConverter {
return completer 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.content.Context
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateVideoFiles
import com.arthenica.ffmpegkit.FFmpegKitConfig import com.arthenica.ffmpegkit.FFmpegKitConfig
import com.arthenica.ffmpegkit.ReturnCode
import java.time.LocalDateTime import java.time.LocalDateTime
class VideoBatchesFolder( class VideoBatchesFolder(
@ -17,6 +19,9 @@ class VideoBatchesFolder(
customFolder, customFolder,
subfolderName, subfolderName,
) { ) {
override val concatenateFunction = ::concatenateVideoFiles
override val ffmpegParameters = FFMPEG_PARAMETERS
override fun getOutputFileForFFmpeg(date: LocalDateTime, extension: String): String { override fun getOutputFileForFFmpeg(date: LocalDateTime, extension: String): String {
return when (type) { return when (type) {
BatchType.INTERNAL -> asInternalGetOutputFile(date, extension).absolutePath 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 { companion object {
fun viaInternalFolder(context: Context) = VideoBatchesFolder(context, BatchType.INTERNAL) fun viaInternalFolder(context: Context) = VideoBatchesFolder(context, BatchType.INTERNAL)
@ -65,5 +48,31 @@ class VideoBatchesFolder(
DocumentFile.fromTreeUri(context, Uri.parse(folder))!! 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",
)
} }
} }