I am using imagePickerController to capture a video and save this video in document directory.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let pickedVideo = (info[UIImagePickerControllerMediaURL]) as! NSURL
UISaveVideoAtPathToSavedPhotosAlbum(pickedVideo.relativePath!, self, nil, nil)
videoUrlForRemoveFromGallery.append(pickedVideo)
let videoData = NSData(contentsOf : pickedVideo as URL)
let ext = pickedVideo.pathExtension
let extFull = "."+ext!
let paths = try! FileManager.default.url(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: false)
let dataPathString = paths.appendingPathComponent(dateStringForFolder+extFull)
let dataPath = dataPathString.path
do {
try videoData?.write(to: dataPathString)
} catch {
print(error)
}
}
Now I want to remove this video from PhotoLibrary. I am doing this in another viewController. For this I am passing
videoUrlForRemoveFromGallery array and wrote this code:
PHPhotoLibrary.shared().performChanges({
let imageAssetToDelete = PHAsset.fetchAssets(withALAssetURLs: self.videoUrlForRemoveFromGallery as [URL], options: nil)
PHAssetChangeRequest.deleteAssets(imageAssetToDelete)
}, completionHandler: {success, error in
print(success ? "Success" : error )
})
But video doesn't remove from PhotoLibrary and I can't play this video from document directory. It says there is no such video file in document directory. But I can save and play video from document directory normally.
Related
I'm displaying to the user an image picker to select a video from the gallery. That works, i pass the mediatype ["public.movie"] and all the videos of the gallery are displayed to pick up. When the user picks it i receive in my dellegate:
extension VNInventoryCollectionVC: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)
if let pickedImage = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.originalImage)] as? UIImage
{
pickImageCallback?(pickedImage)
}
if let videoURL = info["UIImagePickerControllerReferenceURL"] as? URL
{
print(videoURL)
pickImageCallback?(videoURL)
}
dismiss(animated: true)
}
}
After the callback i'm trying to create a image thumbnail from that video, and that is where i'm not able to do it:
let thumbnail = getThumbnailFrom(path: multimedia.video!)
cell.image.image = thumbnail
The function that generates the thumbnail is:
func getThumbnailFrom(path: URL) -> UIImage? {
do {
print("Video URL: \(path)")
print("Video absolute URL: \(path.absoluteURL)")
//let asset = AVURLAsset(url: path.absoluteURL! , options: nil)
var asset : AVAsset = AVAsset(url: path.absoluteURL) as! AVAsset
let imgGenerator = AVAssetImageGenerator(asset: asset)
imgGenerator.appliesPreferredTrackTransform = true
let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil)
let thumbnail = UIImage(cgImage: cgImage)
return thumbnail
} catch let error {
print("*** Error generating thumbnail: \(error.localizedDescription)")
return nil
}
}
In the console the video url displayed is:
Video URL: assets-library://asset/asset.MP4?id=84D1CEDD-7AE4-4FE5-897E-47608DC2CFF0&ext=MP4
Video absolute URL: assets-library://asset/asset.MP4?id=84D1CEDD-7AE4-4FE5-897E-47608DC2CFF0&ext=MP4
The displayed error is that is unable to open the file. The line that breaks is:
let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil)
I suppose the problem is with the url, that i have an assets url and i would require an absolute file path or something like that.
Use UIImagePickerController.InfoKey.mediaURL to key into the info dictionary. UIImagePickerController.InfoKey.referenceURL is deprecated.
func didSelectVideo(at url: URL) {
let asset = AVAsset(url: url)
let generator = AVAssetImageGenerator(asset: asset)
generator.appliesPreferredTrackTransform = true
let cgImage = try! generator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil)
let thumbnail = UIImage(cgImage: cgImage)
cell.image.image = thumbnail
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
dismiss(animated: true) {
guard let url = info[.mediaURL] as? URL else { return }
self.didSelectVideo(at: url)
}
}
I'm trying to save both a recorded video's file path, and a thumbnail from the video to the documents directory. Then, set those two values to an object using the file paths so I can use the object to populate a collection view. With the code I have currently (below), after I record a video, the video path gets saved to the documents directory, and the video path and thumbnail get set to my Post object, and the thumbnail appears properly in my collection view. All good so far.
However only the video path persists between app re-launches since it's in the directory, and the thumbnail isn't. I'd like to save the thumbnail there too but I don't know how to go about it since it appears you can only write URLs to the directory.
This is my first experience with utilizing the documents directory so any help will be appreciated! How can I write the thumbnail (UIImage) to my documents directory along with the video it's from?
Here's my code so far:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let mediaType = info[UIImagePickerControllerMediaType] as! NSString
dismiss(animated: true, completion: nil)
if mediaType == kUTTypeMovie {
// Componenets for a unique ID for the video
var uniqueVideoID = ""
var videoURL:NSURL? = NSURL()
var uniqueID = ""
uniqueID = NSUUID().uuidString
// Get the path as URL
videoURL = info[UIImagePickerControllerMediaURL] as? URL as NSURL?
let myVideoVarData = try! Data(contentsOf: videoURL! as URL)
// Write the video to the Document Directory at myVideoVarData (and set the video's unique ID)
let docPaths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let documentsDirectory: AnyObject = docPaths[0] as AnyObject
uniqueVideoID = uniqueID + "VIDEO.MOV"
let docDataPath = documentsDirectory.appendingPathComponent(uniqueVideoID) as String
try? myVideoVarData.write(to: URL(fileURLWithPath: docDataPath), options: [])
print("docDataPath under picker ", docDataPath)
print("Video saved to documents directory")
// Create a thumbnail image from the video (first frame)
let asset = AVAsset(url: URL(fileURLWithPath: docDataPath))
let assetImageGenerate = AVAssetImageGenerator(asset: asset)
assetImageGenerate.appliesPreferredTrackTransform = true
let time = CMTimeMake(asset.duration.value / 3, asset.duration.timescale)
if let videoImage = try? assetImageGenerate.copyCGImage(at: time, actualTime: nil) {
// Add thumbnail & video path to Post object
let video = Post(pathToVideo: URL(fileURLWithPath: docDataPath), thumbnail: UIImage(cgImage: videoImage))
posts.append(video)
print("Video saved to Post object")
}
}
}
One Suggestion: Save images to Library/Caches if that can be downloaded again as per apple's guide line.
As simple as this:
func saveImageToDocumentDirectory(_ chosenImage: UIImage) -> String {
let directoryPath = NSHomeDirectory().appending("/Documents/")
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 = NSDate().string(withDateFormatter: yyyytoss).appending(".jpg")
let filepath = directoryPath.appending(filename)
let url = NSURL.fileURL(withPath: filepath)
do {
try UIImageJPEGRepresentation(chosenImage, 1.0)?.write(to: url, options: .atomic)
return String.init("/Documents/\(filename)")
} catch {
print(error)
print("file cant not be save at path \(filepath), with error : \(error)");
return filepath
}
}
Swift4:
func saveImageToDocumentDirectory(_ chosenImage: UIImage) -> String {
let directoryPath = NSHomeDirectory().appending("/Documents/")
if !FileManager.default.fileExists(atPath: directoryPath) {
do {
try FileManager.default.createDirectory(at: NSURL.fileURL(withPath: directoryPath), withIntermediateDirectories: true, attributes: nil)
} catch {
print(error)
}
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMddhhmmss"
let filename = dateFormatter.string(from: Date()).appending(".jpg")
let filepath = directoryPath.appending(filename)
let url = NSURL.fileURL(withPath: filepath)
do {
try chosenImage.jpegData(compressionQuality: 1.0)?.write(to: url, options: .atomic)
return String.init("/Documents/\(filename)")
} catch {
print(error)
print("file cant not be save at path \(filepath), with error : \(error)");
return filepath
}
}
I have an app that saves a recorded video to the documents directory, as well as a Post object, and populates a collection view from the Post object. However upon restarting the app, the collection view is empty, so the videos being saved to the docs directory is not persisting (at least I think that's the problem).
This is the function that saves the video:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let mediaType = info[UIImagePickerControllerMediaType] as! NSString
dismiss(animated: true, completion: nil)
if mediaType == kUTTypeMovie {
var uniqueVideoID = ""
var videoURL:NSURL? = NSURL()
var uniqueID = ""
uniqueID = NSUUID().uuidString
// Get the path as URL and store the data in myVideoVarData
videoURL = info[UIImagePickerControllerMediaURL] as? URL as NSURL?
let myVideoVarData = try! Data(contentsOf: videoURL! as URL)
// Write data to temp diroctory
let tempPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let tempDocumentsDirectory: AnyObject = tempPath[0] as AnyObject
uniqueVideoID = uniqueID + "TEMPVIDEO.MOV"
let tempDataPath = tempDocumentsDirectory.appendingPathComponent(uniqueVideoID) as String
try? myVideoVarData.write(to: URL(fileURLWithPath: tempDataPath), options: [])
// Get the time value of the video
let fileURL = URL(fileURLWithPath: tempDataPath)
let asset = AVAsset(url: fileURL)
let duration : CMTime = asset.duration
// Remove the data from the temp Document Diroctory.
do{
let fileManager = FileManager.default
try fileManager.removeItem(atPath: tempDataPath)
} catch {
//Do nothing
}
// Check to see if video is under the 18500 (:30 seconds)
if duration.value <= 18500 {
// Write the data to the Document Directory
let docPaths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let documentsDirectory: AnyObject = docPaths[0] as AnyObject
uniqueVideoID = uniqueID + "VIDEO.MOV"
let docDataPath = documentsDirectory.appendingPathComponent(uniqueVideoID) as String
try? myVideoVarData.write(to: URL(fileURLWithPath: docDataPath), options: [])
print("docDataPath under picker ",docDataPath)
print("Video saved to documents directory")
//Create a thumbnail image from the video
let assetImageGenerate = AVAssetImageGenerator(asset: asset)
assetImageGenerate.appliesPreferredTrackTransform = true
let time = CMTimeMake(asset.duration.value / 3, asset.duration.timescale)
if let videoImage = try? assetImageGenerate.copyCGImage(at: time, actualTime: nil) {
//Add thumbnail & video path to Post object
let video = Post(pathToVideo: URL(fileURLWithPath: docDataPath), thumbnail: UIImage(cgImage: videoImage))
posts.append(video)
print("Video saved to Post object")
}
}else{
print("Video not saved")
}
}
}
Specifically, this is where the video path and thumbnail are added to my object:
//Add thumbnail & video path to Post object
if let videoImage = try? assetImageGenerate.copyCGImage(at: time, actualTime: nil) {
let video = Post(pathToVideo: URL(fileURLWithPath: docDataPath), thumbnail: UIImage(cgImage: videoImage))
posts.append(video)
So I do give it the path to the video in the documents directory; how can I ensure that the data persists there?
EDIT:
To verify if the videos are being saved on the device, connect the device to Xcode and navigate to Window->Devices in Xcode. Then select your device on the left and find your app in the Installed Apps list. Select your app and click on the gear icon at the bottom of the list and press 'Show Containter'. Wait for a few seconds and you should see all the folders in your app.
Secondly, not sure why you are writing the video and deleting it and writing it back again and also why use 'try?' instead of actually catching any exceptions thrown during the file write?
Handle selected image or video:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
print("ok")
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
//what to do to save that image
} else {
//how to get the video and save
}
}
Save it to the document directory:
let path = try! NSFileManager.defaultManager().URLForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomain: NSSearchPathDomainMask.UserDomainMask, appropriateForURL: nil, create: false)
let newPath = path.URLByAppendingPathComponent("image.jpg") //or video.mpg for example
How to save that image to following newPath?
Use following steps to save Image to documents directory
Step 1: Get a path to document directory
let path = try! NSFileManager.defaultManager().URLForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomain: NSSearchPathDomainMask.UserDomainMask, appropriateForURL: nil, create: false)
Step 2: Append FileName in path
let newPath = path.stringByAppendingPathComponent("image.jpg")
Step 3: Decide filetype of Image either JPEG or PNG and convert image to data(byte)
//let pngImageData = UIImagePNGRepresentation(image) // if you want to save as PNG
let jpgImageData = UIImageJPEGRepresentation(image, 1.0) // if you want to save as JPEG
Step 4: write file to created path
let result = jpgImageData!.writeToFile(newPath, atomically: true)
Add above code into your didFinishPickingImage function.
Use following func to save video to documents directory
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject])
{
// *** store the video URL returned by UIImagePickerController *** //
let videoURL = info[UIImagePickerControllerMediaURL] as! NSURL
// *** load video data from URL *** //
let videoData = NSData(contentsOfURL: videoURL)
// *** Get documents directory path *** //
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0]
// *** Append video file name *** //
let dataPath = documentsDirectory.stringByAppendingPathComponent("/videoFileName.mp4")
// *** Write video file data to path *** //
videoData?.writeToFile(dataPath, atomically: false)
}
Updating the accepted answer to Swift 3 in Xcode 8.3.3
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
let path = try! FileManager.default.url(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: false)
let newPath = path.appendingPathComponent("image.jpg")
let jpgImageData = UIImageJPEGRepresentation(image, 1.0)
do {
try jpgImageData!.write(to: newPath)
} catch {
print(error)
}
} else {
let videoURL = info[UIImagePickerControllerMediaURL] as! NSURL
let videoData = NSData(contentsOf: videoURL as URL)
let path = try! FileManager.default.url(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: false)
let newPath = path.appendingPathComponent("/videoFileName.mp4")
do {
try videoData?.write(to: newPath)
} catch {
print(error)
}
}
}
Swift 5
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true)
guard let image = info[.editedImage] as? UIImage else {
print("No image found")
return
}
// saving to application directory
let imageName = UUID().uuidString
let imagePath = getDocumentsDirectory().appendingPathComponent(imageName)
if let jpegData = image.jpegData(compressionQuality: 0.8) {
print("Image Save to path \(imagePath)")
try? jpegData.write(to: imagePath)
}
}
I just haven't "info" in Swift imagePickerController so I don't know how get url and convert it to data to send to web-service.
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
var videoDataURL = info[UIImagePickerControllerMediaURL] as! NSURL!
var videoFileURL = videoDataURL.filePathURL
var video = NSData.dataWithContentsOfMappedFile("\(videoDataURL)")
}
Xcode 10 • Swift 4.2 or later
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
if let url = info[.mediaURL] as? URL {
do {
try FileManager.default.moveItem(at: url, to: documentsDirectoryURL.appendingPathComponent("videoName.mov"))
print("movie saved")
} catch {
print(error)
}
}
}
Xcode 8.3 • Swift 3.1
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
if let fileURL = info[UIImagePickerControllerMediaURL] as? URL {
do {
try FileManager.default.moveItem(at: fileURL, to: documentsDirectoryURL.appendingPathComponent("videoName.mov")
print("movie saved")
} catch {
print(error)
}
}
}
Swift 2
You should use if let to unwrap your optionals. Also NSData.dataWithContentsOfMappedFile was deprecated iOS8. Try using NSData method initializer contentsOfURL:
Note: You need also to change the didFinishPickingMediaWithInfo declaration from [NSObject : AnyObject] to [String : AnyObject]
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if let fileURL = info[UIImagePickerControllerMediaURL] as? NSURL {
if let videoData = NSData(contentsOfURL: fileURL) {
print(videoData.length)
}
}
}
as mentioned by Rob the data can be really large but instead of copying the file you should move the file to the documents folder as follow:
let documentsDirectoryURL = try! NSFileManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
if let fileURL = info[UIImagePickerControllerMediaURL] as? NSURL {
do {
try NSFileManagerdefaultManager().moveItemAtURL(fileURL, toURL: documentsDirectoryURL.URLByAppendingPathComponent("videoName").URLByAppendingPathExtension("mov"))
print("movie saved")
} catch {
print(error)
}
}
There are a couple of issues:
Consider this line:
var videoDataURL = info[UIImagePickerControllerMediaURL] as! NSURL!
This does a forced unwrapping of info[UIImagePickerControllerMediaURL] (which is bad, because if it was nil, the app would crash) and that casts it as an implicitly unwrapped optional NSURL!. That doesn't make sense. Just do a conditional unwrapping (and unwrap to a NSURL, not a NSURL!):
if let videoDataURL = info[UIImagePickerControllerMediaURL] as? NSURL { ... }
The next line calls filePathURL:
var videoFileURL = videoDataURL.filePathURL
If you wanted a file URL, you already have one, so no conversion is needed, but instead just use videoDataURL. If you really wanted a path, you'd use path method:
let videoPath = videoDataURL.path
Frankly, Apple is trying to shift us away from using string paths, so just use the original videoDataURL and avoid the use of both path and filePathURL.
You are using dataWithContentsOfMappedFile:
var video = NSData.dataWithContentsOfMappedFile("\(videoDataURL)")
If you really wanted to use dataWithContentsOfMappedFile, the proper Swift syntax is:
let video = NSData(contentsOfMappedFile: videoPath!)
But dataWithContentsOfMappedFile deprecated, so you should instead use:
let video = try NSData(contentsOfFile: videoPath!, options: .DataReadingMappedIfSafe)
Or, bypassing that videoPath altogether, you could:
let video3 = try NSData(contentsOfURL: videoDataURL, options: .DataReadingMappedIfSafe)
Obviously, those try renditions should be done within a do block with a catch block.
By the way, as you'll see in all of my above examples, one should use let where possible.
--
Quite frankly, I would advise against loading it into a NSData at all. Just copy it with NSFileManager, which is a more efficient use of memory. If the video is long, it could be quite large, and you should avoid loading the whole thing into memory at any given point in time.
So you could:
if let videoDataURL = info[UIImagePickerControllerMediaURL] as? NSURL {
do {
// build your destination URL however you want
//
// let tempFolder = NSURL(fileURLWithPath: NSTemporaryDirectory())
// let destinationURL = tempFolder.URLByAppendingPathComponent("test.mov")
// or
let documents = try NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
let destinationURL = documents.URLByAppendingPathComponent("test.mov")
// but just copy from the video URL to the destination URL
try NSFileManager.defaultManager().copyItemAtURL(videoDataURL, toURL: destinationURL)
} catch {
print(error)
}
}
If you're uploading this to a web service, you'd then use a NSURLSessionUploadTask, using file or stream options. The construction of this request is a separate question, but hopefully you get the idea: With large assets like photos or, especially, videos, don't instantiate a NSData with the asset if you can possibly avoid it.
After picking video this method will get called
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
print("in here")
if let pickedVideo = info[UIImagePickerController.InfoKey(rawValue: UIImagePickerController.InfoKey.mediaURL.rawValue)] as? URL {
print(pickedVideo)
do {
print("Converted")
// let VideoData = try Data(contentsOf: pickedVideo)
uploadVideo(VideoData: try Data(contentsOf: pickedVideo), URL: pickedVideo)
} catch let error {
print(error.localizedDescription)
}
}
}
Find the Simple Solution :
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
self.dismiss(animated: true, completion: nil)
// Using just the information key value
if let url = info[.mediaURL] as? URL {
// Do something with the URL
print("Media URL : ",url)
do {
let videoData = try Data(contentsOf: url)
}
catch let error {
print(error)
}
}
}