This commit is contained in:
scigward 2025-08-19 23:17:57 +03:00
parent 334deec484
commit 970dbf2654
5 changed files with 14 additions and 142 deletions

View file

@ -368,8 +368,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
setupPipIfSupported()
setupTimeBatteryIndicator()
setupTopRowLayout()
self.loadLocalSkipTimestampsIfAvailable()
updateSkipButtonsVisibility()
if !isSkip85Visible {
@ -401,7 +399,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
self?.malID = mal
self?.fetchSkipTimes(type: "op")
self?.fetchSkipTimes(type: "ed")
self?.loadLocalSkipTimestampsIfAvailable()
case .failure(let error):
Logger.shared.log("Unable to fetch MAL ID: \(error)",type:"Error")
}
@ -3866,57 +3863,4 @@ class GradientBlurButton: UIButton {
cleanupVisualEffects()
super.removeFromSuperview()
}
private func loadLocalSkipTimestampsIfAvailable() {
// Try from subtitle file path first
var candidateURLs: [URL] = []
if let sub = subtitlesURL, !sub.isEmpty, let u = URL(string: sub) {
candidateURLs.append(u)
}
if let u = URL(string: streamURL) {
candidateURLs.append(u)
}
for u in candidateURLs {
// Ensure file URL
let fileURL: URL
if u.isFileURL {
fileURL = u
} else if let url = URL(string: u.absoluteString), url.isFileURL {
fileURL = url
} else {
continue
}
let base = fileURL.deletingPathExtension()
let candidates = [
base.appendingPathExtension("skip.json"),
base.deletingLastPathComponent().appendingPathComponent(base.lastPathComponent + ".skip.json")
]
for jsonURL in candidates {
if FileManager.default.fileExists(atPath: jsonURL.path),
let data = try? Data(contentsOf: jsonURL),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
if let op = json["op"] as? [String: Any],
let s = op["start"] as? Double, let e = op["end"] as? Double {
self.skipIntervals.op = CMTimeRange(
start: CMTime(seconds: s, preferredTimescale: 600),
end: CMTime(seconds: e, preferredTimescale: 600)
)
}
if let ed = json["ed"] as? [String: Any],
let s = ed["start"] as? Double, let e = ed["end"] as? Double {
self.skipIntervals.ed = CMTimeRange(
start: CMTime(seconds: s, preferredTimescale: 600),
end: CMTime(seconds: e, preferredTimescale: 600)
)
}
if self.duration > 0 {
self.updateSegments()
}
self.updateSkipButtonsVisibility()
return
}
}
}
}
}

View file

@ -414,9 +414,7 @@ struct AssetMetadata: Codable {
let episodeTitle: String?
let seasonNumber: Int?
let anilistId: Int?
init(
init(
title: String,
overview: String? = nil,
posterURL: URL? = nil,
@ -427,8 +425,7 @@ init(
episode: Int? = nil,
showPosterURL: URL? = nil,
episodeTitle: String? = nil,
seasonNumber: Int? = nil,
anilistId: Int? = nil
seasonNumber: Int? = nil
) {
self.title = title
self.overview = overview
@ -441,9 +438,7 @@ init(
self.showPosterURL = showPosterURL
self.episodeTitle = episodeTitle
self.seasonNumber = seasonNumber
self.anilistId = anilistId
}
}
}
// MARK: - New Group Model

View file

@ -14,9 +14,7 @@ struct DownloadRequest {
let headers: [String: String]
let title: String?
let imageURL: URL?
let aniListID: Int?
let isEpisode: Bool
let isEpisode: Bool
let showTitle: String?
let season: Int?
let episode: Int?
@ -59,11 +57,10 @@ extension JSController {
subtitleURL: URL? = nil, showPosterURL: URL? = nil,
completionHandler: ((Bool, String) -> Void)? = nil) {
let pendingAni = UserDefaults.standard.object(forKey: "PendingAniListIDForDownload") as? Int
let request = DownloadRequest(
url: url, headers: headers, title: title, imageURL: imageURL,
isEpisode: isEpisode, showTitle: showTitle, season: season,
episode: episode, subtitleURL: subtitleURL, showPosterURL: showPosterURL, aniListID: pendingAni
episode: episode, subtitleURL: subtitleURL, showPosterURL: showPosterURL
)
logDownloadStart(request: request)
@ -95,7 +92,13 @@ extension JSController {
self.logM3U8QualitySelected(quality: selectedQuality)
if let qualityURL = URL(string: selectedQuality.url) {
let qualityRequest = DownloadRequest(url: qualityURL, headers: request.headers, title: request.title, imageURL: request.imageURL, isEpisode: request.isEpisode, showTitle: request.showTitle, season: request.season, episode: request.episode, subtitleURL: request.subtitleURL, showPosterURL: request.showPosterURL, aniListID: request.aniListID)
let qualityRequest = DownloadRequest(
url: qualityURL, headers: request.headers, title: request.title,
imageURL: request.imageURL, isEpisode: request.isEpisode,
showTitle: request.showTitle, season: request.season,
episode: request.episode, subtitleURL: request.subtitleURL,
showPosterURL: request.showPosterURL
)
self.downloadWithOriginalMethod(request: qualityRequest, completionHandler: completionHandler)
} else {
self.logM3U8InvalidURL()

View file

@ -114,13 +114,11 @@ extension JSController {
subtitleURL: URL? = nil,
showPosterURL: URL? = nil,
module: ScrapingModule? = nil,
aniListID: Int? = nil,
completionHandler: ((Bool, String) -> Void)? = nil
) {
// If a module is provided, use the stream type aware download
if let module = module {
// Use the stream type aware download method
if let anilist = aniListID { UserDefaults.standard.set(anilist, forKey: "PendingAniListIDForDownload") } else { UserDefaults.standard.removeObject(forKey: "PendingAniListIDForDownload") }
downloadWithStreamTypeSupport(
url: url,
headers: headers,
@ -1216,10 +1214,7 @@ extension JSController: AVAssetDownloadDelegate {
}
// If there's a subtitle URL, download it now that the video is saved
// Save OP/ED skip timestamps JSON in parallel
saveSkipTimestampsJSON(for: persistentURL, anilistId: newAsset.metadata?.anilistId, episodeNumber: newAsset.metadata?.episode)
if let subtitleURL = download.subtitleURL {
if let subtitleURL = download.subtitleURL {
downloadSubtitle(subtitleURL: subtitleURL, assetID: newAsset.id.uuidString)
} else {
// No subtitle URL, so we can consider the download complete
@ -1652,68 +1647,3 @@ enum DownloadQueueStatus: Equatable {
/// Download has been completed
case completed
}
// MARK: - AniSkip Timestamps Saving
private func saveSkipTimestampsJSON(for videoURL: URL, anilistId: Int?, episodeNumber: Int?) {
// Determine destination JSON path next to the video file
let base = videoURL.deletingPathExtension()
let jsonURL = base.appendingPathExtension("skip.json")
func writeJSON(op: (Double,Double)?, ed: (Double,Double)?, mal: Int?) {
var dict: [String: Any] = [
"source": "aniskip",
"createdAt": ISO8601DateFormatter().string(from: Date())
]
if let mal = mal { dict["malId"] = mal }
if let aid = anilistId { dict["anilistId"] = aid }
if let ep = episodeNumber { dict["episode"] = ep }
if let op = op {
dict["op"] = ["start": op.0, "end": op.1]
}
if let ed = ed {
dict["ed"] = ["start": ed.0, "end": ed.1]
}
do {
let data = try JSONSerialization.data(withJSONObject: dict, options: [.prettyPrinted])
try data.write(to: jsonURL, options: .atomic)
} catch {
print("Failed to write skip JSON: \(error.localizedDescription)")
}
}
guard let anilistId = anilistId, anilistId > 0, let ep = episodeNumber else {
// No IDs; nothing to fetch
return
}
// Map AniList -> MAL then fetch AniSkip for both OP and ED
AniListMutation().fetchMalID(animeId: anilistId) { result in
switch result {
case .success(let mal):
let group = DispatchGroup()
var opInterval: (Double,Double)? = nil
var edInterval: (Double,Double)? = nil
func fetch(type: String, assign: @escaping ((Double,Double))->Void) {
guard let url = URL(string: "https://api.aniskip.com/v2/skip-times/\(mal)/\(ep)?types=\(type)&episodeLength=0") else { return }
group.enter()
URLSession.shared.dataTask(with: url) { data, _, _ in
defer { group.leave() }
guard let data = data,
let resp = try? JSONDecoder().decode(AniSkipResponse.self, from: data),
resp.found, let interval = resp.results.first?.interval else { return }
assign((interval.startTime, interval.endTime))
}.resume()
}
fetch(type: "op") { opInterval = $0 }
fetch(type: "ed") { edInterval = $0 }
group.notify(queue: .global(qos: .utility)) {
writeJSON(op: opInterval, ed: edInterval, mal: mal)
}
case .failure(let e):
print("Failed to map AniList to MAL for skip JSON: \(e.localizedDescription)")
}
}
}

View file

@ -691,7 +691,7 @@ private extension EpisodeCell {
let fullEpisodeTitle = episodeTitle.isEmpty ? baseTitle : "\(baseTitle): \(episodeTitle)"
let animeTitle = parentTitle.isEmpty ? "Unknown Anime" : parentTitle
jsController.startDownload(
jsController.downloadWithStreamTypeSupport(
url: url,
headers: headers,
title: fullEpisodeTitle,