I use the method (a the end of this question) to retrieve video from the device. What it does, it finds the first video in the library, creates export session and exports the video into MOV file.
After two runs of the application (stopping the app between method runs), two resulting files are being compared. Both files are different. I was expecting that both files would be the same, as the same asset is being exported.
One more remark: running the method twice in the same application run gives me two identical files as expected.
Is it possible to make PhotoKit to export the same file every time it runs?
- (void)testVideoRetrievalSO {
PHAsset *oneVideo = [[PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeVideo options:nil] firstObject];
PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
options.networkAccessAllowed = YES;
options.deliveryMode = PHVideoRequestOptionsDeliveryModeHighQualityFormat;
options.version = PHVideoRequestOptionsVersionOriginal;
[[PHImageManager defaultManager] requestExportSessionForVideo:oneVideo
options:options
exportPreset:AVAssetExportPresetPassthrough
resultHandler:
^(AVAssetExportSession * _Nullable exportSession, NSDictionary * _Nullable info) {
NSLog(#"Video test run on asset %#", oneVideo.localIdentifier);
NSString *folderPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [[[NSUUID UUID] UUIDString] stringByAppendingPathExtension:#"mov"];
NSString *tempFile = [folderPath stringByAppendingPathComponent:fileName];
NSURL *tempFileUrl = [NSURL fileURLWithPath:tempFile];
[exportSession setOutputFileType:AVFileTypeQuickTimeMovie];
[exportSession setOutputURL:tempFileUrl];
[exportSession exportAsynchronouslyWithCompletionHandler:^{
NSLog(#"Video test run exported video into file: %#", tempFile);
}];
}];
}
UPDATED
It's not clear but I think exporting video from the camera roll does not guarantee fetching same video in every time. So I copied the video from camera roll to my document folder with url (avurlasset.URL) by [NSFileManager copyItemAtURL:toURL:error:] then it copies the same video file in every time. For now it is my final solution.
In this case you have to use requestAVAssetForVideo not requestExportSessionForVideo
So in your case,
PHVideoRequestOptions *options = [PHVideoRequestOptions new];
options.version = PHVideoRequestOptionsVersionOriginal;
[[PHImageManager defaultManager] requestAVAssetForVideo:asset
options:options
resultHandler:
^(AVAsset * _Nullable avasset,
AVAudioMix * _Nullable audioMix,
NSDictionary * _Nullable info)
{
NSError *error;
AVURLAsset *avurlasset = (AVURLAsset*) avasset;
// Write to documents folder
NSURL *fileURL = [NSURL fileURLWithPath:tmpShareFilePath];
if ([[NSFileManager defaultManager] copyItemAtURL:avurlasset.URL
toURL:fileURL
error:&error]) {
NSLog(#"Copied correctly");
}
}];
Related
I'm building an app in React Native, and I need to get an image from the Photo Library as base64 using the photo uri (e.g. photos://A2B9B28B-9A3E-4190-BCA0-B11F1E587085/L0/002). I was originally using the Asset Library but I've updated my app to use the new Photo Library and I'm struggling to get it to work.
My old Asset Library method was:
{
NSURL *url = [[NSURL alloc] initWithString:input];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
ALAssetRepresentation *rep = [asset defaultRepresentation];
CGImageRef imageRef = [rep fullScreenImage];
NSData *imageData = UIImagePNGRepresentation([UIImage imageWithCGImage:imageRef]);
NSString *base64Encoded = [imageData base64EncodedStringWithOptions:0];
callback(#[[NSNull null], base64Encoded]);
} failureBlock:^(NSError *error) {
NSLog(#"that didn't work %#", error);
callback(#[error]);
}];
}
It is basicly the same as the code you already have, it has just been split into two parts. First get a PHAsset using an id, then get the image from that asset using the PHImageManager.
To get the PHAsset from the library use
[+ (PHFetchResult<PHAsset *> *)fetchAssetsWithALAssetURLs:(NSArray<NSURL *> *)assetURLs options:(nullable PHFetchOptions *)options;
This method is synchronous and immediately returns an array of PHAsset but these assets don't contain any image data.
next use
[PHImageManager defaultManager] to get the image. This is asynchronous like the ALAssetsLibrary method. use - (PHImageRequestID)requestImageDataForAsset:(PHAsset *)asset options:(PHImageRequestOptions *)options resultHandler:(void (^)(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info))resultHandler;
The code should be something like this (obviously you have to fill in your error codes and error domain for your application)):
PHFetchResult* results = [PHAsset fetchAssetsWithLocalIdentifiers:#[input] options:nil];
PHAsset *asset = [results firstObject];
if (asset) {
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
if (imageData){
NSString *base64Encoded = [imageData base64EncodedStringWithOptions:0];
callback(#[[NSNull null], base64Encoded]);
}else{
NSError* error = info[PHImageErrorKey];
if (!error) {
error = [NSError errorWithDomain:#"" code:-1 userInfo:#{NSLocalizedDescriptionKey:#"image not found"}];
}
callback(nil, error);
}
}];
}else{
NSError* error = [NSError errorWithDomain:#"" code:-1 userInfo:#{NSLocalizedDescriptionKey:#"asset not found"}];
callback(nil, error);
}
You can see the api in PHAsset+ (PHFetchResult<PHAsset *> *)fetchAssetsWithALAssetURLs:(NSArray<NSURL *> *)assetURLs options:(nullable PHFetchOptions *)options;
How do I upload Live photos to a server? I am using AFNetworking for the upload of images, videos, and Slow motion videos. The upload of images and videos is very straightforward.
Upload of Images and Videos
//Physical location i.e. URL for video
PHVideoRequestOptions *options = [PHVideoRequestOptions new];
options.networkAccessAllowed = YES;
[[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if ([asset isKindOfClass:[AVURLAsset class]]) {
NSLog(#"%#",((AVURLAsset *)asset).URL);
}
}];
//Physical location i.e. URL for an image
[asset requestContentEditingInputWithOptions:nil
completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
NSURL *imageURL = contentEditingInput.fullSizeImageURL;
}];
These URLs are used for the upload of images and video Files.
// manager is of class AFHTTPSessionManager initialised with background session.
NSURLsession *uploadTask=[manager uploadTaskWithRequest:request
fromFile:[NSURL URLWithString:fdi.filePath]
progress:nil
completionHandler:nil];
Upload of Slow Motion file
Slow motion file is a combination of 2 or more video files. If you try to upload it like a normal video file then it would loose its slow motion feature. To work this out, first we have to create a slow motion file, then save it on the disk and then upload it.
PHVideoRequestOptions *options = [PHVideoRequestOptions new];
options.networkAccessAllowed = YES;
[[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if(([asset isKindOfClass:[AVComposition class]] && ((AVComposition *)asset).tracks.count == 2)){
//Added by UD for slow motion videos. See Here: https://overflow.buffer.com/2016/02/29/slow-motion-video-ios/
//Output URL
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = paths.firstObject;
NSString *myPathDocs = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"mergeSlowMoVideo-%d.mov",arc4random() % 1000]];
NSURL *url = [NSURL fileURLWithPath:myPathDocs];
//Begin slow mo video export
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
exporter.outputURL = url;
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.shouldOptimizeForNetworkUse = YES;
[exporter exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (exporter.status == AVAssetExportSessionStatusCompleted) {
NSURL *URL = exporter.outputURL;
self.filePath=URL.absoluteString;
NSURLsession *uploadTask=[manager uploadTaskWithRequest:request
fromFile:[NSURL URLWithString:self.filePath]
progress:nil
completionHandler:nil];
//Use above method or use the below one.
// NSData *videoData = [NSData dataWithContentsOfURL:URL];
//
//// Upload
//[self uploadSelectedVideo:video data:videoData];
}
});
}];
}
}];
How to upload a Live Photo?
A Live photo is combination of 2 files, *.jpg and *mov. We can neither upload it like a photo or video nor we can create a new file from the combination of 2 or more files like we did with the slow motion video because we have to send both the files of a live photo to the server.
It is clear that we have to send both the files to the server. But how to send multiple files in a single request.
Read a very interesting solution to this problem. It suggests that we do not need to upload both the *jpg and *.mov to the server for live photo. We can only upload the video and create image at the server from the mid point of video.
The Live Photo begins with the middle of the video - I just read this
video, find the middle, save the image.
We can also create a Live Photo from a video as the iPhone 6s does.
The 6s captures a short video with the photo and plays the video when
the photo is 3D touched. However, by using this method of creating a
PHLivePhoto and playing it using the PHLivePhotoView, we can get it to
work on any iPhone, such as my regular iPhone 6.
Source: See Here.
We can get the video URL of a live photo. See How to get extract video URL from a live photo.
But I am not sure whether this would be good solution for this problem. Can any tell me whether it is a good solution or not.
I am using the ctAssetPickerController for Picking videos and images. After the user has finished picking videos and images I want to copy them to another folder. I am using the export session and requestAvAssetForVideo method but the problem is it copies only one file at a time and I am not able to get the name of the file that the user is selecting, and it is copying videos only. How do I copy the images also? Below is my code.
- (void)assetsPickerController:(CTAssetsPickerController *)picker didFinishPickingAssets:(NSArray *)assets
{
PHImageManager *manager = [PHImageManager defaultManager];
[manager requestAVAssetForVideo:obj options:PHVideoRequestOptionsDeliveryModeAutomatic resultHandler:^(AVAsset * asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
NSString *imageName = #"img_o462.mp4";
NSString *selectedFilesCopyPath = [[self getRootFolderPath] stringByAppendingString:#"/Photos"];
if (![[NSFileManager defaultManager]fileExistsAtPath:selectedFilesCopyPath]) {
[[NSFileManager defaultManager]createDirectoryAtPath:selectedFilesCopyPath withIntermediateDirectories:NO attributes:nil error:nil];
}
NSString *copyPath = [selectedFilesCopyPath stringByAppendingString:[NSString stringWithFormat:#"/%#",imageName]];
// NSError *copyError= nil;
exporter.outputURL = [NSURL fileURLWithPath:copyPath];
exporter.outputFileType = AVFileTypeMPEG4;
[exporter exportAsynchronouslyWithCompletionHandler:^{
// here your file will be saved into file system at specified exportUrl
NSLog(#"file copied");
if (exporter.status == AVAssetExportSessionStatusCompleted) {
NSLog(#"success");
[self showErrorAlert:#"Success"];
} else {
NSLog(#"error: %#", [exporter error]);
[self showErrorAlert:exporter.error];
}
}];
}];
}
What is the best way to upload videos from gallery using Photos framework?
Before I used ALAssetRepresentation and next method:
- (NSUInteger)getBytes:(uint8_t *)buffer fromOffset:(long long)offset length:(NSUInteger)length error:(NSError **)error;
this allowed to upload file without first copying it to app temp directory. Don’t see any alternatives in Photos framework. Only way seems to use AVAssetExportSession -> export to local directory -> upload, but this requires additional storage space (could be a problem, if video is too big)
Seems the only valid way is to request AVAsset from PHImageManager, and check if returned asset is AVURLAsset. In this case URL can be used to directly access file and get needed chunk of bytes:
[[PHImageManager defaultManager] requestAVAssetForVideo:videoAsset options:nil resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if ([asset isKindOfClass:[AVURLAsset class]]) {
NSURL *URL = [(AVURLAsset *)asset URL];
// use URL to get file content
}
}];
This will not work with slow motion videos, because AVComposition instead of AVURLAsset is returned. Possible solution is to use PHVideoRequestOptionsVersionOriginal video file version:
PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
options.version = PHVideoRequestOptionsVersionOriginal;
[[PHImageManager defaultManager] requestAVAssetForVideo:videoAsset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if ([asset isKindOfClass:[AVURLAsset class]]) {
NSURL *URL = [(AVURLAsset *)asset URL];
// use URL to get file content
}
}];
And to get fullsize image url:
PHContentEditingInputRequestOptions *options = [[PHContentEditingInputRequestOptions alloc] init];
options.canHandleAdjustmentData = ^BOOL(PHAdjustmentData *adjustmentData) {
return YES;
};
[imageAsset requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
// use contentEditingInput.fullSizeImageURL
}];
The Photo Framework for iOS 8 is able to return a URL for images using the PHImageFileURLKey. However I can't seem to read this URL. I only need read access. So far, any place I attempt to use the URL given, it is treated as though the file doesn't exist at all. How can I get data using the URL?
Code that successfully returns PHAssets and a URL:
PHFetchResult* phFetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil]
PHImageManager *manager = [PHImageManager defaultManager];
PHImageRequestOptions* options = [[PHImageRequestOptions alloc] init];
options.synchronous = YES; // Force sequential work. We have nothing to do until this block returns.
PHAsset* asset = [phFetchResult objectAtIndex:index];
[manager requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options resultHandler:^(UIImage *resultImage, NSDictionary *info)
{
_image = resultImage;
NSURL* fileURL = [info objectForKey:#"PHImageFileURLKey"];
_imageLocation = [fileURL relativePath];
}];
Code showing nothing found:
NSURL* fileURL = [info objectForKey:#"PHImageFileURLKey"];
NSLog(#"File exist?: %d", [[NSFileManager defaultManager] fileExistsAtPath:[fileURL relativePath]]);
My current workaround is to take the UIImage given and save it to the /tmp directory and then use that URL. It's not an ideal solution and I'd really like to know the purpose of the PHImageFileURLKey
I research on PHImageFileURLKey and i think there is no way to find image data by PHImageFileURLKey but you can find your image data by using asset local identifier instead of PHImageFileURLKey.
My code for your reference:
You can use your asset's local identifier and will get the image in response.
PHFetchResult *savedAssets = [PHAsset fetchAssetsWithLocalIdentifiers:#[asset.localIdentifier] options:nil];
[savedAssets enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
//this gets called for every asset from its localIdentifier you saved
PHImageRequestOptions * imageRequestOptions = [[PHImageRequestOptions alloc] init];
imageRequestOptions.synchronous = YES;
imageRequestOptions.deliveryMode = PHImageRequestOptionsResizeModeFast;
[[PHImageManager defaultManager]requestImageForAsset:asset targetSize:CGSizeMake(50,50) contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
NSLog(#"get image from result");
}];
}];