Reputation: 1024
This seems to be something that should be very straight forward based on all the examples and docs I've read, but I am still unable to get this to work for some strange reason.
I am using Alamofire Frame work to download a video from instagram. Once downloaded, I then want to save the video to the Camera Roll. Here is my code to Download the video and save to disk:
let destination: (NSURL, NSHTTPURLResponse) -> (NSURL) = {
(temporaryURL, response) in
if let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
let finalPath = directoryURL.URLByAppendingPathComponent("\(Scripts.dateToString2(date: NSDate())).\(response.suggestedFilename!)")
InstagramEngine.downloadMediaPath = finalPath
Scripts.log("Final Path >> \(finalPath)")
return finalPath
}
return temporaryURL
}
let request = Alamofire.download(.GET, self.videoURL, destination)
request.response { _, response, data, error in
NSNotificationCenter.defaultCenter().postNotificationName(kMediaDownloadComplete, object: nil)
}
Once the download is complete, the Notification is triggered which calls this function to save it to Camera Roll:
UISaveVideoAtPathToSavedPhotosAlbum(InstagramEngine.downloadMediaPath.URLString, self, Selector("video:didFinishSavingWithError:contextInfo:"), nil)
Everything is being called based on my log statements and no errors occured. Even didFinishSavingWithError is being called successfully for the UISaveVideoAtPathToSavedPhotosAlbum and I confirmed no errors found. But when I go check the camera roll, I still see no video saved there. Any ideas?
Upvotes: 3
Views: 7421
Reputation: 48065
Here's how to download and save video, using Alamofire. Code is in Swift 4. Note that we need to use Photos
framework because AssetsLibrary
was deprecated
import UIKit
import Alamofire
import Photos
/// Download and save video to camera roll
final class VideoFetcher {
private let fileManager: FileManager
init(fileManager: FileManager = FileManager.default) {
self.fileManager = fileManager
}
func downloadAndSave(videoUrl: URL, completion: @escaping (Bool) -> Void) {
let destination: (URL, HTTPURLResponse) -> (URL, DownloadRequest.DownloadOptions) = {
tempUrl, response in
let option = DownloadRequest.DownloadOptions()
let finalUrl = tempUrl.deletingPathExtension().appendingPathExtension(videoUrl.pathExtension)
return (finalUrl, option)
}
Alamofire.download(videoUrl, to: destination)
.response(completionHandler: { [weak self] response in
guard response.error == nil,
let destinationUrl = response.destinationURL else {
completion(false)
return
}
self?.save(videoFileUrl: destinationUrl, completion: completion)
})
}
private func save(videoFileUrl: URL, completion: @escaping (Bool) -> Void) {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoFileUrl)
}, completionHandler: { succeeded, error in
guard error == nil, succeeded else {
completion(false)
return
}
completion(true)
})
}
}
Upvotes: 0
Reputation: 2012
Unfortunately there is a bug related to UISaveVideoAtPathToSavedPhotosAlbum
and the format mp4
, which is the format used by Instagram.
There is a helper method called UIVideoAtPathIsCompatibleWithSavedPhotosAlbum
to help indicate whether a video is compatible with the method UISaveVideoAtPathToSavedPhotosAlbum
. This returns false
for the video downloaded from Instagram.
Luckily it is still possible to store the videos into the camera roll. This is possible using ALAssetsLibrary
. I've tried to take your sample code and adapt it to use ALAssetsLibrary
, hopefully this can help you to get it working.
import AssetsLibrary
...
...
func downloadVideoToCameraRoll() {
// Local variable pointing to the local file path for the downloaded video
var localFileUrl: String?
// A closure for generating the local file path for the downloaded video. This will be pointing to the Documents directory with a unique UDID file name.
let destination: (NSURL, NSHTTPURLResponse) -> (NSURL) = {
(temporaryURL, response) in
if let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
let finalPath = directoryURL.URLByAppendingPathComponent("\(NSUUID()).\(response.suggestedFilename!)")
localFileUrl = finalPath.absoluteString
return finalPath
}
return temporaryURL
}
// The media post which should be downloaded
let postURL = NSURL(string: "https://api.instagram.com/v1/media/" + "952201134785549382_250131908" + "?access_token=" + InstagramEngine.sharedEngine().accessToken)!
// Then some magic happens that turns the postURL into the videoURL, which is the actual url of the video media:
let videoURL = NSURL(string: "https://scontent.cdninstagram.com/hphotos-xfp1/t50.2886-16/11104555_1603400416544760_416259564_s.mp4")!
// Download starts
let request = Alamofire.download(.GET, videoURL, destination)
// Completion handler for the download
request.response { (request, response, data, error) -> Void in
if let path = localFileUrl {
let isVideoCompatible = UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)
println("bool: \(isVideoCompatible)") // This logs out "bool: false"
let library = ALAssetsLibrary()
library.writeVideoAtPathToSavedPhotosAlbum(NSURL(string: path), completionBlock: { (url, error) -> Void in
// Done! Go check your camera roll
})
}
}
}
Upvotes: 4