feat: support Miruro provider tracks, proxies, and native embeds
This commit is contained in:
@@ -8,18 +8,21 @@ struct ResolvedSubtitle: Decodable, Sendable {
|
||||
let label: String
|
||||
let isDefault: Bool
|
||||
private let headerFields: [String: String]
|
||||
var proxy: MiruroProxy? = nil
|
||||
var headers: String {
|
||||
headerFields.sorted { $0.key < $1.key }.map { "\($0.key): \($0.value)\r\n" }.joined()
|
||||
}
|
||||
fileprivate var validatedHeaders: [String: String] { headerFields }
|
||||
|
||||
init(url: String, format: String, language: String, label: String, isDefault: Bool, headers: [String: String]) {
|
||||
init(url: String, format: String, language: String, label: String, isDefault: Bool,
|
||||
headers: [String: String], proxy: MiruroProxy? = nil) {
|
||||
self.url = url
|
||||
self.format = format
|
||||
self.language = language
|
||||
self.label = label
|
||||
self.isDefault = isDefault
|
||||
self.headerFields = headers
|
||||
self.proxy = proxy
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case url, format, language, label
|
||||
@@ -34,6 +37,13 @@ struct ResolvedSource: Sendable {
|
||||
let identity: String
|
||||
let title: String
|
||||
let subtitles: [ResolvedSubtitle]
|
||||
var requirements: MiruroPlaybackRequirements = .init()
|
||||
var playbackLifetime: (any Sendable)? = nil
|
||||
|
||||
func replacingSubtitles(_ subtitles: [ResolvedSubtitle]) -> ResolvedSource {
|
||||
ResolvedSource(url: url, headers: headers, identity: identity, title: title, subtitles: subtitles,
|
||||
requirements: requirements, playbackLifetime: playbackLifetime)
|
||||
}
|
||||
|
||||
struct Document: Decodable {
|
||||
struct Identity: Decodable {
|
||||
@@ -87,14 +97,7 @@ struct ResolvedSource: Sendable {
|
||||
validIdentity(mediaID: id.media_id, episode: id.episode),
|
||||
validURL(document.url), validHeaders(document.headers),
|
||||
validText(document.title ?? "", limit: 4096),
|
||||
document.subtitles.count <= 32,
|
||||
document.subtitles.filter({ $0.isDefault }).count <= 1 else { throw InvalidSource() }
|
||||
for subtitle in document.subtitles {
|
||||
guard validURL(subtitle.url), subtitle.format == "vtt",
|
||||
validText(subtitle.language, limit: 128), !subtitle.language.isEmpty,
|
||||
validText(subtitle.label, limit: 4096), !subtitle.label.isEmpty,
|
||||
validHeaders(subtitle.validatedHeaders) else { throw InvalidSource() }
|
||||
}
|
||||
validSubtitles(document.subtitles) else { throw InvalidSource() }
|
||||
let headers = document.headers.sorted { $0.key < $1.key }
|
||||
.map { "\($0.key): \($0.value)\r\n" }.joined()
|
||||
return ResolvedSource(url: document.url, headers: headers,
|
||||
@@ -102,16 +105,26 @@ struct ResolvedSource: Sendable {
|
||||
subtitles: document.subtitles)
|
||||
}
|
||||
|
||||
static func validSubtitles(_ subtitles: [ResolvedSubtitle]) -> Bool {
|
||||
subtitles.count <= 32 && subtitles.filter({ $0.isDefault }).count <= 1 &&
|
||||
subtitles.allSatisfy {
|
||||
validURL($0.url) && $0.format == "vtt" &&
|
||||
validText($0.language, limit: 128) && !$0.language.isEmpty &&
|
||||
validText($0.label, limit: 4096) && !$0.label.isEmpty &&
|
||||
validHeaders($0.validatedHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
private static func isIDByte(_ byte: UInt8) -> Bool {
|
||||
(65...90).contains(byte) || (97...122).contains(byte) ||
|
||||
(48...57).contains(byte) || byte == 45 || byte == 95
|
||||
}
|
||||
|
||||
private static func validText(_ text: String, limit: Int) -> Bool {
|
||||
static func validText(_ text: String, limit: Int) -> Bool {
|
||||
text.utf8.count <= limit && !text.unicodeScalars.contains { $0.value < 32 || $0.value == 127 }
|
||||
}
|
||||
|
||||
private static func validURL(_ text: String) -> Bool {
|
||||
static func validURL(_ text: String) -> Bool {
|
||||
guard !text.isEmpty, validText(text, limit: 32768),
|
||||
!text.contains(where: { $0.isWhitespace }),
|
||||
let components = URLComponents(string: text),
|
||||
@@ -122,7 +135,7 @@ struct ResolvedSource: Sendable {
|
||||
return components.port == nil || (1...65535).contains(components.port!)
|
||||
}
|
||||
|
||||
private static func validHeaders(_ headers: [String: String]) -> Bool {
|
||||
static func validHeaders(_ headers: [String: String]) -> Bool {
|
||||
guard headers.count <= 64 else { return false }
|
||||
var names = Set<String>()
|
||||
var size = 0
|
||||
|
||||
Reference in New Issue
Block a user