From 5a242bf84953b1fdc2cf1f3c03b63436e5c7fe4c Mon Sep 17 00:00:00 2001 From: eevee Date: Fri, 5 Jul 2024 10:11:54 +0300 Subject: [PATCH] romanized genius lyrics --- .../EeveeSpotify/Lyrics/CustomLyrics.x.swift | 5 +- .../Models/Genius/GeniusHitResult.swift | 1 + .../Models/Settings/LyricsOptions.swift | 5 ++ .../Lyrics/Protocols/LyricsRepository.swift | 2 +- .../Repositories/GeniusLyricsRepository.swift | 71 ++++++++++--------- .../Repositories/LrclibLyricsRepository.swift | 2 +- .../MusixmatchLyricsRepository.swift | 2 +- .../Extensions/UserDefaults+Extension.swift | 14 ++-- ...eveeSettingsView+LyricsColorsSection.swift | 4 +- .../Settings/Views/EeveeSettingsView.swift | 9 ++- ...sViewController+LyricsOptionsSection.swift | 17 +++++ ...sViewController+LyricsSourceSection.swift} | 19 ++--- 12 files changed, 88 insertions(+), 63 deletions(-) create mode 100644 Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsOptions.swift create mode 100644 Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsOptionsSection.swift rename Sources/EeveeSpotify/Settings/Views/{EeveeSettingsViewController+LyricsSections.swift => EeveeSettingsViewController+LyricsSourceSection.swift} (87%) diff --git a/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift b/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift index af4e84b..0107637 100644 --- a/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift +++ b/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift @@ -127,6 +127,7 @@ func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data { spotifyTrackId: track.URI().spt_trackIdentifier() ) + let options = UserDefaults.lyricsOptions var source = UserDefaults.lyricsSource var repository: LyricsRepository = switch source { @@ -140,7 +141,7 @@ func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data { // do { - lyricsDto = try repository.getLyrics(searchQuery) + lyricsDto = try repository.getLyrics(searchQuery, options: options) lastLyricsError = nil } @@ -189,7 +190,7 @@ func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data { source = .genius repository = GeniusLyricsRepository() - lyricsDto = try repository.getLyrics(searchQuery) + lyricsDto = try repository.getLyrics(searchQuery, options: options) } let lyrics = Lyrics.with { diff --git a/Sources/EeveeSpotify/Lyrics/Models/Genius/GeniusHitResult.swift b/Sources/EeveeSpotify/Lyrics/Models/Genius/GeniusHitResult.swift index 763ab3e..083efb5 100644 --- a/Sources/EeveeSpotify/Lyrics/Models/Genius/GeniusHitResult.swift +++ b/Sources/EeveeSpotify/Lyrics/Models/Genius/GeniusHitResult.swift @@ -3,4 +3,5 @@ import Foundation struct GeniusHitResult: Decodable { var id: Int var title: String + var artistNames: String } diff --git a/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsOptions.swift b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsOptions.swift new file mode 100644 index 0000000..7611527 --- /dev/null +++ b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsOptions.swift @@ -0,0 +1,5 @@ +import Foundation + +struct LyricsOptions: Codable, Equatable { + var geniusRomanizations: Bool +} diff --git a/Sources/EeveeSpotify/Lyrics/Protocols/LyricsRepository.swift b/Sources/EeveeSpotify/Lyrics/Protocols/LyricsRepository.swift index 9d4184e..700b0fe 100644 --- a/Sources/EeveeSpotify/Lyrics/Protocols/LyricsRepository.swift +++ b/Sources/EeveeSpotify/Lyrics/Protocols/LyricsRepository.swift @@ -1,5 +1,5 @@ import Foundation protocol LyricsRepository { - func getLyrics(_ query: LyricsSearchQuery) throws -> LyricsDto + func getLyrics(_ query: LyricsSearchQuery, options: LyricsOptions) throws -> LyricsDto } diff --git a/Sources/EeveeSpotify/Lyrics/Repositories/GeniusLyricsRepository.swift b/Sources/EeveeSpotify/Lyrics/Repositories/GeniusLyricsRepository.swift index 62b041e..86127d7 100644 --- a/Sources/EeveeSpotify/Lyrics/Repositories/GeniusLyricsRepository.swift +++ b/Sources/EeveeSpotify/Lyrics/Repositories/GeniusLyricsRepository.swift @@ -1,6 +1,8 @@ import Foundation struct GeniusLyricsRepository: LyricsRepository { + + private let jsonDecoder: JSONDecoder private let apiUrl = "https://api.genius.com" private let session: URLSession @@ -13,6 +15,9 @@ struct GeniusLyricsRepository: LyricsRepository { ] session = URLSession(configuration: configuration) + + jsonDecoder = JSONDecoder() + jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase } private func perform( @@ -48,7 +53,7 @@ struct GeniusLyricsRepository: LyricsRepository { throw error } - let rootResponse = try JSONDecoder().decode(GeniusRootResponse.self, from: data!) + let rootResponse = try jsonDecoder.decode(GeniusRootResponse.self, from: data!) return rootResponse.response } @@ -79,12 +84,28 @@ struct GeniusLyricsRepository: LyricsRepository { // - private func mostRelevantHitResult(hits: [GeniusHit], strippedTitle: String) -> GeniusHitResult? { - return ( - hits.first( - where: { $0.result.title.containsInsensitive(strippedTitle) } - ) ?? hits.first - )?.result + private func mostRelevantHitResult( + hits: [GeniusHit], + strippedTitle: String, + romanized: Bool + ) -> GeniusHitResult { + let results = hits.map { $0.result } + + let matchingByTitle = results.filter( + { $0.title.containsInsensitive(strippedTitle) } + ) + + if matchingByTitle.isEmpty { + return results.first! + } + + if romanized, let romanizedSong = matchingByTitle.first( + where: { $0.artistNames == "Genius Romanizations" } + ) { + return romanizedSong + } + + return matchingByTitle.first! } private func mapLyricsLines(_ rawLines: [String]) -> [String] { @@ -102,36 +123,20 @@ struct GeniusLyricsRepository: LyricsRepository { return lines } - func getLyrics(_ query: LyricsSearchQuery) throws -> LyricsDto { + func getLyrics(_ query: LyricsSearchQuery, options: LyricsOptions) throws -> LyricsDto { let strippedTitle = query.title.strippedTrackTitle - var queries = [ - "\(strippedTitle) \(query.primaryArtist)" - ] - - if UserDefaults.romanizedLyrics { - queries = [ - "\(strippedTitle) \(query.primaryArtist) (Romanized)", - "\(strippedTitle) \(query.primaryArtist)" - ] - } - var hits: [GeniusHit] = [] + let hits = try searchSong("\(strippedTitle) \(query.primaryArtist)") - for searchQuery in queries { - do { - hits = try searchSong(searchQuery) - if !hits.isEmpty { - break - } - } catch { - // Continue to the next query if the current one fails - } - } - - guard !hits.isEmpty, - let song = mostRelevantHitResult(hits: hits, strippedTitle: strippedTitle) else { + guard !hits.isEmpty else { throw LyricsError.NoSuchSong } - + + let song = mostRelevantHitResult( + hits: hits, + strippedTitle: strippedTitle, + romanized: options.geniusRomanizations + ) + let songInfo = try getSongInfo(song.id) let plainLines = songInfo.lyrics.plain.components(separatedBy: "\n") diff --git a/Sources/EeveeSpotify/Lyrics/Repositories/LrclibLyricsRepository.swift b/Sources/EeveeSpotify/Lyrics/Repositories/LrclibLyricsRepository.swift index 5a6813c..571eb21 100644 --- a/Sources/EeveeSpotify/Lyrics/Repositories/LrclibLyricsRepository.swift +++ b/Sources/EeveeSpotify/Lyrics/Repositories/LrclibLyricsRepository.swift @@ -92,7 +92,7 @@ struct LrcLibLyricsRepository: LyricsRepository { } } - func getLyrics(_ query: LyricsSearchQuery) throws -> LyricsDto { + func getLyrics(_ query: LyricsSearchQuery, options: LyricsOptions) throws -> LyricsDto { let strippedTitle = query.title.strippedTrackTitle let songs = try searchSong("\(strippedTitle) \(query.primaryArtist)") diff --git a/Sources/EeveeSpotify/Lyrics/Repositories/MusixmatchLyricsRepository.swift b/Sources/EeveeSpotify/Lyrics/Repositories/MusixmatchLyricsRepository.swift index f64075d..382ce5c 100644 --- a/Sources/EeveeSpotify/Lyrics/Repositories/MusixmatchLyricsRepository.swift +++ b/Sources/EeveeSpotify/Lyrics/Repositories/MusixmatchLyricsRepository.swift @@ -46,7 +46,7 @@ struct MusixmatchLyricsRepository: LyricsRepository { return data! } - func getLyrics(_ query: LyricsSearchQuery) throws -> LyricsDto { + func getLyrics(_ query: LyricsSearchQuery, options: LyricsOptions) throws -> LyricsDto { let data = try perform( "/ws/1.1/macro.subtitles.get", diff --git a/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift b/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift index a1866d9..73097dd 100644 --- a/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift +++ b/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift @@ -7,12 +7,12 @@ extension UserDefaults { private static let lyricsSourceKey = "lyricsSource" private static let musixmatchTokenKey = "musixmatchToken" private static let geniusFallbackKey = "geniusFallback" - private static let romanizedLyricsKey = "romanizedLyrics" private static let fallbackReasonsKey = "fallbackReasons" private static let darkPopUpsKey = "darkPopUps" private static let patchTypeKey = "patchType" private static let overwriteConfigurationKey = "overwriteConfiguration" private static let lyricsColorsKey = "lyricsColors" + private static let lyricsOptionsKey = "lyricsOptions" static var lyricsSource: LyricsSource { get { @@ -45,12 +45,16 @@ extension UserDefaults { } } - static var romanizedLyrics: Bool { + static var lyricsOptions: LyricsOptions { get { - defaults.object(forKey: romanizedLyricsKey) as? Bool ?? false + if let data = defaults.object(forKey: lyricsOptionsKey) as? Data { + return try! JSONDecoder().decode(LyricsOptions.self, from: data) + } + + return LyricsOptions(geniusRomanizations: false) } - set (romanized) { - defaults.set(romanized, forKey: romanizedLyricsKey) + set (lyricsOptions) { + defaults.set(try! JSONEncoder().encode(lyricsOptions), forKey: lyricsOptionsKey) } } diff --git a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView+LyricsColorsSection.swift b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView+LyricsColorsSection.swift index e0adace..e679b49 100644 --- a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView+LyricsColorsSection.swift +++ b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView+LyricsColorsSection.swift @@ -45,8 +45,8 @@ You can set a static color or a normalization factor based on the extracted trac } } - .onChange(of: lyricsColors) { newLyricsColors in - UserDefaults.lyricsColors = newLyricsColors + .onChange(of: lyricsColors) { lyricsColors in + UserDefaults.lyricsColors = lyricsColors } } } diff --git a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift index 777294a..b5f3750 100644 --- a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift +++ b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift @@ -8,7 +8,9 @@ struct EeveeSettingsView: View { @State var overwriteConfiguration = UserDefaults.overwriteConfiguration @State var lyricsSource = UserDefaults.lyricsSource + @State var geniusFallback = UserDefaults.geniusFallback @State var lyricsColors = UserDefaults.lyricsColors + @State var lyricsOptions = UserDefaults.lyricsOptions @State var latestVersion = "" @@ -19,7 +21,8 @@ struct EeveeSettingsView: View { PremiumSections() - LyricsSections() + LyricsSourceSection() + LyricsOptionsSection() LyricsColorsSection() @@ -51,9 +54,9 @@ struct EeveeSettingsView: View { } .listStyle(GroupedListStyle()) - .ignoresSafeArea(.keyboard) - + .animation(.default, value: lyricsSource) + .animation(.default, value: geniusFallback) .animation(.default, value: patchType) .animation(.default, value: lyricsColors) .animation(.default, value: latestVersion) diff --git a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsOptionsSection.swift b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsOptionsSection.swift new file mode 100644 index 0000000..5d9731b --- /dev/null +++ b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsOptionsSection.swift @@ -0,0 +1,17 @@ +import SwiftUI + +extension EeveeSettingsView { + @ViewBuilder func LyricsOptionsSection() -> some View { + if lyricsSource == .genius || geniusFallback { + Section { + Toggle( + "Romanized Genius Lyrics", + isOn: $lyricsOptions.geniusRomanizations + ) + } + .onChange(of: lyricsOptions) { lyricsOptions in + UserDefaults.lyricsOptions = lyricsOptions + } + } + } +} diff --git a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSourceSection.swift similarity index 87% rename from Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift rename to Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSourceSection.swift index 3db2dc9..5be110d 100644 --- a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift +++ b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSourceSection.swift @@ -47,7 +47,7 @@ extension EeveeSettingsView { } - @ViewBuilder func LyricsSections() -> some View { + @ViewBuilder func LyricsSourceSection() -> some View { Section(footer: Text(""" You can select the lyrics source you prefer. @@ -105,16 +105,8 @@ If the tweak is unable to find a song or process the lyrics, you'll see a "Could UserDefaults.lyricsSource = newSource } - if lyricsSource == .genius { - Section { - Toggle( - "Use Romanized (Romaji) Lyrics when Available", - isOn: Binding( - get: { UserDefaults.romanizedLyrics }, - set: { UserDefaults.romanizedLyrics = $0 } - ) - ) - } + .onChange(of: geniusFallback) { geniusFallback in + UserDefaults.geniusFallback = geniusFallback } if lyricsSource != .genius { @@ -123,10 +115,7 @@ If the tweak is unable to find a song or process the lyrics, you'll see a "Could ) { Toggle( "Genius Fallback", - isOn: Binding( - get: { UserDefaults.geniusFallback }, - set: { UserDefaults.geniusFallback = $0 } - ) + isOn: $geniusFallback ) Toggle(