給你的視頻打上個(gè)人標(biāo)簽
話不多說(shuō)婶芭,先上代碼:
CommonEditVideo.h
//
// CommonEditVideo.h
// MediumEdit
//
// Created by Input on 2016/9/23.
// Copyright ? 2016年 Input. All rights reserved.
//
/**
* 使用時(shí)請(qǐng)?jiān)趇nfo.plist文件加入key: NSPhotoLibraryUsageDescription value: 打開(kāi)相冊(cè)
* 水印格式: 自定義字符 + 時(shí)間 + 機(jī)型
*
*/
#import <UIKit/UIColor.h>
#import <AVFoundation/AVFoundation.h>
@protocol CommonEditVideoDelegate <NSObject>
@required
//導(dǎo)出到文件結(jié)束, 暫時(shí)沒(méi)有加狀態(tài)處理
- (void)didExport:(nullable NSURL *) url error:(nullable NSError *) error;
//返回是否導(dǎo)出到文件, 不希望文件加水印只是臨時(shí)添加請(qǐng)返回NO
- (BOOL)willExport:(nullable AVAssetExportSession *) exporter;
//開(kāi)始編輯
- (void)willEdit;
@end
/**
* 水印位置
*/
typedef enum : NSUInteger {
AlignmentUp, //居上
AlignmentCenter, //居中
AlignmentDown, //居下
} Alignment;
@interface CommonEditVideo: NSObject
@property (nonatomic, nullable, weak) id<CommonEditVideoDelegate> delegate; //視頻編輯相關(guān)代理
@property (nonatomic, nullable, strong) NSString *watermarkTitle; //水印內(nèi)容
@property (nonatomic, nullable, strong) NSURL *url; //圖片輸出位置(默認(rèn)為相冊(cè))
@property Alignment watermarkPlace; //水印位置
@property BOOL isShowTime; //是否顯示時(shí)間(YES)
@property BOOL isShowModel; //是否顯示拍攝機(jī)型(YES)
- (nullable instancetype)init;
- (void)startEditVideo:(nonnull NSURL *) assetURL;
- (void)videoOutput;
@end
CommonEditVideo.m
//
// CommonEditVideo.m
// MediumEdit
//
// Created by Input on 2016/9/23.
// Copyright ? 2016年 Input. All rights reserved.
//
#import "CommonEditVideo.h"
#import <MobileCoreServices/UTCoreTypes.h>
#import <AssetsLibrary/ALAssetsLibrary.h>
@interface CommonEditVideo ()
@property (nonatomic, nullable, strong) AVAssetExportSession *exporter; //視頻導(dǎo)出
@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];
}
//初始化及構(gòu)造新視頻
[self exporterInitialize];
}
//導(dǎo)出結(jié)果回調(diào)
- (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{
//水印相關(guān)設(shè)定
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設(shè)置
if (!self.url){
self.exporter.outputURL = url;
}else{
self.exporter.outputURL = self.url;
}
//導(dǎo)出
if (flag){
[self.exporter exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self exportDidFinish];
});
}];
}
}
@end
解說(shuō)
? 首先創(chuàng)建一個(gè) CommonEditVideo
對(duì)象,使用這個(gè)對(duì)像得實(shí)現(xiàn)CommonEditVideoDelegate
協(xié)議昏鹃;
CommonEditVideoDelegate
協(xié)議有三個(gè)方法,分別在開(kāi)始編輯視頻及保存到文件前后調(diào)用烤咧,其中實(shí)現(xiàn)willExport返回一個(gè)真假值沛申,如果是想在放器界面添加水印在這里返回一個(gè)NO即可,參數(shù)就是已經(jīng)處理好的AVAssetExportSession
婿奔,這個(gè)時(shí)候你可以直接拿AVAssetExportSession
對(duì)象的asset
播放了芙盘;如果是要將視頻永久保存則返回一個(gè)YES,當(dāng)視頻導(dǎo)出到文件結(jié)束將會(huì)調(diào)用didExport
脸秽,這個(gè)函數(shù)會(huì)給你一個(gè)導(dǎo)出狀態(tài)及地址,默認(rèn)保存到相冊(cè)蝴乔,你也可以設(shè)置CommonEditVideo
對(duì)象的url
值记餐。
? 水印可以放在三個(gè)位置:上中下;默認(rèn)居中薇正∑停可以設(shè)置是否顯示時(shí)間及拍攝機(jī)型。自定義字符放在水印最前端挖腰。
下面的代碼就是我測(cè)試用的視圖控制器的實(shí)現(xiàn)部分
ViewController.m
//
// ViewController.m
// MediumEdit
//
// Created by Input on 2016/9/23.
// Copyright ? 2016年 Input. All rights reserved.
//
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#import <ImageIO/ImageIO.h>
#import <MediaPlayer/MPMoviePlayerViewController.h>
#import <MobileCoreServices/UTCoreTypes.h>
@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;
//開(kāi)始編輯
[videoEdit startEditVideo: [info objectForKey:UIImagePickerControllerMediaURL]];
//輸出視頻
[videoEdit videoOutput];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - CommonEditVideoDelegate
- (void)willEdit{
NSLog(@"%@", @"開(kāi)始編輯");
}
- (BOOL)willExport:(AVAssetExportSession *)exporter{
NSLog(@"%@", @"編輯完成\n開(kāi)始導(dǎo)出到文件");
// 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
測(cè)試時(shí)間不長(zhǎng)雕沿,也只試過(guò)系統(tǒng)自帶的相機(jī)拍攝的視頻處理,如果使用途中遇到BUG請(qǐng)留言猴仑,大家一起探討审轮。