Created
April 16, 2019 07:15
-
-
Save ufo22940268/210eab0d58828da73534313942894a16 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// GifSizeEstimator.swift | |
// Gifer | |
// | |
// Created by Frank Cheng on 2019/4/15. | |
// Copyright © 2019 Frank Cheng. All rights reserved. | |
// | |
import Foundation | |
import AVKit | |
import ImageIO | |
import MobileCoreServices | |
extension URL { | |
var attributes: [FileAttributeKey : Any]? { | |
do { | |
return try FileManager.default.attributesOfItem(atPath: path) | |
} catch let error as NSError { | |
print("FileAttribute error: \(error)") | |
} | |
return nil | |
} | |
var fileSize: UInt64 { | |
return attributes?[.size] as? UInt64 ?? UInt64(0) | |
} | |
var fileSizeInMB: Double { | |
return Double(fileSize)/pow(1024, 2) | |
} | |
var fileSizeString: String { | |
return ByteCountFormatter.string(fromByteCount: Int64(fileSize), countStyle: .file) | |
} | |
var creationDate: Date? { | |
return attributes?[.creationDate] as? Date | |
} | |
} | |
class GifConfigCalibrator { | |
let options: GifGenerator.Options | |
var asset: AVAsset | |
var initialProcessConfig: GifProcessConfig | |
init(options: GifGenerator.Options, asset: AVAsset, processConfig: GifProcessConfig) { | |
self.options = options | |
self.asset = asset | |
self.initialProcessConfig = processConfig | |
} | |
var gifFilePath: URL? { | |
get { | |
let documentsDirectoryURL: URL? = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) | |
return documentsDirectoryURL?.appendingPathComponent("estimate_animated.gif") | |
} | |
} | |
func calibrateSize(under memoryInMB: Double, completion: @escaping (GifProcessConfig) -> Void) { | |
let group = DispatchGroup() | |
let possibleConfigs = Array(0..<10).reduce([GifProcessConfig](), {(ar, index) in | |
var ar = ar | |
if ar.isEmpty { | |
ar.append(initialProcessConfig) | |
} else { | |
if let reducedConfig = ar.last!.reduce() { | |
ar.append(reducedConfig) | |
} | |
} | |
return ar | |
}) | |
var validConfigs = [GifProcessConfig]() | |
for config in possibleConfigs { | |
group.enter() | |
getEstimateSize(processConfig: config) { (estimateSize) in | |
print("estimate size: \(estimateSize) config gif size: \(config.gifSize)") | |
if estimateSize < memoryInMB { | |
validConfigs.append(config) | |
} | |
group.leave() | |
} | |
} | |
group.notify(queue: .main) { | |
var finalConfig = validConfigs.max { $0.gifSize.width < $1.gifSize.width } | |
if finalConfig == nil { | |
print("using lowest config") | |
finalConfig = self.initialProcessConfig.lowestConfig | |
} | |
print("finalConfig: \(String(describing: finalConfig))") | |
completion(finalConfig!) | |
} | |
} | |
private func getEstimateSize(processConfig: GifProcessConfig, completion: @escaping (Double) -> Void) { | |
getSize(processConfig: processConfig, sample: true) { (sampleSize) in | |
let sliceCount = self.options.splitVideo(extractedImageCountPerSecond: processConfig.extractImageCountPerSecond).count | |
var estimateSize = sampleSize*Double(sliceCount) | |
//Increase estimate size. Because the estimate size will be smaller than real size for sometime. | |
estimateSize = estimateSize + estimateSize*0.2 | |
completion(estimateSize) | |
} | |
} | |
private func getSize(processConfig: GifProcessConfig, sample: Bool, completion: @escaping (Double) -> Void) { | |
var times = options.splitVideo(extractedImageCountPerSecond: processConfig.extractImageCountPerSecond) | |
if sample { | |
times = Array(times[0...0]) | |
} | |
generateGif(processConfig: processConfig, in: times) { file in | |
completion(file.fileSizeInMB) | |
} | |
} | |
func generateGif(processConfig: GifProcessConfig, in times: [CMTime], completion: @escaping (URL) -> Void) { | |
let times = times.map { NSValue(time: $0) } | |
let fileProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: 0]] as CFDictionary | |
let fileURL: URL? = self.gifFilePath | |
guard let url = fileURL as CFURL?, let destination = CGImageDestinationCreateWithURL(url, kUTTypeGIF, times.count, nil) else { fatalError() } | |
CGImageDestinationSetProperties(destination, fileProperties) | |
let generator: AVAssetImageGenerator = AVAssetImageGenerator(asset: asset) | |
generator.maximumSize = processConfig.gifSize | |
generator.generateCGImagesAsynchronously(forTimes: times) { (requestTime, cgImage, time, _, _) in | |
guard let cgImage = cgImage else { return } | |
let frameProperties: CFDictionary = [(kCGImagePropertyGIFDictionary as String): [(kCGImagePropertyGIFUnclampedDelayTime as String): processConfig.gifDelayTime]] as CFDictionary | |
CGImageDestinationAddImage(destination, cgImage, frameProperties) | |
if requestTime == times.last!.timeValue { | |
CGImageDestinationFinalize(destination) | |
completion(fileURL!) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment