On a device running iOS 13, [exportSession exportAsynchronouslyWithCompletionHandler: always fails with message "The operation could not be completed" while converting .MOV video to mp4. However, the same code runs fine on iOS prior to 13 i.e 12. I am pasting below my complete method
- (void)encodeVideo:(NSString *)videoURL
{
// Create the asset url with the video file
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:videoURL] options:nil];
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
// Check if video is supported for conversion or not
if ([compatiblePresets containsObject: AVAssetExportPresetLowQuality])
{
//Create Export session
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset presetName:AVAssetExportPresetLowQuality];
//Creating temp path to save the converted video
NSString* documentsDirectory= [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* myDocumentPath= [documentsDirectory stringByAppendingPathComponent:#"temp.mp4"];
NSURL *url = [[NSURL alloc] initFileURLWithPath:myDocumentPath];
//Check if the file already exists then remove the previous file
if ([[NSFileManager defaultManager]fileExistsAtPath:myDocumentPath])
{
[[NSFileManager defaultManager]removeItemAtPath:myDocumentPath error:nil];
}
exportSession.outputURL = url;
//set the output file format if you want to make it in other file format (ex .3gp)
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.shouldOptimizeForNetworkUse = YES;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status])
{
case AVAssetExportSessionStatusFailed:
NSLog(#"Export session failed");
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Export canceled");
break;
case AVAssetExportSessionStatusCompleted:
{
//Video conversion finished
NSLog(#"Successful!");
}
break;
default:
break;
}
}];
}
else
{
NSLog(#"Video file not supported!");
}
}
1.
Create Folder -
let filePath = documentDirectory.appendingPathComponent("FolderName")
if !fileManager.fileExists(atPath: filePath.path) {
do {
try fileManager.createDirectory(atPath: filePath.path, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error.localizedDescription)
return nil
}
}
2.
Let url = videoURL
destinationURL = filePath.appendingPathComponent("filename.mp4")
url.startAccessingSecurityScopedResource()
do {
try FileManager.default.copyItem(at: url, to: destinationURL)
} catch {
Logging.Log.error("EncodeVideo failed \(error.localizedDescription)")
}
url.startAccessingSecurityScopedResource()
Start Mov to MP4 now it is working.
(Please note I've already looked at this other SO post.)
The Problem
I'm trying to convert an avi video to an mp4 so that I can play it natively on an iOS app using Objective-C
What I've Tried
I'm trying the following code to do that conversion:
- (void)convertVideoToLowQuailtyWithInputURL:(NSURL*)inputURL outputURL:(NSURL*)outputURL handler:(void (^)(AVAssetExportSession*))handler {
[[NSFileManager defaultManager] removeItemAtURL:outputURL error:nil];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
exportSession.outputURL = outputURL;
exportSession.outputFileType = AVFileTypeMPEG4;
[exportSession exportAsynchronouslyWithCompletionHandler:^(void) {
handler(exportSession);
}];
}
The error that is returned from the exportSession is Cannot Open
Extra Information
When I run the video that I'm trying to convert through Mediainfo I get the following for the video:
7 332kb/s, 1920*1080 (16:9), at 25.000 FPS, AVC (Baseline#L3.1) (CABAC / 1 Ref Frames)
And this for the audio:
128 kb/s, 8 000 Hz, 16 bits, 1 channel, PCM (Little / Signed)
I also used the exportPresetsCompatibleWithAsset: method on AVAssetExportSession and got the following results:
AVAssetExportPreset1920x1080,
AVAssetExportPresetLowQuality,
AVAssetExportPresetAppleM4A,
AVAssetExportPresetHEVCHighestQuality,
AVAssetExportPreset640x480,
AVAssetExportPreset3840x2160,
AVAssetExportPresetHEVC3840x2160,
AVAssetExportPresetHighestQuality,
AVAssetExportPreset1280x720,
AVAssetExportPresetMediumQuality,
AVAssetExportPreset960x540,
AVAssetExportPresetHEVC1920x1080
Another thing to note is that when playing with the preset and the output I managed to get an audio only file that was basically white noise. This was using the preset AVAssetExportPresetAppleM4A.
I hope that I've jotted down enough information.
Update
Using the comment by Ashley, i've created a function to return the export settings compatible with the asset.
- (void)determineCompatibleExportForAsset:(AVURLAsset *)asset completion:(void(^)(NSArray<ExportSettings *> *exports))handler {
NSArray<NSString *> *presets = #[
AVAssetExportPresetLowQuality,
AVAssetExportPresetMediumQuality,
AVAssetExportPresetHighestQuality,
AVAssetExportPresetHEVCHighestQuality,
AVAssetExportPreset640x480,
AVAssetExportPreset960x540,
AVAssetExportPreset1280x720,
AVAssetExportPreset1920x1080,
AVAssetExportPreset3840x2160,
AVAssetExportPresetHEVC1920x1080,
AVAssetExportPresetHEVC3840x2160,
AVAssetExportPresetAppleM4A,
AVAssetExportPresetPassthrough
];
NSArray<NSString *> *outputs = #[
AVFileTypeQuickTimeMovie,
AVFileTypeMPEG4,
AVFileTypeAppleM4V,
AVFileTypeAppleM4A,
AVFileType3GPP,
AVFileType3GPP2,
AVFileTypeCoreAudioFormat,
AVFileTypeWAVE,
AVFileTypeAIFF,
AVFileTypeAIFC,
AVFileTypeAMR,
AVFileTypeMPEGLayer3,
AVFileTypeSunAU,
AVFileTypeAC3,
AVFileTypeEnhancedAC3,
AVFileTypeJPEG,
AVFileTypeDNG,
AVFileTypeHEIC,
AVFileTypeAVCI,
AVFileTypeHEIF,
AVFileTypeTIFF
];
__block int counter = 0;
int totalCount = (int)presets.count * (int)outputs.count;
NSMutableArray<ExportSettings *> *exportSettingsArray = [#[] mutableCopy];
for (NSString *preset in presets) {
for (NSString *output in outputs) {
[AVAssetExportSession determineCompatibilityOfExportPreset:preset withAsset:asset outputFileType:output completionHandler:^(BOOL compatible) {
if (compatible) {
ExportSettings *exportSettings = [[ExportSettings alloc] initWithPreset:preset outputType:output];
[exportSettingsArray addObject:exportSettings];
}
counter++;
if (counter == totalCount) {
if (handler) {
handler([exportSettingsArray copy]);
}
}
}];
}
}
}
The results of this are as follows:
"Preset: AVAssetExportPresetAppleM4A Output: com.apple.m4a-audio",
"Preset: AVAssetExportPresetPassthrough Output: com.microsoft.waveform-audio",
"Preset: AVAssetExportPresetPassthrough Output: public.aifc-audio",
"Preset: AVAssetExportPresetPassthrough Output: public.aiff-audio",
"Preset: AVAssetExportPresetPassthrough Output: com.apple.coreaudio-format",
"Preset: AVAssetExportPresetPassthrough Output: com.apple.quicktime-movie"
From this I deduced that using the preset AVAssetExportPresetPassthrough and output type AVFileTypeQuickTimeMovie would be compatible.
However when running the following code: (i've tried .mp4, .mov and .qt for the file type)
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"MyVideo.mov"];
NSURL *outputURL = [NSURL fileURLWithPath:filePath];
NSURL *localURL = [NSBundle URLForResource:#"20180626_145233-v" withExtension:#"avi" subdirectory:nil inBundleWithURL:[NSBundle mainBundle].bundleURL];
[self convertVideoToLowQuailtyWithInputURL:localURL outputURL:outputURL handler:^(AVAssetExportSession *exportSession) {
switch ([exportSession status]) {
case AVAssetExportSessionStatusFailed:
NSLog(#"Export failed: %#", [exportSession error]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Export canceled");
break;
case AVAssetExportSessionStatusCompleted:
NSLog(#"Successfully");
NSLog(#"OutputURL: %#", outputURL.absoluteString);
break;
default:
break;
}
}];
Which calls:
- (void)convertVideoToLowQuailtyWithInputURL:(NSURL*)inputURL outputURL:(NSURL*)outputURL handler:(void (^)(AVAssetExportSession*))handler {
[[NSFileManager defaultManager] removeItemAtURL:outputURL error:nil];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetPassthrough];
exportSession.outputURL = outputURL;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
[exportSession exportAsynchronouslyWithCompletionHandler:^(void) {
handler(exportSession);
}];
}
I get this error:
Export failed: Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedFailureReason=An unknown error occurred (-12842), NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x60400024def0 {Error Domain=NSOSStatusErrorDomain Code=-12842 "(null)"}}
The reason you cannot open the file is because iOS does not support AVI files.
Here is the link to the Apple documentation on supported file types within AVFoundation.
Or this image for posterity should the values change:
Finally, you can just inspect the definition of AVFileType from within XCode to see this list for yourself.
For what it is worth, a cursory inspection on AVI via Google indicates that there are limitations with the AVI container that have been rectified in newer formats that are supported by iOS, so there is likely little incentive to add support at a later date.
While also a potential issue being that Microsoft created AVI, I cannot locate any restrictive licensing that would prohibit someone from using it, but IANAL.
You should use FFmpeg library for that kind of purposes.
Here is an open-source video-player based on FFmpeg: https://github.com/kolyvan/kxmovie
I don't know if this player still works for the latest iOS but in any case in source codes, you can discover many interesting things that can help you with your problem (such as how to use FFmpeg library).
Very easy to do with MobileFFMpeg
https://stackoverflow.com/a/59325680/1466453
Once you have MobileFFMpeg then make a call as follows:
[MobileFFMpeg execute:#"-i infile.avi youroutput.mp4"];
I'm creating an iOS app with the functionality to record audio for several minutes. I need to have some kind of auto save function every 10/20/30 seconds to have a fallback for when the app crashes / incoming phone calls / closing app completely.
What I've tried already:
I tried to work with the file after a crash, I could see the file in iTunes & play it on my MacBook but I couldn't play it in the app anymore (probably not saved in the right way?)
I tried duplicating the file while recording but the duplicate was also not workable (probably the same reason?)
Saving the file every .. seconds was not possible at all
I've tried to detect the moment the app was crashing/closed and then save the file quickly and store it locally. This was working but wasn't reliable enough that all the code was executed before the app went in to the background.
How can I make some sort of auto-save function so I always have a fallback for the recorded audio file??
I'm recording the audio with the AVAudioRecorder and AVAudioSession. Exporting the audio file (when done or when crashing/closing) is done with an AVMutableComposition and AVAssetExportSession.
Here is some of my code for initialising the AVAudioRecorder:
self.recorder = [[AVAudioRecorder alloc] initWithURL:filePath settings:_recordSetting error:NULL];
self.recorder.delegate = self;
self.recorder.meteringEnabled = YES;
[self.recorder prepareToRecord];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
And here is how I export the audio:
AVMutableComposition* composition = [[AVMutableComposition alloc] init];
AVMutableCompositionTrack *audioCombinedTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[audioCombinedTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [audio1 duration]) ofTrack:[audio1.tracks objectAtIndex:0] atTime:kCMTimeZero error:&error];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetPassthrough];
NSString *latestFileName = [NSString stringWithFormat:#"%ld", (long)[[NSDate date] timeIntervalSince1970]];
NSString *exportPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.wav", latestFileName]];
_exportURL = [NSURL fileURLWithPath:exportPath];
exportSession.outputURL = _exportURL;
exportSession.outputFileType = AVFileTypeWAVE;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
NSLog (#"Exporting. status is %ld", (long)exportSession.status);
switch (exportSession.status) {
case AVAssetExportSessionStatusFailed:
NSLog(#"export failed");
break;
case AVAssetExportSessionStatusExporting: {
NSLog(#"export exporting");
break;
}
case AVAssetExportSessionStatusCancelled: {
NSLog(#"export cancelled");
break;
}
case AVAssetExportSessionStatusWaiting: {
NSLog(#"export waiting");
break;
}
case AVAssetExportSessionStatusUnknown: {
NSLog(#"export unknown");
break;
}
case AVAssetExportSessionStatusCompleted: {
NSLog(#"export done");
break;
}
};
}];
Note: when using the app (so just recording and stopping the recorder) the audio files are created correctly and everything works fine.
I have been using AVPlayer to play video where the underlying asset that is backed by a Streaming URL.
I am trying to create a snip of the video (just a 9 second clip), in order to save the clip locally.
I tried AVAssetExportSession with the following code, but the session returns AVAssetExportSessionStatusFailed every time. The specific error is Error Domain=AVFoundationErrorDomain Code=-11800, which means unknown error.
AVURLAsset *otherAsset = [[AVURLAsset alloc] initWithURL:streamURL options:options];
NSArray *exportPresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:otherAsset];
NSLog(exportPresets.description);
AVAssetExportSession *session = [AVAssetExportSession exportSessionWithAsset:otherAsset presetName:AVAssetExportPresetMediumQuality];
NSArray *supportedFileTypes = session.supportedFileTypes;
NSLog(supportedFileTypes.description);
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
NSString *dstPath = [documentsDirectory stringByAppendingString:#"/sample.mov"];
NSURL *savetUrl = [NSURL fileURLWithPath:dstPath];
session.outputFileType = #"com.apple.quicktime-movie";
session.outputURL = savetUrl;
session.shouldOptimizeForNetworkUse = YES;
[session exportAsynchronouslyWithCompletionHandler:
^(void ) {
switch ([session status]) {
case AVAssetExportSessionStatusFailed:
NSLog([session error].description);
NSLog(#"Export failed: %#",[[session error]localizedDescription]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Export canceled");
break;
default:
NSLog(#"Export Success, File Saved.");
break;
}
}
];
I've checked to make sure that I am not overwriting a file, and checked other causes of AVAssetExportSessionStatusFailed.
I am guessing that AVAssetExportSession is not meant to be used with streaming assets. (I could be wrong).
Is my guess right, or is there something else I need to do. Is there an easier way to snip and cache a streaming URL?
Thanks for your time!
For as far as I know, there is no way to export other than local files under iOS7.
It seems to work fine under iOS8 though.
I suggest you just use NSData.writeToFile(filePath, atomically: bool) if you don't have to set an exportInterval.
Hope this'll help!
I have a requirement where user will be allowed to trim a audio file before submitting to server. The trimming function works fine in iOS 6 and not in iOS 7.
This happens in iOS 7 when user chooses a song from iTunes library and start trimming. It appears as trimmed. The new file which is created after trimming plays upto trimmed and rest will be blank. Also the duration shows the original song duration. This doesn't happen for all files. It happens only for some files. Also I did check the exportable and hasProtectedContent . Both have correct values (exportable - yes, hasProtectedContent - no). Can I know what could be issue in iOS 7.
I am pasting the trimming audio file code for reference
- (void)trimAndExportAudio:(AVAsset *)avAsset withDuration:(NSInteger)durationInSeconds withStartTime:(NSInteger)startTime endTime:(NSInteger)endTime toFileName:(NSString *)filename withTrimCompleteBlock:(TrimCompleteBlock)trimCompleteBlock
{
if (startTime < 0 || startTime > durationInSeconds || startTime >= endTime)
{
CGLog(#"start time = %d endTime %d durationInSeconds %d", startTime, endTime, durationInSeconds);
trimCompleteBlock(NO, #"Invalid Start Time");
return;
}
if (endTime > durationInSeconds)
{
CGLog(#"start time = %d endTime %d durationInSeconds %d", startTime, endTime, durationInSeconds);
trimCompleteBlock(NO, #"Invalid End Time");
return;
}
// create the export session
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:avAsset presetName:AVAssetExportPresetAppleM4A];
if (exportSession == nil)
{
trimCompleteBlock(NO, #"Could not create an Export Session.");
return;
}
//export file path
NSError *removeError = nil;
NSString *filePath = [[CGUtilities applicationLibraryMyRecordingsDirectory] stringByAppendingPathComponent:filename];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&removeError];
}
if (removeError)
{
CGLog(#"Error removing existing file = %#", removeError);
}
// create trim time range
CMTime exportStartTime = CMTimeMake(startTime, 1);
CMTime exportStopTime = CMTimeMake(endTime, 1);
CMTimeRange exportTimeRange = CMTimeRangeFromTimeToTime(exportStartTime, exportStopTime);
// configure export session output with all our parameters
exportSession.outputURL = [NSURL fileURLWithPath:filePath]; // output path
exportSession.outputFileType = AVFileTypeAppleM4A; // output file type
exportSession.timeRange = exportTimeRange; // trim time range
//perform the export
__weak AVAssetExportSession *weakExportSession = exportSession;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
if (AVAssetExportSessionStatusCompleted == exportSession.status)
{
if (![filename isEqualToString:kLibraryTempFileName])
{
//created a new recording
}
trimCompleteBlock(YES, nil);
}
else if (AVAssetExportSessionStatusFailed == exportSession.status)
{
// a failure may happen because of an event out of your control
// for example, an interruption like a phone call comming in
// make sure and handle this case appropriately
trimCompleteBlock(NO, weakExportSession.error.description);
}
else
{
trimCompleteBlock(NO, weakExportSession.error.description);
}
}];
}
Thanks
We can import AVFoundation/AVFoundation.h
-(BOOL)trimAudiofile{
float audioStartTime;//define start time of audio
float audioEndTime;//define end time of audio
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd_HH-mm-ss"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryCachesDirectory = [paths objectAtIndex:0];
libraryCachesDirectory = [libraryCachesDirectory stringByAppendingPathComponent:#"Caches"];
NSString *OutputFilePath = [libraryCachesDirectory stringByAppendingFormat:#"/output_%#.mp4", [dateFormatter stringFromDate:[NSDate date]]];
NSURL *audioFileOutput = [NSURL fileURLWithPath:OutputFilePath];
NSURL *audioFileInput;//<Path of orignal audio file>
if (!audioFileInput || !audioFileOutput)
{
return NO;
}
[[NSFileManager defaultManager] removeItemAtURL:audioFileOutput error:NULL];
AVAsset *asset = [AVAsset assetWithURL:audioFileInput];
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:asset
presetName:AVAssetExportPresetAppleM4A];
if (exportSession == nil)
{
return NO;
}
CMTime startTime = CMTimeMake((int)(floor(audioStartTime * 100)), 100);
CMTime stopTime = CMTimeMake((int)(ceil(audioEndTime * 100)), 100);
CMTimeRange exportTimeRange = CMTimeRangeFromTimeToTime(startTime, stopTime);
exportSession.outputURL = audioFileOutput;
exportSession.timeRange = exportTimeRange;
exportSession.outputFileType = AVFileTypeAppleM4A;
[exportSession exportAsynchronouslyWithCompletionHandler:^
{
if (AVAssetExportSessionStatusCompleted == exportSession.status)
{
NSLog(#"Export OK");
}
else if (AVAssetExportSessionStatusFailed == exportSession.status)
{
NSLog(#"Export failed: %#", [[exportSession error] localizedDescription]);
}
}];
return YES;
}