视频处理之添加水印 | 编辑前 | | :--------------------------------------: | |  | | 编辑后 | | :--------------------------------------: | | | 话不多说,先上代码: CommonEditVideo.h ```objective-c // // CommonEditVideo.h // MediumEdit // // Created by Input on 2016/9/23. // Copyright © 2016年 Input. All rights reserved. // /** * 使用时请在info.plist文件加入key: NSPhotoLibraryUsageDescription value: 打开相册 * 水印格式: 自定义字符 + 时间 + 机型 * */ #import #import @protocol CommonEditVideoDelegate @required //导出到文件结束, 暂时没有加状态处理 - (void)didExport:(nullable NSURL *) url error:(nullable NSError *) error; //返回是否导出到文件, 不希望文件加水印只是临时添加请返回NO - (BOOL)willExport:(nullable AVAssetExportSession *) exporter; //开始编辑 - (void)willEdit; @end /** * 水印位置 */ typedef enum : NSUInteger { AlignmentUp, //居上 AlignmentCenter, //居中 AlignmentDown, //居下 } Alignment; @interface CommonEditVideo: NSObject @property (nonatomic, nullable, weak) id delegate; //视频编辑相关代理 @property (nonatomic, nullable, strong) NSString *watermarkTitle; //水印内容 @property (nonatomic, nullable, strong) NSURL *url; //图片输出位置(默认为相册) @property Alignment watermarkPlace; //水印位置 @property BOOL isShowTime; //是否显示时间(YES) @property BOOL isShowModel; //是否显示拍摄机型(YES) - (nullable instancetype)init; - (void)startEditVideo:(nonnull NSURL *) assetURL; - (void)videoOutput; @end ``` CommonEditVideo.m ```objective-c // // CommonEditVideo.m // MediumEdit // // Created by Input on 2016/9/23. // Copyright © 2016年 Input. All rights reserved. // #import "CommonEditVideo.h" #import #import @interface CommonEditVideo () @property (nonatomic, nullable, strong) AVAssetExportSession *exporter; //视频导出 @property (nonatomic, nullable, strong) AVAsset *videoAsset; //视频资源 @end @implementation CommonEditVideo - (nullable instancetype)init{ self = [super init]; if (self){ self.isShowTime = YES; self.isShowModel = YES; self.watermarkPlace = AlignmentCenter; } return self; } - (void)startEditVideo:(nonnull NSURL *) assetURL{ self.videoAsset = [[AVURLAsset alloc] initWithURL: assetURL options: nil]; if (self.delegate){ [self.delegate willEdit]; } //初始化及构造新视频 [self exporterInitialize]; } //导出结果回调 - (void)exportDidFinish{ if (self.exporter.status == AVAssetExportSessionStatusCompleted) { NSURL *outputURL = self.exporter.outputURL; ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:outputURL]) { [library writeVideoAtPathToSavedPhotosAlbum:outputURL completionBlock:^(NSURL *assetURL, NSError *error){ dispatch_async(dispatch_get_main_queue(), ^{ if (self.delegate){ [self.delegate didExport: self.exporter.outputURL error: error]; } }); }]; } } } - (void)editWatermarkTitle{ //水印相关设定 NSString *modelStr = nil; for (int i = 0; i < self.videoAsset.commonMetadata.count; i++ ){ AVMetadataItem *data = self.videoAsset.commonMetadata[i]; // NSLog(@"%@",data); if ([data.commonKey isEqual: @"creationDate"]){ if (self.isShowTime){ NSDateFormatter *timeFormatter = [NSDateFormatter new]; timeFormatter.dateFormat = @"' ' yyyy-MM-dd/HH:mm"; self.watermarkTitle = [self.watermarkTitle stringByAppendingString:[timeFormatter stringFromDate:data.dateValue]]; } }; if ([data.commonKey isEqual: @"model"]){ if (self.isShowModel){ modelStr = @" "; modelStr = [modelStr stringByAppendingString: data.stringValue]; } }; }; self.watermarkTitle = [self.watermarkTitle stringByAppendingString:modelStr]; } - (void)applyVideoEffectsToComposition:(nonnull AVMutableVideoComposition *)composition size:(CGSize)size{ [self editWatermarkTitle]; CATextLayer *subtitle1Text = [[CATextLayer alloc] init]; [subtitle1Text setFontSize:36]; subtitle1Text.frame = [self CGRectFromWatermarkPlaceAndVideoSzie:size]; [subtitle1Text setString:self.watermarkTitle]; [subtitle1Text setAlignmentMode:kCAAlignmentCenter]; [subtitle1Text setForegroundColor:[[UIColor redColor] CGColor]]; CALayer *overlayLayer = [CALayer layer]; [overlayLayer addSublayer:subtitle1Text]; overlayLayer.frame = CGRectMake(0, 0, size.width, size.height); [overlayLayer setMasksToBounds:YES]; CALayer *parentLayer = [CALayer layer]; CALayer *videoLayer = [CALayer layer]; parentLayer.frame = CGRectMake(0, 0, size.width, size.height); videoLayer.frame = CGRectMake(0, 0, size.width, size.height); [parentLayer addSublayer:videoLayer]; [parentLayer addSublayer:overlayLayer]; composition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer]; } //返回水印位置 - (CGRect)CGRectFromWatermarkPlaceAndVideoSzie:(CGSize)size{ switch (self.watermarkPlace) { case AlignmentUp: return CGRectMake(0, 0, size.width, size.height); case AlignmentDown: return CGRectMake(0, 0, size.width, 80); default: return CGRectMake(0, 0, size.width, size.height / 2); } } - (void)exporterInitialize{ AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init]; AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, self.videoAsset.duration) ofTrack:[[self.videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:kCMTimeZero error:nil]; AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; [audioTrack insertTimeRange: CMTimeRangeMake(kCMTimeZero, self.videoAsset.duration) ofTrack:[[self.videoAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil]; self.exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetHighestQuality]; AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, self.videoAsset.duration); AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack]; AVAssetTrack *videoAssetTrack = [[self.videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; mainInstruction.layerInstructions = [NSArray arrayWithObjects:videolayerInstruction,nil]; AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition]; CGSize naturalSize; naturalSize = videoAssetTrack.naturalSize; float renderWidth, renderHeight; renderWidth = naturalSize.width; renderHeight = naturalSize.height; mainCompositionInst.renderSize = CGSizeMake(renderWidth, renderHeight); mainCompositionInst.instructions = [NSArray arrayWithObject:mainInstruction]; mainCompositionInst.frameDuration = CMTimeMake(1, 30); [self applyVideoEffectsToComposition:mainCompositionInst size:naturalSize]; self.exporter.videoComposition = mainCompositionInst; self.exporter.outputFileType = AVFileTypeQuickTimeMovie; self.exporter.shouldOptimizeForNetworkUse = YES; self.exporter.metadata = self.videoAsset.metadata; } - (void)videoOutput{ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *myPathDocs = [documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat:@"(Input)FinalVideo-%d.mov",arc4random() % 1000]]; NSURL *url = [NSURL fileURLWithPath:myPathDocs]; BOOL flag; if (self.delegate){ flag = [self.delegate willExport:self.exporter]; }else{ flag = YES; } //视频输出url设置 if (!self.url){ self.exporter.outputURL = url; }else{ self.exporter.outputURL = self.url; } //导出 if (flag){ [self.exporter exportAsynchronouslyWithCompletionHandler:^{ dispatch_async(dispatch_get_main_queue(), ^{ [self exportDidFinish]; }); }]; } } @end ``` ## 解说 首先创建一个 `CommonEditVideo`对象,使用这个对像得实现`CommonEditVideoDelegate`协议; `CommonEditVideoDelegate`协议有三个方法,分别在开始编辑视频及保存到文件前后调用,其中实现willExport返回一个真假值,如果是想在放器界面添加水印在这里返回一个NO即可,参数就是已经处理好的`AVAssetExportSession`,这个时候你可以直接拿`AVAssetExportSession`对象的`asset`播放了;如果是要将视频永久保存则返回一个YES,当视频导出到文件结束将会调用`didExport`,这个函数会给你一个导出状态及地址,默认保存到相册,你也可以设置`CommonEditVideo`对象的`url`值。 水印可以放在三个位置:上中下;默认居中。可以设置是否显示时间及拍摄机型。自定义字符放在水印最前端。 下面的代码就是我测试用的视图控制器的实现部分 ViewController.m ```objective-c // // ViewController.m // MediumEdit // // Created by Input on 2016/9/23. // Copyright © 2016年 Input. All rights reserved. // #import "ViewController.h" #import #import #import #import @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; UIButton *btn = [UIButton buttonWithType: UIButtonTypeSystem]; btn.bounds = CGRectMake(0, 0, 100, 40); btn.center = self.view.center; btn.backgroundColor = [UIColor cyanColor]; btn.layer.cornerRadius = 5; [btn addTarget:self action:@selector(didBtn:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btn]; } - (void)didBtn:(UIButton *)sender{ UIImagePickerController *mediaUI = [[UIImagePickerController alloc] init]; mediaUI.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; mediaUI.mediaTypes = [[NSArray alloc] initWithObjects: (NSString *) kUTTypeMovie, nil]; mediaUI.allowsEditing = YES; mediaUI.delegate = self; [self presentViewController:mediaUI animated:YES completion:nil]; } #pragma mark - UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { [self dismissViewControllerAnimated:YES completion:nil]; CommonEditVideo *videoEdit = [[CommonEditVideo alloc]init]; videoEdit.delegate = self; videoEdit.watermarkTitle = @"input"; videoEdit.watermarkPlace = AlignmentDown; //开始编辑 [videoEdit startEditVideo: [info objectForKey:UIImagePickerControllerMediaURL]]; //输出视频 [videoEdit videoOutput]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [self dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - CommonEditVideoDelegate - (void)willEdit{ NSLog(@"%@", @"开始编辑"); } - (BOOL)willExport:(AVAssetExportSession *)exporter{ NSLog(@"%@", @"编辑完成\n开始导出到文件"); // NSLog(@"%lld",exporter.asset.duration.value); return YES; } - (void)didExport:(NSURL *)url error:(NSError *)error{ if (!error){ NSLog(@"%@", url); MPMoviePlayerViewController *playerCtr = [[MPMoviePlayerViewController alloc]initWithContentURL:url]; [self presentViewController:playerCtr animated:YES completion:nil]; } } @end ```
没有评论