Permalink
Please sign in to comment.
Showing
with
2,256 additions
and 13 deletions.
- +1 โ1 .gitignore
- +2 โ6 GifCapture/Camera/Saver.swift
- +1 โ1 Podfile
- +4 โ4 Podfile.lock
- +12 โ0 Pods/Manifest.lock
- +22 โ0 Pods/NSGIF/LICENSE
- +26 โ0 Pods/NSGIF/NSGIF/NSGIF.h
- +235 โ0 Pods/NSGIF/NSGIF/NSGIF.m
- +71 โ0 Pods/NSGIF/README.md
- +862 โ0 Pods/Pods.xcodeproj/project.pbxproj
- +26 โ0 Pods/Target Support Files/NSGIF/Info.plist
- +5 โ0 Pods/Target Support Files/NSGIF/NSGIF-dummy.m
- +12 โ0 Pods/Target Support Files/NSGIF/NSGIF-prefix.pch
- +17 โ0 Pods/Target Support Files/NSGIF/NSGIF-umbrella.h
- +6 โ0 Pods/Target Support Files/NSGIF/NSGIF.modulemap
- +10 โ0 Pods/Target Support Files/NSGIF/NSGIF.xcconfig
- +26 โ0 Pods/Target Support Files/Pods-GifCapture/Info.plist
- +29 โ0 Pods/Target Support Files/Pods-GifCapture/Pods-GifCapture-acknowledgements.markdown
- +61 โ0 Pods/Target Support Files/Pods-GifCapture/Pods-GifCapture-acknowledgements.plist
- +5 โ0 Pods/Target Support Files/Pods-GifCapture/Pods-GifCapture-dummy.m
- +99 โ0 Pods/Target Support Files/Pods-GifCapture/Pods-GifCapture-frameworks.sh
- +99 โ0 Pods/Target Support Files/Pods-GifCapture/Pods-GifCapture-resources.sh
- +16 โ0 Pods/Target Support Files/Pods-GifCapture/Pods-GifCapture-umbrella.h
- +9 โ0 Pods/Target Support Files/Pods-GifCapture/Pods-GifCapture.debug.xcconfig
- +6 โ0 Pods/Target Support Files/Pods-GifCapture/Pods-GifCapture.modulemap
- +9 โ0 Pods/Target Support Files/Pods-GifCapture/Pods-GifCapture.release.xcconfig
- +26 โ0 Pods/Target Support Files/Pods-GifCaptureTests/Info.plist
- +3 โ0 Pods/Target Support Files/Pods-GifCaptureTests/Pods-GifCaptureTests-acknowledgements.markdown
- +29 โ0 Pods/Target Support Files/Pods-GifCaptureTests/Pods-GifCaptureTests-acknowledgements.plist
- +5 โ0 Pods/Target Support Files/Pods-GifCaptureTests/Pods-GifCaptureTests-dummy.m
- +92 โ0 Pods/Target Support Files/Pods-GifCaptureTests/Pods-GifCaptureTests-frameworks.sh
- +99 โ0 Pods/Target Support Files/Pods-GifCaptureTests/Pods-GifCaptureTests-resources.sh
- +16 โ0 Pods/Target Support Files/Pods-GifCaptureTests/Pods-GifCaptureTests-umbrella.h
- +8 โ0 Pods/Target Support Files/Pods-GifCaptureTests/Pods-GifCaptureTests.debug.xcconfig
- +6 โ0 Pods/Target Support Files/Pods-GifCaptureTests/Pods-GifCaptureTests.modulemap
- +8 โ0 Pods/Target Support Files/Pods-GifCaptureTests/Pods-GifCaptureTests.release.xcconfig
- +26 โ0 Pods/Target Support Files/Pods-GifCaptureUITests/Info.plist
- +3 โ0 Pods/Target Support Files/Pods-GifCaptureUITests/Pods-GifCaptureUITests-acknowledgements.markdown
- +29 โ0 Pods/Target Support Files/Pods-GifCaptureUITests/Pods-GifCaptureUITests-acknowledgements.plist
- +5 โ0 Pods/Target Support Files/Pods-GifCaptureUITests/Pods-GifCaptureUITests-dummy.m
- +92 โ0 Pods/Target Support Files/Pods-GifCaptureUITests/Pods-GifCaptureUITests-frameworks.sh
- +99 โ0 Pods/Target Support Files/Pods-GifCaptureUITests/Pods-GifCaptureUITests-resources.sh
- +16 โ0 Pods/Target Support Files/Pods-GifCaptureUITests/Pods-GifCaptureUITests-umbrella.h
- +8 โ0 Pods/Target Support Files/Pods-GifCaptureUITests/Pods-GifCaptureUITests.debug.xcconfig
- +6 โ0 Pods/Target Support Files/Pods-GifCaptureUITests/Pods-GifCaptureUITests.modulemap
- +8 โ0 Pods/Target Support Files/Pods-GifCaptureUITests/Pods-GifCaptureUITests.release.xcconfig
- +1 โ1 README.md
| @@ -1,12 +1,12 @@ | ||
| PODS: | ||
| - - Regift (1.3.0) | ||
| + - NSGIF (1.2) | ||
| DEPENDENCIES: | ||
| - - Regift (~> 1.3) | ||
| + - NSGIF (~> 1.2) | ||
| SPEC CHECKSUMS: | ||
| - Regift: 43c5e057949a9e2b16de0bcf0a221a5dbd033211 | ||
| + NSGIF: 73b435330174a9d04db9886a232c605a3e8139b5 | ||
| -PODFILE CHECKSUM: b24bde26b211ae94cf275ae7dd2b538c7f53c11f | ||
| +PODFILE CHECKSUM: 0121fb276cd88b274fc9084a6a3072e0798b3bb3 | ||
| COCOAPODS: 1.2.0 |
| @@ -0,0 +1,12 @@ | ||
| +PODS: | ||
| + - NSGIF (1.2) | ||
| + | ||
| +DEPENDENCIES: | ||
| + - NSGIF (~> 1.2) | ||
| + | ||
| +SPEC CHECKSUMS: | ||
| + NSGIF: 73b435330174a9d04db9886a232c605a3e8139b5 | ||
| + | ||
| +PODFILE CHECKSUM: 0121fb276cd88b274fc9084a6a3072e0798b3bb3 | ||
| + | ||
| +COCOAPODS: 1.2.0 |
| @@ -0,0 +1,22 @@ | ||
| +The MIT License (MIT) | ||
| + | ||
| +Copyright (c) 2015 Sebastian Dobrincu | ||
| + | ||
| +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. | ||
| + |
| @@ -0,0 +1,26 @@ | ||
| +// | ||
| +// NSGIF.h | ||
| +// | ||
| +// Created by Sebastian Dobrincu | ||
| +// | ||
| + | ||
| +#import <Foundation/Foundation.h> | ||
| +#import <CoreGraphics/CoreGraphics.h> | ||
| +#import <ImageIO/ImageIO.h> | ||
| +#import <AVFoundation/AVFoundation.h> | ||
| + | ||
| +#if TARGET_OS_IPHONE | ||
| + #import <MobileCoreServices/MobileCoreServices.h> | ||
| + #import <UIKit/UIKit.h> | ||
| +#elif TARGET_OS_MAC | ||
| + #import <CoreServices/CoreServices.h> | ||
| + #import <WebKit/WebKit.h> | ||
| +#endif | ||
| + | ||
| +@interface NSGIF : NSObject | ||
| + | ||
| ++ (void)optimalGIFfromURL:(NSURL*)videoURL loopCount:(int)loopCount completion:(void(^)(NSURL *GifURL))completionBlock; | ||
| + | ||
| ++ (void)createGIFfromURL:(NSURL*)videoURL withFrameCount:(int)frameCount delayTime:(int)delayTime loopCount:(int)loopCount completion:(void(^)(NSURL *GifURL))completionBlock; | ||
| + | ||
| +@end |
| @@ -0,0 +1,235 @@ | ||
| +// | ||
| +// NSGIF.m | ||
| +// | ||
| +// Created by Sebastian Dobrincu | ||
| +// | ||
| + | ||
| +#import "NSGIF.h" | ||
| + | ||
| +@implementation NSGIF | ||
| + | ||
| +// Declare constants | ||
| +#define fileName @"NSGIF.gif" | ||
| +#define timeInterval @(600) | ||
| +#define tolerance @(0.01) | ||
| + | ||
| +typedef NS_ENUM(NSInteger, GIFSize) { | ||
| + GIFSizeVeryLow = 2, | ||
| + GIFSizeLow = 3, | ||
| + GIFSizeMedium = 5, | ||
| + GIFSizeHigh = 7, | ||
| + GIFSizeOriginal = 10 | ||
| +}; | ||
| + | ||
| +#pragma mark - Public methods | ||
| + | ||
| ++ (void)optimalGIFfromURL:(NSURL*)videoURL loopCount:(int)loopCount completion:(void(^)(NSURL *GifURL))completionBlock { | ||
| + | ||
| + int delayTime = 0.2; | ||
| + | ||
| + // Create properties dictionaries | ||
| + NSDictionary *fileProperties = [self filePropertiesWithLoopCount:loopCount]; | ||
| + NSDictionary *frameProperties = [self framePropertiesWithDelayTime:delayTime]; | ||
| + | ||
| + AVURLAsset *asset = [AVURLAsset assetWithURL:videoURL]; | ||
| + | ||
| + float videoWidth = [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize].width; | ||
| + float videoHeight = [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize].height; | ||
| + | ||
| + GIFSize optimalSize = GIFSizeMedium; | ||
| + if (videoWidth >= 1200 || videoHeight >= 1200) | ||
| + optimalSize = GIFSizeVeryLow; | ||
| + else if (videoWidth >= 800 || videoHeight >= 800) | ||
| + optimalSize = GIFSizeLow; | ||
| + else if (videoWidth >= 400 || videoHeight >= 400) | ||
| + optimalSize = GIFSizeMedium; | ||
| + else if (videoWidth < 400|| videoHeight < 400) | ||
| + optimalSize = GIFSizeHigh; | ||
| + | ||
| + // Get the length of the video in seconds | ||
| + float videoLength = (float)asset.duration.value/asset.duration.timescale; | ||
| + int framesPerSecond = 4; | ||
| + int frameCount = videoLength*framesPerSecond; | ||
| + | ||
| + // How far along the video track we want to move, in seconds. | ||
| + float increment = (float)videoLength/frameCount; | ||
| + | ||
| + // Add frames to the buffer | ||
| + NSMutableArray *timePoints = [NSMutableArray array]; | ||
| + for (int currentFrame = 0; currentFrame<frameCount; ++currentFrame) { | ||
| + float seconds = (float)increment * currentFrame; | ||
| + CMTime time = CMTimeMakeWithSeconds(seconds, [timeInterval intValue]); | ||
| + [timePoints addObject:[NSValue valueWithCMTime:time]]; | ||
| + } | ||
| + | ||
| + // Prepare group for firing completion block | ||
| + dispatch_group_t gifQueue = dispatch_group_create(); | ||
| + dispatch_group_enter(gifQueue); | ||
| + | ||
| + __block NSURL *gifURL; | ||
| + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | ||
| + | ||
| + gifURL = [self createGIFforTimePoints:timePoints fromURL:videoURL fileProperties:fileProperties frameProperties:frameProperties frameCount:frameCount gifSize:optimalSize]; | ||
| + | ||
| + dispatch_group_leave(gifQueue); | ||
| + }); | ||
| + | ||
| + dispatch_group_notify(gifQueue, dispatch_get_main_queue(), ^{ | ||
| + // Return GIF URL | ||
| + completionBlock(gifURL); | ||
| + }); | ||
| + | ||
| +} | ||
| + | ||
| ++ (void)createGIFfromURL:(NSURL*)videoURL withFrameCount:(int)frameCount delayTime:(int)delayTime loopCount:(int)loopCount completion:(void(^)(NSURL *GifURL))completionBlock { | ||
| + | ||
| + // Convert the video at the given URL to a GIF, and return the GIF's URL if it was created. | ||
| + // The frames are spaced evenly over the video, and each has the same duration. | ||
| + // delayTime is the amount of time for each frame in the GIF. | ||
| + // loopCount is the number of times the GIF will repeat. Defaults to 0, which means repeat infinitely. | ||
| + | ||
| + // Create properties dictionaries | ||
| + NSDictionary *fileProperties = [self filePropertiesWithLoopCount:loopCount]; | ||
| + NSDictionary *frameProperties = [self framePropertiesWithDelayTime:delayTime]; | ||
| + | ||
| + AVURLAsset *asset = [AVURLAsset assetWithURL:videoURL]; | ||
| + | ||
| + // Get the length of the video in seconds | ||
| + float videoLength = (float)asset.duration.value/asset.duration.timescale; | ||
| + | ||
| + // How far along the video track we want to move, in seconds. | ||
| + float increment = (float)videoLength/frameCount; | ||
| + | ||
| + // Add frames to the buffer | ||
| + NSMutableArray *timePoints = [NSMutableArray array]; | ||
| + for (int currentFrame = 0; currentFrame<frameCount; ++currentFrame) { | ||
| + float seconds = (float)increment * currentFrame; | ||
| + CMTime time = CMTimeMakeWithSeconds(seconds, [timeInterval intValue]); | ||
| + [timePoints addObject:[NSValue valueWithCMTime:time]]; | ||
| + } | ||
| + | ||
| + // Prepare group for firing completion block | ||
| + dispatch_group_t gifQueue = dispatch_group_create(); | ||
| + dispatch_group_enter(gifQueue); | ||
| + | ||
| + __block NSURL *gifURL; | ||
| + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | ||
| + | ||
| + gifURL = [self createGIFforTimePoints:timePoints fromURL:videoURL fileProperties:fileProperties frameProperties:frameProperties frameCount:frameCount gifSize:GIFSizeMedium]; | ||
| + | ||
| + dispatch_group_leave(gifQueue); | ||
| + }); | ||
| + | ||
| + dispatch_group_notify(gifQueue, dispatch_get_main_queue(), ^{ | ||
| + // Return GIF URL | ||
| + completionBlock(gifURL); | ||
| + }); | ||
| + | ||
| +} | ||
| + | ||
| +#pragma mark - Base methods | ||
| + | ||
| ++ (NSURL *)createGIFforTimePoints:(NSArray *)timePoints fromURL:(NSURL *)url fileProperties:(NSDictionary *)fileProperties frameProperties:(NSDictionary *)frameProperties frameCount:(int)frameCount gifSize:(GIFSize)gifSize{ | ||
| + | ||
| + NSString *temporaryFile = [NSTemporaryDirectory() stringByAppendingString:fileName]; | ||
| + NSURL *fileURL = [NSURL fileURLWithPath:temporaryFile]; | ||
| + CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL, kUTTypeGIF , frameCount, NULL); | ||
| + | ||
| + if (fileURL == nil) | ||
| + return nil; | ||
| + | ||
| + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil]; | ||
| + AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; | ||
| + generator.appliesPreferredTrackTransform = YES; | ||
| + | ||
| + CMTime tol = CMTimeMakeWithSeconds([tolerance floatValue], [timeInterval intValue]); | ||
| + generator.requestedTimeToleranceBefore = tol; | ||
| + generator.requestedTimeToleranceAfter = tol; | ||
| + | ||
| + NSError *error = nil; | ||
| + CGImageRef previousImageRefCopy = nil; | ||
| + for (NSValue *time in timePoints) { | ||
| + CGImageRef imageRef; | ||
| + | ||
| + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR | ||
| + imageRef = (float)gifSize/10 != 1 ? ImageWithScale([generator copyCGImageAtTime:[time CMTimeValue] actualTime:nil error:&error], (float)gifSize/10) : [generator copyCGImageAtTime:[time CMTimeValue] actualTime:nil error:&error]; | ||
| + #elif TARGET_OS_MAC | ||
| + imageRef = [generator copyCGImageAtTime:[time CMTimeValue] actualTime:nil error:&error]; | ||
| + #endif | ||
| + | ||
| + if (error) { | ||
| + NSLog(@"Error copying image: %@", error); | ||
| + } | ||
| + if (imageRef) { | ||
| + CGImageRelease(previousImageRefCopy); | ||
| + previousImageRefCopy = CGImageCreateCopy(imageRef); | ||
| + } else if (previousImageRefCopy) { | ||
| + imageRef = CGImageCreateCopy(previousImageRefCopy); | ||
| + } else { | ||
| + NSLog(@"Error copying image and no previous frames to duplicate"); | ||
| + return nil; | ||
| + } | ||
| + CGImageDestinationAddImage(destination, imageRef, (CFDictionaryRef)frameProperties); | ||
| + CGImageRelease(imageRef); | ||
| + } | ||
| + CGImageRelease(previousImageRefCopy); | ||
| + | ||
| + CGImageDestinationSetProperties(destination, (CFDictionaryRef)fileProperties); | ||
| + // Finalize the GIF | ||
| + if (!CGImageDestinationFinalize(destination)) { | ||
| + NSLog(@"Failed to finalize GIF destination: %@", error); | ||
| + return nil; | ||
| + } | ||
| + CFRelease(destination); | ||
| + | ||
| + return fileURL; | ||
| +} | ||
| + | ||
| +#pragma mark - Helpers | ||
| + | ||
| +CGImageRef ImageWithScale(CGImageRef imageRef, float scale) { | ||
| + | ||
| + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR | ||
| + CGSize newSize = CGSizeMake(CGImageGetWidth(imageRef)*scale, CGImageGetHeight(imageRef)*scale); | ||
| + CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height)); | ||
| + | ||
| + UIGraphicsBeginImageContextWithOptions(newSize, NO, 0); | ||
| + CGContextRef context = UIGraphicsGetCurrentContext(); | ||
| + if (!context) { | ||
| + return nil; | ||
| + } | ||
| + | ||
| + // Set the quality level to use when rescaling | ||
| + CGContextSetInterpolationQuality(context, kCGInterpolationHigh); | ||
| + CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height); | ||
| + | ||
| + CGContextConcatCTM(context, flipVertical); | ||
| + // Draw into the context; this scales the image | ||
| + CGContextDrawImage(context, newRect, imageRef); | ||
| + | ||
| + // Get the resized image from the context and a UIImage | ||
| + imageRef = CGBitmapContextCreateImage(context); | ||
| + | ||
| + UIGraphicsEndImageContext(); | ||
| + #endif | ||
| + | ||
| + return imageRef; | ||
| +} | ||
| + | ||
| +#pragma mark - Properties | ||
| + | ||
| ++ (NSDictionary *)filePropertiesWithLoopCount:(int)loopCount { | ||
| + return @{(NSString *)kCGImagePropertyGIFDictionary: | ||
| + @{(NSString *)kCGImagePropertyGIFLoopCount: @(loopCount)} | ||
| + }; | ||
| +} | ||
| + | ||
| ++ (NSDictionary *)framePropertiesWithDelayTime:(int)delayTime { | ||
| + | ||
| + return @{(NSString *)kCGImagePropertyGIFDictionary: | ||
| + @{(NSString *)kCGImagePropertyGIFDelayTime: @(delayTime)}, | ||
| + (NSString *)kCGImagePropertyColorModel:(NSString *)kCGImagePropertyColorModelRGB | ||
| + }; | ||
| +} | ||
| + | ||
| +@end |
Oops, something went wrong.
0 comments on commit
456e13d