Skip to content

Commit 742909e

Browse files
authored
Romanized MusixMatch Lyrics (#229)
1 parent 5a242bf commit 742909e

4 files changed

Lines changed: 161 additions & 20 deletions

File tree

Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsOptions.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ import Foundation
22

33
struct LyricsOptions: Codable, Equatable {
44
var geniusRomanizations: Bool
5+
var musixmatchRomanizations: Bool
56
}

Sources/EeveeSpotify/Lyrics/Repositories/MusixmatchLyricsRepository.swift

Lines changed: 148 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ struct MusixmatchLyricsRepository: LyricsRepository {
55
private let apiUrl = "https://apic.musixmatch.com"
66

77
private func perform(
8-
_ path: String,
8+
_ path: String,
99
query: [String:Any] = [:]
1010
) throws -> Data {
1111

@@ -49,7 +49,7 @@ struct MusixmatchLyricsRepository: LyricsRepository {
4949
func getLyrics(_ query: LyricsSearchQuery, options: LyricsOptions) throws -> LyricsDto {
5050

5151
let data = try perform(
52-
"/ws/1.1/macro.subtitles.get",
52+
"/ws/1.1/macro.subtitles.get",
5353
query: [
5454
"track_spotify_id": query.spotifyTrackId,
5555
"subtitle_format": "mxm",
@@ -59,7 +59,7 @@ struct MusixmatchLyricsRepository: LyricsRepository {
5959

6060
// 😭😭😭
6161

62-
guard
62+
guard
6363
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
6464
let message = json["message"] as? [String: Any],
6565
let body = message["body"] as? [String: Any],
@@ -68,7 +68,7 @@ struct MusixmatchLyricsRepository: LyricsRepository {
6868
throw LyricsError.DecodingError
6969
}
7070

71-
if let header = message["header"] as? [String: Any],
71+
if let header = message["header"] as? [String: Any],
7272
header["status_code"] as? Int == 401 {
7373
throw LyricsError.InvalidMusixmatchToken
7474
}
@@ -100,15 +100,83 @@ struct MusixmatchLyricsRepository: LyricsRepository {
100100
throw LyricsError.DecodingError
101101
}
102102

103-
return LyricsDto(
104-
lines: subtitles.map { subtitle in
105-
LyricsLineDto(
106-
content: subtitle.text.lyricsNoteIfEmpty,
107-
offsetMs: Int(subtitle.time.total * 1000)
103+
if !UserDefaults.lyricsOptions.musixmatchRomanizations {
104+
return LyricsDto(
105+
lines: subtitles.map { subtitle in
106+
LyricsLineDto(
107+
content: subtitle.text.lyricsNoteIfEmpty,
108+
offsetMs: Int(subtitle.time.total * 1000)
109+
)
110+
},
111+
timeSynced: true
112+
)
113+
} else {
114+
do {
115+
let subtitleLang = subtitle["subtitle_language"] as? String ?? ""
116+
let romajiLang = "r\(subtitleLang.prefix(1))"
117+
118+
let romajiData = try perform(
119+
"/ws/1.1/crowd.track.translations.get",
120+
query: [
121+
"track_spotify_id": query.spotifyTrackId,
122+
"selected_language": romajiLang
123+
]
108124
)
109-
},
110-
timeSynced: true
111-
)
125+
126+
guard
127+
let romajiJson = try? JSONSerialization.jsonObject(with: romajiData, options: []) as? [String: Any],
128+
let romajiMessage = romajiJson["message"] as? [String: Any],
129+
let romajiBody = romajiMessage["body"] as? [String: Any],
130+
let translationsList = romajiBody["translations_list"] as? [[String: Any]]
131+
else {
132+
throw LyricsError.DecodingError
133+
}
134+
135+
var translationDict: [String: String] = [:]
136+
137+
for translation in translationsList {
138+
if let translationInfo = translation["translation"] as? [String: Any],
139+
let translationMatch = translationInfo["subtitle_matched_line"] as? String,
140+
let translationString = translationInfo["description"] as? String {
141+
if translationMatch != translationString {
142+
translationDict[translationMatch] = translationString
143+
}
144+
145+
}
146+
}
147+
148+
let modifiedSubtitles = subtitles.map { subtitle in
149+
var modifiedText = subtitle.text
150+
for (translationMatch, translationString) in translationDict {
151+
modifiedText = modifiedText.replacingOccurrences(of: translationMatch, with: translationString)
152+
}
153+
return MusixmatchSubtitle(
154+
text: modifiedText,
155+
time: subtitle.time
156+
)
157+
}
158+
159+
return LyricsDto(
160+
lines: modifiedSubtitles.map { subtitle in
161+
LyricsLineDto(
162+
content: subtitle.text.lyricsNoteIfEmpty,
163+
offsetMs: Int(subtitle.time.total * 1000)
164+
)
165+
},
166+
timeSynced: true
167+
)
168+
} catch {
169+
return LyricsDto(
170+
lines: subtitles.map { subtitle in
171+
LyricsLineDto(
172+
content: subtitle.text.lyricsNoteIfEmpty,
173+
offsetMs: Int(subtitle.time.total * 1000)
174+
)
175+
},
176+
timeSynced: true
177+
)
178+
}
179+
}
112180
}
113181
}
114182
}
@@ -130,13 +198,74 @@ struct MusixmatchLyricsRepository: LyricsRepository {
130198
throw LyricsError.MusixmatchRestricted
131199
}
132200

133-
return LyricsDto(
134-
lines: plainLyrics
135-
.components(separatedBy: "\n")
136-
.dropLast()
137-
.map { LyricsLineDto(content: $0.lyricsNoteIfEmpty) },
138-
timeSynced: false
139-
)
201+
if (!UserDefaults.lyricsOptions.musixmatchRomanizations) {
202+
return LyricsDto(
203+
lines: plainLyrics
204+
.components(separatedBy: "\n")
205+
.dropLast()
206+
.map { LyricsLineDto(content: $0.lyricsNoteIfEmpty) },
207+
timeSynced: false
208+
)
209+
} else {
210+
do {
211+
let subtitleLang = lyrics["lyrics_language"] as? String ?? ""
212+
let romajiLang = "r\(subtitleLang.prefix(1))"
213+
214+
let romajiData = try perform(
215+
"/ws/1.1/crowd.track.translations.get",
216+
query: [
217+
"track_spotify_id": query.spotifyTrackId,
218+
"selected_language": romajiLang
219+
]
220+
)
221+
222+
guard
223+
let romajiJson = try? JSONSerialization.jsonObject(with: romajiData, options: []) as? [String: Any],
224+
let romajiMessage = romajiJson["message"] as? [String: Any],
225+
let romajiBody = romajiMessage["body"] as? [String: Any],
226+
let translationsList = romajiBody["translations_list"] as? [[String: Any]]
227+
else {
228+
throw LyricsError.DecodingError
229+
}
230+
231+
var translationDict: [String: String] = [:]
232+
233+
for translation in translationsList {
234+
if let translationInfo = translation["translation"] as? [String: Any],
235+
let translationMatch = translationInfo["matched_line"] as? String,
236+
let translationString = translationInfo["description"] as? String {
237+
if translationMatch != translationString {
238+
translationDict[translationMatch] = translationString
239+
}
240+
241+
}
242+
}
243+
244+
let modifiedLyrics = plainLyrics
245+
.components(separatedBy: "\n")
246+
.dropLast()
247+
.map { line in
248+
var modifiedLine = line
249+
for (translationMatch, translationString) in translationDict {
250+
modifiedLine = modifiedLine.replacingOccurrences(of: translationMatch, with: translationString)
251+
}
252+
return modifiedLine
253+
}
254+
255+
return LyricsDto(
256+
lines: modifiedLyrics.map { LyricsLineDto(content: $0.lyricsNoteIfEmpty) },
257+
timeSynced: false
258+
)
259+
} catch {
260+
return LyricsDto(
261+
lines: plainLyrics
262+
.components(separatedBy: "\n")
263+
.dropLast()
264+
.map { LyricsLineDto(content: $0.lyricsNoteIfEmpty) },
265+
timeSynced: false
266+
)
267+
}
268+
}
140269
}
141270
}
142271

Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ extension UserDefaults {
5151
return try! JSONDecoder().decode(LyricsOptions.self, from: data)
5252
}
5353

54-
return LyricsOptions(geniusRomanizations: false)
54+
return LyricsOptions(geniusRomanizations: false, musixmatchRomanizations: false)
5555
}
5656
set (lyricsOptions) {
5757
defaults.set(try! JSONEncoder().encode(lyricsOptions), forKey: lyricsOptionsKey)

Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsOptionsSection.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,16 @@ extension EeveeSettingsView {
1313
UserDefaults.lyricsOptions = lyricsOptions
1414
}
1515
}
16+
if lyricsSource == .musixmatch {
17+
Section {
18+
Toggle(
19+
"Romanized MusixMatch Lyrics",
20+
isOn: $lyricsOptions.musixmatchRomanizations
21+
)
22+
}
23+
.onChange(of: lyricsOptions) { lyricsOptions in
24+
UserDefaults.lyricsOptions = lyricsOptions
25+
}
26+
}
1627
}
1728
}

0 commit comments

Comments
 (0)