Skip to content

Commit 89d9462

Browse files
committed
Fix Spotify lyrics fetch
1 parent 08c8d76 commit 89d9462

File tree

6 files changed

+268
-120
lines changed

6 files changed

+268
-120
lines changed
Lines changed: 0 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1 @@
1-
import Foundation
21

3-
struct LRCLIBRepsonse: Decodable {
4-
struct LyricLine: Decodable, Hashable {
5-
let startTimeMS: TimeInterval
6-
let words: String
7-
let id = UUID()
8-
9-
enum CodingKeys: String, CodingKey {
10-
case startTimeMS = "startTimeMs"
11-
case words
12-
}
13-
14-
init(from decoder: Decoder) throws {
15-
let container = try decoder.container(keyedBy: CodingKeys.self)
16-
self.startTimeMS = try TimeInterval(container.decode(String.self, forKey: .startTimeMS))!
17-
self.words = try container.decode(String.self, forKey: .words)
18-
}
19-
20-
init(startTime: TimeInterval, words: String) {
21-
self.startTimeMS = startTime
22-
self.words = words
23-
}
24-
}
25-
26-
let id: Int
27-
let name, trackName, artistName, albumName: String
28-
let duration: Int
29-
let instrumental: Bool
30-
let plainLyrics, syncedLyrics: String
31-
let lyrics: [LyricLine]
32-
33-
enum CodingKeys: CodingKey {
34-
case id
35-
case name
36-
case trackName
37-
case artistName
38-
case albumName
39-
case duration
40-
case instrumental
41-
case plainLyrics
42-
case syncedLyrics
43-
// case lyrics
44-
}
45-
46-
static func decodeLyrics(input: String) -> [LyricLine] {
47-
var lyricsArray: [LyricLine] = []
48-
let lines = input.components(separatedBy: "\n")
49-
50-
for line in lines {
51-
// Use regex to match the timestamp and the lyrics
52-
let regex = try! NSRegularExpression(pattern: #"\[(\d{2}:\d{2}\.\d{2})\]\s*(.*)"#)
53-
let matches = regex.matches(in: line, range: NSRange(line.startIndex ..< line.endIndex, in: line))
54-
55-
for match in matches {
56-
if let timestampRange = Range(match.range(at: 1), in: line),
57-
let lyricsRange = Range(match.range(at: 2), in: line) {
58-
let timestamp = String(line[timestampRange])
59-
let lyrics = String(line[lyricsRange])
60-
lyricsArray.append(LyricLine(startTime: timestamp.convertToTimeInterval(), words: lyrics))
61-
}
62-
}
63-
}
64-
65-
return lyricsArray
66-
}
67-
68-
init(from decoder: any Decoder) throws {
69-
let container = try decoder.container(keyedBy: CodingKeys.self)
70-
self.id = try container.decode(Int.self, forKey: .id)
71-
self.name = try container.decode(String.self, forKey: .name)
72-
self.trackName = try container.decode(String.self, forKey: .trackName)
73-
self.artistName = try container.decode(String.self, forKey: .artistName)
74-
self.albumName = try container.decode(String.self, forKey: .albumName)
75-
self.duration = try container.decode(Int.self, forKey: .duration)
76-
self.instrumental = try container.decode(Bool.self, forKey: .instrumental)
77-
if instrumental {
78-
self.plainLyrics = ""
79-
self.syncedLyrics = ""
80-
self.lyrics = []
81-
} else {
82-
self.plainLyrics = try container.decode(String.self, forKey: .plainLyrics)
83-
self.syncedLyrics = try container.decode(String.self, forKey: .syncedLyrics)
84-
self.lyrics = Self.decodeLyrics(input: syncedLyrics)
85-
}
86-
}
87-
}
88-
89-
extension String {
90-
fileprivate func convertToTimeInterval() -> TimeInterval {
91-
guard self != "" else {
92-
return 0
93-
}
94-
95-
var interval: Double = 0
96-
97-
let parts = self.components(separatedBy: ":")
98-
for (index, part) in parts.reversed().enumerated() {
99-
interval += (Double(part) ?? 0) * pow(Double(60), Double(index))
100-
}
101-
102-
return interval * 1000 // Convert seconds to milliseconds
103-
}
104-
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,92 @@
11
import Foundation
2+
import LyricsCore
3+
import CXShim
4+
import CXExtensions
5+
import Regex
6+
7+
// https://lrclib.net/api/search
8+
#if canImport(FoundationNetworking)
9+
import FoundationNetworking
10+
#endif
11+
12+
extension LyricsProviders {
13+
public final class LRCLIB {
14+
public init() {}
15+
}
16+
}
17+
18+
public struct LRCLIBResponse: Codable {
19+
let id: Int
20+
let name: String
21+
let trackName: String
22+
let artistName: String
23+
let albumName: String
24+
let duration: Double
25+
let instrumental: Bool
26+
let plainLyrics: String?
27+
let syncedLyrics: String?
28+
}
29+
30+
extension LyricsProviders.LRCLIB: _LyricsProvider {
31+
32+
public typealias LyricsToken = LRCLIBResponse
33+
34+
public static let service: String? = "LRCLIB"
35+
36+
public func lyricsSearchPublisher(request: LyricsSearchRequest) -> AnyPublisher<LyricsToken, Never> {
37+
let url = switch request.searchTerm {
38+
case .keyword(let string):
39+
URL(string: "https://lrclib.net/api/search?q=\(string)")!
40+
case .info(let title, let artist):
41+
URL(string: "https://lrclib.net/api/search?track_name=\(title)&artist_name=\(artist)")!
42+
}
43+
var req = URLRequest(url: url)
44+
req.httpMethod = "GET"
45+
return sharedURLSession.cx.dataTaskPublisher(for: req)
46+
.map(\.data)
47+
.decode(type: [LRCLIBResponse].self, decoder: JSONDecoder().cx)
48+
.replaceError(with: [])
49+
.flatMap(Publishers.Sequence.init)
50+
.map { $0 as LyricsToken }
51+
.eraseToAnyPublisher()
52+
}
53+
54+
public func lyricsFetchPublisher(token: LyricsToken) -> AnyPublisher<Lyrics, Never> {
55+
guard let syncedLyrics = token.syncedLyrics, let lyrics = Lyrics(syncedLyrics) else { return Empty<Lyrics, Never>().eraseToAnyPublisher() }
56+
57+
58+
return Just(lyrics).eraseToAnyPublisher()
59+
// let url = URL(string: netEaseLyricsBaseURLString + parameter.stringFromHttpParameters)!
60+
// return sharedURLSession.cx.dataTaskPublisher(for: url)
61+
// .map(\.data)
62+
// .decode(type: NetEaseResponseSingleLyrics.self, decoder: JSONDecoder().cx)
63+
// .compactMap {
64+
// let lyrics: Lyrics
65+
// let transLrc = ($0.tlyric?.fixedLyric).flatMap(Lyrics.init(_:))
66+
// if let kLrc = ($0.klyric?.fixedLyric).flatMap(Lyrics.init(netEaseKLyricContent:)) {
67+
// transLrc.map(kLrc.forceMerge)
68+
// lyrics = kLrc
69+
// } else if let lrc = ($0.lrc?.fixedLyric).flatMap(Lyrics.init(_:)) {
70+
// transLrc.map(lrc.merge)
71+
// lyrics = lrc
72+
// } else {
73+
// return nil
74+
// }
75+
//
76+
// // FIXME: merge inline time tags back to lyrics
77+
// // if let taggedLrc = (model.klyric?.lyric).flatMap(Lyrics.init(netEaseKLyricContent:))
78+
//
79+
// lyrics.idTags[.title] = token.value.name
80+
// lyrics.idTags[.artist] = token.value.artists.first?.name
81+
// lyrics.idTags[.album] = token.value.album.name
82+
// lyrics.idTags[.lrcBy] = $0.lyricUser?.nickname
83+
//
84+
// lyrics.length = Double(token.value.duration) / 1000
85+
// lyrics.metadata.artworkURL = token.value.album.picUrl
86+
// lyrics.metadata.serviceToken = "\(token.value.id)"
87+
//
88+
// return lyrics
89+
// }.ignoreError()
90+
// .eraseToAnyPublisher()
91+
}
92+
}

Sources/LyricsService/Provider/Service.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ extension LyricsProviders {
3737
.netease,
3838
.qq,
3939
.kugou,
40-
.gecimi,
41-
.syair,
4240
.lrclib,
4341
]
4442
}

0 commit comments

Comments
 (0)