feat: Use VideoBatchesFolder and AudioBatchesFolder

This commit is contained in:
Myzel394 2023-11-27 14:57:42 +01:00
parent 3d17012fb7
commit b3bb43367a
No known key found for this signature in database
GPG Key ID: 50098FCA22080F0F
6 changed files with 216 additions and 57 deletions

View File

@ -0,0 +1,71 @@
package app.myzel394.alibi.helpers
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import com.arthenica.ffmpegkit.FFmpegKitConfig
import java.time.LocalDateTime
class AudioBatchesFolder(
override val context: Context,
override val type: BatchType,
override val customFolder: DocumentFile? = null,
override val subfolderName: String = ".recordings",
) : BatchesFolder(
context,
type,
customFolder,
subfolderName,
) {
override fun getOutputFileForFFmpeg(
date: LocalDateTime,
extension: String,
): String {
return when (type) {
BatchType.INTERNAL -> asInternalGetOutputFile(date, extension).absolutePath
BatchType.CUSTOM -> FFmpegKitConfig.getSafParameterForWrite(
context,
customFolder!!.createFile(
"audio/${extension}",
getName(date, extension),
)!!.uri
)!!
}
}
override suspend fun concatenate(
recordingStart: LocalDateTime,
extension: String,
disableCache: Boolean,
) {
if (!disableCache && checkIfOutputAlreadyExists(recordingStart, extension)) {
return
}
val filePaths = getBatchesForFFmpeg()
val outputFile = getOutputFileForFFmpeg(
date = recordingStart,
extension = extension,
)
MediaConverter.concatenateAudioFiles(
inputFiles = filePaths,
outputFile = outputFile,
).await()
}
companion object {
fun viaInternalFolder(context: Context): BatchesFolder {
return AudioBatchesFolder(context, BatchType.INTERNAL)
}
fun viaCustomFolder(context: Context, folder: DocumentFile): BatchesFolder {
return AudioBatchesFolder(context, BatchType.CUSTOM, folder)
}
fun importFromFolder(folder: String, context: Context): BatchesFolder = when (folder) {
"_'internal" -> viaInternalFolder(context)
else -> viaCustomFolder(context, DocumentFile.fromTreeUri(context, Uri.parse(folder))!!)
}
}
}

View File

@ -11,11 +11,11 @@ import android.net.Uri
import android.os.ParcelFileDescriptor
import java.io.FileDescriptor
data class BatchesFolder(
val context: Context,
val type: BatchType,
val customFolder: DocumentFile? = null,
val subfolderName: String = ".recordings",
abstract class BatchesFolder(
open val context: Context,
open val type: BatchType,
open val customFolder: DocumentFile? = null,
open val subfolderName: String = ".recordings",
) {
private var customFileFileDescriptor: ParcelFileDescriptor? = null
@ -24,7 +24,7 @@ data class BatchesFolder(
BatchType.INTERNAL -> getFolder(context).mkdirs()
BatchType.CUSTOM -> {
if (customFolder!!.findFile(subfolderName) == null) {
customFolder.createDirectory(subfolderName)
customFolder!!.createDirectory(subfolderName)
}
}
}
@ -89,22 +89,6 @@ data class BatchesFolder(
return getCustomDefinedFolder().createFile("audio/$extension", getName(date, extension))!!
}
fun getOutputFileForFFmpeg(
date: LocalDateTime,
extension: String,
): String {
return when (type) {
BatchType.INTERNAL -> asInternalGetOutputFile(date, extension).absolutePath
BatchType.CUSTOM -> FFmpegKitConfig.getSafParameterForWrite(
context,
customFolder!!.createFile(
"audio/${extension}",
getName(date, extension),
)!!.uri
)!!
}
}
fun checkIfOutputAlreadyExists(
date: LocalDateTime,
extension: String
@ -122,27 +106,16 @@ data class BatchesFolder(
}
}
suspend fun exportToOneFile(
abstract fun getOutputFileForFFmpeg(
date: LocalDateTime,
extension: String,
): String
abstract suspend fun concatenate(
recordingStart: LocalDateTime,
extension: String,
disableCache: Boolean = false,
) {
if (!disableCache && checkIfOutputAlreadyExists(recordingStart, extension)) {
return
}
val filePaths = getBatchesForFFmpeg()
val outputFile = getOutputFileForFFmpeg(
date = recordingStart,
extension = extension,
)
MediaConverter.concatenate(
inputFiles = filePaths,
outputFile = outputFile,
extraCommand = " -acodec copy"
).await()
}
)
fun exportFolderForSettings(): String {
return when (type) {
@ -218,20 +191,7 @@ data class BatchesFolder(
}
companion object {
fun viaInternalFolder(context: Context): BatchesFolder {
return BatchesFolder(context, BatchType.INTERNAL)
}
fun viaCustomFolder(context: Context, folder: DocumentFile): BatchesFolder {
return BatchesFolder(context, BatchType.CUSTOM, folder)
}
fun getFolder(context: Context) = File(context.filesDir, RECORDER_SUBFOLDER_NAME)
fun importFromFolder(folder: String, context: Context): BatchesFolder = when (folder) {
"_'internal" -> viaInternalFolder(context)
else -> viaCustomFolder(context, DocumentFile.fromTreeUri(context, Uri.parse(folder))!!)
}
}
}

View File

@ -1,13 +1,16 @@
package app.myzel394.alibi.helpers
import android.content.Context
import android.util.Log
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.ReturnCode
import kotlinx.coroutines.CompletableDeferred
import java.io.File
import java.util.UUID
class MediaConverter {
companion object {
fun concatenate(
fun concatenateAudioFiles(
inputFiles: Iterable<String>,
outputFile: String,
extraCommand: String = "",
@ -17,7 +20,9 @@ class MediaConverter {
val filePathsConcatenated = inputFiles.joinToString("|")
val command =
"-protocol_whitelist saf,concat,content,file,subfile" +
" -i 'concat:$filePathsConcatenated' -y" +
" -i 'concat:$filePathsConcatenated'" +
" -y" +
" -acodec copy" +
extraCommand +
" $outputFile"
@ -43,5 +48,54 @@ class MediaConverter {
return completer
}
private fun createTempFile(content: String): File {
val name = UUID.randomUUID().toString()
return File.createTempFile("temp-$name", ".txt").apply {
writeText(content)
}
}
fun concatenateVideoFiles(
inputFiles: Iterable<String>,
outputFile: String,
extraCommand: String = "",
): CompletableDeferred<Unit> {
val completer = CompletableDeferred<Unit>()
val listFile = createTempFile(inputFiles.joinToString("\n", prefix = "file "))
val command =
"-protocol_whitelist saf,concat,content,file,subfile" +
" -f concat" +
" -y" +
" -i ${listFile.absolutePath}" +
" -c copy" +
extraCommand +
" $outputFile"
FFmpegKit.executeAsync(
command
) { session ->
if (!ReturnCode.isSuccess(session!!.returnCode)) {
Log.d(
"Video Concatenation",
String.format(
"Command failed with state %s and rc %s.%s",
session.state,
session.returnCode,
session.failStackTrace,
)
)
completer.completeExceptionally(Exception("Failed to concatenate videos"))
} else {
completer.complete(Unit)
}
}
return completer
}
}
}

View File

@ -0,0 +1,71 @@
package app.myzel394.alibi.helpers
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import com.arthenica.ffmpegkit.FFmpegKitConfig
import java.time.LocalDateTime
class VideoBatchesFolder(
override val context: Context,
override val type: BatchesFolder.BatchType,
override val customFolder: DocumentFile? = null,
override val subfolderName: String = ".recordings",
) : BatchesFolder(
context,
type,
customFolder,
subfolderName,
) {
override fun getOutputFileForFFmpeg(date: LocalDateTime, extension: String): String {
return when (type) {
BatchType.INTERNAL -> asInternalGetOutputFile(date, extension).absolutePath
BatchType.CUSTOM -> FFmpegKitConfig.getSafParameterForWrite(
context,
customFolder!!.createFile(
"video/${extension}",
getName(date, extension),
)!!.uri
)!!
}
}
override suspend fun concatenate(
recordingStart: LocalDateTime,
extension: String,
disableCache: Boolean
) {
if (!disableCache && checkIfOutputAlreadyExists(recordingStart, extension)) {
return
}
val filePaths = getBatchesForFFmpeg()
val outputFile = getOutputFileForFFmpeg(
date = recordingStart,
extension = extension,
)
MediaConverter.concatenateAudioFiles(
inputFiles = filePaths,
outputFile = outputFile,
).await()
}
companion object {
fun viaInternalFolder(context: Context): BatchesFolder {
return VideoBatchesFolder(context, BatchType.INTERNAL)
}
fun viaCustomFolder(context: Context, folder: DocumentFile): BatchesFolder {
return VideoBatchesFolder(context, BatchType.CUSTOM, folder)
}
fun importFromFolder(folder: String, context: Context): BatchesFolder = when (folder) {
"_'internal" -> AudioBatchesFolder.viaInternalFolder(context)
else -> AudioBatchesFolder.viaCustomFolder(
context,
DocumentFile.fromTreeUri(context, Uri.parse(folder))!!
)
}
}
}

View File

@ -11,6 +11,7 @@ import androidx.camera.video.Recorder
import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture
import androidx.core.content.ContextCompat
import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.db.RecordingInformation
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope

View File

@ -44,6 +44,7 @@ import app.myzel394.alibi.ui.utils.rememberFileSaverDialog
import app.myzel394.alibi.R
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.helpers.AudioBatchesFolder
import app.myzel394.alibi.helpers.BatchesFolder
import app.myzel394.alibi.ui.effects.rememberSettings
import app.myzel394.alibi.ui.models.AudioRecorderModel
@ -128,9 +129,10 @@ fun AudioRecorderScreen(
val recording = audioRecorder.recorderService?.getRecordingInformation()
?: settings.lastRecording
?: throw Exception("No recording information available")
val batchesFolder = BatchesFolder.importFromFolder(recording.folderPath, context)
val batchesFolder =
AudioBatchesFolder.importFromFolder(recording.folderPath, context)
batchesFolder.exportToOneFile(
batchesFolder.concatenate(
recording.recordingStart,
recording.fileExtension,
)