mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
AudioQuality Parsing Improv
This commit is contained in:
parent
ba50bc789d
commit
86e6a9f3a8
@ -139,8 +139,8 @@ class ForegroundService : LifecycleService() {
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
downloadService.value.executeSuspending {
|
downloadService.value.executeSuspending {
|
||||||
fetcher.findBestDownloadLink(track).fold(
|
fetcher.findBestDownloadLink(track).fold(
|
||||||
success = { url ->
|
success = { res ->
|
||||||
enqueueDownload(url, track)
|
enqueueDownload(res.first, track.apply { audioQuality = res.second })
|
||||||
},
|
},
|
||||||
failure = { error ->
|
failure = { error ->
|
||||||
failed++
|
failed++
|
||||||
|
@ -23,9 +23,9 @@ import org.gradle.kotlin.dsl.accessors.runtime.addDependencyTo
|
|||||||
|
|
||||||
object Versions {
|
object Versions {
|
||||||
// App's Version (To be bumped at each update)
|
// App's Version (To be bumped at each update)
|
||||||
const val versionName = "3.2.11"
|
const val versionName = "3.3"
|
||||||
|
|
||||||
const val versionCode = 23
|
const val versionCode = 24
|
||||||
|
|
||||||
// Kotlin
|
// Kotlin
|
||||||
const val kotlinVersion = "1.5.21"
|
const val kotlinVersion = "1.5.21"
|
||||||
|
@ -131,6 +131,7 @@ class AndroidFileManager(
|
|||||||
val conversionResult = mediaConverter.convertAudioFile(
|
val conversionResult = mediaConverter.convertAudioFile(
|
||||||
inputFilePath = songFile.absolutePath,
|
inputFilePath = songFile.absolutePath,
|
||||||
outputFilePath = convertedFilePath,
|
outputFilePath = convertedFilePath,
|
||||||
|
trackDetails.audioQuality
|
||||||
)
|
)
|
||||||
|
|
||||||
conversionResult.map { outputFilePath ->
|
conversionResult.map { outputFilePath ->
|
||||||
|
@ -142,6 +142,7 @@ class DesktopFileManager(
|
|||||||
val conversionResult = mediaConverter.convertAudioFile(
|
val conversionResult = mediaConverter.convertAudioFile(
|
||||||
inputFilePath = songFile.absolutePath,
|
inputFilePath = songFile.absolutePath,
|
||||||
outputFilePath = convertedFilePath,
|
outputFilePath = convertedFilePath,
|
||||||
|
trackDetails.audioQuality
|
||||||
)
|
)
|
||||||
|
|
||||||
conversionResult.map { outputFilePath ->
|
conversionResult.map { outputFilePath ->
|
||||||
|
@ -41,6 +41,7 @@ data class TrackDetails(
|
|||||||
val progress: Int = 2,
|
val progress: Int = 2,
|
||||||
val downloadLink: String? = null,
|
val downloadLink: String? = null,
|
||||||
val downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
|
val downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
|
||||||
|
var audioQuality: AudioQuality = AudioQuality.KBPS192,
|
||||||
var outputFilePath: String, // UriString in Android
|
var outputFilePath: String, // UriString in Android
|
||||||
var videoID: String? = null,
|
var videoID: String? = null,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.shabinder.common.models.saavn
|
package com.shabinder.common.models.saavn
|
||||||
|
|
||||||
|
import com.shabinder.common.models.AudioQuality
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -43,4 +44,6 @@ data class SaavnSong @OptIn(ExperimentalSerializationApi::class) constructor(
|
|||||||
val vlink: String? = null,
|
val vlink: String? = null,
|
||||||
val year: String,
|
val year: String,
|
||||||
var downloaded: DownloadStatus = DownloadStatus.NotDownloaded
|
var downloaded: DownloadStatus = DownloadStatus.NotDownloaded
|
||||||
)
|
) {
|
||||||
|
val audioQuality get() = if (is320Kbps) AudioQuality.KBPS320 else AudioQuality.KBPS160
|
||||||
|
}
|
||||||
|
@ -91,27 +91,35 @@ class FetchPlatformQueryResult(
|
|||||||
suspend fun findBestDownloadLink(
|
suspend fun findBestDownloadLink(
|
||||||
track: TrackDetails,
|
track: TrackDetails,
|
||||||
preferredQuality: AudioQuality = preferenceManager.audioQuality
|
preferredQuality: AudioQuality = preferenceManager.audioQuality
|
||||||
): SuspendableEvent<String, Throwable> {
|
): SuspendableEvent<Pair<String, AudioQuality>, Throwable> {
|
||||||
var downloadLink: String? = null
|
var downloadLink: String? = null
|
||||||
|
var audioQuality = AudioQuality.KBPS192
|
||||||
|
|
||||||
val errorTrace = buildString(track) {
|
val errorTrace = buildString(track) {
|
||||||
if (track.videoID != null) {
|
if (track.videoID != null) {
|
||||||
// We Already have VideoID
|
// We Already have VideoID
|
||||||
downloadLink = when (track.source) {
|
downloadLink = when (track.source) {
|
||||||
Source.JioSaavn -> {
|
Source.JioSaavn -> {
|
||||||
saavnProvider.getSongFromID(track.videoID.requireNotNull()).component1()?.media_url
|
saavnProvider.getSongFromID(track.videoID.requireNotNull()).component1()
|
||||||
|
?.also { audioQuality = it.audioQuality }
|
||||||
|
?.media_url
|
||||||
}
|
}
|
||||||
Source.YouTube -> {
|
Source.YouTube -> {
|
||||||
youtubeMp3.getMp3DownloadLink(track.videoID.requireNotNull(), preferredQuality)
|
youtubeMp3.getMp3DownloadLink(
|
||||||
.let { ytMp3Link ->
|
track.videoID.requireNotNull(),
|
||||||
if (ytMp3Link is SuspendableEvent.Failure || ytMp3Link.component1().isNullOrBlank()) {
|
preferredQuality
|
||||||
|
).let { ytMp3Link ->
|
||||||
|
if (ytMp3Link is SuspendableEvent.Failure || ytMp3Link.component1()
|
||||||
|
.isNullOrBlank()
|
||||||
|
) {
|
||||||
appendPadded(
|
appendPadded(
|
||||||
"Yt1sMp3 Failed for ${track.videoID}:",
|
"Yt1sMp3 Failed for ${track.videoID}:",
|
||||||
ytMp3Link.component2()
|
ytMp3Link.component2()
|
||||||
?: "couldn't fetch link for ${track.videoID} ,trying manual extraction"
|
?: "couldn't fetch link for ${track.videoID} ,trying manual extraction"
|
||||||
)
|
)
|
||||||
appendLine("Trying Local Extraction")
|
appendLine("Trying Local Extraction")
|
||||||
youtubeProvider.ytDownloader.getVideo(track.videoID!!).get()?.url
|
youtubeProvider.ytDownloader.getVideo(track.videoID!!)
|
||||||
|
.get()?.url
|
||||||
} else ytMp3Link.component1()
|
} else ytMp3Link.component1()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,24 +145,35 @@ class FetchPlatformQueryResult(
|
|||||||
appendPadded("Fetching From Saavn Failed:", saavnError.stackTraceToString())
|
appendPadded("Fetching From Saavn Failed:", saavnError.stackTraceToString())
|
||||||
// Saavn Failed, Lets Try Fetching Now From Youtube Music
|
// Saavn Failed, Lets Try Fetching Now From Youtube Music
|
||||||
youtubeMusic.findMp3SongDownloadURLYT(track, preferredQuality).also {
|
youtubeMusic.findMp3SongDownloadURLYT(track, preferredQuality).also {
|
||||||
|
// Append Error To StackTrace
|
||||||
if (it is SuspendableEvent.Failure)
|
if (it is SuspendableEvent.Failure)
|
||||||
appendPadded("Fetching From YT-Music Failed:", it.component2()?.stackTraceToString())
|
appendPadded(
|
||||||
|
"Fetching From YT-Music Failed:",
|
||||||
|
it.component2()?.stackTraceToString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadLink = queryResult.component1()
|
queryResult.component1()?.let {
|
||||||
|
downloadLink = it.first
|
||||||
|
audioQuality = it.second
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return if (downloadLink.isNullOrBlank()) SuspendableEvent.error(
|
return if (downloadLink.isNullOrBlank()) SuspendableEvent.error(
|
||||||
SpotiFlyerException.DownloadLinkFetchFailed(errorTrace)
|
SpotiFlyerException.DownloadLinkFetchFailed(errorTrace)
|
||||||
) else SuspendableEvent.success(downloadLink.requireNotNull())
|
) else SuspendableEvent.success(Pair(downloadLink.requireNotNull(),audioQuality))
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private fun addToDatabaseAsync(link: String, result: PlatformQueryResult) {
|
private fun addToDatabaseAsync(link: String, result: PlatformQueryResult) {
|
||||||
GlobalScope.launch(dispatcherIO) {
|
GlobalScope.launch(dispatcherIO) {
|
||||||
db?.add(
|
db?.add(
|
||||||
result.folderType, result.title, link, result.coverUrl, result.trackList.size.toLong()
|
result.folderType,
|
||||||
|
result.title,
|
||||||
|
link,
|
||||||
|
result.coverUrl,
|
||||||
|
result.trackList.size.toLong()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,7 @@ package com.shabinder.common.providers.saavn
|
|||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.shabinder.common.core_components.file_manager.FileManager
|
import com.shabinder.common.core_components.file_manager.FileManager
|
||||||
import com.shabinder.common.core_components.file_manager.finalOutputDir
|
import com.shabinder.common.core_components.file_manager.finalOutputDir
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.*
|
||||||
import com.shabinder.common.models.PlatformQueryResult
|
|
||||||
import com.shabinder.common.models.SpotiFlyerException
|
|
||||||
import com.shabinder.common.models.TrackDetails
|
|
||||||
import com.shabinder.common.models.event.coroutines.SuspendableEvent
|
import com.shabinder.common.models.event.coroutines.SuspendableEvent
|
||||||
import com.shabinder.common.models.saavn.SaavnSong
|
import com.shabinder.common.models.saavn.SaavnSong
|
||||||
import com.shabinder.common.models.spotify.Source
|
import com.shabinder.common.models.spotify.Source
|
||||||
@ -81,6 +78,7 @@ class SaavnProvider(
|
|||||||
albumArtURL = it.image.replace("http:", "https:"),
|
albumArtURL = it.image.replace("http:", "https:"),
|
||||||
lyrics = it.lyrics ?: it.lyrics_snippet,
|
lyrics = it.lyrics ?: it.lyrics_snippet,
|
||||||
source = Source.JioSaavn,
|
source = Source.JioSaavn,
|
||||||
|
audioQuality = if(it.is320Kbps) AudioQuality.KBPS320 else AudioQuality.KBPS160,
|
||||||
outputFilePath = fileManager.finalOutputDir(it.song, type, subFolder, fileManager.defaultDir() /*".m4a"*/)
|
outputFilePath = fileManager.finalOutputDir(it.song, type, subFolder, fileManager.defaultDir() /*".m4a"*/)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -32,18 +32,21 @@ interface JioSaavnRequests {
|
|||||||
trackName: String,
|
trackName: String,
|
||||||
trackArtists: List<String>,
|
trackArtists: List<String>,
|
||||||
preferredQuality: AudioQuality
|
preferredQuality: AudioQuality
|
||||||
): SuspendableEvent<String, Throwable> = searchForSong(trackName).map { songs ->
|
): SuspendableEvent<Pair<String,AudioQuality>, Throwable> = searchForSong(trackName).map { songs ->
|
||||||
val bestMatch = sortByBestMatch(songs, trackName, trackArtists).keys.firstOrNull() ?:
|
val bestMatch = sortByBestMatch(songs, trackName, trackArtists).keys.firstOrNull() ?:
|
||||||
throw SpotiFlyerException.DownloadLinkFetchFailed("No SAAVN Match Found for $trackName")
|
throw SpotiFlyerException.DownloadLinkFetchFailed("No SAAVN Match Found for $trackName")
|
||||||
|
|
||||||
|
var audioQuality: AudioQuality = AudioQuality.KBPS160
|
||||||
val m4aLink: String by getSongFromID(bestMatch).map { song ->
|
val m4aLink: String by getSongFromID(bestMatch).map { song ->
|
||||||
val optimalQuality = if (song.is320Kbps && ((preferredQuality.kbps.toIntOrNull()
|
val optimalQuality = if (song.is320Kbps && ((preferredQuality.kbps.toIntOrNull()
|
||||||
?: 0) > 160)
|
?: 0) > 160)
|
||||||
) AudioQuality.KBPS320 else AudioQuality.KBPS160
|
) AudioQuality.KBPS320 else AudioQuality.KBPS160
|
||||||
|
audioQuality = optimalQuality
|
||||||
|
|
||||||
song.media_url.requireNotNull().replaceAfterLast("_", "${optimalQuality.kbps}.mp4")
|
song.media_url.requireNotNull().replaceAfterLast("_", "${optimalQuality.kbps}.mp4")
|
||||||
}
|
}
|
||||||
|
|
||||||
m4aLink
|
Pair(m4aLink,audioQuality)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun searchForSong(
|
suspend fun searchForSong(
|
||||||
|
@ -50,7 +50,7 @@ class YoutubeMusic constructor(
|
|||||||
suspend fun findMp3SongDownloadURLYT(
|
suspend fun findMp3SongDownloadURLYT(
|
||||||
trackDetails: TrackDetails,
|
trackDetails: TrackDetails,
|
||||||
preferredQuality: AudioQuality = fileManager.preferenceManager.audioQuality
|
preferredQuality: AudioQuality = fileManager.preferenceManager.audioQuality
|
||||||
): SuspendableEvent<String, Throwable> {
|
): SuspendableEvent<Pair<String,AudioQuality>, Throwable> {
|
||||||
return getYTIDBestMatch(trackDetails).flatMap { videoID ->
|
return getYTIDBestMatch(trackDetails).flatMap { videoID ->
|
||||||
// As YT compress Audio hence there is no benefit of quality for more than 192
|
// As YT compress Audio hence there is no benefit of quality for more than 192
|
||||||
val optimalQuality =
|
val optimalQuality =
|
||||||
@ -67,6 +67,8 @@ class YoutubeMusic constructor(
|
|||||||
message = "Caught Following Errors While Finding Downloadable Link for $videoID : \n${it.stackTraceToString()}"
|
message = "Caught Following Errors While Finding Downloadable Link for $videoID : \n${it.stackTraceToString()}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}.map {
|
||||||
|
Pair(it,optimalQuality)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,9 @@ actual suspend fun downloadTracks(
|
|||||||
list.forEach { trackDetails ->
|
list.forEach { trackDetails ->
|
||||||
DownloadScope.executeSuspending { // Send Download to Pool.
|
DownloadScope.executeSuspending { // Send Download to Pool.
|
||||||
fetcher.findBestDownloadLink(trackDetails).fold(
|
fetcher.findBestDownloadLink(trackDetails).fold(
|
||||||
success = { url ->
|
success = { res ->
|
||||||
downloadFile(url).collect {
|
trackDetails.audioQuality = res.second
|
||||||
|
downloadFile(res.first).collect {
|
||||||
when (it) {
|
when (it) {
|
||||||
is DownloadResult.Error -> {
|
is DownloadResult.Error -> {
|
||||||
DownloadProgressFlow.emit(
|
DownloadProgressFlow.emit(
|
||||||
|
@ -33,8 +33,9 @@ actual suspend fun downloadTracks(
|
|||||||
withContext(dispatcherIO) {
|
withContext(dispatcherIO) {
|
||||||
allTracksStatus[track.title] = DownloadStatus.Queued
|
allTracksStatus[track.title] = DownloadStatus.Queued
|
||||||
fetcher.findBestDownloadLink(track).fold(
|
fetcher.findBestDownloadLink(track).fold(
|
||||||
success = { url ->
|
success = { res ->
|
||||||
downloadFile(url).collect {
|
track.audioQuality = res.second
|
||||||
|
downloadFile(res.first).collect {
|
||||||
when (it) {
|
when (it) {
|
||||||
is DownloadResult.Success -> {
|
is DownloadResult.Success -> {
|
||||||
println("Download Completed")
|
println("Download Completed")
|
||||||
|
Loading…
Reference in New Issue
Block a user