mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-21 16:54:33 +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("dev.icerock.moko:parcelize:0.6.1")
|
||||
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
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
|
@ -28,7 +28,7 @@ object Versions {
|
||||
const val ktLint = "10.0.0"
|
||||
|
||||
// DI
|
||||
const val koin = "3.0.1"
|
||||
const val koin = "3.0.2"
|
||||
|
||||
// Logger
|
||||
const val kermit = "0.1.9"
|
||||
@ -62,10 +62,10 @@ object Koin {
|
||||
val core = "io.insert-koin:koin-core:${Versions.koin}"
|
||||
val test = "io.insert-koin:koin-test:${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 {
|
||||
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 palette = "androidx.palette:palette-ktx:1.0.0"
|
||||
const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutinesVersion}"
|
||||
|
@ -36,6 +36,7 @@ data class TrackDetails(
|
||||
var albumArtURL: String,
|
||||
var source: Source,
|
||||
val progress: Int = 2,
|
||||
val downloadLink: String? = null,
|
||||
val downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
|
||||
var outputFilePath: String, // UriString in Android
|
||||
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.PlatformQueryResult
|
||||
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.spotify.Source
|
||||
import io.github.shabinder.fuzzywuzzy.diffutils.FuzzySearch
|
||||
import io.ktor.client.HttpClient
|
||||
|
||||
class SaavnProvider(
|
||||
@ -75,14 +77,76 @@ class SaavnProvider(
|
||||
year = it.year,
|
||||
comment = it.copyright_text,
|
||||
trackUrl = it.perma_url,
|
||||
videoID = it.id,
|
||||
downloadLink = it.media_url, // Downloadable Link
|
||||
downloaded = it.updateStatusIfPresent(type, subFolder),
|
||||
albumArtURL = it.image.replace("http:", "https:"),
|
||||
lyrics = it.lyrics ?: it.lyrics_snippet,
|
||||
videoID = it.media_url, // Downloadable Link
|
||||
source = Source.JioSaavn,
|
||||
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 {
|
||||
return if (dir.isPresent(
|
||||
dir.finalOutputDir(
|
||||
|
@ -37,14 +37,15 @@ import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
import kotlinx.serialization.json.putJsonObject
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
private const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
|
||||
|
||||
class YoutubeMusic constructor(
|
||||
private val logger: Kermit,
|
||||
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? {
|
||||
return try {
|
||||
@ -213,11 +214,11 @@ class YoutubeMusic constructor(
|
||||
trackName: String,
|
||||
trackArtists: List<String>,
|
||||
trackDurationSec: Int,
|
||||
): Map<String, Int> {
|
||||
): Map<String, Float> {
|
||||
/*
|
||||
* "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) {
|
||||
|
||||
@ -241,7 +242,7 @@ class YoutubeMusic constructor(
|
||||
// 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
|
||||
var artistMatchNumber = 0F
|
||||
|
||||
if (result.type == "Song") {
|
||||
for (artist in trackArtists) {
|
||||
@ -255,26 +256,26 @@ class YoutubeMusic constructor(
|
||||
}
|
||||
}
|
||||
|
||||
if (artistMatchNumber == 0) {
|
||||
if (artistMatchNumber == 0F) {
|
||||
// logger.d{ "YT Api Removing: $result" }
|
||||
continue
|
||||
}
|
||||
|
||||
val artistMatch = (artistMatchNumber / trackArtists.size) * 100
|
||||
val artistMatch = (artistMatchNumber / trackArtists.size.toFloat()) * 100F
|
||||
|
||||
// Duration Match
|
||||
/*! time match = 100 - (delta(duration)**2 / original duration * 100)
|
||||
! 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
|
||||
! wen we calculate the avg match value*/
|
||||
val difference = result.duration?.split(":")?.get(0)?.toInt()?.times(60)
|
||||
?.plus(result.duration?.split(":")?.get(1)?.toInt() ?: 0)
|
||||
?.minus(trackDurationSec)?.absoluteValue ?: 0
|
||||
val nonMatchValue: Float = ((difference * difference).toFloat() / trackDurationSec.toFloat())
|
||||
val durationMatch = 100 - (nonMatchValue * 100)
|
||||
val difference: Float = result.duration?.split(":")?.get(0)?.toFloat()?.times(60)
|
||||
?.plus(result.duration?.split(":")?.get(1)?.toFloat() ?: 0F)
|
||||
?.minus(trackDurationSec)?.absoluteValue ?: 0F
|
||||
val nonMatchValue: Float = ((difference * difference) / trackDurationSec.toFloat())
|
||||
val durationMatch: Float = 100 - (nonMatchValue * 100F)
|
||||
|
||||
val avgMatch = (artistMatch + durationMatch) / 2
|
||||
linksWithMatchValue[result.videoId.toString()] = avgMatch.toInt()
|
||||
val avgMatch: Float = (artistMatch + durationMatch) / 2F
|
||||
linksWithMatchValue[result.videoId.toString()] = avgMatch
|
||||
}
|
||||
// logger.d("YT Api Result"){"$trackName - $linksWithMatchValue"}
|
||||
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap().also {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.shabinder.common.di.saavn
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import io.ktor.util.InternalAPI
|
||||
import io.ktor.util.decodeBase64Bytes
|
||||
import java.security.SecureRandom
|
||||
@ -9,7 +8,7 @@ import javax.crypto.SecretKey
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.DESKeySpec
|
||||
|
||||
@SuppressLint("GetInstance")
|
||||
@Suppress("GetInstance")
|
||||
@OptIn(InternalAPI::class)
|
||||
actual suspend fun decryptURL(url: String): String {
|
||||
val dks = DESKeySpec("38346591".toByteArray())
|
||||
|
@ -18,6 +18,7 @@ application {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(Extras.fuzzyWuzzy)
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinVersion}")
|
||||
implementation("io.ktor:ktor-client-core:1.5.4")
|
||||
implementation("io.ktor:ktor-client-apache:1.5.4")
|
||||
|
@ -1,14 +1,80 @@
|
||||
package utils
|
||||
|
||||
import io.github.shabinder.fuzzywuzzy.diffutils.FuzzySearch
|
||||
import jiosaavn.JioSaavnRequests
|
||||
import jiosaavn.models.SaavnPlaylist
|
||||
import jiosaavn.models.SaavnSearchResult
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
// Test Class- at development Time
|
||||
fun main() = runBlocking {
|
||||
fun main(): Unit = runBlocking {
|
||||
val jioSaavnClient = object : JioSaavnRequests {}
|
||||
val resp: SaavnPlaylist? = jioSaavnClient.getPlaylist(
|
||||
URL = "https://www.jiosaavn.com/featured/hindi_chartbusters/u-75xwHI4ks_"
|
||||
val resp = jioSaavnClient.searchForSong(
|
||||
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