mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
Desktop FFmpeg Handling
This commit is contained in:
parent
3a93cd5f91
commit
3f4008e2be
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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")
|
||||||
|
@ -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) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user