上傳到服務(wù)器的圖片乖酬,在電腦上打開發(fā)現(xiàn)都是橫著的
查詢原因發(fā)現(xiàn)因為圖片中有一個Exif頭信息,上傳的時候直接把UIImage轉(zhuǎn)為NSData后它就丟失了。iPhone拍照以橫屏作為正方向,Windows以豎直作為正方向泽艘。偏偏這個方向信息就存儲在這個Exif中,丟失了方向的照片在Windows上就……迷路了 : )
我翻開歷史一查镐依,這歷史沒有年代匹涮,歪歪斜斜的每頁上都寫著“仁義道德”幾個字。我橫豎睡不著槐壳,仔細看了半夜然低,才從字縫里看出字來,滿本都寫著兩個字是“吃人”务唐! —— 魯迅
怎么解決雳攘?
- 在UIImage轉(zhuǎn)換為NSData的時候加入Exif使其不丟失
- 傳輸NSData的時候?qū)⒎较蛞煌瑐魅敕?wù)器,由接收方處理
- 上傳前先讀取方向信息枫笛,將圖片進行旋轉(zhuǎn)
第一條肯定是最好最合理的吨灭,不過Windows上還存在不讀取Exif的照片查看器,并且也著實沒找到相關(guān)方法只能另辟蹊徑了刑巧。
第二條過于復(fù)雜喧兄,而且不僅自己沒處理好問題還影響了接受方的邏輯无畔。
最后只好按第三條,自己處理好再給別人使用繁莹。
將圖片按照正確方向旋轉(zhuǎn)####
圖片很好處理檩互,Graphics在繪制UIImage的時候會讀取Exif繪制出正確的圖像。利用這一點可以用很少的代碼獲取修正后的UIImage咨演。
我們給UIImage寫一個分類來方便使用
- (UIImage *)normalizedImage {
if (self.imageOrientation == UIImageOrientationUp) return self;
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
[self drawInRect:(CGRect){0, 0, self.size}];
UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return normalizedImage;
}
這樣就很簡單地解決了轉(zhuǎn)化為NSData之后圖片Exif信息丟失導(dǎo)致圖片旋轉(zhuǎn)的問題闸昨,然而還沒高興起來就發(fā)現(xiàn)視頻同樣有這個事兒……
在視頻文件的信息中同樣包含一個Rotation信息,它包含了此視頻拍攝時的方向薄风,視頻播放時播放器會讀取它然后用正確的方向播放饵较,如果它丟了
你的產(chǎn)品經(jīng)理會教你如何做人 —— 百里瀲長
萬幸的是
AVAssetExportSession
可以對視頻壓縮轉(zhuǎn)碼,同時可以編輯視頻方向遭赂,正好視頻上傳有壓縮和將wav轉(zhuǎn)碼為mp4的需求循诉,這下可以順帶解決。
視頻轉(zhuǎn)碼壓縮及方向修正####
使用AVAsset
讀取到文件撇他,然后訪問AVAssetTrack
粗暴直接地做一個判斷就可以得到方向
+ (NSUInteger)degressFromVideoFileWithURL:(NSURL *)url {
NSUInteger degress = 0;
AVAsset *asset = [AVAsset assetWithURL:url];
NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
if([tracks count] > 0) {
AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
CGAffineTransform t = videoTrack.preferredTransform;
if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){
// Portrait
degress = 90;
}else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){
// PortraitUpsideDown
degress = 270;
}else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){
// LandscapeRight
degress = 0;
}else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){
// LandscapeLeft
degress = 180;
}
}
return degress;
}
AVAssetExportSession
需要一個AVComposition
茄猫,我們將上面的代碼整合一下,利用獲取到的方向信息來決定視頻大小
- (AVMutableVideoComposition *)getVideoComposition:(AVAsset *)asset {
AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
CGSize videoSize = videoTrack.naturalSize;
NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
if([tracks count] > 0) {
AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
CGAffineTransform t = videoTrack.preferredTransform;
if((t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0) ||
(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0)){
videoSize = CGSizeMake(videoSize.height, videoSize.width);
}
}
composition.naturalSize = videoSize;
videoComposition.renderSize = videoSize;
videoComposition.frameDuration = CMTimeMakeWithSeconds( 1 / videoTrack.nominalFrameRate, 600);
AVMutableCompositionTrack *compositionVideoTrack;
compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:videoTrack atTime:kCMTimeZero error:nil];
AVMutableVideoCompositionLayerInstruction *layerInst;
layerInst = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
[layerInst setTransform:videoTrack.preferredTransform atTime:kCMTimeZero];
AVMutableVideoCompositionInstruction *inst = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
inst.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
inst.layerInstructions = [NSArray arrayWithObject:layerInst];
videoComposition.instructions = [NSArray arrayWithObject:inst];
return videoComposition;
}
最后只需要在壓縮轉(zhuǎn)碼的時候?qū)⑺砣?code>AVAssetExportSession的videoComposition
中困肩,這里我將輸出的視頻文件寫入到Temp下的VideoCompression目錄中划纽,文件名叫VideoCompressionTemp.mp4
- (void)lowQuailtyWithInputURL:(NSURL *)inputURL blockHandler:(void (^)(AVAssetExportSession *session, NSURL *compressionVideoURL))handler {
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetMediumQuality];
NSString *path = [NSString stringWithFormat:@"%@VideoCompression/",NSTemporaryDirectory()];
NSFileManager *fileManage = [[NSFileManager alloc] init]; static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
if(![fileManage fileExistsAtPath:path]){
[fileManage createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
}
});
if([fileManage fileExistsAtPath:[NSString stringWithFormat:@"%@VideoCompressionTemp.mp4",path]]){
[fileManage removeItemAtPath:[NSString stringWithFormat:@"%@VideoCompressionTemp.mp4",path] error:nil];
}
NSURL *compressionVideoURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@VideoCompressionTemp.mp4",path]];
session.outputURL = compressionVideoURL;
session.outputFileType = AVFileTypeMPEG4;
session.shouldOptimizeForNetworkUse = YES;
session.videoComposition = [self getVideoComposition:asset];
[session exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(),^{
switch ([session status]) {
case AVAssetExportSessionStatusFailed:{
NSLog(@"Export failed: %@ : %@", [[session error] localizedDescription], [session error]);
handler(session, nil);
break;
}case AVAssetExportSessionStatusCancelled:{
NSLog(@"Export canceled");
handler(session, nil);
break;
}default:
handler(session,compressionVideoURL);
break;
}
});
}];
}