diff --git a/Aerial.xcodeproj/project.pbxproj b/Aerial.xcodeproj/project.pbxproj index ffa11ca1..ab91f496 100644 --- a/Aerial.xcodeproj/project.pbxproj +++ b/Aerial.xcodeproj/project.pbxproj @@ -3241,7 +3241,7 @@ CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 3.2.7beta7a1; + CURRENT_PROJECT_VERSION = 3.2.7beta8; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 3L54M5L5KK; ENABLE_HARDENED_RUNTIME = YES; @@ -3249,7 +3249,7 @@ INSTALL_PATH = "$(HOME)/Library/Screen Savers"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.2.7beta7a1; + MARKETING_VERSION = 3.2.7beta8; PRODUCT_BUNDLE_IDENTIFIER = com.johncoates.Aerial; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3270,7 +3270,7 @@ CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 3.2.7beta7a1; + CURRENT_PROJECT_VERSION = 3.2.7beta8; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 3L54M5L5KK; ENABLE_HARDENED_RUNTIME = YES; @@ -3278,7 +3278,7 @@ INSTALL_PATH = "$(HOME)/Library/Screen Savers"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.2.7beta7a1; + MARKETING_VERSION = 3.2.7beta8; OTHER_CODE_SIGN_FLAGS = "--timestamp"; PRODUCT_BUNDLE_IDENTIFIER = com.johncoates.Aerial; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Aerial/Source/Models/AerialVideo.swift b/Aerial/Source/Models/AerialVideo.swift index 53694332..39a4878f 100644 --- a/Aerial/Source/Models/AerialVideo.swift +++ b/Aerial/Source/Models/AerialVideo.swift @@ -50,7 +50,7 @@ final class AerialVideo: CustomStringConvertible, Equatable { // Returns the closest video we have in the manifests private func getClosestAvailable(wanted: VideoFormat) -> URL { - if urls[wanted] != "" { + if urls[wanted] != "" && urls[wanted] != nil { return getURL(string: urls[wanted]!) } else { // Fallback diff --git a/Aerial/Source/Models/Cache/PoiStringProvider.swift b/Aerial/Source/Models/Cache/PoiStringProvider.swift index 7311f7ec..9d06b77c 100644 --- a/Aerial/Source/Models/Cache/PoiStringProvider.swift +++ b/Aerial/Source/Models/Cache/PoiStringProvider.swift @@ -47,7 +47,7 @@ final class PoiStringProvider { private func loadBundle() { // Idle string bundle - var bundlePath = Cache.supportPath.appending("/tvOS 16") + var bundlePath = Cache.supportPath.appending("/macOS 14") if PrefsAdvanced.ciOverrideLanguage == "" { debugLog("Preferred languages : \(Locale.preferredLanguages)") @@ -83,7 +83,7 @@ final class PoiStringProvider { } if let sb = Bundle.init(path: bundlePath) { - let dictPath = Cache.supportPath.appending("/tvOS 16/TVIdleScreenStrings.bundle/en.lproj/Localizable.nocache.strings") + let dictPath = Cache.supportPath.appending("/macOS 14/TVIdleScreenStrings.bundle/en.lproj/Localizable.nocache.strings") // We could probably only work with that... if let sd = NSDictionary(contentsOfFile: dictPath) as? [String: String] { @@ -154,6 +154,12 @@ final class PoiStringProvider { (!video.communityPoi.isEmpty && !getPoiKeys(video: video).isEmpty) } + func getLocalizedNameKey(key: String) -> String { + guard ensureLoadedBundle() else { return "" } + + return stringBundle!.localizedString(forKey: key, value: "", table: "Localizable.nocache") + } + // MARK: - Community data // siftlint:disable:next cyclomatic_complexity private func getCommunityPathForLocale() -> String { diff --git a/Aerial/Source/Models/Downloads/DownloadManager.swift b/Aerial/Source/Models/Downloads/DownloadManager.swift index bf78cecc..aef18876 100644 --- a/Aerial/Source/Models/Downloads/DownloadManager.swift +++ b/Aerial/Source/Models/Downloads/DownloadManager.swift @@ -177,8 +177,8 @@ extension DownloadOperation: URLSessionTaskDelegate { FileHelpers.unTar(file: destinationDirectory.appending("/resources-16.tar"), atPath: destinationDirectory) } else if folder == "tvOS 12" { FileHelpers.unTar(file: destinationDirectory.appending("/resources.tar"), atPath: destinationDirectory) - } else if folder == "macOS 14b1" { - FileHelpers.unTar(file: destinationDirectory.appending("/resources-14-0-3.tar"), atPath: destinationDirectory) + } else if folder == "macOS 14" { + FileHelpers.unTar(file: destinationDirectory.appending("/resources-14-0-9.tar"), atPath: destinationDirectory) } debugLog("Finished downloading \(task.originalRequest!.url!.absoluteString)") diff --git a/Aerial/Source/Models/Sources/Source.swift b/Aerial/Source/Models/Sources/Source.swift index 14d9fb5e..b543a53d 100644 --- a/Aerial/Source/Models/Sources/Source.swift +++ b/Aerial/Source/Models/Sources/Source.swift @@ -12,7 +12,7 @@ import Foundation // 11 is similar to 12+, but does not include pointsOfInterests // 12/13 share a same format, and we use that format for local videos too enum SourceType: Int, Codable { - case local, tvOS10, tvOS11, tvOS12 + case local, tvOS10, tvOS11, tvOS12, macOS } enum SourceScene: String, Codable { @@ -29,7 +29,7 @@ struct Source: Codable { var isCachable: Bool var license: String var more: String - + func isEnabled() -> Bool { if PrefsVideos.enabledSources.keys.contains(name) { return PrefsVideos.enabledSources[name]! @@ -137,6 +137,8 @@ struct Source: Codable { return parseOldVideoManifest(jsondata) } else if name.starts(with: "tvOS 13") { return parseVideoManifest(jsondata) + getMissingVideos() // Oh, Victoria Harbour 2... + } else if name.starts(with: "macOS") { + return parseMacManifest(jsondata) } else { return parseVideoManifest(jsondata) } @@ -270,6 +272,20 @@ struct Source: Codable { return [] } } + + func getSubcategoryFor(_ asset: MacAsset, manifest: MacManifest) -> String { + for category in manifest.categories { + if category.subcategories != nil { + for subcategory in category.subcategories! { + if subcategory.id == asset.subcategories.first { + return PoiStringProvider.sharedInstance.getLocalizedNameKey(key:subcategory.localizedNameKey) + } + } + } + } + + return "Not found" + } func getSecondaryNameFor(_ asset: VideoAsset) -> String { let poiStringProvider = PoiStringProvider.sharedInstance @@ -281,6 +297,13 @@ struct Source: Codable { } } + func getSecondaryNameFor(_ asset: MacAsset) -> String { + let poiStringProvider = PoiStringProvider.sharedInstance + + return poiStringProvider.getLocalizedNameKey(key: asset.localizedNameKey) + } + + func getSceneFor(_ asset: VideoAsset) -> String { if let updatedScene = SourceInfo.getSceneForVideo(id: asset.id) { return updatedScene.rawValue.lowercased() @@ -289,6 +312,16 @@ struct Source: Codable { } } + func getSceneFor(_ asset: MacAsset) -> String { + if let updatedScene = SourceInfo.getSceneForVideo(id: asset.id) { + return updatedScene.rawValue.lowercased() + } else { + return "landscape" + } + } + + + // Generate URLs func urlsFor(_ asset: VideoAsset) -> [VideoFormat: String] { return [.v1080pH264: localizePath(asset.url1080H264), .v1080pHEVC: localizePath(asset.url1080SDR), @@ -298,6 +331,16 @@ struct Source: Codable { .v4KSDR240: localizePath(asset.url4KSDR240FPS) ] } + // Mac manifest only has 240 fps + func urlsFor(_ asset: MacAsset) -> [VideoFormat: String] { + return [.v1080pH264: "", + .v1080pHEVC: "", + .v1080pHDR: "", + .v4KHEVC: "", + .v4KHDR: "", + .v4KSDR240: localizePath(asset.url4KSDR240FPS) ] + } + func oldUrlsFor(_ asset: VideoAsset) -> [VideoFormat: String] { var url1080pHEVC = "" var url1080pHDR = "" @@ -393,9 +436,6 @@ struct Source: Codable { func parseVideoManifest(_ data: Data) -> [AerialVideo] { if let videoManifest = try? newJSONDecoder().decode(VideoManifest.self, from: data) { - // Let's save the manifest here - // manifest = videoManifest - var processedVideos: [AerialVideo] = [] for asset in videoManifest.assets { @@ -423,6 +463,37 @@ struct Source: Codable { errorLog("### Could not parse manifest data") return [] } + + func parseMacManifest(_ data: Data) -> [AerialVideo] { + if let videoManifest = try? newJSONDecoder().decode(MacManifest.self, from: data) { + var processedVideos: [AerialVideo] = [] + + for asset in videoManifest.assets { + let (isDupe, _) = SourceInfo.findDuplicate(id: asset.id, url1080pH264: "") + + if !isDupe { + let video = AerialVideo(id: asset.id, + name: getSubcategoryFor(asset, manifest: videoManifest), + secondaryName: getSecondaryNameFor(asset), + type: "video", + timeOfDay: "day", + scene: getSceneFor(asset), + urls: urlsFor(asset), + source: self, + poi: asset.pointsOfInterest, // ?? [:], + communityPoi: PoiStringProvider.sharedInstance.getCommunityPoi(id: asset.id)) + + processedVideos.append(video) + } + } + + return processedVideos + } + + errorLog("### Could not parse manifest data") + return [] + } + } // MARK: - VideoManifest @@ -472,3 +543,56 @@ struct VideoAsset: Codable { case type } } + +// MARK: - MACManifest +struct MacManifest: Codable { + let localizationVersion: LocalizationVersion + let categories: [SubcategoryElement] + let initialAssetCount: Int + let assets: [MacAsset] + let version: Int +} + +// MARK: - Asset +struct MacAsset: Codable { + let shotID: String + let previewImage: String + let localizedNameKey, accessibilityLabel: String + let preferredOrder: Int + let categories: [CategoryEnum] + let id: String + let subcategories: [String] + let pointsOfInterest: [String: String] + let url4KSDR240FPS: String + let includeInShuffle, showInTopLevel: Bool + let group: LocalizationVersion? + + enum CodingKeys: String, CodingKey { + case shotID, previewImage, localizedNameKey, accessibilityLabel, preferredOrder, categories, id, subcategories, pointsOfInterest + case url4KSDR240FPS = "url-4K-SDR-240FPS" + case includeInShuffle, showInTopLevel, group + } +} + +enum CategoryEnum: String, Codable { + case a33A55D9Edea4596A8506C10B54Fbbb5 = "A33A55D9-EDEA-4596-A850-6C10B54FBBB5" + case the55B7C95DCeaf4Fd8AdefF5Bc657D8F6D = "55B7C95D-CEAF-4FD8-ADEF-F5BC657D8F6D" + case the5Ef4117148624F93800CAd86Ce5E6891 = "5EF41171-4862-4F93-800C-AD86CE5E6891" + case the8Be8B5246Eae43F5A3E801Dcfa1Bcd4B = "8BE8B524-6EAE-43F5-A3E8-01DCFA1BCD4B" +} + +enum LocalizationVersion: String, Codable { + case the19J1 = "19J-1" + case the19K1 = "19K-1" + case the21J1 = "21J-1" +} + +// MARK: - SubcategoryElement +struct SubcategoryElement: Codable { + let subcategories: [SubcategoryElement]? + let localizedDescriptionKey, representativeAssetID: String + let previewImage: String + let id: String + let preferredOrder: Int + let localizedNameKey: String +} diff --git a/Aerial/Source/Models/Sources/SourceList.swift b/Aerial/Source/Models/Sources/SourceList.swift index 4e71079c..a96da377 100644 --- a/Aerial/Source/Models/Sources/SourceList.swift +++ b/Aerial/Source/Models/Sources/SourceList.swift @@ -16,10 +16,10 @@ struct SourceHeader { // swiftlint:disable:next type_body_length struct SourceList { // This is the current one until next fall - static let macOS14 = Source(name: "macOS 14b1", + static let macOS14 = Source(name: "macOS 14", description: "High framerate videos from macOS 14 Sonoma", - manifestUrl: "https://sylvan.apple.com/itunes-assets/Aerials126/v4/82/2e/34/822e344c-f5d2-878c-3d56-508d5b09ed61/resources-14-0-3.tar", - type: .tvOS12, + manifestUrl: "https://sylvan.apple.com/itunes-assets/Aerials126/v4/82/2e/34/822e344c-f5d2-878c-3d56-508d5b09ed61/resources-14-0-9.tar", + type: .macOS, scenes: [.nature, .city, .space, .sea], isCachable: true, license: "", @@ -63,16 +63,16 @@ struct SourceList { license: "", more: "")*/ - static let tvOS10 = Source(name: "tvOS 10", + /* static let tvOS10 = Source(name: "tvOS 10", description: "Apple TV screensavers from tvOS 10", manifestUrl: "http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/videos/entries.json", type: .tvOS10, scenes: [.nature, .city], isCachable: true, license: "", - more: "") + more: "")*/ - static var list: [Source] = [macOS14, tvOS16, tvOS13, tvOS10] + foundSources + static var list: [Source] = [macOS14, tvOS16, tvOS13] + foundSources // static var list: [Source] = foundSources // This is where the magic happens diff --git a/Aerial/Source/Views/AerialView+Player.swift b/Aerial/Source/Views/AerialView+Player.swift index 91e71b64..f5bedd2f 100644 --- a/Aerial/Source/Views/AerialView+Player.swift +++ b/Aerial/Source/Views/AerialView+Player.swift @@ -66,10 +66,15 @@ extension AerialView { // The layers for descriptions, clock, message layerManager.setupExtraLayers(layer: layer, frame: self.frame) + // Make sure we set the retinaness here + layerManager.setContentScale(scale: self.window?.backingScaleFactor ?? 1.0) // An extra layer to try and contravent a macOS graphics driver bug // This is useful on High Sierra+ on Intel Macs - setupGlitchWorkaroundLayer(layer: layer) + if #available(macOS 12.0, *) { + } else { + setupGlitchWorkaroundLayer(layer: layer) + } } // MARK: - AVPlayerItem Notifications