查看: 1505|回复: 0

[IOS开发教程] iOS 在线视频生成GIF图功能的方法

发表于 2018-4-13 08:00:05

在一些视频APP中,都可以看到一个将在线视频转成GIF图的功能。下面就来说说思路以及实现。我们知道本地视频可以生成GIF,那么将在线视频截取成本地视频不就可以了吗?经过比较,腾讯视频App也是这么做的。话不多说,下面开始上代码:

第一步:截取视频

  1. #pragma mark -截取视频
  2. - (void)interceptVideoAndVideoUrl:(NSURL *)videoUrl withOutPath:(NSString *)outPath outputFileType:(NSString *)outputFileType range:(NSRange)videoRange intercept:(InterceptBlock)interceptBlock {
  3. _interceptBlock =interceptBlock;
  4. //不添加背景音乐
  5. NSURL *audioUrl =nil;
  6. //AVURLAsset此类主要用于获取媒体信息,包括视频、声音等
  7. AVURLAsset* audioAsset = [[AVURLAsset alloc] initWithURL:audioUrl options:nil];
  8. AVURLAsset* videoAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:nil];
  9. //创建AVMutableComposition对象来添加视频音频资源的AVMutableCompositionTrack
  10. AVMutableComposition* mixComposition = [AVMutableComposition composition];
  11. //CMTimeRangeMake(start, duration),start起始时间,duration时长,都是CMTime类型
  12. //CMTimeMake(int64_t value, int32_t timescale),返回CMTime,value视频的一个总帧数,timescale是指每秒视频播放的帧数,视频播放速率,(value / timescale)才是视频实际的秒数时长,timescale一般情况下不改变,截取视频长度通过改变value的值
  13. //CMTimeMakeWithSeconds(Float64 seconds, int32_t preferredTimeScale),返回CMTime,seconds截取时长(单位秒),preferredTimeScale每秒帧数
  14. //开始位置startTime
  15. CMTime startTime = CMTimeMakeWithSeconds(videoRange.location, videoAsset.duration.timescale);
  16. //截取长度videoDuration
  17. CMTime videoDuration = CMTimeMakeWithSeconds(videoRange.length, videoAsset.duration.timescale);
  18. CMTimeRange videoTimeRange = CMTimeRangeMake(startTime, videoDuration);
  19. //视频采集compositionVideoTrack
  20. AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
  21. // 避免数组越界 tracksWithMediaType 找不到对应的文件时候返回空数组
  22. //TimeRange截取的范围长度
  23. //ofTrack来源
  24. //atTime插放在视频的时间位置
  25. [compositionVideoTrack insertTimeRange:videoTimeRange ofTrack:([videoAsset tracksWithMediaType:AVMediaTypeVideo].count>0) ? [videoAsset tracksWithMediaType:AVMediaTypeVideo].firstObject : nil atTime:kCMTimeZero error:nil];
  26. //视频声音采集(也可不执行这段代码不采集视频音轨,合并后的视频文件将没有视频原来的声音)
  27. AVMutableCompositionTrack *compositionVoiceTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
  28. [compositionVoiceTrack insertTimeRange:videoTimeRange ofTrack:([videoAsset tracksWithMediaType:AVMediaTypeAudio].count>0)?[videoAsset tracksWithMediaType:AVMediaTypeAudio].firstObject:nil atTime:kCMTimeZero error:nil];
  29. //声音长度截取范围==视频长度
  30. CMTimeRange audioTimeRange = CMTimeRangeMake(kCMTimeZero, videoDuration);
  31. //音频采集compositionCommentaryTrack
  32. AVMutableCompositionTrack *compositionAudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
  33. [compositionAudioTrack insertTimeRange:audioTimeRange ofTrack:([audioAsset tracksWithMediaType:AVMediaTypeAudio].count > 0) ? [audioAsset tracksWithMediaType:AVMediaTypeAudio].firstObject : nil atTime:kCMTimeZero error:nil];
  34. //AVAssetExportSession用于合并文件,导出合并后文件,presetName文件的输出类型
  35. AVAssetExportSession *assetExportSession = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetPassthrough];
  36. //混合后的视频输出路径
  37. NSURL *outPutURL = [NSURL fileURLWithPath:outPath];
  38. if ([[NSFileManager defaultManager] fileExistsAtPath:outPath])
  39. {
  40. [[NSFileManager defaultManager] removeItemAtPath:outPath error:nil];
  41. }
  42. //输出视频格式
  43. assetExportSession.outputFileType = outputFileType;
  44. assetExportSession.outputURL = outPutURL;
  45. //输出文件是否网络优化
  46. assetExportSession.shouldOptimizeForNetworkUse = YES;
  47. [assetExportSession exportAsynchronouslyWithCompletionHandler:^{
  48. dispatch_async(dispatch_get_main_queue(), ^{
  49. switch (assetExportSession.status) {
  50. case AVAssetExportSessionStatusFailed:
  51. if (_interceptBlock) {
  52. _interceptBlock(assetExportSession.error,outPutURL);
  53. }
  54. break;
  55. case AVAssetExportSessionStatusCancelled:{
  56. logdebug(@"Export Status: Cancell");
  57. break;
  58. }
  59. case AVAssetExportSessionStatusCompleted: {
  60. if (_interceptBlock) {
  61. _interceptBlock(nil,outPutURL);
  62. }
  63. break;
  64. }
  65. case AVAssetExportSessionStatusUnknown: {
  66. logdebug(@"Export Status: Unknown");
  67. }
  68. case AVAssetExportSessionStatusExporting : {
  69. logdebug(@"Export Status: Exporting");
  70. }
  71. case AVAssetExportSessionStatusWaiting: {
  72. logdebug(@"Export Status: Wating");
  73. }
  74. }
  75. });
  76. }];
  77. }
复制代码

第二步:本地视频生成GIF图

  1. /**
  2. 生成GIF图片
  3. @param videoURL 视频的路径URL
  4. @param loopCount 播放次数
  5. @param time 每帧的时间间隔 默认0.25s
  6. @param imagePath 存放GIF图片的文件路径
  7. @param completeBlock 完成的回调
  8. */
  9. #pragma mark--制作GIF
  10. - (void)createGIFfromURL:(NSURL*)videoURL loopCount:(int)loopCount delayTime:(CGFloat )time gifImagePath:(NSString *)imagePath complete:(CompleteBlock)completeBlock {
  11. _completeBlock =completeBlock;
  12. float delayTime = time?:0.25;
  13. // Create properties dictionaries
  14. NSDictionary *fileProperties = [self filePropertiesWithLoopCount:loopCount];
  15. NSDictionary *frameProperties = [self framePropertiesWithDelayTime:delayTime];
  16. AVURLAsset *asset = [AVURLAsset assetWithURL:videoURL];
  17. float videoWidth = [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize].width;
  18. float videoHeight = [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize].height;
  19. GIFSize optimalSize = GIFSizeMedium;
  20. if (videoWidth >= 1200 || videoHeight >= 1200)
  21. optimalSize = GIFSizeVeryLow;
  22. else if (videoWidth >= 800 || videoHeight >= 800)
  23. optimalSize = GIFSizeLow;
  24. else if (videoWidth >= 400 || videoHeight >= 400)
  25. optimalSize = GIFSizeMedium;
  26. else if (videoWidth < 400|| videoHeight < 400)
  27. optimalSize = GIFSizeHigh;
  28. // Get the length of the video in seconds
  29. float videoLength = (float)asset.duration.value/asset.duration.timescale;
  30. int framesPerSecond = 4;
  31. int frameCount = videoLength*framesPerSecond;
  32. // How far along the video track we want to move, in seconds.
  33. float increment = (float)videoLength/frameCount;
  34. // Add frames to the buffer
  35. NSMutableArray *timePoints = [NSMutableArray array];
  36. for (int currentFrame = 0; currentFrame<frameCount; ++currentFrame) {
  37. float seconds = (float)increment * currentFrame;
  38. CMTime time = CMTimeMakeWithSeconds(seconds, [timeInterval intValue]);
  39. [timePoints addObject:[NSValue valueWithCMTime:time]];
  40. }
  41. //completion block
  42. NSURL *gifURL = [self createGIFforTimePoints:timePoints fromURL:videoURL fileProperties:fileProperties frameProperties:frameProperties gifImagePath:imagePath frameCount:frameCount gifSize:_gifSize?:GIFSizeMedium];
  43. if (_completeBlock) {
  44. // Return GIF URL
  45. _completeBlock(_error,gifURL);
  46. }
  47. }
复制代码

经过上面两步,就可以生成本地的视频和GIF图了,存储在沙盒即可。贴上两步所用到的方法:

  1. #pragma mark - Base methods
  2. - (NSURL *)createGIFforTimePoints:(NSArray *)timePoints fromURL:(NSURL *)url fileProperties:(NSDictionary *)fileProperties frameProperties:(NSDictionary *)frameProperties gifImagePath:(NSString *)imagePath frameCount:(int)frameCount gifSize:(GIFSize)gifSize{
  3. NSURL *fileURL = [NSURL fileURLWithPath:imagePath];
  4. if (fileURL == nil)
  5. return nil;
  6. CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL, kUTTypeGIF , frameCount, NULL);
  7. CGImageDestinationSetProperties(destination, (CFDictionaryRef)fileProperties);
  8. AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
  9. AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
  10. generator.appliesPreferredTrackTransform = YES;
  11. CMTime tol = CMTimeMakeWithSeconds([tolerance floatValue], [timeInterval intValue]);
  12. generator.requestedTimeToleranceBefore = tol;
  13. generator.requestedTimeToleranceAfter = tol;
  14. NSError *error = nil;
  15. CGImageRef previousImageRefCopy = nil;
  16. for (NSValue *time in timePoints) {
  17. CGImageRef imageRef;
  18. #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
  19. imageRef = (float)gifSize/10 != 1 ? createImageWithScale([generator copyCGImageAtTime:[time CMTimeValue] actualTime:nil error:&error], (float)gifSize/10) : [generator copyCGImageAtTime:[time CMTimeValue] actualTime:nil error:&error];
  20. #elif TARGET_OS_MAC
  21. imageRef = [generator copyCGImageAtTime:[time CMTimeValue] actualTime:nil error:&error];
  22. #endif
  23. if (error) {
  24. _error =error;
  25. logdebug(@"Error copying image: %@", error);
  26. return nil;
  27. }
  28. if (imageRef) {
  29. CGImageRelease(previousImageRefCopy);
  30. previousImageRefCopy = CGImageCreateCopy(imageRef);
  31. } else if (previousImageRefCopy) {
  32. imageRef = CGImageCreateCopy(previousImageRefCopy);
  33. } else {
  34. _error =[NSError errorWithDomain:NSStringFromClass([self class]) code:0 userInfo:@{NSLocalizedDescriptionKey:@"Error copying image and no previous frames to duplicate"}];
  35. logdebug(@"Error copying image and no previous frames to duplicate");
  36. return nil;
  37. }
  38. CGImageDestinationAddImage(destination, imageRef, (CFDictionaryRef)frameProperties);
  39. CGImageRelease(imageRef);
  40. }
  41. CGImageRelease(previousImageRefCopy);
  42. // Finalize the GIF
  43. if (!CGImageDestinationFinalize(destination)) {
  44. _error =error;
  45. logdebug(@"Failed to finalize GIF destination: %@", error);
  46. if (destination != nil) {
  47. CFRelease(destination);
  48. }
  49. return nil;
  50. }
  51. CFRelease(destination);
  52. return fileURL;
  53. }
  54. #pragma mark - Helpers
  55. CGImageRef createImageWithScale(CGImageRef imageRef, float scale) {
  56. #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
  57. CGSize newSize = CGSizeMake(CGImageGetWidth(imageRef)*scale, CGImageGetHeight(imageRef)*scale);
  58. CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
  59. UIGraphicsBeginImageContextWithOptions(newSize, NO, 0);
  60. CGContextRef context = UIGraphicsGetCurrentContext();
  61. if (!context) {
  62. return nil;
  63. }
  64. // Set the quality level to use when rescaling
  65. CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
  66. CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height);
  67. CGContextConcatCTM(context, flipVertical);
  68. // Draw into the context; this scales the image
  69. CGContextDrawImage(context, newRect, imageRef);
  70. //Release old image
  71. CFRelease(imageRef);
  72. // Get the resized image from the context and a UIImage
  73. imageRef = CGBitmapContextCreateImage(context);
  74. UIGraphicsEndImageContext();
  75. #endif
  76. return imageRef;
  77. }
  78. #pragma mark - Properties
  79. - (NSDictionary *)filePropertiesWithLoopCount:(int)loopCount {
  80. return @{(NSString *)kCGImagePropertyGIFDictionary:
  81. @{(NSString *)kCGImagePropertyGIFLoopCount: @(loopCount)}
  82. };
  83. }
  84. - (NSDictionary *)framePropertiesWithDelayTime:(float)delayTime {
  85. return @{(NSString *)kCGImagePropertyGIFDictionary:
  86. @{(NSString *)kCGImagePropertyGIFDelayTime: @(delayTime)},
  87. (NSString *)kCGImagePropertyColorModel:(NSString *)kCGImagePropertyColorModelRGB
  88. };
  89. }
复制代码

最后,截取的本地视频可用AVPlayer播放,生成的GIF图则用UIWebView或者WKWebView又或者 YYImage 加载即可。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持程序员之家。



回复

使用道具 举报