2023-08-23

解決視頻轉(zhuǎn)webp方案

分為兩步:1.視頻剪輯后分幀數(shù). 2:圖片數(shù)組添加到webp

#pragma mark 裁剪視頻
/// 裁剪視頻
+ (void)cutVideoAndExportVideoWithVideoAsset:(AVAsset *)videoAsset startTime:(CGFloat)startTime endTime:(CGFloat)endTime completion:(void (^)(NSURL *outputPath, NSError *error, ST_VideoState state))completion
{
    NSError *error;
    //1 創(chuàng)建AVMutableComposition對(duì)象來添加視頻音頻資源的AVMutableCompositionTrack
    AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
    // 2 設(shè)置采集區(qū)域
    CGSize videoRange = CGSizeMake(startTime, (endTime - startTime)); // 開始位置, 裁剪的長度
    CMTime startT = CMTimeMakeWithSeconds(videoRange.width, videoAsset.duration.timescale);
    CMTime videoDuration = CMTimeMakeWithSeconds(videoRange.height, videoAsset.duration.timescale); //截取長度videoDuration
    CMTimeRange timeRange = CMTimeRangeMake(startT, videoDuration);
    
    // 3 - 視頻通道  工程文件中的軌道洛口,有音頻軌问拘、視頻軌等蒸矛,里面可以插入各種對(duì)應(yīng)的素材
    AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
                                                                        preferredTrackID:kCMPersistentTrackID_Invalid];
    /*TimeRange截取的范圍長度   ofTrack來源  atTime插放在視頻的時(shí)間位置*/
    [videoTrack insertTimeRange:timeRange
                        ofTrack:([videoAsset tracksWithMediaType:AVMediaTypeVideo].count > 0) ? [videoAsset tracksWithMediaType:AVMediaTypeVideo].firstObject : nil
                         atTime:kCMTimeZero
                          error:&error];
    
    
    // 3 - 導(dǎo)出視頻 - 返回?cái)?shù)字包含了 AVAssetExportPreset1280x720多個(gè)這樣的數(shù)組
    AVMutableVideoComposition *videoComposition = [self fixedCompositionWithAsset:videoAsset];
    
    [self _getExportVideoWithAvAssset:mixComposition videoComposition:videoComposition audioMix:nil timeRange:timeRange completion:completion cut:YES];
}
/// 獲取優(yōu)化后的視頻轉(zhuǎn)向信息
+ (AVMutableVideoComposition *)fixedCompositionWithAsset:(AVAsset *)videoAsset
{
    //1,可以用來對(duì)視頻進(jìn)行操作,用來生成video的組合指令柿汛,包含多段instruction
    AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
    // 視頻轉(zhuǎn)向
    int degrees = [self degressFromVideoFileWithAsset:videoAsset];
    CGAffineTransform translateToCenter;
    CGAffineTransform mixedTransform;
    videoComposition.frameDuration = CMTimeMake(1, 30);
    
    NSArray *tracks = [videoAsset tracksWithMediaType:AVMediaTypeVideo];
    AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
    // 一個(gè)指令趟据,決定一個(gè)timeRange內(nèi)每個(gè)軌道的狀態(tài)券犁,包含多個(gè)layerInstruction
    AVMutableVideoCompositionInstruction *roateInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    roateInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, [videoAsset duration]);
    // 在一個(gè)指令的時(shí)間范圍內(nèi),某個(gè)軌道的狀態(tài)
    AVMutableVideoCompositionLayerInstruction *roateLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
    
    if (degrees == 90) // UIImageOrientationRight
    {
        // 順時(shí)針旋轉(zhuǎn)90°
        translateToCenter = CGAffineTransformMakeTranslation(videoTrack.naturalSize.height, 0.0);
        mixedTransform = CGAffineTransformRotate(translateToCenter, M_PI_2);
        videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.height, videoTrack.naturalSize.width);
        [roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
    }
    else if (degrees == 180) // UIImageOrientationDown
    {
        // 順時(shí)針旋轉(zhuǎn)180°
        translateToCenter = CGAffineTransformMakeTranslation(videoTrack.naturalSize.width, videoTrack.naturalSize.height);
        mixedTransform = CGAffineTransformRotate(translateToCenter, M_PI);
        videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.width, videoTrack.naturalSize.height);
        [roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
    }
    else if (degrees == 270) // UIImageOrientationLeft
    {
        // 順時(shí)針旋轉(zhuǎn)270°
        translateToCenter = CGAffineTransformMakeTranslation(0.0, videoTrack.naturalSize.width);
        mixedTransform = CGAffineTransformRotate(translateToCenter, M_PI_2 * 3.0);
        videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.height, videoTrack.naturalSize.width);
        [roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
    }
    // 方向是 0 不做處理
    
    roateInstruction.layerInstructions = @[roateLayerInstruction];
    videoComposition.instructions = @[roateInstruction]; // 加入視頻方向信息
    
    return videoComposition;
}

/// 設(shè)置導(dǎo)出對(duì)象
+ (void)_getExportVideoWithAvAssset:(AVAsset *)videoAsset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix timeRange:(CMTimeRange)timeRange completion:(void (^)(NSURL *outputPath, NSError *error, ST_VideoState state))completion cut:(BOOL)isCut
{
    NSURL *outputURL = [self _getExportVideoPathForType:@"mp4"]; // 創(chuàng)建輸出的路徑
    NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:videoAsset];
    if ([compatiblePresets containsObject:AVAssetExportPresetHighestQuality])
    {
        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
                                               initWithAsset:videoAsset
                                               presetName:AVAssetExportPresetHighestQuality]; //AVAssetExportPresetPassthrough可能返回沒有處理過的視頻
        if (!isCut)
        {
            exportSession.timeRange = timeRange; //截取時(shí)間---直接導(dǎo)出的方法只能從0開始
        }
        if (videoComposition.renderSize.width)
        {                                                      // 注意方向是 0 不要做處理否則會(huì)導(dǎo)出失敗
            exportSession.videoComposition = videoComposition; // 修正視頻轉(zhuǎn)向
        }
        exportSession.outputURL = outputURL;             // 輸出URL
        exportSession.shouldOptimizeForNetworkUse = YES; // 優(yōu)化網(wǎng)絡(luò)
        exportSession.outputFileType = AVFileTypeQuickTimeMovie;
        NSArray *supportedTypeArray = exportSession.supportedFileTypes; //支持的格式
        if ([supportedTypeArray containsObject:AVFileTypeMPEG4])        //MP4
        {
            exportSession.outputFileType = AVFileTypeMPEG4;
        }
        else if (supportedTypeArray.count == 0)
        {
            NSError *error = [NSError ST_PhotoSDKVideoActionDescription:@"視頻類型暫不支持導(dǎo)出"];
            if (completion)
            {
                completion(nil, error, ST_ExportSessionStatusFailed);
            }
            return;
        }
        else
        {
            exportSession.outputFileType = [supportedTypeArray objectAtIndex:0];
        }
        
        // 開始異步導(dǎo)出視頻
        __block NSError *error;
        
        [exportSession exportAsynchronouslyWithCompletionHandler:^(void) {
            dispatch_async(dispatch_get_main_queue(), ^{
                switch (exportSession.status)
                {
                    case AVAssetExportSessionStatusUnknown:
                    {
                        error = [NSError ST_PhotoSDKVideoActionDescription:@"AVAssetExportSessionStatusUnknown"];
                        if (completion)
                        {
                            completion(nil, error, ST_ExportSessionStatusUnknown);
                        }
                        break;
                    }
                    case AVAssetExportSessionStatusWaiting:
                    {
                        error = [NSError ST_PhotoSDKVideoActionDescription:@"AVAssetExportSessionStatusWaiting"];
                        if (completion)
                        {
                            completion(nil, error, ST_ExportSessionStatusWaiting);
                        }
                        break;
                    }
                    case AVAssetExportSessionStatusExporting:
                    {
                        error = [NSError ST_PhotoSDKVideoActionDescription:@"AVAssetExportSessionStatusExporting"];
                        if (completion)
                        {
                            completion(nil, error, ST_ExportSessionStatusExporting);
                        }
                        break;
                    }
                    case AVAssetExportSessionStatusCompleted:
                    {
                        if (completion)
                        {
                            completion(outputURL, nil, ST_ExportSessionStatusCompleted);
                        }
                        break;
                    }
                    case AVAssetExportSessionStatusFailed:
                    {
                        error = [NSError ST_PhotoSDKVideoActionDescription:[NSString stringWithFormat:@"導(dǎo)出失敗:%@", exportSession.error]];
                        if (completion)
                        {
                            completion(nil, error, ST_ExportSessionStatusFailed);
                        }
                        break;
                    }
                    default:
                        break;
                }
            });
        }];
    }
}

//分幀,合成處理.這里主要為了控制生成webp文件大小內(nèi)容
-(void)saveToWebpByVideoPath:(NSURL *)videoUrl andProgressBlock:(void (^)(float))progressBlock andPathBlock:(void (^)(NSString *))pathBlock{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSString *filePath = [self createWebpFilePath];

        YYImageEncoder *encoder = [[YYImageEncoder alloc] initWithType:YYImageTypeWebP];
        encoder.loopCount = 0;
        encoder.quality = 0.4; // 質(zhì)量設(shè)置為0.5汹碱,減小文件大小
        encoder.lossless = NO; // 使用有損壓縮模式
       
        
        AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoUrl options:nil];
        AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
        generator.appliesPreferredTrackTransform = YES;
        generator.requestedTimeToleranceAfter = kCMTimeZero;
        generator.requestedTimeToleranceBefore = kCMTimeZero;
        generator.maximumSize = CGSizeMake(256, 256); // 設(shè)置最大尺寸為512x512
        generator.apertureMode = AVAssetImageGeneratorApertureModeCleanAperture; // 設(shè)置孔徑模式為清晰孔徑
        
        NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
        
        if ([tracks count] > 0) {
            AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
            float frameRate = [videoTrack nominalFrameRate];
            float duration = CMTimeGetSeconds([videoTrack timeRange].duration);
            int frameCount = (int)(frameRate * duration); ///<---幀數(shù)率 * 時(shí)間
            int maxCount = 20; ///<---最大幀數(shù)  計(jì)算方案,總幀數(shù)大于20,計(jì)算多少幀取一幀
            
            int minTime = frameCount/maxCount;
            // 最后一個(gè)有效幀的索引
            int lastValidFrameIndex = -1;
            int count = 0;
            for (int i = 0; i < frameCount; i++) {
                ///<最大幀數(shù)/時(shí)間 == 每秒最小幀率 , 如果i%最小幀率==0 取
                if(frameCount>20 && i%minTime!=0){
                    continue;
                }
                CMTime time = CMTimeMakeWithSeconds(i / frameRate, asset.duration.timescale);
                NSError *error = nil;
                CGImageRef image = [generator copyCGImageAtTime:time actualTime:nil error:&error];

                if (image) { // 確保圖像不為空
                    UIImage *img = [UIImage imageWithCGImage:image];
                    img = [self resizeImage:img toSize:CGSizeMake(512, 512)];
                    NSData * data = [GIF2MP4 compressImageQualityWithImage:img toByte:10*1024];
    //                NSData *data = [self compressOriginalImage:img toMaxDataSizeKBytes:5];
                    [encoder addImageWithData:data duration:1.0 / frameRate*(minTime/2)];

                    lastValidFrameIndex = i;
                    CGImageRelease(image);
                }
                count++;
                progressBlock(count*0.04);
                if(count>=19){
                    break;
                }
            }
            
            // 如果最后一個(gè)有效幀不是最后一幀粘衬,則將其重復(fù)添加
            if (lastValidFrameIndex >= 0 && lastValidFrameIndex < frameCount - 1) {
                CMTime time = CMTimeMakeWithSeconds(lastValidFrameIndex / frameRate, asset.duration.timescale);
                CGImageRef image = [generator copyCGImageAtTime:time actualTime:nil error:nil];
                if (image) {
                    UIImage *img = [UIImage imageWithCGImage:image];
                    img = [self resizeImage:img toSize:CGSizeMake(512, 512)];
                    NSData * data = [GIF2MP4 compressImageQualityWithImage:img toByte:10*1024];
                    [encoder addImageWithData:data duration:1.0 / frameRate*(minTime/2)];
                    CGImageRelease(image);
                }
                count+=1;
                [SVProgressHUD showProgress:count*0.025 status:@"loadIng"];
                progressBlock(count*0.04);

            }
            DLog(@"data 大小為%ld",[encoder encode].length);
            [encoder encodeToFile:filePath];
            progressBlock(1);
            pathBlock(filePath);
        }
    });
}

記錄一下

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子稚新,更是在濱河造成了極大的恐慌勘伺,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褂删,死亡現(xiàn)場離奇詭異飞醉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)屯阀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門缅帘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人难衰,你說我怎么就攤上這事钦无。” “怎么了盖袭?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵失暂,是天一觀的道長。 經(jīng)常有香客問我鳄虱,道長弟塞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任醇蝴,我火速辦了婚禮宣肚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悠栓。我一直安慰自己霉涨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布惭适。 她就那樣靜靜地躺著笙瑟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪癞志。 梳的紋絲不亂的頭發(fā)上往枷,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音凄杯,去河邊找鬼错洁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛戒突,可吹牛的內(nèi)容都是我干的屯碴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼膊存,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼导而!你這毒婦竟也來了忱叭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤今艺,失蹤者是張志新(化名)和其女友劉穎韵丑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虚缎,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撵彻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了遥巴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片千康。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖铲掐,靈堂內(nèi)的尸體忽然破棺而出拾弃,到底是詐尸還是另有隱情,我是刑警寧澤摆霉,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布豪椿,位于F島的核電站,受9級(jí)特大地震影響携栋,放射性物質(zhì)發(fā)生泄漏搭盾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一婉支、第九天 我趴在偏房一處隱蔽的房頂上張望鸯隅。 院中可真熱鬧,春花似錦向挖、人聲如沸蝌以。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跟畅。三九已至,卻和暖如春溶推,著一層夾襖步出監(jiān)牢的瞬間徊件,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工蒜危, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虱痕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓辐赞,卻偏偏與公主長得像皆疹,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子占拍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 嗯略就,今天先祝自己!結(jié)婚14周年快樂晃酒。 這幾天晚上輾轉(zhuǎn)反側(cè)睡不著表牢,是突然,開始懷念了過去贝次?還是看了房琪的那本《真希望...
    豬不哭的碎碎念閱讀 72評(píng)論 0 2
  • 熱血江湖手游:最全干貨攻略崔兴,從零開始稱霸武林 2023-08-23 10:24 熱血江湖手游公測(cè)有一段時(shí)間了,還有...
    圓夢(mèng)菌閱讀 53評(píng)論 0 0
  • 木瓜分為兩種蛔翅,一種主作藥用敲茄,另外一種可以直接作為水果食用。 番木瓜是長圓形山析,熟時(shí)橙黃色堰燎,果肉厚,內(nèi)壁著生多數(shù)黑色的...
    陽月初柒閱讀 34評(píng)論 0 1
  • 之前試過一次,但是設(shè)置起來感覺比較簡單爵政,這次重裝了系統(tǒng)仅讽,然后可能羅技的軟件也更新了,又找了一會(huì)資料才記起來怎么應(yīng)用...
    nuke_hydrogen閱讀 781評(píng)論 0 0
  • 昨晚回來倒頭就睡钾挟,職稱材料還沒有整完洁灵,今天要抓緊,弄個(gè)差不多掺出。 昨天下午同學(xué)們一下子把日記給交齊了徽千,很開...
    吳怡彤閱讀 63評(píng)論 0 0