TMDB thanks to @qooode

Co-Authored-By: qooode <71751652+qooode@users.noreply.github.com>
This commit is contained in:
Francesco 2025-06-01 17:39:51 +02:00
parent 62802fd30e
commit 99a5c6303c
2 changed files with 116 additions and 78 deletions

View file

@ -69,12 +69,18 @@ struct EpisodeCell: View {
}
}
let tmdbID: Int?
let seasonNumber: Int?
init(episodeIndex: Int, episode: String, episodeID: Int, progress: Double,
itemID: Int, totalEpisodes: Int? = nil, defaultBannerImage: String = "",
module: ScrapingModule, parentTitle: String, showPosterURL: String? = nil,
isMultiSelectMode: Bool = false, isSelected: Bool = false,
onSelectionChanged: ((Bool) -> Void)? = nil,
onTap: @escaping (String) -> Void, onMarkAllPrevious: @escaping () -> Void) {
onTap: @escaping (String) -> Void, onMarkAllPrevious: @escaping () -> Void,
tmdbID: Int? = nil,
seasonNumber: Int? = nil
) {
self.episodeIndex = episodeIndex
self.episode = episode
self.episodeID = episodeID
@ -99,6 +105,8 @@ struct EpisodeCell: View {
self.onSelectionChanged = onSelectionChanged
self.onTap = onTap
self.onMarkAllPrevious = onMarkAllPrevious
self.tmdbID = tmdbID
self.seasonNumber = seasonNumber
}
var body: some View {
@ -211,13 +219,14 @@ struct EpisodeCell: View {
.onAppear {
updateProgress()
updateDownloadStatus()
if let type = module.metadata.type?.lowercased(), type == "anime" {
if UserDefaults.standard.string(forKey: "metadataProviders") ?? "Anilist" == "AniList" {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
fetchAnimeEpisodeDetails()
}
} else {
isLoading = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
fetchTMDBEpisodeImage()
}
}
if let totalEpisodes = totalEpisodes, episodeID + 1 < totalEpisodes {
@ -232,12 +241,9 @@ struct EpisodeCell: View {
updateProgress()
}
.onChange(of: itemID) { newID in
// 1) Clear any cached title/image so that the UI shows the loading spinner:
loadedFromCache = false
isLoading = true
retryAttempts = maxRetryAttempts // reset retries if you want
// 2) Call the same logic you already use to pull per-episode info:
retryAttempts = maxRetryAttempts
fetchEpisodeDetails()
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("downloadProgressChanged"))) { _ in
@ -767,6 +773,34 @@ struct EpisodeCell: View {
}
}
private func fetchTMDBEpisodeImage() {
guard let tmdbID = tmdbID, let season = seasonNumber else { return }
let episodeNum = episodeID + 1
let urlString = "https://api.themoviedb.org/3/tv/\(tmdbID)/season/\(season)/episode/\(episodeNum)/images?api_key=738b4edd0a156cc126dc4a4b8aea4aca"
guard let url = URL(string: urlString) else { return }
URLSession.custom.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else { return }
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let stills = json["stills"] as? [[String: Any]],
let firstStill = stills.first,
let filePath = firstStill["file_path"] as? String {
let imageUrl = "https://image.tmdb.org/t/p/w780\(filePath)"
DispatchQueue.main.async {
self.episodeImageUrl = imageUrl
self.isLoading = false
}
}
} catch {
Logger.shared.log("Failed to parse TMDB episode image response: \(error.localizedDescription)", type: "Error")
DispatchQueue.main.async {
self.isLoading = false
}
}
}.resume()
}
private func calculateMaxSwipeDistance() -> CGFloat {
var buttonCount = 1

View file

@ -280,7 +280,7 @@ struct MediaInfoView: View {
}
.onAppear {
UIScrollView.appearance().bounces = false
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarTitle("")
.navigationViewStyle(StackNavigationViewStyle())
@ -335,7 +335,7 @@ struct MediaInfoView: View {
}
playAndBookmarkSection
if episodeLinks.count == 1 {
VStack(spacing: 12) {
HStack(spacing: 12) {
@ -464,9 +464,9 @@ struct MediaInfoView: View {
.foregroundColor(.gray)
.padding(.vertical, 4)
}
Divider()
if let _ = customAniListID {
Button(action: {
customAniListID = nil
@ -665,6 +665,51 @@ struct MediaInfoView: View {
}
}
@ViewBuilder
private var flatEpisodeList: some View {
LazyVStack(spacing: 15) {
ForEach(episodeLinks.indices.filter { selectedRange.contains($0) }, id: \.self) { i in
let ep = episodeLinks[i]
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
let defaultBannerImageValue = getBannerImageBasedOnAppearance()
EpisodeCell(
episodeIndex: i,
episode: ep.href,
episodeID: ep.number - 1,
progress: progress,
itemID: itemID ?? 0,
totalEpisodes: episodeLinks.count,
defaultBannerImage: defaultBannerImageValue,
module: module,
parentTitle: title,
showPosterURL: imageUrl,
isMultiSelectMode: isMultiSelectMode,
isSelected: selectedEpisodes.contains(ep.number),
onSelectionChanged: { isSelected in
if isSelected {
selectedEpisodes.insert(ep.number)
} else {
selectedEpisodes.remove(ep.number)
}
},
onTap: { imageUrl in
episodeTapAction(ep: ep, imageUrl: imageUrl)
},
onMarkAllPrevious: {
markAllPreviousEpisodesInFlatList(ep: ep, index: i)
},
tmdbID: tmdbID,
seasonNumber: 1
)
.disabled(isFetchingEpisode)
}
}
}
@ViewBuilder
private var seasonsEpisodeList: some View {
let seasons = groupedEpisodes()
@ -702,9 +747,11 @@ struct MediaInfoView: View {
},
onMarkAllPrevious: {
markAllPreviousEpisodesAsWatched(ep: ep, inSeason: true)
}
},
tmdbID: tmdbID,
seasonNumber: selectedSeason + 1
)
.disabled(isFetchingEpisode)
.disabled(isFetchingEpisode)
}
}
} else {
@ -731,70 +778,6 @@ struct MediaInfoView: View {
}
}
private func markAllPreviousEpisodesAsWatched(ep: EpisodeLink, inSeason: Bool) {
let userDefaults = UserDefaults.standard
var updates = [String: Double]()
if inSeason {
let seasons = groupedEpisodes()
for ep2 in seasons[selectedSeason] where ep2.number < ep.number {
let href = ep2.href
updates["lastPlayedTime_\(href)"] = 99999999.0
updates["totalTime_\(href)"] = 99999999.0
}
for (key, value) in updates {
userDefaults.set(value, forKey: key)
}
userDefaults.synchronize()
Logger.shared.log("Marked episodes watched within season \(selectedSeason + 1) of \"\(title)\".", type: "General")
}
}
@ViewBuilder
private var flatEpisodeList: some View {
LazyVStack(spacing: 15) {
ForEach(episodeLinks.indices.filter { selectedRange.contains($0) }, id: \.self) { i in
let ep = episodeLinks[i]
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
let defaultBannerImageValue = getBannerImageBasedOnAppearance()
EpisodeCell(
episodeIndex: i,
episode: ep.href,
episodeID: ep.number - 1,
progress: progress,
itemID: itemID ?? 0,
totalEpisodes: episodeLinks.count,
defaultBannerImage: defaultBannerImageValue,
module: module,
parentTitle: title,
showPosterURL: imageUrl,
isMultiSelectMode: isMultiSelectMode,
isSelected: selectedEpisodes.contains(ep.number),
onSelectionChanged: { isSelected in
if isSelected {
selectedEpisodes.insert(ep.number)
} else {
selectedEpisodes.remove(ep.number)
}
},
onTap: { imageUrl in
episodeTapAction(ep: ep, imageUrl: imageUrl)
},
onMarkAllPrevious: {
markAllPreviousEpisodesInFlatList(ep: ep, index: i)
}
)
.disabled(isFetchingEpisode)
}
}
}
private func fetchMetadataIDIfNeeded() {
let provider = UserDefaults.standard.string(forKey: "metadataProviders") ?? "Anilist"
let cleaned = cleanTitle(title)
@ -822,6 +805,27 @@ struct MediaInfoView: View {
}
}
private func markAllPreviousEpisodesAsWatched(ep: EpisodeLink, inSeason: Bool) {
let userDefaults = UserDefaults.standard
var updates = [String: Double]()
if inSeason {
let seasons = groupedEpisodes()
for ep2 in seasons[selectedSeason] where ep2.number < ep.number {
let href = ep2.href
updates["lastPlayedTime_\(href)"] = 99999999.0
updates["totalTime_\(href)"] = 99999999.0
}
for (key, value) in updates {
userDefaults.set(value, forKey: key)
}
userDefaults.synchronize()
Logger.shared.log("Marked episodes watched within season \(selectedSeason + 1) of \"\(title)\".", type: "General")
}
}
private func markAllPreviousEpisodesInFlatList(ep: EpisodeLink, index: Int) {
let userDefaults = UserDefaults.standard
var updates = [String: Double]()