I need to reduce the dimension of a video taken with an hybrid app without reduce the resolution therefore I'm trying to modify the cordova video editor plugin to reduce the dimension of the video by changing the bitrate. I've tried to use SDAVAssetExportSession without any success and getting tons of CVPixel errors. The plugin code is:
- (void) transcodeVideo:(CDVInvokedUrlCommand*)command
{
NSDictionary* options = [command.arguments objectAtIndex:0];
if ([options isKindOfClass:[NSNull class]]) {
options = [NSDictionary dictionary];
}
NSString *assetPath = [options objectForKey:@"fileUri"];
NSString *videoFileName = [options objectForKey:@"outputFileName"];
CDVQualityType qualityType = ([options objectForKey:@"quality"]) ? [[options objectForKey:@"quality"] intValue] : LowQuality;
NSString *presetName = Nil;
switch(qualityType) {
case HighQuality:
presetName = AVAssetExportPresetHighestQuality;
break;
case MediumQuality:
default:
presetName = AVAssetExportPresetMediumQuality;
break;
case LowQuality:
presetName = AVAssetExportPresetLowQuality;
}
CDVOutputFileType outputFileType = ([options objectForKey:@"outputFileType"]) ? [[options objectForKey:@"outputFileType"] intValue] : MPEG4;
BOOL optimizeForNetworkUse = ([options objectForKey:@"optimizeForNetworkUse"]) ? [[options objectForKey:@"optimizeForNetworkUse"] intValue] : NO;
float videoDuration = [[options objectForKey:@"duration"] floatValue];
BOOL saveToPhotoAlbum = [options objectForKey:@"saveToLibrary"] ? [[options objectForKey:@"saveToLibrary"] boolValue] : YES;
NSString *stringOutputFileType = Nil;
NSString *outputExtension = Nil;
switch (outputFileType) {
case QUICK_TIME:
stringOutputFileType = AVFileTypeQuickTimeMovie;
outputExtension = @".mov";
break;
case M4A:
stringOutputFileType = AVFileTypeAppleM4A;
outputExtension = @".m4a";
break;
case M4V:
stringOutputFileType = AVFileTypeAppleM4V;
outputExtension = @".m4v";
break;
case MPEG4:
default:
stringOutputFileType = AVFileTypeMPEG4;
outputExtension = @".mp4";
break;
}
// remove file:// from the assetPath if it is there
assetPath = [[assetPath stringByReplacingOccurrencesOfString:@"file://" withString:@""] mutableCopy];
// check if the video can be saved to photo album before going further
if (saveToPhotoAlbum && !UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(assetPath))
{
NSString *error = @"Video cannot be saved to photo album";
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error ] callbackId:command.callbackId];
return;
}
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *tempVideoPath =[NSString stringWithFormat:@"%@/%@%@", docDir, videoFileName, @".mov"];
NSData *videoData = [NSData dataWithContentsOfFile:assetPath];
[videoData writeToFile:tempVideoPath atomically:NO];
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:tempVideoPath] options:nil];
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality])
{
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset presetName: presetName];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *videoPath = [NSString stringWithFormat:@"%@/%@%@", [paths objectAtIndex:0], videoFileName, outputExtension];
exportSession.outputURL = [NSURL fileURLWithPath:videoPath];
exportSession.outputFileType = stringOutputFileType;
exportSession.shouldOptimizeForNetworkUse = optimizeForNetworkUse;
NSLog(@"videopath of your file: %@", videoPath);
if (videoDuration)
{
int32_t preferredTimeScale = 600;
CMTime startTime = CMTimeMakeWithSeconds(0, preferredTimeScale);
CMTime stopTime = CMTimeMakeWithSeconds(videoDuration, preferredTimeScale);
CMTimeRange exportTimeRange = CMTimeRangeFromTimeToTime(startTime, stopTime);
exportSession.timeRange = exportTimeRange;
}
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status]) {
case AVAssetExportSessionStatusCompleted:
if (saveToPhotoAlbum) {
UISaveVideoAtPathToSavedPhotosAlbum(videoPath, self, nil, nil);
}
NSLog(@"Export Complete %d %@", exportSession.status, exportSession.error);
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:videoPath] callbackId:command.callbackId];
break;
case AVAssetExportSessionStatusFailed:
NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[[exportSession error] localizedDescription]] callbackId:command.callbackId];
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export canceled");
break;
default:
NSLog(@"Export default in switch");
break;
}
}];
}
}
How could I implement AVAssetWriter inside the cordova plugin?
NSDictionary *settings = @{AVVideoCodecKey:AVVideoCodecH264,
AVVideoWidthKey:@(video_width),
AVVideoHeightKey:@(video_height),
AVVideoCompressionPropertiesKey:
@{AVVideoAverageBitRateKey:@(desired_bitrate),
AVVideoProfileLevelKey:AVVideoProfileLevelH264Main31, /* Or whatever profile & level you wish to use */
AVVideoMaxKeyFrameIntervalKey:@(desired_keyframe_interval)}};
AVAssetWriterInput* writer_input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
I don't really need a flexible solution, hardcoding is fine. I'm not really an Object-C expert (and I find it a quite obscure language)
I managed to solve the issue with SDAVAssetExportSession. Next, I'm contacting the author to commit the changes. Here is the code (MUST be added the CoreVideo.framework):
- (void) transcodeVideo:(CDVInvokedUrlCommand*)command
{
NSDictionary* options = [command.arguments objectAtIndex:0];
if ([options isKindOfClass:[NSNull class]]) {
options = [NSDictionary dictionary];
}
NSString *assetPath = [options objectForKey:@"fileUri"];
NSString *videoFileName = [options objectForKey:@"outputFileName"];
CDVQualityType qualityType = ([options objectForKey:@"quality"]) ? [[options objectForKey:@"quality"] intValue] : LowQuality;
NSString *presetName = Nil;
switch(qualityType) {
case HighQuality:
presetName = AVAssetExportPresetHighestQuality;
break;
case MediumQuality:
default:
presetName = AVAssetExportPresetMediumQuality;
break;
case LowQuality:
presetName = AVAssetExportPresetLowQuality;
}
CDVOutputFileType outputFileType = ([options objectForKey:@"outputFileType"]) ? [[options objectForKey:@"outputFileType"] intValue] : MPEG4;
BOOL optimizeForNetworkUse = ([options objectForKey:@"optimizeForNetworkUse"]) ? [[options objectForKey:@"optimizeForNetworkUse"] intValue] : NO;
float videoDuration = [[options objectForKey:@"duration"] floatValue];
BOOL saveToPhotoAlbum = [options objectForKey:@"saveToLibrary"] ? [[options objectForKey:@"saveToLibrary"] boolValue] : YES;
NSString *stringOutputFileType = Nil;
NSString *outputExtension = Nil;
switch (outputFileType) {
case QUICK_TIME:
stringOutputFileType = AVFileTypeQuickTimeMovie;
outputExtension = @".mov";
break;
case M4A:
stringOutputFileType = AVFileTypeAppleM4A;
outputExtension = @".m4a";
break;
case M4V:
stringOutputFileType = AVFileTypeAppleM4V;
outputExtension = @".m4v";
break;
case MPEG4:
default:
stringOutputFileType = AVFileTypeMPEG4;
outputExtension = @".mp4";
break;
}
// remove file:// from the assetPath if it is there
assetPath = [[assetPath stringByReplacingOccurrencesOfString:@"file://" withString:@""] mutableCopy];
// check if the video can be saved to photo album before going further
if (saveToPhotoAlbum && !UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(assetPath))
{
NSString *error = @"Video cannot be saved to photo album";
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error ] callbackId:command.callbackId];
return;
}
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *tempVideoPath =[NSString stringWithFormat:@"%@/%@%@", docDir, videoFileName, @".mov"];
NSData *videoData = [NSData dataWithContentsOfFile:assetPath];
[videoData writeToFile:tempVideoPath atomically:NO];
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:tempVideoPath] options:nil];
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
SDAVAssetExportSession *encoder = [SDAVAssetExportSession.alloc initWithAsset:avAsset];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *videoPath = [NSString stringWithFormat:@"%@/%@%@", [paths objectAtIndex:0], videoFileName, outputExtension];
encoder.outputFileType = stringOutputFileType;
encoder.outputURL = [NSURL fileURLWithPath:videoPath];
encoder.shouldOptimizeForNetworkUse = optimizeForNetworkUse;
encoder.videoSettings = @
{
AVVideoCodecKey: AVVideoCodecH264,
AVVideoWidthKey: @1280,
AVVideoHeightKey: @720,
AVVideoCompressionPropertiesKey: @
{
AVVideoAverageBitRateKey: @1200000,
AVVideoProfileLevelKey: AVVideoProfileLevelH264High40,
},
};
encoder.audioSettings = @
{
AVFormatIDKey: @(kAudioFormatMPEG4AAC),
AVNumberOfChannelsKey: @2,
AVSampleRateKey: @44100,
AVEncoderBitRateKey: @128000,
};
[encoder exportAsynchronouslyWithCompletionHandler:^
{
if (encoder.status == AVAssetExportSessionStatusCompleted)
{
if (saveToPhotoAlbum) {
UISaveVideoAtPathToSavedPhotosAlbum(videoPath, self, nil, nil);
}
NSLog(@"Export Complete %d %@", encoder.status, encoder.error);
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:videoPath] callbackId:command.callbackId];
}
else if (encoder.status == AVAssetExportSessionStatusCancelled)
{
NSLog(@"Video export cancelled");
}
else
{
NSLog(@"Export failed: %@", [[encoder error] localizedDescription]);
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[[encoder error] localizedDescription]] callbackId:command.callbackId];
}
/*switch ([encoder status]) {
case AVAssetExportSessionStatusCompleted:
break;
case AVAssetExportSessionStatusFailed:
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export canceled");
break;
default:
NSLog(@"Export default in switch");
break;
}*/
}];
/*if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality])
{
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset presetName: presetName];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *videoPath = [NSString stringWithFormat:@"%@/%@%@", [paths objectAtIndex:0], videoFileName, outputExtension];
exportSession.outputURL = [NSURL fileURLWithPath:videoPath];
exportSession.outputFileType = stringOutputFileType;
exportSession.shouldOptimizeForNetworkUse = optimizeForNetworkUse;
NSLog(@"videopath of your file: %@", videoPath);
if (videoDuration)
{
int32_t preferredTimeScale = 600;
CMTime startTime = CMTimeMakeWithSeconds(0, preferredTimeScale);
CMTime stopTime = CMTimeMakeWithSeconds(videoDuration, preferredTimeScale);
CMTimeRange exportTimeRange = CMTimeRangeFromTimeToTime(startTime, stopTime);
exportSession.timeRange = exportTimeRange;
}
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status]) {
case AVAssetExportSessionStatusCompleted:
if (saveToPhotoAlbum) {
UISaveVideoAtPathToSavedPhotosAlbum(videoPath, self, nil, nil);
}
NSLog(@"Export Complete %d %@", exportSession.status, exportSession.error);
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:videoPath] callbackId:command.callbackId];
break;
case AVAssetExportSessionStatusFailed:
NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[[exportSession error] localizedDescription]] callbackId:command.callbackId];
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export canceled");
break;
default:
NSLog(@"Export default in switch");
break;
}
}];
}*/
}