給你的視頻打上個(gè)人標(biāo)簽

給你的視頻打上個(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)留言猴仑,大家一起探討审轮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市辽俗,隨后出現(xiàn)的幾起案子疾渣,更是在濱河造成了極大的恐慌,老刑警劉巖崖飘,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榴捡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡朱浴,警方通過(guò)查閱死者的電腦和手機(jī)吊圾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)翰蠢,“玉大人项乒,你說(shuō)我怎么就攤上這事×翰祝” “怎么了板丽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)趁尼。 經(jīng)常有香客問(wèn)我埃碱,道長(zhǎng),這世上最難降的妖魔是什么酥泞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任砚殿,我火速辦了婚禮,結(jié)果婚禮上芝囤,老公的妹妹穿的比我還像新娘似炎。我一直安慰自己辛萍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布羡藐。 她就那樣靜靜地躺著贩毕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪仆嗦。 梳的紋絲不亂的頭發(fā)上辉阶,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音瘩扼,去河邊找鬼谆甜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛集绰,可吹牛的內(nèi)容都是我干的规辱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼栽燕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼罕袋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起碍岔,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤炫贤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后付秕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體兰珍,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年询吴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掠河。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猛计,死狀恐怖唠摹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奉瘤,我是刑警寧澤勾拉,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站盗温,受9級(jí)特大地震影響藕赞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卖局,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一斧蜕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砚偶,春花似錦批销、人聲如沸洒闸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)丘逸。三九已至,卻和暖如春掀宋,著一層夾襖步出監(jiān)牢的瞬間深纲,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工布朦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昼窗。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓是趴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親澄惊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子唆途,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容