Desktop FFmpeg Handling

This commit is contained in:
shabinder 2021-09-05 18:18:34 +05:30
parent 3a93cd5f91
commit 3f4008e2be
6 changed files with 49 additions and 7 deletions

View File

@ -12,7 +12,7 @@ actual fun Dialog(
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
AnimatedVisibility(isVisible) { AnimatedVisibility(isVisible) {
androidx.compose.ui.window.v1.Dialog(onDismiss) { androidx.compose.ui.window.Dialog(onDismiss) {
content() content()
} }
} }

View File

@ -19,6 +19,7 @@ package com.shabinder.common.core_components.file_manager
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.github.kokorin.jaffree.JaffreeException
import com.mpatric.mp3agic.InvalidDataException import com.mpatric.mp3agic.InvalidDataException
import com.mpatric.mp3agic.Mp3File import com.mpatric.mp3agic.Mp3File
import com.shabinder.common.core_components.media_converter.MediaConverter import com.shabinder.common.core_components.media_converter.MediaConverter
@ -35,6 +36,7 @@ import com.shabinder.common.models.dispatcherIO
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.failure import com.shabinder.common.models.event.coroutines.failure
import com.shabinder.common.models.event.coroutines.map import com.shabinder.common.models.event.coroutines.map
import com.shabinder.common.models.methods
import com.shabinder.database.Database import com.shabinder.database.Database
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@ -152,13 +154,18 @@ class DesktopFileManager(
.setId3v2TagsAndSaveFile(trackDetails, trackDetails.outputFilePath) .setId3v2TagsAndSaveFile(trackDetails, trackDetails.outputFilePath)
addToLibrary(trackDetails.outputFilePath) addToLibrary(trackDetails.outputFilePath)
}.failure { }.fold(
success = {},
failure = {
throw it throw it
} }
)
File(convertedFilePath).delete()
} else throw e } else throw e
} }
SuspendableEvent.success(trackDetails.outputFilePath) SuspendableEvent.success(trackDetails.outputFilePath)
} catch (e: Throwable) { } catch (e: Throwable) {
if(e is JaffreeException) methods.value.showPopUpMessage("No FFmpeg found at path.")
if (songFile.exists()) songFile.delete() if (songFile.exists()) songFile.delete()
logger.e { "${songFile.absolutePath} could not be created" } logger.e { "${songFile.absolutePath} could not be created" }
SuspendableEvent.error(e) SuspendableEvent.error(e)

View File

@ -6,6 +6,7 @@ import com.github.kokorin.jaffree.ffmpeg.UrlOutput
import com.shabinder.common.models.AudioQuality import com.shabinder.common.models.AudioQuality
import org.koin.dsl.bind import org.koin.dsl.bind
import org.koin.dsl.module import org.koin.dsl.module
import kotlin.io.path.Path
class DesktopMediaConverter : MediaConverter() { class DesktopMediaConverter : MediaConverter() {
@ -15,11 +16,14 @@ class DesktopMediaConverter : MediaConverter() {
audioQuality: AudioQuality, audioQuality: AudioQuality,
progressCallbacks: (Long) -> Unit, progressCallbacks: (Long) -> Unit,
) = executeSafelyInPool { ) = executeSafelyInPool {
val audioBitrate =
if (audioQuality == AudioQuality.UNKNOWN) 192 else audioQuality.kbps.toIntOrNull()
?: 192
FFmpeg.atPath().run { FFmpeg.atPath().run {
addInput(UrlInput.fromUrl(inputFilePath)) addInput(UrlInput.fromUrl(inputFilePath))
setOverwriteOutput(true) setOverwriteOutput(true)
if (audioQuality != AudioQuality.UNKNOWN) { if (audioQuality != AudioQuality.UNKNOWN) {
addArguments("-b:a", "${audioQuality.kbps}k") addArguments("-b:a", "${audioBitrate}k")
} }
addArguments("-acodec", "libmp3lame") addArguments("-acodec", "libmp3lame")
addArgument("-vn") addArgument("-vn")

View File

@ -61,13 +61,28 @@ actual suspend fun downloadTracks(
) )
} }
is DownloadResult.Success -> { // Todo clear map is DownloadResult.Success -> { // Todo clear map
fileManager.saveFileWithMetadata(it.byteArray, trackDetails) {} DownloadProgressFlow.emit(
DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Converting) }
)
fileManager.saveFileWithMetadata(it.byteArray, trackDetails).fold(
failure = {
DownloadProgressFlow.emit(
DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed(it)) }
)
},
success = {
DownloadProgressFlow.emit( DownloadProgressFlow.emit(
DownloadProgressFlow.replayCache.getOrElse( DownloadProgressFlow.replayCache.getOrElse(
0 0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloaded) } ) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloaded) }
) )
} }
)
}
} }
} }
}, },

View File

@ -44,6 +44,7 @@ kotlin {
implementation(project(":common:compose")) implementation(project(":common:compose"))
implementation(project(":common:providers")) implementation(project(":common:providers"))
implementation(project(":common:root")) implementation(project(":common:root"))
implementation("com.github.kokorin.jaffree:jaffree:2021.08.16")
// Decompose // Decompose
implementation(Decompose.decompose) implementation(Decompose.decompose)

View File

@ -20,6 +20,7 @@ import androidx.compose.material.Surface
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.ComposeWindow import androidx.compose.ui.awt.ComposeWindow
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application import androidx.compose.ui.window.application
@ -30,6 +31,8 @@ import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.jetbrains.lifecycle.LifecycleController import com.arkivanov.decompose.extensions.compose.jetbrains.lifecycle.LifecycleController
import com.arkivanov.essenty.lifecycle.LifecycleRegistry import com.arkivanov.essenty.lifecycle.LifecycleRegistry
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.github.kokorin.jaffree.JaffreeException
import com.github.kokorin.jaffree.ffmpeg.FFmpeg
import com.shabinder.common.di.* import com.shabinder.common.di.*
import com.shabinder.common.core_components.analytics.AnalyticsManager import com.shabinder.common.core_components.analytics.AnalyticsManager
import com.shabinder.common.core_components.file_manager.DownloadProgressFlow import com.shabinder.common.core_components.file_manager.DownloadProgressFlow
@ -39,6 +42,7 @@ import com.shabinder.common.core_components.utils.isInternetAccessible
import com.shabinder.common.models.Actions import com.shabinder.common.models.Actions
import com.shabinder.common.models.PlatformActions import com.shabinder.common.models.PlatformActions
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import com.shabinder.common.providers.FetchPlatformQueryResult import com.shabinder.common.providers.FetchPlatformQueryResult
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.translations.Strings import com.shabinder.common.translations.Strings
@ -89,10 +93,19 @@ fun main() {
) { ) {
val root: SpotiFlyerRoot = SpotiFlyerRootContent(rootComponent) val root: SpotiFlyerRoot = SpotiFlyerRootContent(rootComponent)
showToast = root.callBacks::showToast showToast = root.callBacks::showToast
// FFmpeg WARNING
try {
FFmpeg.atPath().addArgument("-version").execute();
} catch (e: Exception) {
if (e is JaffreeException) methods.value.showPopUpMessage("WARNING!\nFFmpeg not found at path")
} }
} }
} }
} }
}
// Download Tracking for Desktop Apps for Now will be measured using `GitHub Releases` // Download Tracking for Desktop Apps for Now will be measured using `GitHub Releases`
// https://tooomm.github.io/github-release-stats/?username=Shabinder&repository=SpotiFlyer // https://tooomm.github.io/github-release-stats/?username=Shabinder&repository=SpotiFlyer
} }
@ -108,6 +121,8 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
override val analyticsManager: AnalyticsManager = koin.get() override val analyticsManager: AnalyticsManager = koin.get()
override val preferenceManager: PreferenceManager = koin.get<PreferenceManager>().also { override val preferenceManager: PreferenceManager = koin.get<PreferenceManager>().also {
it.analyticsManager = analyticsManager it.analyticsManager = analyticsManager
// Allow Analytics for Desktop
analyticsManager.giveConsent()
} }
override val downloadProgressFlow = DownloadProgressFlow override val downloadProgressFlow = DownloadProgressFlow
override val actions: Actions = object : Actions { override val actions: Actions = object : Actions {