mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
Preferred Audio Quality Impl
This commit is contained in:
parent
a44f4cc061
commit
994b20d0a1
@ -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 "))
|
||||||
|
@ -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(
|
||||||
|
@ -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()) }
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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} "))
|
||||||
|
Loading…
Reference in New Issue
Block a user