给你的视频打上个人标签

给你的视频打上个人标签

编辑前
编辑后

话不多说,先上代码:

CommonEditVideo.h

//
//  CommonEditVideo.h
//  MediumEdit
//
//  Created by Input on 2016/9/23.
//  Copyright © 2016年 Input. All rights reserved.
//
/**
 * 使用时请在info.plist文件加入key: NSPhotoLibraryUsageDescription value: 打开相册
 * 水印格式: 自定义字符 + 时间 + 机型
 *
 */

#import <UIKit/UIColor.h>
#import <AVFoundation/AVFoundation.h>

@protocol  CommonEditVideoDelegate <NSObject>

@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<CommonEditVideoDelegate>     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

//
//  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;          //视频导出
@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

//
//  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;

    //开始编辑
    [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

测试时间不长,也只试过系统自带的相机拍摄的视频处理,如果使用途中遇到BUG请留言,大家一起探讨。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容