From 611802607e30bdf10a5c078ed3336d4c36f60d09 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Sun, 1 Jun 2025 23:01:34 +0200 Subject: [PATCH] bunch of stuffs --- Sora/Managers/ImagePrefetchManager.swift | 134 ----- Sora/Managers/ImageUpscaler.swift | 165 ------ Sora/Managers/PerformanceMonitor.swift | 510 ------------------ .../Tracking Services/TMDB/TMDB-FetchID.swift | 7 +- .../EpisodeCell/EpisodeCell.swift | 8 +- Sora/Views/MediaInfoView/MediaInfoView.swift | 2 - .../SettingsViewGeneral.swift | 2 +- Sulfur.xcodeproj/project.pbxproj | 14 +- 8 files changed, 5 insertions(+), 837 deletions(-) delete mode 100644 Sora/Managers/ImagePrefetchManager.swift delete mode 100644 Sora/Managers/ImageUpscaler.swift delete mode 100644 Sora/Managers/PerformanceMonitor.swift diff --git a/Sora/Managers/ImagePrefetchManager.swift b/Sora/Managers/ImagePrefetchManager.swift deleted file mode 100644 index a4d342c..0000000 --- a/Sora/Managers/ImagePrefetchManager.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// ImagePrefetchManager.swift -// Sora -// -// Created by doomsboygaming on 5/22/25 -// - -import Foundation -import Kingfisher -import UIKit - -/// Manager for image prefetching, caching, and optimization -class ImagePrefetchManager { - static let shared = ImagePrefetchManager() - - // Prefetcher for batch prefetching images - private let prefetcher = ImagePrefetcher( - urls: [], - options: [ - .processor(DownsamplingImageProcessor(size: CGSize(width: 100, height: 56))), - .scaleFactor(UIScreen.main.scale), - .cacheOriginalImage - ] - ) - - // Keep track of what's already prefetched to avoid duplication - private var prefetchedURLs = Set() - private let prefetchQueue = DispatchQueue(label: "com.sora.imagePrefetch", qos: .utility) - - init() { - // Set up KingfisherManager for optimal image loading - ImageCache.default.memoryStorage.config.totalCostLimit = 300 * 1024 * 1024 // 300MB - ImageCache.default.diskStorage.config.sizeLimit = 1000 * 1024 * 1024 // 1GB - ImageDownloader.default.downloadTimeout = 15.0 // 15 seconds - } - - /// Prefetch a batch of images - func prefetchImages(_ urls: [String]) { - prefetchQueue.async { [weak self] in - guard let self = self else { return } - - // Filter out already prefetched URLs and invalid URLs - let urlObjects = urls.compactMap { URL(string: $0) } - .filter { !self.prefetchedURLs.contains($0) } - - guard !urlObjects.isEmpty else { return } - - // Create a new prefetcher with the URLs and start it - let newPrefetcher = ImagePrefetcher( - urls: urlObjects, - options: [ - .processor(DownsamplingImageProcessor(size: CGSize(width: 100, height: 56))), - .scaleFactor(UIScreen.main.scale), - .cacheOriginalImage - ] - ) - newPrefetcher.start() - - // Track prefetched URLs - urlObjects.forEach { self.prefetchedURLs.insert($0) } - } - } - - /// Prefetch a single image - func prefetchImage(_ url: String) { - guard let urlObject = URL(string: url), - !prefetchedURLs.contains(urlObject) else { - return - } - - prefetchQueue.async { [weak self] in - guard let self = self else { return } - - // Create a new prefetcher with the URL and start it - let newPrefetcher = ImagePrefetcher( - urls: [urlObject], - options: [ - .processor(DownsamplingImageProcessor(size: CGSize(width: 100, height: 56))), - .scaleFactor(UIScreen.main.scale), - .cacheOriginalImage - ] - ) - newPrefetcher.start() - - // Track prefetched URL - self.prefetchedURLs.insert(urlObject) - } - } - - /// Prefetch episode images for a batch of episodes - func prefetchEpisodeImages(anilistId: Int, startEpisode: Int, count: Int) { - prefetchQueue.async { [weak self] in - guard let self = self else { return } - - // Get metadata for episodes in the range - for episodeNumber in startEpisode...(startEpisode + count) where episodeNumber > 0 { - EpisodeMetadataManager.shared.fetchMetadata(anilistId: anilistId, episodeNumber: episodeNumber) { result in - switch result { - case .success(let metadata): - self.prefetchImage(metadata.imageUrl) - case .failure: - break - } - } - } - } - } - - /// Clear prefetch queue and stop any ongoing prefetch operations - func cancelPrefetching() { - prefetcher.stop() - } -} - -// MARK: - KFImage Extension -extension KFImage { - /// Load an image with optimal settings for episode thumbnails - static func optimizedEpisodeThumbnail(url: URL?) -> KFImage { - return KFImage(url) - .setProcessor(DownsamplingImageProcessor(size: CGSize(width: 100, height: 56))) - .memoryCacheExpiration(.seconds(300)) - .cacheOriginalImage() - .fade(duration: 0.25) - .onProgress { _, _ in - // Track progress if needed - } - .onSuccess { _ in - // Success logger removed to reduce logs - } - .onFailure { error in - Logger.shared.log("Failed to load image: \(error)", type: "Error") - } - } -} \ No newline at end of file diff --git a/Sora/Managers/ImageUpscaler.swift b/Sora/Managers/ImageUpscaler.swift deleted file mode 100644 index 70643d8..0000000 --- a/Sora/Managers/ImageUpscaler.swift +++ /dev/null @@ -1,165 +0,0 @@ -// -// ImageUpscaler.swift -// Sulfur -// -// Created by seiike on 26/05/2025. -// - - -import UIKit -import CoreImage -import CoreImage.CIFilterBuiltins -import Vision -import CoreML -import Kingfisher - -public enum ImageUpscaler { - /// Lanczos interpolation + unsharp mask for sharper upscaling. - /// - Parameters: - /// - scale: The factor to upscale (e.g. 2.0 doubles width/height). - /// - sharpeningIntensity: The unsharp mask intensity (0...1). - /// - sharpeningRadius: The unsharp mask radius in pixels. - public static func lanczosProcessor( - scale: CGFloat, - sharpeningIntensity: Float = 0.7, - sharpeningRadius: Float = 2.0 - ) -> ImageProcessor { - return LanczosUpscaleProcessor( - scale: scale, - sharpeningIntensity: sharpeningIntensity, - sharpeningRadius: sharpeningRadius - ) - } - - public static func superResolutionProcessor(modelURL: URL) -> ImageProcessor { - return MLScaleProcessor(modelURL: modelURL) - } -} - -// MARK: - Lanczos + Unsharp Mask Processor -public struct LanczosUpscaleProcessor: ImageProcessor { - public let scale: CGFloat - public let sharpeningIntensity: Float - public let sharpeningRadius: Float - public var identifier: String { - "com.yourapp.lanczos_\(scale)_sharp_\(sharpeningIntensity)_\(sharpeningRadius)" - } - - public init( - scale: CGFloat, - sharpeningIntensity: Float = 0.7, - sharpeningRadius: Float = 2.0 - ) { - self.scale = scale - self.sharpeningIntensity = sharpeningIntensity - self.sharpeningRadius = sharpeningRadius - } - - public func process( - item: ImageProcessItem, - options: KingfisherParsedOptionsInfo - ) -> KFCrossPlatformImage? { - - let inputImage: KFCrossPlatformImage? - switch item { - case .image(let image): - inputImage = image - case .data(let data): - inputImage = KFCrossPlatformImage(data: data) - } - guard let uiImage = inputImage, - let cgImage = uiImage.cgImage else { - return nil - } - - let ciInput = CIImage(cgImage: cgImage) - - let scaleFilter = CIFilter.lanczosScaleTransform() - scaleFilter.inputImage = ciInput - scaleFilter.scale = Float(scale) - scaleFilter.aspectRatio = 1.0 - guard let scaledCI = scaleFilter.outputImage else { - return uiImage - } - - let unsharp = CIFilter.unsharpMask() - unsharp.inputImage = scaledCI - unsharp.intensity = sharpeningIntensity - unsharp.radius = sharpeningRadius - guard let sharpCI = unsharp.outputImage else { - return UIImage(ciImage: scaledCI) - } - - let context = CIContext(options: nil) - guard let outputCG = context.createCGImage(sharpCI, from: sharpCI.extent) else { - return UIImage(ciImage: sharpCI) - } - return KFCrossPlatformImage(cgImage: outputCG) - } -} - -// MARK: - Core ML Super-Resolution Processor -public struct MLScaleProcessor: ImageProcessor { - private let request: VNCoreMLRequest - private let ciContext = CIContext() - public let identifier: String - - public init(modelURL: URL) { - - self.identifier = "com.yourapp.ml_sr_\(modelURL.lastPathComponent)" - guard let mlModel = try? MLModel(contentsOf: modelURL), - let visionModel = try? VNCoreMLModel(for: mlModel) else { - fatalError("Failed to load Core ML model at \(modelURL)") - } - let req = VNCoreMLRequest(model: visionModel) - req.imageCropAndScaleOption = .scaleFill - self.request = req - } - - public func process( - item: ImageProcessItem, - options: KingfisherParsedOptionsInfo - ) -> KFCrossPlatformImage? { - - let inputImage: KFCrossPlatformImage? - switch item { - case .image(let image): - inputImage = image - case .data(let data): - inputImage = KFCrossPlatformImage(data: data) - } - guard let uiImage = inputImage, - let cgImage = uiImage.cgImage else { - return nil - } - - let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) - do { - try handler.perform([request]) - } catch { - print("[MLScaleProcessor] Vision error: \(error)") - return uiImage - } - guard let obs = request.results?.first as? VNPixelBufferObservation else { - return uiImage - } - - let ciOutput = CIImage(cvPixelBuffer: obs.pixelBuffer) - let rect = CGRect( - origin: .zero, - size: CGSize( - width: CVPixelBufferGetWidth(obs.pixelBuffer), - height: CVPixelBufferGetHeight(obs.pixelBuffer) - ) - ) - guard let finalCG = ciContext.createCGImage(ciOutput, from: rect) else { - return uiImage - } - return KFCrossPlatformImage(cgImage: finalCG) - } -} - -// the sweet spot (for mediainfoview poster) -// .setProcessor(ImageUpscaler.lanczosProcessor(scale: 3.2, -// sharpeningIntensity: 0.75, -// sharpeningRadius: 2.25)) diff --git a/Sora/Managers/PerformanceMonitor.swift b/Sora/Managers/PerformanceMonitor.swift deleted file mode 100644 index 3a45fae..0000000 --- a/Sora/Managers/PerformanceMonitor.swift +++ /dev/null @@ -1,510 +0,0 @@ -// -// PerformanceMonitor.swift -// Sora -// -// Created by doomsboygaming on 5/22/25 -// - -import Foundation -import SwiftUI -import Kingfisher -import QuartzCore - -/// Performance metrics tracking system with advanced jitter detection -class PerformanceMonitor: ObservableObject { - static let shared = PerformanceMonitor() - - // Published properties to allow UI observation - @Published private(set) var networkRequestCount: Int = 0 - @Published private(set) var cacheHitCount: Int = 0 - @Published private(set) var cacheMissCount: Int = 0 - @Published private(set) var averageLoadTime: TimeInterval = 0 - @Published private(set) var memoryUsage: UInt64 = 0 - @Published private(set) var diskUsage: UInt64 = 0 - @Published private(set) var isEnabled: Bool = false - - // Advanced performance metrics for jitter detection - @Published private(set) var currentFPS: Double = 60.0 - @Published private(set) var mainThreadBlocks: Int = 0 - @Published private(set) var memorySpikes: Int = 0 - @Published private(set) var cpuUsage: Double = 0.0 - @Published private(set) var jitterEvents: Int = 0 - - // Internal tracking properties - private var loadTimes: [TimeInterval] = [] - private var startTimes: [String: Date] = [:] - private var memoryTimer: Timer? - private var logTimer: Timer? - - // Advanced monitoring properties - private var displayLink: CADisplayLink? - private var frameCount: Int = 0 - private var lastFrameTime: CFTimeInterval = 0 - private var frameTimes: [CFTimeInterval] = [] - private var lastMemoryUsage: UInt64 = 0 - private var mainThreadOperations: [String: CFTimeInterval] = [:] - private var cpuTimer: Timer? - - // Thresholds for performance issues - private let mainThreadBlockingThreshold: TimeInterval = 0.016 // 16ms for 60fps - private let memorySpikeTreshold: UInt64 = 50 * 1024 * 1024 // 50MB spike - private let fpsThreshold: Double = 50.0 // Below 50fps is considered poor - - private init() { - // Default is off unless explicitly enabled - isEnabled = UserDefaults.standard.bool(forKey: "enablePerformanceMonitoring") - - // Setup memory monitoring if enabled - if isEnabled { - startMonitoring() - } - } - - // MARK: - Public Methods - - /// Enable or disable the performance monitoring - func setEnabled(_ enabled: Bool) { - isEnabled = enabled - UserDefaults.standard.set(enabled, forKey: "enablePerformanceMonitoring") - - if enabled { - startMonitoring() - } else { - stopMonitoring() - } - } - - /// Reset all tracked metrics - func resetMetrics() { - networkRequestCount = 0 - cacheHitCount = 0 - cacheMissCount = 0 - averageLoadTime = 0 - loadTimes = [] - startTimes = [:] - - // Reset advanced metrics - mainThreadBlocks = 0 - memorySpikes = 0 - jitterEvents = 0 - frameTimes = [] - frameCount = 0 - mainThreadOperations = [:] - - updateMemoryUsage() - - Logger.shared.log("Performance metrics reset", type: "Debug") - } - - /// Track a network request starting - func trackRequestStart(identifier: String) { - guard isEnabled else { return } - - networkRequestCount += 1 - startTimes[identifier] = Date() - } - - /// Track a network request completing - func trackRequestEnd(identifier: String) { - guard isEnabled, let startTime = startTimes[identifier] else { return } - - let endTime = Date() - let duration = endTime.timeIntervalSince(startTime) - loadTimes.append(duration) - - // Update average load time - if !loadTimes.isEmpty { - averageLoadTime = loadTimes.reduce(0, +) / Double(loadTimes.count) - } - - // Remove start time to avoid memory leaks - startTimes.removeValue(forKey: identifier) - } - - /// Track a cache hit - func trackCacheHit() { - guard isEnabled else { return } - cacheHitCount += 1 - } - - /// Track a cache miss - func trackCacheMiss() { - guard isEnabled else { return } - cacheMissCount += 1 - } - - // MARK: - Advanced Performance Monitoring - - /// Track the start of a main thread operation - func trackMainThreadOperationStart(operation: String) { - guard isEnabled else { return } - mainThreadOperations[operation] = CACurrentMediaTime() - } - - /// Track the end of a main thread operation and detect blocking - func trackMainThreadOperationEnd(operation: String) { - guard isEnabled, let startTime = mainThreadOperations[operation] else { return } - - let endTime = CACurrentMediaTime() - let duration = endTime - startTime - - if duration > mainThreadBlockingThreshold { - mainThreadBlocks += 1 - jitterEvents += 1 - - let durationMs = Int(duration * 1000) - Logger.shared.log("🚨 Main thread blocked for \(durationMs)ms during: \(operation)", type: "Performance") - } - - mainThreadOperations.removeValue(forKey: operation) - } - - /// Track memory spikes during downloads - func checkMemorySpike() { - guard isEnabled else { return } - - let currentMemory = getAppMemoryUsage() - - if lastMemoryUsage > 0 { - let spike = currentMemory > lastMemoryUsage ? currentMemory - lastMemoryUsage : 0 - - if spike > memorySpikeTreshold { - memorySpikes += 1 - jitterEvents += 1 - - let spikeSize = Double(spike) / (1024 * 1024) - Logger.shared.log("🚨 Memory spike detected: +\(String(format: "%.1f", spikeSize))MB", type: "Performance") - } - } - - lastMemoryUsage = currentMemory - memoryUsage = currentMemory - } - - /// Start frame rate monitoring - private func startFrameRateMonitoring() { - guard displayLink == nil else { return } - - displayLink = CADisplayLink(target: self, selector: #selector(frameCallback)) - displayLink?.add(to: .main, forMode: .common) - - frameCount = 0 - lastFrameTime = CACurrentMediaTime() - frameTimes = [] - } - - /// Stop frame rate monitoring - private func stopFrameRateMonitoring() { - displayLink?.invalidate() - displayLink = nil - } - - /// Frame callback for FPS monitoring - @objc private func frameCallback() { - let currentTime = CACurrentMediaTime() - - if lastFrameTime > 0 { - let frameDuration = currentTime - lastFrameTime - frameTimes.append(frameDuration) - - // Keep only last 60 frames for rolling average - if frameTimes.count > 60 { - frameTimes.removeFirst() - } - - // Calculate current FPS - if !frameTimes.isEmpty { - let averageFrameTime = frameTimes.reduce(0, +) / Double(frameTimes.count) - currentFPS = 1.0 / averageFrameTime - - // Detect FPS drops - if currentFPS < fpsThreshold { - jitterEvents += 1 - Logger.shared.log("🚨 FPS drop detected: \(String(format: "%.1f", currentFPS))fps", type: "Performance") - } - } - } - - lastFrameTime = currentTime - frameCount += 1 - } - - /// Get current CPU usage - private func getCPUUsage() -> Double { - var info = mach_task_basic_info() - var count = mach_msg_type_number_t(MemoryLayout.size) / 4 - - let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) { - $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { - task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count) - } - } - - if kerr == KERN_SUCCESS { - // This is a simplified CPU usage calculation - // For more accurate results, we'd need to track over time - return Double(info.user_time.seconds + info.system_time.seconds) - } else { - return 0.0 - } - } - - /// Get the current cache hit rate - var cacheHitRate: Double { - let total = cacheHitCount + cacheMissCount - guard total > 0 else { return 0 } - return Double(cacheHitCount) / Double(total) - } - - /// Log current performance metrics - func logMetrics() { - guard isEnabled else { return } - - checkMemorySpike() - - let hitRate = String(format: "%.1f%%", cacheHitRate * 100) - let avgLoad = String(format: "%.2f", averageLoadTime) - let memory = String(format: "%.1f MB", Double(memoryUsage) / (1024 * 1024)) - let disk = String(format: "%.1f MB", Double(diskUsage) / (1024 * 1024)) - let fps = String(format: "%.1f", currentFPS) - let cpu = String(format: "%.1f%%", cpuUsage) - - let metrics = """ - 📊 Performance Metrics Report: - ═══════════════════════════════ - Network & Cache: - - Network Requests: \(networkRequestCount) - - Cache Hit Rate: \(hitRate) (\(cacheHitCount)/\(cacheHitCount + cacheMissCount)) - - Average Load Time: \(avgLoad)s - - System Resources: - - Memory Usage: \(memory) - - Disk Usage: \(disk) - - CPU Usage: \(cpu) - - Performance Issues: - - Current FPS: \(fps) - - Main Thread Blocks: \(mainThreadBlocks) - - Memory Spikes: \(memorySpikes) - - Total Jitter Events: \(jitterEvents) - ═══════════════════════════════ - """ - - Logger.shared.log(metrics, type: "Performance") - - // Alert if performance is poor - if jitterEvents > 0 { - Logger.shared.log("⚠️ Performance issues detected! Check logs above for details.", type: "Warning") - } - } - - // MARK: - Private Methods - - private func startMonitoring() { - // Setup timer to update memory usage periodically and check for spikes - memoryTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] _ in - self?.checkMemorySpike() - } - - // Setup timer to log metrics periodically - logTimer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] _ in - self?.logMetrics() - } - - // Setup CPU monitoring timer - cpuTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] _ in - self?.cpuUsage = self?.getCPUUsage() ?? 0.0 - } - - // Make sure timers run even when scrolling - RunLoop.current.add(memoryTimer!, forMode: .common) - RunLoop.current.add(logTimer!, forMode: .common) - RunLoop.current.add(cpuTimer!, forMode: .common) - - // Start frame rate monitoring - startFrameRateMonitoring() - - Logger.shared.log("Advanced performance monitoring started - tracking FPS, main thread blocks, memory spikes", type: "Debug") - } - - private func stopMonitoring() { - memoryTimer?.invalidate() - memoryTimer = nil - - logTimer?.invalidate() - logTimer = nil - - cpuTimer?.invalidate() - cpuTimer = nil - - stopFrameRateMonitoring() - - Logger.shared.log("Performance monitoring stopped", type: "Debug") - } - - private func updateMemoryUsage() { - memoryUsage = getAppMemoryUsage() - diskUsage = getCacheDiskUsage() - } - - private func getAppMemoryUsage() -> UInt64 { - var info = mach_task_basic_info() - var count = mach_msg_type_number_t(MemoryLayout.size) / 4 - - let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) { - $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { - task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count) - } - } - - if kerr == KERN_SUCCESS { - return info.resident_size - } else { - return 0 - } - } - - private func getCacheDiskUsage() -> UInt64 { - // Try to get Kingfisher's disk cache size - let diskCache = ImageCache.default.diskStorage - - do { - let size = try diskCache.totalSize() - return UInt64(size) - } catch { - Logger.shared.log("Failed to get disk cache size: \(error)", type: "Error") - return 0 - } - } -} - -// MARK: - Extensions to integrate with managers - -extension EpisodeMetadataManager { - /// Integrate performance tracking - func trackFetchStart(anilistId: Int, episodeNumber: Int) { - let identifier = "metadata_\(anilistId)_\(episodeNumber)" - PerformanceMonitor.shared.trackRequestStart(identifier: identifier) - } - - func trackFetchEnd(anilistId: Int, episodeNumber: Int) { - let identifier = "metadata_\(anilistId)_\(episodeNumber)" - PerformanceMonitor.shared.trackRequestEnd(identifier: identifier) - } - - func trackCacheHit() { - PerformanceMonitor.shared.trackCacheHit() - } - - func trackCacheMiss() { - PerformanceMonitor.shared.trackCacheMiss() - } -} - -extension ImagePrefetchManager { - /// Integrate performance tracking - func trackImageLoadStart(url: String) { - let identifier = "image_\(url.hashValue)" - PerformanceMonitor.shared.trackRequestStart(identifier: identifier) - } - - func trackImageLoadEnd(url: String) { - let identifier = "image_\(url.hashValue)" - PerformanceMonitor.shared.trackRequestEnd(identifier: identifier) - } - - func trackImageCacheHit() { - PerformanceMonitor.shared.trackCacheHit() - } - - func trackImageCacheMiss() { - PerformanceMonitor.shared.trackCacheMiss() - } -} - -// MARK: - Debug View -struct PerformanceMetricsView: View { - @ObservedObject private var monitor = PerformanceMonitor.shared - @State private var isExpanded = false - - var body: some View { - VStack { - HStack { - Text("Performance Metrics") - .font(.headline) - - Spacer() - - Button(action: { - isExpanded.toggle() - }) { - Image(systemName: isExpanded ? "chevron.up" : "chevron.down") - } - } - .padding(.horizontal) - - if isExpanded { - VStack(alignment: .leading, spacing: 4) { - Text("Network Requests: \(monitor.networkRequestCount)") - Text("Cache Hit Rate: \(Int(monitor.cacheHitRate * 100))%") - Text("Avg Load Time: \(String(format: "%.2f", monitor.averageLoadTime))s") - Text("Memory: \(String(format: "%.1f MB", Double(monitor.memoryUsage) / (1024 * 1024)))") - - Divider() - - // Advanced metrics - Text("FPS: \(String(format: "%.1f", monitor.currentFPS))") - .foregroundColor(monitor.currentFPS < 50 ? .red : .primary) - Text("Main Thread Blocks: \(monitor.mainThreadBlocks)") - .foregroundColor(monitor.mainThreadBlocks > 0 ? .red : .primary) - Text("Memory Spikes: \(monitor.memorySpikes)") - .foregroundColor(monitor.memorySpikes > 0 ? .orange : .primary) - Text("Jitter Events: \(monitor.jitterEvents)") - .foregroundColor(monitor.jitterEvents > 0 ? .red : .primary) - - HStack { - Button(action: { - monitor.resetMetrics() - }) { - Text("Reset") - .font(.caption) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(4) - } - - Button(action: { - monitor.logMetrics() - }) { - Text("Log") - .font(.caption) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Color.green) - .foregroundColor(.white) - .cornerRadius(4) - } - - Toggle("", isOn: Binding( - get: { monitor.isEnabled }, - set: { monitor.setEnabled($0) } - )) - .labelsHidden() - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal) - .padding(.bottom, 8) - .background(Color.secondary.opacity(0.1)) - .cornerRadius(8) - .padding(.horizontal) - } - } - .padding(.vertical, 4) - .background(Color.secondary.opacity(0.05)) - .cornerRadius(8) - .padding(8) - } -} \ No newline at end of file diff --git a/Sora/Tracking Services/TMDB/TMDB-FetchID.swift b/Sora/Tracking Services/TMDB/TMDB-FetchID.swift index 818bc96..9ada479 100644 --- a/Sora/Tracking Services/TMDB/TMDB-FetchID.swift +++ b/Sora/Tracking Services/TMDB/TMDB-FetchID.swift @@ -23,9 +23,6 @@ class TMDBFetcher { let results: [TMDBResult] } - private let apiKey = "738b4edd0a156cc126dc4a4b8aea4aca" - private let session = URLSession.custom - func fetchBestMatchID(for title: String, completion: @escaping (Int?, MediaType?) -> Void) { let group = DispatchGroup() var bestResults: [(id: Int, score: Double, type: MediaType)] = [] @@ -48,13 +45,13 @@ class TMDBFetcher { private func fetchBestMatchID(for title: String, type: MediaType, completion: @escaping (Int?, Double?) -> Void) { let query = title.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" - let urlString = "https://api.themoviedb.org/3/search/\(type.rawValue)?api_key=\(apiKey)&query=\(query)" + let urlString = "https://api.themoviedb.org/3/search/\(type.rawValue)?api_key=738b4edd0a156cc126dc4a4b8aea4aca&query=\(query)" guard let url = URL(string: urlString) else { completion(nil, nil) return } - session.dataTask(with: url) { data, _, error in + URLSession.custom.dataTask(with: url) { data, _, error in guard let data = data, error == nil else { completion(nil, nil) return diff --git a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift index 4433986..c576bb2 100644 --- a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift +++ b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift @@ -779,8 +779,6 @@ struct EpisodeCell: View { let urlString = "https://api.themoviedb.org/3/tv/\(tmdbID)/season/\(season)/episode/\(episodeNum)?api_key=738b4edd0a156cc126dc4a4b8aea4aca" guard let url = URL(string: urlString) else { return } - let tmdbImageWidth = UserDefaults.standard.string(forKey: "tmdbImageWidth") ?? "original" - URLSession.custom.dataTask(with: url) { data, _, error in guard let data = data, error == nil else { return } do { @@ -789,11 +787,7 @@ struct EpisodeCell: View { let stillPath = json["still_path"] as? String let imageUrl: String if let stillPath = stillPath { - if tmdbImageWidth == "original" { - imageUrl = "https://image.tmdb.org/t/p/original\(stillPath)" - } else { - imageUrl = "https://image.tmdb.org/t/p/w\(tmdbImageWidth)\(stillPath)" - } + imageUrl = "https://image.tmdb.org/t/p/w300\(stillPath)" } else { imageUrl = "" } diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index e159a6c..bb2ceb9 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -212,14 +212,12 @@ struct MediaInfoView: View { .fill(Color.gray.opacity(0.3)) .shimmering() } - .setProcessor(ImageUpscaler.lanczosProcessor(scale: 3, sharpeningIntensity: 1.5, sharpeningRadius: 0.8)) .resizable() .aspectRatio(contentMode: .fill) .frame(width: UIScreen.main.bounds.width, height: 700) .clipped() KFImage(URL(string: imageUrl)) .placeholder { EmptyView() } - .setProcessor(ImageUpscaler.lanczosProcessor(scale: 3, sharpeningIntensity: 1.5, sharpeningRadius: 0.8)) .resizable() .aspectRatio(contentMode: .fill) .frame(width: UIScreen.main.bounds.width, height: 700) diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index c91bb09..9588545 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -236,7 +236,7 @@ struct SettingsViewGeneral: View { SettingsPickerRow( icon: "square.stack.3d.down.right", - title: "Thumbnails Width", + title: "Poster Quality", options: TMDBimageWidhtList, optionToString: { $0 }, selection: $TMDBimageWidht, diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index 2118699..cc5ca4a 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -83,7 +83,6 @@ 1E26E9E72DA9577900B9DC02 /* VolumeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26E9E62DA9577900B9DC02 /* VolumeSlider.swift */; }; 1E47859B2DEBC5960095BF2F /* AnilistMatchPopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E47859A2DEBC5960095BF2F /* AnilistMatchPopupView.swift */; }; 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; }; - 1EA64DCD2DE5030100AC14BC /* ImageUpscaler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EA64DCC2DE5030100AC14BC /* ImageUpscaler.swift */; }; 1EAC7A322D888BC50083984D /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */; }; 1EF5C3A92DB988E40032BF07 /* CommunityLib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EF5C3A82DB988D70032BF07 /* CommunityLib.swift */; }; 7205AEDB2DCCEF9500943F3F /* EpisodeMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7205AED72DCCEF9500943F3F /* EpisodeMetadata.swift */; }; @@ -98,9 +97,7 @@ 72443C7F2DC8038300A61321 /* SettingsViewDownloads.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72443C7E2DC8038300A61321 /* SettingsViewDownloads.swift */; }; 727220712DD642B100C2A4A2 /* JSController-StreamTypeDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7272206F2DD642B100C2A4A2 /* JSController-StreamTypeDownload.swift */; }; 727220722DD642B100C2A4A2 /* JSController+MP4Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = 727220702DD642B100C2A4A2 /* JSController+MP4Download.swift */; }; - 72AC3A012DD4DAEB00C60B96 /* ImagePrefetchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72AC39FE2DD4DAEA00C60B96 /* ImagePrefetchManager.swift */; }; 72AC3A022DD4DAEB00C60B96 /* EpisodeMetadataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72AC39FD2DD4DAEA00C60B96 /* EpisodeMetadataManager.swift */; }; - 72AC3A032DD4DAEB00C60B96 /* PerformanceMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72AC39FF2DD4DAEA00C60B96 /* PerformanceMonitor.swift */; }; 73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */; }; /* End PBXBuildFile section */ @@ -181,7 +178,6 @@ 1E26E9E62DA9577900B9DC02 /* VolumeSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeSlider.swift; sourceTree = ""; }; 1E47859A2DEBC5960095BF2F /* AnilistMatchPopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnilistMatchPopupView.swift; sourceTree = ""; }; 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = ""; }; - 1EA64DCC2DE5030100AC14BC /* ImageUpscaler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUpscaler.swift; sourceTree = ""; }; 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; 1EF5C3A82DB988D70032BF07 /* CommunityLib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityLib.swift; sourceTree = ""; }; 7205AED72DCCEF9500943F3F /* EpisodeMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeMetadata.swift; sourceTree = ""; }; @@ -197,8 +193,6 @@ 7272206F2DD642B100C2A4A2 /* JSController-StreamTypeDownload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSController-StreamTypeDownload.swift"; sourceTree = ""; }; 727220702DD642B100C2A4A2 /* JSController+MP4Download.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSController+MP4Download.swift"; sourceTree = ""; }; 72AC39FD2DD4DAEA00C60B96 /* EpisodeMetadataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeMetadataManager.swift; sourceTree = ""; }; - 72AC39FE2DD4DAEA00C60B96 /* ImagePrefetchManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePrefetchManager.swift; sourceTree = ""; }; - 72AC39FF2DD4DAEA00C60B96 /* PerformanceMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceMonitor.swift; sourceTree = ""; }; 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JavaScriptCore+Extensions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -327,9 +321,9 @@ 133D7C6C2D2BE2500075467E /* Sora */ = { isa = PBXGroup; children = ( - 72AC3A002DD4DAEA00C60B96 /* Managers */, 130C6BF82D53A4C200DC1432 /* Sora.entitlements */, 13DC0C412D2EC9BA00D0F966 /* Info.plist */, + 72AC3A002DD4DAEA00C60B96 /* Managers */, 13103E802D589D6C000F0673 /* Tracking Services */, 133D7C852D2BE2640075467E /* Utils */, 133D7C7B2D2BE2630075467E /* Views */, @@ -629,10 +623,7 @@ 72AC3A002DD4DAEA00C60B96 /* Managers */ = { isa = PBXGroup; children = ( - 1EA64DCC2DE5030100AC14BC /* ImageUpscaler.swift */, 72AC39FD2DD4DAEA00C60B96 /* EpisodeMetadataManager.swift */, - 72AC39FE2DD4DAEA00C60B96 /* ImagePrefetchManager.swift */, - 72AC39FF2DD4DAEA00C60B96 /* PerformanceMonitor.swift */, ); path = Managers; sourceTree = ""; @@ -788,15 +779,12 @@ 727220712DD642B100C2A4A2 /* JSController-StreamTypeDownload.swift in Sources */, 727220722DD642B100C2A4A2 /* JSController+MP4Download.swift in Sources */, 132FC5B32DE31DAE009A80F7 /* SettingsViewAlternateAppIconPicker.swift in Sources */, - 1EA64DCD2DE5030100AC14BC /* ImageUpscaler.swift in Sources */, 0402DA132DE7B5EC003BB42C /* SearchStateView.swift in Sources */, 0402DA142DE7B5EC003BB42C /* SearchResultsGrid.swift in Sources */, 0402DA152DE7B5EC003BB42C /* SearchComponents.swift in Sources */, 1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */, 04F08EDF2DE10C1D006B29D9 /* TabBar.swift in Sources */, - 72AC3A012DD4DAEB00C60B96 /* ImagePrefetchManager.swift in Sources */, 72AC3A022DD4DAEB00C60B96 /* EpisodeMetadataManager.swift in Sources */, - 72AC3A032DD4DAEB00C60B96 /* PerformanceMonitor.swift in Sources */, 72443C7F2DC8038300A61321 /* SettingsViewDownloads.swift in Sources */, 13DB46922D900BCE008CBC03 /* SettingsViewTrackers.swift in Sources */, 7222485F2DCBAA2C00CABE2D /* DownloadModels.swift in Sources */,