How to Get Video from a Live Photo in iOS - ios

I'm trying to figure it out, but can't find any useful information.
I only found this:
PHAssetResourceManager.defaultManager().writeDataForAssetResource(assetRes,
toFile: fileURL, options: nil, completionHandler:
{
// Video file has been written to path specified via fileURL
}
but I'm ashamed to say I have no idea how to play it out.
I've created a UIImagePickerController and loaded an Image from the Camera Roll.

Use this code to get the video from live photo:
- (void)videoUrlForLivePhotoAsset:(PHAsset*)asset withCompletionBlock:(void (^)(NSURL* url))completionBlock{
if([asset isKindOfClass:[PHAsset class]]){
NSString* identifier = [(PHAsset*)asset localIdentifier];
NSString* filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.mov",[NSString stringWithFormat:#"%.0f",[[NSDate date] timeIntervalSince1970]]]];
NSURL *fileUrl = [NSURL fileURLWithPath:filePath];
PHLivePhotoRequestOptions* options = [PHLivePhotoRequestOptions new];
options.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;
options.networkAccessAllowed = YES;
[[PHImageManager defaultManager] requestLivePhotoForAsset:asset targetSize:[UIScreen mainScreen].bounds.size contentMode:PHImageContentModeDefault options:options resultHandler:^(PHLivePhoto * _Nullable livePhoto, NSDictionary * _Nullable info) {
if(livePhoto){
NSArray* assetResources = [PHAssetResource assetResourcesForLivePhoto:livePhoto];
PHAssetResource* videoResource = nil;
for(PHAssetResource* resource in assetResources){
if (resource.type == PHAssetResourceTypePairedVideo) {
videoResource = resource;
break;
}
}
if(videoResource){
[[PHAssetResourceManager defaultManager] writeDataForAssetResource:videoResource toFile:fileUrl options:nil completionHandler:^(NSError * _Nullable error) {
if(!error){
completionBlock(fileUrl);
}else{
completionBlock(nil);
}
}];
}else{
completionBlock(nil);
}
}else{
completionBlock(nil);
}
}];
}else{
completionBlock(nil);
}
}
Basically what you have to do is that you first need to fetch the PHLivePhoto object from your PHAsset. After that, you will have to traverse all the asset resources within your live photo and check if it is of type PHAssetResourceTypePairedVideo.
If yes, you got your video. Now you will require to save it to some temporary directory as I did here and use this file for whatever purpose you may have.
To Play this video, you can use the following code:
NSURL *videoURL = [NSURL fileURLWithPath:fileUrl];
AVPlayer *player = [AVPlayer playerWithURL:videoURL];
AVPlayerViewController *playerViewController = [AVPlayerViewController new];
playerViewController.player = player;
[self presentViewController:playerViewController animated:YES completion:nil];
Feel free to ask if you need any clarification.
P.S.- I made a few changes in this method to remove dependency of my application's code so the above code is untested, however I feel it should work as expected.

Swift 4 version
import Photos
import MobileCoreServices
// <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
#IBAction func showImagePicker(sender: UIButton) {
let picker = UIImagePickerController()
picker.delegate = self;
picker.allowsEditing = false;
picker.sourceType = .photoLibrary;
picker.mediaTypes = [kUTTypeLivePhoto as String, kUTTypeImage as String];
present(picker, animated: true, completion: nil);
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
guard
let livePhoto = info[UIImagePickerControllerLivePhoto] as? PHLivePhoto,
let photoDir = generateFolderForLivePhotoResources()
else {
return;
}
let assetResources = PHAssetResource.assetResources(for: livePhoto)
for resource in assetResources {
// SAVE FROM BUFFER
// let buffer = NSMutableData()
// PHAssetResourceManager.default().requestData(for: resource, options: nil, dataReceivedHandler: { (chunk) in
// buffer.append(chunk)
// }, completionHandler: {[weak self] error in
// self?.saveAssetResource(resource: resource, inDirectory: photoDir, buffer: buffer, maybeError: error)
// })
// SAVE DIRECTLY
saveAssetResource(resource: resource, inDirectory: photoDir, buffer: nil, maybeError: nil)
}
picker.dismiss(animated: true) {}
}
func saveAssetResource(
resource: PHAssetResource,
inDirectory: NSURL,
buffer: NSMutableData?, maybeError: Error?
) -> Void {
guard maybeError == nil else {
print("Could not request data for resource: \(resource), error: \(String(describing: maybeError))")
return
}
let maybeExt = UTTypeCopyPreferredTagWithClass(
resource.uniformTypeIdentifier as CFString,
kUTTagClassFilenameExtension
)?.takeRetainedValue()
guard let ext = maybeExt else {
return
}
guard var fileUrl = inDirectory.appendingPathComponent(NSUUID().uuidString) else {
print("file url error")
return
}
fileUrl = fileUrl.appendingPathExtension(ext as String)
if let buffer = buffer, buffer.write(to: fileUrl, atomically: true) {
print("Saved resource form buffer \(resource) to filepath \(String(describing: fileUrl))")
} else {
PHAssetResourceManager.default().writeData(for: resource, toFile: fileUrl, options: nil) { (error) in
print("Saved resource directly \(resource) to filepath \(String(describing: fileUrl))")
}
}
}
func generateFolderForLivePhotoResources() -> NSURL? {
let photoDir = NSURL(
// NB: Files in NSTemporaryDirectory() are automatically cleaned up by the OS
fileURLWithPath: NSTemporaryDirectory(),
isDirectory: true
).appendingPathComponent(NSUUID().uuidString)
let fileManager = FileManager()
// we need to specify type as ()? as otherwise the compiler generates a warning
let success : ()? = try? fileManager.createDirectory(
at: photoDir!,
withIntermediateDirectories: true,
attributes: nil
)
return success != nil ? photoDir! as NSURL : nil
}
In depth tutorial here Live Photo API on iOS

The question is a little confusing
First, If you want to pick live photo and play live photo.I recommend you use the Photos Framework instead of UIImagePickerController. This way you can fetch the asset and have more control. Then you can play the live photo as mov or the muted version with PHLivePhotoView by setting the startPlayback(with:) to hint or full.
You can refer the code here :
a github repo LivePreview show you how to select live photo and play it.
Second, if you want convert live photo to mov, the code you pasted will work, and if you want to play mov directly, you may want use AVPlayer
Plus, WWDC provides Example app using Photos framework

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let phAsset = info[.phAsset] as? PHAsset
imagePickerController.dismiss(animated: true, completion: nil)
let style = phAsset?.playbackStyle
if(style != .livePhoto) {
print("This is not a live photo")
return
}
let filePath = NSTemporaryDirectory() + String(format: "%.0f", NSDate().timeIntervalSince1970) + "_.mov"
let fileURL = NSURL(fileURLWithPath: filePath)
let options = PHLivePhotoRequestOptions()
options.deliveryMode = .fastFormat
options.isNetworkAccessAllowed = true
PHImageManager.default().requestLivePhoto(for: phAsset!, targetSize: CGSize(width: 1920, height: 1080), contentMode: PHImageContentMode.default, options: options) { livePhoto, info in
if((livePhoto) != nil) {
let assetResources = PHAssetResource.assetResources(for: livePhoto!)
var videoResource : PHAssetResource?
for resources in assetResources {
if(resources.type == .pairedVideo) {
videoResource = resources
break
}
}
guard let videoResource = videoResource else {
fatalError("video resource is nil")
}
PHAssetResourceManager.default().writeData(for: videoResource, toFile: fileURL as URL, options: nil) { error in
let avAsset : AVAsset = AVAsset(url: fileURL as URL)
DispatchQueue.main.async { [self] in
// Whatever you do using fileURL or avAsset.
}
}
}
}
}

Swift 5
func videoUrlForLivePhotoAsset(asset: PHAsset, completionHandler: #escaping (_ result: URL?) -> Void) {
print("videoUrlForLivePhotoAsset: \(asset)")
let options : PHLivePhotoRequestOptions = PHLivePhotoRequestOptions.init()
options.deliveryMode = .fastFormat
options.isNetworkAccessAllowed = true
PHImageManager.default().requestLivePhoto(for: asset, targetSize: UIScreen.main.bounds.size, contentMode: .default, options: options) { (livePhoto, info) in
if livePhoto != nil {
let assetResources : [PHAssetResource] = PHAssetResource.assetResources(for: livePhoto!)
var videoResource : PHAssetResource?
for resource in assetResources {
if resource.type == .pairedVideo {
videoResource = resource
break
}
}
guard let photoDir = self.generateFolderForLivePhotoResources() else {
return
}
print("videoResource: \(videoResource)")
if videoResource != nil {
self.saveAssetResource(resource: videoResource!, inDirectory: photoDir, buffer: nil, maybeError: nil) { (fileUrl) in
completionHandler(fileUrl)
}
}
} else {
completionHandler(nil)
}
}
}
func saveAssetResource(
resource: PHAssetResource,
inDirectory: NSURL,
buffer: NSMutableData?, maybeError: Error?, completionHandler: #escaping (_ result: URL?) -> Void) {
guard maybeError == nil else {
print("Could not request data for resource: \(resource), error: \(String(describing: maybeError))")
return
}
let maybeExt = UTTypeCopyPreferredTagWithClass(
resource.uniformTypeIdentifier as CFString,
kUTTagClassFilenameExtension
)?.takeRetainedValue()
guard let ext = maybeExt else {
return
}
guard var fileUrl = inDirectory.appendingPathComponent(NSUUID().uuidString) else {
print("file url error")
return
}
fileUrl = fileUrl.appendingPathExtension(ext as String)
if let buffer = buffer, buffer.write(to: fileUrl, atomically: true) {
print("Saved resource form buffer \(resource) to filepath \(String(describing: fileUrl))")
completionHandler(fileUrl)
} else {
PHAssetResourceManager.default().writeData(for: resource, toFile: fileUrl, options: nil) { (error) in
print("Saved resource directly \(resource) to filepath \(String(describing: fileUrl))")
if error == nil {
completionHandler(fileUrl)
} else {
completionHandler(nil)
}
}
}
}
func generateFolderForLivePhotoResources() -> NSURL? {
let photoDir = NSURL(
// NB: Files in NSTemporaryDirectory() are automatically cleaned up by the OS
fileURLWithPath: NSTemporaryDirectory(),
isDirectory: true
).appendingPathComponent(NSUUID().uuidString)
let fileManager = FileManager()
// we need to specify type as ()? as otherwise the compiler generates a warning
let success : ()? = try? fileManager.createDirectory(
at: photoDir!,
withIntermediateDirectories: true,
attributes: nil
)
return success != nil ? photoDir! as NSURL : nil
}
Invoke with the following:
let asset = PHAsset.init()
self.videoUrlForLivePhotoAsset(asset: asset!) { (url) in
print("url: \(url)")
}
Note: You need to clean up the Temp and Documents directory, and remove the files.

Related

iOS App is crashing when selecting multiple videos from photo library - memory warning

My app is crashing when I select multiple videos(1+ min each) from photo library because of memory warning. I'm trying to compress videos but still the same issue although compression code is working properly. I want my app to select 5 videos at once and send them in chat. Whatsapp is allowing the user to select 30 videos and send them in chat but my app is crashing because of memory issues after 3 videos only.
I am using "AssetsPickerViewController" lib for multiple pic/video selection.
func assetsPicker(controller: AssetsPickerViewController, selected assets: [PHAsset]) {
self.dismiss(animated: true, completion: nil)
var isImages = false
var mediaData: [Data] = []
let imageManager = PHCachingImageManager.default()
DispatchQueue.main.async {
self.appDelegate.helper.showHUD(withMessage: "Preparing media", withObject: self)
}
autoreleasepool {
for selectedAsset in assets {
if selectedAsset.mediaType == .image {
isImages = true
let option = PHImageRequestOptions()
option.isSynchronous = true
option.isNetworkAccessAllowed = true
imageManager.requestImageData(for: selectedAsset, options: option) { (assetData, assetDataUTI, assetOrientation, assetInfo) in
if let data = assetData {
if let image = UIImage(data: data)?.upOrientation(), let finalData = image.jpegData(compressionQuality: 0.5) {
if let newData = ImageHelper.removeExifData(data: finalData as NSData) {
mediaData.append(newData as Data)
self.checkFileStatus(mediaCount: mediaData.count, assetsCount: assets.count, data: mediaData, isImages: isImages)
} else {
mediaData.append(finalData)
self.checkFileStatus(mediaCount: mediaData.count, assetsCount: assets.count, data: mediaData, isImages: isImages)
}
} else {
mediaData.append(data)
self.checkFileStatus(mediaCount: mediaData.count, assetsCount: assets.count, data: mediaData, isImages: isImages)
}
}
}
} else if selectedAsset.mediaType == .video {
let options: PHVideoRequestOptions = PHVideoRequestOptions()
options.isNetworkAccessAllowed = true
options.deliveryMode = .fastFormat
self.convertVideo(phAsset: selectedAsset) { (data) in
if let finalData = data {
mediaData.append(finalData)
self.checkFileStatus(mediaCount: mediaData.count, assetsCount: assets.count, data: mediaData, isImages: isImages)
} else {
}
}
}
}
}
}
func compressVideo(inputURL: URL, outputURL: URL, handler:#escaping (_ exportSession: AVAssetExportSession?)-> Void) {
let urlAsset = AVURLAsset(url: inputURL, options: nil)
guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else {
handler(nil)
return
}
exportSession.outputURL = outputURL
exportSession.outputFileType = AVFileType.mp4
exportSession.shouldOptimizeForNetworkUse = true
exportSession.exportAsynchronously { () -> Void in
handler(exportSession)
}
}

Export files from Music Library to Documents directory (Swift 5)

In my app I want to export files from Music Library and save them to Documents directory. I already fetch file list from Music Library and I get url of each file. But I still have two problems:
I can export files only in "m4a" format, but I'd like to export it in format what file was in Music Library (mp3, wav, aac or else)
How can I save files from Music Library to Documents directory with url?
My code now looks like this:
private func exportFile(with assetURL: URL, completionHandler: #escaping (_ fileURL: URL?, _ error: Error?) -> ()) {
let asset = AVURLAsset(url: assetURL)
guard let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else {
completionHandler(nil, nil)
return
}
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(NSUUID().uuidString)
.appendingPathExtension("m4a")
exporter.outputURL = fileURL
exporter.outputFileType = AVFileType.m4a
exporter.exportAsynchronously {
if exporter.status == .completed {
completionHandler(fileURL, nil)
} else {
completionHandler(nil, exporter.error)
}
}
func downloadFile(file: MPMediaItem?, callback: #escaping () -> ()) {
guard let file = file,
let url = file.url
else { return }
exportFile(with: url) { fileURL, error in
guard let fileURL = fileURL, error == nil else {
print(error?.localizedDescription)
return
}
//Here I'd like to save file to Documents dir
print("\(fileURL)")
}
callback()
}
Solution for second question.
I put the code in one method and save directly to the Documents folder, not to a temporary folder:
func downloadFile(file: MPMediaItem?, callback: #escaping () -> ()) {
guard let file = file,
let url = file.assetURL
else { return }
let exportSession = AVAssetExportSession(asset: AVAsset(url: url), presetName: AVAssetExportPresetAppleM4A)
exportSession?.shouldOptimizeForNetworkUse = true
exportSession?.outputFileType = AVFileType.m4a
let documentURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let outputURL = documentURL.appendingPathComponent("\(file.title!).m4a")
// //Delete Existing file if needs
// do {
// try FileManager.default.removeItem(at: outputURL)
// } catch let error as NSError {
// print(error.debugDescription)
// }
exportSession?.outputURL = outputURL
exportSession?.exportAsynchronously(completionHandler: { () -> () in
if exportSession!.status == AVAssetExportSession.Status.completed {
print("Export Successful")
DispatchQueue.main.async {
//Update IU
callback()
}
} else {
print("Export failed")
}
})
}

Upload a video to firebase using Swift

I am trying to pick a video from the camera roll and then uploading that video to firebase storage. So far I am able to pick a video but it is not uploading to firebase, how can I upload it to firebase storage?
func uploadVideoToDB(url: URL){
let storageReference = Storage.storage().reference().child("video.mov")
storageReference.putFile(from: url)
}
func fetchVideos(section: Int){
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate = self
imagePickerController.mediaTypes = ["public.movie"]
present(imagePickerController, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]){
let url = info[UIImagePickerController.InfoKey(rawValue: "UIImagePickerControllerReferenceURL")] as? NSURL
DataService.instance.uploadVideoToDB(url: url! as URL)
imagePickerController.dismiss(animated: true, completion: nil)
}
Call this function to upload the video to firebase storage
func uploadTOFireBaseVideo(url: URL,
success : #escaping (String) -> Void,
failure : #escaping (Error) -> Void) {
let name = "\(Int(Date().timeIntervalSince1970)).mp4"
let path = NSTemporaryDirectory() + name
let dispatchgroup = DispatchGroup()
dispatchgroup.enter()
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let outputurl = documentsURL.appendingPathComponent(name)
var ur = outputurl
self.convertVideo(toMPEG4FormatForVideo: url as URL, outputURL: outputurl) { (session) in
ur = session.outputURL!
dispatchgroup.leave()
}
dispatchgroup.wait()
let data = NSData(contentsOf: ur as URL)
do {
try data?.write(to: URL(fileURLWithPath: path), options: .atomic)
} catch {
print(error)
}
let storageRef = Storage.storage().reference().child("Videos").child(name)
if let uploadData = data as Data? {
storageRef.putData(uploadData, metadata: nil
, completion: { (metadata, error) in
if let error = error {
failure(error)
}else{
let strPic:String = (metadata?.downloadURL()?.absoluteString)!
success(strPic)
}
})
}
}
Following function converts the video to mp4 format so that it can be viewed on any device either it be iOS or android
func convertVideo(toMPEG4FormatForVideo inputURL: URL, outputURL: URL, handler: #escaping (AVAssetExportSession) -> Void) {
try! FileManager.default.removeItem(at: outputURL as URL)
let asset = AVURLAsset(url: inputURL as URL, options: nil)
let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)!
exportSession.outputURL = outputURL
exportSession.outputFileType = .mp4
exportSession.exportAsynchronously(completionHandler: {
handler(exportSession)
})
}
This is what did it for me:
needed to convert url into Data then use putData instead of putFile
func uploadVideoToDB(url: URL){
let filename = UUID().uuidString
let ref = Storage.storage().reference().child("videos").child("\(filename).mp4")
do {
let videoData = try Data(contentsOf: url)
ref.putData(videoData)
} catch {
print(error)
}
}

How to upload to S3 bucket taking a photo with the iOS Camera

I am trying to use the AWS S3 bucket to store user photos from when they have taken them from their phones. I right now have my code set up to the point where the user is able to take a photo of something and have that show up on the UIImageView.
The issue I am encountering is that I have no clue how to store it on the S3 bucket, I have code right now that is able to store a specified photo the bucket, but not really code that is able to store a photo that is taken from the camera.
Take Photo code
#IBAction func takePhoto(_ sender: Any) {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerController.SourceType.camera
imagePicker.allowsEditing = false
self.present(imagePicker, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
takenPhoto.contentMode = .scaleToFill
takenPhoto.image = pickedImage
print(takenPhoto.image = pickedImage)
}
picker.dismiss(animated: true, completion: nil)
}
AWS S3 Bucket Code
#IBAction func uploadFile(_ sender: Any) {
uploadFile(with: "eartj", type: ".jpeg")
}
func uploadFile(with resource: String, type: String){
let key = "\(resource),\(type)"
let imagePath = Bundle.main.path(forResource: resource, ofType: type)!
let imageUrl = URL(fileURLWithPath: imagePath)
let request = AWSS3TransferManagerUploadRequest()!
request.bucket = "wuuurktest"
request.key = key
request.body = imageUrl
request.acl = .publicReadWrite
let transferManager = AWSS3TransferManager.default()
transferManager.upload(request).continueWith(executor: AWSExecutor.mainThread()) { (task) -> Any? in
if let error = task.error {
print(error)
}
if task.result != nil {
print("Uploaded File")
}
return nil
}
}
Link to the guide I am using to create the file upload
https://www.youtube.com/watch?v=UMgApUhg7ic
Most of the answers are outdated and too complicated. I was struggling with the same problem and finally found a solution.
This works best for me and works on Swift 5.
First of all, let's update the function to upload images to AWS.
func uploadToS3(url: URL) {
let fileArr = url.path.components(separatedBy: "/") // Path will be too long, so you have to separate the elements by / and store in an array
let key = fileArr.last // We get the last element of the array which in our case will be the image (my-image.jpg)
let localImageUrl = url
let request = AWSS3TransferManagerUploadRequest()!
request.bucket = bucketName
request.key = key
request.body = localImageUrl
request.acl = .publicReadWrite
let transferManager = AWSS3TransferManager.default()
transferManager.upload(request).continueWith(executor: AWSExecutor.mainThread()) { (task) -> Any? in
if let error = task.error {
print(error)
}
if task.result != nil {
print("Uploaded \(key)")
let contentUrl = self.s3Url.appendingPathComponent(bucketName).appendingPathComponent(key!)
self.contentUrl = contentUrl
}
return nil
}
}
In this block of code:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
takenPhoto.contentMode = .scaleToFill
takenPhoto.image = pickedImage
print(takenPhoto.image = pickedImage)
// Add here:
let url = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent("my-image", isDirectory: false)
.appendingPathExtension("jpg") /* here we are naming the image 'my-image' and it will be 'jpg', if you want you can add a counter to increase the number each time you upload an image, and you make something like this: "my-image-\(counter)"*/
// Then write to disk
if let data = pickedImage.jpegData(compressionQuality: 0.8) {
do {
try data.write(to: url)
uploadToS3(url: url) //Call the updated function to store to AWS bucket
} catch {
print("Handle the error, i.e. disk can be full")
}
}
}
picker.dismiss(animated: true, completion: nil)
}
With this implementation, the image will be uploaded immediately to the server once you select the image from the library.
First thing you need to do is to store the picked image in your app's document directory as a temporary file. As soon as your image is picked, save it to the document directory using the below function.
func saveFileToDocumentDirectory(file: Data, fileExtension: String, folderName: String) -> URL? {
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd_HHmmss"
let stringOfDateTimeStamp = formatter.string(from: Date())
print("Date time stamp String: \(stringOfDateTimeStamp)")
let directoryPath = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("\(folderName)/")
if !FileManager.default.fileExists(atPath: directoryPath) {
do {
try FileManager.default.createDirectory(at: NSURL.fileURL(withPath: directoryPath), withIntermediateDirectories: true, attributes: nil)
} catch {
print(error)
}
}
let filename = "/\(stringOfDateTimeStamp)_\(fileExtension)"
let customPath = "\(folderName)\(filename)"
let filepath = directoryPath+filename
print("FilePATH: \(filepath)")
let url = NSURL.fileURL(withPath: filepath)
do {
try file.write(to: url, options: .atomic)
print("CustomPAth:\(customPath)")
print(String.init("\(directoryPath)\(filename)"))
return url
} catch {
print(error)
print("file cant not be save at path \(filepath), with error : \(error)");
return nil
}
}
This will return a URL and you can then use the below function to upload that file to your S3 bucket.
func uploadToS3(url: URL, contentType: String, fileExtension: String){
SwiftLoader.show(title: "Uploading File", animated: true)
let accessKey = "YOUR_ACCESS_KEY"
let secretKey = "YOUR_SECRET_KEY"
let credentialsProvider = AWSStaticCredentialsProvider(accessKey: accessKey, secretKey: secretKey)
let configuration = AWSServiceConfiguration(region: .USWest2, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
let remoteName = "IMG_\(UUID().uuidString)"+".\(fileExtension)"
let S3BucketName = "YOUR_BUCKET_NAME"
let uploadRequest = AWSS3TransferManagerUploadRequest()!
uploadRequest.body = url
uploadRequest.key = remoteName
uploadRequest.bucket = S3BucketName
uploadRequest.contentType = contentType
uploadRequest.acl = .publicRead
let transferManager = AWSS3TransferManager.default()
transferManager.upload(uploadRequest).continueWith(block: { (task: AWSTask) -> Any? in
if let error = task.error {
print("Upload failed with error: (\(error.localizedDescription))")
DispatchQueue.main.async {
print("An error occurred while Uploading your file, try again.")
SwiftLoader.hide()
}
}
if task.result != nil {
let url = AWSS3.default().configuration.endpoint.url
let publicURL = url?.appendingPathComponent(uploadRequest.bucket!).appendingPathComponent(uploadRequest.key!)
print("Uploaded to:\(String(describing: publicURL))")
}
return nil
})
}
Don't forget to delete your temporary file once your upload is successful.
Here is an example using TransferUtility:-
import AWSCognitoIdentityProvider
import AWSS3
typealias progressBlock = (_ progress: Double) -> Void
typealias completionBlock = (_ response: Any?, _ error: Error?) -> Void
//using Utility upload expression
func uploadImage(with image: URL, key: String?, progress: progressBlock?, completion: completionBlock?) {
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = { (task: AWSS3TransferUtilityTask, awsProgress: Progress) -> Void in
//print(awsProgress.fractionCompleted)
guard let uploadProgress = progress else { return }
DispatchQueue.main.async {
uploadProgress(awsProgress.fractionCompleted)
}
}
expression.setValue("public-read-write", forRequestHeader: "x-amz-acl")
expression.setValue("public-read-write", forRequestParameter: "x-amz-acl")
// Completion block
var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?
completionHandler = { (task, error) -> Void in
DispatchQueue.main.async(execute: {
if error == nil {
let url = AWSS3.default().configuration.endpoint.url
let publicURL = url?.appendingPathComponent(AWS.bucketName).appendingPathComponent(key!)
print("Uploaded to:\(String(describing: publicURL))")
if let completionBlock = completion {
completionBlock(publicURL?.absoluteString, nil)
}
} else {
if let completionBlock = completion {
completionBlock(nil, error)
}
}
})
}
// Start uploading using AWSS3TransferUtility
let awsTransferUtility = AWSS3TransferUtility.default()
awsTransferUtility.uploadFile(
image as URL,
bucket: AWS.bucketName, //Make sure you write the correct bucket name here
key: key!, //"private/{user_identity_id}/my-picture.png"
contentType: "image/png",
expression: expression,
completionHandler: completionHandler).continueWith(block: { (task) -> Any? in
if let error = task.error {
print("error is: \(error.localizedDescription)")
}
if let _ = task.result {
// your uploadTask
print("Starting upload...")
}
return nil
})
}
Two parameters I am passing:-
image: URL and key: String?
Here is how I get image and image name (key):-
//setting temp name for upload // I am using a random string here
let imageName = "\(CommonMethod.randomString(length: 6))" + ".png"
//settings temp location for image
let tempDirectoryUrl = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(imageName)
guard let localUrlPath = image!.save(at: tempDirectoryUrl) else { return }
//URL
print(localUrlPath)
Happy coding!

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)")
}
}
}

Resources