iOS Photo Library read access - ios

After the user gave us permission to access his Camera Roll. We would like to grab the data and upload it to our services from inside our app.
Is there a way to access the video data from the file? The only way to open the video file is to create an AVAsset. But that's not enough for me.
I'm aware off
func requestExportSessionForVideo(_ asset: PHAsset!,
options options: PHVideoRequestOptions!,
exportPreset exportPreset: String!,
resultHandler resultHandler: ((AVAssetExportSession!,
[NSObject : AnyObject]!) -> Void)!) -> PHImageRequestID
But in my case I just want to upload the video to our service I don't want to do:
first copy the video by doing an export into my local app data
and then send that data up to our service.
delete the data.
This approach above uses a lot of extra space and time and users with full 16GB iPhones it doesn't work well.
Ok this is what I tried so far using the URL
var anAcces = sourceURL?.startAccessingSecurityScopedResource
if !NSFileManager.defaultManager().fileExistsAtPath(sourceURL!.path!) {
NSLog("not exist")
}
var aFileCoordinator = NSFileCoordinator(filePresenter:nil)
var anError: NSError?
aFileCoordinator.coordinateReadingItemAtURL(sourceURL!, options:.ForUploading, error:&anError, byAccessor: { (newURL: NSURL!) -> Void in
var data = NSData(contentsOfURL: newURL)
})
if let unError = anError {
NSLog("Error \(unError)")
}
sourceURL?.stopAccessingSecurityScopedResource
This logs the following:
2015-02-08 16:20:01.947 Cameo[15706:2288691] not exist
2015-02-08 16:20:01.991 Cameo[15706:2288691] Error Error Domain=NSCocoaErrorDomain Code=257 "The operation couldn’t be completed. (Cocoa error 257.)" UserInfo=0x170876480 {NSURL=file:///var/mobile/Media/DCIM/100APPLE/IMG_0155.MOV, NSFilePath=/var/mobile/Media/DCIM/100APPLE/IMG_0155.MOV, NSUnderlyingError=0x17005a9a0 "The operation couldn’t be completed. Operation not permitted"}

Thanks to Paul suggestion I figured it out:
You have to create an PHImageManager requestAVAssetForVideo session in that block you have access to the file and read its data from the url.
let imageManager = PHImageManager.defaultManager()
let videoRequestOptions = PHVideoRequestOptions()
videoRequestOptions.deliveryMode = .HighQualityFormat
videoRequestOptions.version = .Current
videoRequestOptions.networkAccessAllowed = true
videoRequestOptions.progressHandler = { (progress: Double, error: NSError!, stop: UnsafeMutablePointer<ObjCBool>, [NSObject : AnyObject]!) -> Void in
NSLog("Progress: %#", progress.description)
}
videoRequestOptions.progressHandler = { (progress: Double, error: NSError!, stop: UnsafeMutablePointer<ObjCBool>, [NSObject : AnyObject]!) -> Void in
NSLog("Progress: %#", progress.description)
}
imageManager.requestAVAssetForVideo(nextAsset, options: videoRequestOptions, resultHandler: { (avAsset: AVAsset!, avAudioMix: AVAudioMix!, info: [NSObject : AnyObject]!) -> Void in
if let nextURLAsset = avAsset as? AVURLAsset {
let sourceURL = nextURLAsset.URL
if NSFileManager.defaultManager().fileExistsAtPath(sourceURL.path!) {
NSLog("exist file")
}
var data = NSData(contentsOfURL: sourceURL)
if let aData = data {
NSLog("length : <\(aData.length)")
}
else {
NSLog("no data read.")
}
}
}

Regarding the issue:
Failed to issue sandbox extension for file file:///var/mobile/Media/DCIM/100APPLE/IMG_0730.MOV, errno = 1
My workaround for this issue was to create a temporary path which I was able to access the the Media-File:
Future<void> loadAssets() async {
List<Asset> resultList = <Asset>[];
String error = 'No Error Detected';
final temp = await Directory.systemTemp.create();
List<File> imagesFileList = [];
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 300,
enableCamera: true,
selectedAssets: imagesAssetsList,
cupertinoOptions: CupertinoOptions(takePhotoIcon: "chat"),
materialOptions: MaterialOptions(
actionBarColor: "#abcdef",
actionBarTitle: "Example App",
allViewTitle: "All Photos",
useDetailsView: false,
selectCircleStrokeColor: "#000000",
),
);
} on Exception catch (e) {
error = e.toString();
}
if (!mounted) return;
for (int i = 0; i < resultList.length; i++) {
final data = await resultList[i].getByteData();
imagesFileList.add(await File('${temp.path}/img$i').writeAsBytes(
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes)));
print('pathnew: ${imagesFileList[i].path}');
await uploadFileToStorage(imagesFileList[i].path);
}
setState(() {
imagesAssetsList = resultList;
_error = error;
});
}
I hope it will work!

Related

Export of Video file using PHImageManager fails:AVFoundationErrorDomain Code=-11800 "The operation could not be completed" Code=-12212

I am trying to export a video asset from camera roll on a simulator using PHImageManager from the Photos iOS SDK. When the export completion block executes, it results in this error:
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedFailureReason=An unknown error occurred (-12212), NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x600001baa4c0 {Error Domain=NSOSStatusErrorDomain Code=-12212 "(null)"}}
This happens every time with the specified video on the simulator's camera roll. However, this inconsistently occurs on a real device; some videos exporting fine, some not.
Why is this happening, how to fix it, and where can I find the error code documentation?
Here is the function it happens in:
public func exportVideoFile(
options: PHVideoRequestOptions? = nil,
outputURL: URL? = nil,
outputFileType: AVFileType = .mov,
progressBlock: ((Double) -> Void)? = nil,
completionBlock: #escaping ((URL, String) -> Void)
) {
guard
let phAsset = self.phAsset,
phAsset.mediaType == .video,
let writeURL = videoFilename(phAsset: phAsset),
let mimetype = MIMEType(writeURL)
else { return }
var requestOptions = PHVideoRequestOptions()
if let options = options {
requestOptions = options
} else {
requestOptions.isNetworkAccessAllowed = true
requestOptions.deliveryMode = .fastFormat
}
requestOptions.progressHandler = { progress, _, _, _ in
DispatchQueue.main.async {
debugPrint("progress", progress)
progressBlock?(progress)
}
}
PHImageManager.default().requestExportSession(
forVideo: phAsset, options: requestOptions,
exportPreset: AVAssetExportPreset1280x720
) { session, _ in
guard let session = session else { return }
session.outputURL = writeURL
session.outputFileType = outputFileType
session.shouldOptimizeForNetworkUse = true
session.exportAsynchronously {
completionBlock(writeURL, mimetype)
if let err = session.error { // Error happens here
debugPrint("Video Export Session Error: \(err.localizedDescription)")
} else {
debugPrint("Video Export Session Status: \(session.status)")
}
}
}
}
Solution:
I found out the error code "-12212" refers to kVTColorCorrectionPixelTransferFailedErr.
So I thought it's got to be a preset or quality setting issue...
I had previously tried setting requestOptions.deliveryMode = .highQualityFormat but that did not work. However, I noticed the following call had a preset for quality:
PHImageManager.default().requestExportSession(
forVideo: phAsset, options: requestOptions,
exportPreset: AVAssetExportPreset1280x720
)
When I changed it to this:
PHImageManager.default().requestExportSession(
forVideo: phAsset, options: requestOptions,
exportPreset: AVAssetExportPresetHighestQuality
)
It worked!

How to download a file from a AWS S3 bucket in iOS?

In my iOS app, I try to download a file from an AWS S3 bucket. Here is what I tried:
I initialize AWSMobileClient:
import AWSMobileClient
import AWSS3
let configuration = AWSServiceConfiguration(
region: AWSRegionType.EUCentral1,
credentialsProvider: AWSMobileClient.default())
AWSServiceManager.default().defaultServiceConfiguration = configuration
AWSMobileClient.default().initialize { (userState: UserState?, error: Error?) in
if (userState != nil)
{
print("Initialize OK : \(userState.debugDescription)")
}
if (error != nil)
{
print("Initialize error: \(String(describing: error))")
}
}
I got:
"initialize OK : Optional(AWSMobileClient.UserState.guest)"
Now I try to download a file:
let expression = AWSS3TransferUtilityDownloadExpression()
expression.progressBlock = {(task, progress) in DispatchQueue.main.async(execute: {
print("Progress : \(progress)")
})
}
let completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock = {
(task: AWSS3TransferUtilityDownloadTask, url: URL?, data: Data?, error: Error?) -> Void in
DispatchQueue.main.async(execute: {
print("End download 1")
})
}
let fileManager = FileManager.default
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("100.ogg")
let transferUtility: AWSS3TransferUtility = AWSS3TransferUtility.default()
transferUtility.download(
to: fileURL,
bucket: "my-s3-bucket",
key: "100.ogg",
expression: expression,
completionHandler: completionHandler).continueWith { (task) -> AnyObject? in
if let error = task.error {
print("Error download : \(error)")
}
if let result = task.result {
print("Result : \(result.debugDescription)")
}
print("End download 2 : \(fileManager.fileExists(atPath: fileURL.absoluteString))")
return nil
}
I got:
"Result : <AWSS3TransferUtilityDownloadTask: 0x6000020bd4d0>
"End download 2 : false"
I don't get any progress, and I also don't get the "End download 1"
So basically, I dont get any error, but it does look like nothing has been downloaded. Also, on a side note, it works well with the Android version of my app, so it's very likely that there is an error in my code.
So what should I change to make it work?
Thanks.
My bad, the example above is actually working, But I had to:
change fileManager.fileExists(atPath: fileURL.absoluteString)) by fileManager.fileExists(atPath: fileURL.path))
check if file exists in the first completionHandler (where I wrote print("End download 1"))

How to download A LOT of files from S3 using the transfer utility?

I have several thousand images I want to download from a S3 bucket to an iOS App.
But I'm getting memory issues I'm unable to track down.
Here is my sketchy code:
let client = HttpClient<[SomeImage]>()
client.get(fromURL: URL(string: endpoint)!) {
(result, error) in
if let error = error {
self.log(message: "\(error)", level: .error)
return
}
if let result = result {
let downloadGroup = DispatchGroup()
var count = 0
// just assembling a list of s3 keys to download here...
for item in result {
for image in (item.images ?? []) {
let prefix = "\(image.key)/"
for key in ["\(globalGetThumbnailS3Key(byImageKey: image.key))",
"\(globalGetPreviewS3Key(byImageKey: image.key))"] {
count = count + 1
let completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock = {
(task, URL, data, error) in
if let error = error {
self.log(message: "\(error)", level: .error)
return
}
if let data = data, let localDir = FileManager.default.applicationSupportURL {
do {
let imageURL = localDir.appendingPathComponent(key)
FileManager.default.directoryExistsOrCreate(localDir.appendingPathComponent(prefix))
try data.write(to: imageURL)
self.log(message: "downloaded \(prefix)\(key) to \(imageURL.absoluteString)", level: .verbose)
} catch let error {
self.log(message: "\(error)", level: .error)
return
}
}
}
bgSyncQueue.async(group: downloadGroup) {
self.transferUtility.downloadData(fromBucket: "\(globalDerivedImagesBucket)", key: key,
expression: nil,
completionHandler: completionHandler).continueWith {
(task) in
if let error = task.error {
// iirc, this error is caused, if the task couldnt be created due to being offline
self.log(message: "\(error)", level: .error)
return nil
}
if let result = task.result {
// do something with the task?
return nil
}
return nil
}
}
}
}
}
self.log(message: "\(count) images to download...", level: .debug)
bgSyncQueue.activate()
downloadGroup.notify(queue: DispatchQueue.main) {
self.log(message: "All items downloaded?!")
}
}
}
}
So I put all calls to the transfer utility in a serial dispatch queue, which is initially inactive. Then I activate the queue and downloading starts just fine. But after a while the app crashes with "Message from debugger: Terminated due to memory issue."
The app is only consuming about 100M of memory though. What am I overlooking?
Rob's suggestion to use the "downloadToUrl" method was the way to go, without using GCD on my part. Thanks again, Rob!
The transferUtility seems to be a fine tool, though very badly documented.
Here is the simple code used to download about 20k of images:
for key in keys {
let imageURL = localDir.appendingPathComponent(key.1)
let completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock = {
(task, URL, data, error) in
if let error = error {
self.log(message: "failed downloading \(key.1): \(error)", level: .error)
DispatchQueue.main.async {
countingDown()
}
return
}
DispatchQueue.main.async {
countingDown()
if let onProgress = self.onProgress {
onProgress(100.0 - ((100.0 / Double(total)) * Double(count)))
}
}
//self.log(message: "downloaded \(key.1)")
}
transferUtility.download(to: imageURL, bucket: "\(globalDerivedImagesBucket)", key: key.1, expression: nil, completionHandler: completionHandler).continueWith {
(task) in
if let error = error {
self.log(message: "\(error)", level: .error)
DispatchQueue.main.async {
countingDown()
}
return nil
}
return nil
}
}
You may need to consider using an autoreleasepool to better manage the memory used by the bridged data types as detailed here
Exert from article (in case of link changes)
Consider the code:
func run() {
guard let file = Bundle.main.path(forResource: "bigImage", ofType: "png") else {
return
}
for i in 0..<1000000 {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
}
Even though we’re in Swift, this will result in the same absurd memory spike shown in the Obj-C example! This is because the Data init is a bridge to the original Obj-C [NSDatadataWithContentsOfURL] -- which unfortunately still calls autorelease somewhere inside of it. Just like in Obj-C, you can solve this with the Swift version of #autoreleasepool; autoreleasepool without the #:
autoreleasepool {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
Disclaimer: I am no expert in Swift or Objective-C advanced memory management but I have used this in a similar scenario with good results.

Loading images from external storage using Core Graphics not working iOS 13

I am attempting to load photos located on external storage (SD card) using core graphics in iOS 13 (beta). The code below works fine when the files are on the device. When the files are on external storage however it fails returning nil and I don't know why.
I believe I am using the correct security scoping.
I loaded the file URLs from a security scoped folder url as per Providing Access to Directories
guard folderUrl.startAccessingSecurityScopedResource() else {
return nil
}
defer { folderUrl.stopAccessingSecurityScopedResource() }
guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, options) else {
throw Error.failedToOpenImage(message: "Failed to open image at \(imageURL)")
}
So... for my own project, where I ran into the same issue, I now have the following function to give me a thumbnail, going from elegant and quick to brute force.
static func thumbnailForImage(at url: URL, completion: (Result<UIImage, Error>) -> Void) {
let shouldStopAccessing = url.startAccessingSecurityScopedResource()
defer { if shouldStopAccessing { url.stopAccessingSecurityScopedResource() } }
let coordinator = NSFileCoordinator()
var error: NSError?
coordinator.coordinate(readingItemAt: url, options: .withoutChanges, error: &error) { url in
var thumbnailImage: UIImage?
var storedError: NSError?
var imageSource: CGImageSource?
print("Strategy 1: Via URL resource key")
do {
let resourceKeys = Set([URLResourceKey.thumbnailDictionaryKey])
let resources = try url.resourceValues(forKeys: resourceKeys)
if let dict = resources.thumbnailDictionary, let resource = dict[URLThumbnailDictionaryItem.NSThumbnail1024x1024SizeKey] {
thumbnailImage = resource
} else {
throw "No thumbnail dictionary"
}
} catch let error {
storedError = error as NSError
}
let options = [kCGImageSourceCreateThumbnailFromImageIfAbsent: true, kCGImageSourceShouldAllowFloat: true, kCGImageSourceCreateThumbnailWithTransform: true]
if thumbnailImage == nil {
print("Strategy 2: Via CGImageSourceCreateWithURL")
imageSource = CGImageSourceCreateWithURL(url as CFURL, options as CFDictionary)
}
if thumbnailImage == nil && imageSource == nil {
print("Strategy 3: Via CGImageSourceCreateWithData")
let data = try? Data.init(contentsOf: url)
if let data = data {
imageSource = CGImageSourceCreateWithData(data as CFData, options as CFDictionary)
}
}
if let imageSource = imageSource, thumbnailImage == nil {
print("Attempting thumbnail creation from source created in strategy 2 or 3")
if let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) {
thumbnailImage = UIImage(cgImage: image)
}
}
if let thumbnailImage = thumbnailImage {
print("Success")
completion(.success(thumbnailImage))
} else {
print("Failure")
if let error = storedError { completion(.failure(error)) }
else { completion(.failure("Everything just fails...")) }
}
}
if let error = error { completion(.failure(error)) }
}
Basically it works by trying to get a thumbnail via the URL resources first. This is the quickest and nicest way, of it works. If that fails, I try CGImageSourceCreateWithURL. That works most of the time, except on remote storage. I suspect that's still a bug and submitted a feedback ticket to apple for this. I suggest you do the same. Last attempt, just try to read the entire file using NSData and creating an image source via CGImageSourceCreateWithData...
So far, if it's an image file I, this seems to produce a thumbnail most of the time. It can be quite slow though, having to read the entire file.

Retrieve video data from local file system

Im creating a simple app to upload a recorded video to a web API. After recording the video in app I can successfully play the video within the app, but I'm struggling to access the video to upload it.
The video is at assets-library://asset/asset.MOV?id=3AFCEC9B-17DE-4D75-9B87-0AD50BAB9BFF&ext=MOV, which can be loaded using MPMoviePlayerController(contentURL: url) so I know it exists there.
I've tried the following few methods with no success:
Method 1
let url = NSURL(fileURLWithPath: thisNote.url!)
println("This url = \(thisNote.url)")
let videoData = NSData(contentsOfURL: url!, options: nil, error: &e)
error = The operation couldn’t be completed. No such file or directory
Method 2
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let docsDir = dirPaths[0] as! String
let videoFilePath = docsDir.stringByAppendingPathComponent(Constants.Directories.appFolder)
let filePath = NSBundle.pathForResource(thisNote.fileName, ofType: "video/quicktime", inDirectory: Constants.Directories.appFolder)
filepath = nil
Method 3
var video:NSData = NSData()
ALAssetsLibrary().assetForURL(url, resultBlock: { (asset : ALAsset!) -> Void in
if let rep : ALAssetRepresentation = asset.defaultRepresentation(){
var error: NSError?
let length = Int(rep.size())
let from = Int64(0)
let data = NSMutableData(length: length)!
let numRead = rep.getBytes(UnsafeMutablePointer(data.mutableBytes), fromOffset: from, length: length, error: &error)
video = data
self.uploadNote(video, note: thisNote)
}
}){ (error : NSError!) -> Void in
println("Asset library error: \(error)")
}
The result block is not reached, nor is the error printed.
Any help on this would be appreciated.
Method three was the closest to the final solution. I ended up enumerating through the asset library to find the right video file. Whilst I'm sure this is not the most efficient way to get the video, it works. Here is the code I used, I hope it helps someone......
let url = NSURL(string: thisNote.url!)
var video:NSData = NSData()
AssetLibraryHelper.sharedInstance.assetForURL(url, resultBlock: { (foundAsset : ALAsset!) -> Void in
var asset:ALAsset?
if foundAsset == nil {
// Workaround: Enumerate over the asset library to find matching URL
AssetLibraryHelper.sharedInstance.enumerateGroupsWithTypes(
ALAssetsGroupType(ALAssetsGroupPhotoStream),
usingBlock: { (group: ALAssetsGroup?, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
if group != nil {
group!.enumerateAssetsWithOptions(NSEnumerationOptions.Reverse, usingBlock: {(enumeratedAsset: ALAsset!, index: Int, stopGroup: UnsafeMutablePointer<ObjCBool>) -> Void in
if enumeratedAsset != nil {
asset = enumeratedAsset
stop.initialize(true)
stopGroup.initialize(true)
}
})
}
}, failureBlock: { (error: NSError!) -> Void in
print("Error enumerating assets \(error)")
})
}
else {
asset = foundAsset
}
print("Asset is \(asset)")
if let rep : ALAssetRepresentation = asset!.defaultRepresentation(){
let image = UIImage(CGImage: rep.fullResolutionImage().takeUnretainedValue())
let imageData = UIImageJPEGRepresentation(image, 0.7)
var error: NSError?
let length = Int(rep.size())
let from = Int64(0)
let data = NSMutableData(length: length)!
let _ = rep.getBytes(UnsafeMutablePointer(data.mutableBytes), fromOffset: from, length: length, error: &error)
video = data
self.uploadNote(video, note: thisNote, imageFile: imageData!)
}
}){ (error : NSError!) -> Void in
print("Asset library error: \(error)")
}

Resources