mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 17:14:32 +01:00
YoutubeMusic.kt Ported to KMP
This commit is contained in:
parent
cc4362963a
commit
6e20cc3b0a
@ -5,6 +5,7 @@ import com.shabinder.common.database.createDb
|
|||||||
import com.shabinder.common.database.getLogger
|
import com.shabinder.common.database.getLogger
|
||||||
import com.shabinder.common.providers.GaanaProvider
|
import com.shabinder.common.providers.GaanaProvider
|
||||||
import com.shabinder.common.providers.SpotifyProvider
|
import com.shabinder.common.providers.SpotifyProvider
|
||||||
|
import com.shabinder.common.providers.YoutubeMusic
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.features.json.*
|
import io.ktor.client.features.json.*
|
||||||
import io.ktor.client.features.json.serializer.*
|
import io.ktor.client.features.json.serializer.*
|
||||||
@ -24,6 +25,7 @@ fun commonModule(enableNetworkLogs: Boolean) = module {
|
|||||||
single { Dir() }
|
single { Dir() }
|
||||||
single { createDb() }
|
single { createDb() }
|
||||||
single { Kermit(getLogger()) }
|
single { Kermit(getLogger()) }
|
||||||
|
single { YoutubeMusic(get(),get()) }
|
||||||
single { SpotifyProvider(get(),get(),get(),get()) }
|
single { SpotifyProvider(get(),get(),get(),get()) }
|
||||||
single { GaanaProvider(get(),get(),get(),get()) }
|
single { GaanaProvider(get(),get(),get(),get()) }
|
||||||
single { YoutubeProvider(get(),get(),get(),get()) }
|
single { YoutubeProvider(get(),get(),get(),get()) }
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package com.shabinder.common
|
|
||||||
|
|
||||||
import co.touchlab.kermit.Logger
|
|
||||||
import io.ktor.client.*
|
|
||||||
|
|
||||||
expect class YoutubeMusic(
|
|
||||||
logger: Logger,
|
|
||||||
httpClient: HttpClient
|
|
||||||
) {
|
|
||||||
fun getYTTracks(response: String): List<YoutubeTrack>
|
|
||||||
fun sortByBestMatch(
|
|
||||||
ytTracks: List<YoutubeTrack>,
|
|
||||||
trackName: String,
|
|
||||||
trackArtists: List<String>,
|
|
||||||
trackDurationSec: Int
|
|
||||||
): Map<String, Int>
|
|
||||||
|
|
||||||
suspend fun getYoutubeMusicResponse(query: String): String
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +1,30 @@
|
|||||||
package com.shabinder.common
|
package com.shabinder.common.providers
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import co.touchlab.kermit.Logger
|
import co.touchlab.kermit.Logger
|
||||||
import com.beust.klaxon.JsonArray
|
import com.shabinder.common.YoutubeTrack
|
||||||
import com.beust.klaxon.JsonObject
|
|
||||||
import com.beust.klaxon.Parser
|
|
||||||
import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch
|
import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.*
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
import kotlinx.serialization.json.putJsonObject
|
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
private const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
|
private const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
|
||||||
|
|
||||||
actual class YoutubeMusic actual constructor(
|
class YoutubeMusic constructor(
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
private val httpClient:HttpClient,
|
private val httpClient:HttpClient,
|
||||||
) {
|
) {
|
||||||
private val tag = "YTMUSIC"
|
private val tag = "YT Music"
|
||||||
actual fun getYTTracks(response: String):List<YoutubeTrack>{
|
suspend fun getYTTracks(query: String):List<YoutubeTrack>{
|
||||||
val youtubeTracks = mutableListOf<YoutubeTrack>()
|
val youtubeTracks = mutableListOf<YoutubeTrack>()
|
||||||
|
|
||||||
val stringBuilder: StringBuilder = StringBuilder(response)
|
val responseObj = Json.parseToJsonElement(getYoutubeMusicResponse(query))
|
||||||
val responseObj: JsonObject = Parser.default().parse(stringBuilder) as JsonObject
|
val contentBlocks = responseObj.jsonObject["contents"]
|
||||||
val contentBlocks = responseObj.obj("contents")?.obj("sectionListRenderer")?.array<JsonObject>("contents")
|
?.jsonObject?.get("sectionListRenderer")
|
||||||
val resultBlocks = mutableListOf<JsonArray<JsonObject>>()
|
?.jsonObject?.get("contents")?.jsonArray
|
||||||
|
|
||||||
|
val resultBlocks = mutableListOf<JsonArray>()
|
||||||
if (contentBlocks != null) {
|
if (contentBlocks != null) {
|
||||||
for (cBlock in contentBlocks){
|
for (cBlock in contentBlocks){
|
||||||
/**
|
/**
|
||||||
@ -36,11 +33,12 @@ actual class YoutubeMusic actual constructor(
|
|||||||
*results for xyz, search for abc instead') we have no use for them, the for
|
*results for xyz, search for abc instead') we have no use for them, the for
|
||||||
*loop below if throw a keyError if we don't ignore them
|
*loop below if throw a keyError if we don't ignore them
|
||||||
*/
|
*/
|
||||||
if(cBlock.containsKey("itemSectionRenderer")){
|
if(cBlock.jsonObject.containsKey("itemSectionRenderer")){
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for(contents in cBlock.obj("musicShelfRenderer")?.array<JsonObject>("contents") ?: listOf()){
|
for(contents in cBlock.jsonObject["musicShelfRenderer"]?.jsonObject?.get("contents")?.jsonArray
|
||||||
|
?: listOf()){
|
||||||
/**
|
/**
|
||||||
* apparently content Blocks without an 'overlay' field don't have linkBlocks
|
* apparently content Blocks without an 'overlay' field don't have linkBlocks
|
||||||
* I have no clue what they are and why there even exist
|
* I have no clue what they are and why there even exist
|
||||||
@ -51,21 +49,24 @@ actual class YoutubeMusic actual constructor(
|
|||||||
TODO check and correct
|
TODO check and correct
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
val result = contents.obj("musicResponsiveListItemRenderer")
|
val result = contents.jsonObject["musicResponsiveListItemRenderer"]
|
||||||
?.array<JsonObject>("flexColumns")
|
?.jsonObject?.get("flexColumns")?.jsonArray
|
||||||
|
|
||||||
//Add the linkBlock
|
//Add the linkBlock
|
||||||
val linkBlock = contents.obj("musicResponsiveListItemRenderer")
|
val linkBlock = contents.jsonObject["musicResponsiveListItemRenderer"]
|
||||||
?.obj("overlay")
|
?.jsonObject?.get("overlay")
|
||||||
?.obj("musicItemThumbnailOverlayRenderer")
|
?.jsonObject?.get("musicItemThumbnailOverlayRenderer")
|
||||||
?.obj("content")
|
?.jsonObject?.get("content")
|
||||||
?.obj("musicPlayButtonRenderer")
|
?.jsonObject?.get("musicPlayButtonRenderer")
|
||||||
?.obj("playNavigationEndpoint")
|
?.jsonObject?.get("playNavigationEndpoint")
|
||||||
|
|
||||||
// detailsBlock is always a list, so we just append the linkBlock to it
|
// detailsBlock is always a list, so we just append the linkBlock to it
|
||||||
// instead of carrying along all the other junk from "musicResponsiveListItemRenderer"
|
// instead of carrying along all the other junk from "musicResponsiveListItemRenderer"
|
||||||
linkBlock?.let { result?.add(it) }
|
val finalResult = buildJsonArray {
|
||||||
result?.let { resultBlocks.add(it) }
|
result?.let { add(it) }
|
||||||
|
linkBlock?.let { add(it) }
|
||||||
|
}
|
||||||
|
resultBlocks.add(finalResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,18 +110,17 @@ actual class YoutubeMusic actual constructor(
|
|||||||
! result[:-1] ,i.e., skip last element in array '
|
! result[:-1] ,i.e., skip last element in array '
|
||||||
*/
|
*/
|
||||||
for(detail in result.subList(0,result.size-1)){
|
for(detail in result.subList(0,result.size-1)){
|
||||||
if(detail.obj("musicResponsiveListItemFlexColumnRenderer")?.size!! < 2) continue
|
if(detail.jsonObject["musicResponsiveListItemFlexColumnRenderer"]?.jsonObject?.size?:0 < 2) continue
|
||||||
|
|
||||||
// if not a dummy, collect All Variables
|
// if not a dummy, collect All Variables
|
||||||
val details = detail.obj("musicResponsiveListItemFlexColumnRenderer")
|
val details = detail.jsonObject["musicResponsiveListItemFlexColumnRenderer"]
|
||||||
?.obj("text")
|
?.jsonObject?.get("text")
|
||||||
?.array<JsonObject>("runs") ?: listOf()
|
?.jsonObject?.get("runs")?.jsonArray ?: listOf()
|
||||||
|
|
||||||
for (d in details){
|
for (d in details){
|
||||||
d["text"]?.let {
|
d.jsonObject["text"]?.jsonPrimitive?.contentOrNull?.let {
|
||||||
if(it.toString() != " • "){
|
if(it != " • "){
|
||||||
availableDetails.add(
|
availableDetails.add(it)
|
||||||
it.toString()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,7 +143,7 @@ actual class YoutubeMusic actual constructor(
|
|||||||
! reference the dict keys by index
|
! reference the dict keys by index
|
||||||
*/
|
*/
|
||||||
|
|
||||||
val videoId:String? = result.last().obj("watchEndpoint")?.get("videoId") as String?
|
val videoId:String? = result.last().jsonObject["watchEndpoint"]?.jsonObject?.get("videoId")?.jsonPrimitive?.content
|
||||||
val ytTrack = YoutubeTrack(
|
val ytTrack = YoutubeTrack(
|
||||||
name = availableDetails[0],
|
name = availableDetails[0],
|
||||||
type = availableDetails[1],
|
type = availableDetails[1],
|
||||||
@ -152,7 +152,6 @@ actual class YoutubeMusic actual constructor(
|
|||||||
videoId = videoId
|
videoId = videoId
|
||||||
)
|
)
|
||||||
youtubeTracks.add(ytTrack)
|
youtubeTracks.add(ytTrack)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,8 +159,8 @@ actual class YoutubeMusic actual constructor(
|
|||||||
return youtubeTracks
|
return youtubeTracks
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
fun sortByBestMatch(
|
||||||
actual fun sortByBestMatch(ytTracks:List<YoutubeTrack>,
|
ytTracks:List<YoutubeTrack>,
|
||||||
trackName:String,
|
trackName:String,
|
||||||
trackArtists:List<String>,
|
trackArtists:List<String>,
|
||||||
trackDurationSec:Int,
|
trackDurationSec:Int,
|
||||||
@ -233,7 +232,7 @@ actual class YoutubeMusic actual constructor(
|
|||||||
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap()
|
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
actual suspend fun getYoutubeMusicResponse(query: String):String{
|
private suspend fun getYoutubeMusicResponse(query: String):String{
|
||||||
return httpClient.post("https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey"){
|
return httpClient.post("https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey"){
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
headers{
|
headers{
|
@ -1,252 +0,0 @@
|
|||||||
package com.shabinder.common
|
|
||||||
|
|
||||||
import co.touchlab.kermit.Logger
|
|
||||||
import com.beust.klaxon.JsonArray
|
|
||||||
import com.beust.klaxon.JsonObject
|
|
||||||
import com.beust.klaxon.Parser
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
import kotlinx.serialization.json.putJsonObject
|
|
||||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
private const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
|
|
||||||
|
|
||||||
actual class YoutubeMusic actual constructor(
|
|
||||||
private val logger: Logger,
|
|
||||||
private val httpClient:HttpClient,
|
|
||||||
) {
|
|
||||||
private val tag = "YTMUSIC"
|
|
||||||
actual fun getYTTracks(response: String):List<YoutubeTrack>{
|
|
||||||
val youtubeTracks = mutableListOf<YoutubeTrack>()
|
|
||||||
|
|
||||||
val stringBuilder: StringBuilder = StringBuilder(response)
|
|
||||||
val responseObj: JsonObject = Parser.default().parse(stringBuilder) as JsonObject
|
|
||||||
val contentBlocks = responseObj.obj("contents")?.obj("sectionListRenderer")?.array<JsonObject>("contents")
|
|
||||||
val resultBlocks = mutableListOf<JsonArray<JsonObject>>()
|
|
||||||
if (contentBlocks != null) {
|
|
||||||
for (cBlock in contentBlocks){
|
|
||||||
/**
|
|
||||||
*Ignore user-suggestion
|
|
||||||
*The 'itemSectionRenderer' field is for user notices (stuff like - 'showing
|
|
||||||
*results for xyz, search for abc instead') we have no use for them, the for
|
|
||||||
*loop below if throw a keyError if we don't ignore them
|
|
||||||
*/
|
|
||||||
if(cBlock.containsKey("itemSectionRenderer")){
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for(contents in cBlock.obj("musicShelfRenderer")?.array<JsonObject>("contents") ?: listOf()){
|
|
||||||
/**
|
|
||||||
* apparently content Blocks without an 'overlay' field don't have linkBlocks
|
|
||||||
* I have no clue what they are and why there even exist
|
|
||||||
*
|
|
||||||
if(!contents.containsKey("overlay")){
|
|
||||||
println(contents)
|
|
||||||
continue
|
|
||||||
TODO check and correct
|
|
||||||
}*/
|
|
||||||
|
|
||||||
val result = contents.obj("musicResponsiveListItemRenderer")
|
|
||||||
?.array<JsonObject>("flexColumns")
|
|
||||||
|
|
||||||
//Add the linkBlock
|
|
||||||
val linkBlock = contents.obj("musicResponsiveListItemRenderer")
|
|
||||||
?.obj("overlay")
|
|
||||||
?.obj("musicItemThumbnailOverlayRenderer")
|
|
||||||
?.obj("content")
|
|
||||||
?.obj("musicPlayButtonRenderer")
|
|
||||||
?.obj("playNavigationEndpoint")
|
|
||||||
|
|
||||||
// detailsBlock is always a list, so we just append the linkBlock to it
|
|
||||||
// instead of carrying along all the other junk from "musicResponsiveListItemRenderer"
|
|
||||||
linkBlock?.let { result?.add(it) }
|
|
||||||
result?.let { resultBlocks.add(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We only need results that are Songs or Videos, so we filter out the rest, since
|
|
||||||
! Songs and Videos are supplied with different details, extracting all details from
|
|
||||||
! both is just carrying on redundant data, so we also have to selectively extract
|
|
||||||
! relevant details. What you need to know to understand how we do that here:
|
|
||||||
!
|
|
||||||
! Songs details are ALWAYS in the following order:
|
|
||||||
! 0 - Name
|
|
||||||
! 1 - Type (Song)
|
|
||||||
! 2 - com.shabinder.spotiflyer.models.gaana.Artist
|
|
||||||
! 3 - Album
|
|
||||||
! 4 - Duration (mm:ss)
|
|
||||||
!
|
|
||||||
! Video details are ALWAYS in the following order:
|
|
||||||
! 0 - Name
|
|
||||||
! 1 - Type (Video)
|
|
||||||
! 2 - Channel
|
|
||||||
! 3 - Viewers
|
|
||||||
! 4 - Duration (hh:mm:ss)
|
|
||||||
!
|
|
||||||
! We blindly gather all the details we get our hands on, then
|
|
||||||
! cherrypick the details we need based on their index numbers,
|
|
||||||
! we do so only if their Type is 'Song' or 'Video
|
|
||||||
*/
|
|
||||||
|
|
||||||
for(result in resultBlocks){
|
|
||||||
|
|
||||||
// Blindly gather available details
|
|
||||||
val availableDetails = mutableListOf<String>()
|
|
||||||
|
|
||||||
/*
|
|
||||||
Filter Out dummies here itself
|
|
||||||
! 'musicResponsiveListItemFlexColumnRenderer' should have more that one
|
|
||||||
! sub-block, if not its a dummy, why does the YTM response contain dummies?
|
|
||||||
! I have no clue. We skip these.
|
|
||||||
|
|
||||||
! Remember that we appended the linkBlock to result, treating that like the
|
|
||||||
! other constituents of a result block will lead to errors, hence the 'in
|
|
||||||
! result[:-1] ,i.e., skip last element in array '
|
|
||||||
*/
|
|
||||||
for(detail in result.subList(0,result.size-1)){
|
|
||||||
if(detail.obj("musicResponsiveListItemFlexColumnRenderer")?.size!! < 2) continue
|
|
||||||
|
|
||||||
// if not a dummy, collect All Variables
|
|
||||||
val details = detail.obj("musicResponsiveListItemFlexColumnRenderer")
|
|
||||||
?.obj("text")
|
|
||||||
?.array<JsonObject>("runs") ?: listOf()
|
|
||||||
for (d in details){
|
|
||||||
d["text"]?.let {
|
|
||||||
if(it.toString() != " • "){
|
|
||||||
availableDetails.add(
|
|
||||||
it.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// log("YT Music details",availableDetails.toString())
|
|
||||||
/*
|
|
||||||
! Filter Out non-Song/Video results and incomplete results here itself
|
|
||||||
! From what we know about detail order, note that [1] - indicate result type
|
|
||||||
*/
|
|
||||||
if ( availableDetails.size == 5 && availableDetails[1] in listOf("Song","Video") ){
|
|
||||||
|
|
||||||
// skip if result is in hours instead of minutes (no song is that long)
|
|
||||||
if(availableDetails[4].split(':').size != 2) continue
|
|
||||||
|
|
||||||
/*
|
|
||||||
! grab Video ID
|
|
||||||
! this is nested as [playlistEndpoint/watchEndpoint][videoId/playlistId/...]
|
|
||||||
! so hardcoding the dict keys for data look up is an ardours process, since
|
|
||||||
! the sub-block pattern is fixed even though the key isn't, we just
|
|
||||||
! reference the dict keys by index
|
|
||||||
*/
|
|
||||||
|
|
||||||
val videoId:String? = result.last().obj("watchEndpoint")?.get("videoId") as String?
|
|
||||||
val ytTrack = YoutubeTrack(
|
|
||||||
name = availableDetails[0],
|
|
||||||
type = availableDetails[1],
|
|
||||||
artist = availableDetails[2],
|
|
||||||
duration = availableDetails[4],
|
|
||||||
videoId = videoId
|
|
||||||
)
|
|
||||||
youtubeTracks.add(ytTrack)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.i(youtubeTracks.joinToString(" abc \n"),tag)
|
|
||||||
return youtubeTracks
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun sortByBestMatch(ytTracks:List<YoutubeTrack>,
|
|
||||||
trackName:String,
|
|
||||||
trackArtists:List<String>,
|
|
||||||
trackDurationSec:Int,
|
|
||||||
):Map<String,Int>{
|
|
||||||
/*
|
|
||||||
* "linksWithMatchValue" is map with Youtube VideoID and its rating/match with 100 as Max Value
|
|
||||||
**/
|
|
||||||
val linksWithMatchValue = mutableMapOf<String,Int>()
|
|
||||||
|
|
||||||
for (result in ytTracks){
|
|
||||||
|
|
||||||
// LoweCasing Name to match Properly
|
|
||||||
// most song results on youtube go by $artist - $songName or artist1/artist2
|
|
||||||
var hasCommonWord = false
|
|
||||||
|
|
||||||
val resultName = result.name?.toLowerCase()?.replace("-"," ")?.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("YT Api 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 = 0
|
|
||||||
|
|
||||||
if(result.type == "Song"){
|
|
||||||
for (artist in trackArtists){
|
|
||||||
if(FuzzySearch.ratio(artist.toLowerCase(),result.artist?.toLowerCase()) > 85)
|
|
||||||
artistMatchNumber++
|
|
||||||
}
|
|
||||||
}else{//i.e. is a Video
|
|
||||||
for (artist in trackArtists) {
|
|
||||||
if(FuzzySearch.partialRatio(artist.toLowerCase(),result.name?.toLowerCase()) > 85)
|
|
||||||
artistMatchNumber++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(artistMatchNumber == 0) {
|
|
||||||
//log("YT Api Removing", result.toString())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val artistMatch = (artistMatchNumber / trackArtists.size ) * 100
|
|
||||||
|
|
||||||
// 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 avgMatch = (artistMatch + durationMatch)/2
|
|
||||||
linksWithMatchValue[result.videoId.toString()] = avgMatch.toInt()
|
|
||||||
}
|
|
||||||
//log("YT Api Result", "$trackName - $linksWithMatchValue")
|
|
||||||
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
actual suspend fun getYoutubeMusicResponse(query: String):String{
|
|
||||||
return httpClient.post("https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey"){
|
|
||||||
contentType(ContentType.Application.Json)
|
|
||||||
headers{
|
|
||||||
//append("Content-Type"," application/json")
|
|
||||||
append("Referer"," https://music.youtube.com/search")
|
|
||||||
}
|
|
||||||
body = buildJsonObject {
|
|
||||||
putJsonObject("context"){
|
|
||||||
putJsonObject("client"){
|
|
||||||
put("clientName" ,"WEB_REMIX")
|
|
||||||
put("clientVersion" ,"0.1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
put("query",query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,15 +24,17 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
wasm32("wasm")
|
||||||
iosArm64("ios")
|
iosArm64("ios")
|
||||||
iosX64("iosSim")
|
iosX64("iosSim")
|
||||||
macosX64("macos")
|
macosX64("macos")
|
||||||
mingwX64("win")
|
mingwX64("win")
|
||||||
wasm32("wasm")
|
|
||||||
linuxArm32Hfp("linArm32")
|
linuxArm32Hfp("linArm32")
|
||||||
linuxMips32("linMips32")
|
linuxMips32("linMips32")
|
||||||
linuxMipsel32("linMipsel32")
|
linuxMipsel32("linMipsel32")
|
||||||
linuxX64("lin64")
|
linuxX64("lin64")
|
||||||
|
*/
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
@ -81,16 +83,16 @@ kotlin {
|
|||||||
implementation kotlin("stdlib-js")
|
implementation kotlin("stdlib-js")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nativeMain {
|
/*nativeMain {
|
||||||
kotlin.srcDir('src/nativeMain/kotlin')
|
kotlin.srcDir('src/nativeMain/kotlin')
|
||||||
}
|
}*/
|
||||||
|
|
||||||
iosSimMain.dependsOn iosMain
|
//iosSimMain.dependsOn iosMain
|
||||||
iosSimTest.dependsOn iosTest
|
//iosSimTest.dependsOn iosTest
|
||||||
|
|
||||||
configure([targets.ios, targets.iosSim, targets.macos, targets.win, targets.linArm32, targets.linMips32, targets.linMipsel32, targets.lin64]) {
|
/*configure([targets.ios, targets.iosSim, targets.macos, targets.win, targets.linArm32, targets.linMips32, targets.linMipsel32, targets.lin64]) {
|
||||||
compilations.main.source(sourceSets.nativeMain)
|
compilations.main.source(sourceSets.nativeMain)
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user