mirror of
https://github.com/cranci1/Sora.git
synced 2026-05-19 00:01:48 +00:00
Add AniList tracking for downloaded episodes + Add auto skip filler episodes option (#266)
This commit is contained in:
parent
05914ccd36
commit
5e4838d954
9 changed files with 76 additions and 29 deletions
|
|
@ -415,6 +415,8 @@ struct AssetMetadata: Codable {
|
||||||
let seasonNumber: Int?
|
let seasonNumber: Int?
|
||||||
/// Indicates whether this episode is a filler (derived from metadata at download time)
|
/// Indicates whether this episode is a filler (derived from metadata at download time)
|
||||||
let isFiller: Bool?
|
let isFiller: Bool?
|
||||||
|
let aniListID: Int?
|
||||||
|
let totalEpisodes: Int?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
title: String,
|
title: String,
|
||||||
|
|
@ -428,7 +430,9 @@ struct AssetMetadata: Codable {
|
||||||
showPosterURL: URL? = nil,
|
showPosterURL: URL? = nil,
|
||||||
episodeTitle: String? = nil,
|
episodeTitle: String? = nil,
|
||||||
seasonNumber: Int? = nil,
|
seasonNumber: Int? = nil,
|
||||||
isFiller: Bool? = nil
|
isFiller: Bool? = nil,
|
||||||
|
aniListID: Int? = nil,
|
||||||
|
totalEpisodes: Int? = nil
|
||||||
) {
|
) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.overview = overview
|
self.overview = overview
|
||||||
|
|
@ -442,6 +446,8 @@ struct AssetMetadata: Codable {
|
||||||
self.episodeTitle = episodeTitle
|
self.episodeTitle = episodeTitle
|
||||||
self.seasonNumber = seasonNumber
|
self.seasonNumber = seasonNumber
|
||||||
self.isFiller = isFiller
|
self.isFiller = isFiller
|
||||||
|
self.aniListID = aniListID
|
||||||
|
self.totalEpisodes = totalEpisodes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,11 @@ struct DownloadRequest {
|
||||||
let aniListID: Int?
|
let aniListID: Int?
|
||||||
let malID: Int?
|
let malID: Int?
|
||||||
let isFiller: Bool?
|
let isFiller: Bool?
|
||||||
|
let totalEpisodes: Int?
|
||||||
|
|
||||||
init(url: URL, headers: [String: String], title: String? = nil, imageURL: URL? = nil,
|
init(url: URL, headers: [String: String], title: String? = nil, imageURL: URL? = nil,
|
||||||
isEpisode: Bool = false, showTitle: String? = nil, season: Int? = nil,
|
isEpisode: Bool = false, showTitle: String? = nil, season: Int? = nil,
|
||||||
episode: Int? = nil, subtitleURL: URL? = nil, showPosterURL: URL? = nil, aniListID: Int? = nil, malID: Int? = nil, isFiller: Bool? = nil) {
|
episode: Int? = nil, subtitleURL: URL? = nil, showPosterURL: URL? = nil, aniListID: Int? = nil, malID: Int? = nil, isFiller: Bool? = nil, totalEpisodes: Int? = nil) {
|
||||||
self.url = url
|
self.url = url
|
||||||
self.headers = headers
|
self.headers = headers
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
@ -40,6 +41,7 @@ struct DownloadRequest {
|
||||||
self.aniListID = aniListID
|
self.aniListID = aniListID
|
||||||
self.malID = malID
|
self.malID = malID
|
||||||
self.isFiller = isFiller
|
self.isFiller = isFiller
|
||||||
|
self.totalEpisodes = totalEpisodes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,6 +64,7 @@ extension JSController {
|
||||||
showTitle: String? = nil, season: Int? = nil, episode: Int? = nil,
|
showTitle: String? = nil, season: Int? = nil, episode: Int? = nil,
|
||||||
subtitleURL: URL? = nil, showPosterURL: URL? = nil,
|
subtitleURL: URL? = nil, showPosterURL: URL? = nil,
|
||||||
aniListID: Int? = nil, malID: Int? = nil, isFiller: Bool? = nil,
|
aniListID: Int? = nil, malID: Int? = nil, isFiller: Bool? = nil,
|
||||||
|
totalEpisodes: Int? = nil,
|
||||||
completionHandler: ((Bool, String) -> Void)? = nil) {
|
completionHandler: ((Bool, String) -> Void)? = nil) {
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -69,7 +72,7 @@ extension JSController {
|
||||||
url: url, headers: headers, title: title, imageURL: imageURL,
|
url: url, headers: headers, title: title, imageURL: imageURL,
|
||||||
isEpisode: isEpisode, showTitle: showTitle, season: season,
|
isEpisode: isEpisode, showTitle: showTitle, season: season,
|
||||||
episode: episode, subtitleURL: subtitleURL, showPosterURL: showPosterURL,
|
episode: episode, subtitleURL: subtitleURL, showPosterURL: showPosterURL,
|
||||||
aniListID: aniListID, malID: malID, isFiller: isFiller
|
aniListID: aniListID, malID: malID, isFiller: isFiller, totalEpisodes: totalEpisodes
|
||||||
)
|
)
|
||||||
|
|
||||||
logDownloadStart(request: request)
|
logDownloadStart(request: request)
|
||||||
|
|
@ -114,7 +117,8 @@ extension JSController {
|
||||||
showPosterURL: request.showPosterURL,
|
showPosterURL: request.showPosterURL,
|
||||||
aniListID: request.aniListID,
|
aniListID: request.aniListID,
|
||||||
malID: request.malID,
|
malID: request.malID,
|
||||||
isFiller: request.isFiller
|
isFiller: request.isFiller,
|
||||||
|
totalEpisodes: request.totalEpisodes
|
||||||
)
|
)
|
||||||
self.downloadWithOriginalMethod(request: qualityRequest, completionHandler: completionHandler)
|
self.downloadWithOriginalMethod(request: qualityRequest, completionHandler: completionHandler)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -142,6 +146,7 @@ extension JSController {
|
||||||
imageURL: URL? = nil, isEpisode: Bool = false, showTitle: String? = nil,
|
imageURL: URL? = nil, isEpisode: Bool = false, showTitle: String? = nil,
|
||||||
season: Int? = nil, episode: Int? = nil, subtitleURL: URL? = nil,
|
season: Int? = nil, episode: Int? = nil, subtitleURL: URL? = nil,
|
||||||
showPosterURL: URL? = nil, aniListID: Int? = nil, malID: Int? = nil, isFiller: Bool? = nil,
|
showPosterURL: URL? = nil, aniListID: Int? = nil, malID: Int? = nil, isFiller: Bool? = nil,
|
||||||
|
totalEpisodes: Int? = nil,
|
||||||
completionHandler: ((Bool, String) -> Void)? = nil) {
|
completionHandler: ((Bool, String) -> Void)? = nil) {
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -149,7 +154,7 @@ extension JSController {
|
||||||
url: url, headers: headers, title: title, imageURL: imageURL,
|
url: url, headers: headers, title: title, imageURL: imageURL,
|
||||||
isEpisode: isEpisode, showTitle: showTitle, season: season,
|
isEpisode: isEpisode, showTitle: showTitle, season: season,
|
||||||
episode: episode, subtitleURL: subtitleURL, showPosterURL: showPosterURL,
|
episode: episode, subtitleURL: subtitleURL, showPosterURL: showPosterURL,
|
||||||
aniListID: aniListID, malID: malID, isFiller: isFiller
|
aniListID: aniListID, malID: malID, isFiller: isFiller, totalEpisodes: totalEpisodes
|
||||||
)
|
)
|
||||||
|
|
||||||
downloadMP4(request: request, completionHandler: completionHandler)
|
downloadMP4(request: request, completionHandler: completionHandler)
|
||||||
|
|
@ -382,7 +387,9 @@ extension JSController {
|
||||||
showPosterURL: request.showPosterURL ?? request.imageURL,
|
showPosterURL: request.showPosterURL ?? request.imageURL,
|
||||||
episodeTitle: nil,
|
episodeTitle: nil,
|
||||||
seasonNumber: nil,
|
seasonNumber: nil,
|
||||||
isFiller: request.isFiller
|
isFiller: request.isFiller,
|
||||||
|
aniListID: request.aniListID,
|
||||||
|
totalEpisodes: request.totalEpisodes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -436,6 +443,7 @@ extension JSController {
|
||||||
aniListID: request.aniListID,
|
aniListID: request.aniListID,
|
||||||
malID: request.malID,
|
malID: request.malID,
|
||||||
isFiller: request.isFiller,
|
isFiller: request.isFiller,
|
||||||
|
totalEpisodes: request.totalEpisodes,
|
||||||
completionHandler: completionHandler
|
completionHandler: completionHandler
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ extension JSController {
|
||||||
aniListID: Int? = nil,
|
aniListID: Int? = nil,
|
||||||
malID: Int? = nil,
|
malID: Int? = nil,
|
||||||
isFiller: Bool? = nil,
|
isFiller: Bool? = nil,
|
||||||
|
totalEpisodes: Int? = nil,
|
||||||
completionHandler: ((Bool, String) -> Void)? = nil
|
completionHandler: ((Bool, String) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
// If a module is provided, use the stream type aware download
|
// If a module is provided, use the stream type aware download
|
||||||
|
|
@ -137,6 +138,7 @@ extension JSController {
|
||||||
aniListID: aniListID,
|
aniListID: aniListID,
|
||||||
malID: malID,
|
malID: malID,
|
||||||
isFiller: isFiller,
|
isFiller: isFiller,
|
||||||
|
totalEpisodes: totalEpisodes,
|
||||||
completionHandler: completionHandler
|
completionHandler: completionHandler
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
@ -163,7 +165,9 @@ extension JSController {
|
||||||
season: season,
|
season: season,
|
||||||
episode: episode,
|
episode: episode,
|
||||||
showPosterURL: showPosterURL, // Main show poster
|
showPosterURL: showPosterURL, // Main show poster
|
||||||
isFiller: isFiller
|
isFiller: isFiller,
|
||||||
|
aniListID: aniListID,
|
||||||
|
totalEpisodes: totalEpisodes
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create the download ID now so we can use it for notifications
|
// Create the download ID now so we can use it for notifications
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ extension JSController {
|
||||||
aniListID: Int? = nil,
|
aniListID: Int? = nil,
|
||||||
malID: Int? = nil,
|
malID: Int? = nil,
|
||||||
isFiller: Bool? = nil,
|
isFiller: Bool? = nil,
|
||||||
|
totalEpisodes: Int? = nil,
|
||||||
completionHandler: ((Bool, String) -> Void)? = nil
|
completionHandler: ((Bool, String) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
let streamType = module.metadata.streamType.lowercased()
|
let streamType = module.metadata.streamType.lowercased()
|
||||||
|
|
@ -59,6 +60,7 @@ extension JSController {
|
||||||
aniListID: aniListID,
|
aniListID: aniListID,
|
||||||
malID: malID,
|
malID: malID,
|
||||||
isFiller: isFiller,
|
isFiller: isFiller,
|
||||||
|
totalEpisodes: totalEpisodes,
|
||||||
completionHandler: completionHandler
|
completionHandler: completionHandler
|
||||||
)
|
)
|
||||||
}else {
|
}else {
|
||||||
|
|
@ -77,6 +79,7 @@ extension JSController {
|
||||||
aniListID: aniListID,
|
aniListID: aniListID,
|
||||||
malID: malID,
|
malID: malID,
|
||||||
isFiller: isFiller,
|
isFiller: isFiller,
|
||||||
|
totalEpisodes: totalEpisodes,
|
||||||
completionHandler: completionHandler
|
completionHandler: completionHandler
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -281,8 +281,8 @@ struct DownloadView: View {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
subtitlesURL: asset.localSubtitleURL?.absoluteString,
|
subtitlesURL: asset.localSubtitleURL?.absoluteString,
|
||||||
aniListID: 0,
|
aniListID: asset.metadata?.aniListID ?? 0,
|
||||||
totalEpisodes: asset.metadata?.episode ?? 0,
|
totalEpisodes: asset.metadata?.totalEpisodes ?? 0,
|
||||||
episodeImageUrl: asset.metadata?.posterURL?.absoluteString ?? "",
|
episodeImageUrl: asset.metadata?.posterURL?.absoluteString ?? "",
|
||||||
headers: nil
|
headers: nil
|
||||||
)
|
)
|
||||||
|
|
@ -1050,7 +1050,6 @@ struct EnhancedShowEpisodesView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
heroImageSection
|
|
||||||
mainScrollView
|
mainScrollView
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
.ignoresSafeArea(.container, edges: .top)
|
.ignoresSafeArea(.container, edges: .top)
|
||||||
|
|
@ -1099,7 +1098,10 @@ struct EnhancedShowEpisodesView: View {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var mainScrollView: some View {
|
private var mainScrollView: some View {
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView(showsIndicators: false) {
|
||||||
contentContainer
|
ZStack(alignment: .top) {
|
||||||
|
heroImageSection
|
||||||
|
contentContainer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
UIScrollView.appearance().bounces = false
|
UIScrollView.appearance().bounces = false
|
||||||
|
|
@ -1108,24 +1110,22 @@ struct EnhancedShowEpisodesView: View {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var heroImageSection: some View {
|
private var heroImageSection: some View {
|
||||||
if let posterURL = group.posterURL {
|
Group {
|
||||||
LazyImage(url: posterURL) { state in
|
if let posterURL = group.posterURL {
|
||||||
if let uiImage = state.imageContainer?.image {
|
LazyImage(url: posterURL) { @MainActor state in
|
||||||
Image(uiImage: uiImage)
|
if let uiImage = state.imageContainer?.image {
|
||||||
.resizable()
|
Image(uiImage: uiImage)
|
||||||
.aspectRatio(contentMode: .fill)
|
.resizable()
|
||||||
} else {
|
.aspectRatio(contentMode: .fill)
|
||||||
placeholderGradient
|
.frame(width: UIScreen.main.bounds.width, height: 700)
|
||||||
|
.clipped()
|
||||||
|
} else {
|
||||||
|
placeholderGradient
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
placeholderGradient
|
||||||
}
|
}
|
||||||
.ignoresSafeArea(.all)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: 400)
|
|
||||||
.clipped()
|
|
||||||
} else {
|
|
||||||
placeholderGradient
|
|
||||||
.ignoresSafeArea(.all)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: 400)
|
|
||||||
.clipped()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1142,6 +1142,8 @@ struct EnhancedShowEpisodesView: View {
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.frame(width: UIScreen.main.bounds.width, height: 700)
|
||||||
|
.clipped()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
||||||
|
|
@ -747,7 +747,8 @@ private extension EpisodeCell {
|
||||||
showPosterURL: showPosterImageURL,
|
showPosterURL: showPosterImageURL,
|
||||||
aniListID: itemID,
|
aniListID: itemID,
|
||||||
malID: malIDFromParent,
|
malID: malIDFromParent,
|
||||||
isFiller: isFiller
|
isFiller: isFiller,
|
||||||
|
totalEpisodes: totalEpisodes
|
||||||
) { success, message in
|
) { success, message in
|
||||||
if success {
|
if success {
|
||||||
Logger.shared.log("Started download for Episode \(self.episodeID + 1): \(self.episode)", type: "Download")
|
Logger.shared.log("Started download for Episode \(self.episodeID + 1): \(self.episode)", type: "Download")
|
||||||
|
|
|
||||||
|
|
@ -1373,7 +1373,21 @@ struct MediaInfoView: View {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextEpisode = episodeLinks[currentIndex + 1]
|
let autoSkipFillers = UserDefaults.standard.bool(forKey: "autoSkipFillers")
|
||||||
|
var nextIndex = currentIndex + 1
|
||||||
|
|
||||||
|
if autoSkipFillers, let fillerSet = jikanFillerSet {
|
||||||
|
while nextIndex < episodeLinks.count, fillerSet.contains(episodeLinks[nextIndex].number) {
|
||||||
|
Logger.shared.log("Skipping filler episode \(episodeLinks[nextIndex].number)", type: "Debug")
|
||||||
|
nextIndex += 1
|
||||||
|
}
|
||||||
|
guard nextIndex < episodeLinks.count else {
|
||||||
|
Logger.shared.log("No more non-filler episodes to play", type: "Info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextEpisode = episodeLinks[nextIndex]
|
||||||
selectedEpisodeNumber = nextEpisode.number
|
selectedEpisodeNumber = nextEpisode.number
|
||||||
fetchStream(href: nextEpisode.href)
|
fetchStream(href: nextEpisode.href)
|
||||||
DropManager.shared.showDrop(
|
DropManager.shared.showDrop(
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,7 @@ struct SettingsViewBackup: View {
|
||||||
"skipIntroOutroVisible",
|
"skipIntroOutroVisible",
|
||||||
"pipButtonVisible",
|
"pipButtonVisible",
|
||||||
"autoplayNext",
|
"autoplayNext",
|
||||||
|
"autoSkipFillers",
|
||||||
"videoQualityWiFi",
|
"videoQualityWiFi",
|
||||||
"videoQualityCellular",
|
"videoQualityCellular",
|
||||||
"subtitlesEnabled",
|
"subtitlesEnabled",
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,7 @@ struct SettingsViewPlayer: View {
|
||||||
@AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false
|
@AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false
|
||||||
@AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true
|
@AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true
|
||||||
@AppStorage("autoplayNext") private var autoplayNext: Bool = true
|
@AppStorage("autoplayNext") private var autoplayNext: Bool = true
|
||||||
|
@AppStorage("autoSkipFillers") private var autoSkipFillers: Bool = false
|
||||||
@AppStorage("introDBEnabled") private var introDBEnabled: Bool = true
|
@AppStorage("introDBEnabled") private var introDBEnabled: Bool = true
|
||||||
|
|
||||||
@AppStorage("videoQualityWiFi") private var wifiQuality: String = VideoQualityPreference.defaultWiFiPreference.rawValue
|
@AppStorage("videoQualityWiFi") private var wifiQuality: String = VideoQualityPreference.defaultWiFiPreference.rawValue
|
||||||
|
|
@ -301,6 +302,13 @@ struct SettingsViewPlayer: View {
|
||||||
showDivider: true
|
showDivider: true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SettingsToggleRow(
|
||||||
|
icon: "forward.fill",
|
||||||
|
title: NSLocalizedString("Auto Skip Filler Episodes", comment: ""),
|
||||||
|
isOn: $autoSkipFillers,
|
||||||
|
showDivider: true
|
||||||
|
)
|
||||||
|
|
||||||
SettingsTextFieldRow(
|
SettingsTextFieldRow(
|
||||||
icon: "timer",
|
icon: "timer",
|
||||||
title: NSLocalizedString("Completion Percentage", comment: ""),
|
title: NSLocalizedString("Completion Percentage", comment: ""),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue