Lavvo
Lavvo

Reputation: 1024

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?

Upvotes: 3

Views: 7421

Answers (2)

onmyway133
onmyway133

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

Kumuluzz
Kumuluzz

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

Related Questions