Ios Swift-将视频转换为数据和字符串并使用UTF8编码返回nil-通过HTTP POST将视频发送到AWS S3存储桶
问题的高级解释(在Swift 5中)Ios Swift-将视频转换为数据和字符串并使用UTF8编码返回nil-通过HTTP POST将视频发送到AWS S3存储桶,ios,swift,amazon-web-services,amazon-s3,video,Ios,Swift,Amazon Web Services,Amazon S3,Video,问题的高级解释(在Swift 5中) 我正在使用AVAssetWriter 我正在使用exportSession.exportAsynchronously在MP4中对视频进行编码(但是我可以跳过这一步,我仍然有相同的问题) 我通过HTTP POST将视频发送到AWS S3存储桶,其中包含: let fileData=尝试将NSData(contentsOfFile:videoPathMP4.path,选项:[])作为数据使用 让fileContent=String(数据:fileData,编
AVAssetWriter
exportSession.exportAsynchronously
在MP4中对视频进行编码(但是我可以跳过这一步,我仍然有相同的问题)
let fileData=尝试将NSData(contentsOfFile:videoPathMP4.path,选项:[])作为数据使用
让fileContent=String(数据:fileData,编码:.utf8)
fileContent
现在为nil
,这意味着视频数据不能用UTF8解释。如果我使用UTF16,它可以工作(我得到一个字符串),但当我在服务器端收到消息时,它不是一个可读的MP4文件(它已损坏?)。我觉得这是因为它应该是UTF8中的字符串,但我无法将视频数据转换为UTF8字符串发送到服务器
如何以UTF8格式发送此数据,或者如何仅以NSData格式发送视频数据?我是不是看错了
以下是不同步骤中的代码片段:
步骤1-在MOV中录制视频:
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
let timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer).seconds
switch _captureState {
case .start:
print ("starting to record")
// Set up recorder
_filename = UUID().uuidString
let videoPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("\(_filename).mov")
let writer = try! AVAssetWriter(outputURL: videoPath, fileType: .mov)
let settings = _videoOutput!.recommendedVideoSettingsForAssetWriter(writingTo: .mov)
let input = AVAssetWriterInput(mediaType: .video, outputSettings: settings)
input.mediaTimeScale = CMTimeScale(bitPattern: 600)
input.expectsMediaDataInRealTime = true
input.transform = CGAffineTransform(rotationAngle: .pi/2)
let adapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: input, sourcePixelBufferAttributes: nil)
if writer.canAdd(input) {
writer.add(input)
}
writer.startWriting()
writer.startSession(atSourceTime: .zero)
_assetWriter = writer
_assetWriterInput = input
_adpater = adapter
_captureState = .capturing
_time = timestamp
case .capturing:
if _assetWriterInput?.isReadyForMoreMediaData == true {
let time = CMTime(seconds: timestamp - _time, preferredTimescale: CMTimeScale(600))
_adpater?.append(CMSampleBufferGetImageBuffer(sampleBuffer)!, withPresentationTime: time)
}
break
case .end:
guard _assetWriterInput?.isReadyForMoreMediaData == true, _assetWriter!.status != .failed else { break }
_assetWriterInput?.markAsFinished()
_assetWriter?.finishWriting { [weak self] in
self?._captureState = .idle
self?._assetWriter = nil
self?._assetWriterInput = nil
print ("Finished writing video file: \(self!._filename)")
}
default:
break
}
}
步骤2-在MP4中编码视频(同步以避免发送数据时出现争用情况):
步骤3-发送视频(获取到S3的临时URL并进行身份验证以发送实际的视频文件,然后发送视频文件-所有阻止调用的调用):
private func sendVideo(文件名:String,记录ID:String){
//获取临时URL和凭据
让url=url(字符串:“https:/”)!
//服务器的JSON负载
让数据=[
“记录ID”:记录ID,
]as[字符串:任意]
let group=DispatchGroup()
group.enter()
sendVideoURLRequestToRestAPI(url:url,数据:数据)
{响应,json,错误在
//将在完成或出错时调用。
guard let statusCode=响应?.statusCode其他{
打印(“[sendVideo]未返回状态代码-请求失败”)
返回
}
打印(“来自服务器的状态代码:\(状态代码)”)
如果状态代码!=200{
DispatchQueue.main.async{
让alert2=UIAlertController(标题:“错误”,消息:“提交数据时出错-请重试!-\(json[“body”]as?String)”,首选样式:。警报)
addAction(UIAlertAction(标题:“确定”,样式:。默认,处理程序:nil))
self.present(警报2,动画:true)
}
}否则{//成功
让videoURL=json[“url”]作为!字符串
设参数=[
[
“密钥”:“密钥”,
“值”:(json[“字段”]as![String:Any])[“键”!,
“类型”:“文本”
],
[
“密钥”:“AWSAccessKeyId”,
“值”:(json[“字段”]as![String:Any][“AWSAccessKeyId”]!,
“类型”:“文本”
],
[
“密钥”:“签名”,
“值”:(json[“字段”]as![String:Any])[“签名”!,
“类型”:“文本”
],
[
“密钥”:“策略”,
“值”:(json[“字段”]as![String:Any])[“策略”!,
“类型”:“文本”
],
[
“密钥”:“x-amz-security-token”,
“值”:(json[“字段”]as![String:Any][“x-amz-security-token”]!,
“类型”:“文本”
],
[
“密钥”:“文件”,
“src”:文件名,
“类型”:“文件”,
“内容类型”:“视频/mp4”
]
]as[[String:Any]]
let boundary=“boundary-\(UUID().uuidString)”
var body=“”
变量错误:错误?=nil
参数中的参数{
如果参数[“禁用”]==nil{
让paramName=param[“key”]!
正文+=“--\(边界)\r\n”
body+=“内容处置:表单数据;名称=\”\(paramName)\“”
如果参数[“contentType”]!=nil{
body+=“\r\n内容类型:\(参数[“contentType”]as!String)”
}
让paramType=param[“type”]作为!字符串
如果paramType==“text”{
让paramValue=param[“value”]作为!字符串
正文+=“\r\n\r\n\(参数值)\r\n”
}否则{
让paramSrc=param[“src”]作为!字符串
打印(“paramSrc:\(paramSrc)”)
让videoPath=FileManager.default.url(用于:.documentDirectory,位于:.userDomainMask中)。首先!.appendingPathComponent(“\(paramSrc.mov”)
encodeVideo(位于:videoPath,completionHandler:{url,错误在
保护错误==nil else{
打印(“错误视频mp4转换”)
打印(字符串(描述:错误))
返回
}
})
func encodeVideo(at videoURL: URL, completionHandler: ((URL?, Error?) -> Void)?) {
let avAsset = AVURLAsset(url: videoURL, options: nil)
let startDate = Date()
//Create Export session
guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) else {
completionHandler?(nil, nil)
return
}
//Creating temp path to save the converted video
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
let filePath = documentsDirectory.appendingPathComponent("rendered-Video.mp4")
//Check if the file already exists then remove the previous file
if FileManager.default.fileExists(atPath: filePath.path) {
do {
try FileManager.default.removeItem(at: filePath)
} catch {
completionHandler?(nil, error)
}
}
exportSession.outputURL = filePath
exportSession.outputFileType = AVFileType.mp4
exportSession.shouldOptimizeForNetworkUse = true
let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
let range = CMTimeRangeMake(start: start, duration: avAsset.duration)
exportSession.timeRange = range
let group = DispatchGroup()
group.enter()
exportSession.exportAsynchronously(completionHandler: {() -> Void in
switch exportSession.status {
case .failed:
print(exportSession.error ?? "NO ERROR")
completionHandler?(nil, exportSession.error)
case .cancelled:
print("Export canceled")
completionHandler?(nil, nil)
case .completed:
//Video conversion finished
let endDate = Date()
let time = endDate.timeIntervalSince(startDate)
print(time)
print("Successful!")
print(exportSession.outputURL ?? "NO OUTPUT URL")
completionHandler?(exportSession.outputURL, nil)
default: break
}
group.leave()
})
group.wait(timeout: .now() + 10.0) // blocks current queue
}
private func sendVideo(filename: String, recordID: String){
//Obtain temporary URL and credentials
let url = URL(string: "https://<OMITTED>")!
//JSON Payload for server
let data = [
"recordid" : recordID,
] as [String : Any]
let group = DispatchGroup()
group.enter()
Helper.sendVideoURLRequestToRestAPI(url: url, data: data)
{ response, json, error in
// will be called at either completion or at an error.
guard let statusCode = response?.statusCode else{
print ("[sendVideo] no status code returned - request failed")
return
}
print ("Status Code from server: \(statusCode)")
if statusCode != 200 {
DispatchQueue.main.async {
let alert2 = UIAlertController(title: "Error", message: "Error submitting data - please try again! - \(json["body"] as? String)", preferredStyle: .alert)
alert2.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert2, animated: true)
}
} else { //success
let videoURL = json["url"] as! String
let parameters = [
[
"key": "key",
"value": (json["fields"] as! [String : Any])["key"]!,
"type": "text"
],
[
"key": "AWSAccessKeyId",
"value": (json["fields"] as! [String : Any])["AWSAccessKeyId"]!,
"type": "text"
],
[
"key": "signature",
"value": (json["fields"] as! [String : Any])["signature"]!,
"type": "text"
],
[
"key": "policy",
"value": (json["fields"] as! [String : Any])["policy"]!,
"type": "text"
],
[
"key": "x-amz-security-token",
"value": (json["fields"] as! [String : Any])["x-amz-security-token"]!,
"type": "text"
],
[
"key": "file",
"src": filename,
"type": "file",
"contentType": "video/mp4"
]
] as [[String : Any]]
let boundary = "Boundary-\(UUID().uuidString)"
var body = ""
var error: Error? = nil
for param in parameters {
if param["disabled"] == nil {
let paramName = param["key"]!
body += "--\(boundary)\r\n"
body += "Content-Disposition:form-data; name=\"\(paramName)\""
if param["contentType"] != nil {
body += "\r\nContent-Type: \(param["contentType"] as! String)"
}
let paramType = param["type"] as! String
if paramType == "text" {
let paramValue = param["value"] as! String
body += "\r\n\r\n\(paramValue)\r\n"
} else {
let paramSrc = param["src"] as! String
print("paramSrc: \(paramSrc)")
let videoPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("\(paramSrc).mov")
self.encodeVideo(at: videoPath, completionHandler: { url, error in
guard error == nil else {
print ("error video mp4 conversion")
print(String(describing: error))
return
}
})
let videoPathMP4 = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("rendered-Video.mp4")
do {
let fileData = try NSData(contentsOfFile:videoPathMP4.path, options:[]) as Data
let fileContent = String(data: fileData, encoding: .utf8)! //Exception thrown here - IF I change this to UTF16 it doesn't throw an exception but the file arrives corrupted on server side.
body += "; filename=\"\(paramName)\"\r\n"
+ "Content-Type: \"content-type header\"\r\n\r\n\(fileContent)\r\n" //paramName = paramSrc
// print ("Body: \(body)")
} catch {
print ("Could not open video file: \(videoPathMP4.path)")
return
}
}
}
}
body += "--\(boundary)--\r\n";
let postData = body.data(using: .utf8) //UTF8
var request = URLRequest(url: URL(string: videoURL)!,timeoutInterval: Double.infinity)
request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postData
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
print ("error")
print(String(describing: error))
return
}
guard let data = data else {
print ("Error unpacking data")
print(String(describing: error))
//semaphore.signal()
return
}
print ("data \(data) - \(response)")
print(String(data: data, encoding: .utf8)!)
})
task.resume()
group.leave()
}
}
group.wait() // blocks current queue
}