This below code is fired when I press delete button after selecting messages I want to delete in chat room.
- (void)deleteButtonPressed:(id)sender {
if (arrayToDelete.count) {
for (NSString *str in arrayToDelete) {
NSLog(#"msgID --> %#",str);
[self.chatModel.dataSource removeObject:str]; //??? Remove data from the screen
[[FMDBManager sharedInstance] deleteMessageByMessageId:str]; //??? Delete data from database
}
[arrayToDelete removeAllObjects];
[self.chatTableView reloadData];
}
}
This line successfully removes selected messages from the chat room.
[self.chatModel.dataSource removeObject:str]; //??? Remove data from the screen
When I go out the chat room and re-enter, those messages still exist, so I have this line below.
[[FMDBManager sharedInstance] deleteMessageByMessageId:str]; //??? Delete data from database
I think the above line should delete those selected messages from the database but when I re-enter the chat room I still see those messages. Here below are related code to that.
- (void)deleteMessageByMessageId:(NSString *)messageId {
FMDatabase *db = [self getterDataBase];
[db open];
NSString *sqlString = [NSString stringWithFormat:#"DELETE FROM message WHERE messageId = '%#'",messageId];
BOOL status = [db executeUpdate:sqlString];
NSLog(#"Delete MessageById:%# Status:%d",messageId,status);
[db close];
}
I've found that when chat room calls viewDidLoad it will eventually call the method callBackGetChannelLogNew where server will sync-up data with chat room tableview and local database.
- (void)callBackGetChannelLogNew:(NSDictionary *)resultDataDic status:(enumAPI_STATUS)eAPI_STATUS {
if (isFirstTimeUpdate) {
}
if (eAPI_STATUS == API_STATUS_SUCCEE) {
NSString *readString=[NSString stringWithFormat:#"%#",resultDataDic[#"read_arr"]];
if ([readString isEqualToString:#""]) {
// NSLog(#"read_arr is empty");
}
else {
NSArray *read_arr=resultDataDic[#"read_arr"];
// Copy read_arr
self.readArray=[read_arr mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
[self dealWithReadArray:read_arr];
});
}
NSArray *data = [resultDataDic objectForKey:#"msg"];
if (data.count > 0) {
apiHaveData = YES;
} else {
apiHaveData = NO;
self.loadIngView.hidden = YES;
isLoadingData = NO;
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
// Reverse order of data
NSArray* reversedArray = [[data reverseObjectEnumerator] allObjects];
NSMutableArray *messageFromOtherArray = [NSMutableArray new];
NSMutableArray *messageAllArray = [NSMutableArray new];
for (int i = 0; i < reversedArray.count; i++) {
NSDictionary *_dic = reversedArray[i];
NSString *fromId = [_dic objectForKey:#"fid"];
NSString *message = [NSString stringWithFormat:#"%#",[_dic objectForKey:#"say"]];
if ([ObjectManager getChatMessageKindWithString:message] == MessageTypeText) {
message = [ObjectManager decryptWithString:message];
}
NSString *messageId = [_dic objectForKey:#"mid"];
NSString *toId = [_dic objectForKey:#"tid"];
NSDateFormatter *_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = #"yyyy-MM-dd HH:mm:ss.SSS";
NSDate *date_t = [NSDate dateWithTimeIntervalSince1970:[[_dic objectForKey:#"t"] doubleValue]/1000.0]; //換算成日期
NSString *stringDate = [_formatter stringFromDate:date_t];
NSString *sendDate = stringDate;
NSString *lid = _dic[#"lid"];
NSMutableDictionary *myDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
fromId,#"fromId",
message,#"message",
messageId,#"messageId",
sendDate,#"sendDate",
toId,#"toId",
lid,#"lid",
nil];
NSString *isRead;
if (_chatRoomType == ChatRoomTypePrivate) {
if ([_dic[#"r"] intValue]) {
isRead = #"1";
myDic[#"isRead"] = isRead;
lastReadMessageId = [NSString stringWithFormat:#"%#",messageId];
}
}
if (i == 0) {
if (lidForAPI != [_dic[#"lid"] intValue]) {
lidForAPI = [_dic[#"lid"] intValue];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
apiHaveData = NO;
self.loadIngView.hidden = YES;
isLoadingData = NO;
});
return ;
}
}
if (![myDic[#"fromId"] isEqualToString:[User sharedUser].account]) {
[messageFromOtherArray addObject:myDic];
}
if (_chatRoomType == ChatRoomTypeGroup) {
[myDic setObject:#"1" forKey:#"isGroupMessage"];
}
[myDic setObject:#"1" forKey:#"did_I_Read"];
[messageAllArray addObject:myDic];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self setupViewWithMessageArray:messageAllArray]; //???? Here server sync-up data with tableview
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
if (_chatRoomType == ChatRoomTypePrivate) {
if (messageFromOtherArray.count > 0 && isUplaodLastRead == NO) {
isUplaodLastRead = YES;
NSDictionary *lastReadMsgDic = messageFromOtherArray.lastObject;
[self callMsgReadAPI:lastReadMsgDic];
}
} else {
if (messageAllArray.count > 0 && isUplaodLastRead == NO) {
isUplaodLastRead = YES;
NSDictionary *lastReadMsgDic = messageAllArray.lastObject;
[self callMsgReadAPI:lastReadMsgDic];
}
}
self.chatModel.channelTopic = _topic;
NSArray *read_arr=resultDataDic[#"read_arr"];
[self dealMySendMessageReadedWithReadArray:read_arr AndMessageArray:messageAllArray];
[self saveMessageWithArray:messageAllArray]; //???? Here server sync-up data with local db
});
});
}
}
This lines will sync-up data from server to tableview
dispatch_async(dispatch_get_main_queue(), ^{
[self setupViewWithMessageArray:messageAllArray]; //???? Here server sync-up data with tableview
});
Here below is the method setupViewWithMessageArray
- (void)setupViewWithMessageArray:(NSArray *)messageAllArray {
if (!isFirstTimeUpdate) {
isFirstTimeUpdate = YES;
self.chatModel.dataSource = nil;
[self.chatTableView reloadData];
self.chatModel.dataSource = [[NSMutableArray alloc] init];
[self addMessageWithArray:messageAllArray];
[self.chatTableView reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.chatModel.dataSource.count-1 inSection:0];
[self.chatTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
} else {
[self addMessageWithArray:messageAllArray];
[self reloadTableViewWithoutMove];
}
self.loadIngView.hidden = YES;
isLoadingData = NO;
if (_chatRoomType == ChatRoomTypePrivate) {
if (lastReadMessageId) {
[self.chatModel setPrivateChatListAllReadFormMessageId:lastReadMessageId];
}
}
}
This line will sync-up data from server to local db
[self saveMessageWithArray:messageAllArray]; //???? Here server sync-up data with local db
Here below is the method saveMessageWithArray
- (void)saveMessageWithArray:(NSArray *)messageArray {
for (NSDictionary *myDic in messageArray) {
if (![[FMDBManager sharedInstance] didMessageExistWithMessageID:[myDic objectForKey:#"messageId"]]) {
[[FMDBManager sharedInstance] SaveMessage:myDic];
}
else {
NSString *mid=[NSString stringWithFormat:#"%#",myDic[#"messageId"]];
NSString *isRead = myDic[#"isReaed"];
if (isRead) {
[[FMDBManager sharedInstance] UpdateisReadWithMessageID:mid];
}
}
}
}
So I think now my question is how I can update messageAllArray with arrayToDelete before server sync-up?
Here is a function of progressbar. In the sdk v1 I can use "S3Objectsummary" to know the summary of the the file, but in the sdk v2 i can not found the "S3Objectsummary".
Which one is the similar one in the v2? If any one can show an example that will be great.
Also, i have the same question with
S3GetObjectRequest/S3GetObjectResponse/S3PutObjectRequest/AmazonClientException
Code is in the sdk ios v1:
-(AmazonS3Client *)s3{
[self validateCredentials];
return s3;}
-(void)validateCredentials{
NSLog(#"validating credentials.");
if (s3 == nil) {
[self clearCredentials];
s3 = [[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY];
}
}
-(void)setProgressBar{
[delegate setProgressStatus:progPercent];
}
-(void)downloadPlists{
#try {
NSArray *Plists = [[self s3] listObjectsInBucket:#"~~~~"];
float numfile = 1;
float totalfiles = [Plists count];
for (S3ObjectSummary *file in Plists) {
float percent = numfile/totalfiles;
progPercent = [NSNumber numberWithFloat:percent];
[self performSelectorOnMainThread:#selector(setProgressBar) withObject:progPercent waitUntilDone:YES];
numfile++;
NSString *key = [file key];
NSLog(#"key: %#", key);
if ([key rangeOfString:#".plist"].location != NSNotFound) {
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistFilePath = [NSString stringWithFormat:#"%#/Plists/%#",docDir, key];
NSLog(#"plistFilePath: %#", plistFilePath);
S3GetObjectRequest *plist = [[S3GetObjectRequest alloc] initWithKey:key withBucket:#"~~~~~"];
S3GetObjectResponse *getObjectResponse = [[self s3] getObject:plist];
NSData *data2 = [NSData dataWithData: getObjectResponse.body];
NSString *courseFilePath = [plistFilePath substringToIndex:[plistFilePath rangeOfString:#"/" options:NSBackwardsSearch].location];
bool testDirectoryCreated = [[NSFileManager defaultManager]createDirectoryAtPath: courseFilePath
withIntermediateDirectories: YES
attributes: nil
error: NULL];
if (!testDirectoryCreated)
NSLog(#"error creating test directory.");
if (![data2 writeToFile:plistFilePath atomically:YES])
NSLog(#"error writing to path.");
}
}
}
#catch (NSException *exception) {
UIAlertView *failureAlert = [[UIAlertView alloc] initWithTitle:#"Oops!" message:[NSString stringWithFormat: #"There was an error performing this operation. Please try again later. Error: %#", exception] delegate:nil cancelButtonTitle:#"Okay" otherButtonTitles: nil];
[failureAlert show];
}
}
I try to do the same thing in v2 in the code as follow, is the code right?
-(void)downloadPlists
{
AWSS3 *s3 = [AWSS3 defaultS3];
AWSS3ListObjectsRequest *listObjectReq=[AWSS3ListObjectsRequest new];
listObjectReq.bucket=#"PLists";
[[[s3 listObjects:listObjectReq] continueWithBlock:^id(BFTask *task) {
if(task.error){
NSLog(#"the request failed. error %#",task.error);
}
if(task.result){
AWSS3ListObjectsOutput *listObjectsOutput=task.result;
NSArray *Plists = task.result; //Is the result of task in listObjectOutput a NSArray?
float numfile = 1;
float totalfiles = [Plists count];
for(AWSS3Object *file in listObjectsOutput.contents){
float percent = numfile/totalfiles;
progPercent = [NSNumber numberWithFloat:percent];
[self performSelectorOnMainThread:#selector(setProgressBar) withObject:progPercent waitUntilDone:YES];
numfile++;
NSString *key = [file key];
NSLog(#"key: %#", key);
if ([key rangeOfString:#".plist"].location != NSNotFound) {
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistFilePath = [NSString stringWithFormat:#"%#/Plists/%#",docDir, key];
NSLog(#"plistFilePath: %#", plistFilePath);
AWSS3TransferManager *transferManager = [AWSS3TransferManager defaultS3TransferManager];
AWSS3TransferManagerDownloadRequest *downloadRequest = [AWSS3TransferManagerDownloadRequest new];
downloadRequest.bucket = #"PLists";
downloadRequest.key = key;
//downloadRequest.downloadingFileURL=[NSURL fileURLWithPath: #"???"]; I'm not sure the path. In sdk V1 there is no URL ?
[[transferManager download: downloadRequest] continueWithBlock:^id(BFTask *task) {
if (task.error) {
UIAlertView *failureAlert = [[UIAlertView alloc] initWithTitle:#"Oops!"
message:[NSString stringWithFormat: #"There was an error performing this operation. Please try again later. Error: %#", task.error]
delegate:nil
cancelButtonTitle:#"Okay"
otherButtonTitles: nil];
[failureAlert show];
}
if (task.result) {
AWSS3TransferManagerDownloadOutput *downloadOutput = task.result;
NSData *data2 = [NSData dataWithData: downloadOutput.body];
NSString *courseFilePath = [plistFilePath substringToIndex:[plistFilePath rangeOfString:#"/" options:NSBackwardsSearch].location];
bool testDirectoryCreated = [[NSFileManager defaultManager]createDirectoryAtPath: courseFilePath
withIntermediateDirectories: YES
attributes: nil
error: NULL];
if (!testDirectoryCreated)
NSLog(#"error creating test directory.");
if (![data2 writeToFile:plistFilePath atomically:YES])
NSLog(#"error writing to path.");
}
return nil;
}];
}
}
return nil;
}
return nil;
}] waitUntilFinished]; //In the integration test still use the "waitUntilFinisher".But in the "Working with BFTask" said the continueWithBolck won't execute until the previous asychronous call has already finished exceuting?
}
AWSS3Object in v2 is equivalent to S3ObjectSummary in v1.
You are not invoking - listObjects:, so your v2 code snippet does not work. You should take a look at the integration test as an example. Note that you should avoid calling - waitUntilFinished in your production app. See Working with BFTask for further details.
I set the shutter speed, ISO and other camera parameters and capture an image using the code below. If I set the ISO to something like 119, it gets reported as 125 in the Exif info (it is always rounded to a standard value). How can I tell what the real ISO was? The maxISO gets reported as 734 but if I set it to 734 it is 800 in the Exif information. I had the same problem with the exposure time. If I set it to 800ms it shows up as 1s in the but the value calculated from the ShutterSpeedValue is correct.
- (void)setCameraSettings:(long)expTime1000thSec iso:(int)isoValue
{
if ( currentCaptureDevice ) {
[captureSession beginConfiguration];
NSError *error = nil;
if ([currentCaptureDevice lockForConfiguration:&error]) {
if ([currentCaptureDevice isExposureModeSupported:AVCaptureExposureModeLocked]) {
CMTime minTime, maxTime, exposureTime;
if ( isoValue < minISO ) {
isoValue = minISO;
} else if ( isoValue > maxISO ) {
isoValue = maxISO;
}
exposureTime = CMTimeMake(expTime1000thSec, EXP_TIME_UNIT); // in 1/EXP_TIME_UNIT of a second
minTime = currentCaptureDevice.activeFormat.minExposureDuration;
maxTime = currentCaptureDevice.activeFormat.maxExposureDuration;
if ( CMTimeCompare(exposureTime, minTime) < 0 ) {
exposureTime = minTime;
} else if ( CMTimeCompare(exposureTime, maxTime) > 0 ) {
exposureTime = maxTime;
}
NSLog(#"setting exp time to %lld/%d s (want %ld) iso=%d", exposureTime.value, exposureTime.timescale, expTime1000thSec, isoValue);
[currentCaptureDevice setExposureModeCustomWithDuration:exposureTime ISO:isoValue completionHandler:nil];
}
if (currentCaptureDevice.lowLightBoostSupported) {
currentCaptureDevice.automaticallyEnablesLowLightBoostWhenAvailable = NO;
NSLog(#"setting automaticallyEnablesLowLightBoostWhenAvailable = NO");
}
// lock the gains
if ([currentCaptureDevice isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeLocked]) {
currentCaptureDevice.whiteBalanceMode = AVCaptureWhiteBalanceModeLocked;
NSLog(#"setting AVCaptureWhiteBalanceModeLocked");
}
// set the gains
AVCaptureWhiteBalanceGains gains;
gains.redGain = 1.0;
gains.greenGain = 1.0;
gains.blueGain = 1.0;
AVCaptureWhiteBalanceGains normalizedGains = [self normalizedGains:gains];
[currentCaptureDevice setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains:normalizedGains completionHandler:nil];
NSLog(#"setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains g.red=%.2lf g.green=%.2lf g.blue=%.2lf",
normalizedGains.redGain, normalizedGains.greenGain, normalizedGains.blueGain);
[currentCaptureDevice unlockForConfiguration];
}
[captureSession commitConfiguration];
}
}
- (void)captureStillImage
{
NSLog(#"about to request a capture from: %#", [self stillImageOutput]);
if ( videoConnection ) {
waitingForCapture = true;
NSLog(#"requesting a capture from: %#", [self stillImageOutput]);
[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection
completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
if ( imageSampleBuffer ) {
CFDictionaryRef exifAttachments = CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
if (exifAttachments) {
NSLog(#"attachements: %#", exifAttachments);
} else {
NSLog(#"no attachments");
}
NSLog(#"name: %#", [currentCaptureDevice localizedName]);
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
UIImage *image = [[UIImage alloc] initWithData:imageData];
[self setStillImage:image];
NSDictionary *dict = (__bridge NSDictionary*)exifAttachments;
NSString *value = [dict objectForKey:#"PixelXDimension"];
[self setImageWidth:[NSNumber numberWithInt:[value intValue]]];
value = [dict objectForKey:#"PixelYDimension"];
[self setImageHeight:[NSNumber numberWithInt:[value intValue]]];
value = [dict objectForKey:#"BrightnessValue"];
[self setImageBrightnessValue:[NSNumber numberWithFloat:[value floatValue]]];
value = [dict objectForKey:#"ShutterSpeedValue"];
double val = [value doubleValue];
val = 1.0 / pow(2.0, val);
[self setImageExposureTime:[NSNumber numberWithDouble:val]];
value = [dict objectForKey:#"ApertureValue"];
[self setImageApertureValue:[NSNumber numberWithFloat:[value floatValue]]];
NSArray *values = [dict objectForKey:#"ISOSpeedRatings"];
[self setImageISOSpeedRatings:[NSNumber numberWithInt:[ [values objectAtIndex:0] intValue]]];
NSLog(#"r/g/b gains = %.2lf/%.2lf/%.2lf",
currentCaptureDevice.deviceWhiteBalanceGains.redGain,
currentCaptureDevice.deviceWhiteBalanceGains.greenGain,
currentCaptureDevice.deviceWhiteBalanceGains.blueGain);
[[NSNotificationCenter defaultCenter] postNotificationName:kImageCapturedSuccessfully object:nil];
} else {
NSLog(#"imageSampleBuffer = NULL");
}
waitingForCapture = false;
}];
} else {
NSLog(#"can't capture from: videoConnection");
}
}
I answered your other question, same thing, you are not setting the CustomSettings mode:
[device lockForConfiguration:nil];
if([device isExposureModeSupported:AVCaptureExposureModeCustom]){
[device setExposureMode:AVCaptureExposureModeCustom];
[device setExposureModeCustomWithDuration:exposureTime ISO:exposureISO completionHandler:^(CMTime syncTime) {}];
[device setExposureTargetBias:exposureBIAS completionHandler:^(CMTime syncTime) {}];
}
Regards.
I added:
[device setExposureMode:AVCaptureExposureModeCustom];
and changed my completionHandler from nil to:
completionHandler:^(CMTime syncTime) {}
and I still see the same results. It seems that ISO can only be set to the "standard" values and not to any value between minISO and maxISO.
I am trying to create a profile inside my application to show only his twitter profile. So far I do have the time line working but I don't have the number counter for the tweets,followers and following. I'm pretty sure I can use something what my code looks like but just don't know how, any help? Thanks
Code:
STTwitterAPI *twitter = [STTwitterAPI twitterAPIAppOnlyWithConsumerKey:#"ConsumerKey"
consumerSecret:#"consumerSecret"];
[twitter verifyCredentialsWithSuccessBlock:^(NSString *bearerToken) {
[twitter getUserTimelineWithScreenName:#"MikesiOSHelp"
successBlock:^(NSArray *statuses) {
self.twitterFeed = [NSMutableArray arrayWithArray:statuses];
[self->tableView reloadData];
} errorBlock:^(NSError *error) {
NSLog(#"%#", error.debugDescription);
}];
} errorBlock:^(NSError *error) {
NSLog(#"%#", error.debugDescription);
}];
You should check out their demo application.
Your request for finding the followers of a user is explicitly covered in the CLI demo:
typedef void (^AllFollowersBlock_t)(NSArray *allFollowers);
void getFollowers(STTwitterAPI *twitter,
NSString *screenName,
NSMutableArray *followers,
NSString *cursor,
AllFollowersBlock_t allFollowersBlock) {
if(followers == nil) followers = [NSMutableArray array];
NSMutableDictionary *md = [NSMutableDictionary dictionary];
md[#"screen_name"] = screenName;
if(cursor) md[#"cursor"] = cursor;
md[#"skip_status"] = #"1";
md[#"include_user_entities"] = #"0";
[twitter getResource:#"followers/list.json"
baseURLString:kBaseURLStringAPI_1_1
parameters:md
downloadProgressBlock:^(id json) {
//
} successBlock:^(NSDictionary *rateLimits, id response) {
NSArray *users = nil;
NSString *previousCursor = nil;
NSString *nextCursor = nil;
if([response isKindOfClass:[NSDictionary class]]) {
users = [response valueForKey:#"users"];
previousCursor = [response valueForKey:#"previous_cursor_str"];
nextCursor = [response valueForKey:#"next_cursor_str"];
}
NSLog(#"-- users: %#", #([users count]));
NSLog(#"-- previousCursor: %#", previousCursor);
NSLog(#"-- nextCursor: %#", nextCursor);
[followers addObjectsFromArray:users];
if([nextCursor integerValue] == 0) {
allFollowersBlock(followers);
return;
}
/**/
NSString *remainingString = [rateLimits objectForKey:#"x-rate-limit-remaining"];
NSString *resetString = [rateLimits objectForKey:#"x-rate-limit-reset"];
NSInteger remainingInteger = [remainingString integerValue];
NSInteger resetInteger = [resetString integerValue];
NSTimeInterval timeInterval = 0;
if(remainingInteger == 0) {
NSDate *resetDate = [[NSDate alloc] initWithTimeIntervalSince1970:resetInteger];
timeInterval = [resetDate timeIntervalSinceDate:[NSDate date]] + 5;
}
NSLog(#"-- wait for %# seconds", #(timeInterval));
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
getFollowers(twitter, screenName, followers, nextCursor, allFollowersBlock);
});
} errorBlock:^(NSError *error) {
NSLog(#"-- error: %#", error);
}];
}
I want to change the container of .mov video files that I pick using
UIImagePickerController and compressed them via AVAssetExportSession with AVAssetExportPresetMediumQuality and shouldOptimizeForNetworkUse = YES to .mp4 container.
I need programmatically way/sample code to perform a fastest trans-wrap on iPhone/iPad application
I tried to set AVAssetExportSession.outputFileType property to AVFileTypeMPEG4 but it is not supported and I got an exception.
I tried to do this transform using AVAssetWriter by specifying fileType:AVFileTypeMPEG4, actually I got .mp4 output file, but it was not wrap-trans, the output file was 3x bigger than source, and the convert process took 128 sec for video with 60 sec duration.
I need solution that will run quickly and will keep the file size
This is the code I use to convert .mov to .mp4:
I set assetWriter options on setUpReaderAndWriterReturningError method
#import "MCVideoConverter.h"
#import <AVFoundation/AVAsset.h>
#import <AVFoundation/AVAssetTrack.h>
#import <AVFoundation/AVAssetReader.h>
#import <AVFoundation/AVAssetReaderOutput.h>
#import <AVFoundation/AVAssetWriter.h>
#import <AVFoundation/AVAssetWriterInput.h>
#import <AVFoundation/AVMediaFormat.h>
#import <AVFoundation/AVAudioSettings.h>
#import <AVFoundation/AVVideoSettings.h>
#import <AVFoundation/AVAssetImageGenerator.h>
#import <AVFoundation/AVTime.h>
#import <CoreMedia/CMSampleBuffer.h>
#protocol RWSampleBufferChannelDelegate;
#interface RWSampleBufferChannel : NSObject
{
#private
AVAssetReaderOutput *assetReaderOutput;
AVAssetWriterInput *assetWriterInput;
dispatch_block_t completionHandler;
dispatch_queue_t serializationQueue;
BOOL finished; // only accessed on serialization queue
}
- (id)initWithAssetReaderOutput:(AVAssetReaderOutput *)assetReaderOutput assetWriterInput:(AVAssetWriterInput *)assetWriterInput;
- (void)startWithDelegate:(id <RWSampleBufferChannelDelegate>)delegate completionHandler:(dispatch_block_t)completionHandler; // delegate is retained until completion handler is called. Completion handler is guaranteed to be called exactly once, whether reading/writing finishes, fails, or is cancelled. Delegate may be nil.
- (void)cancel;
#property (nonatomic, readonly) NSString *mediaType;
#end
#protocol RWSampleBufferChannelDelegate <NSObject>
#required
- (void)sampleBufferChannel:(RWSampleBufferChannel *)sampleBufferChannel didReadSampleBuffer:(CMSampleBufferRef)sampleBuffer;
#end
#interface MCVideoConverter () <RWSampleBufferChannelDelegate>
// These three methods are always called on the serialization dispatch queue
- (BOOL)setUpReaderAndWriterReturningError:(NSError **)outError; // make sure "tracks" key of asset is loaded before calling this
- (BOOL)startReadingAndWritingReturningError:(NSError **)outError;
- (void)readingAndWritingDidFinishSuccessfully:(BOOL)success withError:(NSError *)error;
#end
#implementation MCVideoConverter
+ (NSArray *)readableTypes
{
return [AVURLAsset audiovisualTypes];;
}
+ (BOOL)canConcurrentlyReadDocumentsOfType:(NSString *)typeName
{
return YES;
}
- (id)init
{
self = [super init];
if (self)
{
NSString *serializationQueueDescription = [NSString stringWithFormat:#"%# serialization queue", self];
serializationQueue = dispatch_queue_create([serializationQueueDescription UTF8String], NULL);
}
return self;
}
- (void)dealloc
{
[asset release];
[outputURL release];
[assetReader release];
[assetWriter release];
[audioSampleBufferChannel release];
[videoSampleBufferChannel release];
if (serializationQueue)
dispatch_release(serializationQueue);
[super dealloc];
}
#synthesize asset=asset;
#synthesize timeRange=timeRange;
#synthesize writingSamples=writingSamples;
#synthesize outputURL=outputURL;
#synthesize propgerssView;
- (void)convertVideo:(NSURL*) inputURL outputURL: (NSURL*) _outputURL progress:(UIProgressView*) _propgerssView
{
self.asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
self.propgerssView = _propgerssView;
cancelled = NO;
[self performSelector:#selector(startProgressSheetWithURL:) withObject:_outputURL afterDelay:0.0]; // avoid starting a new sheet while in
}
- (void)startProgressSheetWithURL:(NSURL *)localOutputURL
{
[self setOutputURL:localOutputURL];
[self setWritingSamples:YES];
AVAsset *localAsset = [self asset];
[localAsset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObjects:#"tracks", #"duration", nil] completionHandler:^
{
// Dispatch the setup work to the serialization queue, to ensure this work is serialized with potential cancellation
dispatch_async(serializationQueue, ^{
// Since we are doing these things asynchronously, the user may have already cancelled on the main thread. In that case, simply return from this block
if (cancelled)
return;
BOOL success = YES;
NSError *localError = nil;
success = ([localAsset statusOfValueForKey:#"tracks" error:&localError] == AVKeyValueStatusLoaded);
if (success)
success = ([localAsset statusOfValueForKey:#"duration" error:&localError] == AVKeyValueStatusLoaded);
if (success)
{
[self setTimeRange:CMTimeRangeMake(kCMTimeZero, [localAsset duration])];
// AVAssetWriter does not overwrite files for us, so remove the destination file if it already exists
NSFileManager *fm = [NSFileManager defaultManager];
NSString *localOutputPath = [localOutputURL path];
if ([fm fileExistsAtPath:localOutputPath])
success = [fm removeItemAtPath:localOutputPath error:&localError];
}
// Set up the AVAssetReader and AVAssetWriter, then begin writing samples or flag an error
if (success)
success = [self setUpReaderAndWriterReturningError:&localError];
if (success)
success = [self startReadingAndWritingReturningError:&localError];
if (!success)
[self readingAndWritingDidFinishSuccessfully:success withError:localError];
});
}];
}
- (BOOL)setUpReaderAndWriterReturningError:(NSError **)outError
{
BOOL success = YES;
NSError *localError = nil;
AVAsset *localAsset = [self asset];
NSURL *localOutputURL = [self outputURL];
// Create asset reader and asset writer
assetReader = [[AVAssetReader alloc] initWithAsset:asset error:&localError];
success = (assetReader != nil);
if (success)
{
//changed assetWriter = [[AVAssetWriter alloc] initWithURL:localOutputURL fileType:AVFileTypeQuickTimeMovie error:&localError];
assetWriter = [[AVAssetWriter alloc] initWithURL:localOutputURL fileType:AVFileTypeMPEG4 error:&localError];
success = (assetWriter != nil);
}
// Create asset reader outputs and asset writer inputs for the first audio track and first video track of the asset
if (success)
{
AVAssetTrack *audioTrack = nil, *videoTrack = nil;
// Grab first audio track and first video track, if the asset has them
NSArray *audioTracks = [localAsset tracksWithMediaType:AVMediaTypeAudio];
if ([audioTracks count] > 0)
audioTrack = [audioTracks objectAtIndex:0];
NSArray *videoTracks = [localAsset tracksWithMediaType:AVMediaTypeVideo];
if ([videoTracks count] > 0)
videoTrack = [videoTracks objectAtIndex:0];
if (audioTrack)
{
// Decompress to Linear PCM with the asset reader
NSDictionary *decompressionAudioSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM], AVFormatIDKey,
nil];
AVAssetReaderOutput *output = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:decompressionAudioSettings];
[assetReader addOutput:output];
AudioChannelLayout stereoChannelLayout = {
.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo,
.mChannelBitmap = 0,
.mNumberChannelDescriptions = 0
};
NSData *channelLayoutAsData = [NSData dataWithBytes:&stereoChannelLayout length:offsetof(AudioChannelLayout, mChannelDescriptions)];
// Compress to 128kbps AAC with the asset writer
NSDictionary *compressionAudioSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:kAudioFormatMPEG4AAC], AVFormatIDKey,
[NSNumber numberWithInteger:128000], AVEncoderBitRateKey,
[NSNumber numberWithInteger:44100], AVSampleRateKey,
channelLayoutAsData, AVChannelLayoutKey,
[NSNumber numberWithUnsignedInteger:2], AVNumberOfChannelsKey,
nil];
AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:[audioTrack mediaType] outputSettings:compressionAudioSettings];
[assetWriter addInput:input];
// Create and save an instance of RWSampleBufferChannel, which will coordinate the work of reading and writing sample buffers
audioSampleBufferChannel = [[RWSampleBufferChannel alloc] initWithAssetReaderOutput:output assetWriterInput:input];
}
if (videoTrack)
{
// Decompress to ARGB with the asset reader
NSDictionary *decompressionVideoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32ARGB], (id)kCVPixelBufferPixelFormatTypeKey,
[NSDictionary dictionary], (id)kCVPixelBufferIOSurfacePropertiesKey,
nil];
AVAssetReaderOutput *output = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoTrack outputSettings:decompressionVideoSettings];
[assetReader addOutput:output];
// Get the format description of the track, to fill in attributes of the video stream that we don't want to change
CMFormatDescriptionRef formatDescription = NULL;
NSArray *formatDescriptions = [videoTrack formatDescriptions];
if ([formatDescriptions count] > 0)
formatDescription = (CMFormatDescriptionRef)[formatDescriptions objectAtIndex:0];
// Grab track dimensions from format description
CGSize trackDimensions = {
.width = 0.0,
.height = 0.0,
};
if (formatDescription)
trackDimensions = CMVideoFormatDescriptionGetPresentationDimensions(formatDescription, false, false);
else
trackDimensions = [videoTrack naturalSize];
// Grab clean aperture, pixel aspect ratio from format description
NSMutableDictionary *compressionSettings = nil;
// [NSMutableDictionary dictionaryWithObjectsAndKeys:
// AVVideoProfileLevelH264Baseline30, AVVideoProfileLevelKey,
// [NSNumber numberWithInt:960000], AVVideoAverageBitRateKey,
// [NSNumber numberWithInt:1],AVVideoMaxKeyFrameIntervalKey,
// nil ];
//NSDictionary *videoSettings = nil;
if (formatDescription)
{
NSDictionary *cleanAperture = nil;
NSDictionary *pixelAspectRatio = nil;
CFDictionaryRef cleanApertureFromCMFormatDescription = CMFormatDescriptionGetExtension(formatDescription, kCMFormatDescriptionExtension_CleanAperture);
if (cleanApertureFromCMFormatDescription)
{
cleanAperture = [NSDictionary dictionaryWithObjectsAndKeys:
CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureWidth), AVVideoCleanApertureWidthKey,
CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureHeight), AVVideoCleanApertureHeightKey,
CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureHorizontalOffset), AVVideoCleanApertureHorizontalOffsetKey,
CFDictionaryGetValue(cleanApertureFromCMFormatDescription, kCMFormatDescriptionKey_CleanApertureVerticalOffset), AVVideoCleanApertureVerticalOffsetKey,
nil];
}
CFDictionaryRef pixelAspectRatioFromCMFormatDescription = CMFormatDescriptionGetExtension(formatDescription, kCMFormatDescriptionExtension_PixelAspectRatio);
if (pixelAspectRatioFromCMFormatDescription)
{
pixelAspectRatio = [NSDictionary dictionaryWithObjectsAndKeys:
CFDictionaryGetValue(pixelAspectRatioFromCMFormatDescription, kCMFormatDescriptionKey_PixelAspectRatioHorizontalSpacing), AVVideoPixelAspectRatioHorizontalSpacingKey,
CFDictionaryGetValue(pixelAspectRatioFromCMFormatDescription, kCMFormatDescriptionKey_PixelAspectRatioVerticalSpacing), AVVideoPixelAspectRatioVerticalSpacingKey,
nil];
}
if (cleanAperture || pixelAspectRatio)
{
if (cleanAperture)
[compressionSettings setObject:cleanAperture forKey:AVVideoCleanApertureKey];
if (pixelAspectRatio)
[compressionSettings setObject:pixelAspectRatio forKey:AVVideoPixelAspectRatioKey];
}
}
// Compress to H.264 with the asset writer
NSMutableDictionary *videoSettings = [NSMutableDictionary dictionaryWithObjectsAndKeys:
AVVideoCodecH264, AVVideoCodecKey,
[NSNumber numberWithDouble:trackDimensions.width], AVVideoWidthKey,
[NSNumber numberWithDouble:trackDimensions.height], AVVideoHeightKey,
nil];
if (compressionSettings)
[videoSettings setObject:compressionSettings forKey:AVVideoCompressionPropertiesKey];
AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:[videoTrack mediaType] outputSettings:videoSettings];
[assetWriter addInput:input];
// Create and save an instance of RWSampleBufferChannel, which will coordinate the work of reading and writing sample buffers
videoSampleBufferChannel = [[RWSampleBufferChannel alloc] initWithAssetReaderOutput:output assetWriterInput:input];
}
}
if (outError)
*outError = localError;
return success;
}
- (BOOL)startReadingAndWritingReturningError:(NSError **)outError
{
BOOL success = YES;
NSError *localError = nil;
// Instruct the asset reader and asset writer to get ready to do work
success = [assetReader startReading];
if (!success)
localError = [assetReader error];
if (success)
{
success = [assetWriter startWriting];
if (!success)
localError = [assetWriter error];
}
if (success)
{
dispatch_group_t dispatchGroup = dispatch_group_create();
// Start a sample-writing session
[assetWriter startSessionAtSourceTime:[self timeRange].start];
// Start reading and writing samples
if (audioSampleBufferChannel)
{
// Only set audio delegate for audio-only assets, else let the video channel drive progress
id <RWSampleBufferChannelDelegate> delegate = nil;
if (!videoSampleBufferChannel)
delegate = self;
dispatch_group_enter(dispatchGroup);
[audioSampleBufferChannel startWithDelegate:delegate completionHandler:^{
dispatch_group_leave(dispatchGroup);
}];
}
if (videoSampleBufferChannel)
{
dispatch_group_enter(dispatchGroup);
[videoSampleBufferChannel startWithDelegate:self completionHandler:^{
dispatch_group_leave(dispatchGroup);
}];
}
// Set up a callback for when the sample writing is finished
dispatch_group_notify(dispatchGroup, serializationQueue, ^{
BOOL finalSuccess = YES;
NSError *finalError = nil;
if (cancelled)
{
[assetReader cancelReading];
[assetWriter cancelWriting];
}
else
{
if ([assetReader status] == AVAssetReaderStatusFailed)
{
finalSuccess = NO;
finalError = [assetReader error];
}
if (finalSuccess)
{
finalSuccess = [assetWriter finishWriting];
if (!finalSuccess)
finalError = [assetWriter error];
}
}
[self readingAndWritingDidFinishSuccessfully:finalSuccess withError:finalError];
});
dispatch_release(dispatchGroup);
}
if (outError)
*outError = localError;
return success;
}
- (void)cancel
{
self.propgerssView = nil;
// Dispatch cancellation tasks to the serialization queue to avoid races with setup and teardown
dispatch_async(serializationQueue, ^{
[audioSampleBufferChannel cancel];
[videoSampleBufferChannel cancel];
cancelled = YES;
});
}
- (void)readingAndWritingDidFinishSuccessfully:(BOOL)success withError:(NSError *)error
{
NSLog(#"%s[%d] - success = %d error = %#", __FUNCTION__, __LINE__, success, error);
if (!success)
{
[assetReader cancelReading];
[assetWriter cancelWriting];
}
// Tear down ivars
[assetReader release];
assetReader = nil;
[assetWriter release];
assetWriter = nil;
[audioSampleBufferChannel release];
audioSampleBufferChannel = nil;
[videoSampleBufferChannel release];
videoSampleBufferChannel = nil;
cancelled = NO;
// Dispatch UI-related tasks to the main queue
dispatch_async(dispatch_get_main_queue(), ^{
if (!success)
{
}
[self setWritingSamples:NO];
});
}
static double progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, CMTimeRange timeRange)
{
CMTime progressTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
progressTime = CMTimeSubtract(progressTime, timeRange.start);
CMTime sampleDuration = CMSampleBufferGetDuration(sampleBuffer);
if (CMTIME_IS_NUMERIC(sampleDuration))
progressTime= CMTimeAdd(progressTime, sampleDuration);
return CMTimeGetSeconds(progressTime) / CMTimeGetSeconds(timeRange.duration);
}
static void removeARGBColorComponentOfPixelBuffer(CVPixelBufferRef pixelBuffer, size_t componentIndex)
{
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
size_t bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
size_t bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
static const size_t bytesPerPixel = 4; // constant for ARGB pixel format
unsigned char *base = (unsigned char *)CVPixelBufferGetBaseAddress(pixelBuffer);
for (size_t row = 0; row < bufferHeight; ++row)
{
for (size_t column = 0; column < bufferWidth; ++column)
{
unsigned char *pixel = base + (row * bytesPerRow) + (column * bytesPerPixel);
pixel[componentIndex] = 0;
}
}
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
}
+ (size_t)componentIndexFromFilterTag:(NSInteger)filterTag
{
return (size_t)filterTag; // we set up the tags in the popup button to correspond directly with the index they modify
}
- (void)sampleBufferChannel:(RWSampleBufferChannel *)sampleBufferChannel didReadSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
CVPixelBufferRef pixelBuffer = NULL;
// Calculate progress (scale of 0.0 to 1.0)
double progress = progressOfSampleBufferInTimeRange(sampleBuffer, [self timeRange]);
NSLog(#"%s[%d] - progress = %f", __FUNCTION__, __LINE__, progress);
// Grab the pixel buffer from the sample buffer, if possible
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (imageBuffer && (CFGetTypeID(imageBuffer) == CVPixelBufferGetTypeID()))
{
pixelBuffer = (CVPixelBufferRef)imageBuffer;
if (filterTag >= 0) // -1 means "no filtering, please"
removeARGBColorComponentOfPixelBuffer(pixelBuffer, [[self class] componentIndexFromFilterTag:filterTag]);
}
}
#end
#interface RWSampleBufferChannel ()
- (void)callCompletionHandlerIfNecessary; // always called on the serialization queue
#end
#implementation RWSampleBufferChannel
- (id)initWithAssetReaderOutput:(AVAssetReaderOutput *)localAssetReaderOutput assetWriterInput:(AVAssetWriterInput *)localAssetWriterInput
{
self = [super init];
if (self)
{
assetReaderOutput = [localAssetReaderOutput retain];
assetWriterInput = [localAssetWriterInput retain];
finished = NO;
NSString *serializationQueueDescription = [NSString stringWithFormat:#"%# serialization queue", self];
serializationQueue = dispatch_queue_create([serializationQueueDescription UTF8String], NULL);
}
return self;
}
- (void)dealloc
{
[assetReaderOutput release];
[assetWriterInput release];
if (serializationQueue)
dispatch_release(serializationQueue);
[completionHandler release];
[super dealloc];
}
- (NSString *)mediaType
{
return [assetReaderOutput mediaType];
}
- (void)startWithDelegate:(id <RWSampleBufferChannelDelegate>)delegate completionHandler:(dispatch_block_t)localCompletionHandler
{
completionHandler = [localCompletionHandler copy]; // released in -callCompletionHandlerIfNecessary
[assetWriterInput requestMediaDataWhenReadyOnQueue:serializationQueue usingBlock:^{
if (finished)
return;
BOOL completedOrFailed = NO;
// Read samples in a loop as long as the asset writer input is ready
while ([assetWriterInput isReadyForMoreMediaData] && !completedOrFailed)
{
CMSampleBufferRef sampleBuffer = [assetReaderOutput copyNextSampleBuffer];
if (sampleBuffer != NULL)
{
if ([delegate respondsToSelector:#selector(sampleBufferChannel:didReadSampleBuffer:)])
[delegate sampleBufferChannel:self didReadSampleBuffer:sampleBuffer];
BOOL success = [assetWriterInput appendSampleBuffer:sampleBuffer];
CFRelease(sampleBuffer);
sampleBuffer = NULL;
completedOrFailed = !success;
}
else
{
completedOrFailed = YES;
}
}
if (completedOrFailed)
[self callCompletionHandlerIfNecessary];
}];
}
- (void)cancel
{
dispatch_async(serializationQueue, ^{
[self callCompletionHandlerIfNecessary];
});
}
- (void)callCompletionHandlerIfNecessary
{
// Set state to mark that we no longer need to call the completion handler, grab the completion handler, and clear out the ivar
BOOL oldFinished = finished;
finished = YES;
if (oldFinished == NO)
{
[assetWriterInput markAsFinished]; // let the asset writer know that we will not be appending any more samples to this input
dispatch_block_t localCompletionHandler = [completionHandler retain];
[completionHandler release];
completionHandler = nil;
if (localCompletionHandler)
{
localCompletionHandler();
[localCompletionHandler release];
}
}
}
#end
Hey It was for a long while, but I end up with good solution and it may help someone in future
my code:
-(void) compressVideo
{
asset = [[AVURLAsset alloc] initWithURL:videoUrl options:nil];
exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetPassthrough];
NSLog(#" %#", [AVAssetExportSession exportPresetsCompatibleWithAsset:asset]);
NSLog(#" %#", exportSession.supportedFileTypes);
NSLog(#"----------------------------------------- convert to mp4");
NSLog(#" %#", exportSession.supportedFileTypes);
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.outputURL = [self outputVideoPath:#"outPut" ext:#"mp4"];
[exportSession exportAsynchronouslyWithCompletionHandler:^{
ICQLog(#" exportSession.status = %d exportSession.error = %#", exportSession.status, exportSession.error);
if ( exportSession && (exportSession.status == AVAssetExportSessionStatusCompleted) )
{
ICQLog(#" exportSession.outputURL = %#", exportSession.outputURL);
// we need to remove temporary files
[[NSFileManager defaultManager] removeItemAtURL:videoUrl error:NULL];
[videoUrl release];
videoUrl = [exportSession.outputURL retain];
}
else
{
//TODO - report error
}
[exportSession release], exportSession = nil;
[asset release], asset = nil;
}];
I can't help with the trans-wrap stuff, I haven't got my head into this.
Is the main priority to get the file output as a .mp4 without having to reprocess it? If it is then just use .mp4 as the file extension of the movie clip that was output by you code and this should work fine. I have used this approach today and it works. i didn't have to convert it from .mov to .mp4 because essentially a .mp4 file is the same as a .mov file with some additional standards based functionality.
Hope this is of help.
This is the code I used.
(BOOL)encodeVideo:(NSURL *)videoURL
{
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
// Create the composition and tracks
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableCompositionTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
NSArray *assetVideoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
if (assetVideoTracks.count <= 0)
{
NSLog(#"Error reading the transformed video track");
return NO;
}
// Insert the tracks in the composition's tracks
AVAssetTrack *assetVideoTrack = [assetVideoTracks firstObject];
[videoTrack insertTimeRange:assetVideoTrack.timeRange ofTrack:assetVideoTrack atTime:CMTimeMake(0, 1) error:nil];
[videoTrack setPreferredTransform:assetVideoTrack.preferredTransform];
AVAssetTrack *assetAudioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
[audioTrack insertTimeRange:assetAudioTrack.timeRange ofTrack:assetAudioTrack atTime:CMTimeMake(0, 1) error:nil];
// Export to mp4
NSString *mp4Quality = [MGPublic isIOSAbove:#"6.0"] ? AVAssetExportPresetMediumQuality : AVAssetExportPresetPassthrough;
NSString *exportPath = [NSString stringWithFormat:#"%#/%#.mp4",
[NSHomeDirectory() stringByAppendingString:#"/tmp"],
[BSCommon uuidString]];
NSURL *exportUrl = [NSURL fileURLWithPath:exportPath];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:mp4Quality];
exportSession.outputURL = exportUrl;
CMTime start = CMTimeMakeWithSeconds(0.0, 0);
CMTimeRange range = CMTimeRangeMake(start, [asset duration]);
exportSession.timeRange = range;
exportSession.outputFileType = AVFileTypeMPEG4;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status])
{
case AVAssetExportSessionStatusCompleted:
NSLog(#"MP4 Successful!");
break;
case AVAssetExportSessionStatusFailed:
NSLog(#"Export failed: %#", [[exportSession error] localizedDescription]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Export canceled");
break;
default:
break;
}
}];
return YES;
}