Retrieve video data from local file system - ios

Im creating a simple app to upload a recorded video to a web API. After recording the video in app I can successfully play the video within the app, but I'm struggling to access the video to upload it.
The video is at assets-library://asset/asset.MOV?id=3AFCEC9B-17DE-4D75-9B87-0AD50BAB9BFF&ext=MOV, which can be loaded using MPMoviePlayerController(contentURL: url) so I know it exists there.
I've tried the following few methods with no success:
Method 1
let url = NSURL(fileURLWithPath: thisNote.url!)
println("This url = \(thisNote.url)")
let videoData = NSData(contentsOfURL: url!, options: nil, error: &e)
error = The operation couldn’t be completed. No such file or directory
Method 2
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let docsDir = dirPaths[0] as! String
let videoFilePath = docsDir.stringByAppendingPathComponent(Constants.Directories.appFolder)
let filePath = NSBundle.pathForResource(thisNote.fileName, ofType: "video/quicktime", inDirectory: Constants.Directories.appFolder)
filepath = nil
Method 3
var video:NSData = NSData()
ALAssetsLibrary().assetForURL(url, resultBlock: { (asset : ALAsset!) -> Void in
if let rep : ALAssetRepresentation = asset.defaultRepresentation(){
var error: NSError?
let length = Int(rep.size())
let from = Int64(0)
let data = NSMutableData(length: length)!
let numRead = rep.getBytes(UnsafeMutablePointer(data.mutableBytes), fromOffset: from, length: length, error: &error)
video = data
self.uploadNote(video, note: thisNote)
}
}){ (error : NSError!) -> Void in
println("Asset library error: \(error)")
}
The result block is not reached, nor is the error printed.
Any help on this would be appreciated.

Method three was the closest to the final solution. I ended up enumerating through the asset library to find the right video file. Whilst I'm sure this is not the most efficient way to get the video, it works. Here is the code I used, I hope it helps someone......
let url = NSURL(string: thisNote.url!)
var video:NSData = NSData()
AssetLibraryHelper.sharedInstance.assetForURL(url, resultBlock: { (foundAsset : ALAsset!) -> Void in
var asset:ALAsset?
if foundAsset == nil {
// Workaround: Enumerate over the asset library to find matching URL
AssetLibraryHelper.sharedInstance.enumerateGroupsWithTypes(
ALAssetsGroupType(ALAssetsGroupPhotoStream),
usingBlock: { (group: ALAssetsGroup?, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
if group != nil {
group!.enumerateAssetsWithOptions(NSEnumerationOptions.Reverse, usingBlock: {(enumeratedAsset: ALAsset!, index: Int, stopGroup: UnsafeMutablePointer<ObjCBool>) -> Void in
if enumeratedAsset != nil {
asset = enumeratedAsset
stop.initialize(true)
stopGroup.initialize(true)
}
})
}
}, failureBlock: { (error: NSError!) -> Void in
print("Error enumerating assets \(error)")
})
}
else {
asset = foundAsset
}
print("Asset is \(asset)")
if let rep : ALAssetRepresentation = asset!.defaultRepresentation(){
let image = UIImage(CGImage: rep.fullResolutionImage().takeUnretainedValue())
let imageData = UIImageJPEGRepresentation(image, 0.7)
var error: NSError?
let length = Int(rep.size())
let from = Int64(0)
let data = NSMutableData(length: length)!
let _ = rep.getBytes(UnsafeMutablePointer(data.mutableBytes), fromOffset: from, length: length, error: &error)
video = data
self.uploadNote(video, note: thisNote, imageFile: imageData!)
}
}){ (error : NSError!) -> Void in
print("Asset library error: \(error)")
}

Related

AWS S3 iOS SDK: How to resume upload after connection is interrupted?

This is my code to accomplish the upload task:
let image = UIImage(named: "12.jpeg")
let fileManager = FileManager.default
let imageData = UIImageJPEGRepresentation(image!, 0.99)
let path = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("\(imageData!).jpeg")
fileManager.createFile(atPath: path as String, contents: imageData, attributes: nil)
let fileUrl = NSURL(fileURLWithPath: path)
uploadRequest?.bucket = "testrawdata"
uploadRequest?.key = "test/loodfd.jpeg"
uploadRequest?.contentType = "image/jpeg"
uploadRequest?.body = fileUrl as URL!
uploadRequest?.serverSideEncryption = AWSS3ServerSideEncryption.awsKms
uploadRequest?.uploadProgress = { (bytesSent, totalBytesSent, totalBytesExpectedToSend) -> Void in
DispatchQueue.main.async(execute: {
print("bytes sent \(bytesSent), total bytes sent \(totalBytesSent), of total \(totalBytesExpectedToSend)")
})
}
transferManager?.upload(uploadRequest).continue(with: AWSExecutor.mainThread(), withSuccessBlock: { (taskk: AWSTask) -> Any? in
if taskk.error != nil {
// Error.
} else {
// Do something with your result.
}
return nil
})
}
I know I don't need to apply it to image, but this is just an example, by default I'm going to send files like 100mb.
When I put my phone into airplane mode during the transfer then turn the network on again, it does not finish the upload task.
Docs are not saying explicitly what should I do to resume interrupted task.
Here is what I tried:
I put initialization of request and manager into viewDidLoad() to assure I'm not creating another request
class ViewController: UIViewController {
var uploadRequest:AWSS3TransferManagerUploadRequest!
var transferManager: AWSS3TransferManager!
override func viewDidLoad() {
super.viewDidLoad()
uploadRequest = AWSS3TransferManagerUploadRequest()
transferManager = AWSS3TransferManager.default()
}
and tried to call
func resumeTransfer() {
transferManager?.resumeAll(nil)
}
But it does not work.
Thanks in advance
It turns out that Transfer Utility is the right tool to accomplish this task
func uploadData(data: NSData) {
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = progressBlock
let transferUtility = AWSS3TransferUtility.default()
transferUtility.uploadData(
data as Data,
bucket: "test",
key: "test/test.jpeg",
contentType: "image/jpeg",
expression: expression,
completionHander: completionHandler).continue(successBlock: { (task) -> AnyObject! in
if let error = task.error {
NSLog("Error: %#",error.localizedDescription);
}
if let exception = task.exception {
NSLog("Exception: %#",exception.description);
}
if let _ = task.result {
NSLog("Upload Starting!")
// Do something with uploadTask.
}
return nil;
})
}
This way all upload stuff happens in the background, I don't have to worry about app being killed by the iOS, about networks problem etc.
One can even specify
configuration?.allowsCellularAccess = false
in AWSServiceConfiguration
to resume the task only when wifi is available.

Swift upload to s3 bucket does not end

I'm trying to upload an image to a bucket. The connection is made, the upload apparently starts but does not progress. The permissions on the server I consider to be correct, because an android app is able to upload.
In my appdelegate I have this:
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast1, identityPoolId: "us-east-1:XXXXXX-XXXX-XXXX-XXXX-XXXX”, unauthRoleArn: "arn:aws:iam::XXXXX:role/Cognito_mybucketUnauth_Role", authRoleArn: "arn:aws:iam::XXXXX:role/Cognito_mybucketAuth_Role", identityProviderManager: nil)
let configuration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
And this to get the image and upload
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]){
//getting details of image
let uploadFileURL = info[UIImagePickerControllerReferenceURL] as! NSURL
let imageName = uploadFileURL.lastPathComponent
let documentDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first! as String
// getting local path
let localPath = (documentDirectory as NSString).stringByAppendingPathComponent(imageName!)
//getting actual image
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
let data = UIImagePNGRepresentation(image)
data!.writeToFile(localPath, atomically: true)
let imageData = NSData(contentsOfFile: localPath)!
imageURL = NSURL(fileURLWithPath: localPath)
CampoImagem.image = image
picker.dismissViewControllerAnimated(true, completion: nil)
uploadImage()
}
func uploadImage(){
//defining bucket and upload file name
let S3BucketName: String = “mybucket"
let S3UploadKeyName: String = "profile/testImage.jpg"
let expression = AWSS3TransferUtilityUploadExpression()
/*expression.uploadProgress = {(task: AWSS3TransferUtilityTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) in
dispatch_async(dispatch_get_main_queue(), {
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
print("Progress is: \(progress)")
})
}*/
self.uploadCompletionHandler = { (task, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {
if ((error) != nil){
print("Failed with error")
print("Error: \(error!)");
}
else{
print("Sucess")
}
})
}
let transferUtility = AWSS3TransferUtility.defaultS3TransferUtility()
transferUtility.uploadFile(imageURL, bucket: S3BucketName, key: S3UploadKeyName, contentType: "image/jpeg", expression: expression, completionHander: uploadCompletionHandler).continueWithBlock { (task) -> AnyObject! in
if let error = task.error {
print("Error: \(error.localizedDescription)")
}
if let exception = task.exception {
print("Exception: \(exception.description)")
}
if let _ = task.result {
print("Upload Starting!")
}
return nil;
}
}
Print: Upload Starting!
I suspect it's something when ID and permission to complete the upload with aws, but I think if it was the upload would not start, correct?
How can I solve this?
Please check this code you can also check by adding credentials in this demo project
https://github.com/awslabs/aws-sdk-ios-samples/blob/master/S3TransferUtility-Sample/Swift/S3BackgroundTransferSampleSwift/FirstViewController.swift

Save metadata for custom image with Swift (iOS) and Photos framework

I am trying to add metadata to my synthetically generated and save it to camera roll by using the Photos framework. I got the saving and editing working but I just can seem to figure out how to add metadata. I have tried many approaches like adding the metadata by creating a CoreGraphics image (see code below). All these approaches do not give me an error but I just cannot see the metadata when I open the image on my Mac.
Can anyone point me in the right direction here?
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let assets : PHFetchResult = PHAsset.fetchAssetsWithLocalIdentifiers([self.localIdentifier], options: nil);
let asset : PHAsset = assets[0] as! PHAsset;
let changeRequest = PHAssetChangeRequest(forAsset: asset);
changeRequest.location = self.currentLocation;
asset.requestContentEditingInputWithOptions(nil, completionHandler: { (input: PHContentEditingInput?,
info: [NSObject : AnyObject]) -> Void in
guard let input = input else { return }
let imageManager : PHImageManager = PHImageManager();
let requestoptions : PHImageRequestOptions = PHImageRequestOptions();
requestoptions.resizeMode = PHImageRequestOptionsResizeMode.None;
imageManager.requestImageForAsset(asset, targetSize: PHImageManagerMaximumSize, contentMode: PHImageContentMode.Default, options: PHImageRequestOptions(), resultHandler: { (let image : UIImage?, _) -> Void in
let output : PHContentEditingOutput = PHContentEditingOutput(contentEditingInput: input);
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let changeRequest = PHAssetChangeRequest(forAsset: asset);
/* Neu */
let imageData : NSData = NSData(contentsOfURL: (input.fullSizeImageURL)!)!;
let image : CIImage = CIImage(data: imageData)!;
let dataPtr = CFDataCreate(kCFAllocatorDefault, UnsafePointer<UInt8>(imageData.bytes), imageData.length)
// Save off the properties
let imageSource : CGImageSourceRef = CGImageSourceCreateWithData(dataPtr, nil)!;
var metadata : NSMutableDictionary = NSMutableDictionary(dictionary: CGImageSourceCopyProperties(imageSource, nil)!);
/* Add some values to metadata */
....
NSLog("New metadata: %#", metadata);
// Save the image
let outputImageSource : CGImageSourceRef = CGImageSourceCreateWithData(dataPtr, nil)!;
let jpegData : CFMutableDataRef = CFDataCreateMutable(kCFAllocatorDefault, 0);
let outputDestination : CGImageDestinationRef = CGImageDestinationCreateWithData(jpegData, CGImageSourceGetType(outputImageSource)!, 1, nil)!;
// add the image data to the destination
CGImageDestinationAddImageFromSource(outputDestination, outputImageSource, 0, metadata);
if CGImageDestinationFinalize(outputDestination)
{
NSLog("Successful image creation.");
// process the image rendering, adjustment data creation and finalize the asset edit.
}
else
{
NSLog("Image creation failed.");
}
(jpegData as NSData).writeToURL(output.renderedContentURL, atomically: true);
let options : String = NSString(format: "%f|%f|%f|%f|%f|%f", self.saturationSlider.value, self.warmthSlider.value, self.brightnessSlider.value, self.sharpnessSlider.value, self.contrastSlider.value, self.gammaSlider.value ) as String;
let nsObject: AnyObject? = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"];
output.adjustmentData = PHAdjustmentData(formatIdentifier: NSBundle.mainBundle().bundleIdentifier!,
formatVersion: nsObject as! String,
data: options.dataUsingEncoding(NSUTF8StringEncoding)!);
changeRequest.contentEditingOutput = output;
}, completionHandler: { (_bool, _error) -> Void in
if !_bool && error != nil
{
NSLog("%#", error!);
}
});
});
});
}, completionHandler: { (_bool, _error) -> Void in
});
You can add/set a few properties on the creationRequest object. I don't know about adding custom metadata.
PHPhotoLibrary.shared().performChanges({
let creationRequest = PHAssetChangeRequest.creationRequestForAsset(from: self.anImage!)
let aLocation = CLLocation(latitude: 27.63866, longitude: -80.39707)
creationRequest.location = aLocation
creationRequest.isFavorite = true
creationRequest.creationDate = Date()
}, completionHandler: {success, error in
if !success {
print("error creating asset: \(error!)")
} else {
print("success creating asset!")
}
})

Play video downloaded through CloudKit as CKAsset - iOS

I'm making an app that records video, uploads it to iCloud using CloudKit with a CKAsset, then downloads the file and plays it in an AVPlayer. This is all written in Swift 2.0
I have gotten the data downloaded, and I think I've been able to reference it but I'm not sure. Data/garbage does print when I convert the URL into an NSData object and print it to the console. The video files gets downloaded as a binary file however. I was able to go to the CloudKit dashboard and download the file and append '.mov' to it, and it opened in Quicktime no problem.
So I think my main issue is that I can't work out how to get the video file to actually play, since the file has no extension. I have tried appending '.mov' to the end with URLByAppendingPathExtension() to no avail. Let me know of any ideas!
Upload Video
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let tempURL = info[UIImagePickerControllerMediaURL] as! NSURL
dismissViewControllerAnimated(true) { () -> Void in
self.uploadVideoToiCloud(tempURL)
print("\n Before Upload: \(tempURL)\n")
}
}
func uploadVideoToiCloud(url: NSURL) {
let videoRecord = CKRecord(recordType: "video", recordID: id)
videoRecord["title"] = "This is the title"
let videoAsset = CKAsset(fileURL: url)
videoRecord["video"] = videoAsset
CKContainer.defaultContainer().privateCloudDatabase.saveRecord(videoRecord) { (record, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if error == nil {
print("upload successful")
} else {
print(error!)
}
})
}
}
Download Video
func downloadVideo(id: CKRecordID) {
privateDatabase.fetchRecordWithID(id) { (results, error) -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
if error != nil {
print(" Error Fetching Record " + error!.localizedDescription)
} else {
if results != nil {
print("pulled record")
let record = results!
let videoFile = record.objectForKey("video") as! CKAsset
self.videoURL = videoFile.fileURL
print(" After Download: \(self.videoURL!)")
self.videoAsset = AVAsset(URL: self.videoURL!)
self.playVideo()
} else {
print("results Empty")
}
}
}
}
}
The root problem is that AVPlayer expects a file extension, for example .mov, but CKAsset's fileURL property points to a file that lacks an extension. The cleanest solution is to create a hard link, which avoids shuffling megabytes of data around and requires no disk space:
- (NSURL *)videoURL {
return [self createHardLinkToVideoFile];
}
- (NSURL *)createHardLinkToVideoFile {
NSError *err;
if (![self.hardURL checkResourceIsReachableAndReturnError:nil]) {
if (![[NSFileManager defaultManager] linkItemAtURL:self.asset.fileURL toURL:self.hardURL error:&err]) {
// if creating hard link failed it is still possible to create a copy of self.asset.fileURL and return the URL of the copy
}
}
return self.hardURL;
}
- (void)removeHardLinkToVideoFile {
NSError *err;
if ([self.hardURL checkResourceIsReachableAndReturnError:nil]) {
if (![[NSFileManager defaultManager] removeItemAtURL:self.hardURL error:&err]) {
}
}
}
- (NSURL *)hardURL {
return [self.asset.fileURL URLByAppendingPathExtension:#"mov"];
}
Then in the view controller, point AVPlayer to videoURL instead of asset.fileURL.
Solution ended up being that I forgot to specify the filename before I wrote the data to it. I was using URLByAppendingPathExtension and it messed up the URL, ended up using URLByAppendingPathComponent and adding a filename there. Here's the solution that worked for me! Thanks for the comments guys.
func downloadVideo(id: CKRecordID) {
privateDatabase.fetchRecordWithID(id) { (results, error) -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
if error != nil {
print(" Error Fetching Record " + error!.localizedDescription)
} else {
if results != nil {
print("pulled record")
let record = results as CKRecord!
let videoFile = record.objectForKey("video") as! CKAsset
self.videoURL = videoFile.fileURL as NSURL!
let videoData = NSData(contentsOfURL: self.videoURL!)
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let destinationPath = NSURL(fileURLWithPath: documentsPath).URLByAppendingPathComponent("filename.mov", isDirectory: false) //This is where I messed up.
NSFileManager.defaultManager().createFileAtPath(destinationPath.path!, contents:videoData, attributes:nil)
self.videoURL = destinationPath
self.videoAsset = AVURLAsset(URL: self.videoURL!)
self.playVideo()
} else {
print("results Empty")
}
}
}
}
}
Here's the solution for multiple video download from CloudKit. Using this you can store the video on multiple destination and get easily file path
import AVKit
import CloudKit
var assetForVideo = [CKAsset]()
var videoURLForGetVideo = NSURL()
database.perform(queryForVideo, inZoneWith: nil) { [weak self] record, Error in
guard let records = record, Error == nil else {
return
}
DispatchQueue.main.async { [self] in
self?.assetForVideo = records.compactMap({ $0.value(forKey: "video") as? CKAsset })
for (i,dt) in self!.assetForVideo.enumerated(){
self!.videoURLForGetVideo = (dt.fileURL as NSURL?)!
let videoData = NSData(contentsOf: self!.videoURLForGetVideo as URL)
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let destinationPath = NSURL(fileURLWithPath: documentsPath).appendingPathComponent(self!.assetForVideo.count == i ? "filename\(self!.assetForVideo.count).mov" : "filename\(i+1).mov", isDirectory: false)! as NSURL
FileManager.default.createFile(atPath: destinationPath.path!, contents: videoData as Data?, attributes: nil)
self?.videoURLForGetVideo = destinationPath
self!.videoAssett = AVURLAsset(url: self!.videoURLForGetVideo as URL)
let abc = self!.videoAssett.url
let videoURL = URL(string: "\(abc)")
}
}
}

How can I play an AAC file which I obtain from a parse server?

Here is a code snippet from my program. I get the data from the PFFile, get the NSURL, and then finally the NSDATA which I pass to my audio player. Nothing plays though and I don't know why.
var audioFile: PFFile = sound!["file"] as! PFFile
audioFile.getDataInBackgroundWithBlock { (imageData: NSData?, error: NSError?) -> Void in
if (error == nil) {
let audioURLString = audioFile.url!
let audioURL = NSURL(string: audioURLString)!
println(audioURL) //<-- this prints http://files.parsetfss.com/1d4f1088-a07c-43c7-ada2-7f246e6a8167/tfss-db262c85-f6ba-4a5e-a065-9f9e345a929a-sound.aac
let audioData = NSData(contentsOfURL: audioURL)!
println(audioData) // <--this prints 0000001c 66747970 4d344120 00000000....
let player = AVAudioPlayer(data: audioData, error: nil)
println(player) // <-- this prints <AVAudioPlayer: 0x174200200>
player.play()
}
}

Resources