mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
fix: Fix BatchesFolder export
This commit is contained in:
parent
18195c9893
commit
0364a79dcb
@ -16,25 +16,21 @@ import java.time.format.DateTimeFormatter
|
||||
data class AudioRecorderExporter(
|
||||
val recording: RecordingInformation,
|
||||
) {
|
||||
private fun getInternalFilePaths(context: Context): List<File> =
|
||||
getFolder(context)
|
||||
.listFiles()
|
||||
?.filter {
|
||||
val name = it.nameWithoutExtension
|
||||
|
||||
name.toIntOrNull() != null
|
||||
}
|
||||
?.toList()
|
||||
?: emptyList()
|
||||
|
||||
suspend fun concatenateFiles(
|
||||
context: Context,
|
||||
batchesFolder: BatchesFolder,
|
||||
outputFilePath: String,
|
||||
forceConcatenation: Boolean = false,
|
||||
) {
|
||||
val filePaths = batchesFolder.getBatchesForFFmpeg().joinToString("|")
|
||||
val outputFile =
|
||||
batchesFolder.getOutputFileForFFmpeg(recording.recordingStart, recording.fileExtension)
|
||||
|
||||
if (batchesFolder.checkIfOutputAlreadyExists(
|
||||
recording.recordingStart,
|
||||
recording.fileExtension
|
||||
) && !forceConcatenation
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
val command =
|
||||
"-protocol_whitelist saf,concat,content,file,subfile" +
|
||||
@ -44,7 +40,7 @@ data class AudioRecorderExporter(
|
||||
" -metadata batch_count='${filePaths.length}'" +
|
||||
" -metadata batch_duration='${recording.intervalDuration}'" +
|
||||
" -metadata max_duration='${recording.maxDuration}'" +
|
||||
" $outputFile"
|
||||
" $outputFilePath"
|
||||
|
||||
val session = FFmpegKit.execute(command)
|
||||
|
||||
@ -68,34 +64,6 @@ data class AudioRecorderExporter(
|
||||
|
||||
companion object {
|
||||
fun getFolder(context: Context) = File(context.filesDir, RECORDER_SUBFOLDER_NAME)
|
||||
|
||||
fun clearAllRecordings(context: Context) {
|
||||
getFolder(context).deleteRecursively()
|
||||
}
|
||||
|
||||
fun hasRecordingsAvailable(context: Context) =
|
||||
getFolder(context).listFiles()?.isNotEmpty() ?: false
|
||||
|
||||
fun linkBatches(context: Context, batchesFolder: Uri, destinationFolder: File) {
|
||||
val folder =
|
||||
DocumentFile.fromTreeUri(
|
||||
context,
|
||||
batchesFolder,
|
||||
)!!
|
||||
|
||||
destinationFolder.mkdirs()
|
||||
|
||||
folder.listFiles().forEach {
|
||||
if (it.name?.substringBeforeLast(".")?.toIntOrNull() == null) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
Os.symlink(
|
||||
"${folder.uri}/${it.name}",
|
||||
"${destinationFolder.absolutePath}/${it.name}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,10 +68,47 @@ data class BatchesFolder(
|
||||
}
|
||||
}
|
||||
|
||||
fun getName(date: LocalDateTime, extension: String): String {
|
||||
val name = date
|
||||
.format(DateTimeFormatter.ISO_DATE_TIME)
|
||||
.toString()
|
||||
.replace(":", "-")
|
||||
.replace(".", "_")
|
||||
|
||||
return "$name.$extension"
|
||||
}
|
||||
|
||||
fun asInternalGetOutputFile(date: LocalDateTime, extension: String): File {
|
||||
return File(getInternalFolder(), getName(date, extension))
|
||||
}
|
||||
|
||||
fun asCustomGetOutputFile(
|
||||
date: LocalDateTime,
|
||||
extension: String,
|
||||
): DocumentFile {
|
||||
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
|
||||
): Boolean {
|
||||
val name = date
|
||||
.format(DateTimeFormatter.ISO_DATE_TIME)
|
||||
.toString()
|
||||
@ -79,14 +116,9 @@ data class BatchesFolder(
|
||||
.replace(".", "_")
|
||||
|
||||
return when (type) {
|
||||
BatchType.INTERNAL -> File(getInternalFolder(), "$name.$extension").absolutePath
|
||||
BatchType.CUSTOM -> FFmpegKitConfig.getSafParameterForWrite(
|
||||
context,
|
||||
getCustomDefinedFolder().createFile(
|
||||
"audio/${extension}",
|
||||
"${name}.${extension}"
|
||||
)!!.uri
|
||||
)!!
|
||||
BatchType.INTERNAL -> File(getInternalFolder(), "$name.$extension").exists()
|
||||
BatchType.CUSTOM ->
|
||||
getCustomDefinedFolder().findFile("${name}.${extension}")?.exists() ?: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +136,13 @@ data class BatchesFolder(
|
||||
}
|
||||
}
|
||||
|
||||
fun hasRecordingsAvailable(): Boolean {
|
||||
return when (type) {
|
||||
BatchType.INTERNAL -> getInternalFolder().listFiles()?.isNotEmpty() ?: false
|
||||
BatchType.CUSTOM -> getCustomDefinedFolder().listFiles().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteOldRecordings(earliestCounter: Long) {
|
||||
when (type) {
|
||||
BatchType.INTERNAL -> getInternalFolder().listFiles()?.forEach {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package app.myzel394.alibi.ui.components.AudioRecorder.molecules
|
||||
|
||||
import android.Manifest
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@ -21,13 +20,11 @@ import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberStandardBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -39,22 +36,12 @@ import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import app.myzel394.alibi.NotificationHelper
|
||||
import app.myzel394.alibi.R
|
||||
import app.myzel394.alibi.dataStore
|
||||
import app.myzel394.alibi.db.AppSettings
|
||||
import app.myzel394.alibi.helpers.AudioRecorderExporter
|
||||
import app.myzel394.alibi.helpers.AudioRecorderExporter.Companion.clearAllRecordings
|
||||
import app.myzel394.alibi.helpers.BatchesFolder
|
||||
import app.myzel394.alibi.services.RecorderNotificationHelper
|
||||
import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE
|
||||
import app.myzel394.alibi.ui.components.atoms.PermissionRequester
|
||||
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
||||
import app.myzel394.alibi.ui.utils.rememberFileSaverDialog
|
||||
import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.flow.lastOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
|
||||
|
@ -107,8 +107,7 @@ fun RecordingStatus(
|
||||
DeleteButton(
|
||||
onDelete = {
|
||||
audioRecorder.stopRecording(context)
|
||||
|
||||
AudioRecorderExporter.clearAllRecordings(context)
|
||||
audioRecorder.batchesFolder!!.deleteRecordings();
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package app.myzel394.alibi.ui.screens
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@ -18,7 +20,13 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Snackbar
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -28,8 +36,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.navigation.NavController
|
||||
import app.myzel394.alibi.ui.components.AudioRecorder.organisms.RecordingStatus
|
||||
import app.myzel394.alibi.ui.components.AudioRecorder.molecules.StartRecording
|
||||
@ -38,7 +45,6 @@ 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.db.RecordingInformation
|
||||
import app.myzel394.alibi.helpers.AudioRecorderExporter
|
||||
import app.myzel394.alibi.helpers.BatchesFolder
|
||||
import app.myzel394.alibi.ui.effects.rememberSettings
|
||||
@ -52,6 +58,7 @@ fun AudioRecorderScreen(
|
||||
navController: NavController,
|
||||
audioRecorder: AudioRecorderModel,
|
||||
) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val context = LocalContext.current
|
||||
|
||||
val dataStore = context.dataStore
|
||||
@ -62,10 +69,10 @@ fun AudioRecorderScreen(
|
||||
settings.audioRecorderSettings.getMimeType()
|
||||
) {
|
||||
if (settings.audioRecorderSettings.deleteRecordingsImmediately) {
|
||||
AudioRecorderExporter.clearAllRecordings(context)
|
||||
audioRecorder.batchesFolder!!.deleteRecordings()
|
||||
}
|
||||
|
||||
if (!AudioRecorderExporter.hasRecordingsAvailable(context)) {
|
||||
if (!audioRecorder.batchesFolder!!.hasRecordingsAvailable()) {
|
||||
scope.launch {
|
||||
dataStore.updateData {
|
||||
it.setLastRecording(null)
|
||||
@ -89,6 +96,29 @@ fun AudioRecorderScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val successMessage = stringResource(R.string.ui_audioRecorder_action_save_success)
|
||||
val openMessage = stringResource(R.string.ui_audioRecorder_action_save_openFolder)
|
||||
|
||||
fun openFolder(uri: Uri) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
fun showSnackbar(uri: Uri) {
|
||||
scope.launch {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = successMessage,
|
||||
actionLabel = openMessage,
|
||||
duration = SnackbarDuration.Short,
|
||||
)
|
||||
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
openFolder(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveRecording() {
|
||||
scope.launch {
|
||||
isProcessingAudio = true
|
||||
@ -97,16 +127,39 @@ fun AudioRecorderScreen(
|
||||
delay(100)
|
||||
|
||||
try {
|
||||
AudioRecorderExporter(
|
||||
audioRecorder.recorderService?.getRecordingInformation()
|
||||
?: settings.lastRecording
|
||||
?: throw Exception("No recording information available"),
|
||||
).concatenateFiles(
|
||||
context,
|
||||
audioRecorder.recorderService!!.batchesFolder
|
||||
val recording = audioRecorder.recorderService?.getRecordingInformation()
|
||||
?: settings.lastRecording
|
||||
?: throw Exception("No recording information available")
|
||||
val outputFile = audioRecorder.batchesFolder!!.getOutputFileForFFmpeg(
|
||||
recording.recordingStart,
|
||||
recording.fileExtension
|
||||
)
|
||||
|
||||
// saveFile(file, file.name)
|
||||
AudioRecorderExporter(recording).concatenateFiles(
|
||||
context,
|
||||
audioRecorder.recorderService!!.batchesFolder,
|
||||
outputFile,
|
||||
)
|
||||
|
||||
val name = audioRecorder.batchesFolder!!.getName(
|
||||
recording.recordingStart,
|
||||
recording.fileExtension,
|
||||
)
|
||||
|
||||
when (audioRecorder.batchesFolder!!.type) {
|
||||
BatchesFolder.BatchType.INTERNAL -> {
|
||||
saveFile(
|
||||
audioRecorder.batchesFolder!!.asInternalGetOutputFile(
|
||||
recording.recordingStart,
|
||||
recording.fileExtension,
|
||||
), name
|
||||
)
|
||||
}
|
||||
|
||||
BatchesFolder.BatchType.CUSTOM -> {
|
||||
showSnackbar(audioRecorder.batchesFolder!!.customFolder!!.uri)
|
||||
}
|
||||
}
|
||||
} catch (error: Exception) {
|
||||
Log.getStackTraceString(error)
|
||||
} finally {
|
||||
@ -198,6 +251,21 @@ fun AudioRecorderScreen(
|
||||
}
|
||||
)
|
||||
Scaffold(
|
||||
snackbarHost = {
|
||||
SnackbarHost(
|
||||
hostState = snackbarHostState,
|
||||
snackbar = {
|
||||
Snackbar(
|
||||
snackbarData = it,
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
actionColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
actionContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
dismissActionContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
@ -118,4 +118,6 @@
|
||||
<string name="ui_settings_option_saveFolder_warning_text">By default, Alibi will save the recording batches into its private, encrypted file storage. You can change this and specify an external, unencrypted folder. This will allow you to access the batches manually. ONLY DO THIS IF YOU KNOW WHAT YOU ARE DOING!</string>
|
||||
<string name="ui_settings_option_saveFolder_warning_action_confirm">Yes, change folder</string>
|
||||
<string name="ui_settings_option_saveFolder_action_default_label">Use private, encrypted storage</string>
|
||||
<string name="ui_audioRecorder_action_save_success">Recording has been saved successfully!</string>
|
||||
<string name="ui_audioRecorder_action_save_openFolder">Open</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user