From 5c245502288ed9e22f6a34d7de299ec32b35656c Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Fri, 12 Nov 2021 22:38:07 +0900 Subject: [PATCH] Change to utilize image API Change the url image format to webp --- JellyfinPlayer.xcodeproj/project.pbxproj | 16 ++- .../Landscape/ItemLandscapeMainView.swift | 2 +- .../BaseItemDtoExtensions.swift | 118 +++++++++++------- .../BaseItemPersonExtensions.swift | 26 ++-- Shared/Extensions/URLExtensions.swift | 26 ++++ 5 files changed, 125 insertions(+), 63 deletions(-) create mode 100644 Shared/Extensions/URLExtensions.swift diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index eec8530e..2b4d2ddb 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -199,6 +199,9 @@ 62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */; }; 62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */; }; 62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */; }; + 62E1DCC3273CE19800C9AE76 /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */; }; + 62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */; }; + 62E1DCC5273CE19800C9AE76 /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */; }; 62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */; }; 62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */; }; 62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */; }; @@ -534,6 +537,7 @@ 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerCoodinator.swift; sourceTree = ""; }; 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCoordinator.swift; sourceTree = ""; }; 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListCoordinator.swift; sourceTree = ""; }; + 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLExtensions.swift; sourceTree = ""; }; 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaViewModel.swift; sourceTree = ""; }; 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchViewModel.swift; sourceTree = ""; }; 62E632DF267D30CA0063E547 /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.swift; sourceTree = ""; }; @@ -1059,6 +1063,7 @@ isa = PBXGroup; children = ( 5389277B263CC3DB0035E14B /* BlurHashDecode.swift */, + 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */, 6267B3D526710B8900A7371D /* CollectionExtensions.swift */, E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */, 6267B3D92671138200A7371D /* ImageExtensions.swift */, @@ -1818,6 +1823,7 @@ E1AD104E26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */, 62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */, 536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */, + 62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */, 5310695B2684E7EE00CFFDBA /* AudioView.swift in Sources */, 5398514726B64E4100101B49 /* SearchBarView.swift in Sources */, 091B5A8D268315D400D78B61 /* ServerDiscovery.swift in Sources */, @@ -1977,6 +1983,7 @@ 535870AD2669D8DD00D05A09 /* Typings.swift in Sources */, E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */, E13DD3D5271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */, + 62E1DCC3273CE19800C9AE76 /* URLExtensions.swift in Sources */, C4BE0769271FC164003F4AD1 /* TVLibrariesView.swift in Sources */, E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */, 6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */, @@ -2025,6 +2032,7 @@ E13DD3D7271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */, E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */, E13DD3CA27164B80009D4DAF /* SwiftfinStore.swift in Sources */, + 62E1DCC5273CE19800C9AE76 /* URLExtensions.swift in Sources */, 62EC353226766849000E9F2D /* SessionManager.swift in Sources */, 536D3D79267BD5D00004248C /* ViewModel.swift in Sources */, E1D4BF8C2719F39F00A11E64 /* AppAppearance.swift in Sources */, @@ -2365,7 +2373,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 4BHXT8RHFR; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2402,7 +2410,7 @@ CURRENT_PROJECT_VERSION = 66; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 4BHXT8RHFR; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2433,7 +2441,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 4BHXT8RHFR; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2460,7 +2468,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 4BHXT8RHFR; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/JellyfinPlayer/Views/ItemView/Landscape/ItemLandscapeMainView.swift b/JellyfinPlayer/Views/ItemView/Landscape/ItemLandscapeMainView.swift index 22390b34..91fcf5d7 100644 --- a/JellyfinPlayer/Views/ItemView/Landscape/ItemLandscapeMainView.swift +++ b/JellyfinPlayer/Views/ItemView/Landscape/ItemLandscapeMainView.swift @@ -92,7 +92,7 @@ struct ItemLandscapeMainView: View { bh: viewModel.item.getBackdropImageBlurHash()) .opacity(0.3) .edgesIgnoringSafeArea(.all) - .blur(radius: 4) + .blur(radius: 8) // iPadOS is making the view go all the way to the edge. // We have to accomodate this here diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift index 95036a2f..4d52a410 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift @@ -1,11 +1,11 @@ // - /* - * SwiftFin is subject to the terms of the Mozilla Public - * License, v2.0. If a copy of the MPL was not distributed with this - * file, you can obtain one at https://mozilla.org/MPL/2.0/. - * - * Copyright 2021 Aiden Vigue & Jellyfin Contributors - */ +/* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ import Foundation import JellyfinAPI @@ -14,35 +14,48 @@ import UIKit // 001fC^ = dark grey plain blurhash public extension BaseItemDto { - // MARK: Images func getSeriesBackdropImageBlurHash() -> String { - let rawImgURL = getSeriesBackdropImage(maxWidth: 1).absoluteString - let imgTag = rawImgURL.components(separatedBy: "&tag=")[1] + let imgURL = getSeriesBackdropImage(maxWidth: 1) + guard let imgTag = imgURL.queryParameters?["tag"], + let hash = imageBlurHashes?.backdrop?[imgTag] + else { + return "001fC^" + } - return imageBlurHashes?.backdrop?[imgTag] ?? "001fC^" + return hash } func getSeriesPrimaryImageBlurHash() -> String { - let rawImgURL = getSeriesPrimaryImage(maxWidth: 1).absoluteString - let imgTag = rawImgURL.components(separatedBy: "&tag=")[1] + let imgURL = getSeriesPrimaryImage(maxWidth: 1) + guard let imgTag = imgURL.queryParameters?["tag"], + let hash = imageBlurHashes?.primary?[imgTag] + else { + return "001fC^" + } - return imageBlurHashes?.primary?[imgTag] ?? "001fC^" + return hash } func getPrimaryImageBlurHash() -> String { - let rawImgURL = getPrimaryImage(maxWidth: 1).absoluteString - let imgTag = rawImgURL.components(separatedBy: "&tag=")[1] + let imgURL = getPrimaryImage(maxWidth: 1) + guard let imgTag = imgURL.queryParameters?["tag"], + let hash = imageBlurHashes?.primary?[imgTag] + else { + return "001fC^" + } - return imageBlurHashes?.primary?[imgTag] ?? "001fC^" + return hash } func getBackdropImageBlurHash() -> String { - let rawImgURL = getBackdropImage(maxWidth: 1).absoluteString - let imgTag = rawImgURL.components(separatedBy: "&tag=")[1] + let imgURL = getBackdropImage(maxWidth: 1) + guard let imgTag = imgURL.queryParameters?["tag"] else { + return "001fC^" + } - if rawImgURL.contains("Backdrop") { + if imgURL.queryParameters?[ImageType.backdrop.rawValue] == nil { return imageBlurHashes?.backdrop?[imgTag] ?? "001fC^" } else { return imageBlurHashes?.primary?[imgTag] ?? "001fC^" @@ -50,31 +63,34 @@ public extension BaseItemDto { } func getBackdropImage(maxWidth: Int) -> URL { - var imageType = "" - var imageTag = "" + var imageType = ImageType.backdrop + var imageTag: String? var imageItemId = id ?? "" if primaryImageAspectRatio ?? 0.0 < 1.0 { - imageType = "Backdrop" if !(backdropImageTags?.isEmpty ?? true) { - imageTag = (backdropImageTags ?? [""])[0] + imageTag = backdropImageTags?.first } } else { - imageType = "Primary" - imageTag = imageTags?["Primary"] ?? "" + imageType = .primary + imageTag = imageTags?[ImageType.primary.rawValue] ?? "" } - if imageTag == "" || imageItemId == "" { - imageType = "Backdrop" + if imageTag == nil || imageItemId.isEmpty { if !(parentBackdropImageTags?.isEmpty ?? true) { - imageTag = (parentBackdropImageTags ?? [""])[0] + imageTag = parentBackdropImageTags?.first imageItemId = parentBackdropItemId ?? "" } } let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = - "\(SessionManager.main.currentLogin.server.uri)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)" + + let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: imageItemId, + imageType: imageType, + maxWidth: Int(x), + quality: 85, + tag: imageTag, + format: .webp).URLString return URL(string: urlString)! } @@ -86,39 +102,45 @@ public extension BaseItemDto { } func getSeriesBackdropImage(maxWidth: Int) -> URL { - let imageType = "Backdrop" - let imageTag = (parentBackdropImageTags ?? [""])[0] - let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = - "\(SessionManager.main.currentLogin.server.uri)/Items/\(parentBackdropItemId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)" + let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: parentBackdropItemId ?? "", + imageType: .backdrop, + maxWidth: Int(x), + quality: 85, + tag: parentBackdropImageTags?.first, + format: .webp).URLString return URL(string: urlString)! } func getSeriesPrimaryImage(maxWidth: Int) -> URL { - let imageType = "Primary" - let imageTag = seriesPrimaryImageTag ?? "" let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = - "\(SessionManager.main.currentLogin.server.uri)/Items/\(seriesId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)" + let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: seriesId ?? "", + imageType: .primary, + maxWidth: Int(x), + quality: 85, + tag: seriesPrimaryImageTag, + format: .webp).URLString return URL(string: urlString)! } func getPrimaryImage(maxWidth: Int) -> URL { - let imageType = "Primary" - var imageTag = imageTags?["Primary"] ?? "" + let imageType = ImageType.primary + var imageTag = imageTags?[ImageType.primary.rawValue] ?? "" var imageItemId = id ?? "" - if imageTag == "" || imageItemId == "" { + if imageTag.isEmpty || imageItemId.isEmpty { imageTag = seriesPrimaryImageTag ?? "" imageItemId = seriesId ?? "" } let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = - "\(SessionManager.main.currentLogin.server.uri)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)" - // print(urlString) + let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: imageItemId, + imageType: imageType, + maxWidth: Int(x), + quality: 85, + tag: imageTag, + format: .webp).URLString return URL(string: urlString)! } @@ -174,14 +196,14 @@ public extension BaseItemDto { } var itemType: ItemType { - guard let originalType = self.type, let knownType = ItemType(rawValue: originalType) else { return .unknown } + guard let originalType = type, let knownType = ItemType(rawValue: originalType) else { return .unknown } return knownType } // MARK: PortraitHeaderViewURL func portraitHeaderViewURL(maxWidth: Int) -> URL { - switch self.itemType { + switch itemType { case .movie, .season, .series: return getPrimaryImage(maxWidth: maxWidth) case .episode: diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift index 007ed999..23770cc4 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift @@ -10,23 +10,29 @@ import JellyfinAPI import UIKit extension BaseItemPerson { - + // MARK: Get Image func getImage(baseURL: String, maxWidth: Int) -> URL { - let imageType = "Primary" - let imageTag = primaryImageTag ?? "" - let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - - let urlString = "\(baseURL)/Items/\(id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)" + + let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: id ?? "", + imageType: .primary, + maxWidth: Int(x), + quality: 85, + tag: primaryImageTag, + format: .webp).URLString return URL(string: urlString)! } - + func getBlurHash() -> String { - let rawImgURL = getImage(baseURL: "", maxWidth: 1).absoluteString - let imgTag = rawImgURL.components(separatedBy: "&tag=")[1] + let imgURL = getImage(baseURL: "", maxWidth: 1) + guard let imgTag = imgURL.queryParameters?["tag"], + let hash = imageBlurHashes?.primary?[imgTag] + else { + return "001fC^" + } - return imageBlurHashes?.primary?[imgTag] ?? "001fC^" + return hash } // MARK: First Role diff --git a/Shared/Extensions/URLExtensions.swift b/Shared/Extensions/URLExtensions.swift new file mode 100644 index 00000000..ec7ea5d2 --- /dev/null +++ b/Shared/Extensions/URLExtensions.swift @@ -0,0 +1,26 @@ +// +/* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Foundation + +public extension URL { + /// Dictionary of the URL's query parameters + var queryParameters: [String: String]? { + guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false), + let queryItems = components.queryItems else { return nil } + + var items: [String: String] = [:] + + for queryItem in queryItems { + items[queryItem.name] = queryItem.value + } + + return items + } +}