This commit is contained in:
2024-02-27 22:09:30 +03:00
parent bfa3231823
commit 38a3141d43
479 changed files with 36348 additions and 10142 deletions

1
providers/lrclib/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build

View File

@@ -0,0 +1,24 @@
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.android.lint)
}
dependencies {
implementation(projects.providers.common)
implementation(libs.kotlin.coroutines)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.client.serialization)
implementation(libs.ktor.serialization.json)
detektPlugins(libs.detekt.compose)
detektPlugins(libs.detekt.formatting)
}
kotlin {
jvmToolchain(libs.versions.jvm.get().toInt())
}

View File

@@ -0,0 +1,79 @@
package it.hamy.lrclib
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.serialization.kotlinx.json.json
import it.hamy.extensions.runCatchingCancellable
import it.hamy.lrclib.models.Track
import it.hamy.lrclib.models.bestMatchingFor
import kotlinx.serialization.json.Json
import kotlin.time.Duration
object LrcLib {
private val client by lazy {
HttpClient(CIO) {
install(ContentNegotiation) {
json(
Json {
isLenient = true
ignoreUnknownKeys = true
}
)
}
defaultRequest {
url("https://lrclib.net")
}
expectSuccess = true
}
}
private suspend fun queryLyrics(artist: String, title: String, album: String? = null) =
client.get("/api/search") {
parameter("track_name", title)
parameter("artist_name", artist)
if (album != null) parameter("album_name", album)
}.body<List<Track>>().filter { it.syncedLyrics != null }
suspend fun lyrics(
artist: String,
title: String,
duration: Duration,
album: String? = null
) = runCatchingCancellable {
val tracks = queryLyrics(artist, title, album)
tracks.bestMatchingFor(title, duration)?.syncedLyrics?.let(LrcLib::Lyrics)
}
suspend fun lyrics(artist: String, title: String) = runCatchingCancellable {
queryLyrics(artist = artist, title = title, album = null)
}
@JvmInline
value class Lyrics(val text: String) {
val sentences
get() = runCatching {
buildMap {
put(0L, "")
text.trim().lines().filter { it.length >= 10 }.forEach {
put(
it[8].digitToInt() * 10L +
it[7].digitToInt() * 100 +
it[5].digitToInt() * 1000 +
it[4].digitToInt() * 10000 +
it[2].digitToInt() * 60 * 1000 +
it[1].digitToInt() * 600 * 1000,
it.substring(10)
)
}
}
}.getOrNull()
}
}

View File

@@ -0,0 +1,19 @@
package it.hamy.lrclib.models
import kotlinx.serialization.Serializable
import kotlin.math.abs
import kotlin.time.Duration
@Serializable
data class Track(
val id: Int,
val trackName: String,
val artistName: String,
val duration: Long,
val plainLyrics: String?,
val syncedLyrics: String?
)
internal fun List<Track>.bestMatchingFor(title: String, duration: Duration) =
firstOrNull { it.duration == duration.inWholeSeconds }
?: minByOrNull { abs(it.trackName.length - title.length) }