iOS Photos library - how to get the REAL filename? - ios

I am looking for either a Swift or Objective C solution for this. Here, I am showing my example using swift.
I am using the Photos framework on iOS and having a hard time finding the REAL filename instead of the IMG_4227.JPG type. The name of my image is actually myawesomefilename.jpg and I see this file name if I airdrop or transfer to dropbox the image from my iPhone. But when I use the Photos framework, I get the names as IMG_4227.JPG.
There is an app on the app store which is able to get the real file names of all images, so it is definitely possible. I just don't know how.
Here's how I am currently getting the IMG_4227.JPG type names:
func exifTapped(asset: PHAsset) {
print("name: \(asset.value(forKey: "filename"))")
getURL(ofPhotoWith: asset) { (url) in
print("URL: \(url)")
let data = NSData.init(contentsOf: url!)!
if let imageSource = CGImageSourceCreateWithData(data, nil) {
let imageProperties2 = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)! as NSDictionary
print("imageProperties2: ", imageProperties2)
}
}
}
func getURL(ofPhotoWith mPhasset: PHAsset, completionHandler : #escaping ((_ responseURL : URL?) -> Void)) {
if mPhasset.mediaType == .image {
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.isNetworkAccessAllowed = true
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
mPhasset.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput, info) in
completionHandler(contentEditingInput!.fullSizeImageURL)
})
} else if mPhasset.mediaType == .video {
let options: PHVideoRequestOptions = PHVideoRequestOptions()
options.version = .original
options.isNetworkAccessAllowed = true
PHImageManager.default().requestAVAsset(forVideo: mPhasset, options: options, resultHandler: { (asset, audioMix, info) in
if let urlAsset = asset as? AVURLAsset {
let localVideoUrl = urlAsset.url
completionHandler(localVideoUrl)
} else {
completionHandler(nil)
}
})
}
}
EDIT: None of the duplicate solutions are any different than what I already have and they all give the IMG_4227.JPG type names instead of the real name. So this is not a duplicate.

I figured it out.
let resources = PHAssetResource.assetResources(for: asset)
print("Filename: \((resources.first as! PHAssetResource).originalFilename)")

Related

How to find media file type using MPMediaItemPropertyMediaType like mp3/mpeg/m4a in Swift iOS

I am developing a music app in Swift3. Where I need to select any audio song from device gallery and upload this to Amazon S3. Here is my code :
func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection)
{
self.dismiss(animated: true, completion: nil)
let item: MPMediaItem? = (mediaItemCollection.items[0] as? MPMediaItem)
let url: URL? = item?.value(forProperty: MPMediaItemPropertyAssetURL) as! URL?
let albumTitle = item?.value( forProperty: MPMediaItemPropertyTitle ) as! String
let albumMediaType = item?.value( forProperty: MPMediaItemPropertyMediaType) as! Int
}
As an output, I am getting Asset Url : ipod-library://item/item.mp3?id=8739819345820495362 And Media Type is coming in integer 1/2/3/4....
Now, my problem is I need to know the file type of the selected song, because when I am adding hard code '.mp3' to file and upload this to Amazon S3 then some of audio files are playing and rest is not!
Can you please help me out to resolve this issue? Thank you.
Edit: Here is my code to convert AssetUrl to path :
func exportMedia(_ assetURL: URL, completionHandler: #escaping (_ fileURL: URL?, _ error: Error?) -> ()) {
let asset = AVURLAsset(url: assetURL)
guard let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else {
completionHandler(nil, ExportError.unableToCreateExporter)
return
}
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(NSUUID().uuidString)
.appendingPathExtension("m4a")
exporter.outputURL = fileURL
exporter.outputFileType = "com.apple.m4a-audio"
exporter.exportAsynchronously {
if exporter.status == .completed {
completionHandler(fileURL, nil)
} else {
completionHandler(nil, exporter.error)
}
}
}

how to get image from PHImageFileURLKey

guys.
I am getting images URL from photos by using below code.
func getAllImagesURL() -> [URL]
{
var arr_URL = [URL]()
for index in 0..<fetchResult.count
{
imgManager.requestImageData(for: fetchResult.object(at: index) as PHAsset, options: requestOptions, resultHandler: { (imagedata, dataUTI, orientation, info) in
if let fileName = (info?["PHImageFileURLKey"] as? URL)
{
//do sth with file name
arr_URL.append(fileName)
}
})
}
return arr_URL
}
By using this URL key I want to get the image from photos.I have searched and found below code.But it still not working.
func getImage(assetUrl: URL) -> UIImage? {
let asset = PHAsset.fetchAssets(withALAssetURLs: [assetUrl], options: nil)
guard let result = asset.firstObject else {
return nil
}
var assetImage: UIImage?
let options = PHImageRequestOptions()
options.isSynchronous = true
PHImageManager.default().requestImage(for: result, targetSize: UIScreen.main.bounds.size, contentMode: PHImageContentMode.aspectFill, options: options) { image, info in
assetImage = image
}
return assetImage
}
It returns nil.So please help me.How to get the image by using URL key.
Thanks in Advance..
In the getImage(assetUrl: URL) -> UIImage? method, you are using
let asset = PHAsset.fetchAssets(withALAssetURLs: [assetUrl], options: nil)
here assetUrl is the url that should be taken from if you are using the AssetsLibrary. This library is deprecated from iOS 9.0 onwards. We have to use Photos library instead.
BTW, you are already getting all the images(data) in getAllImageURLs() method. Just convert that method to get all the images and process those images as required. You can use the below method to get all the images.
func getAllImages() -> [UIImage]?
{
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: true)]
let fetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
var allImages = [UIImage]()
for index in 0..<fetchResult.count
{
let asset = fetchResult.object(at: index) as PHAsset
imgManager.requestImage(for: asset, targetSize: UIScreen.main.bounds.size, contentMode: .aspectFill, options: requestOptions, resultHandler: { (uiimage, info) in
allImages.append(uiimage!)
})
}
return allImages
}
NOTE: tweak this method as per your requirements.

How to upload PHAssets in background

In Objective-C I want to upload all PHAssets (images and videos) to my server in background. Can any please suggest me how can I do this?
let mPhasset : PHAsset = PHAsset()
if mPhasset.mediaType == .image {
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
mPhasset.requestContentEditingInput(with: options, completionHandler: {(contentEditingInput: PHContentEditingInput?, info: [AnyHashable: Any]) -> Void in
if let url = contentEditingInput?.fullSizeImageURL {
// IMPORTANT
// this url avaiable only in this block
// we can't perform async operation in bg
// cause access will expired
// Now you can copy using FileManager
// and after this upload copied file
}
})
// Alternative
let imageManager = PHCachingImageManager()
let opt = PHImageRequestOptions()
opt.resizeMode = .exact
opt.deliveryMode = .highQualityFormat
imageManager.requestImage(for: mPhasset,
targetSize: CGSize(width: mPhasset.pixelWidth, height: mPhasset.pixelHeight),
contentMode: .aspectFill,
options: opt,
resultHandler:
{ (image: UIImage?, _) in
// now you can go to background and do whatever you want
// benefit that you don't need to copy file in main thread
// which can lag your app
// disadvantage of this method: we load image in memory
})
} else if mPhasset.mediaType == .video {
let options: PHVideoRequestOptions = PHVideoRequestOptions()
options.version = .current
PHImageManager.default().requestExportSession(forVideo: mPhasset, options: options, exportPreset: AVAssetExportPresetHighestQuality, resultHandler: { (session, info) in
// if we just request video url asset and try upload to server
// we will have problems with slow mo videos
// also we will be on the main thread and access for video will expire, if we go to background
// + size of video is very huge
guard let session = session else { return }
session.outputURL = URL(fileURLWithPath: "our output path")
session.outputFileType = AVFileTypeQuickTimeMovie
session.shouldOptimizeForNetworkUse = true
session.exportAsynchronously {
if (session.status == .completed) {
// success
} else {
}
}
})
}

Check if image exists in Photos

I have an album of images that is managed by a remote server. I would like to give the user an option to download the album and store it to a custom album in Photos. But since the album is dynamic (photos get added to it) the user can download it multiple times. I don't want to download the same pictures multiple times, only the new ones.
Is it possible to associate some metadata (unique id) when I store the image in the Photo app? And then check if that image already exists?
I am using the Photos Framework to create the custom album and save the photos.
Edit: Here is my code for creating the custom album and saving photos
/** Returns the first album from the photos app with the specified name. */
static func getAlbumWithName(name: String, completion: (album: PHAssetCollection?) -> Void) {
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "localizedTitle = %#", name)
let fetchResult = PHAssetCollection.fetchAssetCollectionsWithType(PHAssetCollectionType.Album, subtype: PHAssetCollectionSubtype.Any, options: fetchOptions)
if fetchResult.count > 0 {
guard let album = fetchResult.firstObject as? PHAssetCollection else {return}
completion(album: album)
} else {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(name)
}, completionHandler: { (result, error) in
if result {
FileUtils.getAlbumWithName(name, completion: completion)
} else {
completion(album: nil)
}
})
}
}
/** Adds an image to the specified photos app album */
private static func addImage(image: UIImage, toAlbum album: PHAssetCollection, completion: ((status: Bool) -> Void)?) {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
let assetPlaceholder = assetRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: album)
albumChangeRequest?.addAssets([assetPlaceholder!])
}) { (status, error) in
completion?(status: status)
}
}
All you need to do is read "localIdentifier" from the asset placeholder. I've augmented your code to return the identifier in the completion handler. You may like to deal with those optionals.
private static func addImage(image: UIImage, toAlbum album: PHAssetCollection, completion: ((status: Bool, identifier: String?) -> Void)?) {
var localIdentifier: String?
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
let assetPlaceholder = assetRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: album)
albumChangeRequest?.addAssets([assetPlaceholder!])
localIdentifier = assetPlaceholder?.localIdentifier
}) { (status, error) in
completion?(status: status, identifier: localIdentifier)
}
}
When you want to read that asset again your load image method might look something like this (I haven't used your conventions or variable names). This will read the asset synchronously but I'm sure you can spot the async option.
internal func loadPhoto(identifier: String) -> UIImage? {
if assetCollection == nil {
return nil
}
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "localIdentifier = %#", identifier)
let fetchResult = PHAsset.fetchAssetsInAssetCollection(assetCollection, options: fetchOptions)
if fetchResult.count > 0 {
if let asset = fetchResult.firstObject as? PHAsset {
let options = PHImageRequestOptions()
options.deliveryMode = .HighQualityFormat
options.synchronous = true
var result: UIImage?
PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight), contentMode: .AspectFit, options: options, resultHandler: {(image: UIImage?, _: [NSObject: AnyObject]?) -> Void in
result = image
})
return result
}
}
return nil
}

iOS : How to convert a video PHAsset to NSData

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

Resources