I'm trying to trim and download online video using AVExportSession.
code:
FileMove *fileMove = (FileMove*)data;
NSString *url = #"http://download.wavetlan.com/SVV/Media/HTTP/H264/Talkinghead_Media/H264_test1_Talkinghead_mp4_480x360.mp4";
NSURL *videoURL = [NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSLog(#"VideoURL: %#",videoURL);
AVAsset *anAsset = [AVURLAsset URLAssetWithURL:videoURL options:nil];
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:anAsset];
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
initWithAsset:anAsset presetName:AVAssetExportPresetLowQuality];
NSURL *outputURL = [NSURL fileURLWithPath:fileMove.dst];
NSLog(#"outputURL: %#",outputURL);
exportSession.outputURL = outputURL;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
CMTime start = CMTimeMakeWithSeconds(1.0, 600);
CMTime duration = CMTimeMakeWithSeconds(3.0, 600);
CMTimeRange range = CMTimeRangeMake(start, duration);
exportSession.timeRange = range;
if ([[NSFileManager defaultManager] fileExistsAtPath:fileMove.dst])
[[NSFileManager defaultManager] removeItemAtPath:fileMove.dst error:nil];
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status]) {
case AVAssetExportSessionStatusFailed:
NSLog(#"Export failed: %#", [[exportSession error]description ]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Export canceled");
break;
default:
break;
}
}];
}
Error:
Export failed: Error Domain=AVFoundationErrorDomain Code=-11800 "The
operation could not be completed" UserInfo=0x635a820
{NSLocalizedDescription=The operation could not be completed,
NSUnderlyingError=0x1ff4240 "The operation couldn’t be completed.
(OSStatus error -12780.)", NSLocalizedFailureReason=An unknown error
occurred (-12780)}
Do you guys see any issue in the code ? Is there any limitation in AVExportSession accessing online video ?
I've managed to trim remote video using AVFoundation. Here is the sample code which is written in Swift:
let range: CMTimeRange
let sourceURL: NSURL
let targetFileURL: NSURL
let requiredKeys = [ "exportable", "tracks" ]
let asset = AVAsset(URL: sourceURL)
asset.loadValuesAsynchronouslyForKeys(requiredKeys) {
// Error handling code here
precondition(asset.statusOfValueForKey("exportable", error: nil) == .Loaded)
precondition(asset.statusOfValueForKey("tracks", error: nil) == .Loaded)
precondition(asset.exportable)
let composition = AVMutableComposition()
do {
try composition.insertTimeRange(range, ofAsset: asset, atTime: kCMTimeZero)
} catch {
// Error handling code here
return
}
let finalComposition = composition.copy() as! AVComposition
guard let export = AVAssetExportSession(asset: finalComposition, presetName: AVAssetExportPresetPassthrough) else {
// Error handling code here
return
}
export.outputURL = targetFileURL
export.outputFileType = AVFileTypeMPEG4
export.exportAsynchronouslyWithCompletionHandler {
switch export.status {
case .Completed:
// Alright!
break
case .Cancelled, .Failed:
// Error handling code here
break
default:
fatalError("Shouldn't be called")
}
}
}
The URL used for creating an AVAsset needs to be a local file that you have access to. You will need to download it onto the device before creating the AVAsset like so
I think we are not able to export online videos
see here:
Unable to export AVPlayerItem
So I guess, you can download video first locally and then trim,save and use it.
Related
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.
I am now trying to export an mp3 file that has been player using AVPlayer (using an url) so it doesn't have to be downloaded twice.
This is my sample code:
I've tried every outputFileType...
self.exporter = [[AVAssetExportSession alloc] initWithAsset:self.asset presetName:AVAssetExportPresetPassthrough];
}
NSError *error;
NSLog(#"export.supportedFileTypes : %#",self.exporter.supportedFileTypes);
// "com.apple.quicktime-movie",
// "com.apple.m4a-audio",
// "public.mpeg-4",
// "com.apple.m4v-video",
// "public.3gpp",
// "org.3gpp.adaptive-multi-rate-audio",
// "com.microsoft.waveform-audio",
// "public.aiff-audio",
// "public.aifc-audio",
// "com.apple.coreaudio-format"
self.exporter.outputFileType = #"public.aiff-audio";
self.exporter.shouldOptimizeForNetworkUse = YES;
NSURL *a = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
NSURL *url = [a URLByAppendingPathComponent:#"filename.mp3"];
NSString *filePath = [url absoluteString];
self.exporter.outputURL = url;
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]){
[self.exporter exportAsynchronouslyWithCompletionHandler:^{
if (self.exporter.status == AVAssetExportSessionStatusCompleted)
{
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]){
NSLog(#"File doesn't exist at path");
}else {
NSLog#"File saved!");
}
}
else if(self.exporter.status == AVAssetExportSessionStatusFailed){
NSLog(#"Failed");
}else if(self.exporter.status == AVAssetExportSessionStatusUnknown){
NSLog(#"Unknown");
}else if(self.exporter.status == AVAssetExportSessionStatusCancelled){
NSLog(#"Cancelled");
}else if(self.exporter.status == AVAssetExportSessionStatusWaiting){
NSLog(#"Waiting");
}else if(self.exporter.status == AVAssetExportSessionStatusExporting){
NSLog(#"Exporting");
}
NSLog(#"Exporter error! : %#",self.exporter.error);
}];
}}else{
NSLog(#"File already exists at path");
}
If this is not possible to accomplish, is there any work around?
Also, as I can change the format of the audio file. What's the ideal type to work with AVAudioPlayer?
It appears AVAssetExportSession only supports filetypes for mp3 transcoding with com.apple.quicktime-movie (.mov) and com.apple.coreaudio-format (.caf) using the AVAssetExportPresetPassthrough preset. You must also be sure to use one of these file extensions when writing your output file otherwise it won't save.
Supported output filetype and extensions for an mp3 input file are in bold (tested on OS X 10.11.6):
com.apple.quicktime-movie (.mov)
com.apple.m4a-audio (.m4a)
public.mpeg-4 (.mp4)
com.apple.m4v-video (.m4v)
org.3gpp.adaptive-multi-rate-audio (.amr)
com.microsoft.waveform-audio (.wav)
public.aiff-audio (.aiff)
public.aifc-audio (.aifc)
com.apple.coreaudio-format (.caf)
If you don't mind performing a proper transcode of the audio data to another format, then you don't have to use the AVAssetExportPresetPassthrough preset. There are also AVAssetExportPresetLowQuality, AVAssetExportPresetMediumQuality, and AVAssetExportPresetHighestQuality. In the sample code that follows, the output URL has extension .m4a and the resulting transcode plays in iTunes and other media players:
AVAsset * asset = [AVAsset assetWithURL:inputURL];
AVAssetExportSession * exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.outputURL = outputURL;
exportSession.metadata = asset.metadata;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
if (exportSession.status == AVAssetExportSessionStatusCompleted)
{
NSLog(#"AV export succeeded.");
}
else if (exportSession.status == AVAssetExportSessionStatusCancelled)
{
NSLog(#"AV export cancelled.");
}
else
{
NSLog(#"AV export failed with error: %# (%ld)", exportSession.error.localizedDescription, (long)exportSession.error.code);
}
}];
I tried to export audio file with mp3 format from ipod library. and this is my solution below.
extension DirectoryListViewController: MPMediaPickerControllerDelegate {
public func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
guard let mediaItem = mediaItemCollection.items.first else { ImportExternalFileService.shared.alertImportError(); return }
guard let url = mediaItem.assetURL else { ImportExternalFileService.shared.alertImportError(); return }
guard let songTitle = mediaItem.title else { ImportExternalFileService.shared.alertImportError(); return }
guard let exportSession = AVAssetExportSession(asset: AVURLAsset(url: url), presetName: AVAssetExportPresetAppleM4A) else {
ImportExternalFileService.shared.alertImportError(); return
}
exportSession.outputFileType = .m4a
exportSession.metadata = AVURLAsset(url: url).metadata
exportSession.shouldOptimizeForNetworkUse = true
guard let fileExtension = UTTypeCopyPreferredTagWithClass(exportSession.outputFileType!.rawValue as CFString, kUTTagClassFilenameExtension) else {
ImportExternalFileService.shared.alertImportError(); return
}
let documentURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let outputURL = documentURL.appendingPathComponent("\(songTitle).\(fileExtension.takeUnretainedValue())")
/* Dont't forget to remove the existing url, or exportSession will throw error: can't save file */
do {
try FileManager.default.removeItem(at: outputURL)
} catch let error as NSError {
print(error.debugDescription)
}
exportSession.outputURL = outputURL
exportSession.exportAsynchronously(completionHandler: {
if exportSession.status == .completed {
DispatchQueue.main.async {
ImportExternalFileService.shared.importRecordFile(url: exportSession.outputURL!)
}
} else {
print("AV export failed with error:- ", exportSession.error!.localizedDescription)
}
})
}
public func mediaPickerDidCancel(_ mediaPicker: MPMediaPickerController) {
dismiss(animated: true, completion: nil)
}
}
You can't, but you can export m4a.
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:audioAsset presetName:AVAssetExportPresetAppleM4A];
exportSession.outputURL = [NSURL fileURLWithPath:exportPath];
exportSession.outputFileType = AVFileTypeAppleM4A;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
}];
I am using AVAssetExportSession for Audio Recording with assert and here is my code to convert AVAssert to AVAssertExportSession.
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:self.asset presetName:AVAssetExportPresetAppleM4A];
exportSession.outputURL = [NSURL URLWithString:dataPath];
exportSession.outputFileType = AVFileTypeAppleM4A;
exportSession.shouldOptimizeForNetworkUse = YES;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
NSLog(#".... Audio... %#",exportSession);
}];
It gives me output like this
<AVAssetExportSession: 0x177f4b30, asset = <AVURLAsset: 0x18981f60, URL = file:///private/var/mobile/Containers/Data/Application/8BB39AD5-EEFB-4AF1-A913-B26C5C072E61/tmp/1422861622SCVideo.0.m4a>, presetName = AVAssetExportPresetAppleM4A, outputFileType = com.apple.m4a-audio
Here i just want URL to NSString.
Help me for this
After the export session is complete, you can get what you want. Because it is an asynchronous operation.
[exportSession exportAsynchronouslyWithCompletionHandler:^{
if (exportSession.status == AVAssetExportSessionStatusFailed) {
NSLog(#"failed");
} else if(exportSession.status == AVAssetExportSessionStatusCompleted){
NSLog(#"completed!");
// here you can get the output url.
}
}];
I have 4 videos files which I have edited and added to a mutable composition. I am trying to use the export session to export the files, however, when I am exporting, only the first track in the list of tracks below gets exported
<AVAssetExportSession: 0x60800001da50, asset = <AVMutableComposition: 0x6080002240a0 tracks = (
"<AVMutableCompositionTrack: 0x600000224ca0 trackID = 1, mediaType = vide, editCount = 8>",
"<AVMutableCompositionTrack: 0x600000226da0 trackID = 2, mediaType = vide, editCount = 10>",
"<AVMutableCompositionTrack: 0x60000023e180 trackID = 3, mediaType = vide, editCount = 3>",
"<AVMutableCompositionTrack: 0x60000023e500 trackID = 4, mediaType = vide, editCount = 7>"
)>, presetName = AVAssetExportPreset1280x720, outputFileType = (null)
Only the first track with trackID = 1 gets exported. Here is the export session source:
// Create path to output file
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *myPathDocs = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithFormat:#"ProcessedVideo-%d.mov", arc4random() % 1000]];
NSURL *url = [NSURL fileURLWithPath:myPathDocs];
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:batchComposition presetName:AVAssetExportPreset1280x720];
NSLog(#"%#", exporter);
exporter.outputURL = url;
exporter.outputFileType = AVFileTypeQuickTimeMovie;
[exporter exportAsynchronouslyWithCompletionHandler:^(void) {
switch (exporter.status) {
case AVAssetExportSessionStatusCompleted:
NSLog(#"Completed");
break;
case AVAssetExportSessionStatusFailed:
NSLog(#"Failed:%#",exporter.error);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Canceled:%#",exporter.error);
break;
default:
break;
}
}];
Does anyone have any ideas how I can get all 4 tracks to export to a single .mov file using export session?
You can only export to one NSURL with AVAssetExportSession. If you want to export 4 separate files, you will have to export 4 times.
Thanks to jlw for his help. What I found the problem was that I was adding multiple video tracks to mutable composition. What instead I should have done is made a single video track and applied all edits from the other asset tracks onto the single video tracks. Seems AVAssetExportSession will only export a single track as noted by jlw.
Summary:
Create mutable composition
Create mutable composition track
Apply asset tracks to the composition (insertTimeRange)
Export the mutable composition
I think there is no need to create new AVMutableComposition. Instead just assign your AVVideoComposition? to the videoComposition property of your exporter.
guard let playerItem = getPlayerItem() as? AVPlayerItem else {
return
}
let asset = playerItem.asset
let videoComposition: AVVideoComposition? = playerItem.videoComposition
let path = NSTemporaryDirectory().stringByAppendingFormat("/video.mov")
if NSFileManager.defaultManager().fileExistsAtPath(path) {
do {
try NSFileManager.defaultManager().removeItemAtPath(path)
} catch {
print("Temporary file removing error.")
}
}
let outputURL = NSURL.fileURLWithPath(path)
guard let exporter = AVAssetExportSession(asset: asset,
presetName: AVAssetExportPresetHighestQuality) else {
return
}
exporter.outputURL = outputURL
exporter.outputFileType = AVFileTypeQuickTimeMovie
exporter.shouldOptimizeForNetworkUse = true
exporter.videoComposition = videoComposition // 👈
exporter.exportAsynchronouslyWithCompletionHandler {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(outputURL)
}) { (success: Bool, error: NSError?) -> Void in
if success {
} else {
}
}
}
So I've been trying to use AVAssetExportSession to trim a square video. But for some reason I just keep getting this error:
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo=0x1a03be70 {NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x1a04c5e0 "The operation couldn’t be completed. (OSStatus error -12769.)", NSLocalizedFailureReason=An unknown error occurred (-12769)}
From Apple's website I found out that -11800 is an unknown error, but what about OSStatus error -12769? I searched everywhere on the internet and I had not seen any question/problem related to this error code. Please help. Thanks!
My code here:
AVAsset *asset = [[AVURLAsset alloc]initWithURL:self.originalVidUrl options:nil];
AVAssetTrack *clipVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
if (clipVideoTrack.naturalSize.width==clipVideoTrack.naturalSize.height) {
NSLog(#"Square video");
NSArray *presets = [AVAssetExportSession exportPresetsCompatibleWithAsset:asset];
if ([presets containsObject:AVAssetExportPresetHighestQuality]) {
self.exportSession = [[AVAssetExportSession alloc]initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
self.exportSession.outputURL = [NSURL fileURLWithPath: self.tmpVidPath];
self.exportSession.outputFileType = AVFileTypeMPEG4;
CMTime start = ...
CMTime duration = ...
CMTimeRange range = CMTimeRangeMake(start, duration);
self.exportSession.timeRange = range;
[self.exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([self.exportSession status]) {
case AVAssetExportSessionStatusFailed:
NSLog(#"%#",self.exportSession.error);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Export canceled");
break;
default:
NSLog(#"Export Success, File Saved.");
break;
}
}];
}
}
P.S. this code works for videos taken with the native camera app (i.e. non-square/non-processed videos).
I think I found the solution, but I have no idea why or how it worked.
Simply change the preset name from AVAssetExportPresetHighestQuality to AVAssetExportPreset1280x720, and you're good to go!