Preferred Audio Quality Impl

This commit is contained in:
shabinder 2021-07-11 02:20:31 +05:30
parent a44f4cc061
commit 994b20d0a1
9 changed files with 44 additions and 22 deletions

View File

@ -421,7 +421,7 @@ class MainActivity : ComponentActivity() {
if (f.canWrite()) { if (f.canWrite()) {
// hell yeah :) // hell yeah :)
preferenceManager.setDownloadDirectory(path) preferenceManager.setDownloadDirectory(path)
callBack(dir.defaultDir()) callBack(path)
showPopUpMessage(Strings.downloadDirectorySetTo("\n${dir.defaultDir()}")) showPopUpMessage(Strings.downloadDirectorySetTo("\n${dir.defaultDir()}"))
}else{ }else{
showPopUpMessage(Strings.noWriteAccess("\n$path ")) showPopUpMessage(Strings.noWriteAccess("\n$path "))

View File

@ -18,6 +18,7 @@ package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.database.DownloadRecordDatabaseQueries import com.shabinder.common.database.DownloadRecordDatabaseQueries
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.di.providers.GaanaProvider import com.shabinder.common.di.providers.GaanaProvider
import com.shabinder.common.di.providers.SaavnProvider import com.shabinder.common.di.providers.SaavnProvider
import com.shabinder.common.di.providers.SpotifyProvider import com.shabinder.common.di.providers.SpotifyProvider
@ -26,6 +27,7 @@ import com.shabinder.common.di.providers.YoutubeMusic
import com.shabinder.common.di.providers.YoutubeProvider import com.shabinder.common.di.providers.YoutubeProvider
import com.shabinder.common.di.providers.get import com.shabinder.common.di.providers.get
import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3 import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3
import com.shabinder.common.models.AudioQuality
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.SpotiFlyerException import com.shabinder.common.models.SpotiFlyerException
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
@ -48,6 +50,7 @@ class FetchPlatformQueryResult(
private val youtubeMp3: YoutubeMp3, private val youtubeMp3: YoutubeMp3,
private val audioToMp3: AudioToMp3, private val audioToMp3: AudioToMp3,
val dir: Dir, val dir: Dir,
val preferenceManager: PreferenceManager,
val logger: Kermit val logger: Kermit
) { ) {
private val db: DownloadRecordDatabaseQueries? private val db: DownloadRecordDatabaseQueries?
@ -89,18 +92,19 @@ class FetchPlatformQueryResult(
// 1) Try Finding on JioSaavn (better quality upto 320KBPS) // 1) Try Finding on JioSaavn (better quality upto 320KBPS)
// 2) If Not found try finding on Youtube Music // 2) If Not found try finding on Youtube Music
suspend fun findMp3DownloadLink( suspend fun findMp3DownloadLink(
track: TrackDetails track: TrackDetails,
preferredQuality: AudioQuality = preferenceManager.audioQuality
): SuspendableEvent<String,Throwable> = ): SuspendableEvent<String,Throwable> =
if (track.videoID != null) { if (track.videoID != null) {
// We Already have VideoID // We Already have VideoID
when (track.source) { when (track.source) {
Source.JioSaavn -> { Source.JioSaavn -> {
saavnProvider.getSongFromID(track.videoID.requireNotNull()).flatMap { song -> saavnProvider.getSongFromID(track.videoID.requireNotNull()).flatMap { song ->
song.media_url?.let { audioToMp3.convertToMp3(it) } ?: findHighestQualityMp3Link(track) song.media_url?.let { audioToMp3.convertToMp3(it) } ?: findMp3Link(track,preferredQuality)
} }
} }
Source.YouTube -> { Source.YouTube -> {
youtubeMp3.getMp3DownloadLink(track.videoID.requireNotNull()).flatMapError { youtubeMp3.getMp3DownloadLink(track.videoID.requireNotNull(),preferredQuality).flatMapError {
logger.e("Yt1sMp3 Failed") { it.message ?: "couldn't fetch link for ${track.videoID} ,trying manual extraction" } logger.e("Yt1sMp3 Failed") { it.message ?: "couldn't fetch link for ${track.videoID} ,trying manual extraction" }
youtubeProvider.ytDownloader.getVideo(track.videoID!!).get()?.url?.let { m4aLink -> youtubeProvider.ytDownloader.getVideo(track.videoID!!).get()?.url?.let { m4aLink ->
audioToMp3.convertToMp3(m4aLink) audioToMp3.convertToMp3(m4aLink)
@ -109,24 +113,26 @@ class FetchPlatformQueryResult(
} }
else -> { else -> {
/*We should never reach here for now*/ /*We should never reach here for now*/
findHighestQualityMp3Link(track) findMp3Link(track,preferredQuality)
} }
} }
} else { } else {
findHighestQualityMp3Link(track) findMp3Link(track,preferredQuality)
} }
private suspend fun findHighestQualityMp3Link( private suspend fun findMp3Link(
track: TrackDetails track: TrackDetails,
preferredQuality: AudioQuality
):SuspendableEvent<String,Throwable> { ):SuspendableEvent<String,Throwable> {
// Try Fetching Track from Jio Saavn // Try Fetching Track from Jio Saavn
return saavnProvider.findMp3SongDownloadURL( return saavnProvider.findMp3SongDownloadURL(
trackName = track.title, trackName = track.title,
trackArtists = track.artists trackArtists = track.artists,
preferredQuality = preferredQuality
).flatMapError { saavnError -> ).flatMapError { saavnError ->
logger.e { "Fetching From Saavn Failed: \n${saavnError.stackTraceToString()}" } logger.e { "Fetching From Saavn Failed: \n${saavnError.stackTraceToString()}" }
// Saavn Failed, Lets Try Fetching Now From Youtube Music // Saavn Failed, Lets Try Fetching Now From Youtube Music
youtubeMusic.findMp3SongDownloadURLYT(track).flatMapError { ytMusicError -> youtubeMusic.findMp3SongDownloadURLYT(track,preferredQuality).flatMapError { ytMusicError ->
// If Both Failed Bubble the Exception Up with both StackTraces // If Both Failed Bubble the Exception Up with both StackTraces
SuspendableEvent.error( SuspendableEvent.error(
SpotiFlyerException.DownloadLinkFetchFailed( SpotiFlyerException.DownloadLinkFetchFailed(

View File

@ -12,5 +12,5 @@ fun providersModule() = module {
single { YoutubeProvider(get(), get(), get()) } single { YoutubeProvider(get(), get(), get()) }
single { YoutubeMp3(get(), get()) } single { YoutubeMp3(get(), get()) }
single { YoutubeMusic(get(), get(), get(), get(), get()) } single { YoutubeMusic(get(), get(), get(), get(), get()) }
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get()) } single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
} }

View File

@ -18,6 +18,7 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.providers.requests.youtubeMp3.Yt1sMp3 import com.shabinder.common.di.providers.requests.youtubeMp3.Yt1sMp3
import com.shabinder.common.models.AudioQuality
import com.shabinder.common.models.corsApi import com.shabinder.common.models.corsApi
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.map import com.shabinder.common.models.event.coroutines.map
@ -37,7 +38,7 @@ interface YoutubeMp3: Yt1sMp3 {
} }
} }
suspend fun getMp3DownloadLink(videoID: String): SuspendableEvent<String,Throwable> = getLinkFromYt1sMp3(videoID).map { suspend fun getMp3DownloadLink(videoID: String,quality: AudioQuality): SuspendableEvent<String,Throwable> = getLinkFromYt1sMp3(videoID,quality).map {
corsApi + it corsApi + it
} }
} }

View File

@ -18,6 +18,7 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3 import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3
import com.shabinder.common.models.AudioQuality
import com.shabinder.common.models.SpotiFlyerException import com.shabinder.common.models.SpotiFlyerException
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.YoutubeTrack import com.shabinder.common.models.YoutubeTrack
@ -57,11 +58,14 @@ class YoutubeMusic constructor(
// Get Downloadable Link // Get Downloadable Link
suspend fun findMp3SongDownloadURLYT( suspend fun findMp3SongDownloadURLYT(
trackDetails: TrackDetails trackDetails: TrackDetails,
preferredQuality: AudioQuality
): SuspendableEvent<String, Throwable> { ): SuspendableEvent<String, 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
val optimalQuality = if((preferredQuality.kbps.toIntOrNull() ?: 0) > 192) AudioQuality.KBPS192 else preferredQuality
// 1 Try getting Link from Yt1s // 1 Try getting Link from Yt1s
youtubeMp3.getMp3DownloadLink(videoID).flatMapError { youtubeMp3.getMp3DownloadLink(videoID, optimalQuality).flatMapError {
// 2 if Yt1s failed , Extract Manually // 2 if Yt1s failed , Extract Manually
youtubeProvider.ytDownloader.getVideo(videoID).get()?.url?.let { m4aLink -> youtubeProvider.ytDownloader.getVideo(videoID).get()?.url?.let { m4aLink ->
audioToMp3.convertToMp3(m4aLink) audioToMp3.convertToMp3(m4aLink)

View File

@ -3,6 +3,7 @@ package com.shabinder.common.di.providers.requests.saavn
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.globalJson import com.shabinder.common.di.globalJson
import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3 import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3
import com.shabinder.common.models.AudioQuality
import com.shabinder.common.models.corsApi import com.shabinder.common.models.corsApi
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.map import com.shabinder.common.models.event.coroutines.map
@ -38,11 +39,13 @@ interface JioSaavnRequests {
suspend fun findMp3SongDownloadURL( suspend fun findMp3SongDownloadURL(
trackName: String, trackName: String,
trackArtists: List<String>, trackArtists: List<String>,
preferredQuality: AudioQuality
): SuspendableEvent<String,Throwable> = searchForSong(trackName).map { songs -> ): SuspendableEvent<String,Throwable> = searchForSong(trackName).map { songs ->
val bestMatches = sortByBestMatch(songs, trackName, trackArtists) val bestMatches = sortByBestMatch(songs, trackName, trackArtists)
val m4aLink: String by getSongFromID(bestMatches.keys.first()).map { song -> val m4aLink: String by getSongFromID(bestMatches.keys.first()).map { song ->
song.media_url.requireNotNull() val optimalQuality = if(song.is320Kbps && ((preferredQuality.kbps.toIntOrNull() ?: 0) > 160)) AudioQuality.KBPS320 else AudioQuality.KBPS160
song.media_url.requireNotNull().replaceAfterLast("_","${optimalQuality.kbps}.mp4")
} }
val mp3Link by audioToMp3.convertToMp3(m4aLink) val mp3Link by audioToMp3.convertToMp3(m4aLink)

View File

@ -17,6 +17,7 @@
package com.shabinder.common.di.providers.requests.youtubeMp3 package com.shabinder.common.di.providers.requests.youtubeMp3
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.models.AudioQuality
import com.shabinder.common.models.corsApi import com.shabinder.common.models.corsApi
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.flatMap import com.shabinder.common.models.event.coroutines.flatMap
@ -42,7 +43,7 @@ interface Yt1sMp3 {
/* /*
* Downloadable Mp3 Link for YT videoID. * Downloadable Mp3 Link for YT videoID.
* */ * */
suspend fun getLinkFromYt1sMp3(videoID: String): SuspendableEvent<String,Throwable> = getKey(videoID).flatMap { key -> suspend fun getLinkFromYt1sMp3(videoID: String,quality: AudioQuality): SuspendableEvent<String,Throwable> = getKey(videoID,quality).flatMap { key ->
getConvertedMp3Link(videoID, key).map { getConvertedMp3Link(videoID, key).map {
it["dlink"].requireNotNull() it["dlink"].requireNotNull()
.jsonPrimitive.content.replace("\"", "") .jsonPrimitive.content.replace("\"", "")
@ -53,7 +54,7 @@ interface Yt1sMp3 {
* POST:https://yt1s.com/api/ajaxSearch/index * POST:https://yt1s.com/api/ajaxSearch/index
* Body Form= q:yt video link ,vt:format=mp3 * Body Form= q:yt video link ,vt:format=mp3
* */ * */
private suspend fun getKey(videoID: String): SuspendableEvent<String,Throwable> = SuspendableEvent { private suspend fun getKey(videoID: String,quality: AudioQuality): SuspendableEvent<String,Throwable> = SuspendableEvent {
val response: JsonObject = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index") { val response: JsonObject = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index") {
body = FormDataContent( body = FormDataContent(
Parameters.build { Parameters.build {
@ -63,10 +64,17 @@ interface Yt1sMp3 {
) )
} }
response.getJsonObject("links") val mp3Keys = response.getJsonObject("links")
.getJsonObject("mp3") .getJsonObject("mp3")
.getJsonObject("192")
?.get("k").requireNotNull().jsonPrimitive.content val requestedKBPS = when(quality) {
AudioQuality.KBPS128 -> "mp3128"
else -> quality.kbps
}
val specificQualityKey = mp3Keys.getJsonObject(requestedKBPS) ?: mp3Keys.getJsonObject("192")
specificQualityKey?.get("k").requireNotNull().jsonPrimitive.content
} }
private suspend fun getConvertedMp3Link(videoID: String, key: String): SuspendableEvent<JsonObject,Throwable> = SuspendableEvent { private suspend fun getConvertedMp3Link(videoID: String, key: String): SuspendableEvent<JsonObject,Throwable> = SuspendableEvent {

View File

@ -64,7 +64,7 @@ internal class SpotiFlyerPreferenceImpl(
override fun selectNewDownloadDirectory() { override fun selectNewDownloadDirectory() {
actions.setDownloadDirectoryAction { actions.setDownloadDirectoryAction {
store.accept(Intent.SetDownloadDirectory(dir.defaultDir())) store.accept(Intent.SetDownloadDirectory(it))
} }
} }

View File

@ -116,7 +116,7 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
val directory = fileChooser.selectedFile val directory = fileChooser.selectedFile
if(directory.canWrite()){ if(directory.canWrite()){
preferenceManager.setDownloadDirectory(directory.absolutePath) preferenceManager.setDownloadDirectory(directory.absolutePath)
callBack(dir.defaultDir()) callBack(directory.absolutePath)
showPopUpMessage("${Strings.setDownloadDirectory()} \n${dir.defaultDir()}") showPopUpMessage("${Strings.setDownloadDirectory()} \n${dir.defaultDir()}")
} else { } else {
showPopUpMessage(Strings.noWriteAccess("\n${directory.absolutePath} ")) showPopUpMessage(Strings.noWriteAccess("\n${directory.absolutePath} "))