上傳到服務(wù)器的圖片汁掠,在電腦上打開發(fā)現(xiàn)都是橫著的
查詢原因發(fā)現(xiàn)因為圖片中有一個Exif頭信息潭辈,上傳的時候直接把UIImage轉(zhuǎn)為NSData后它就丟失了匆背。iPhone拍照以橫屏作為正方向者铜,Windows以豎直作為正方向静稻。偏偏這個方向信息就存儲在這個Exif中脚作,丟失了方向的照片在Windows上就……迷路了 : )
我翻開歷史一查葫哗,這歷史沒有年代,歪歪斜斜的每頁上都寫著“仁義道德”幾個字球涛。我橫豎睡不著劣针,仔細看了半夜,才從字縫里看出字來亿扁,滿本都寫著兩個字是“吃人”捺典! —— 魯迅
怎么解決?
1从祝、 在UIImage轉(zhuǎn)換為NSData的時候加入Exif使其不丟失
2襟己、 傳輸NSData的時候?qū)⒎较蛞煌瑐魅敕?wù)器,由接收方處理
3牍陌、 上傳前先讀取方向信息擎浴,將圖片進行旋轉(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ū)⑺砣階VAssetExportSession的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;
}
});
}];
}