视频导出失败
jiangyuan0336 opened this issue · 35 comments
提bug前必看
请先回答下列三个问题,否则不允处理,谢谢配合。
1、我最新的Demo是否有这个bug?【如果Demo没问题,请升级新版】
答:有
2、你用的是什么版本?升级到最新版后是否正常?
答:version 3.2.0,已经是最新的
3、是否有改动过我库内部的代码?【如有,请告诉我你改了什么】
答:没有改
bug内容描述
就是相册里面有两个视频,可以选取,但是如果导出的话就出现视频导出失败,TZImagePickerController[54670:16431685] 视频导出失败:视频导出失败,error:Error Domain=AVFoundationErrorDomain Code=-11800 "这项操作无法完成" UserInfo={NSLocalizedFailureReason=发生未知错误(-12780), NSLocalizedDescription=这项操作无法完成, NSUnderlyingError=0x2830ea9a0 {Error Domain=NSOSStatusErrorDomain Code=-12780 "(null)"}}
我如何复现这个bug?
我可以把视频给你
截图
其它说明
有没有其它要补充的?比如你的初始化TZImagePickerController的代码
IMG_3964.mp4.zip
IMG_3966.mp4.zip
这是导出失败的两个视频,麻烦您有时间看看。
@jiangyuan0336 已复现,没啥时间看...
求PR呀...
PR是什么?
@jiangyuan0336 Pull Request哈,你fork TZ这个仓库,修复这个bug,再提交PR到TZ这个仓库,我验证后merge下发个版,以后大家就都可以使用你修复后的版本咯
同样遇到了这个问题,
我在getVideoOutputPathWithAsset: presetName: success: failure:
里将presetName 改为了AVAssetExportPresetLowQuality|AVAssetExportPresetMediumQuality 视频可以正常导出, IMG_3964.mp4.zip 也可正常导出 是否会引起其他问题还未验证
视频导出失败:视频导出失败,error:Error Domain=AVFoundationErrorDomain Code=-11800 "这项操作无法完成" UserInfo={NSLocalizedFailureReason=发生未知错误(-12780), NSLocalizedDescription=这项操作无法完成, NSUnderlyingError=0x28146f930 {Error Domain=NSOSStatusErrorDomain Code=-12780 "(null)"}}
只有把presetName 变成AVAssetExportPresetLowQuality才可以
我打印了一下AVAssetExportPreset的枚举值,发现这个视频包含AVAssetExportPresetHigh这个类型,但是导出确实存在问题的
搜了一圈好像挺多人都遇到这个错误,但是都没有合适的解法...
presetName设置为AVAssetExportPresetLowQuality后IMG_3964.mp4确实可以导出了,原因不明...
如果导出压缩失败了 是不是可以直接把这个资源文件写入呢 不通过压缩
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
PHVideoRequestOptions* options = [[PHVideoRequestOptions alloc] init];
options.version = PHVideoRequestOptionsVersionOriginal;
options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
options.networkAccessAllowed = YES;
[[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset* avasset, AVAudioMix* audioMix, NSDictionary* info){
// NSLog(@"Info:\n%@",info);
AVURLAsset *videoAsset = (AVURLAsset*)avasset;
//在处理120帧无声音视频的时候,直接崩溃,原因是AVURLAsset *videoAsset = (AVURLAsset *)asset;因为asset是个基类,它有时会是AVComposition而不是AVURLAsset,这样就得不到视频的url,从而导致程序崩溃
if ([videoAsset isKindOfClass:[AVURLAsset class]]) {
if (videoAsset.URL){
NSString *outputPath = self.outPutFile;
NSData *data = [NSData dataWithContentsOfURL:videoAsset.URL];
if (data){
[data writeToFile:outputPath atomically:YES];
success(outputPath);
}else{
failure(@"导出失败", nil);
}
}else{
failure(@"导出失败", nil);
}
}else{
failure(@"导出失败", nil);
}
}];
// [self startExportVideoWithVideoAsset:videoAsset presetName:presetName success:success failure:failure];
}
我现在没有使用他给的指定presetName的方法 而是用的上面这个,就不会出现这样的问题了
NSArray *presets = [AVAssetExportSession exportPresetsCompatibleWithAsset:videoAsset];
NSNumber *size;
[videoAsset.URL getResourceValue:&size forKey:NSURLFileSizeKey error:nil];
if([size floatValue]<4194304){
presetName= AVAssetExportPresetHighestQuality;
}
//视频压缩
if ([presets containsObject:presetName]) {
NSError *error = nil;
AVAssetTrack *assetVideoTrack = nil;
AVAssetTrack *assetAudioTrack = nil;
CMTime start = CMTimeMakeWithSeconds(0, videoAsset.duration.timescale);
CMTimeRange range = CMTimeRangeMake(start, videoAsset.duration);
// Check if the asset contains video and audio tracks
if ([[videoAsset tracksWithMediaType:AVMediaTypeVideo] count] != 0) {
assetVideoTrack = [videoAsset tracksWithMediaType:AVMediaTypeVideo][0];
}
if ([[videoAsset tracksWithMediaType:AVMediaTypeAudio] count] != 0) {
assetAudioTrack = [videoAsset tracksWithMediaType:AVMediaTypeAudio][0];
}
CMTime insertionPoint = kCMTimeZero;
AVMutableComposition *composition = [[AVMutableComposition alloc] init];
// Insert the video and audio tracks from AVAsset
if (assetVideoTrack != nil) {
// 视频通道 工程文件中的轨道,有音频轨、视频轨等,里面可以插入各种对应的素材
AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// 视频方向
[compositionVideoTrack setPreferredTransform:assetVideoTrack.preferredTransform];
// 把视频轨道数据加入到可变轨道中 这部分可以做视频裁剪TimeRange
[compositionVideoTrack insertTimeRange:range ofTrack:assetVideoTrack atTime:insertionPoint error:&error];
}
if (assetAudioTrack != nil) {
AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
compositionAudioTrack.preferredTransform = assetAudioTrack.preferredTransform;
[compositionAudioTrack insertTimeRange:range ofTrack:assetAudioTrack atTime:insertionPoint error:&error];
}
NSDateFormatter *formater = [[NSDateFormatter alloc] init];
NSString *outputPath = [NSHomeDirectory() stringByAppendingFormat:@"/tmp/video-%@.mp4", [formater stringFromDate:[NSDate date]]];
if (videoAsset.URL && videoAsset.URL.lastPathComponent) {
outputPath = [outputPath stringByReplacingOccurrencesOfString:@".mp4" withString:[NSString stringWithFormat:@"-%@", videoAsset.URL.lastPathComponent]];
}
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:presetName];
NSURL *url = [NSURL fileURLWithPath:outputPath];
NSFileManager *fm = [NSFileManager new];
if ([fm fileExistsAtPath:outputPath]) {
[fm removeItemAtPath:outputPath error:nil];
}
/** 删除原来剪辑的视频 */
[fm removeItemAtPath:outputPath error:nil];
exportSession.outputURL = url;
NSLog(@"%@",outputPath);
NSArray *supportedTypeArray = exportSession.supportedFileTypes;
if ([supportedTypeArray containsObject:AVFileTypeMPEG4]) {
exportSession.outputFileType = AVFileTypeMPEG4;
} else if (supportedTypeArray.count == 0) {
if (handler) handler(@"该视频类型暂不支持上传",nil);
NSLog(@"No supported file types 视频类型暂不支持导出");
return;
} else {
exportSession.outputFileType = [supportedTypeArray objectAtIndex:0];
}
if (![[NSFileManager defaultManager] fileExistsAtPath:[NSHomeDirectory() stringByAppendingFormat:@"/tmp"]]) {
[[NSFileManager defaultManager] createDirectoryAtPath:[NSHomeDirectory() stringByAppendingFormat:@"/tmp"] withIntermediateDirectories:YES attributes:nil error:nil];
}
if (videoAsset.duration.timescale == 0 || exportSession == nil) {
/** 这个情况AVAssetExportSession会卡死 */
NSError *failError = [NSError errorWithDomain:@"LFVideoExportSessionError" code:(-100) userInfo:@{NSLocalizedDescriptionKey:@"exportSession init fail"}];
if (handler) handler(@"视频压缩失败",failError);
return;
}
WS(weakSelf)
[exportSession exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
switch (exportSession.status) {
case AVAssetExportSessionStatusFailed:
NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export canceled");
break;
case AVAssetExportSessionStatusCompleted:
NSLog(@"Export completed");
break;
default:
break;
}
if ([exportSession status] == AVAssetExportSessionStatusCompleted && [fm fileExistsAtPath:outputPath]) {
//成功
[weakSelf uploadVideo:outputPath success:success failure:handler];
} else {
if (handler) handler([[exportSession error] localizedDescription],exportSession.error);
}
});
}];
}else{
if (handler) handler(@"视频压缩失败",nil);
}
使用这个可以成功压缩上面的两个视频 具体原理还在研究
我也遇到了这个问题,即使使用 @mlp1995 的方法,但是只要是某些android上传的视频,使用iOS手机保存到相册,再去压缩导出依然不行。
后来我使用了七牛的框架(PLShortVideoKit)中的PLSAVAssetExportSession类替换了AVAssetExportSession,具体代码如下:
NSMutableDictionary *outputSettings = [NSMutableDictionary dictionary];
NSMutableDictionary *movieSettings = [NSMutableDictionary dictionary];
outputSettings[PLSMovieSettingsKey] = movieSettings;
movieSettings[PLSAssetKey] = videoAsset;
movieSettings[PLSStartTimeKey] = [NSNumber numberWithFloat:0.f];
movieSettings[PLSDurationKey] = [NSNumber numberWithFloat:CMTimeGetSeconds(videoAsset.duration)];
movieSettings[PLSVolumeKey] = [NSNumber numberWithFloat:1.0f];
PLSAVAssetExportSession *exportSession = [[PLSAVAssetExportSession alloc] initWithAsset:videoAsset];
exportSession.outputFileType = PLSFileTypeMPEG4;
exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.outputSettings = outputSettings;
exportSession.isExportMovieToPhotosAlbum = NO;
[exportSession setCompletionBlock:^(NSURL *url) {
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
success(url.path);
}
});
}];
[exportSession setFailureBlock:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showErrorWithStatus:@"合成视频失败"];
});
}];
[exportSession exportAsynchronously];
上面的方法对我无效。
我发现视频导出失败的问题是在同时选择多个视频的时候出现的,而一次只选择一个视频时就没有问题。我的解决方法是,创建一个串行队列,将导出视频并上传的异步任务放到队列中,通过信号量来控制任务的执行,保证每次只导出一个视频,上传完成后再导出下一个,就没再出现这个问题。总之,这个问题应该是同时导出多个视频导致的。
希望能帮助到有需要的人。
上面的方法对我无效。
我发现视频导出失败的问题是在同时选择多个视频的时候出现的,而一次只选择一个视频时就没有问题。我的解决方法是,创建一个串行队列,将导出视频并上传的异步任务放到队列中,通过信号量来控制任务的执行,保证每次只导出一个视频,上传完成后再导出下一个,就没再出现这个问题。总之,这个问题应该是同时导出多个视频导致的。
希望能帮助到有需要的人。
emmmmm,不一定的,目前据我遇到的情况来看,无法导出的视频都是一些其他软件(可能是从安卓)合成的视频,用苹果下载到相册,AVAssetExportSession可能就会无法导出这些视频,上面的其他方法对我来说确实没用,不过七牛的那个SDK对我这边是生效的,所以建议使用AVAssetWriter和AVAssetReader来导出这些视频,可以自己写一个或者使用第三方的。
只是七牛的那个SDK需要授权才可以使用,所以我打印了PLSAVAssetExportSession的私有方法名,找到了他的私有类PLShortVideoExporter(应该是这个名字,我记不清了),继续打印了私有方法名,然后我发现PLShortVideoExporter的那些方法名和另一个我以前使用的三方库SDAVAssetExportSession几乎完全一样,我之前弃用SDAVAssetExportSession是因为存在一些其他的bug,有一些特殊的视频会导致-[AVAssetReaderOutput copyNextSampleBuffer]挂起,进度就始终卡在百分之99.几。但是七牛的没有这个问题,可能七牛的大佬对这个优化了一下直接拿来用了。
所以如果不想从头开始写AVAssetWriter和AVAssetReader,建议可以自己基于SDAVAssetExportSession做一些修改。
由于导出失败这个问题已经困扰了我很久,网上能查到的资料也不多,所以我暂时使用AVAssetWriter和AVAssetReader来辅助导出相册视频(大部分视频都可以使用AVAssetExportSession导出,当AVAssetExportSession导出失败是我再用AVAssetWriter和AVAssetReader来导出)。
以上就是我遇到的问题,和临时解决的方案,仅供参考,如果有不当的地方欢迎指出。
ps:有没有大佬比较了解AVVideoAverageBitRateKey怎么才能设置一个合适的值,因为使用AVAssetWriter和AVAssetReader的时候AVVideoAverageBitRateKey我没能找到合适的标准,设小了模糊,设大了视频偏大,请有经验的大佬指点一下。
不知楼主当前有好的办法解决吗?
安卓手机第三方app编辑过的视频,在苹果手机相册里可以打开,使用系统相册编辑压缩也是失败,但是微信上可以压缩,压缩后的视频可以正常使用压缩😂
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure { PHVideoRequestOptions* options = [[PHVideoRequestOptions alloc] init]; options.version = PHVideoRequestOptionsVersionOriginal; options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic; options.networkAccessAllowed = YES; [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset* avasset, AVAudioMix* audioMix, NSDictionary* info){ // NSLog(@"Info:\n%@",info); AVURLAsset *videoAsset = (AVURLAsset*)avasset; //在处理120帧无声音视频的时候,直接崩溃,原因是AVURLAsset *videoAsset = (AVURLAsset *)asset;因为asset是个基类,它有时会是AVComposition而不是AVURLAsset,这样就得不到视频的url,从而导致程序崩溃 if ([videoAsset isKindOfClass:[AVURLAsset class]]) { if (videoAsset.URL){ NSString *outputPath = self.outPutFile; NSData *data = [NSData dataWithContentsOfURL:videoAsset.URL]; if (data){ [data writeToFile:outputPath atomically:YES]; success(outputPath); }else{ failure(@"导出失败", nil); } }else{ failure(@"导出失败", nil); } }else{ failure(@"导出失败", nil); } }]; // [self startExportVideoWithVideoAsset:videoAsset presetName:presetName success:success failure:failure]; }
可以使用 亲测
TZImageManager.m
617 行:
[self getVideoOutputPathWithAsset:asset presetName:AVAssetExportPreset640x480 success:success failure:failure];
这里指定了 export 输出的格式是 640*480
目前测试出,如果原视频小于等于这个分辨率,一定会报错
@yehot 这个presetName可以从外面设置哈,你那边有测试出如何设置才确保成功吗?
感觉和视频没关系,我手机相册大部分视频都会失败,一模一样的错误。
@charlesYun 如果你是iOS14的话,那是iCloud的就会失败:#1397
非iOS14的和视频有挺大关系
@banchichen 那请问应该怎么解决呢?
@charlesYun 目前还没看到解法,这里讨论也比较多:ivpusic/react-native-image-crop-picker#1415 。可以关注一下
@banchichen 我换另一种导出方式,测试了一些视频暂时没有发现有失败的情况,需要的可以试试
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
// [self getVideoOutputPathWithAsset:asset presetName:AVAssetExportPresetMediumQuality success:success failure:failure];
// Option
PHVideoRequestOptions *option = [[PHVideoRequestOptions alloc] init];
option.version = PHVideoRequestOptionsVersionCurrent;
option.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
option.networkAccessAllowed = YES;
// Manager
PHImageManager *manager = [PHImageManager defaultManager];
[manager requestExportSessionForVideo:asset options:option exportPreset:AVAssetExportPresetHighestQuality resultHandler:^(AVAssetExportSession *_Nullable exportSession, NSDictionary *_Nullable info) {
// Path
NSString *outputPath = [CMFileManager documentDirectoryAtPath:@"CMVideo" documentDirectoryType:CMDocumentDirectoryTypeDocuments];
outputPath = [outputPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4", [[asset valueForKey:@"filename"] stringByDeletingPathExtension]]];
if ([CMFileManager fileExistsAtPath:outputPath]) {
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
success(outputPath);
}
});
return;
}
// Export
exportSession.outputURL = [NSURL fileURLWithPath:outputPath];
exportSession.shouldOptimizeForNetworkUse = NO;
exportSession.outputFileType = AVFileTypeMPEG4;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
switch ([exportSession status]) {
case AVAssetExportSessionStatusFailed: {
if (failure) {
failure(@"视频导出失败", nil);
}
} break;
case AVAssetExportSessionStatusCompleted: {
if (success) {
success(outputPath);
}
} break;
default:
break;
}
});
}];
}];
}
升级到3.5.3后,大家发现的会导出失败的视频,可以建一个共享相册,然后邀请我 736420282@qq.com
我来尝试解决哈
3.5.3版本修复了iOS14下iCloud视频导出失败的问题,感谢 @charlesYun 提供的方案👍
同时在TZImageManager
里提供了一个新方法:
/// 得到视频原始文件地址
- (void)requestVideoURLWithAsset:(PHAsset *)asset success:(void (^)(NSURL *videoURL))success failure:(void (^)(NSDictionary* info))failure;
通过它可以读取到视频的原始地址URL,然后用文件操作的相关API可以访问视频原始数据(未经压缩),或者这样读也行:
NSData *data = [NSData dataWithContentsOfURL:videoURL];
大家若还发现视频导出失败问题,请建一个共享相册,然后邀请我 736420282@qq.com,开一个新issue(或者加群),我再来解决
然后3.5.3也修复了批量导出视频时,因导出路径重复导致的偶现失败
请问这个问题解决了吗?
@YaoWeiCheng 参考上条回复,你的问题是?
@gxtai 参考上面的回复哈...最新版能复现的话,把这个视频共享给我下