Last active
December 10, 2024 20:43
-
-
Save christianselig/50b9cd47ed9e5f7a7e580930f4c8c2b5 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
import UIKit | |
import AVFoundation | |
import Photos | |
import MobileCoreServices | |
class ViewController: UIViewController { | |
override func viewDidAppear(_ animated: Bool) { | |
super.viewDidAppear(animated) | |
startVideoToGIFProcess() | |
} | |
func startVideoToGIFProcess() { | |
// Download the video and write it to temp storage | |
print("Downloading video…") | |
let data = try! Data(contentsOf: URL(string: "https://i.imgur.com/dXxP7a9.mp4")!) | |
let fileName = String(format: "%@_%@", ProcessInfo.processInfo.globallyUniqueString, "html5gif.mp4") | |
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName) | |
try! data.write(to: fileURL, options: [.atomic]) | |
createGIF(fromVideoAtURL: fileURL) | |
} | |
func createGIF(fromVideoAtURL url: URL) { | |
print("Downloaded!") | |
let frameRate: Int = 20 | |
let duration: TimeInterval = 9.68 | |
let totalFrames = Int(duration * TimeInterval(frameRate)) | |
let delayBetweenFrames: TimeInterval = 1.0 / TimeInterval(frameRate) | |
var timeValues: [NSValue] = [] | |
for frameNumber in 0 ..< totalFrames { | |
let seconds = TimeInterval(delayBetweenFrames) * TimeInterval(frameNumber) | |
let time = CMTime(seconds: seconds, preferredTimescale: Int32(NSEC_PER_SEC)) | |
timeValues.append(NSValue(time: time)) | |
} | |
let asset = AVURLAsset(url: url) | |
let generator = AVAssetImageGenerator(asset: asset) | |
generator.requestedTimeToleranceBefore = CMTime(seconds: 0.05, preferredTimescale: 600) | |
generator.requestedTimeToleranceAfter = CMTime(seconds: 0.05, preferredTimescale: 600) | |
let sizeModifier: CGFloat = 0.1 | |
generator.maximumSize = CGSize(width: 450.0 * sizeModifier, height: 563.0 * sizeModifier) | |
// Set up resulting image | |
let fileProperties: [String: Any] = [ | |
kCGImagePropertyGIFDictionary as String: [ | |
kCGImagePropertyGIFLoopCount as String: 0 | |
] | |
] | |
let frameProperties: [String: Any] = [ | |
kCGImagePropertyGIFDictionary as String: [ | |
kCGImagePropertyGIFDelayTime: delayBetweenFrames | |
] | |
] | |
let resultingFilename = String(format: "%@_%@", ProcessInfo.processInfo.globallyUniqueString, "html5gif.gif") | |
let resultingFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(resultingFilename) | |
let destination = CGImageDestinationCreateWithURL(resultingFileURL as CFURL, kUTTypeGIF, totalFrames, nil)! | |
CGImageDestinationSetProperties(destination, fileProperties as CFDictionary) | |
print("Converting to GIF…") | |
var framesProcessed = 0 | |
let startTime = CFAbsoluteTimeGetCurrent() | |
generator.generateCGImagesAsynchronously(forTimes: timeValues) { (requestedTime, resultingImage, actualTime, result, error) in | |
guard let resultingImage = resultingImage else { return } | |
framesProcessed += 1 | |
CGImageDestinationAddImage(destination, resultingImage, frameProperties as CFDictionary) | |
if framesProcessed == totalFrames { | |
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime | |
print("Done converting to GIF! Frames processed: \(framesProcessed) • Total time: \(timeElapsed) s.") | |
// Save to Photos just to check… | |
let result = CGImageDestinationFinalize(destination) | |
print("Did it succeed?", result) | |
if result { | |
print("Saving to Photos…") | |
PHPhotoLibrary.shared().performChanges({ | |
PHAssetCreationRequest.creationRequestForAssetFromImage(atFileURL: resultingFileURL) | |
}) { (saved, err) in | |
print("Saved?", saved) | |
} | |
} | |
} | |
} | |
} | |
} |
Sorry, one more question - will this work for very large GIFs (ie over a minute)?
@bl791 I looked in Apollo and I have this code, so I'm going to guess right around a minute should be the maximum otherwise iOS will probably run out of memory due to GIFs being a very inefficient video storage format:
/// If the video is any longer than this, do not offer to save it as a GIF
static var upperLimitDurationThresholdForGIF: TimeInterval = 65.0
Thanks!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice, thank you so much!