PhotoKit - get thumbnail image and video data - swift

I'd like to make a list view of videos which saved in user's Photos App.
So, I'm trying to make an array containing thumbnail image and AVAsset(or duration and fileURL).
But in the following code, I'm wondering if I can't make the array because of the multiple blocks.
The reason I wrote to blocks was I wanted to get both the thumbnail image and the video data(duration and fileURL).
Is there a way to make such an array?
PhotoKit Fetch Request
var videos = [Video]()
let imageManager = PHImageManager.default()
let fetchOptions = PHFetchOptions()
let imageRequestOptions = PHImageRequestOptions()
let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions)
fetchResult.enumerateObjects { (phAsset, _, _) in
var video = Video()
imageManager.requestImage(for: phAsset, targetSize: CGSize(width: 200, height: 200), contentMode: .aspectFit, options: imageRequestOptions) { (uiImage, _) in
video.thumbnailImage = uiImage!
self.videos.append(video)
}
imageManager.requestAVAsset(forVideo: phAsset, options: nil) { (avAsset, _, _) in
if avAsset != nil {
video.asset = avAsset!
}
}
}
Array's Element
struct Video {
var thumbnailImage: UIImage?
var asset: AVAsset?
}

Just nest the calls:
var video = Video()
imageManager.requestAVAsset(forVideo: phAsset, options: nil) { (avAsset, _, _) in
if avAsset != nil {
video.asset = avAsset!
}
imageManager.requestImage(for: phAsset, targetSize: CGSize(width: 200, height: 200), contentMode: .aspectFit, options: imageRequestOptions) { (uiImage, _) in
video.thumbnailImage = uiImage!
self.videos.append(video)
}
}

Related

Select Multiple Videos and Convert PHAsset to Video File using OpalImagePickerController in swift

I am working with OpalImagePickerController in Swift dealing with uploading multiple images and videos. I can easily deal with images and converting to PHAsset to UIImage but I am trying to upload multiple videos with OpalImagePickerController but unable to convert PHAsset to Video format to upload. I am providing my code .
let imagePicker = OpalImagePickerController()
imagePicker.imagePickerDelegate = self
imagePicker.allowedMediaTypes = Set([PHAssetMediaType.image])
imagePicker.maximumSelectionsAllowed = 20
self.present(imagePicker, animated: true, completion: nil)
internal func imagePicker(_ picker: OpalImagePickerController, didFinishPickingAssets assets: [PHAsset])
{
var image = UIImage()
for asset in assets
{
switch asset.mediaType {
case .image:
print("Image")
image = asset.getAssetThumbnail()
let data = image.pngData() as NSData?
self.sendImage(data:data! as Data, previewImage: image)
case .video:
print("Video")
let options: PHVideoRequestOptions = PHVideoRequestOptions ()
options.deliveryMode = .highQualityFormat
options.version = .original
PHImageManager.default().requestAVAsset (forVideo: asset, options: options, resultHandler: {(asset, audioMix, info) in
if let urlAsset = asset as? AVURLAsset {
let playerItem = AVPlayerItem(asset: urlAsset)
self.sendVideo(video: playerItem, isFromCamera: false)
} else {
}
})
case .audio:
print("Audio")
default:
print("Unknown")
}
}
picker.dismiss(animated: true, completion: nil)
}
extension PHAsset {
func getAssetThumbnail() -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var thumbnail = UIImage()
option.isSynchronous = true
manager.requestImage(for: self,
targetSize: CGSize(width: self.pixelWidth, height: self.pixelHeight),
contentMode: .aspectFit,
options: option,
resultHandler: {(result, info) -> Void in
thumbnail = result!
})
return thumbnail
}
Please help me to convert PHAsset to Video format

How can I convert a PHLivePhoto to UIImage?

In my app I have a section of code where I need to account for a PHLivePhoto type object and convert this to a UIImage. I believe it has to do with a PHAsset, PHAssetResource, or PHImageManager but unclear how to perform the conversion. What's a good approach on how to convert from PHLivePhoto -> UIImage? Thanks for the help!
if let livePhoto = object as? PHLivePhoto {
let livePhotoResources = PHAssetResource.assetResources(for: livePhoto)
print("\(livePhotoResources)")
// imageBucket is of type UIImage[]
// livePhoto is of type PHLivePhoto but I don't know how to convert this to type PHAsset
viewModel.imageBucket.append(convertImageAsset(asset: **WHAT_DO_I_INSERT_HERE**))
}
...
func convertImageAsset(asset: PHAsset) -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var tmpImage = UIImage()
option.isSynchronous = true
manager.requestImage(
for: asset,
targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight),
contentMode: .aspectFit,
options: option,
resultHandler: {(result, info)->Void in
tmpImage = result!
})
return tmpImage
}
results in:
[<PHAssetResource: 0x28225cdc0> {
type: photo
uti: public.heic
filename: IMG_5442.heic
asset: (null)
locallyAvailable: YES
fileURL: file:///private/var/mobile/Containers/Data/Application/2FB56305-7600-4A8E-9C67-A71B4A4A9607/tmp/live-photo-bundle/75FD3D97-F13E-4F79-A6E9-F0743D443FDD.pvt/IMG_5442.HEIC
width: 0
height: 0
fileSize: 0
analysisType: unavailable
cplResourceType: Unknown
isCurrent: NO
isInCloud: NO
}, <PHAssetResource: 0x28226bc00> {
type: video_cmpl
uti: com.apple.quicktime-movie
filename: IMG_5442.mov
asset: (null)
locallyAvailable: YES
fileURL: file:///private/var/mobile/Containers/Data/Application/2FB56305-7600-4A8E-9C67-A71B4A4A9607/tmp/live-photo-bundle/75FD3D97-F13E-4F79-A6E9-F0743D443FDD.pvt/IMG_5442.MOV
width: 0
height: 0
fileSize: 0
analysisType: unavailable
cplResourceType: Unknown
isCurrent: NO
isInCloud: NO
}]
You need to use PHAsset to fetch the asset, then request image data from PHImageManager.default()
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
guard let assetIdentifier = results.first?.assetIdentifier else {
return
}
if let phAsset = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier], options: nil).firstObject {
PHImageManager.default().requestImageDataAndOrientation(for: phAsset, options: nil) { [weak self] data, _, _, _ in
if let data = data, let image = UIImage(data: data) {
self?.viewModel.imageBucket.append(image)
}
}
}
}
To get assetIdentifier, you need to create PHPickerConfiguration object using the shared photo library. Creating a configuration without a photo library provides only asset data, and doesn't include asset identifiers.
var configuration = PHPickerConfiguration(photoLibrary: .shared())
// Set the filter type according to the user’s selection. .images is a filter to display images, including Live Photos.
configuration.filter = .images
// Set the mode to avoid transcoding, if possible, if your app supports arbitrary image/video encodings.
configuration.preferredAssetRepresentationMode = .current
// Set the selection limit.
configuration.selectionLimit = 1
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
The problem with the accepted answer is that fetching a PHAsset will require photo library access, and one of the main advantages of PHPickerViewController is being able to get the photos without asking for permissions, and completely avoiding all the related edge cases.
So another way of getting a live photo's image would be:
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for result in results {
// Live photos
if result.itemProvider.canLoadObject(ofClass: PHLivePhoto.self) {
result.itemProvider.loadObject(ofClass: PHLivePhoto.self, completionHandler: { livePhoto, error in
let resources = PHAssetResource.assetResources(for: livePhoto as! PHLivePhoto)
let photo = resources.first(where: { $0.type == .photo })!
let imageData = NSMutableData()
PHAssetResourceManager.default().requestData(for: photo, options: nil, dataReceivedHandler: { data in
imageData.append(data)
}, completionHandler: { error in
_ = UIImage(data: imageData as Data)!
})
})
}
}
}

Save Image to Photo Library and retrieve the URL

I can save an image to my photo library using the following code:
PHPhotoLibrary.shared().performChanges({
PHAssetCreationRequest
.creationRequestForAssetFromImage(atFileURL: outfileURL)
}) { (saved, err) in
print("Saved?", saved)
if (saved) {
DispatchQueue.main.async {
onComplete(outfileURL.absoluteString)
}
}
}
But I am trying to load the image I just saved in an Image (SwiftUI) and I want to get the new images fileURL. The outfileURL is a temp file and is not retained. Please note this is a gif - if that has any bearing.
I am trying to use the PHObjectPlaceholder thing but I still don't know how to get the location out:
var placeHolder: PHObjectPlaceholder? = nil
PHPhotoLibrary.shared().performChanges({
let changeRequest = PHAssetCreationRequest.creationRequestForAssetFromImage(atFileURL: outfileURL)
placeHolder = changeRequest?.placeholderForCreatedAsset
}) { (saved, err) in
print("Saved? \(saved) to location \(placeHolder?)") //<--- AAAARGH!!!!
if (saved) {
DispatchQueue.main.async {
onComplete(/*????*/)
}
}
}
From PHObjectPlaceholder you can use localIdentifier
let fetchOptions = PHFetchOptions()
let fetchResult: PHFetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [placeholder.localIdentifier], options: fetchOptions)
if let asset = fetchResult.firstObject {
// Here you can get UIImage from PHAsset
}
So this is possible solution:
var placeHolder: PHObjectPlaceholder? = nil
PHPhotoLibrary.shared().performChanges({
let changeRequest = PHAssetCreationRequest.creationRequestForAssetFromImage(atFileURL: outfileURL)
placeHolder = changeRequest?.placeholderForCreatedAsset
}) { (saved, err) in
if let localIdentifier = placeHolder?.localIdentifier, saved {
let fetchOptions = PHFetchOptions()
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: fetchOptions)
if let phAsset = fetchResult.firstObject {
let targetSize = CGSize(width: CGFloat(phAsset.pixelWidth), height: CGFloat(phAsset.pixelHeight))
let options = PHImageRequestOptions()
PHCachingImageManager.default().requestImage(for: phAsset, targetSize: targetSize, contentMode: .aspectFill, options: options) { (uiImage, info) in
DispatchQueue.main.async {
onComplete(uiImage)
}
}
}
}
}

How to Create thumbnail from local files

I want to create a thumbnail image for files (word, excel, video ....)
This what i did:
import QuickLook
class ThumbsCreator: NSObject {
private var file : File?
init(file: File?) {
super.init()
self.file = file
}
func createThumb() {
let url = URL(string: (self.file?.path()))
}
}
After a lot of search, I found this solution :
import PDFKit
import AVKit
import WebKit
func createThumb() {
let url = URL(string: (self.file?.path()))
switch file?.type {
case: FileType.image.rawValue:
let image = UIImage(contentsOfFile: (url?.path)!)
_finalImage = self.createScaledImage(image: image!)
break
case: FileType.office.rawValue:
//Loading.......
break
case FileType.Pdf.rawValue:
guard let doc = PDFDocument(url: url!) else {return}
guard let page = doc.page(at: 0) else {return}
_finalImage = page.thumbnail(of: CGSize(width: 768, height: 1024), for: .cropBox)
break
case: FileType.video.rawValue:
let asset = AVAsset(url: url!)
let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true
let time = CMTime(seconds: 2, preferredTimescale: 1)
do {
let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
_finalImage = UIImage(cgImage: imageRef)
} catch let error{
print("Error: \(error)")
}
break
}
}
func createScaledImage(image: UIImage) {
let THUMB_WIDTH = 150.0 - 40.0
let THUMB_HEIGHT = THUMB_WIDTH - 23.0
var itemThumb = resizeImage(image: image, constraintSize: CGSize(width: THUMB_WIDTH, height: THUMB_HEIGHT))
let thumbRect = CGRect(x: 0, y: 0, width: 10, height: 10)
UIGraphicsBeginImageContextWithOptions(thumbRect.size, true, 0.0)
let context = UIGraphicsGetCurrentContext()
// Fill a white rect
context?.setFillColor(gray: 1.0, alpha: 1.0)
context?.fill(thumbRect)
// Stroke a gray rect
let comps : [CGFloat] = [0.8, 0.8, 0.8, 1]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let strokeColor = CGColor(colorSpace: colorSpace, components: comps)
context?.setStrokeColor(strokeColor!)
UIRectFrame(thumbRect)
//CGColorRelease(strokeColor!)
itemThumb.draw(in: thumbRect.insetBy(dx: 1, dy: 1))
itemThumb = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
self.finishThumCreation(image: image)
}
}
Starting from iOS 13 and macOS 10.15, there is the QuickLook Thumbnailing API. It supports any file format for which the OS can provide a preview: either because the OS knows this format or because the owner of the third-party format provided a QuickLook plugin.
Here is an example based on Apple's tutorial:
func thumbnail(for fileURL: URL, size: CGSize, scale: CGFloat) {
let request = QLThumbnailGenerator
.Request(fileAt: fileURL, size: size, scale: scale,
representationTypes: .lowQualityThumbnail)
QLThumbnailGenerator.shared.generateRepresentations(for: request)
{ (thumbnail, type, error) in
DispatchQueue.main.async {
if thumbnail == nil || error != nil {
// Handle the error case gracefully.
} else {
// Display the thumbnail that you created.
}
}
}
}
On macOS before 10.15, in my app I fallback to NSWorkspace.shared.icon(forFile:) which provides a document icon based on the file type (but not a thumbnail).
You can use https://developer.apple.com/documentation/uikit/uidocumentinteractioncontroller/1616801-icons
var icons: [UIImage] { get }
let controller = UIDocumentInteractionController(url:someUrl)
print(controller.icons.first)
Only for a video
extension UIViewController {
func thumbnail(_ sourceURL:URL) -> UIImage {
let asset = AVAsset(url: sourceURL)
let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true
let time = CMTime(seconds: 1, preferredTimescale: 1)
do {
let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
return UIImage(cgImage: imageRef)
} catch {
print(error)
return UIImage(named: "NoVideos")!
}
}
}
There's no good API for this yet. There is NSURLThumbnailDictionaryKey, but YMMV. You can indeed get icons via UIDocumentInteractionController.

How do you use a button click function to convert an PHAsset to a UIImage

Ok, so i've been at this for a while and I just can't seem to solve it.
I have an ImagePicker which bundles images into PHAssest. It's from https://github.com/hyperoslo/ImagePicker
When an image is selected, the done button is activated. I have a UIImageView which when i press the func doneButtonDidPress(images: [UIImage]), would convert the PHAsset into a UIImage and load it into the ImageView. Currently, this is my code which doesn't work:
// CONVERT PHASSET TO UIIMAGE
func getAsset(asset: PHAsset) -> UIImage {
let manager = PHImageManager.defaultManager()
let option = PHImageRequestOptions()
var image = UIImage()
option.synchronous = true
manager.requestImageForAsset(asset, targetSize: CGSize(width: 100.0, height: 100.0), contentMode: .AspectFit, options: option, resultHandler: {(result, info)->Void in
image = result!
})
return image
}
func doneButtonDidPress(images: [UIImage]) {
self.imagePickerController.dismissViewControllerAnimated(true, completion: nil)
var selectedImg = imagePickerController.stack.assets
getAsset(selectedImg)
self.SelectedImageView.image = UIImage(named: selectedImg)
}
Which flags with errors. Can anyone help me here?! Thankyou
I am getting UIImage from PHAsset like :
func getAsset(asset: PHAsset) -> UIImage {
var image = UIImage()
let imgManager = PHImageManager.defaultManager()
let requestOptions = PHImageRequestOptions()
requestOptions.synchronous = true
imgManager.requestImageForAsset(asset, targetSize: CGSizeMake(300.0, 300.0), contentMode: PHImageContentMode.AspectFit, options: requestOptions, resultHandler: { (img, _) in
image = img
})
return image
}
This code is tested & currently working in my application.
Done Button method should be like :
func doneButtonDidPress(image: [UIImage]) {
self.imagePickerController.dismissViewControllerAnimated(true, completion: nil)
var selectedImg = imagePickerController.stack.assets.first
self.selectedImageView.image = getAsset(selectedImg)
}