Skip to content

Commit

Permalink
Add fruit music lyrics provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Lambada10 committed Aug 9, 2024
1 parent ee3ee40 commit 8bb37b1
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 2 deletions.
9 changes: 9 additions & 0 deletions app/src/main/java/pl/lambada/songsync/data/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import pl.lambada.songsync.data.remote.github.GithubAPI
import pl.lambada.songsync.data.remote.lyrics_providers.others.AppleAPI
import pl.lambada.songsync.data.remote.lyrics_providers.others.LRCLibAPI
import pl.lambada.songsync.data.remote.lyrics_providers.others.NeteaseAPI
import pl.lambada.songsync.data.remote.lyrics_providers.spotify.SpotifyAPI
Expand Down Expand Up @@ -63,6 +64,9 @@ class MainViewModel : ViewModel() {

// Netease Track ID
private var neteaseID = 0L

// Apple Track ID
private var appleID = 0L
// TODO: Use values from SongInfo object returned by search instead of storing them here

/**
Expand Down Expand Up @@ -93,6 +97,10 @@ class MainViewModel : ViewModel() {
Providers.NETEASE -> NeteaseAPI().getSongInfo(query, offset).also {
this.neteaseID = it?.neteaseID ?: 0
} ?: throw NoTrackFoundException()

Providers.APPLE -> AppleAPI().getSongInfo(query).also {
this.appleID = it?.appleID ?: 0
} ?: throw NoTrackFoundException()
}
} catch (e: InternalErrorException) {
throw e
Expand All @@ -116,6 +124,7 @@ class MainViewModel : ViewModel() {
Providers.SPOTIFY -> SpotifyLyricsAPI().getSyncedLyrics(songLink, version)
Providers.LRCLIB -> LRCLibAPI().getSyncedLyrics(this.lrcLibID)
Providers.NETEASE -> NeteaseAPI().getSyncedLyrics(this.neteaseID)
Providers.APPLE -> AppleAPI().getSyncedLyrics(this.appleID)
}
} catch (e: Exception) {
null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package pl.lambada.songsync.data.remote.lyrics_providers.others

import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.lambada.songsync.data.EmptyQueryException
import pl.lambada.songsync.domain.model.SongInfo
import pl.lambada.songsync.domain.model.lyrics_providers.others.AppleLyricsResponse
import pl.lambada.songsync.domain.model.lyrics_providers.others.AppleSearchResponse
import pl.lambada.songsync.util.ext.toLrcTimestamp
import pl.lambada.songsync.util.networking.Ktor.client
import pl.lambada.songsync.util.networking.Ktor.json
import java.net.URLEncoder

class AppleAPI {
private val baseURL = "https://paxsenix.alwaysdata.net/"

suspend fun getSongInfo(query: SongInfo): SongInfo? {
val search = withContext(Dispatchers.IO) {
URLEncoder.encode(
"${query.songName} ${query.artistName}",
Charsets.UTF_8.toString()
)
}

if (search == " ")
throw EmptyQueryException()

val response = client.get(
baseURL + "searchAppleMusic.php?q=$search"
)
val responseBody = response.bodyAsText(Charsets.UTF_8)

if (response.status.value !in 200..299)
return null

val json = json.decodeFromString<List<AppleSearchResponse>>(responseBody)

val result = json[0]

return SongInfo(
songName = result.songName,
artistName = result.artistName,
songLink = result.url,
albumCoverLink = result.artwork.replace("{w}", "100").replace("{h}", "100").replace("{f}", "png"),
appleID = result.id
)
}

suspend fun getSyncedLyrics(id: Long): String? {
val response = client.get(
baseURL + "getAppleMusicLyrics.php?id=$id"
)
val responseBody = response.bodyAsText(Charsets.UTF_8)

if (response.status.value !in 200..299 || responseBody.isEmpty())
return null

val json = json.decodeFromString<AppleLyricsResponse>(responseBody)

if (json.content!!.isEmpty())
return null

val syncedLyrics = StringBuilder()
val lines = json.content

when (json.type) {
"Syllable" -> {
for (line in lines) {
syncedLyrics.append("[${line.timestamp.toLrcTimestamp()}]")

for (syllable in line.text) {
syncedLyrics.append("<${syllable.timestamp!!.toLrcTimestamp()}>${syllable.text} ")
}

syncedLyrics.append("<${line.endtime.toLrcTimestamp()}>\n")
}
}
"Line" -> {
for (line in lines) {
syncedLyrics.append("[${line.timestamp.toLrcTimestamp()}]${line.text[0].text}\n")
}
}
else -> return null
}

return syncedLyrics.toString().dropLast(1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ data class SongInfo(
var albumCoverLink: String? = null,
var lrcLibID: Int? = null, // LRCLib-only
var neteaseID: Long? = null, // Netease-only
var appleID: Long? = null, // Apple-only
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package pl.lambada.songsync.domain.model.lyrics_providers.others

import kotlinx.serialization.Serializable

@Serializable
data class AppleSearchResponse(
val id: Long,
val songName: String,
val artistName: String,
val albumName: String,
val artwork: String,
val releaseDate: String,
val duration: Int,
val isrc: String,
val url: String,
val contentRating: String?,
val albumId: String
)

@Serializable
data class AppleLyricsResponse(
val type: String,
val content: List<AppleLyrics>?
)

@Serializable
data class AppleLyrics(
val text: List<AppleLyricsLineDetails>,
val timestamp: Int,
val endtime: Int
)

@Serializable
data class AppleLyricsLineDetails(
val text: String,
val timestamp: Int?
)
Original file line number Diff line number Diff line change
Expand Up @@ -477,5 +477,6 @@ enum class UpdateState {
enum class Providers(val displayName: String) {
SPOTIFY("Spotify (via SpotifyLyricsAPI)"),
LRCLIB("LRCLib"),
NETEASE("Netease")
NETEASE("Netease"),
APPLE("Apple Music")
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ fun SharedTransitionScope.SearchScreen(
verticalAlignment = Alignment.CenterVertically
) {
OutlinedButton(
enabled = viewModel.provider != Providers.LRCLIB,
enabled = viewModel.provider != Providers.LRCLIB && viewModel.provider != Providers.APPLE,
onClick = {
offset += 1
queryResult = SongInfo(
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/pl/lambada/songsync/util/ext/IntExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package pl.lambada.songsync.util.ext

fun Int.toLrcTimestamp(): String {
val minutes = this / 60000
val seconds = (this % 60000) / 1000
val milliseconds = this % 1000

val leadingZeros: Array<String> = arrayOf(
if (minutes < 10) "0" else "",
if (seconds < 10) "0" else "",
if (milliseconds < 10) "00" else if (milliseconds < 100) "0" else ""
)

return "${leadingZeros[0]}$minutes:${leadingZeros[1]}$seconds.${leadingZeros[2]}$milliseconds"
}

0 comments on commit 8bb37b1

Please sign in to comment.