Hi I am trying to merge videos together using AVMutableComposition .
The problem I am having is that the AVAsset tracksWithMediaTypes method returns an empty array and the app crashed.
Can someone point out what I am doing wrong.
Thanks
Here is what I have so far:
-(void) mergeVideosAndAudio:(AVAsset *)audioAsset{
//Load Video Assets
NSError *error;
NSArray *dirFiles;
if ((dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self documentsDirectory] error:&error]) == nil) {
// handle the error
};
// find all the temp files
NSArray *movFiles = [dirFiles filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"self BEGINSWITH 'temp'"]];
NSLog(#"The are %i temp files",movFiles.count);
NSArray *filePathsArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:[self documentsDirectory] error:nil];
//Create the composition
AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
// 1 - Video track
AVMutableCompositionTrack *firstTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
preferredTrackID:kCMPersistentTrackID_Invalid];
CMTime videoTrackDuration;
for (int j = 0; j < filePathsArray.count; j++) {
NSURL *url = [NSURL fileURLWithPath:filePathsArray[j]];
AVURLAsset *currentAsset = [[AVURLAsset alloc]initWithURL:url options:nil];
videoTrackDuration = CMTimeAdd(videoTrackDuration, currentAsset.duration);
CMTime time;
if (j == 0) {
time = kCMTimeZero;
}else{
NSURL *previousAssetURL = [NSURL fileURLWithPath:filePathsArray[j-1]];
AVURLAsset *previousAsset = [[AVURLAsset alloc]initWithURL:previousAssetURL options:nil];
time = previousAsset.duration;
}
[firstTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, currentAsset.duration) ofTrack:[[currentAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:time error:nil];
}
// 2 - Audio track
if (audioAsset!=nil){
AVMutableCompositionTrack *AudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
[AudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoTrackDuration)
ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil];
}
//3 - Get Path for merge
NSString *myPathDocs = [[self documentsDirectory] stringByAppendingPathComponent:
[NSString stringWithFormat:#"mergeVideo-%d.mov",arc4random() % 1000]];
self.fileURL = [NSURL URLWithString: myPathDocs];
// 5 - Create exporter
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition
presetName:AVAssetExportPresetHighestQuality];
exporter.outputURL=self.fileURL;
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.shouldOptimizeForNetworkUse = YES;
[exporter exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self exportDidFinish:exporter];
});
}];
}
-(void)exportDidFinish:(AVAssetExportSession*)session {
if (session.status == AVAssetExportSessionStatusCompleted) {
self.fileURL = session.outputURL;
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:self.fileURL]) {
[library writeVideoAtPathToSavedPhotosAlbum:self.fileURL completionBlock:^(NSURL *assetURL, NSError *error){
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Video Saving Failed"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Video Saved" message:#"Saved To Photo Album"
delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
});
}];
}
}
[self removeTempFilesFromDocuments];
}
Here is a screenshot of the error:
Well, the error is right there "index 0 beyond bounds for empty array".
That means that you have empty array of video assets. Thus, there is something wrong with getting your videoassets via NSFileManager. Go check the array filePathsArray, there is something wrong with it.
First Check video is present at particular path and run proper .
In my case,video is present at path ,but not able to run .
This mostly happen because video not write properly at that path.
Hope it helps.
Please check your files duration that should't be zero.
Not sure if this solves your problem. But I've ran into this issue before when I was trying to load an AVAsset from a url but was getting an asset with no tracks. What fixed it for me was adding "isDirectory:NO" when creating the NSURL:
NSURL *url = [NSURL fileURLWithPath:filePathsArray[j] isDirectory:NO];
Related
Using SCRecorder want to save video after recording, with different playback speed chosen by user ex: 2x, 3x. Using AVPlayer, it can be achieved using this code:
//create mutable composition
AVMutableComposition *mixComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
preferredTrackID:kCMPersistentTrackID_Invalid];
NSError *videoInsertError = nil;
BOOL videoInsertResult = [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)
ofTrack:[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]
atTime:kCMTimeZero
error:&videoInsertError];
if (!videoInsertResult || nil != videoInsertError) {
//handle error
return;
}
//slow down whole video by 2.0
double videoScaleFactor = 2.0;
CMTime videoDuration = asset.duration;
[compositionVideoTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, videoDuration)
toDuration:CMTimeMake(videoDuration.value*videoScaleFactor, videoDuration.timescale)];
//export
AVAssetExportSession* assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition
// presetName:AVAssetExportPresetLowQuality];
But, not getting how to achieve the same using SCRecorder library. Please guide.
Thanks in advance.
Finally got answer my self:
- (void)SlowMotion:(NSURL *)URl
{
AVURLAsset* videoAsset = [AVURLAsset URLAssetWithURL:URl options:nil]; //self.inputAsset;
AVAsset *currentAsset = [AVAsset assetWithURL:URl];
AVAssetTrack *vdoTrack = [[currentAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
//create mutable composition
AVMutableComposition *mixComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *compositionAudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
NSError *videoInsertError = nil;
BOOL videoInsertResult = [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]
atTime:kCMTimeZero
error:&videoInsertError];
if (!videoInsertResult || nil != videoInsertError) {
//handle error
return;
}
NSError *audioInsertError =nil;
BOOL audioInsertResult =[compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
ofTrack:[[currentAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]
atTime:kCMTimeZero
error:&audioInsertError];
if (!audioInsertResult || nil != audioInsertError) {
//handle error
return;
}
CMTime duration =kCMTimeZero;
duration=CMTimeAdd(duration, currentAsset.duration);
//slow down whole video by 2.0
double videoScaleFactor = 2.0;
CMTime videoDuration = videoAsset.duration;
[compositionVideoTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, videoDuration)
toDuration:CMTimeMake(videoDuration.value*videoScaleFactor, videoDuration.timescale)];
[compositionAudioTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, videoDuration)
toDuration:CMTimeMake(videoDuration.value*videoScaleFactor, videoDuration.timescale)];
[compositionVideoTrack setPreferredTransform:vdoTrack.preferredTransform];
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [dirPaths objectAtIndex:0];
NSString *outputFilePath = [docsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"slowMotion.mov"]];
if ([[NSFileManager defaultManager] fileExistsAtPath:outputFilePath])
[[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil];
NSURL *_filePath = [NSURL fileURLWithPath:outputFilePath];
//export
AVAssetExportSession* assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition
presetName:AVAssetExportPresetLowQuality];
assetExport.outputURL=_filePath;
assetExport.outputFileType = AVFileTypeQuickTimeMovie;
exporter.shouldOptimizeForNetworkUse = YES;
[assetExport exportAsynchronouslyWithCompletionHandler:^
{
switch ([assetExport status]) {
case AVAssetExportSessionStatusFailed:
{
NSLog(#"Export session faiied with error: %#", [assetExport error]);
dispatch_async(dispatch_get_main_queue(), ^{
// completion(nil);
});
}
break;
case AVAssetExportSessionStatusCompleted:
{
NSLog(#"Successful");
NSURL *outputURL = assetExport.outputURL;
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:outputURL]) {
[self writeExportedVideoToAssetsLibrary:outputURL];
}
dispatch_async(dispatch_get_main_queue(), ^{
// completion(_filePath);
});
}
break;
default:
break;
}
}];
}
- (void)writeExportedVideoToAssetsLibrary :(NSURL *)url {
NSURL *exportURL = url;
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:exportURL]) {
[library writeVideoAtPathToSavedPhotosAlbum:exportURL completionBlock:^(NSURL *assetURL, NSError *error){
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[error localizedDescription]
message:[error localizedRecoverySuggestion]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
if(!error)
{
// [activityView setHidden:YES];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Sucess"
message:#"video added to gallery successfully"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
#if !TARGET_IPHONE_SIMULATOR
[[NSFileManager defaultManager] removeItemAtURL:exportURL error:nil];
#endif
});
}];
} else {
NSLog(#"Video could not be exported to assets library.");
}
}
SCVideoConfiguration having one property timeScale.
You cant try SCAssetExportSession. which takes SCVideoConfiguration as input.
You can also use SCAssetExportSession, which is the SCRecorder counterpart of AVAssetExportSession.(SCRecorder docs)
/* The time scale of the video
A value more than 1 will make the buffers last longer, it creates
a slow motion effect. A value less than 1 will make the buffers be
shorter, it creates a timelapse effect.
Only used in SCRecorder.
*/
#property (assign, nonatomic) CGFloat timeScale;
I am really stuck on this, i have merged videos (mp4 kind that are saved in xcode, and mov - from users photo library) and audio (mp3 kind saved in xcode). On the simulator it works fine and shows the video and audio, but on the device it doesn't show the video only plays the sound . here is the code for merging:` AVMutableComposition* mixComposition = [AVMutableComposition composition];
//Audio
//Now first load your audio file using AVURLAsset. Make sure you give the correct path of your videos.
NSURL *audio_url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:_video.musicFileName ofType:#"mp3"]];
AVURLAsset *audioAsset = [[AVURLAsset alloc] initWithURL:audio_url options:nil];
CMTimeRange audio_timeRange = CMTimeRangeMake(kCMTimeZero, audioAsset.duration);
//Now we are creating the first AVMutableCompositionTrack containing our audio and add it to our AVMutableComposition object.
AVMutableCompositionTrack *compositionAudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[compositionAudioTrack insertTimeRange:audio_timeRange ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil];
//Video
AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
NSString *fistPartMovieFilename = #"firstpart";
NSString *lastPartMovieFilename = #"lastpart";
//[_video.selectedVideosFileName insertObject:fistPartMovieFilename atIndex:0];
//[_video.selectedVideosFileName addObject:lastPartMovieFilename];
NSMutableArray *timeRanges = [NSMutableArray arrayWithCapacity:_video.selectedVideosFileName.count];
NSMutableArray *tracks = [NSMutableArray arrayWithCapacity:_video.selectedVideosFileName.count];
NSLog(#"_video.selectedVideosFileName %#",_video.selectedVideosFileName);
for (int i=0; i<[_video.selectedVideosFileName count]; i++)
{
AVURLAsset *assetClip;
NSString *fileName = [_video.selectedVideosFileName objectAtIndex:i];
if ([[_video.selectedVideosFileName objectAtIndex:i] isKindOfClass:[NSURL class]])
{
assetClip = [AVAsset assetWithURL:[_video.selectedVideosFileName objectAtIndex:i]];
}
else
{
assetClip = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:[NSString stringWithString:fileName] ofType:#"mp4"]] options:nil];
}
AVAssetTrack *clipVideoTrack = [[assetClip tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
[timeRanges addObject:[NSValue valueWithCMTimeRange:CMTimeRangeMake(kCMTimeZero, assetClip.duration)]];
[tracks addObject:clipVideoTrack];
NSLog(#"timeRanges %#",timeRanges);
NSLog(#"tracks %#",tracks);
NSLog(#"assetClip %#",assetClip);
NSLog(#"clipVideoTrack %#",clipVideoTrack);
}
NSError *error = nil;
BOOL sucsess = [compositionVideoTrack insertTimeRanges:timeRanges
ofTracks:tracks
atTime:kCMTimeZero
error:&error];
if (!sucsess || error) {
NSLog(#"video error %#", error);
}
the error i get when trying on device is : video error
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo=0x1704707c0
{NSUnderlyingError=0x174247050 "The operation couldn’t be completed. (OSStatus error -12780.)", NSLocalizedFailureReason=An unknown error occurred (-12780), NSLocalizedDescription=The operation could not be completed
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [dirPaths objectAtIndex:0];
NSString *outputFilePath = [docsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"Zagoori-%d.mov",arc4random() % 1000]];
NSURL *outputFileUrl = [NSURL fileURLWithPath:outputFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:outputFilePath])
[[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil];
//Now create an AVAssetExportSession object that will save your final video at specified path.
AVAssetExportSession *_assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition
presetName:AVAssetExportPresetLowQuality];
NSLog(#"supportedFileTypes %#", [_assetExport supportedFileTypes]);
_assetExport.outputFileType = #"com.apple.quicktime-movie";
_assetExport.outputURL = outputFileUrl;
[_assetExport exportAsynchronouslyWithCompletionHandler:^
{
dispatch_async(dispatch_get_main_queue(), ^{
switch ([_assetExport status]) {
case AVAssetExportSessionStatusExporting:
NSLog(#"Export in progress ");
case AVAssetExportSessionStatusFailed:
NSLog(#"Export failed (%ld): %#", (long)[[_assetExport error] code], [[_assetExport error] localizedFailureReason]);
[_assetExport cancelExport];
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Export canceled");
break;
case AVAssetExportSessionStatusCompleted:
NSLog(#"Export done");
[self exportDidFinish:_assetExport];
// NSLog (#"finished writing %f", [dtStartDate timeIntervalSinceNow]);
break;
case AVAssetExportSessionStatusWaiting:
NSLog (#"AVAssetExportSessionStatusWaiting");
break;
case AVAssetExportSessionStatusUnknown:
NSLog (#"AVAssetExportSessionStatusUnknown");
break;
}
});
}];
-(void)loadMoviePlayer:(NSURL*)moviePath {
_moviePlayerController = [[MPMoviePlayerController alloc]
initWithContentURL:moviePath];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(introMovieFinished:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:_moviePlayerController];
_moviePlayerController.view.hidden = NO;
_moviePlayerController.view.frame = CGRectMake(0, 0, _videoView.frame.size.width,
_videoView.frame.size.height);
_moviePlayerController.view.backgroundColor = [UIColor clearColor];
_moviePlayerController.scalingMode = MPMovieScalingModeAspectFit;
_moviePlayerController.fullscreen = NO;
[_moviePlayerController prepareToPlay];
[_moviePlayerController readyForDisplay];
[_moviePlayerController setControlStyle:MPMovieControlStyleDefault];
_moviePlayerController.shouldAutoplay = YES;
[_videoView addSubview:_moviePlayerController.view];
[_videoView setHidden:NO];
}
I am really clueless of what the problem could be, Help please! :)
so i have found the problem,it is caused by the AVURLAsset instances
Since the AVURLAsset *assetClip is in inside the for loop, it is not valid outside of it and neither is the tracks that i extracted.
I kept my assetClip in an array and that solved the problem.
I want to convert a MP4-file from the apps Document folder into an Audio-file (mp3 or m4a).
I already tried, but i can not play the converted MP3-file with AVPlayer.
Here is my code:
-(void)convertMP4toMP3withFile:(NSString*)dstPath
{
NSURL *dstURL = [NSURL fileURLWithPath:dstPath];
AVMutableComposition* newAudioAsset = [AVMutableComposition composition];
AVMutableCompositionTrack* dstCompositionTrack;
dstCompositionTrack = [newAudioAsset addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
AVAsset* srcAsset = [AVURLAsset URLAssetWithURL:dstURL options:nil];
AVAssetTrack* srcTrack = [[srcAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
CMTimeRange timeRange = srcTrack.timeRange;
NSError* error;
if(NO == [dstCompositionTrack insertTimeRange:timeRange ofTrack:srcTrack atTime:kCMTimeZero error:&error]) {
NSLog(#"track insert failed: %#\n", error);
return;
}
AVAssetExportSession* exportSesh = [[AVAssetExportSession alloc] initWithAsset:newAudioAsset presetName:AVAssetExportPresetPassthrough];
exportSesh.outputFileType = AVFileTypeAppleM4A;
exportSesh.outputURL = dstURL;
[[NSFileManager defaultManager] removeItemAtURL:dstURL error:nil];
[exportSesh exportAsynchronouslyWithCompletionHandler:^{
AVAssetExportSessionStatus status = exportSesh.status;
NSLog(#"exportAsynchronouslyWithCompletionHandler: %i\n", status);
if(AVAssetExportSessionStatusFailed == status) {
NSLog(#"FAILURE: %#\n", exportSesh.error);
} else if(AVAssetExportSessionStatusCompleted == status) {
NSLog(#"SUCCESS!\n");
NSError *error;
//append the name of the file in jpg form
//check if the file exists (completely unnecessary).
NSString *onlyPath = [dstPath stringByDeletingLastPathComponent];
NSString *toPathString = [NSString stringWithFormat:#"%#/testfile.m4a", onlyPath];
[[NSFileManager defaultManager] moveItemAtPath:dstPath toPath:toPathString error:&error];
[self loadFiles];
}
}];
}
Has anyone a solution for my problem or can improve my code?
replace this line:
exportSesh.outputFileType = AVFileTypeAppleM4A;
with:
exportSesh.outputFileType = AVFileTypeCoreAudioFormat;
& this:
NSString *toPathString = [NSString stringWithFormat:#"%#/testfile.m4a", onlyPath];
with:
NSString *toPathString = [NSString stringWithFormat:#"%#/testfile.mp3", onlyPath];
Worked for me :)
The code does work on converting mp4 to m4a. Maybe your play audio file code is wrong. Following questions may be helpful to you.
No sound coming from AVPlayer
iOS Extracting Audio from .mov file
Im trying to mix one audio file with Video file, but i got an error. " Export failed: The operation could not be completed "
Please correct this code if there is an error.
My Code:
-(void)CompileFilesToMakeMovie
{
AVMutableComposition* mixComposition = [AVMutableComposition composition];
NSString *str=[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"bgsong.mp3"];
NSString* audio_inputFilePath =str;
NSURL* audio_inputFileUrl = [NSURL fileURLWithPath:audio_inputFilePath];
NSString* video_inputFileName = #"movie.mp4";
NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *video_inputFilePath = [documentsDirectory stringByAppendingPathComponent:video_inputFileName];
NSURL* video_inputFileUrl = [NSURL fileURLWithPath:video_inputFilePath];
NSString* outputFileName = #"outputFile.mp4";
NSString* outputFilePath = [documentsDirectory stringByAppendingPathComponent:outputFileName];
NSURL* outputFileUrl = [NSURL fileURLWithPath:outputFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:outputFilePath])
[[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil];
CMTime nextClipStartTime = kCMTimeZero;
AVURLAsset* videoAsset = [[AVURLAsset alloc]initWithURL:video_inputFileUrl options:nil];
CMTimeRange video_timeRange = CMTimeRangeMake(kCMTimeZero,videoAsset.duration);
AVMutableCompositionTrack *a_compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[a_compositionVideoTrack insertTimeRange:video_timeRange ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:nextClipStartTime error:nil];
//nextClipStartTime = CMTimeAdd(nextClipStartTime, a_timeRange.duration);
AVURLAsset* audioAsset = [[AVURLAsset alloc]initWithURL:audio_inputFileUrl options:nil];
CMTimeRange audio_timeRange = CMTimeRangeMake(kCMTimeZero, audioAsset.duration);
AVMutableCompositionTrack *b_compositionAudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[b_compositionAudioTrack insertTimeRange:audio_timeRange ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:nextClipStartTime error:nil];
AVAssetExportSession* _assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetHighestQuality];
_assetExport.outputFileType = #"com.apple.quicktime-movie";
_assetExport.outputURL = outputFileUrl;
[_assetExport exportAsynchronouslyWithCompletionHandler:
^(void ) {
BOOL _success = false;
switch ([_assetExport status]) {
case AVAssetExportSessionStatusCompleted:
_success = true;
NSLog(#"Export Completed");
break;
case AVAssetExportSessionStatusWaiting:
NSLog(#"Export Waiting");
break;
case AVAssetExportSessionStatusExporting:
NSLog(#"Export Exporting");
break;
case AVAssetExportSessionStatusFailed:
{
NSError *error = [_assetExport error];
NSLog(#"Export failed: %#", [error localizedDescription]);
break;
}
case AVAssetExportSessionStatusCancelled:
NSLog(#"Export canceled");
break;
default:
break;
}
if (_success == true) {
ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init];
[assetLibrary writeVideoAtPathToSavedPhotosAlbum:outputFileUrl completionBlock:^(NSURL *assetURL, NSError *error){
NSError *removeError = nil;
[[NSFileManager defaultManager] removeItemAtURL:outputFileUrl error:&removeError];
}];
}
}
];
}
Thanks in Advance
Please try this one ..
AVMutableComposition* mixComposition = [AVMutableComposition composition];
//audio File
NSMutableArray *loTempArr = [[[Database sharedDBDetails] getAllUserDetails:kaudioTable] mutableCopy];
TempFile *lotemp1 = [[TempFile alloc] init];
TempFile *loTemp2 = [[TempFile alloc] init];
loTemp2 = [mallVideoArray objectAtIndex:self.slectedVideoIndex];
for (int i = 0; i < [loTempArr count]; i++)
{
lotemp1 = [loTempArr objectAtIndex:i];
if (loTemp2.mTemp_Key == [lotemp1.mTemp_videorefID intValue])
{
//NSLog(#"%#",lotemp1.mTemp_AudioName);
NSString *filepath = [kDocument_Path stringByAppendingString:[NSString stringWithFormat:#"/audioFolder/%#",lotemp1.mTemp_AudioName]];
NSURL *SongURL = [NSURL fileURLWithPath:filepath];
self.audioAsset = [[AVURLAsset alloc] initWithURL:SongURL options:nil];
CMTime time2=CMTimeMake([lotemp1.mTemp_timeinvideo doubleValue]*600, 600);
AVMutableCompositionTrack *compositionCommentaryTrack2 = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[compositionCommentaryTrack2 insertTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeSubtract(self.videoAsset.duration, time2))
ofTrack:[[self.audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:time2 error:nil];
}
}
AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, self.videoAsset.duration)
ofTrack:[[self.videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:kCMTimeZero error:nil];
// 3.1 - Create AVMutableVideoCompositionInstruction
AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, self.videoAsset.duration);
// 3.2 - Create an AVMutableVideoCompositionLayerInstruction for the video track and fix the orientation.
AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTrack];
AVAssetTrack *videoAssetTrack = [[self.videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
CGAffineTransform transform = CGAffineTransformIdentity;//
transform = videoAssetTrack.preferredTransform;
[videolayerInstruction setTransform:transform atTime:kCMTimeZero];
[videolayerInstruction setOpacity:0.0 atTime:self.videoAsset.duration];
mainInstruction.layerInstructions = [NSArray arrayWithObjects:videolayerInstruction,nil];
// 3.3 - Add instructions
AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition];
float renderWidth, renderHeight;
renderWidth = self.movieController.view.frame.size.width;
renderHeight = self.movieController.view.frame.size.height;
CGSize size;
if(flipActionFlag == 4 || flipActionFlag == 5)
{
size = CGSizeMake(videoAssetTrack.naturalSize.height, videoAssetTrack.naturalSize.width);
}
else
{
size = videoAssetTrack.naturalSize;
}
//NSLog(#"%#",NSStringFromCGSize(size));
mainCompositionInst.renderSize = size;//size//CGSizeMake(renderWidth, renderHeight)
mainCompositionInst.instructions = [NSArray arrayWithObject:mainInstruction];
mainCompositionInst.frameDuration = CMTimeMake(1, 30);
// 4 - Get path
TempFile *mnewtemp = [[TempFile alloc] init];
mnewtemp.mTemp_videoName = [NSString stringWithFormat:#"Video_%d.m4v",loTemp.mTemp_Key+1];
[[Database sharedDBDetails] insertNewRowWithData:mnewtemp forTable:kvideoTable];
NSString *myPathDocs = [kDocument_Path stringByAppendingPathComponent:
[NSString stringWithFormat:#"Video/Video_%d.m4v",loTemp.mTemp_Key+1]];
NSURL *url = [NSURL fileURLWithPath:myPathDocs];
// 5 - Create exporter
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition
presetName:AVAssetExportPresetHighestQuality];
exporter.outputURL=url;
exporter.outputFileType = AVFileTypeQuickTimeMovie;//#"com.apple.quicktime-movie";AVFileTypeQuickTimeMovie
exporter.shouldOptimizeForNetworkUse = YES;
exporter.videoComposition = mainCompositionInst;
[exporter exportAsynchronouslyWithCompletionHandler:^
{
int exportStatus = exporter.status;
NSLog(#"exportStatus = %d",exportStatus);
switch (exportStatus)
{
case AVAssetExportSessionStatusFailed: {NSError *exportError = exporter.error;NSLog (#"AVAssetExportSessionStatusFailed: %#", exportError);break;}
case AVAssetExportSessionStatusCompleted: { NSLog (#"AVAssetExportSessionStatusCompleted--"); break;}
case AVAssetExportSessionStatusUnknown: { NSLog (#"AVAssetExportSessionStatusUnknown"); break;}
case AVAssetExportSessionStatusExporting: { NSLog (#"AVAssetExportSessionStatusExporting"); break;}
case AVAssetExportSessionStatusCancelled: { NSLog (#"AVAssetExportSessionStatusCancelled"); break;}
case AVAssetExportSessionStatusWaiting:{NSLog (#"AVAssetExportSessionStatusWaiting"); break;}
default: { NSLog (#"didn't get export status"); break;}
}
dispatch_async(dispatch_get_main_queue(), ^
{
[self exportDidFinish:exporter];
});
}];
}}
- (void)exportDidFinish:(AVAssetExportSession*)session
{
[losaveView removeFromSuperview];
if (session.status == AVAssetExportSessionStatusCompleted)
{
NSURL *outputURL = session.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 (error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Video Saving Failed"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
else
{
self.mallVideoArray = [[[Database sharedDBDetails] getAllUserDetails:kvideoTable] mutableCopy];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Video Saved" message:#"Saved To Photo Album"
delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
});
}];
}
}
}
I got solution for my question.
- (IBAction)MergeAndSave:(id)sender
{
NSString *str=[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"bgsong.mp3"];
NSString* audio_inputFilePath =str;
NSURL *SongURL =[NSURL fileURLWithPath:audio_inputFilePath];
audioAsset = [AVAsset assetWithURL:SongURL];
NSString* video_inputFileName = #"movie.mp4";
NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *video_inputFilePath = [documentsDirectory stringByAppendingPathComponent:video_inputFileName];
NSURL* video_inputFileUrl = [NSURL fileURLWithPath:video_inputFilePath];
firstAsset = [AVAsset assetWithURL:video_inputFileUrl];
secondAsset = [AVAsset assetWithURL:video_inputFileUrl];
if(firstAsset !=nil && secondAsset!=nil){
//Create AVMutableComposition Object.This object will hold our multiple AVMutableCompositionTrack.
AVMutableComposition* mixComposition = [[AVMutableComposition alloc] init];
//VIDEO TRACK
AVMutableCompositionTrack *firstTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[firstTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstAsset.duration) ofTrack:[[firstAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:kCMTimeZero error:nil];
AVMutableCompositionTrack *secondTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[secondTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondAsset.duration) ofTrack:[[secondAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:firstAsset.duration error:nil];
//AUDIO TRACK
if(audioAsset!=nil){
AVMutableCompositionTrack *AudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[AudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstAsset.duration, secondAsset.duration)) ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil];
}
AVMutableVideoCompositionInstruction * MainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
MainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstAsset.duration, secondAsset.duration));
//FIXING ORIENTATION//
AVMutableVideoCompositionLayerInstruction *FirstlayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:firstTrack];
[FirstlayerInstruction setOpacity:0.0 atTime:firstAsset.duration];
MainInstruction.layerInstructions = [NSArray arrayWithObjects:FirstlayerInstruction,nil];;
AVMutableVideoComposition *MainCompositionInst = [AVMutableVideoComposition videoComposition];
MainCompositionInst.instructions = [NSArray arrayWithObject:MainInstruction];
MainCompositionInst.frameDuration = CMTimeMake(1, 30);
MainCompositionInst.renderSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *myPathDocs = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"mergeVideo.mov"]];
NSURL *url = [NSURL fileURLWithPath:myPathDocs];
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetHighestQuality];
exporter.outputURL=url;
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.videoComposition = MainCompositionInst;
exporter.shouldOptimizeForNetworkUse = YES;
[exporter exportAsynchronouslyWithCompletionHandler:^
{
dispatch_async(dispatch_get_main_queue(), ^{
[self playVideo];
//[self performSelector:#selector(playVideo) withObject:nil afterDelay:2.0];
// [self exportDidFinish:exporter];
});
}];
}
}
- (void)exportDidFinish:(AVAssetExportSession*)session
{
if(session.status == AVAssetExportSessionStatusCompleted){
NSURL *outputURL = session.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 (error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Video Saving Failed" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil, nil];
[alert show];
}else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Video Saved" message:#"Saved To Photo Album" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}
});
}];
}
}
audioAsset = nil;
firstAsset = nil;
secondAsset = nil;
}
I have a bunch of video files that I want to merge into one video file, I am using NSMutableData to achieve the task
NSMutableData *concatenatedData = [[NSMutableData alloc] init];
for (int i=0; i <[videoArray count]; i ++) {
[concatenatedData appendData: [videoArray objectAtIndex:i]];
}
[concatenatedData writeToFile:[[NSString alloc] initWithFormat:#"%#%#", NSTemporaryDirectory(), #"outputConct.mov"] atomically:YES];
UISaveVideoAtPathToSavedPhotosAlbum([[NSString alloc] initWithFormat:#"%#%#", NSTemporaryDirectory(),#"outputConct.mov"], nil, nil, nil);
after the video is saved in my camera roll I try to play it but only the first NSData video is in it. I am not sure why.
edit
I tried AVMutableComposition, even then I am having the same issues
AVMutableComposition* mixComposition = [[AVMutableComposition alloc] init];
AVURLAsset* firstAsset;
AVMutableCompositionTrack *firstTrack;
CMTime time = kCMTimeZero;
for (int i=0; i<[videoArray count]; i++) {
firstAsset = [[AVURLAsset alloc]initWithURL:[NSURL fileURLWithPath:[[NSString alloc] initWithFormat:#"%#%d%#", NSTemporaryDirectory(),i, #"output.mov"]] options:nil];
firstTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[firstTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstAsset.duration) ofTrack:[[firstAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:time error:nil];
time = firstAsset.duration;
}
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *myPathDocs = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithFormat:#"mergeVideo-%d.mov",arc4random() % 1000]];
NSURL *url = [NSURL fileURLWithPath:myPathDocs];
// 5 - Create exporter
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition
presetName:AVAssetExportPresetHighestQuality];
exporter.outputURL=url;
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.shouldOptimizeForNetworkUse = YES;
[exporter exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (exporter.status == AVAssetExportSessionStatusCompleted) {
//NSURL *outputURL = exporter.outputURL;
UISaveVideoAtPathToSavedPhotosAlbum(myPathDocs, nil, nil, nil);
}
});
}];
edit
I also tried this, but it gives me an error, it says [__NSArrayM insertObject:atIndex:]: object cannot be nil'
AVMutableComposition *mixComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *compositionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
NSError * error = nil;
NSMutableArray * timeRanges = [NSMutableArray arrayWithCapacity:[videoArray count]];
NSMutableArray * tracks = [NSMutableArray arrayWithCapacity:[videoArray count]];
for (int i=0; i<[videoArray count]; i++) {
AVURLAsset *assetClip = [[AVURLAsset alloc]initWithURL:[NSURL fileURLWithPath:[[NSString alloc] initWithFormat:#"%#%d%#", NSTemporaryDirectory(),i, #"output.mov"]] options:nil];
AVAssetTrack *clipVideoTrackB = [[assetClip tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
[timeRanges addObject:[NSValue valueWithCMTimeRange:CMTimeRangeMake(kCMTimeZero, assetClip.duration)]];
[tracks addObject:clipVideoTrackB];
}
[compositionTrack insertTimeRanges:timeRanges ofTracks:tracks atTime:kCMTimeZero error:&error];
This is the line where the program is crashing.
[compositionTrack insertTimeRanges:timeRanges ofTracks:tracks atTime:kCMTimeZero error:&error];
I am not sure why as both timeRanges and tracks have values in them
You can merge multiple videos and create a single video by appending one after other using AVFoundation classes- AVURLAsset, AVMutableComposition, AVMutableCompositionTrack etc.
You can check this Append/merge multiple video files into one final output video
There is a nice tutorials also
1.Tutorial 1
2.Tutorial 2
Hope it helps you.