修改了GPUImage導(dǎo)入的方式燃观,也解決了視頻編輯后出現(xiàn)90旋轉(zhuǎn)的問(wèn)題褒脯。
網(wǎng)上流行的美顏濾鏡有很多,我的demo里有兩款美顏濾鏡(GPUImageBeautifyFilter
缆毁、FSKGPUImageBeautyFilter
)番川。
我本意是準(zhǔn)備寫一個(gè)相冊(cè)管理的,最后由于項(xiàng)目需求脊框,寫成了短視頻處理颁督,所以項(xiàng)目名字沒(méi)改,demo下面會(huì)給出缚陷,只是給大家一個(gè)相關(guān)思路适篙,不惜勿噴往核。下面直接上我demo里比較重要的幾個(gè)地方:
短視頻功能使用的是GPUImageVideoCamera
箫爷,由于項(xiàng)目里需要使用1:1,所以使用了濾鏡GPUImageCropFilter
聂儒,大家如果有需要虎锚,可以更高這個(gè)濾鏡的設(shè)置,下面給出相機(jī)的設(shè)置代碼:
//相機(jī)設(shè)置
- (void)customSystemSession {
WS(weakSelf)
self.imgView = [UIImageView new];
self.imgView.clipsToBounds = YES;
[self.view addSubview:self.imgView];
[self.imgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(weakSelf.topView.mas_bottom).offset(0);
make.left.equalTo(weakSelf.view.mas_left).offset(0);
make.right.equalTo(weakSelf.view.mas_right).offset(0);
make.height.mas_equalTo(SCREEN_WIDTH);
}];
//美顏相機(jī)
self.kj_videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1280x720 cameraPosition:AVCaptureDevicePositionBack];
self.kj_videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
self.kj_videoCamera.horizontallyMirrorFrontFacingCamera = YES;
self.kj_filterView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_WIDTH)];
self.kj_filterView.fillMode = kGPUImageFillModePreserveAspectRatioAndFill;
// self.filterView.center = self.view.center;
[self.imgView addSubview:self.kj_filterView];
// //剪裁濾鏡(1:1)如果有需要全屏衩婚,這里需要做出更改或者直接去掉這個(gè)濾鏡
self.kj_cropFilter = [[GPUImageCropFilter alloc] initWithCropRegion:CGRectMake(0, 44/SCREEN_HEIGHT, 1, SCREEN_WIDTH/SCREEN_HEIGHT)];
//美顏(默認(rèn)開啟美顏)
self.kj_beautifyFilter = [[FSKGPUImageBeautyFilter alloc] init];
self.kj_beautifyFilter.beautyLevel = 0.9f;//美顏程度
self.kj_beautifyFilter.brightLevel = 0.7f;//美白程度
self.kj_beautifyFilter.toneLevel = 0.9f;//色調(diào)強(qiáng)度
// self.kj_filter = [[GPUImageSaturationFilter alloc] init];
// //濾鏡組
// self.kj_filterGroup = [[GPUImageFilterGroup alloc] init];
// [self.kj_filterGroup addFilter:self.kj_cropFilter];
// [self.kj_filterGroup addFilter:self.kj_beautifyFilter];
// [self openBeautify];
[self.kj_videoCamera addAudioInputsAndOutputs];
[self.kj_videoCamera addTarget:self.kj_cropFilter];
[self.kj_cropFilter addTarget:self.kj_beautifyFilter];
[self.kj_beautifyFilter addTarget:self.kj_filterView];
[self.kj_videoCamera startCameraCapture];
}
對(duì)于相機(jī)的前后鏡頭切換窜护、閃光燈等等這里就不做說(shuō)明了,對(duì)于短視頻我的處理方案是多段視頻合并非春,用GPUImageVideoCamera
來(lái)錄制多段視頻柱徙,帶美顏濾鏡缓屠,其實(shí)這里是可以直接使用實(shí)時(shí)濾鏡的,但是由于公司只需要美顏护侮,所以這里沒(méi)做其它濾鏡的處理(實(shí)際上和美顏濾鏡的切換時(shí)一樣的道理敌完,所以實(shí)時(shí)濾鏡實(shí)際上是在每段視頻拍攝前選好濾鏡后addTarget就好了,當(dāng)然啦羊初,如果是混合使用后滨溉,要先刪除(removeTarget)后添加),但是在后續(xù)我單獨(dú)寫了一個(gè)視頻的簡(jiǎn)單編輯功能长赞,那里有對(duì)于本地視頻晦攒、圖片增加濾鏡的處理,后續(xù)會(huì)講到得哆,接下來(lái)放上短視頻中最重要的部分脯颜,視頻合并:
//確定(完成)
- (void)onCompleteAction:(UIButton *)sender {
if (self.kj_videoArray.count > 0) {
if (self.kj_videoArray.count > 1) {
//需要合并多段視頻
if (!self.kj_outPath) {
self.kj_outPath = [self getVideoOutPath];//合成后的輸出路徑
}
//判斷本地是否已有合成后的視頻文件
if ([[NSFileManager defaultManager] fileExistsAtPath:self.kj_outPath]) {
//如果存在就刪除,重新合成
[[NSFileManager defaultManager] removeItemAtPath:self.kj_outPath error:nil];
}
//音視頻合成工具
AVMutableComposition *kj_composition = [AVMutableComposition composition];
//音頻
AVMutableCompositionTrack *kj_audioTrack = [kj_composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
//視頻
AVMutableCompositionTrack *kj_videoTrack = [kj_composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
//開始合成
[KJUtility showProgressDialogText:@"視頻處理中..."];
CMTime kj_totalDuration = kCMTimeZero;
for (int i = 0; i < self.kj_videoArray.count; i ++) {
NSDictionary *localDict = self.kj_videoArray[i];
NSDictionary* options = @{AVURLAssetPreferPreciseDurationAndTimingKey:@YES};
AVAsset *kj_asset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:localDict[@"path"]] options:options];
//獲取kj_asset中的音頻
NSArray *audioArray = [kj_asset tracksWithMediaType:AVMediaTypeAudio];
AVAssetTrack *kj_assetAudio = audioArray.firstObject;
//向kj_audioTrack中加入音頻
NSError *kj_audioError = nil;
BOOL isComplete_audio = [kj_audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, kj_asset.duration)
ofTrack:kj_assetAudio
atTime:kj_totalDuration
error:&kj_audioError];
NSLog(@"加入音頻%d isComplete_audio:%d error:%@", i, isComplete_audio, kj_audioError);
//獲取kj_asset中的視頻
NSArray *videoArray = [kj_asset tracksWithMediaType:AVMediaTypeVideo];
AVAssetTrack *kj_assetVideo = videoArray.firstObject;
//向kj_videoTrack中加入視頻
NSError *kj_videoError = nil;
BOOL isComplete_video = [kj_videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, kj_asset.duration)
ofTrack:kj_assetVideo
atTime:kj_totalDuration
error:&kj_videoError];
NSLog(@"加入視頻%d isComplete_video:%d error:%@", i, isComplete_video, kj_videoError);
kj_totalDuration = CMTimeAdd(kj_totalDuration, kj_asset.duration);
}
//這里可以加水印的柳恐,但在這里不做水印處理
//視頻導(dǎo)出處理
AVAssetExportSession *kj_export = [AVAssetExportSession exportSessionWithAsset:kj_composition
presetName:AVAssetExportPreset1280x720];
kj_export.outputURL = [NSURL fileURLWithPath:self.kj_outPath];
kj_export.outputFileType = AVFileTypeMPEG4;
kj_export.shouldOptimizeForNetworkUse = YES;
WS(weakSelf)
[kj_export exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
[KJUtility hideProgressDialog];
if (weakSelf.kjFileDelegate && [weakSelf.kjFileDelegate respondsToSelector:@selector(kj_videoFileCompleteLocalPath:)]) {
//合成視頻成功后伐脖,刪除小段視頻
[weakSelf clearAllVideo];
NSLog(@"%@",weakSelf.kj_outPath);
[weakSelf.kjFileDelegate kj_videoFileCompleteLocalPath:weakSelf.kj_outPath];
} else {
[weakSelf saveVideoToLibrary];
}
});
}];
} else {
//只有一段視頻
[KJUtility hideProgressDialog];
NSDictionary *dict = self.kj_videoArray.firstObject;
self.kj_outPath = dict[@"path"];
if (self.kjFileDelegate && [self.kjFileDelegate respondsToSelector:@selector(kj_videoFileCompleteLocalPath:)]) {
[self.kjFileDelegate kj_videoFileCompleteLocalPath:self.kj_outPath];
} else {
[self saveVideoToLibrary];
}
}
}
}
注釋都說(shuō)的很清楚了,就不對(duì)代碼做相關(guān)解釋了乐设,最主要的還是AVMutableComposition
讼庇、AVMutableCompositionTrack
、AVMutableCompositionTrack
的簡(jiǎn)單實(shí)用近尚,百度一下就能看到相關(guān)屬性的說(shuō)明蠕啄,其它的就和普通相機(jī)的處理沒(méi)什么不同的。
另一個(gè)方面戈锻,就是視頻的剪輯了歼跟,大家對(duì)于上傳服務(wù)器的視頻,由于時(shí)間太長(zhǎng)或者是視頻太大格遭,這就需要對(duì)視頻進(jìn)行壓縮和裁剪處理哈街,這里對(duì)于時(shí)間的選取UI層上不做說(shuō)明了,可以看的demo里的UI處理拒迅,下面給出視頻剪裁的代碼:
- (void)onCompleteButtonAction:(UIButton *)sender {
//開始剪裁
[self.kj_player pause];
//開始時(shí)間
CMTime startTime = CMTimeMakeWithSeconds((self.collectionView.contentOffset.x+self.btnStart.frame.origin.x)*self.pixel_time, self.kj_player.currentItem.duration.timescale);
//長(zhǎng)度
CGFloat length = (self.btnEnd.frame.origin.x+self.btnEnd.frame.size.width-self.btnStart.frame.origin.x)*self.pixel_time;
CMTime time_total = [self.kj_player.currentItem duration];
if (length == 1.0*time_total.value/time_total.timescale) {
if (self.kj_videoCapturedelegate && [self.kj_videoCapturedelegate respondsToSelector:@selector(kj_didCaptureCompleteForPath:)]) {
AVURLAsset *urlAsset = (AVURLAsset *)self.kj_player.currentItem.asset;
[self.kj_videoCapturedelegate kj_didCaptureCompleteForPath:urlAsset.URL.path];
} else {
[self onCancelButtonAction:nil];
}
return;
}
[KJUtility showProgressDialogText:@"開始處理"];
if (length > self.kj_maxTime) {
length = self.kj_maxTime;
}
CMTime videoLenth = CMTimeMakeWithSeconds(length, self.kj_player.currentItem.duration.timescale);
CMTimeRange videoTimeRange = CMTimeRangeMake(startTime, videoLenth);
AVAssetExportSession * exportSession = [[AVAssetExportSession alloc] initWithAsset:self.kj_player.currentItem.asset presetName:AVAssetExportPresetMediumQuality];
exportSession.timeRange = videoTimeRange;
NSString *path = [KJUtility kj_getKJAlbumFilePath];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyyMMddHHmmss"];
NSString *fileName = [NSString stringWithFormat:@"%@-%@",[formatter stringFromDate:[NSDate date]], @"kj_video.mp4"];
path = [path stringByAppendingPathComponent:fileName];
exportSession.outputURL = [NSURL fileURLWithPath:path];
exportSession.outputFileType = AVFileTypeMPEG4;
__block BOOL completeOK = NO;
WS(weakSelf)
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch (exportSession.status) {
case AVAssetExportSessionStatusUnknown:
break;
case AVAssetExportSessionStatusWaiting:
break;
case AVAssetExportSessionStatusExporting:
break;
case AVAssetExportSessionStatusCompleted:
completeOK = YES;
break;
case AVAssetExportSessionStatusFailed:
break;
case AVAssetExportSessionStatusCancelled:
break;
};
if (completeOK) {
dispatch_async(dispatch_get_main_queue(), ^{
[KJUtility showAllTextDialog:weakSelf.view Text:@"視頻截取成功"];
if (weakSelf.kj_videoCapturedelegate && [weakSelf.kj_videoCapturedelegate respondsToSelector:@selector(kj_didCaptureCompleteForPath:)]) {
[KJUtility hideProgressDialog];
[weakSelf.kj_videoCapturedelegate kj_didCaptureCompleteForPath:path];
} else {
//保存到相冊(cè)
[KJUtility kj_saveVideoToLibraryForPath:path completeHandler:^(NSString *localIdentifier, BOOL isSuccess) {
if (isSuccess) {
NSFileManager *fileManger = [[NSFileManager alloc] init];
[fileManger removeItemAtPath:path error:nil];
}
dispatch_async(dispatch_get_main_queue(), ^{
[KJUtility hideProgressDialog];
[weakSelf onCancelButtonAction:nil];
});
}];
}
});
} else {
[KJUtility showAllTextDialog:weakSelf.view Text:@"視頻截取失敗"];
}
dispatch_async(dispatch_get_main_queue(), ^{
[KJUtility hideProgressDialog];
});
}];
}
接下來(lái)說(shuō)說(shuō)利用GPUImage對(duì)本地視頻加濾鏡骚秦,如果需要選取濾鏡后,能實(shí)時(shí)看到濾鏡效果璧微,需要用到GPUImageMovie
來(lái)預(yù)覽作箍,濾鏡合成還需要使用到GPUImageMovieWriter
,由于GPUImageMovie
預(yù)覽視頻時(shí)是沒(méi)有聲音的前硫,所以還需要一個(gè)播放器來(lái)播放視頻胞得,這個(gè)可以看我的demo處理,下面貼出對(duì)本地視頻合成濾鏡的處理:
//合成濾鏡
- (void)filterCompositionForFilter:(GPUImageOutput<GPUImageInput> *)filter withVideoUrl:(NSURL *)videoUrl {
if (videoUrl) {
WS(weakSelf)
GPUImageOutput<GPUImageInput> *tmpFilter = filter;
kj_movieComposition = [[GPUImageMovie alloc] initWithURL:videoUrl];
kj_movieComposition.runBenchmark = YES;
kj_movieComposition.playAtActualSpeed = NO;
[kj_movieComposition addTarget:tmpFilter];
//合成后的視頻路徑
NSString *newPath = [KJUtility kj_getKJAlbumFilePath];
newPath = [newPath stringByAppendingPathComponent:[KJUtility kj_getNewFileName]];
unlink([newPath UTF8String]);
NSLog(@"%f,%f",self.kj_player.currentItem.presentationSize.height,self.kj_player.currentItem.presentationSize.width);
CGSize videoSize = self.kj_player.currentItem.presentationSize;
NSURL *tmpUrl = [NSURL fileURLWithPath:newPath];
[self.kj_newVideoPathArray addObject:tmpUrl];
kj_movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:tmpUrl size:videoSize];
kj_movieWriter.shouldPassthroughAudio = YES;
//這里原GPUImage始沒(méi)有這個(gè)屬性的屹电,修改后才有
kj_movieWriter.allowWriteAudio = YES;
kj_movieComposition.audioEncodingTarget = kj_movieWriter;
[tmpFilter addTarget:kj_movieWriter];
[kj_movieComposition enableSynchronizedEncodingUsingMovieWriter:kj_movieWriter];
[kj_movieWriter startRecording];
[kj_movieComposition startProcessing];
__weak GPUImageMovieWriter *weakmovieWriter = kj_movieWriter;
[kj_movieWriter setCompletionBlock:^{
NSLog(@"濾鏡添加成功");
[tmpFilter removeTarget:weakmovieWriter];
[weakmovieWriter finishRecording];
dispatch_async(dispatch_get_main_queue(), ^{
if (weakSelf.kj_selectedMusic) {
//合成音樂(lè)
[weakSelf musicCompositionForMusicInfo:weakSelf.kj_selectedMusic withVideoPath:weakSelf.kj_newVideoPathArray.lastObject];
} else {
[weakSelf saveVideoToLib];
}
});
}];
[kj_movieWriter setFailureBlock:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"濾鏡添加失斀捉!:%@", error);
if ([[NSFileManager defaultManager] fileExistsAtPath:newPath]) {
NSError *delError = nil;
[[NSFileManager defaultManager] removeItemAtPath:newPath error:&delError];
if (delError) {
NSLog(@"刪除沙盒路徑失斣狙病:%@", delError);
}
}
[weakSelf.kj_newVideoPathArray removeLastObject];
[KJUtility hideProgressDialog];
});
}];
}
}
下面來(lái)說(shuō)說(shuō)對(duì)于本地視頻添加音樂(lè)的處理,添加音樂(lè)這里我的注釋寫的很清楚牧愁,直接上代碼(還是利用前面提到的是哪個(gè)類來(lái)處理的瓷炮,原理就是把視頻和音頻單獨(dú)提出來(lái),最后放在工具里合并):
//合成音樂(lè)
- (void)musicCompositionForMusicInfo:(NSDictionary *)musicInfo withVideoPath:(NSURL *)videoUrl {
if (musicInfo && videoUrl) {
//音樂(lè)
NSString *audioPath = [[NSBundle mainBundle] pathForResource:musicInfo[@"music"] ofType:@"mp3"];
NSURL *audioUrl = [NSURL fileURLWithPath:audioPath];
//合成后的視頻輸出路徑
NSString *newPath = [KJUtility kj_getKJAlbumFilePath];
newPath = [newPath stringByAppendingPathComponent:[KJUtility kj_getNewFileName]];
unlink([newPath UTF8String]);
NSURL *newVideoPath = [NSURL fileURLWithPath:newPath];
//合成工具
AVMutableComposition *kj_composition = [AVMutableComposition composition];
//音頻
AVMutableCompositionTrack *kj_audioTrack = [kj_composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
//視頻
AVMutableCompositionTrack *kj_videoTrack = [kj_composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
NSDictionary* kj_options = @{AVURLAssetPreferPreciseDurationAndTimingKey:@YES};
//視頻AVAsset
AVURLAsset *kj_videoAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:kj_options];
//視頻時(shí)間范圍(合成的音樂(lè)不能超過(guò)這個(gè)時(shí)間范圍)
CMTimeRange kj_videoTimeRange = CMTimeRangeMake(kCMTimeZero, kj_videoAsset.duration);
//采集kj_videoAsset中的視頻
NSArray *videoArray = [kj_videoAsset tracksWithMediaType:AVMediaTypeVideo];
AVAssetTrack *kj_assetVideo = videoArray.firstObject;
//采集的視頻加入到視頻通道kj_videoTrack
NSError *kj_videoError = nil;
BOOL isComplete_video = [kj_videoTrack insertTimeRange:kj_videoTimeRange
ofTrack:kj_assetVideo
atTime:kCMTimeZero
error:&kj_videoError];
NSLog(@"加入視頻isComplete_video:%d error:%@",isComplete_video, kj_videoError);
//音頻AVAsset
AVURLAsset *kj_audioAsset = [[AVURLAsset alloc] initWithURL:audioUrl options:kj_options];
//采集kj_audioAsset中的音頻
NSArray *audioArray = [kj_audioAsset tracksWithMediaType:AVMediaTypeAudio];
AVAssetTrack *kj_assetAudio = audioArray.firstObject;
//音頻的范圍
CMTimeRange kj_audioTimeRange = CMTimeRangeMake(kCMTimeZero, kj_audioAsset.duration);
if (CMTimeCompare(kj_audioAsset.duration, kj_videoAsset.duration)) {//當(dāng)視頻時(shí)間小于音頻時(shí)間
kj_audioTimeRange = CMTimeRangeMake(kCMTimeZero, kj_videoAsset.duration);
}
//采集的音頻加入到音頻通道kj_audioTrack
NSError *kj_audioError = nil;
BOOL isComplete_audio = [kj_audioTrack insertTimeRange:kj_audioTimeRange
ofTrack:kj_assetAudio
atTime:kCMTimeZero
error:&kj_audioError];
NSLog(@"加入音頻isComplete_audio:%d error:%@",isComplete_audio, kj_audioError);
//因?yàn)橐4嫦鄡?cè)递宅,所以設(shè)置高質(zhì)量, 這里可以根據(jù)實(shí)際情況進(jìn)行更改
WS(weakSelf)
[KJUtility kj_compressedVideoAsset:kj_composition withPresetName:AVAssetExportPresetHighestQuality withNewSavePath:newVideoPath withCompleteBlock:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(@"轉(zhuǎn)碼失斈锵恪:%@", error);
[KJUtility hideProgressDialog];
} else {
[weakSelf.kj_newVideoPathArray addObject:newVideoPath];
[weakSelf saveVideoToLib];
}
});
}];
}
}
視頻壓縮這個(gè)不用多說(shuō)了,網(wǎng)上太多了办龄,基本上是iOS自帶的就有這個(gè)方法烘绽,很簡(jiǎn)單的幾行代碼:
/**
視頻轉(zhuǎn)碼/壓縮
@param asset AVAsset
@param presetName 視頻質(zhì)量(建議壓縮使用AVAssetExportPresetMediumQuality,存相冊(cè)AVAssetExportPreset1920x1080俐填,根據(jù)需求設(shè)置)
@param savePath 保存的路徑
@param completeBlock 返回狀態(tài)
*/
+ (void)kj_compressedVideoAsset:(AVAsset *)asset
withPresetName:(NSString *)presetName
withNewSavePath:(NSURL *)savePath
withCompleteBlock:(void(^)(NSError *error))completeBlock {
AVAssetExportSession *kj_export = [AVAssetExportSession exportSessionWithAsset:asset
presetName:presetName];
kj_export.outputURL = savePath;
kj_export.outputFileType = AVFileTypeMPEG4;
kj_export.shouldOptimizeForNetworkUse = YES;
[kj_export exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (kj_export.status == AVAssetExportSessionStatusCompleted) {
if (completeBlock) {
completeBlock(nil);
}
} else if (kj_export.status == AVAssetExportSessionStatusFailed) {
if (completeBlock) {
completeBlock(kj_export.error);
}
} else {
NSLog(@"當(dāng)前壓縮進(jìn)度:%f",kj_export.progress);
}
});
}];
}
這里說(shuō)的都是短視頻的處理方案安接,事實(shí)上demo里有相冊(cè)的處理,但是沒(méi)做實(shí)時(shí)更新的處理英融,所以有實(shí)時(shí)更新的需要你自己添加代碼監(jiān)聽并更新顯示盏檐,當(dāng)然啦,我的UI肯定不適合大家驶悟,所以這里只是提供一種方法胡野,不是寫的工具能直接拿來(lái)就能使用,demo支持iOS8*痕鳍。下面給出整個(gè)demo的github:
Kegen
demo很粗糙硫豆,但是注釋都有寫,勿直接使用笼呆、熊响、、GPUImage很強(qiáng)大诗赌,還有很多都沒(méi)使用到汗茄,有需要可以研究研究,很實(shí)用铭若。demo中視頻會(huì)在相冊(cè)里存兩份洪碳,一份是壓縮后的,一個(gè)是沒(méi)壓縮的奥喻,這個(gè)是我做測(cè)試的時(shí)候存的偶宫,如果大家在測(cè)試的時(shí)候不需要非迹,這個(gè)可以在調(diào)用的時(shí)候可以去掉保存环鲤,我的調(diào)用代碼:
- (IBAction)onVideoButtonAction:(UIButton *)sender {
KJVideoAlbumController *ctrl = [[KJVideoAlbumController alloc] init];
ctrl.kj_minTime = 2.0;
ctrl.kj_maxTime = 15.0f;
WS(weakSelf)
ctrl.kj_complete = ^(NSURL *outPath) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf editViewPath:outPath];
});
};
UINavigationController *navc = [[UINavigationController alloc] initWithRootViewController:ctrl];
navc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:navc animated:YES completion:nil];
}
- (void)editVideo:(NSString *)localIdentifier {
WS(weakSelf)
[KJUtility kj_getAssetForLocalIdentifier:localIdentifier completionHandler:^(PHAsset *kj_object) {
dispatch_async(dispatch_get_main_queue(), ^{
[KJUtility kj_requestVideoForAsset:kj_object completion:^(AVURLAsset *asset) {
dispatch_async(dispatch_get_main_queue(), ^{
KJEditVideoViewController *ctrl = [[KJEditVideoViewController alloc] init];
ctrl.kj_localVideo = asset;
ctrl.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
UINavigationController *navc = [[UINavigationController alloc] initWithRootViewController:ctrl];
[weakSelf presentViewController:navc animated:YES completion:nil];
});
}];
});
}];
}
- (void)editViewPath:(NSURL *)path {
KJEditVideoViewController *ctrl = [[KJEditVideoViewController alloc] init];
ctrl.kj_localVideo = path;
ctrl.kj_isSelectCover = YES;
WS(weakSelf)
/**
* videoPath壓縮后的視頻
* localIdentifier保存到相冊(cè)的高質(zhì)量視頻(如果沒(méi)有添加濾鏡或音樂(lè),返回nil)
* kj_cover 封面圖
*/
ctrl.editCompleteBlock = ^(NSURL *videoPath, NSString *localidentifier, UIImage *kj_cover) {
[weakSelf saveVideoToLibVideoUrl:videoPath];
};
ctrl.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
UINavigationController *navc = [[UINavigationController alloc] initWithRootViewController:ctrl];
[self presentViewController:navc animated:YES completion:nil];
}
//保存到相冊(cè)
- (void)saveVideoToLibVideoUrl:(NSURL *)url {
[KJUtility kj_saveVideoToLibraryForPath:url.path completeHandler:^(NSString *localIdentifier, BOOL isSuccess) {
if (isSuccess) {
NSLog(@"保存到相冊(cè)成功");
} else {
NSLog(@"保存到相冊(cè)失敗");
}
}];
}
另外可能大家在看這代碼里面有很多都是調(diào)用KJUtility的代碼憎兽,把h文件放出來(lái)就知道了:
//
// KJUtility.h
// KJAlbumDemo
//
// Created by JOIN iOS on 2017/9/5.
// Copyright ? 2017年 Kegem. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Photos/Photos.h>
#import "UIKit+BaseExtension.h"
#import <YYWebImage.h>
#import <Masonry.h>
#import <YYAnimatedImageView.h>
#import <GPUImage/GPUImage.h>
@protocol KJCustomCameraDelegate <NSObject>
- (void)kj_didStartTakeAction;
- (void)kj_didResetTakeAction;
- (void)kj_didCompleteAction;
- (void)kj_didCancelAction;
@end
@protocol KJVideoFileDelegate <NSObject>
//這里是不保存到相冊(cè)冷离,返回錄制完后的地址
- (void)kj_videoFileCompleteLocalPath:(NSString *)kj_outPath;
@end
//狀態(tài)條的高
#define StatusBarHeight [[UIApplication sharedApplication] statusBarFrame].size.height
//得到屏幕bounds
#define SCREEN_SIZE [UIScreen mainScreen].bounds
//得到屏幕height
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
//得到屏幕width
#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
//self
#define WS(weakSelf) __weak typeof (self) weakSelf = self;
//顏色
#define sYellowColor 0xffd700
@interface KJUtility : NSObject
+ (void)showAllTextDialog:(UIView *)view Text:(NSString *)text;
+ (void)showProgressDialogText:(NSString *)text;
+ (void)hideProgressDialog;
//傳入 秒 得到 xx分鐘xx秒
+ (NSString *)getMMSSFromSS:(NSInteger)seconds;
/**
拍攝的圖片/視頻等存放的路徑
@return 文件路徑
*/
+ (NSString *)kj_getKJAlbumFilePath;
/**
視頻名
@return 返回視頻名
*/
+ (NSString *)kj_getNewFileName;
/**
根據(jù)PHAsset獲取圖片
@param asset PHAsset
@param isSynchronous 同步-YES 異步-NO
@param completion 返回圖片
*/
+ (void)kj_requestImageForAsset:(PHAsset *)asset
withSynchronous:(BOOL)isSynchronous
completion:(void (^)(UIImage *image))completion;
/**
根據(jù)PHAsset獲取視頻
@param kj_asset PHAsset
@param completion AVURLAsset
*/
+ (void)kj_requestVideoForAsset:(PHAsset *)kj_asset
completion:(void (^)(AVURLAsset *asset))completion;
/**
獲取視頻的縮略圖方法
@param urlAsset 視頻的本地路徑
@param start 開始時(shí)間
@param timescale scale
@return 視頻截圖
*/
+ (UIImage *)kj_getScreenShotImageFromVideoPath:(AVURLAsset *)urlAsset
withStart:(CGFloat)start
withTimescale:(CGFloat)timescale ;
/**
圖片保存到系統(tǒng)相冊(cè)
@param image 圖片
@param completionHandler 返回結(jié)果
*/
+ (void)kj_savePhotoToLibraryForImage:(UIImage *)image
completeHandler:(void(^)(NSString *localIdentifier, BOOL isSuccess))completionHandler;
/**
視頻保存到系統(tǒng)相冊(cè)
@param path 視頻路徑
@param completionHandler 返回結(jié)果
*/
+ (void)kj_saveVideoToLibraryForPath:(NSString *)path
completeHandler:(void(^)(NSString *localIdentifier, BOOL isSuccess))completionHandler;
/**
根據(jù)相冊(cè)localid獲取PHAsset
@param localIdentifier 相冊(cè)id
@param completionHandler 返回PHAsset對(duì)象
*/
+ (void)kj_getAssetForLocalIdentifier:(NSString *)localIdentifier
completionHandler:(void(^)(PHAsset *kj_object))completionHandler;
/**
視頻轉(zhuǎn)碼/壓縮
@param asset AVAsset
@param presetName 視頻質(zhì)量(建議壓縮上傳使用AVAssetExportPresetMediumQuality根據(jù)需求設(shè)置)
@param savePath 保存的路徑
@param completeBlock 返回狀態(tài)
*/
+ (void)kj_compressedVideoAsset:(AVAsset *)asset
withPresetName:(NSString *)presetName
withNewSavePath:(NSURL *)savePath
withCompleteBlock:(void(^)(NSError *error))completeBlock;
/**
相冊(cè)授權(quán)
@param ctrl 當(dāng)前控制器
@param completeBlock 返回是否允許訪問(wèn)
*/
+ (void)kj_photoLibraryAuthorizationStatus:(UIViewController *)ctrl
completeBlock:(void (^)(BOOL allowAccess))completeBlock;
/**
相機(jī)授權(quán)
@param ctrl 當(dāng)前控制器
@param completeBlock 返回是否允許訪問(wèn)
*/
+ (void)kj_cameraAuthorizationStatus:(UIViewController *)ctrl
completeBlock:(void (^)(BOOL allowAccess))completeBlock;
/**
麥克風(fēng)授權(quán)
@param ctrl 當(dāng)前控制器
@param completeBlock 返回是否允許訪問(wèn)
*/
+ (void)kj_requestRecordPermission:(UIViewController *)ctrl
completeBlock:(void (^)(BOOL allowAccess))completeBlock;
/**
授權(quán)提示彈出框(跳轉(zhuǎn)到手機(jī)設(shè)置-應(yīng)用權(quán)限)
@param ctrl 當(dāng)前控制器
@param title 提示語(yǔ)
*/
+ (void)kj_authorizationAlert:(UIViewController *)ctrl
tipMessage:(NSString *)title;
/**
圖片加濾鏡
@param image 源圖
@param filterName 濾鏡名字
@return 合成濾鏡后的圖片
*/
+ (UIImage *)kj_imageProcessedUsingGPUImage:(UIImage *)image
withFilterName:(NSString *)filterName;
@end
這里本意是在使用完短視頻的功能后吵冒,刪除沙盒中的kjalbum的文件夾。好了西剥,不在過(guò)多的贅述了痹栖,大家直接下載demo看看就知道了,下面給出效果展示gif瞭空,由于太大揪阿,壓縮處理了,可能有點(diǎn)模糊: