AVAssetExportSession only works first 6 times - ios

Updated code sample, console output and information
I'm trying to get the total duration of a video composition, divide it by 10, round up, then loop through and splice the video composition at those intervals.
It works, but after the 'currentDuration' becomes 60+, it throws a "The requested URL was not found on this server."
Basically if it determines it needs to produce 9 clips, it succeeds the first 6 and fails the other 3. Also my numLoop isn't working as expected, it almost seems the while loop finishes before any of the 9 clips are attempted.
Would love some help/insight into getting all 9 clips exporting.
I've noticed if the video is less than 60 seconds long, it has 100% success rate. Any video I choose over 60 seconds will fails on the 7th clip.
Here's my method:
func splitVideo(videoComposition: AVMutableVideoComposition) {
let fileManager = NSFileManager.defaultManager()
let documentsPath : String = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask,true)[0]
//grab total duration/number of splits
let exporter: AVAssetExportSession = AVAssetExportSession(asset: asset!, presetName:AVAssetExportPresetHighestQuality)!
let totalDuration = Float64(CMTimeGetSeconds(exporter.asset.duration))
let totalDurationPieces = (totalDuration/10)
var numberOfSplits=Int(ceil(Double(totalDurationPieces)))
//prepare for loop
var loopNum : Int = 0
var currentDuration : Float64 = 0
var sessionNumber = (arc4random()%1000)
let opQueue = NSOperationQueue()
opQueue.maxConcurrentOperationCount = 1
while loopNum < numberOfSplits {
//new exporter
var destinationPath: String = documentsPath + "/splitVideo-"+String(sessionNumber)
destinationPath+="-"+String(Int(loopNum))+".mp4"
let new_exporter = AVAssetExportSession(asset: asset!, presetName:AVAssetExportPresetHighestQuality)!
new_exporter.outputURL = NSURL(fileURLWithPath: destinationPath as String)
new_exporter.videoComposition = videoComposition
new_exporter.outputFileType = AVFileTypeMPEG4
new_exporter.shouldOptimizeForNetworkUse = false
new_exporter.timeRange = CMTimeRangeMake(
CMTimeMakeWithSeconds(currentDuration, framesPerSecond!),CMTimeMakeWithSeconds(Float64(10),framesPerSecond!))
// Set up the exporter, then:
opQueue.addOperationWithBlock { () -> Void in
new_exporter.exportAsynchronouslyWithCompletionHandler({
dispatch_async(dispatch_get_main_queue(),{
print("Exporting... \(loopNum)")
self.exportDidFinish(new_exporter, loopNum: loopNum)
})
}) // end completion handler
} // end block
//prepare for next loop
loopNum = loopNum+1
currentDuration = currentDuration+10
if(loopNum>=numberOfSplits){
self.allExportsDone(Int(numberOfSplits))
}
} // end while
}
Here's the exportDidFinish method:
func exportDidFinish(session: AVAssetExportSession, loopNum: Int) {
let outputURL: NSURL = session.outputURL!
let library: ALAssetsLibrary = ALAssetsLibrary()
if(library.videoAtPathIsCompatibleWithSavedPhotosAlbum(outputURL)) {
library.writeVideoAtPathToSavedPhotosAlbum(outputURL, completionBlock: {(url, error) in
//done
print("Success on \(Int(loopNum))")
})
}
}
Here's the console output:
Exporting... 9
2016-08-20 13:39:27.980 TrimVideo[4776:1576022] Video /var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-6.mp4 cannot be saved to the saved photos album: Error Domain=NSURLErrorDomain Code=-1100 "The requested URL was not found on this server." UserInfo={NSUnderlyingError=0x1457f43f0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}, NSErrorFailingURLStringKey=file:///var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-6.mp4, NSErrorFailingURLKey=file:///var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-6.mp4, NSURL=file:///var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-6.mp4, NSLocalizedDescription=The requested URL was not found on this server.}
Exporting... 9
2016-08-20 13:39:27.984 TrimVideo[4776:1576022] Video /var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-7.mp4 cannot be saved to the saved photos album: Error Domain=NSURLErrorDomain Code=-1100 "The requested URL was not found on this server." UserInfo={NSUnderlyingError=0x1457f88c0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}, NSErrorFailingURLStringKey=file:///var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-7.mp4, NSErrorFailingURLKey=file:///var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-7.mp4, NSURL=file:///var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-7.mp4, NSLocalizedDescription=The requested URL was not found on this server.}
Exporting... 9
2016-08-20 13:39:27.988 TrimVideo[4776:1576022] Video /var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-8.mp4 cannot be saved to the saved photos album: Error Domain=NSURLErrorDomain Code=-1100 "The requested URL was not found on this server." UserInfo={NSUnderlyingError=0x14687cb30 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}, NSErrorFailingURLStringKey=file:///var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-8.mp4, NSErrorFailingURLKey=file:///var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-8.mp4, NSURL=file:///var/mobile/Containers/Data/Application/BE125EB7-AFC4-48B5-95C3-941B420BB71F/Documents/splitVideo-972-8.mp4, NSLocalizedDescription=The requested URL was not found on this server.}
Exporting... 9
Success on 9
Exporting... 9
Success on 9
Exporting... 9
Success on 9
Exporting... 9
Success on 9
Exporting... 9

Ok, some news.
ALAssetsLibrary is deprecated since iOS 8, with Apple moving everyone off to the Photos Framework to do this. Good news is that AVAssetExportSession is not deprecated. While you could carry on with the deprecated API, it might be a good idea to rewrite that exportDidFinish function to use the new API.
The while loop in the splitVideo function is throwing out ten concurrent export operations. Its a bit of a guess to be honest, but I suspect that there is some resource contention kicking in once you get to clip 6.
So that needs to be redesigned to be a little more friendly. Best bet is to use an NSOperationQueue with the maxConcurrentOperationsCount set to one (i.e. a serial queue).
Something like:
let opQueue = NSOperationQueue()
opQueue.maxConcurrentOperationsCount = 1
for loopNum in 0..<numberOfSplits {
// Set up the exporter, then:
opQueue.addOperationWithBlock { () -> Void in
new_exporter.exportAsynchronouslyWithCompletionHandler({
dispatch_async(dispatch_get_main_queue(),{
print("Exporting... \(loopNum)")
self.exportDidFinish(new_exporter, loopNum: loopNum)
})
} // end completion handler
} // end block
} // end while
The purpose of this is to ensure that the export operations get run one at a time rather than an attempt at all at once. If that succeeds, you can experiment with upping the maxConcurrentOperationsCount to get some multi-threading going on.

Related

Downloading multiple HLS audio files simultaneously fails

I have been trying to multiple HLS audio files together in iOS. The files are encrypted with a key. I need to fetch the key and store it locally for offline use. When I download a small number (2 or 3 files) of files simultaneously it works fine, but if I start downloading 10-15 files simultaneously most of them fails with the error message-
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could
not be completed" UserInfo={NSLocalizedFailureReason=An unknown error
occurred (-17377), NSLocalizedDescription=The operation could not be
completed}
I am getting error message from NSURLErrorDomain as well but they are rare.
The link of the approach which I am using to fetch the key for offline use is below -
Playing Offline HLS with AES-128 encryption iOS
Any help would be appreciated.
class AudioDownloader {
var productKey: String
var downloadUrl: URL
var downloadSession: AVAssetDownloadURLSession?
var fakeDownloadUrl: URL?
var downloadTask: AVAssetDownloadTask?
func downloadAudio() {
if downloadSession == nil {
let configuration = URLSessionConfiguration.background(withIdentifier: self.productKey)
downloadSession = AVAssetDownloadURLSession(configuration: configuration, assetDownloadDelegate: self, delegateQueue: OperationQueue.main)
configuration.shouldUseExtendedBackgroundIdleMode = true
configuration.httpShouldSetCookies = true
configuration.httpShouldUsePipelining = false
configuration.allowsCellularAccess = true
configuration.isDiscretionary = true
}
self.fakeDownloadUrl = self.convertToScheme(url: self.downloadUrl, scheme: "fakehttp")
let asset = AVURLAsset(url: self.fakeDownloadUrl!)
let loader = asset.resourceLoader
loader.setDelegate(self, queue: DispatchQueue(label: "dispatch2"))
self.downloadTask = downloadSession?.makeAssetDownloadTask(asset: asset, assetTitle: "assetTitle \(self.productKey)", assetArtworkData: nil, options: nil)!
self.downloadTask?.taskDescription = self.productKey
self.downloadTask?.resume()
}
}

SFSpeechRecognizer (Siri Transcription) Timeout Error on iOS App

In my iOS app, I am trying to transcribe prerecorded audio using iOS 10's latest feature, the Speech API.
Multiple sources including the documentation have stated that the audio duration limit for the Speech API (more specifically SFSpeechRecognizer) is 1 minute.
In my code, I have found that any audio files with a length of about 15 seconds or more, will get the following error.
Error Domain=kAFAssistantErrorDomain Code=203 "SessionId=com.siri.cortex.ace.speech.session.event.SpeechSessionId#50a8e246, Message=Timeout waiting for command after 30000 ms" UserInfo={NSLocalizedDescription=SessionId=com.siri.cortex.ace.speech.session.event.SpeechSessionId#50a8e246, Message=Timeout waiting for command after 30000 ms, NSUnderlyingError=0x170248c40 {Error Domain=SiriSpeechErrorDomain Code=100 "(null)"}}
I have searched all over the internet and have not been able to find a solution to this. There also have been people with the same problem. Some people suspect that it's a problem with Nuance.
It is also worth noting that I do get partial results from the transcription process.
Here's the code from my iOS app.
` // Create a speech recognizer request object.
let srRequest = SFSpeechURLRecognitionRequest(url: location)
srRequest.shouldReportPartialResults = false
sr?.recognitionTask(with: srRequest) { (result, error) in
if let error = error {
// Something wrong happened
print(error.localizedDescription)
} else {
if let result = result {
print(4)
print(result.bestTranscription.formattedString)
if result.isFinal {
print(5)
transcript = result.bestTranscription.formattedString
print(result.bestTranscription.formattedString)
// Store the transcript into the database.
print("\nSiri-Transcript: " + transcript!)
// Store the audio transcript into Firebase Realtime Database
self.firebaseRef = FIRDatabase.database().reference()
let ud = UserDefaults.standard
if let uid = ud.string(forKey: "uid") {
print("Storing the transcript into the database.")
let path = "users" + "/" + uid + "/" + "siri_transcripts" + "/" + date_recorded + "/" + filename.components(separatedBy: ".")[0]
print("transcript database path: \(path)")
self.firebaseRef.child(path).setValue(transcript)
}
}
}
}
}`
Thank you for your help.
I haven't confirmed my answer aside from someone else running into the same problem but I believe it is an undocumented limit on prerecorded audio.
Remove the result.isFinal and do a null check for the result instead. Reference: https://github.com/mssodhi/Jarvis-ios/blob/master/Jarvis-ios/HomeCell%2Bspeech.swift
This is true, I extracted the audio file from the video, and if it exceeds 15 seconds, it will give the following error:
Domain = kAFAssistantErrorDomain Code = 203 "Timeout" UserInfo = {
NSLocalizedDescription = Timeout,
NSUnderlyingError = 0x1c0647950 {Error Domain=SiriSpeechErrorDomain Code=100 "(null)"}
}
The key issue is the audio file recognition after more than 15 seconds.
result.isFinal is always 0, which is very frustrating is that there is no accurate timestamp, although it is "Timeout", it has complete recognition content, which makes me feel weird.
If you print out the result traversal, you can see that there is some restriction, which is 15 seconds, but the reason is that the timestamp feedback of the audio file is limited to a limited number, such as 15 or 4 or 9, leading to the end. Timeout feedback is more unstable.
But in real-time speech recognition, you can break through 15 seconds, as described in the official documentation, within one minute.

Error 'The request timed out' when uploading files in parallel on AWSS3

I have a problem with the parallel upload in AWSS3
I'm using
AWSTask(forCompletionOfAllTasks:tasks).continueWithBlock({task -> AWSTask in
to upload files in parallel on S3. It works well with small amount of file, but when I'm sending more than 50 files in parallel, I have a lot of timeout error and I don't understand why.
I tried several time to load 150 files in parallel and I have a timeout after the 60th file was uploaded with success. There is around 50 timeout error which take around 1 or 2 minutes, after that, the upload continue and at the end all the files are uploaded successfully but these timeout slow a lot the upload...
I see the errors in the logs but I don't know how to catch it in the code, the upload request (AWSS3TransferManagerUploadRequest) didn't return an error, so I cannot display an error to inform the user that something goes wrong.
Has anybody an idea how to solve the problem? And if there is no solution for the timeout, do you know how to catch the error for at least display an error on the screen?
I already tried to change the timeoutIntervalForRequest/timeoutIntervalForResource but it doesn't change anything.
The error is (fifty times or more):
20 AWSiOSSDK v2.4.8 [Error] AWSURLSessionManager.m line:212 | -[AWSURLSessionManager URLSession:task:didCompleteWithError:] | Session task failed with error: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSErrorFailingURLStringKey=https://s3.amazonaws.com/***/user-72358/project-***/***.jpg?partNumber=1&uploadId=***, _kCFStreamErrorCodeKey=-2102, NSErrorFailingURLKey=https://s3.amazonaws.com/***/user-***/project-***/***.jpg?partNumber=1&uploadId=***, NSLocalizedDescription=The request timed out., _kCFStreamErrorDomainKey=4, NSUnderlyingError=0x1dc69d80 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102}}}
And here is my code :
func uploadAllFileRequests(imageArray:[String], completion:(filedataReturned:FileData?, uploadOfFileSucceeded: Bool, error: NSError?)-> Void,progress:(totalSent: Int64, totalExpect: Int64)-> Void) -> Void
{
var tasks = [AWSTask]()
let keyCred = s3CredentialsInfo?.key
AWSS3TransferManager.registerS3TransferManagerWithConfiguration(serviceConfiguration, forKey: keyCred)
let transferManager:AWSS3TransferManager = AWSS3TransferManager.S3TransferManagerForKey(keyCred)
for imageFilePath:String in imageArray {
...
let keyOnS3 = "\(keyCred!)/\(filename)"
// url image to upload to s3
let url:NSURL = NSURL(fileURLWithPath: filedata.getFullpath())
// next we set up the S3 upload request manager
let uploadRequest = AWSS3TransferManagerUploadRequest()
uploadRequest?.bucket = s3CredentialsInfo?.bucket
uploadRequest?.key = keyOnS3
uploadRequest?.contentType = type
uploadRequest?.body = url
// Track Upload Progress through AWSNetworkingUploadProgressBlock
uploadRequest?.uploadProgress = {(bytesSent:Int64, totalBytesSent:Int64, totalBytesExpectedToSend:Int64) in
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
progress(totalSent: currentTotal, totalExpect: totalSize)
})
}
tasks.append(transferManager.upload(uploadRequest).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock:{task -> AWSTask in
if(task.result != nil){
// upload success
print("-> upload successfull \(keyOnS3) on S3")
completion(filedataReturned:filedata, uploadOfFileSucceeded: true, error: nil)
}else{
print("->task error upload on S3 : \(task.error)")
}
return task
}))
}
AWSTask(forCompletionOfAllTasks:tasks).continueWithBlock({task -> AWSTask in
print("-> parallel task is \(task)")
if(!task.cancelled && !task.faulted){
// upload success
print("-> upload successfull on S3")
}else{
print("->error of parallel upload on S3")
completion(filedataReturned:nil, uploadOfFileSucceeded: false, error: task.error)
}
return task
})
}
I'm using AWSCore and AWSS3 cocoapods version 2.4.8
Thanks in advance for your help
Stéphanie
The error of time out comes once your network is slow, for this you just have to increate time out in configuration.
In your case
let serviceConfiguration = AWSServiceConfiguration(region: convertedRegion, credentialsProvider: credentialsProvider)
configuration?.timeoutIntervalForRequest = 60 // in seconds

Swift: downloadTaskWithURL sometimes "succeeds" with non-nil location even though file does not exist

The downloadTaskWithURL function sometimes returns a non-nil location for a file that does not exist.
There is no file at http://192.168.0.102:3000/p/1461224691.mp4 in the test environment.
Most of the time, invoking downloadTaskWithURL on this URL results in the expected error message:
Error downloading message during network operation. Download URL:
http://192.168.0.102:3000/p/1461224691.mp4. Location: nil. Error:
Optional(Error Domain=NSURLErrorDomain Code=-1100 "The requested URL
was not found on this server." UserInfo=0x17547b640
{NSErrorFailingURLKey=http://192.168.0.102:3000/p/1461224691.mp4,
NSLocalizedDescription=The requested URL was not found on this
server.,
NSErrorFailingURLStringKey=http://192.168.0.102:3000/p/1461224691.mp4})
Occasionally, and in a non-deterministic way, downloadTaskWithURL believes the file exists and writes something to the location variable. As a result, the guard condition does not fail, and the code continues to execute ... which it should not.
The permanent file created by fileManager.moveItemAtURL(location!, toURL: fileURL) is only 1 byte, confirming that the network file never existed in the first place.
Why does downloadTaskWithURL behave like this?
func download() {
// Verify <networkURL>
guard let downloadURL = NSURL(string: networkURL) where !networkURL.isEmpty else {
print("Error downloading message: invalid download URL. URL: \(networkURL)")
return
}
// Generate filename for storing locally
let suffix = (networkURL as NSString).pathExtension
let localFilename = getUniqueFilename("." + suffix)
// Create download request
let task = NSURLSession.sharedSession().downloadTaskWithURL(downloadURL) { location, response, error in
guard location != nil && error == nil else {
print("Error downloading message during network operation. Download URL: \(downloadURL). Location: \(location). Error: \(error)")
return
}
// If here, no errors so save message to permanent location
let fileManager = NSFileManager.defaultManager()
do {
let documents = try fileManager.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
let fileURL = documents.URLByAppendingPathComponent(localFilename)
try fileManager.moveItemAtURL(location!, toURL: fileURL)
self.doFileDownloaded(fileURL, localFilename: localFilename)
print("Downloaded file # \(localFilename). Download URL: \(downloadURL)")
} catch {
print("Error downloading message during save operation. Network URL: \(self.networkURL). Filename: \(localFilename). Error: \(error)")
}
}
// Start download
print("Starting download # \(downloadURL)")
task.resume()
}
Just to clarify, the server is returning a 404, but the download task is returning a basically-empty file? And you're certain that the server actually returned an error code (by verifying the server logs)?
Either way, I would suggest checking the status code in the response object. If it isn't a 200, then the download task probably just downloaded the response body of an error page. Or, if the status code is 0, the connection timed out. Either way, treat it as a failure.
You might also try forcing this to all happen on a single thread and see if the nondeterminism goes away.

Upload picture using alamofire

so my doubt is on how to upload an image using Alamofire, from what researched people uses the solutions here https://github.com/Alamofire/Alamofire/issues/110. However my curl request should look like this:
curl -X POST "api.local.app.com:9000/1/media/upload" -F “picture=#filename.png” -H “Authorization: Alpha ahudhasiadoaidjiajdiudaiusdhuiahdu” -v
I'm trying to do that this way:
Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders = ["Authorization" : "Alpha \(userToken)" ]
var fileManager = NSFileManager()
var tmpDir = NSTemporaryDirectory()
let filename = "tempPicture.png"
let path = tmpDir.stringByAppendingPathComponent(filename)
var error: NSError?
let imageData = UIImagePNGRepresentation(image)
fileManager.removeItemAtPath(path, error: nil)
println(NSURL(fileURLWithPath: path))
if(imageData.writeToFile(path,atomically: true)){
println("Image saved")
}else{
println("Image not saved")
}
Alamofire.upload(.POST, databaseURL + "/media/upload", NSURL(fileURLWithPath: path)!).progress { (bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) in
println( String(totalBytesWritten) + "/" + String(totalBytesExpectedToWrite))
}
.responseJSON { (request, response, data, error) in
From what I understand this should work however the Alamo returns
Error: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "The
operation couldn’t be completed. (Cocoa error 3840.)" (Invalid value
around character 2.) UserInfo=0x7e16d9e0 {NSDebugDescription=Invalid
value around character 2.}).
I have already provided a detail answer here. Most likely your server expects the image to be multipart form data encoded which is why it is getting rejected. There are many details provided in that link, along with next steps.

Resources