Search code examples
iosswiftvideoalamofirecamera-roll

Download and Save Video to Camera Roll


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?


Solution

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