I have made a library collectionview filled with videos i fetch from the Photos framework, i use thumbnails to display all the different videos using the following method
func requestImageForAsset(_ asset: PHAsset!, targetSize targetSize: CGSize, contentMode contentMode: PHImageContentMode, options options: PHImageRequestOptions!, resultHandler resultHandler: ((UIImage!, [NSObject : AnyObject]!) -> Void)!) -> PHImageRequestID
which works fine.
But my videos always start with a black frame, so all the thumbnails are black. can I offset the frame from which it takes a snapshot image?
This is code i have used in my project which works like a charm for me
func generateThumnail(url: URL) -> UIImage? {
let asset = AVAsset(url: url)
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
assetImgGenerate.maximumSize = CGSize(width: 100, height: 100)
let time = CMTimeMake(1, 30)
if let img = try? assetImgGenerate.copyCGImage(at: time, actualTime: nil) {
return UIImage(cgImage: img)
}
return nil
}
The above function will return the image at 1 sec of the video
This is the way of passing the assetUrl of your video to get the thumbnail
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
let mediaType = info[UIImagePickerControllerMediaType] as! String
if (mediaType == kUTTypeMovie as! String){
let tempVideo = info[UIImagePickerControllerMediaURL] as! URL
if let thumbnail = self.generateThumbnail(url: tempVideo) {
// Use your thumbnail
}
You can fetch the associated AVAsset object with
func requestAVAssetForVideo(_ asset: PHAsset,
options options: PHVideoRequestOptions?,
resultHandler resultHandler: (AVAsset?,
AVAudioMix?,
[NSObject : AnyObject]?) -> Void) -> PHImageRequestID
then load that asset into an AVAssetImageGenerator which is capable of fetching time-specific thumbnails via
func copyCGImageAtTime(_ requestedTime: CMTime,
actualTime actualTime: UnsafeMutablePointer<CMTime>,
error outError: NSErrorPointer) -> CGImage!
I think you need to be looking at the AVFoundation libraries to decode the video and grab the frame you want yourself.
To update the placeholders in the library looking at the docs it seems that you need to create a PHAssetChangeRequest and set the placeholderForCreatedAsset property on it. Then you need to request that the change be performed by the library within a changeblock.
Note: If this is only for your use you can set the placeholder frame within Photos and that should be reflected in your app.
Related
Below is how I fetch an image from the library. When I grant photo access to "Selected Photos" in the system settings for the app the image is as expected the largest size available. However, when I change photo access permissions to "All Photos" the code below produces a thumbnail of the original image. The app runs on ios 15. No iCloud is configured. Can anyone tell me please what is going on?
private func loadImage(assetId: String, done: #escaping (Image?) -> Void) {
let fetchResults: PHFetchResult<PHAsset> =
PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil)
guard let asset: PHAsset = fetchResults.firstObject else {
return
}
let manager = PHImageManager()
manager.requestImage(for: asset, targetSize: PHImageManagerMaximumSize,
contentMode: .aspectFit, options: nil) { (uiImage, _) in
if let uiImage = uiImage {
done(Image(uiImage: uiImage))
} else {
done(nil)
}
}
}
In general I can see why image quality depends on level of access. I was not able to find any documentation on this. What I did find though was that the deliveryMode of the request also matters. when I added the following options to the requestImage call the issue was solved.
let requestOptions = PHImageRequestOptions()
requestOptions.deliveryMode = .highQualityFormat
let manager = PHImageManager()
manager.requestImage(for: asset, targetSize: PHImageManagerMaximumSize,contentMode: .aspectFit, options: requestOptions) { (uiImage, _) in
//do something
}
I use an UIImagePicker to choose an image from the photos app and would like to store the url and later reuse the same image.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let chosenImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
let asset = info[UIImagePickerController.InfoKey.phAsset]
let url = info[UIImagePickerController.InfoKey.referenceURL] as! URL
...
Why is asset nil?
referenceURL is deprecated but contains something like assets-library://asset/asset.JPG?id=95554EBE-DAB8-4A36-9BEA-00BAB0174777&ext=JPG
private func loadImage() -> UIImage? {
let manager = PHImageManager.default()
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "? = %#", "?")
let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
var image: UIImage? = nil
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .highQualityFormat
manager.requestImage(for: fetchResult.object(at: 0), targetSize: CGSize(width: 500, height: 500), contentMode: .aspectFill, options: requestOptions) { img, err in
image = img
}
return image
}
What should the predicate look like? Thanks!
The PHAsset is nil because you didn't obtain user authorization for Photo Library usage beforehand. Your lack of authorization means yes, you can present the picker, and yes, the user can choose a photo, and yes, you can receive the photo image, but you can't look into the Photo Library.
If you get permission beforehand, the PHAsset will be an actual PHAsset and you can use that to go back and get further info about the asset from the Photo Library. Use the asset's identifier if you want to store a persistent reference to it.
https://developer.apple.com/documentation/photokit/phobject/1622400-localidentifier
I surely hope I am missing something because I do not understand why this is working the way it does. I have a PNG Image, which has a fully transparent background because I want to overlay it on other images inside a UIImageView.
PNG images included in the XCode project all work fine as they should. The problem is when I select these same PNG images on the fly using UIImagePickerController and then assigning it to the UIImageView, for some really bizarre reason, it is not respecting it as a PNG Image with transparency and instead it adding a white background.
Anyone seen this before and how do I get around this?
* UPDATE #1: I decided to try something that seems to confirm my theory. I decided to email myself the original PNG images I saved to my device and lo and behold, the images came to me as JPG. Seems to me that when you save an image to Photos on iPhone it converts it to JPG, this is rather shocking to me. Hope someone has a way around this. The original images testImage1.png and testImage2.png saved to Photos and then emailed back to myself, returned as IMG_XXXX.jpg and IMG_XXXX.jpg
* UPDATE #2: I kept playing around we this more and found out a few things and in the process was able to answer my own question. (1) My theory in UPDATE #1 is partially correct, but the conversion does not happen when saving the Photo, seems like it is on the fly. Internally photos stores the original image extension (2) I was able to validate this when I realized in my UIImagePickerControllerDelegate that I was using
let imageData = UIImageJPEGRepresentation(image, 1.0)
instead of this
let imageData = UIImagePNGRepresentation(image)
When I used the second line of code, it was recognizing the original transparency properties for the image.
Yes, the call to UIImageJPEGRepresentation will convert the resulting image into a JPEG, which doesn't support transparency.
BTW, if your intent is to get the NSData for the image for other reasons (e.g. uploading to server, emailing, etc.), I would recommend against both UIImageJPEGRepresentation and UIImagePNGRepresentation. They lose meta data, can make the asset larger, if suffer some image degradation if you use quality factor of less than 1, etc.
Instead, I'd recommend going back and get the original asset from the Photos framework. Thus, in Swift 3:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let url = info[UIImagePickerControllerReferenceURL] as? URL {
let result = PHAsset.fetchAssets(withALAssetURLs: [url], options: nil)
if let asset = result.firstObject {
let manager = PHImageManager.default()
manager.requestImageData(for: asset, options: nil) { imageData, dataUTI, orientation, info in
if let fileURL = info!["PHImageFileURLKey"] as? URL {
let filename = fileURL.lastPathComponent
// use filename here
}
// use imageData here
}
}
}
picker.dismiss(animated: true)
}
If you have to support iOS 7, too, you'd use the equivalent ALAssetsLibrary API, but the idea is the same: Get the original asset rather than round-tripping it through a UIImage.
(For Swift 2 rendition, see previous revision of this answer.)
Swift 3 version of answer by #Rob
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let URL = info[UIImagePickerControllerReferenceURL] as? NSURL {
let result = PHAsset.fetchAssets(withALAssetURLs: [URL as URL], options: nil)
if let asset:PHAsset = result.firstObject! as PHAsset {
let manager = PHImageManager.default()
manager.requestImageData(for: asset, options: nil) { imageData, dataUTI, orientation, info in
let fileURL = info!["PHImageFileURLKey"] as? NSURL
let filename = fileURL?.lastPathComponent;
// use imageData here
}
}
}
picker.dismiss(animated: true, completion: nil)
}
An alternative solution uses the PHAssetResourceManager rather than PHImageManager. Using Xcode 10, Swift 4.2.
func imageFromResourceData(phAsset:PHAsset) {
let assetResources = PHAssetResource.assetResources(for: phAsset)
for resource in assetResources {
if resource.type == PHAssetResourceType.photo {
var imageData = Data()
let options = PHAssetResourceRequestOptions()
options.isNetworkAccessAllowed = true
let _ = PHAssetResourceManager.default().requestData(for: resource, options: options, dataReceivedHandler: { (data:Data) in
imageData.append(data)
}, completionHandler: { (error:Error?) in
if error == nil, let picked = UIImage(data: imageData) {
self.handlePickedImage(picked: picked)
}
})
}
}
}
Use it like this:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
dismiss(animated: true)
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as! NSString
if mediaType == kUTTypeImage || mediaType == kUTTypeLivePhoto {
if #available(iOS 11.0, *) {
if let phAsset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset {
self.imageFromResourceData(phAsset: phAsset)
}
else {
if let picked = (info[UIImagePickerController.InfoKey.originalImage] as? UIImage) {
self.handlePickedImage(picked: picked)
}
}
}
else if let picked = (info[UIImagePickerController.InfoKey.originalImage] as? UIImage) {
self.handlePickedImage(picked: picked)
}
}
}
I want to put the videos stored on my iPhone to my Google Drive.
I have already done with images, but with videos, it's an other problem...
For images, no problem, I convert my asset to an NSData with this method :
data = UIImagePNGRepresentation(result!)!
And I put the image to my drive !
But, for videos, I tried many different ways, but no, I can't.
How can I do ?
Thanks a lot !
I did it !
This is the solution :
PHCachingImageManager().requestAVAssetForVideo(asset, options: nil, resultHandler: {(asset: AVAsset?, audioMix: AVAudioMix?, info: [NSObject : AnyObject]?) in
dispatch_async(dispatch_get_main_queue(), {
let asset = asset as? AVURLAsset
var data = NSData(contentsOfURL: asset.URL)
})
})
And after, you have the good NSData variable which you can use to put your video to the Cloud !
Please add Bellow solution its work for me
if you miss option.isNetworkAccessAllowed = true then you get error for genera the url
private let options: PHVideoRequestOptions = PHVideoRequestOptions()
option.isNetworkAccessAllowed = true
PHImageManager.default().requestAVAsset(-------
Updated for Swift 5
PHImageManager or PHCachingImageManager can be used here
PHImageManager.default().requestAVAsset(forVideo: asset,
options: nil) { (asset, audioMix, info) in
if
let asset = asset as? AVURLAsset,
let data = NSData(contentsOf: asset.url) {
//do smth with data
}
}
}
Fetch synchronously Image/Video Swift 5 + caching
extension PHAsset {
func getImage() -> UIImage? {
let manager = PHCachingImageManager.default
let option = PHImageRequestOptions()
option.isSynchronous = true
var img: UIImage? = nil
manager().requestImage(for: self, targetSize: CGSize(width: self.pixelWidth, height: self.pixelHeight), contentMode: .aspectFit, options: nil, resultHandler: {(result, info) -> Void in
img = result!
})
return img
}
func getVideo() -> NSData? {
let manager = PHCachingImageManager.default
let option = PHImageRequestOptions()
option.isSynchronous = true
var resultData: NSData? = nil
manager().requestAVAsset(forVideo: self, options: nil) { (asset, audioMix, info) in
if let asset = asset as? AVURLAsset, let data = NSData(contentsOf: asset.url) {
resultData = data
}
}
return resultData
}
}
I'm trying to do the equivalent of writeImageToSavedPhotosAlbum with the new Photo framework.
To save the image, I only do this:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
let image = info[UIImagePickerControllerOriginalImage] as UIImage
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let changeRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
}, completionHandler: { (success, error) -> Void in
//
})
}
Of course, there's no magic and since I don't do anything of
info[UIImagePickerControllerMediaMetadata]
, the above code doesn't save any metadata to the "Camera Roll", as you can see using the screenshot of the Preview.app when I connect my iPhone to my Mac.
You get that view by opening Preview.app, selecting File > Import from "you device name"; then you can browse your pictures and see that those taken with the Camera app show exif data such as focal length, while those saved with the above code show empty values.
Now the documentation for creationRequestForAssetFromImage says:
To set metadata properties of the newly created asset, use the
corresponding properties of the change request (listed in Modifying
Assets).
Which links to "+changeRequestForAsset:" and 4 properties (creationDate, location, favorite, hidden), that's a little light. What about the other properties one might want to save (like aperture, focal length, shutter speed, …)?
How are you supposed to save your meta data along the image with the Photo framework?
Here's what I ended up doing:
extension UIImage {
/**
Gets the metadata from the photo album
:param: info The picker dictionary
:param: completionHandler A block to call when the metadata is available
*/
class func requestMetadata(info: [NSObject : AnyObject], completionHandler: ([NSObject : AnyObject]) -> Void) {
let assetUrl = info[UIImagePickerControllerReferenceURL] as! NSURL
let result = PHAsset.fetchAssetsWithALAssetURLs([assetUrl], options: nil)
if let asset = result.firstObject as? PHAsset {
let editOptions = PHContentEditingInputRequestOptions()
editOptions.networkAccessAllowed = true
asset.requestContentEditingInputWithOptions(editOptions, completionHandler: { (contentEditingInput, _) -> Void in
let url = contentEditingInput.fullSizeImageURL
let orientation = contentEditingInput.fullSizeImageOrientation
var inputImage = CoreImage.CIImage(contentsOfURL: url)
completionHandler(inputImage.properties())
})
} else {
completionHandler([:])
}
}
}