mirror of
https://github.com/dirtydishes/dreamio.git
synced 2026-06-06 13:37:24 +00:00
fix opensubtitles native captions
This commit is contained in:
parent
d3c5507763
commit
f34d60af1b
7 changed files with 666 additions and 16 deletions
|
|
@ -84,6 +84,19 @@ final class DreamioWebViewController: UIViewController {
|
|||
const postedSubtitleURLs = new Set();
|
||||
const subtitleURLPattern = /https?:\/\/[^\s"'<>]+(?:\.srt|\.vtt|\.ass|\.ssa|\.sub|opensubtitles|subtitle)[^\s"'<>]*/ig;
|
||||
const subtitleSignalPattern = /subtitle|subtitles|opensubtitles|vtt|srt|ass|ssa/i;
|
||||
const subtitleObjectKeys = [
|
||||
"attributes",
|
||||
"files",
|
||||
"file_id",
|
||||
"url",
|
||||
"download",
|
||||
"link",
|
||||
"file",
|
||||
"file_name",
|
||||
"filename",
|
||||
"language",
|
||||
"lang"
|
||||
];
|
||||
|
||||
const looksNative = (url) => {
|
||||
if (!url || typeof url !== "string") {
|
||||
|
|
@ -132,10 +145,14 @@ final class DreamioWebViewController: UIViewController {
|
|||
const postSubtitleCandidates = (candidates, debug = {}) => {
|
||||
const discoveredCount = candidates.length;
|
||||
const fresh = candidates.filter((candidate) => {
|
||||
if (postedSubtitleURLs.has(candidate.url)) {
|
||||
const key = candidate && (candidate.url || candidate.link || candidate.download || candidate.file || candidate.file_id);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
postedSubtitleURLs.add(candidate.url);
|
||||
if (postedSubtitleURLs.has(String(key))) {
|
||||
return false;
|
||||
}
|
||||
postedSubtitleURLs.add(String(key));
|
||||
return true;
|
||||
});
|
||||
if (fresh.length === 0) {
|
||||
|
|
@ -182,9 +199,12 @@ final class DreamioWebViewController: UIViewController {
|
|||
entry.fileUrl ||
|
||||
entry.fileURL
|
||||
);
|
||||
const url = absoluteURL(rawURL);
|
||||
let url = absoluteURL(rawURL);
|
||||
if (!url && entry && entry.file_id) {
|
||||
url = `https://api.opensubtitles.com/api/v1/download/${encodeURIComponent(String(entry.file_id))}`;
|
||||
}
|
||||
subtitleURLPattern.lastIndex = 0;
|
||||
if (!url || !subtitleURLPattern.test(url)) {
|
||||
if (!url || (!subtitleURLPattern.test(url) && !/api\.opensubtitles\.com\/api\/v1\/download/i.test(url))) {
|
||||
subtitleURLPattern.lastIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
|
@ -198,7 +218,10 @@ final class DreamioWebViewController: UIViewController {
|
|||
language: entry && (entry.lang || entry.language) || ""
|
||||
};
|
||||
subtitleCandidates.push(candidate);
|
||||
postSubtitleCandidates([candidate]);
|
||||
postSubtitleCandidates([candidate], {
|
||||
discovered: 1,
|
||||
totalKnown: subtitleCandidates.length
|
||||
});
|
||||
};
|
||||
|
||||
const inspectTrack = (track) => {
|
||||
|
|
@ -264,6 +287,13 @@ final class DreamioWebViewController: UIViewController {
|
|||
}
|
||||
if (typeof payload === "object") {
|
||||
addSubtitleCandidate(payload);
|
||||
const likelySubtitlePayload = subtitleObjectKeys.some((key) => Object.prototype.hasOwnProperty.call(payload, key));
|
||||
if (likelySubtitlePayload) {
|
||||
postSubtitleCandidates([payload], {
|
||||
source: "payload-object",
|
||||
totalKnown: subtitleCandidates.length
|
||||
});
|
||||
}
|
||||
Object.values(payload).forEach(inspectSubtitlePayload);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -132,8 +132,8 @@ struct StreamCandidate {
|
|||
|
||||
enum SubtitleCandidateParser {
|
||||
private static let supportedExtensions = ["srt", "vtt", "ass", "ssa", "sub"]
|
||||
private static let urlFields = ["url", "href", "src", "link", "subtitles", "subtitle", "subtitleUrl", "subtitleURL", "file", "download"]
|
||||
private static let labelFields = ["label", "name", "title", "file_name", "lang", "language", "id"]
|
||||
private static let urlFields = ["url", "href", "src", "link", "subtitles", "subtitle", "subtitleUrl", "subtitleURL", "file", "download", "fileUrl", "fileURL"]
|
||||
private static let labelFields = ["label", "name", "title", "file_name", "filename", "lang", "language", "id"]
|
||||
private struct CandidateContext {
|
||||
let label: String?
|
||||
let language: String?
|
||||
|
|
@ -194,7 +194,9 @@ enum SubtitleCandidateParser {
|
|||
}
|
||||
|
||||
private static func candidate(from dictionary: [String: Any], context: CandidateContext) -> SubtitleCandidate? {
|
||||
guard let url = urlFields.lazy.compactMap({ subtitleURL(from: dictionary[$0] as? String) }).first else {
|
||||
guard let url = urlFields.lazy.compactMap({ subtitleURL(from: dictionary[$0] as? String) }).first
|
||||
?? openSubtitlesDownloadURL(from: dictionary["file_id"])
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +216,7 @@ enum SubtitleCandidateParser {
|
|||
}
|
||||
|
||||
private static func orderedNestedValues(in dictionary: [String: Any]) -> [Any] {
|
||||
let preferredKeys = ["subtitles", "subtitle", "files", "downloads", "download"]
|
||||
let preferredKeys = ["attributes", "subtitles", "subtitle", "files", "downloads", "download", "data", "results"]
|
||||
var visitedKeys = Set<String>()
|
||||
var values: [Any] = []
|
||||
|
||||
|
|
@ -254,6 +256,22 @@ enum SubtitleCandidateParser {
|
|||
return url
|
||||
}
|
||||
|
||||
private static func openSubtitlesDownloadURL(from value: Any?) -> URL? {
|
||||
let id: String?
|
||||
if let string = value as? String, !string.isEmpty {
|
||||
id = string
|
||||
} else if let number = value as? NSNumber {
|
||||
id = number.stringValue
|
||||
} else {
|
||||
id = nil
|
||||
}
|
||||
|
||||
guard let id else {
|
||||
return nil
|
||||
}
|
||||
return URL(string: "https://api.opensubtitles.com/api/v1/download/\(id)")
|
||||
}
|
||||
|
||||
private static func defaultLabel(for url: URL) -> String {
|
||||
let lastPathComponent = url.deletingPathExtension().lastPathComponent
|
||||
return lastPathComponent.isEmpty ? "External Subtitle" : lastPathComponent
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
|
|||
private var didAutoSelectSubtitleTrack = false
|
||||
private var didUserSelectSubtitleTrack = false
|
||||
private var autoSelectedSubtitleTrackID: Int32?
|
||||
private var externalSubtitleBaselineTrackIDs = Set<Int32>()
|
||||
private var hasPendingExternalSubtitleSelection = false
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
|
@ -47,6 +49,8 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
|
|||
didAutoSelectSubtitleTrack = false
|
||||
didUserSelectSubtitleTrack = false
|
||||
autoSelectedSubtitleTrackID = nil
|
||||
externalSubtitleBaselineTrackIDs.removeAll()
|
||||
hasPendingExternalSubtitleSelection = false
|
||||
let media = VLCMedia(url: request.playbackURL)
|
||||
let headerValue = request.headers
|
||||
.map { "\($0.key): \($0.value)" }
|
||||
|
|
@ -225,22 +229,25 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
|
|||
private func attachSubtitles(_ candidates: [SubtitleCandidate]) -> Int {
|
||||
var attachedCount = 0
|
||||
var duplicateCount = 0
|
||||
let baselineTrackIDs = Set(subtitleTracks.filter { $0.id >= 0 }.map(\.id))
|
||||
candidates.forEach { candidate in
|
||||
guard !attachedSubtitleURLs.contains(candidate.url) else {
|
||||
duplicateCount += 1
|
||||
return
|
||||
}
|
||||
attachedSubtitleURLs.insert(candidate.url)
|
||||
externalSubtitleBaselineTrackIDs.formUnion(baselineTrackIDs)
|
||||
hasPendingExternalSubtitleSelection = true
|
||||
mediaPlayer.addPlaybackSlave(candidate.url, type: .subtitle, enforce: false)
|
||||
attachedCount += 1
|
||||
#if DEBUG
|
||||
print("[DreamioVLC] addPlaybackSlave subtitle=\(URLRedactor.redactedURLString(candidate.url.absoluteString)) label=\(candidate.label) language=\(candidate.language ?? "unknown") ext=\(candidate.url.pathExtension.lowercased())")
|
||||
print("[DreamioVLC] attach accepted subtitle=\(URLRedactor.redactedURLString(candidate.url.absoluteString)) label=\(candidate.label) language=\(candidate.language ?? "unknown") ext=\(candidate.url.pathExtension.lowercased()) visibleBefore=\(baselineTrackIDs.count)")
|
||||
logSubtitleTracks(reason: "after-addPlaybackSlave")
|
||||
#endif
|
||||
}
|
||||
#if DEBUG
|
||||
if !candidates.isEmpty {
|
||||
print("[DreamioVLC] subtitle candidates=\(candidates.count) attached=\(attachedCount) duplicates=\(duplicateCount)")
|
||||
print("[DreamioVLC] subtitle candidates=\(candidates.count) attached=\(attachedCount) duplicates=\(duplicateCount) visible=\(subtitleTracks.filter { $0.id >= 0 }.count)")
|
||||
}
|
||||
#endif
|
||||
guard attachedCount > 0 else {
|
||||
|
|
@ -248,9 +255,12 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
|
|||
}
|
||||
[0.2, 0.6, 1.0, 2.0, 4.0].forEach { delay in
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||
self?.selectInitialSubtitleTrackIfNeeded(reason: "delayed-refresh-\(String(format: "%.1f", delay))")
|
||||
self?.selectPreferredSubtitleTrackIfNeeded(reason: "delayed-refresh-\(String(format: "%.1f", delay))")
|
||||
#if DEBUG
|
||||
self?.logSubtitleTracks(reason: "delayed-refresh-\(String(format: "%.1f", delay))")
|
||||
if delay == 4.0 {
|
||||
self?.logMissingExternalSubtitleTrackIfNeeded()
|
||||
}
|
||||
#endif
|
||||
self?.onSubtitleTracksChange?()
|
||||
}
|
||||
|
|
@ -266,14 +276,27 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
|
|||
}
|
||||
#endif
|
||||
|
||||
private func selectInitialSubtitleTrackIfNeeded(reason: String) {
|
||||
guard !didUserSelectSubtitleTrack,
|
||||
!didAutoSelectSubtitleTrack,
|
||||
private func selectPreferredSubtitleTrackIfNeeded(reason: String) {
|
||||
guard !didUserSelectSubtitleTrack else {
|
||||
return
|
||||
}
|
||||
|
||||
if hasPendingExternalSubtitleSelection,
|
||||
let externalTrack = subtitleTracks.first(where: { $0.id >= 0 && !externalSubtitleBaselineTrackIDs.contains($0.id) }) {
|
||||
selectAutoSubtitleTrack(externalTrack, reason: "\(reason)-external")
|
||||
hasPendingExternalSubtitleSelection = false
|
||||
return
|
||||
}
|
||||
|
||||
guard !didAutoSelectSubtitleTrack,
|
||||
mediaPlayer.currentVideoSubTitleIndex < 0,
|
||||
let track = subtitleTracks.first(where: { $0.id >= 0 }) else {
|
||||
return
|
||||
}
|
||||
selectAutoSubtitleTrack(track, reason: reason)
|
||||
}
|
||||
|
||||
private func selectAutoSubtitleTrack(_ track: SubtitleTrack, reason: String) {
|
||||
didAutoSelectSubtitleTrack = true
|
||||
autoSelectedSubtitleTrackID = track.id
|
||||
#if DEBUG
|
||||
|
|
@ -283,6 +306,15 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
|
|||
scheduleAutoSubtitleSelectionReapply(trackID: track.id)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private func logMissingExternalSubtitleTrackIfNeeded() {
|
||||
guard hasPendingExternalSubtitleSelection else {
|
||||
return
|
||||
}
|
||||
print("[DreamioVLC] attach accepted but no new external subtitle track visible baseline=\(externalSubtitleBaselineTrackIDs.sorted()) visible=\(subtitleTracks.filter { $0.id >= 0 }.map(\.id))")
|
||||
}
|
||||
#endif
|
||||
|
||||
private func scheduleAutoSubtitleSelectionReapply(trackID: Int32) {
|
||||
[0.3, 1.0, 2.0, 4.0].forEach { delay in
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||
|
|
@ -333,7 +365,7 @@ extension VLCNativePlaybackBackend: VLCMediaPlayerDelegate {
|
|||
case .paused, .stopped, .ended:
|
||||
onStateChange?()
|
||||
case .esAdded:
|
||||
selectInitialSubtitleTrackIfNeeded(reason: "esAdded")
|
||||
selectPreferredSubtitleTrackIfNeeded(reason: "esAdded")
|
||||
#if DEBUG
|
||||
logSubtitleTracks(reason: "esAdded")
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue