2
0
Эх сурвалжийг харах

Merge pull request #1500 from onevcat/feature/avasset-image

Feature avasset image data provider
Wei Wang 5 жил өмнө
parent
commit
7c87a634d9

+ 56 - 0
Demo/Demo/Kingfisher-Demo/Base.lproj/Main.storyboard

@@ -371,6 +371,30 @@
                                             <segue destination="WgT-E0-jsn" kind="show" id="Sgu-eB-5JV"/>
                                         </connections>
                                     </tableViewCell>
+                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="2c2-O7-4OG">
+                                        <rect key="frame" x="0.0" y="468" width="414" height="44"/>
+                                        <autoresizingMask key="autoresizingMask"/>
+                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="2c2-O7-4OG" id="Mjl-hg-ebT">
+                                            <rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
+                                            <autoresizingMask key="autoresizingMask"/>
+                                            <subviews>
+                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="AVAsset Image Generator" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="I8e-FH-QjH">
+                                                    <rect key="frame" x="20" y="11.666666666666664" width="194.66666666666666" height="21"/>
+                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                                    <nil key="textColor"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                            </subviews>
+                                            <constraints>
+                                                <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="I8e-FH-QjH" secondAttribute="trailing" constant="20" symbolic="YES" id="KjO-7Q-h0u"/>
+                                                <constraint firstItem="I8e-FH-QjH" firstAttribute="leading" secondItem="Mjl-hg-ebT" secondAttribute="leading" constant="20" symbolic="YES" id="Nib-u8-NUb"/>
+                                                <constraint firstItem="I8e-FH-QjH" firstAttribute="centerY" secondItem="Mjl-hg-ebT" secondAttribute="centerY" id="s3P-9n-qo8"/>
+                                            </constraints>
+                                        </tableViewCellContentView>
+                                        <connections>
+                                            <segue destination="m5P-35-yHH" kind="show" id="iY4-PO-rZO"/>
+                                        </connections>
+                                    </tableViewCell>
                                 </cells>
                             </tableViewSection>
                         </sections>
@@ -828,6 +852,38 @@
             </objects>
             <point key="canvasLocation" x="2654" y="1061"/>
         </scene>
+        <!--Asset Image Generator View Controller-->
+        <scene sceneID="PCw-9j-oCu">
+            <objects>
+                <viewController id="m5P-35-yHH" customClass="AVAssetImageGeneratorViewController" customModule="Kingfisher_Demo" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="Hqg-bo-3DO">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="lTP-vt-DdF">
+                                <rect key="frame" x="87" y="94" width="240" height="240"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="240" id="PRd-E7-RiS"/>
+                                    <constraint firstAttribute="width" constant="240" id="sQj-Or-zGO"/>
+                                </constraints>
+                            </imageView>
+                        </subviews>
+                        <viewLayoutGuide key="safeArea" id="kpf-7x-2lo"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                        <constraints>
+                            <constraint firstItem="lTP-vt-DdF" firstAttribute="centerX" secondItem="kpf-7x-2lo" secondAttribute="centerX" id="2MT-0R-oyk"/>
+                            <constraint firstItem="lTP-vt-DdF" firstAttribute="top" secondItem="kpf-7x-2lo" secondAttribute="top" constant="50" id="mff-5v-1Z1"/>
+                        </constraints>
+                    </view>
+                    <navigationItem key="navigationItem" id="7AD-kL-ZIP"/>
+                    <connections>
+                        <outlet property="imageView" destination="lTP-vt-DdF" id="fk4-PV-mkj"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="Idc-Kb-7jc" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="1850.7246376811595" y="1735.5978260869567"/>
+        </scene>
     </scenes>
     <resources>
         <systemColor name="secondaryLabelColor">

+ 46 - 0
Demo/Demo/Kingfisher-Demo/ViewControllers/AVAssetImageGeneratorViewController.swift

@@ -0,0 +1,46 @@
+//
+//  AVAssetImageGeneratorViewController.swift
+//  Kingfisher
+//
+//  Created by onevcat on 2020/08/09.
+//
+//  Copyright (c) 2020 Wei Wang <onevcat@gmail.com>
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+
+import UIKit
+import AVKit
+import Kingfisher
+
+class AVAssetImageGeneratorViewController: UIViewController {
+    @IBOutlet weak var imageView: UIImageView!
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        let provider = AVAssetImageDataProvider(
+            assetURL: URL(string: "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1280_10MG.mp4")!,
+            seconds: 15.0
+        )
+
+        imageView.kf.setImage(with: provider) { r in
+            print(r)
+        }
+    }
+}

+ 4 - 0
Demo/Kingfisher-Demo.xcodeproj/project.pbxproj

@@ -47,6 +47,7 @@
 		D12EB83E24DD902300329EE1 /* TextAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12EB83D24DD902300329EE1 /* TextAttachmentViewController.swift */; };
 		D12EB84024DDB9E100329EE1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D12EB83F24DDB9E000329EE1 /* LaunchScreen.storyboard */; };
 		D1679A461C4E78B20020FD12 /* Kingfisher-watchOS-Demo Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D1679A451C4E78B20020FD12 /* Kingfisher-watchOS-Demo Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+		D16CC3D824E03FEA00F1A515 /* AVAssetImageGeneratorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16CC3D724E03FEA00F1A515 /* AVAssetImageGeneratorViewController.swift */; };
 		D1A1CCA321A1879600263AD8 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A1CCA221A1879600263AD8 /* MainViewController.swift */; };
 		D1A1CCA721A18A3200263AD8 /* UIViewController+KingfisherOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A1CCA621A18A3200263AD8 /* UIViewController+KingfisherOperation.swift */; };
 		D1A1CCA821A18A3200263AD8 /* UIViewController+KingfisherOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A1CCA621A18A3200263AD8 /* UIViewController+KingfisherOperation.swift */; };
@@ -200,6 +201,7 @@
 		D16218A4238EAA67004A1C6C /* Kingfisher-Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Kingfisher-Demo.entitlements"; sourceTree = "<group>"; };
 		D1679A391C4E78B20020FD12 /* Kingfisher-watchOS-Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Kingfisher-watchOS-Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 		D1679A451C4E78B20020FD12 /* Kingfisher-watchOS-Demo Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Kingfisher-watchOS-Demo Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
+		D16CC3D724E03FEA00F1A515 /* AVAssetImageGeneratorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVAssetImageGeneratorViewController.swift; sourceTree = "<group>"; };
 		D1A1CCA221A1879600263AD8 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
 		D1A1CCA621A18A3200263AD8 /* UIViewController+KingfisherOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+KingfisherOperation.swift"; sourceTree = "<group>"; };
 		D1CE1BCF21A1AFA300419000 /* TransitionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionViewController.swift; sourceTree = "<group>"; };
@@ -389,6 +391,7 @@
 				D1E4CF5321BACBA6004D029D /* ImageDataProviderCollectionViewController.swift */,
 				C959EEE522874DC600467A10 /* ProgressiveJPEGViewController.swift */,
 				D12EB83D24DD902300329EE1 /* TextAttachmentViewController.swift */,
+				D16CC3D724E03FEA00F1A515 /* AVAssetImageGeneratorViewController.swift */,
 			);
 			path = ViewControllers;
 			sourceTree = "<group>";
@@ -732,6 +735,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				D16CC3D824E03FEA00F1A515 /* AVAssetImageGeneratorViewController.swift in Sources */,
 				C959EEE622874DC600467A10 /* ProgressiveJPEGViewController.swift in Sources */,
 				D1CE1BD321A1B45A00419000 /* ImageLoader.swift in Sources */,
 				D12EB83E24DD902300329EE1 /* TextAttachmentViewController.swift in Sources */,

+ 4 - 0
Kingfisher.xcodeproj/project.pbxproj

@@ -61,6 +61,7 @@
 		D12E0C581C47F23500AC98AD /* UIButtonExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12E0C4E1C47F23500AC98AD /* UIButtonExtensionTests.swift */; };
 		D12EB83C24DD8EFC00329EE1 /* NSTextAttachment+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12EB83B24DD8EFC00329EE1 /* NSTextAttachment+Kingfisher.swift */; };
 		D13646742165A1A100A33652 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13646732165A1A100A33652 /* Result.swift */; };
+		D16CC3D624E02E9500F1A515 /* AVAssetImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16CC3D524E02E9500F1A515 /* AVAssetImageDataProvider.swift */; };
 		D16FEA3A23078C63006E67D5 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = D16FE9F623078C63006E67D5 /* LICENSE */; };
 		D16FEA3B23078C63006E67D5 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = D16FE9F723078C63006E67D5 /* README.md */; };
 		D16FEA3C23078C63006E67D5 /* LSStubRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = D16FE9FA23078C63006E67D5 /* LSStubRequest.m */; };
@@ -187,6 +188,7 @@
 		D12E0C4E1C47F23500AC98AD /* UIButtonExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIButtonExtensionTests.swift; sourceTree = "<group>"; };
 		D12EB83B24DD8EFC00329EE1 /* NSTextAttachment+Kingfisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextAttachment+Kingfisher.swift"; sourceTree = "<group>"; };
 		D13646732165A1A100A33652 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
+		D16CC3D524E02E9500F1A515 /* AVAssetImageDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVAssetImageDataProvider.swift; sourceTree = "<group>"; };
 		D16FE9F623078C63006E67D5 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
 		D16FE9F723078C63006E67D5 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
 		D16FE9FA23078C63006E67D5 /* LSStubRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LSStubRequest.m; sourceTree = "<group>"; };
@@ -627,6 +629,7 @@
 				D1A1CC99219FAB4B00263AD8 /* Source.swift */,
 				D12AB69E215D2BB50013BA68 /* Resource.swift */,
 				D1E56444219B16330057AAE3 /* ImageDataProvider.swift */,
+				D16CC3D524E02E9500F1A515 /* AVAssetImageDataProvider.swift */,
 			);
 			path = ImageSource;
 			sourceTree = "<group>";
@@ -813,6 +816,7 @@
 				D12AB6E4215D2BB50013BA68 /* Placeholder.swift in Sources */,
 				4B46CC6921744AC500D90C4A /* DiskStorage.swift in Sources */,
 				4B46CC5F217449C600D90C4A /* MemoryStorage.swift in Sources */,
+				D16CC3D624E02E9500F1A515 /* AVAssetImageDataProvider.swift in Sources */,
 				D1839845216E333E003927D3 /* Delegate.swift in Sources */,
 				D12AB6D8215D2BB50013BA68 /* ImageTransition.swift in Sources */,
 				D1A37BE8215D365A009B39B7 /* ExtensionHelpers.swift in Sources */,

+ 137 - 0
Sources/General/ImageSource/AVAssetImageDataProvider.swift

@@ -0,0 +1,137 @@
+//
+//  AVAssetImageDataProvider.swift
+//  Kingfisher
+//
+//  Created by onevcat on 2020/08/09.
+//
+//  Copyright (c) 2020 Wei Wang <onevcat@gmail.com>
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+
+#if !os(watchOS)
+
+import Foundation
+import AVKit
+
+#if canImport(MobileCoreServices)
+import MobileCoreServices
+#else
+import CoreServices
+#endif
+
+/// A data provider to provide thumbnail data from a given AVKit asset.
+public struct AVAssetImageDataProvider: ImageDataProvider {
+
+    /// The possible error might be caused by the `AVAssetImageDataProvider`.
+    /// - userCancelled: The data provider process is cancelled.
+    /// - invalidImage: The retrieved image is invalid.
+    public enum AVAssetImageDataProviderError: Error {
+        case userCancelled
+        case invalidImage(_ image: CGImage?)
+    }
+
+    /// The asset image generator bound to `self`.
+    public let assetImageGenerator: AVAssetImageGenerator
+
+    /// The time at which the image should be generate in the asset.
+    public let time: CMTime
+
+    private var internalKey: String {
+        return (assetImageGenerator.asset as? AVURLAsset)?.url.absoluteString ?? UUID().uuidString
+    }
+
+    /// The cache key used by `self`.
+    public var cacheKey: String {
+        return "\(internalKey)_\(time.seconds)"
+    }
+
+    /// Creates an asset image data provider.
+    /// - Parameters:
+    ///   - assetImageGenerator: The asset image generator controls data providing behaviors.
+    ///   - time: At which time in the asset the image should be generated.
+    public init(assetImageGenerator: AVAssetImageGenerator, time: CMTime) {
+        self.assetImageGenerator = assetImageGenerator
+        self.time = time
+    }
+
+    /// Creates an asset image data provider.
+    /// - Parameters:
+    ///   - assetURL: The URL of asset for providing image data.
+    ///   - time: At which time in the asset the image should be generated.
+    ///
+    /// This method uses `assetURL` to create an `AVAssetImageGenerator` object and calls
+    /// the `init(assetImageGenerator:time:)` initializer.
+    ///
+    public init(assetURL: URL, time: CMTime) {
+        let asset = AVAsset(url: assetURL)
+        let generator = AVAssetImageGenerator(asset: asset)
+        self.init(assetImageGenerator: generator, time: time)
+    }
+
+    /// Creates an asset image data provider.
+    ///
+    /// - Parameters:
+    ///   - assetURL: The URL of asset for providing image data.
+    ///   - seconds: At which time in seconds in the asset the image should be generated.
+    ///
+    /// This method uses `assetURL` to create an `AVAssetImageGenerator` object, uses `seconds` to create a `CMTime`,
+    /// and calls the `init(assetImageGenerator:time:)` initializer.
+    ///
+    public init(assetURL: URL, seconds: TimeInterval) {
+        let time = CMTime(seconds: seconds, preferredTimescale: 600)
+        self.init(assetURL: assetURL, time: time)
+    }
+
+    public func data(handler: @escaping (Result<Data, Error>) -> Void) {
+        assetImageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: time)]) {
+            (requestedTime, image, imageTime, result, error) in
+            if let error = error {
+                handler(.failure(error))
+                return
+            }
+
+            if result == .cancelled {
+                handler(.failure(AVAssetImageDataProviderError.userCancelled))
+                return
+            }
+
+            guard let cgImage = image, let data = cgImage.jpegData else {
+                handler(.failure(AVAssetImageDataProviderError.invalidImage(image)))
+                return
+            }
+
+            handler(.success(data))
+        }
+    }
+}
+
+extension CGImage {
+    var jpegData: Data? {
+        guard let mutableData = CFDataCreateMutable(nil, 0),
+              let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil)
+        else {
+            return nil
+        }
+        CGImageDestinationAddImage(destination, self, nil)
+        guard CGImageDestinationFinalize(destination) else { return nil }
+        return mutableData as Data
+    }
+}
+
+#endif