Files
Amphlify/Packages/AmphlifyKit/Sources/Domain/Lyrics.swift
T

76 lines
2.3 KiB
Swift

import Foundation
public struct LyricLine: Hashable, Sendable, Identifiable {
public let time: TimeInterval
public let text: String
public var id: TimeInterval {
time
}
public init(time: TimeInterval, text: String) {
self.time = time
self.text = text
}
}
public struct Lyrics: Hashable, Sendable {
public let lines: [LyricLine]
public let isSynced: Bool
public init(lines: [LyricLine], isSynced: Bool) {
self.lines = lines
self.isSynced = isSynced
}
public init?(lrc: String) {
var lines: [LyricLine] = []
var plain: [String] = []
for raw in lrc.split(whereSeparator: \.isNewline) {
var rest = Substring(raw)
var stamps: [TimeInterval] = []
while rest.first == "[", let close = rest.firstIndex(of: "]") {
let tag = rest[rest.index(after: rest.startIndex) ..< close]
rest = rest[rest.index(after: close)...]
if let time = Self.time(tag) {
stamps.append(time)
}
}
let text = rest.trimmingCharacters(in: .whitespaces)
if stamps.isEmpty {
if !text.isEmpty {
plain.append(text)
}
} else {
lines += stamps.map { LyricLine(time: $0, text: text) }
}
}
if !lines.isEmpty {
self.init(lines: lines.sorted { $0.time < $1.time }, isSynced: true)
} else if !plain.isEmpty {
self.init(lines: plain.map { LyricLine(time: 0, text: $0) }, isSynced: false)
} else {
return nil
}
}
public func index(at time: TimeInterval) -> Int? {
guard isSynced else { return nil }
var found: Int?
for (index, line) in lines.enumerated() where line.time <= time {
found = index
}
return found
}
private static func time(_ tag: Substring) -> TimeInterval? {
let parts = tag.split(separator: ":")
guard parts.count == 2, let minutes = Double(parts[0]), let seconds = Double(parts[1]) else { return nil }
return minutes * 60 + seconds
}
}
public protocol LyricsSource: LibrarySource {
func lyrics(for track: EntityID) async throws -> Lyrics?
}