mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
Saavn Sort By Best Match, Build Fixes
This commit is contained in:
parent
5f5473aaf7
commit
ac209e8509
@ -131,7 +131,7 @@ dependencies {
|
|||||||
//implementation("com.jakewharton.timber:timber:4.7.1")
|
//implementation("com.jakewharton.timber:timber:4.7.1")
|
||||||
implementation("dev.icerock.moko:parcelize:0.6.1")
|
implementation("dev.icerock.moko:parcelize:0.6.1")
|
||||||
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
|
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
|
||||||
implementation("com.google.accompanist:accompanist-insets:0.10.0")
|
implementation("com.google.accompanist:accompanist-insets:0.9.1")
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
@ -28,7 +28,7 @@ object Versions {
|
|||||||
const val ktLint = "10.0.0"
|
const val ktLint = "10.0.0"
|
||||||
|
|
||||||
// DI
|
// DI
|
||||||
const val koin = "3.0.1"
|
const val koin = "3.0.2"
|
||||||
|
|
||||||
// Logger
|
// Logger
|
||||||
const val kermit = "0.1.9"
|
const val kermit = "0.1.9"
|
||||||
@ -62,10 +62,10 @@ object Koin {
|
|||||||
val core = "io.insert-koin:koin-core:${Versions.koin}"
|
val core = "io.insert-koin:koin-core:${Versions.koin}"
|
||||||
val test = "io.insert-koin:koin-test:${Versions.koin}"
|
val test = "io.insert-koin:koin-test:${Versions.koin}"
|
||||||
val android = "io.insert-koin:koin-android:${Versions.koin}"
|
val android = "io.insert-koin:koin-android:${Versions.koin}"
|
||||||
val compose = "io.insert-koin:koin-androidx-compose:${Versions.koin}"
|
val compose = "io.insert-koin:koin-androidx-compose:3.0.1"
|
||||||
}
|
}
|
||||||
object Androidx {
|
object Androidx {
|
||||||
const val androidxActivity = "androidx.activity:activity-compose:1.3.0-alpha02"
|
const val androidxActivity = "androidx.activity:activity-compose:1.3.0-alpha07"
|
||||||
const val core = "androidx.core:core-ktx:1.3.2"
|
const val core = "androidx.core:core-ktx:1.3.2"
|
||||||
const val palette = "androidx.palette:palette-ktx:1.0.0"
|
const val palette = "androidx.palette:palette-ktx:1.0.0"
|
||||||
const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutinesVersion}"
|
const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutinesVersion}"
|
||||||
|
@ -36,6 +36,7 @@ data class TrackDetails(
|
|||||||
var albumArtURL: String,
|
var albumArtURL: String,
|
||||||
var source: Source,
|
var source: Source,
|
||||||
val progress: Int = 2,
|
val progress: Int = 2,
|
||||||
|
val downloadLink: String? = null,
|
||||||
val downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
|
val downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
|
||||||
var outputFilePath: String, // UriString in Android
|
var outputFilePath: String, // UriString in Android
|
||||||
var videoID: String? = null,
|
var videoID: String? = null,
|
||||||
|
@ -8,8 +8,10 @@ import com.shabinder.common.di.utils.removeIllegalChars
|
|||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
|
import com.shabinder.common.models.saavn.SaavnSearchResult
|
||||||
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
|
||||||
|
import io.github.shabinder.fuzzywuzzy.diffutils.FuzzySearch
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
|
|
||||||
class SaavnProvider(
|
class SaavnProvider(
|
||||||
@ -75,14 +77,76 @@ class SaavnProvider(
|
|||||||
year = it.year,
|
year = it.year,
|
||||||
comment = it.copyright_text,
|
comment = it.copyright_text,
|
||||||
trackUrl = it.perma_url,
|
trackUrl = it.perma_url,
|
||||||
|
videoID = it.id,
|
||||||
|
downloadLink = it.media_url, // Downloadable Link
|
||||||
downloaded = it.updateStatusIfPresent(type, subFolder),
|
downloaded = it.updateStatusIfPresent(type, subFolder),
|
||||||
albumArtURL = it.image.replace("http:", "https:"),
|
albumArtURL = it.image.replace("http:", "https:"),
|
||||||
lyrics = it.lyrics ?: it.lyrics_snippet,
|
lyrics = it.lyrics ?: it.lyrics_snippet,
|
||||||
videoID = it.media_url, // Downloadable Link
|
|
||||||
source = Source.JioSaavn,
|
source = Source.JioSaavn,
|
||||||
outputFilePath = dir.finalOutputDir(it.song, type, subFolder, dir.defaultDir(), /*".m4a"*/)
|
outputFilePath = dir.finalOutputDir(it.song, type, subFolder, dir.defaultDir(), /*".m4a"*/)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun sortByBestMatch(
|
||||||
|
tracks: List<SaavnSearchResult>,
|
||||||
|
trackName: String,
|
||||||
|
trackArtists: List<String>,
|
||||||
|
): Map<String, Float> {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "linksWithMatchValue" is map with Saavn VideoID and its rating/match with 100 as Max Value
|
||||||
|
**/
|
||||||
|
val linksWithMatchValue = mutableMapOf<String, Float>()
|
||||||
|
|
||||||
|
for (result in tracks) {
|
||||||
|
var hasCommonWord = false
|
||||||
|
|
||||||
|
val resultName = result.title.toLowerCase().replace("/", " ")
|
||||||
|
val trackNameWords = trackName.toLowerCase().split(" ")
|
||||||
|
|
||||||
|
for (nameWord in trackNameWords) {
|
||||||
|
if (nameWord.isNotBlank() && FuzzySearch.partialRatio(nameWord, resultName) > 85) hasCommonWord = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip this Result if No Word is Common in Name
|
||||||
|
if (!hasCommonWord) {
|
||||||
|
// log("Saavn Removing", result.toString())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find artist match
|
||||||
|
// Will Be Using Fuzzy Search Because YT Spelling might be mucked up
|
||||||
|
// match = (no of artist names in result) / (no. of artist names on spotify) * 100
|
||||||
|
var artistMatchNumber = 0F
|
||||||
|
|
||||||
|
// String Containing All Artist Names from JioSaavn Search Result
|
||||||
|
val artistListString = mutableSetOf<String>().apply {
|
||||||
|
result.more_info?.singers?.split(",")?.let { addAll(it) }
|
||||||
|
result.more_info?.primary_artists?.toLowerCase()?.split(",")?.let { addAll(it) }
|
||||||
|
}.joinToString(" , ")
|
||||||
|
|
||||||
|
for (artist in trackArtists) {
|
||||||
|
if (FuzzySearch.partialRatio(artist.toLowerCase(), artistListString) > 85)
|
||||||
|
artistMatchNumber++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (artistMatchNumber == 0F) {
|
||||||
|
// logger.d{ "Saavn Removing: $result" }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val artistMatch: Float = (artistMatchNumber / trackArtists.size.toFloat()) * 100F
|
||||||
|
val nameMatch: Float = FuzzySearch.partialRatio(resultName, trackName).toFloat() / 100F
|
||||||
|
|
||||||
|
val avgMatch = (artistMatch + nameMatch) / 2
|
||||||
|
|
||||||
|
linksWithMatchValue[result.id] = avgMatch
|
||||||
|
}
|
||||||
|
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap().also {
|
||||||
|
logger.d("Saavn Search") { "Match Found for $trackName - ${!it.isNullOrEmpty()}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun SaavnSong.updateStatusIfPresent(folderType: String, subFolder: String): DownloadStatus {
|
private fun SaavnSong.updateStatusIfPresent(folderType: String, subFolder: String): DownloadStatus {
|
||||||
return if (dir.isPresent(
|
return if (dir.isPresent(
|
||||||
dir.finalOutputDir(
|
dir.finalOutputDir(
|
||||||
|
@ -37,14 +37,15 @@ import kotlinx.serialization.json.jsonPrimitive
|
|||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import kotlinx.serialization.json.putJsonObject
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
private const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
|
|
||||||
|
|
||||||
class YoutubeMusic constructor(
|
class YoutubeMusic constructor(
|
||||||
private val logger: Kermit,
|
private val logger: Kermit,
|
||||||
private val httpClient: HttpClient,
|
private val httpClient: HttpClient,
|
||||||
) {
|
) {
|
||||||
private val tag = "YT Music"
|
|
||||||
|
companion object {
|
||||||
|
const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
|
||||||
|
const val tag = "YT Music"
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getYTIDBestMatch(query: String, trackDetails: TrackDetails): String? {
|
suspend fun getYTIDBestMatch(query: String, trackDetails: TrackDetails): String? {
|
||||||
return try {
|
return try {
|
||||||
@ -213,11 +214,11 @@ class YoutubeMusic constructor(
|
|||||||
trackName: String,
|
trackName: String,
|
||||||
trackArtists: List<String>,
|
trackArtists: List<String>,
|
||||||
trackDurationSec: Int,
|
trackDurationSec: Int,
|
||||||
): Map<String, Int> {
|
): Map<String, Float> {
|
||||||
/*
|
/*
|
||||||
* "linksWithMatchValue" is map with Youtube VideoID and its rating/match with 100 as Max Value
|
* "linksWithMatchValue" is map with Youtube VideoID and its rating/match with 100 as Max Value
|
||||||
**/
|
**/
|
||||||
val linksWithMatchValue = mutableMapOf<String, Int>()
|
val linksWithMatchValue = mutableMapOf<String, Float>()
|
||||||
|
|
||||||
for (result in ytTracks) {
|
for (result in ytTracks) {
|
||||||
|
|
||||||
@ -241,7 +242,7 @@ class YoutubeMusic constructor(
|
|||||||
// Find artist match
|
// Find artist match
|
||||||
// Will Be Using Fuzzy Search Because YT Spelling might be mucked up
|
// Will Be Using Fuzzy Search Because YT Spelling might be mucked up
|
||||||
// match = (no of artist names in result) / (no. of artist names on spotify) * 100
|
// match = (no of artist names in result) / (no. of artist names on spotify) * 100
|
||||||
var artistMatchNumber = 0
|
var artistMatchNumber = 0F
|
||||||
|
|
||||||
if (result.type == "Song") {
|
if (result.type == "Song") {
|
||||||
for (artist in trackArtists) {
|
for (artist in trackArtists) {
|
||||||
@ -255,26 +256,26 @@ class YoutubeMusic constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (artistMatchNumber == 0) {
|
if (artistMatchNumber == 0F) {
|
||||||
// logger.d{ "YT Api Removing: $result" }
|
// logger.d{ "YT Api Removing: $result" }
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val artistMatch = (artistMatchNumber / trackArtists.size) * 100
|
val artistMatch = (artistMatchNumber / trackArtists.size.toFloat()) * 100F
|
||||||
|
|
||||||
// Duration Match
|
// Duration Match
|
||||||
/*! time match = 100 - (delta(duration)**2 / original duration * 100)
|
/*! time match = 100 - (delta(duration)**2 / original duration * 100)
|
||||||
! difference in song duration (delta) is usually of the magnitude of a few
|
! difference in song duration (delta) is usually of the magnitude of a few
|
||||||
! seconds, we need to amplify the delta if it is to have any meaningful impact
|
! seconds, we need to amplify the delta if it is to have any meaningful impact
|
||||||
! wen we calculate the avg match value*/
|
! wen we calculate the avg match value*/
|
||||||
val difference = result.duration?.split(":")?.get(0)?.toInt()?.times(60)
|
val difference: Float = result.duration?.split(":")?.get(0)?.toFloat()?.times(60)
|
||||||
?.plus(result.duration?.split(":")?.get(1)?.toInt() ?: 0)
|
?.plus(result.duration?.split(":")?.get(1)?.toFloat() ?: 0F)
|
||||||
?.minus(trackDurationSec)?.absoluteValue ?: 0
|
?.minus(trackDurationSec)?.absoluteValue ?: 0F
|
||||||
val nonMatchValue: Float = ((difference * difference).toFloat() / trackDurationSec.toFloat())
|
val nonMatchValue: Float = ((difference * difference) / trackDurationSec.toFloat())
|
||||||
val durationMatch = 100 - (nonMatchValue * 100)
|
val durationMatch: Float = 100 - (nonMatchValue * 100F)
|
||||||
|
|
||||||
val avgMatch = (artistMatch + durationMatch) / 2
|
val avgMatch: Float = (artistMatch + durationMatch) / 2F
|
||||||
linksWithMatchValue[result.videoId.toString()] = avgMatch.toInt()
|
linksWithMatchValue[result.videoId.toString()] = avgMatch
|
||||||
}
|
}
|
||||||
// logger.d("YT Api Result"){"$trackName - $linksWithMatchValue"}
|
// logger.d("YT Api Result"){"$trackName - $linksWithMatchValue"}
|
||||||
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap().also {
|
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap().also {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.shabinder.common.di.saavn
|
package com.shabinder.common.di.saavn
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import io.ktor.util.InternalAPI
|
import io.ktor.util.InternalAPI
|
||||||
import io.ktor.util.decodeBase64Bytes
|
import io.ktor.util.decodeBase64Bytes
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
@ -9,7 +8,7 @@ import javax.crypto.SecretKey
|
|||||||
import javax.crypto.SecretKeyFactory
|
import javax.crypto.SecretKeyFactory
|
||||||
import javax.crypto.spec.DESKeySpec
|
import javax.crypto.spec.DESKeySpec
|
||||||
|
|
||||||
@SuppressLint("GetInstance")
|
@Suppress("GetInstance")
|
||||||
@OptIn(InternalAPI::class)
|
@OptIn(InternalAPI::class)
|
||||||
actual suspend fun decryptURL(url: String): String {
|
actual suspend fun decryptURL(url: String): String {
|
||||||
val dks = DESKeySpec("38346591".toByteArray())
|
val dks = DESKeySpec("38346591".toByteArray())
|
||||||
|
@ -18,6 +18,7 @@ application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(Extras.fuzzyWuzzy)
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinVersion}")
|
implementation("org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinVersion}")
|
||||||
implementation("io.ktor:ktor-client-core:1.5.4")
|
implementation("io.ktor:ktor-client-core:1.5.4")
|
||||||
implementation("io.ktor:ktor-client-apache:1.5.4")
|
implementation("io.ktor:ktor-client-apache:1.5.4")
|
||||||
|
@ -1,14 +1,80 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
|
import io.github.shabinder.fuzzywuzzy.diffutils.FuzzySearch
|
||||||
import jiosaavn.JioSaavnRequests
|
import jiosaavn.JioSaavnRequests
|
||||||
import jiosaavn.models.SaavnPlaylist
|
import jiosaavn.models.SaavnSearchResult
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
// Test Class- at development Time
|
// Test Class- at development Time
|
||||||
fun main() = runBlocking {
|
fun main(): Unit = runBlocking {
|
||||||
val jioSaavnClient = object : JioSaavnRequests {}
|
val jioSaavnClient = object : JioSaavnRequests {}
|
||||||
val resp: SaavnPlaylist? = jioSaavnClient.getPlaylist(
|
val resp = jioSaavnClient.searchForSong(
|
||||||
URL = "https://www.jiosaavn.com/featured/hindi_chartbusters/u-75xwHI4ks_"
|
query = "Filhall"
|
||||||
)
|
)
|
||||||
println(resp)
|
println(resp.joinToString("\n"))
|
||||||
|
|
||||||
|
val matches = sortByBestMatch(
|
||||||
|
tracks = resp,
|
||||||
|
trackName = "Filhall",
|
||||||
|
trackArtists = listOf("B.Praak", "Nupur Sanon")
|
||||||
|
)
|
||||||
|
debug(matches.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sortByBestMatch(
|
||||||
|
tracks: List<SaavnSearchResult>,
|
||||||
|
trackName: String,
|
||||||
|
trackArtists: List<String>,
|
||||||
|
): Map<String, Float> {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "linksWithMatchValue" is map with Saavn VideoID and its rating/match with 100 as Max Value
|
||||||
|
**/
|
||||||
|
val linksWithMatchValue = mutableMapOf<String, Float>()
|
||||||
|
|
||||||
|
for (result in tracks) {
|
||||||
|
var hasCommonWord = false
|
||||||
|
|
||||||
|
val resultName = result.title.toLowerCase().replace("/", " ")
|
||||||
|
val trackNameWords = trackName.toLowerCase().split(" ")
|
||||||
|
|
||||||
|
for (nameWord in trackNameWords) {
|
||||||
|
if (nameWord.isNotBlank() && FuzzySearch.partialRatio(nameWord, resultName) > 85) hasCommonWord = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip this Result if No Word is Common in Name
|
||||||
|
if (!hasCommonWord) {
|
||||||
|
debug("Saavn Removing Common Word: ", result.toString())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find artist match
|
||||||
|
// Will Be Using Fuzzy Search Because YT Spelling might be mucked up
|
||||||
|
// match = (no of artist names in result) / (no. of artist names on spotify) * 100
|
||||||
|
var artistMatchNumber = 0
|
||||||
|
|
||||||
|
// String Containing All Artist Names from JioSaavn Search Result
|
||||||
|
val artistListString = mutableSetOf<String>().apply {
|
||||||
|
result.more_info?.singers?.split(",")?.let { addAll(it) }
|
||||||
|
result.more_info?.primary_artists?.toLowerCase()?.split(",")?.let { addAll(it) }
|
||||||
|
}.joinToString(" , ")
|
||||||
|
|
||||||
|
for (artist in trackArtists) {
|
||||||
|
if (FuzzySearch.partialRatio(artist.toLowerCase(), artistListString) > 85)
|
||||||
|
artistMatchNumber++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (artistMatchNumber == 0) {
|
||||||
|
debug("Artist Match Saavn Removing: $result")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val artistMatch: Float = (artistMatchNumber.toFloat() / trackArtists.size) * 100
|
||||||
|
val nameMatch: Float = FuzzySearch.partialRatio(resultName, trackName).toFloat() / 100
|
||||||
|
val avgMatch = (artistMatch + nameMatch) / 2
|
||||||
|
|
||||||
|
linksWithMatchValue[result.id] = avgMatch
|
||||||
|
}
|
||||||
|
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap().also {
|
||||||
|
debug("Match Found for $trackName - ${!it.isNullOrEmpty()}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user