ios 视频的裁剪,转换,压缩原创
金蝶云社区-honey缘木鱼
honey缘木鱼
4人赞赏了该文章 1,756次浏览 未经作者许可,禁止转载编辑于2019年05月16日 09:34:15
summary-icon摘要由AI智能服务提供

本文描述了一个视频编辑功能的实现过程,该功能包括从相册选择视频、视频播放、视频裁剪、格式转换及压缩等。首先,通过`TZImagePickerController`选择视频,并利用`PHImageManager`和`AVURLAsset`获取视频原始资源。随后,利用`AVPlayer`和`AVPlayerLayer`实现视频播放功能。在视频裁剪功能上,通过添加`UIPanGestureRecognizer`手势识别器实现滑动裁剪视频的功能,用户可以通过拖动左右滑块来裁剪视频片段。此外,还提到了一些功能的具体实现步骤,如滑动手势的响应和裁剪视频的具体逻辑。整体流程涵盖了视频处理的基本步骤,包括选择、播放和编辑视频。

最近项目中有一个视频编辑功能,类似一个视频处理工具,针对于视频的裁剪(大小和尺寸),格式的转换(mp4,mov,gif),视频的压缩等功能。

实现功能截图


主要实现步骤为:

一.从相册选择视频播放

TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:1 delegate:nil];
    imagePickerVc.allowPickingVideo = YES;
    imagePickerVc.allowPickingImage = NO;
    WS(ws);
    [imagePickerVc setDidFinishPickingVideoHandle:^(UIImage *coverImage,id asset){
        MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
        hud.label.text = @"Processing...";
        PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
        options.version = PHImageRequestOptionsVersionOriginal;
        options.networkAccessAllowed = YES;
        options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
        PHImageManager *manager = [PHImageManager defaultManager];
        [manager requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {            AVURLAsset *urlAsset = (AVURLAsset *)asset;            NSURL *url = urlAsset.URL;            dispatch_async(dispatch_get_main_queue(), ^{
             [MBProgressHUD hideHUDForView:ws.view animated:YES];              
                    
              NSString * sandboxExtensionTokenKey = info[@"PHImageFileSandboxExtensionTokenKey"];                NSArray * arr = [sandboxExtensionTokenKey componentsSeparatedByString:@";"];                 NSString * filePath = [arr[arr.count - 1]substringFromIndex:9];                CGFloat size = [MyUtility getFileSize:filePath];
              SNHVideoTrimmerController *vc = [[SNHVideoTrimmerController alloc] init];
              vc.videoUrl = url;//视频地址
              vc.fileSize = size;
               vc.changeAsset = asset;
              vc.flag = tap.view.tag;
              [self.navigationController pushViewController:vc animated:YES];
            
            });
        }];
    }];
    [self presentViewController:imagePickerVc animated:YES completion:nil];

二.用AVPlayer实现视频播放(自己封装个简单的播放器即可)

   self.playerItem = [AVPlayerItem playerItemWithAsset:self.asset];  
    self.player = [AVPlayer playerWithPlayerItem:self.playerItem];   
     self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];  
       self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;   
        self.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;    
        self.playerLayer.frame = self.videoPlayerView.bounds;
    [self.videoPlayerView.layer addSublayer:self.playerLayer];

三.具体功能的实现


(1).视频大小的裁剪主要是实现滑动视图(视图遮盖、左右滑动裁剪)功能。

 //拖动手势:改变要截取的内容
    UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveOverlayView:)];
    panGestureRecognizer.maximumNumberOfTouches = 1;
    [self addGestureRecognizer:panGestureRecognizer];

用户左右滑动裁剪视频的主要功能代码的实现:

#pragma mark - =================== 滑动手势 ===================
- (void)moveOverlayView:(UIPanGestureRecognizer *)gesture
{    
    switch (gesture.state) {   
         case UIGestureRecognizerStateBegan:
        {         
           CGFloat offsetWidth = 12;      
           CGPoint po = [gesture locationInView:self];            
            CGRect leftThumbViewRectInSelf = [self convertRect:CGRectMake(-offsetWidth, 0, _leftThumbView.frame.size.width + offsetWidth * 2, _leftThumbView.frame.size.height) fromView:_leftThumbView];            BOOL isLeft =  CGRectContainsPoint(leftThumbViewRectInSelf, po);
            _isDraggingLeftOverlayView = isLeft;            
            CGRect rightThumbViewRectInSelf = [self convertRect:CGRectMake(-offsetWidth, 0, _rightThumbView.frame.size.width + offsetWidth * 2, _rightThumbView.frame.size.height) fromView:_rightThumbView];            BOOL isRight =  CGRectContainsPoint(rightThumbViewRectInSelf, po);
            _isDraggingRightOverlayView = isRight;    
                    if (!isLeft && !isRight) {       
                             return;
            }            if (CMTimeGetSeconds([self.asset duration]) <= self.minLength) {
            
            
                [self showNoSmallerTrimMessage];         
                       return;
            }            if (isRight){            
                self.rightStartPoint = [gesture locationInView:self];
                _isDraggingRightOverlayView = YES;
                _isDraggingLeftOverlayView = NO;
            }          
              else if (isLeft){        
                self.leftStartPoint = [gesture locationInView:self];
                _isDraggingRightOverlayView = NO;
                _isDraggingLeftOverlayView = YES;
            }
            
        }  
          break;    
            case UIGestureRecognizerStateChanged:
        {            
            if (CMTimeGetSeconds([self.asset duration]) <= self.minLength) {                return;
            }            
            CGPoint point = [gesture locationInView:self];            //------------------------------------------------------------------------------------------------------------
            // Right
            if (_isDraggingRightOverlayView){                
                CGFloat deltaX = point.x - self.rightStartPoint.x;                
                CGPoint center = self.rightOverlayView.center;
                center.x += deltaX;                CGFloat newRightViewMidX = center.x;                CGFloat minX = CGRectGetMaxX(self.leftOverlayView.frame) + self.minLength * self.widthPerSecond;                CGFloat maxX = CMTimeGetSeconds([self.asset duration]) <= self.maxLength + 0.5 ? CGRectGetMaxX(self.frameView.frame) : CGRectGetWidth(self.frame) - self.thumbWidth;                if (newRightViewMidX - self.overlayWidth/2 < minX) {
                    newRightViewMidX = minX + self.overlayWidth/2;                    //
                    [self showNoSmallerTrimMessage];
                } else if (newRightViewMidX - self.overlayWidth/2 > maxX) {
                    newRightViewMidX = maxX + self.overlayWidth/2;
                }                
                self.rightOverlayView.center = CGPointMake(newRightViewMidX, self.rightOverlayView.center.y);                self.rightStartPoint = point;
            }            else if (_isDraggingLeftOverlayView){                
                //------------------------------------------------------------------------------------------------------------
                // Left
                CGFloat deltaX = point.x - self.leftStartPoint.x;                
                CGPoint center = self.leftOverlayView.center;
                center.x += deltaX;     
                           
                    
                 CGFloat newLeftViewMidX = center.x;      
                  CGFloat maxWidth = CGRectGetMinX(self.rightOverlayView.frame) - (self.minLength * self.widthPerSecond);                CGFloat newLeftViewMinX = newLeftViewMidX - self.overlayWidth/2;                if (newLeftViewMinX < self.thumbWidth - self.overlayWidth) {
                    newLeftViewMidX = self.thumbWidth - self.overlayWidth + self.overlayWidth/2;
                } else if (newLeftViewMinX + self.overlayWidth > maxWidth) {
                    newLeftViewMidX = maxWidth - self.overlayWidth / 2;                    //
                    [self showNoSmallerTrimMessage];
                }                
                self.leftOverlayView.center = CGPointMake(newLeftViewMidX, self.leftOverlayView.center.y);                self.leftStartPoint = point;
            }            //------------------------------------------------------------------------------------------------------------
            
            [self updateBorderFrames];
            [self notifyDelegateOfDidChange];            
            
            break;
        }       
         case UIGestureRecognizerStateEnded:
        {            
            if (CMTimeGetSeconds([self.asset duration]) <= self.minLength) {                return;
            }
            [self notifyDelegateOfEndEditing];
        }            
        default:     
              break;
    }
}

监听左右滑动距离,显示时间即为要裁剪的视频区间段。

- (void)trimmerView:(SNHVideoTrimmerView *)trimmerView didChangeLeftPosition:(CGFloat)startTime rightPosition:(CGFloat)endTime
{
    _restartOnPlay = YES;
    [self.player pause];    self.isPlaying = NO;   
     self.playBtn.hidden = NO;
    [self stopPlaybackTimeChecker];
    
    [self.trimmerView hideTracker:true];    
    if (startTime != self.startTime) {    
        //then it moved the left position, we should rearrange the bar
        [self seekVideoToPos:startTime];
    }    else{ // right has changed
        [self seekVideoToPos:endTime];
    }    self.startTime = startTime;  
      self.stopTime = endTime;
}

(2).视频转换格式(mp4,mov,gif)


我们用手机录制视频时,一般格式为mov,所以我们就需要格式转化。
//转化为mov,mp4时

 exporter.outputFileType = [self transOutPutFileType:fileType];
 //导出的视频格式类型- (NSString *)transOutPutFileType:(NSString *)fileType {  
   if ([fileType isEqualToString:@"mov"]) {  
         return AVFileTypeQuickTimeMovie;
    }else if ([fileType isEqualToString:@"mp4"]) {    
        return AVFileTypeMPEG4;
    }else {   
         return AVFileTypeQuickTimeMovie;
    }
}

//mov转化为GIF(视频转化为图片,图片转化为GIF图)

//把视频转换成图片数组- (void)creatAvasset:(AVAsset *)asset{ 
  AVAssetImageGenerator *imagegenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];    CMTime time = asset.duration;    
    NSInteger totalTimer = (NSInteger)CMTimeGetSeconds(time);    //总共要取的张数
    NSInteger totalCount = 0;  
      //最多取的时常
    if (totalTimer > Maxsconds) { 
         self.animationTimer = Maxsconds;
        totalCount = Maxsconds * FTPNumber;
    }else{       
         self.animationTimer = totalTimer;
        totalCount = totalTimer * FTPNumber;
        
    }    
    NSMutableArray *timeArray = [NSMutableArray array]; 
       for (NSInteger i = 0; i < totalCount; i++) {   
            CMTime timeFrame = CMTimeMake(i, FTPNumber);   
          NSValue *timeValue = [NSValue valueWithCMTime:timeFrame];
        [timeArray addObject:timeValue];
    }    //防止出现偏差
    imagegenerator.requestedTimeToleranceBefore = kCMTimeZero;
    imagegenerator.requestedTimeToleranceAfter = kCMTimeZero;
    
    [self.totalImageArray removeAllObjects];    
    //转换成图片
    
    [imagegenerator generateCGImagesAsynchronouslyForTimes:timeArray completionHandler:^(CMTime requestedTime, CGImageRef  _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {        
        
        switch (result) {     
               case AVAssetImageGeneratorFailed:
            {                //获取失败
                NSLog(@"获取失败");
            }            break;          
                case AVAssetImageGeneratorCancelled:
            {//                获取已取消
                NSLog(@"获取已经取消");
            }               
             break;              
              case AVAssetImageGeneratorSucceeded:
            {//                获取成功

                    NSData *data = UIImageJPEGRepresentation([UIImage imageWithCGImage:image], 0.6);
                    [self.totalImageArray addObject:data];          
                          if ( requestedTime.value >= totalCount -1) {                    
                    dispatch_async(dispatch_get_main_queue(), ^{       
                         if (self.blockVideo) {                            
                            self.blockVideo(@"成功处理");                 
                            //随便处理生产一个gif
                            [self ImageArrayToGif];
                        }
                      
                    });
                    
                }
               
            }             
               break;         
             default:           
              break;
        }
    }];
}

//把图片转成gif

-(void)ImageArrayToGif{    
    NSMutableArray * images= [NSMutableArray array];    
    for (NSData *dataImage in self.totalImageArray) {
        [images addObject:[UIImage imageWithData:dataImage]];
        
    }//生成载gif的文件在Document中
    NSString *path = [self creatPathGif];    
    self.gifPath = path;    //配置gif属性
    CGImageDestinationRef destion;    
    CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)path, kCFURLPOSIXPathStyle, false);
    destion = CGImageDestinationCreateWithURL(url, kUTTypeGIF, images.count, NULL);    
    NSDictionary *frameDic = [NSDictionary dictionaryWithObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat:0.3],(NSString*)kCGImagePropertyGIFDelayTime, nil] forKey:(NSString*)kCGImagePropertyGIFDelayTime];    
    NSMutableDictionary *gifParmdict = [NSMutableDictionary dictionaryWithCapacity:2];    //颜色
    [gifParmdict setObject:[NSNumber numberWithBool:YES] forKey:(NSString*)kCGImagePropertyGIFHasGlobalColorMap];//    颜色类型
    [gifParmdict setObject:(NSString*)kCGImagePropertyColorModelRGB forKey:(NSString*)kCGImagePropertyColorModel];//   颜色深度
    [gifParmdict setObject:[NSNumber numberWithInt:8] forKey:(NSString*)kCGImagePropertyDepth];//   是否重复
    [gifParmdict setObject:[NSNumber numberWithInt:0] forKey:(NSString*)kCGImagePropertyGIFLoopCount];    
    NSDictionary *gifProperty = [NSDictionary dictionaryWithObject:gifParmdict forKey:(NSString*)kCGImagePropertyGIFDictionary];//    合成gif
    for (UIImage *dimage in images) {        
        //可以在这里对图片进行压缩
        
        CGImageDestinationAddImage(destion, dimage.CGImage, (__bridge CFDictionaryRef)frameDic);
    }    
    CGImageDestinationSetProperties(destion,(__bridge CFDictionaryRef)gifProperty);    
    CGImageDestinationFinalize(destion);   
     CFRelease(destion);   
      if(self.getGifPath){  
      
              self.getGifPath(path);
    }
}

(3).视频压缩


AVAssetExportPresetLowQuality     低质量 可以通过移动网络分享   

AVAssetExportPresetMediumQuality     中等质量 可以通过WIFI网络分享
AVAssetExportPresetHighestQuality    高等质量

关键代码为:

- (void) sliderChange:(id) sender {  
  if ([sender isKindOfClass:[UISlider class]]) {   
       UISlider * slider = sender;     
          int value = slider.value;       
           if(value<=30){
            _compressType = AVAssetExportPresetLowQuality;
        }else if (value>30&&value<=70){
              _compressType = AVAssetExportPresetMediumQuality;
        }else
        {
               _compressType = AVAssetExportPresetHighestQuality;
        }        if(self.fileSize*value/100>1024){
        
             _targetTitle.text = [NSString stringWithFormat:@"Target size: %@(%d)%%",[NSString stringWithFormat:@"%d MB",(int)self.fileSize*value/100/1024],value];
        }else
        {
            _targetTitle.text = [NSString stringWithFormat:@"Target size: %@(%d)%%",[NSString stringWithFormat:@"%d Kb",(int)self.fileSize*value/100],value];
        }
    }
}

四.视频导出


当我们完成上述的功能后,需要保存新得到的视频,我们要先把新的视频导出,然后保存到本地。

//5.视频输出
    self.outputURL = [NSURL fileURLWithPath: self.outPutPath];   
     AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:
     self.mixComposition presetName:self.presetName];
    
    exporter.outputURL = self.outputURL;
    exporter.videoComposition = self.videoComposition;
    exporter.outputFileType = [self transOutPutFileType:fileType];
    exporter.shouldOptimizeForNetworkUse = self.shouldOptimizeForNetworkUse;
    [exporter exportAsynchronouslyWithCompletionHandler:^{        
        dispatch_async(dispatch_get_main_queue(), ^{          
          if (exporter.status == AVAssetExportSessionStatusCompleted) {                
          if (successBlock) {
                    successBlock(self.outputURL);
                }
            }else{               
             NSLog(@"exporter %@",exporter.error);                
            if (failureBlock) {
                    failureBlock(exporter.error);
                }
                
            }
        });
    }];

具体实现请参考demo下载地址,如有错误,请评论指出!相互学习!



本篇独发金蝶云社区


图标赞 4
4人点赞
还没有人点赞,快来当第一个点赞的人吧!
图标打赏
0人打赏
还没有人打赏,快来当第一个打赏的人吧!