mirror of
https://github.com/whoeevee/EeveeSpotifyReborn.git
synced 2026-01-09 00:23:20 +01:00
yodaluca23 has confirmed andrea Filice ban prs forever
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
struct LyricsSearchQuery {
|
||||
struct LyricsSearchQuery: Hashable {
|
||||
var title: String
|
||||
var primaryArtist: String
|
||||
var spotifyTrackId: String
|
||||
|
||||
@@ -3,17 +3,35 @@ import UIKit
|
||||
|
||||
class MusixmatchLyricsRepository: LyricsRepository {
|
||||
private let apiUrl = "https://apic.musixmatch.com"
|
||||
|
||||
|
||||
var selectedLanguage: String
|
||||
|
||||
|
||||
static let shared = MusixmatchLyricsRepository(
|
||||
language: UserDefaults.lyricsOptions.musixmatchLanguage
|
||||
)
|
||||
|
||||
|
||||
private init(language: String) {
|
||||
selectedLanguage = language
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
private class CachedLyrics {
|
||||
let dto: LyricsDto
|
||||
|
||||
init(dto: LyricsDto) {
|
||||
self.dto = dto
|
||||
}
|
||||
}
|
||||
|
||||
private let lyricsCache = NSCache<NSString, CachedLyrics>()
|
||||
|
||||
private func getCacheKey(for query: LyricsSearchQuery) -> String {
|
||||
return "\(query.hashValue)_\(selectedLanguage)"
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
private func perform(
|
||||
_ path: String,
|
||||
query: [String: Any] = [:]
|
||||
@@ -26,7 +44,7 @@ class MusixmatchLyricsRepository: LyricsRepository {
|
||||
|
||||
let queryString = finalQuery.queryString
|
||||
stringUrl += "?\(queryString)"
|
||||
|
||||
|
||||
let request = URLRequest(url: URL(string: stringUrl)!)
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
@@ -48,9 +66,9 @@ class MusixmatchLyricsRepository: LyricsRepository {
|
||||
|
||||
return data!
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
|
||||
|
||||
private func getMacroCalls(_ data: Data) throws -> [String: Any] {
|
||||
guard
|
||||
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
||||
@@ -62,15 +80,16 @@ class MusixmatchLyricsRepository: LyricsRepository {
|
||||
}
|
||||
|
||||
if let header = message["header"] as? [String: Any],
|
||||
header["status_code"] as? Int == 401 {
|
||||
header["status_code"] as? Int == 401
|
||||
{
|
||||
throw LyricsError.invalidMusixmatchToken
|
||||
}
|
||||
|
||||
|
||||
return macroCalls
|
||||
}
|
||||
|
||||
|
||||
private func getFirstSubtitle(_ subtitlesMessage: [String: Any]) throws -> [String: Any] {
|
||||
guard
|
||||
guard
|
||||
let subtitlesBody = subtitlesMessage["body"] as? [String: Any],
|
||||
let subtitleList = subtitlesBody["subtitle_list"] as? [[String: Any]],
|
||||
let firstSubtitle = subtitleList.first,
|
||||
@@ -78,25 +97,27 @@ class MusixmatchLyricsRepository: LyricsRepository {
|
||||
else {
|
||||
throw LyricsError.decodingError
|
||||
}
|
||||
|
||||
|
||||
if let restricted = subtitle["restricted"] as? Bool, restricted {
|
||||
throw LyricsError.musixmatchRestricted
|
||||
}
|
||||
|
||||
|
||||
return subtitle
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
|
||||
private func getTranslations(_ spotifyTrackId: String, selectedLanguage: String) throws -> [String: String] {
|
||||
|
||||
private func getTranslations(_ spotifyTrackId: String, selectedLanguage: String) throws
|
||||
-> [String: String]
|
||||
{
|
||||
let data = try perform(
|
||||
"/ws/1.1/crowd.track.translations.get",
|
||||
query: [
|
||||
"track_spotify_id": spotifyTrackId,
|
||||
"selected_language": selectedLanguage
|
||||
"selected_language": selectedLanguage,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
guard
|
||||
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
||||
let message = json["message"] as? [String: Any],
|
||||
@@ -109,27 +130,34 @@ class MusixmatchLyricsRepository: LyricsRepository {
|
||||
let translations = translationsList.compactMap {
|
||||
$0["translation"] as? [String: Any]
|
||||
}
|
||||
|
||||
|
||||
return translations.reduce(into: [:]) { dictionary, translation in
|
||||
dictionary[translation["subtitle_matched_line"] as! String] = translation["description"] as? String
|
||||
dictionary[translation["subtitle_matched_line"] as! String] =
|
||||
translation["description"] as? String
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
|
||||
|
||||
func getLyrics(_ query: LyricsSearchQuery, options: LyricsOptions) throws -> LyricsDto {
|
||||
let cacheKey = getCacheKey(for: query)
|
||||
|
||||
if let cached = lyricsCache.object(forKey: cacheKey as NSString) {
|
||||
return cached.dto
|
||||
}
|
||||
|
||||
var musixmatchQuery = [
|
||||
"track_spotify_id": query.spotifyTrackId,
|
||||
"subtitle_format": "mxm",
|
||||
"q_track": query.title,
|
||||
"q_artist": query.primaryArtist
|
||||
"q_artist": query.primaryArtist,
|
||||
]
|
||||
|
||||
|
||||
if !selectedLanguage.isEmpty {
|
||||
musixmatchQuery["selected_language"] = selectedLanguage
|
||||
musixmatchQuery["part"] = "subtitle_translated"
|
||||
}
|
||||
|
||||
|
||||
let data = try perform(
|
||||
"/ws/1.1/macro.subtitles.get",
|
||||
query: musixmatchQuery
|
||||
@@ -139,7 +167,7 @@ class MusixmatchLyricsRepository: LyricsRepository {
|
||||
|
||||
var romanized = false
|
||||
var translation: LyricsTranslationDto? = nil
|
||||
|
||||
|
||||
let macroCalls = try getMacroCalls(data)
|
||||
|
||||
if let trackSubtitlesGet = macroCalls["track.subtitles.get"] as? [String: Any],
|
||||
@@ -149,55 +177,55 @@ class MusixmatchLyricsRepository: LyricsRepository {
|
||||
let subtitleBody = subtitle["subtitle_body"] as? String,
|
||||
let subtitles = try? JSONDecoder().decode(
|
||||
[MusixmatchSubtitle].self, from: subtitleBody.data(using: .utf8)!
|
||||
) {
|
||||
|
||||
)
|
||||
{
|
||||
|
||||
let romanizationLanguage = "r\(subtitleLanguage.prefix(1))"
|
||||
|
||||
|
||||
var lyricsLines = subtitles.dropLast().map { subtitle in
|
||||
LyricsLineDto(
|
||||
content: subtitle.text.lyricsNoteIfEmpty,
|
||||
offsetMs: Int(subtitle.time.total * 1000)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
lyricsLines.append(
|
||||
LyricsLineDto(
|
||||
content: "",
|
||||
offsetMs: Int(subtitles.last!.time.total * 1000)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if selectedLanguage != subtitleLanguage,
|
||||
let subtitleTranslated = subtitle["subtitle_translated"] as? [String: Any],
|
||||
let subtitleTranslatedBody = subtitleTranslated["subtitle_body"] as? String,
|
||||
let subtitlesTranslated = try? JSONDecoder().decode(
|
||||
let subtitleTranslated = subtitle["subtitle_translated"] as? [String: Any],
|
||||
let subtitleTranslatedBody = subtitleTranslated["subtitle_body"] as? String,
|
||||
let subtitlesTranslated = try? JSONDecoder().decode(
|
||||
[MusixmatchSubtitle].self, from: subtitleTranslatedBody.data(using: .utf8)!
|
||||
)
|
||||
)
|
||||
{
|
||||
if selectedLanguage == romanizationLanguage {
|
||||
romanized = true
|
||||
|
||||
|
||||
for (index, subtitleTranslated) in subtitlesTranslated.enumerated() {
|
||||
if !subtitleTranslated.text.isEmpty {
|
||||
lyricsLines[index].content = subtitleTranslated.text
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
translation = LyricsTranslationDto(
|
||||
languageCode: selectedLanguage,
|
||||
lines: subtitlesTranslated.map { $0.text }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if options.romanization && selectedLanguage != romanizationLanguage {
|
||||
if let translations = try? getTranslations(
|
||||
query.spotifyTrackId,
|
||||
selectedLanguage: romanizationLanguage
|
||||
) {
|
||||
romanized = true
|
||||
|
||||
|
||||
for (original, translation) in translations {
|
||||
for i in 0..<lyricsLines.count {
|
||||
if lyricsLines[i].content == original {
|
||||
@@ -207,50 +235,59 @@ class MusixmatchLyricsRepository: LyricsRepository {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var romanization = LyricsRomanizationStatus.original
|
||||
|
||||
|
||||
if romanized {
|
||||
romanization = .romanized
|
||||
}
|
||||
else if subtitleLanguage.isCanBeRomanizedLanguage {
|
||||
} else if subtitleLanguage.isCanBeRomanizedLanguage {
|
||||
romanization = .canBeRomanized
|
||||
}
|
||||
|
||||
return LyricsDto(
|
||||
|
||||
let lyricsDto = LyricsDto(
|
||||
lines: lyricsLines,
|
||||
timeSynced: true,
|
||||
romanization: romanization,
|
||||
translation: translation
|
||||
)
|
||||
|
||||
lyricsCache.setObject(CachedLyrics(dto: lyricsDto), forKey: cacheKey as NSString)
|
||||
return lyricsDto
|
||||
}
|
||||
|
||||
|
||||
if let trackLyricsGet = macroCalls["track.lyrics.get"] as? [String: Any],
|
||||
let lyricsMessage = trackLyricsGet["message"] as? [String: Any],
|
||||
let lyricsHeader = lyricsMessage["header"] as? [String: Any],
|
||||
let lyricsStatusCode = lyricsHeader["status_code"] as? Int {
|
||||
|
||||
let lyricsMessage = trackLyricsGet["message"] as? [String: Any],
|
||||
let lyricsHeader = lyricsMessage["header"] as? [String: Any],
|
||||
let lyricsStatusCode = lyricsHeader["status_code"] as? Int
|
||||
{
|
||||
|
||||
if lyricsStatusCode == 404 {
|
||||
throw LyricsError.noSuchSong
|
||||
}
|
||||
|
||||
|
||||
if let lyricsBody = lyricsMessage["body"] as? [String: Any],
|
||||
let lyrics = lyricsBody["lyrics"] as? [String: Any],
|
||||
let lyricsLanguage = lyrics["lyrics_language"] as? String,
|
||||
let plainLyrics = lyrics["lyrics_body"] as? String {
|
||||
|
||||
let lyrics = lyricsBody["lyrics"] as? [String: Any],
|
||||
let lyricsLanguage = lyrics["lyrics_language"] as? String,
|
||||
let plainLyrics = lyrics["lyrics_body"] as? String
|
||||
{
|
||||
|
||||
if let restricted = lyrics["restricted"] as? Bool, restricted {
|
||||
throw LyricsError.musixmatchRestricted
|
||||
}
|
||||
|
||||
return LyricsDto(
|
||||
lines: plainLyrics
|
||||
|
||||
let lyricsDto = LyricsDto(
|
||||
lines:
|
||||
plainLyrics
|
||||
.components(separatedBy: "\n")
|
||||
.dropLast()
|
||||
.map { LyricsLineDto(content: $0.lyricsNoteIfEmpty) },
|
||||
timeSynced: false,
|
||||
romanization: lyricsLanguage.isCanBeRomanizedLanguage ? .canBeRomanized : .original
|
||||
romanization: lyricsLanguage.isCanBeRomanizedLanguage
|
||||
? .canBeRomanized : .original
|
||||
)
|
||||
|
||||
lyricsCache.setObject(CachedLyrics(dto: lyricsDto), forKey: cacheKey as NSString)
|
||||
return lyricsDto
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user