I try to convert a video with NSData, it works well with little videos or 100mb, but my big files (4.44Gb) are not sent...
var video_data: NSData?
do {
video_data = try NSData(contentsOfFile: (videoPath), options: NSData.ReadingOptions.alwaysMapped)
} catch let error as NSError {
video_data = nil
return
}
How can I put big files in NSData?
Error Domain=NSCocoaErrorDomain Code=256 "Impossible d’ouvrir le fichier « D9C7DABF-4BE3-4105-8D76-AA92B1D1502E_video.notsend »." UserInfo={NSFilePath=/var/mobile/Containers/Data/Application/EAE9B4C4-BE6B-490C-BEE7-381B2DF27CC9/Library/LEADS/D9C7DABF-4BE3-4105-8D76-AA92B1D1502E_video.notsend, NSUnderlyingError=0x283be1380 {Error Domain=NSPOSIXErrorDomain Code=12 "Cannot allocate memory"}}
Any ideas ?
Thanks in advance.
EDIT 1: PARAMETERS TO SEND:
Here is the entire function. I need all that parameters to send to my server. I need to send the eventId, the contactId, the type, and the file in a Data value. The problem is that I have an error, I don't know how to put a 4.44Go file in a Data with InputStream.
func uploadVideo(_ videoPath: String, fileName: String, eventId: Int, contactId: Int, type: Int, callback: @escaping (_ data:Data?, _ resp:HTTPURLResponse?, _ error:NSError?) -> Void)
{
var video_data: Data
video_data = self.getNextChunk(urlOfFile: NSURL(string: videoPath)! as URL)!
let WSURL:String = "https://" + "renauldsqffssfd3.sqdfs.fr/qsdf"
let requestURLString = "\(WSURL)/qsdfqsf/qsdf/sdfqs/dqsfsdf/"
let url = URL(string: requestURLString)
let request = NSMutableURLRequest(url: url!)
request.httpMethod = "POST"
let boundary = generateBoundaryString()
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("Keep-Alive", forHTTPHeaderField: "Connection")
let body = NSMutableData()
let mimetype = "video/mp4"
//define the data post parameter
body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Disposition:form-data; name=\"eventId\"\r\n\r\n".data(using: String.Encoding.utf8)!)
body.append("\(eventId)\r\n".data(using: String.Encoding.utf8)!)
body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Disposition:form-data; name=\"contactId\"\r\n\r\n".data(using: String.Encoding.utf8)!)
body.append("\(contactId)\r\n".data(using: String.Encoding.utf8)!)
body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Disposition:form-data; name=\"type\"\r\n\r\n".data(using: String.Encoding.utf8)!)
body.append("\(type)\r\n".data(using: String.Encoding.utf8)!)
body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Disposition:form-data; name=\"file\"; filename=\"\(fileName)\"\r\n".data(using: String.Encoding.utf8)!)
body.append("Content-Type: \(mimetype)\r\n\r\n".data(using: String.Encoding.utf8)!)
body.append(video_data)
body.append("\r\n".data(using: String.Encoding.utf8)!)
body.append("--\(boundary)--\r\n".data(using: String.Encoding.utf8)!)
request.httpBody = body as Data
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
let task = session.uploadTask(with: request as URLRequest, from: body as Data) { loc, resp, err in
if (resp != nil)
{
let status = (resp as! HTTPURLResponse).statusCode
}
callback(loc, resp as? HTTPURLResponse, err as NSError?)
}
task.resume()
}
public func getNextChunk(urlOfFile: URL) -> Data?{
if inputStream == nil {
inputStream = InputStream(url: urlOfFile)!
inputStream!.open()
}
var buffer = [UInt8](repeating: 0, count: 1024*1024)
let len = inputStream!.read(&buffer, maxLength: 1024*1024)
if len == 0 {
return nil
}
return Data(buffer)
}
EDIT 2: COMPLEMENT TO THE SOLUTION:
The Rob solution above is perfect. I just added a control of the space on the disk to alert if the temporary file cannot copy, to delete it if incomplete, and finally advice the problem to the user.
Indeed, without that control the app will try to send to the server the file even if the file is incomplete...
func sizeOfFileAtPath(path: String) -> UInt64
{
var fileSize : UInt64
do {
//return [FileAttributeKey : Any]
let attr = try FileManager.default.attributesOfItem(atPath: path)
fileSize = attr[FileAttributeKey.size] as! UInt64
//if you convert to NSDictionary, you can get file size old way as well.
let dict = attr as NSDictionary
fileSize = dict.fileSize()
return fileSize
} catch {
print("Error: \(error)")
}
return 0
}
private func buildPayloadFile(videoFileURL: URL, boundary: String, fileName: String, eventId: Int, contactId: Int, type: Int) throws -> URL {
let mimetype = "video/mp4"
let payloadFileURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(UUID().uuidString)
guard let stream = OutputStream(url: payloadFileURL, append: false) else {
throw UploadError.unableToOpenPayload(payloadFileURL)
}
stream.open()
//define the data post parameter
stream.write("--\(boundary)\r\n")
stream.write("Content-Disposition:form-data; name=\"eventId\"\r\n\r\n")
stream.write("\(eventId)\r\n")
stream.write("--\(boundary)\r\n")
stream.write("Content-Disposition:form-data; name=\"contactId\"\r\n\r\n")
stream.write("\(contactId)\r\n")
stream.write("--\(boundary)\r\n")
stream.write("Content-Disposition:form-data; name=\"type\"\r\n\r\n")
stream.write("\(type)\r\n")
stream.write("--\(boundary)\r\n")
stream.write("Content-Disposition:form-data; name=\"file\"; filename=\"\(fileName)\"\r\n")
stream.write("Content-Type: \(mimetype)\r\n\r\n")
if stream.append(contentsOf: videoFileURL) < 0 {
throw UploadError.unableToOpenVideo(videoFileURL)
}
stream.write("\r\n")
stream.write("--\(boundary)--\r\n")
stream.close()
/*-------BEGIN ADDITION TO THE CODE---------*/
//check the size
let temporaryFileSize = self.sizeOfFileAtPath(path: payloadFileURL.relativePath)
let originalFileSize = self.sizeOfFileAtPath(path: videoFileURL.relativePath)
if (temporaryFileSize < originalFileSize || temporaryFileSize == 0)
{
let alert = UIAlertView()
alert.title = "Alert"
alert.message = "There is not enough space on the disk."
alert.addButton(withTitle: "Ok")
alert.show()
do {
try FileManager.default.removeItem(at: payloadFileURL)
} catch let error as NSError {
print("Error: \(error.domain)")
}
}
/*-------END ADDITION TO THE CODE---------*/
return payloadFileURL
}
In the catch area you have a error object, this is your answer.
UPD: I supposed this error, and right cause is
Code=12 "Cannot allocate memory"
You can try to split like - Is calling read:maxLength: once for every NSStreamEventHasBytesAvailable correct?
When dealing with assets as large as that, you want to avoid using
Data
(andNSData
) entirely. So:InputStream
;OutputStream
; andhttpBody
of the request; andAll of this avoids ever loading the whole asset into memory at one time and your peak memory usage will be far lower than it would have been if you use
Data
. This also ensures that this is unlikely to ever fail due to a lack of RAM.By the way, uploading this as a file also has the virtue that you can consider using a background
URLSessionConfiguration
at some future date (i.e. the upload of a 4 gb video is likely to take so long that the user might not be inclined to leave the app running and let the upload finish; background sessions let the upload finish even if your app is no longer running; but background uploads require file-based tasks, not relying on thehttpBody
of the request).That's a whole different issue, beyond the scope here, but hopefully the above illustrates the key issue here, namely don't use
NSData
/Data
when dealing with assets that are this large.Please note, the above uses the following extension to
OutputStream
, including method to write strings to output streams and to append the contents of another file to the stream:According to Apple documentation, you can use
NSData(contentsOf:options:)
to "read short files synchronously", so it's not supposed to be able to handle a 4 GB file. Instead you could useInputStream
and initialize it with the URL with your file path.