diff --git a/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift b/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift index e60500f..360d10f 100644 --- a/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift +++ b/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift @@ -213,7 +213,8 @@ func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data { lyricsDto = try repository.getLyrics(searchQuery, options: options) } - lastLyricsWasRomanized = lyricsDto.romanized + lastLyricsWasRomanized = lyricsDto.romanization == .romanized + || (lyricsDto.romanization == .canBeRomanized && UserDefaults.lyricsOptions.romanization) lastLyricsAreEmpty = lyricsDto.lines.isEmpty let lyrics = Lyrics.with { diff --git a/Sources/EeveeSpotify/Lyrics/Models/LyricsDto.swift b/Sources/EeveeSpotify/Lyrics/Models/LyricsDto.swift index f0276b0..861e9cc 100644 --- a/Sources/EeveeSpotify/Lyrics/Models/LyricsDto.swift +++ b/Sources/EeveeSpotify/Lyrics/Models/LyricsDto.swift @@ -3,7 +3,7 @@ import Foundation struct LyricsDto { var lines: [LyricsLineDto] var timeSynced: Bool - var romanized: Bool = false + var romanization: LyricsRomanizationStatus var translation: LyricsTranslationDto? func toLyricsData(source: String) -> LyricsData { @@ -13,8 +13,10 @@ struct LyricsDto { $0.providedBy = "\(source) (EeveeSpotify)" } - lyricsData.lines = lines.isEmpty - ? [ + let shouldRomanize = UserDefaults.lyricsOptions.romanization + + if lines.isEmpty { + lyricsData.lines = [ LyricsLine.with { $0.content = "This song is instrumental." }, @@ -25,12 +27,17 @@ struct LyricsDto { $0.content = "" } ] - : lines.map { line in + } + else { + lyricsData.lines = lines.map { line in LyricsLine.with { - $0.content = line.content + $0.content = (shouldRomanize && romanization == .canBeRomanized) + ? line.content.applyingTransform(.toLatin, reverse: false)! + : line.content $0.offsetMs = Int32(line.offsetMs ?? 0) } } + } if let translation = translation { lyricsData.translation = LyricsTranslation.with { diff --git a/Sources/EeveeSpotify/Lyrics/Models/LyricsRomanizationStatus.swift b/Sources/EeveeSpotify/Lyrics/Models/LyricsRomanizationStatus.swift new file mode 100644 index 0000000..e3912d1 --- /dev/null +++ b/Sources/EeveeSpotify/Lyrics/Models/LyricsRomanizationStatus.swift @@ -0,0 +1,7 @@ +import Foundation + +enum LyricsRomanizationStatus { + case romanized + case canBeRomanized + case original +} diff --git a/Sources/EeveeSpotify/Lyrics/Repositories/GeniusLyricsRepository.swift b/Sources/EeveeSpotify/Lyrics/Repositories/GeniusLyricsRepository.swift index d5529fe..77fc1bf 100644 --- a/Sources/EeveeSpotify/Lyrics/Repositories/GeniusLyricsRepository.swift +++ b/Sources/EeveeSpotify/Lyrics/Repositories/GeniusLyricsRepository.swift @@ -145,12 +145,22 @@ struct GeniusLyricsRepository: LyricsRepository { ) let songInfo = try getSongInfo(song.id) - let plainLines = songInfo.lyrics.plain.components(separatedBy: "\n") + let plainLyrics = songInfo.lyrics.plain + let plainLines = plainLyrics.components(separatedBy: "\n") + + var romanization = LyricsRomanizationStatus.original + + if hasFoundRomanizedLyrics { + romanization = .romanized + } + else if plainLyrics.canBeRomanized { + romanization = .canBeRomanized + } return LyricsDto( lines: mapLyricsLines(plainLines).map { line in LyricsLineDto(content: line) }, timeSynced: false, - romanized: hasFoundRomanizedLyrics + romanization: romanization ) } } diff --git a/Sources/EeveeSpotify/Lyrics/Repositories/LrclibLyricsRepository.swift b/Sources/EeveeSpotify/Lyrics/Repositories/LrclibLyricsRepository.swift index 571eb21..6719e12 100644 --- a/Sources/EeveeSpotify/Lyrics/Repositories/LrclibLyricsRepository.swift +++ b/Sources/EeveeSpotify/Lyrics/Repositories/LrclibLyricsRepository.swift @@ -105,7 +105,8 @@ struct LrcLibLyricsRepository: LyricsRepository { lines: mapSyncedLyricsLines( syncedLyrics.components(separatedBy: "\n").dropLast() ), - timeSynced: true + timeSynced: true, + romanization: syncedLyrics.canBeRomanized ? .canBeRomanized : .original ) } @@ -118,7 +119,8 @@ struct LrcLibLyricsRepository: LyricsRepository { .components(separatedBy: "\n") .dropLast() .map { content in LyricsLineDto(content: content) }, - timeSynced: false + timeSynced: false, + romanization: plainLyrics.canBeRomanized ? .canBeRomanized : .original ) } } diff --git a/Sources/EeveeSpotify/Lyrics/Repositories/MusixmatchLyricsRepository.swift b/Sources/EeveeSpotify/Lyrics/Repositories/MusixmatchLyricsRepository.swift index 85b4817..866bb23 100644 --- a/Sources/EeveeSpotify/Lyrics/Repositories/MusixmatchLyricsRepository.swift +++ b/Sources/EeveeSpotify/Lyrics/Repositories/MusixmatchLyricsRepository.swift @@ -228,10 +228,19 @@ class MusixmatchLyricsRepository: LyricsRepository { } } + var romanization = LyricsRomanizationStatus.original + + if romanized { + romanization = .romanized + } + else if lyricsLines.map({ $0.content }).joined().canBeRomanized { + romanization = .canBeRomanized + } + return LyricsDto( lines: lyricsLines, timeSynced: true, - romanized: romanized, + romanization: romanization, translation: translation ) } @@ -258,7 +267,8 @@ class MusixmatchLyricsRepository: LyricsRepository { .components(separatedBy: "\n") .dropLast() .map { LyricsLineDto(content: $0.lyricsNoteIfEmpty) }, - timeSynced: false + timeSynced: false, + romanization: plainLyrics.canBeRomanized ? .canBeRomanized : .original ) } } diff --git a/Sources/EeveeSpotify/Lyrics/Repositories/PetitLyricsRepository.swift b/Sources/EeveeSpotify/Lyrics/Repositories/PetitLyricsRepository.swift index 0c1996f..824f410 100644 --- a/Sources/EeveeSpotify/Lyrics/Repositories/PetitLyricsRepository.swift +++ b/Sources/EeveeSpotify/Lyrics/Repositories/PetitLyricsRepository.swift @@ -115,7 +115,10 @@ struct PetitLyricsRepository: LyricsRepository { offsetMs: $0.words.first!.starttime ) }, - timeSynced: true + timeSynced: true, + romanization: lyrics.lines.map { $0.linestring }.joined().canBeRomanized + ? .canBeRomanized + : .original ) case .plain: @@ -125,7 +128,8 @@ struct PetitLyricsRepository: LyricsRepository { lines: stringLyrics .components(separatedBy: "\n") .map { LyricsLineDto(content: $0) }, - timeSynced: false + timeSynced: false, + romanization: stringLyrics.canBeRomanized ? .canBeRomanized : .original ) default: diff --git a/Sources/EeveeSpotify/Models/Extensions/String+Extension.swift b/Sources/EeveeSpotify/Models/Extensions/String+Extension.swift index 88572b9..fa4e40c 100644 --- a/Sources/EeveeSpotify/Models/Extensions/String+Extension.swift +++ b/Sources/EeveeSpotify/Models/Extensions/String+Extension.swift @@ -1,4 +1,5 @@ import Foundation +import NaturalLanguage extension String { @@ -45,6 +46,17 @@ extension String { ) } + var canBeRomanized: Bool { + let languageRecognizer = NLLanguageRecognizer() + languageRecognizer.processString(self) + + if let code = languageRecognizer.dominantLanguage?.rawValue { + return ["ja", "ko"].contains(code) || code.contains("zh") + } + + return false + } + var hexadecimal: Data? { var data = Data(capacity: count / 2) diff --git a/Sources/EeveeSpotify/Settings/Views/Sections/EeveeLyricsSettingsView.swift b/Sources/EeveeSpotify/Settings/Views/Sections/EeveeLyricsSettingsView.swift index 7410e56..0c2a4d1 100644 --- a/Sources/EeveeSpotify/Settings/Views/Sections/EeveeLyricsSettingsView.swift +++ b/Sources/EeveeSpotify/Settings/Views/Sections/EeveeLyricsSettingsView.swift @@ -37,13 +37,11 @@ struct EeveeLyricsSettingsView: View { // - if [.genius, .musixmatch].contains(lyricsSource) || geniusFallback { - Section(footer: Text("Load romanized lyrics from Musixmatch or Genius.")) { - Toggle( - "Romanized Lyrics", - isOn: $lyricsOptions.romanization - ) - } + Section(footer: Text("Display romanized lyrics for Japanese, Korean, and Chinese.")) { + Toggle( + "Romanized Lyrics", + isOn: $lyricsOptions.romanization + ) } if lyricsSource == .musixmatch { @@ -64,7 +62,7 @@ struct EeveeLyricsSettingsView: View { .foregroundColor(.gray) } } footer: { - Text("You can enter a 2-letter Musixmatch language code and see translated lyrics on Musixmatch if they are available. It overrides Romanized Lyrics.") + Text("You can enter a 2-letter Musixmatch language code and see translated lyrics on Musixmatch if they are available.") } }